@@ -1,9 +1,7 @@
1−{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE LambdaCase #-}
6{-# LANGUAGE LambdaCase #-}
9{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE OverloadedStrings #-}
@@ -231,6 +229,8 @@ fromFilePath ss f = do
231 either (throwM . InvalidFileError f . Text.pack) pure
229 either (throwM . InvalidFileError f . Text.pack) pure
232 (fromJSONCredentials ss bs)
230 (fromJSONCredentials ss bs)
232+-- | Attempt to parse either a @service_account@ or @authorized_user@ formatted
233+-- JSON value to obtain credentials.
234fromJSONCredentials :: [OAuthScope]
234fromJSONCredentials :: [OAuthScope]
235 -> LBS.ByteString
235 -> LBS.ByteString
236 -> Either String Credentials
236 -> Either String Credentials
@@ -244,57 +244,80 @@ fromJSONCredentials ss bs = do
244 ", Failed parsing authorized_user: " ++ ye
244 ", Failed parsing authorized_user: " ++ ye
245 _ -> x <|> y
245 _ -> x <|> y
247+-- | An 'OAuthToken' that can potentially be expired, with the original
248+-- credentials that can be used to perform a refresh.
250+ { _credentials :: !Credentials
251+ , _token :: !OAuthToken
254+-- | Check if the given token is still valid, ie. younger than the projected
256+validate :: MonadIO m => Auth -> m Bool
257+validate a = (< _tokenExpiry (_token a)) <$> liftIO getCurrentTime
259+type Store = MVar Auth
261+-- | Construct storage containing the credentials which have not yet been
262+-- exchanged or refreshed.
263+initStore :: (MonadIO m, MonadCatch m)
268+initStore c l m = exchange c l m >>= liftIO . newMVar
270+-- | Concurrently read the current token, and if expired, then
271+-- safely perform a single serial refresh.
272+getToken :: (MonadIO m, MonadCatch m)
278+ x <- liftIO (readMVar s)
281+ then pure (_token x)
282+ else liftIO . modifyMVar s $ \y -> do
285+ then pure (y, _token y)
290+-- | Perform the initial credentials exchange to obtain a valid 'OAuthToken'
291+-- suitable for authorizing requests.
247exchange :: (MonadIO m, MonadCatch m)
292exchange :: (MonadIO m, MonadCatch m)
248 => Credentials
293 => Credentials
249 -> Logger
294 -> Logger
250 -> Manager
295 -> Manager
251− -> m OAuthToken
296+ -> m Auth
252−exchange c l m =
297+exchange c l = fmap (Auth c) . action l
253− case c of
299+ action = case c of
254− FromMetadata s -> metadataToken s l m
300+ FromMetadata s -> metadataToken s
255− FromAccount a ss -> serviceAccountToken a ss l m
301+ FromAccount a ss -> serviceAccountToken a ss
256− FromClient c' n -> exchangeCode c' n l m
302+ FromClient c n -> exchangeCode c n
257− FromUser u -> authorizedUserToken u Nothing l m
303+ FromUser u -> authorizedUserToken u Nothing
305+-- | Refresh an existing 'OAuthToken' using
259refresh :: (MonadIO m, MonadCatch m)
306refresh :: (MonadIO m, MonadCatch m)
260− => Credentials
307+ => Auth
262 -> Logger
308 -> Logger
263 -> Manager
309 -> Manager
264− -> m OAuthToken
310+ -> m Auth
265−refresh c t l m =
311+refresh (Auth c t) l = fmap (Auth c) . action l
266− case c of
313+ action = case c of
267− FromMetadata s -> metadataToken s l m
314+ FromMetadata s -> metadataToken s
268− FromAccount a ss -> serviceAccountToken a ss l m
315+ FromAccount a ss -> serviceAccountToken a ss
269− FromClient c _ -> refreshToken c t l m
316+ FromClient x _ -> refreshToken x t
270− FromUser u -> authorizedUserToken u (_tokenRefresh t) l m
317+ FromUser u -> authorizedUserToken u (_tokenRefresh t)
272−-- | A credentials value in one of two possible states, pre-exchange and
319+-- | Apply the (by way of possible token refresh) a bearer token to the
273−-- containing a valid, but possibly expired access token.
320+-- authentication header of a request.
275− = Exchange !Credentials
276− -- ^ Signifies that the initial token refresh has not occured, and the
277− -- 'Credentials' need to be signed and exchanged for a valid 'OAuthToken'.
279− | Refresh !Credentials !OAuthToken
280− -- ^ An 'OAuthToken' that can potentially be expired, with the original
281− -- credentials that can be used to perform a refresh.
283−validateToken :: MonadIO m => Auth -> m (Maybe OAuthToken)
284−validateToken Exchange {} = pure Nothing
285−validateToken (Refresh _ t) = do
286− ts <- liftIO getCurrentTime
287− if ts < _tokenExpiry t
291−type Store = MVar Auth
293−-- | Construct storage containing the credentials which have not yet been
294−-- exchanged or refreshed.
295−emptyStore :: MonadIO m => Credentials -> m Store
296−emptyStore !c = liftIO . newMVar $ Exchange c
298authorize :: (MonadIO m, MonadCatch m)
321authorize :: (MonadIO m, MonadCatch m)
299 => Client.Request
322 => Client.Request
@@ -309,26 +332,3 @@ authorize rq s l m = bearer <$> getToken s l m
309 , "Bearer " <> Text.encodeUtf8 (toText (_tokenAccess t))
332 , "Bearer " <> Text.encodeUtf8 (toText (_tokenAccess t))
310 ) : Client.requestHeaders rq
333 ) : Client.requestHeaders rq
313−getToken :: (MonadIO m, MonadCatch m)
319− x <- liftIO (readMVar s)
320− mx <- validateToken x
323− Nothing -> liftIO . modifyMVar s $ \y -> do
324− my <- validateToken y
326− Just t -> pure (y, t)
331− pure (Refresh c t, t)
333− t' <- refresh c t l m
334− pure (Refresh c t', t')