@@ -19,56 +19,75 @@
20-- Explicitly specify your Google credentials, or retrieve them
20-- Explicitly specify your Google credentials, or retrieve them
21-- from the underlying OS.
21-- from the underlying OS.
22−module Network.Google.Auth where
22+module Network.Google.Auth
24− -- -- * Authentication
24+ -- * Credentials
25− -- -- ** Retrieving Authentication
25+ -- ** Discovery
26− -- getAuth
26+ , discover
29+ , fromJSONCredentials
27− -- , Credentials (..)
31+ , Credentials (..)
28− -- , Auth
32+ , Auth (..)
34+ -- ** Thread-safe Storage
30− -- -- ** Authorising Requests
38+ -- ** Authorizing Requests
31− -- , authorize
39+ , authorize
41+ -- ** Constructing Callback URLs
33− -- -- ** Default Constants
46+ -- ** Default Constants
34− -- -- *** Google Compute Engine
47+ -- *** Google Compute Engine
35− -- , checkGCEVar
48+ , checkGCEVar
37− -- -- *** Cloud SDK
50+ -- *** Cloud SDK
38− -- , cloudSDKConfigDir
51+ , cloudSDKConfigDir
39− -- , cloudSDKConfigPath
52+ , cloudSDKConfigPath
41− -- -- *** Application Default Credentials
54+ -- *** Application Default Credentials
42− -- , defaultCredentialsFile
55+ , defaultCredentialsFile
43− -- , defaultCredentialsPath
56+ , defaultCredentialsPath
45− -- -- ** Credential Styles
50− -- -- *** OAuth Token
54− -- -- *** Authorized User
55− -- , AuthorizedUser (..)
56− -- , fromAuthorizedUser
58− -- -- *** Service Account
59− -- , ServiceAccount (..)
60− -- , fromServiceAccount
62− -- -- ** Handling Errors
58+ -- ** Handling Errors
63− -- , AsAuthError (..)
59+ , AsAuthError (..)
64− -- , AuthError (..)
60+ , AuthError (..)
62+ -- * Credential Types
63+ , ServiceAccount (..)
64+ , AuthorizedUser (..)
66− -- -- ** Re-exported Types
72+ -- * Re-exported Types
69− -- , ServiceId (..)
73+ , ServiceId (..)
70− -- , ClientId (..)
74+ , ClientId (..)
79+ -- * Exchange and Refresh Internals
71− -- ) where
90+ ) where
73import Control.Applicative
92import Control.Applicative
74import Control.Concurrent
93import Control.Concurrent
@@ -213,15 +232,21 @@ getConfigDirectory = do
213 then pure h
232 then pure h
214 else pure $! h </> ".config"
233 else pure $! h </> ".config"
216−data RefreshError = RefreshError
235+-- | Given a client identifier, client secret, and a list of scopes to authorize,
217− { _error :: !Text
236+-- construct a URL that can be used to obtain the 'OAuthCode' required to
218− , _description :: !(Maybe Text)
237+-- instantiate 'FromClient'-style credentials.
219− }
238+formURL :: OAuthClient -> [OAuthScope] -> Text
239+formURL OAuthClient{..} ss =
240+ LText.toStrict . LText.decodeUtf8 . Build.toLazyByteString $
241+ buildText accountsURL
242+ <> "?response_type=code"
243+ <> "&client_id=" <> buildText _clientId
244+ <> "&redirect_uri=" <> buildText redirectURI
245+ <> "&scope=" <> queryEncodeScopes ss
221−instance FromJSON RefreshError where
247+-- | @urn:ietf:wg:oauth:2.0:oob@.
222− parseJSON = withObject "refresh_error" $ \o -> RefreshError
248+redirectURI :: Text
223− <$> o .: "error"
249+redirectURI = "urn:ietf:wg:oauth:2.0:oob"
224− <*> o .:? "error_description"
226{-| Service Account credentials which are typically generated/download
251{-| Service Account credentials which are typically generated/download
227from the Google Developer console of the following form:
252from the Google Developer console of the following form:
@@ -369,21 +394,11 @@ data Auth
370type Store = MVar Auth
395type Store = MVar Auth
397+-- | Construct storage containing the credentials which have not yet been
398+-- exchanged or refreshed.
372emptyStore :: MonadIO m => Credentials -> m Store
399emptyStore :: MonadIO m => Credentials -> m Store
373emptyStore !c = liftIO . newMVar $ Exchange c
400emptyStore !c = liftIO . newMVar $ Exchange c
375−-- FIXME: support for generating the url using scopes to obtain the OAuthCode.
378−-- The parameter is include_granted_scopes and the allowed values are true and
379−-- false (the default is false). When the value is true, the effect is that if
380−-- your scope authorization request is granted, the Google authorization server
381−-- will roll this authorization together with all the previous authorizations
382−-- granted to the requesting user from the requesting app.
384−-- FIXME: Get around to replacing the threading of Logger, Manager through
385−-- every exchange/refresh call.
387-- | Attempt credentials discovery via the following steps:
402-- | Attempt credentials discovery via the following steps:
389-- * Read the default credentials from a file specified by
404-- * Read the default credentials from a file specified by
@@ -521,18 +536,6 @@ exchange c l m =
521 FromUser AuthorizedUser{..} ->
536 FromUser AuthorizedUser{..} ->
522 refreshToken _userId _userSecret (Just _userRefresh) l m
537 refreshToken _userId _userSecret (Just _userRefresh) l m
524−formURL :: OAuthClient -> [OAuthScope] -> Text
525−formURL OAuthClient{..} ss =
526− LText.toStrict . LText.decodeUtf8 . Build.toLazyByteString $
527− buildText accountsURL
528− <> "?response_type=code"
529− <> "&client_id=" <> buildText _clientId
530− <> "&redirect_uri=" <> buildText redirectURI
531− <> "&scope=" <> queryEncodeScopes ss
534−redirectURI = "urn:ietf:wg:oauth:2.0:oob"
536exchangeCode :: (MonadIO m, MonadCatch m)
539exchangeCode :: (MonadIO m, MonadCatch m)
537 => OAuthClient
540 => OAuthClient
538 -> OAuthCode
541 -> OAuthCode
@@ -646,21 +649,6 @@ encodeJWTBearer s ss = liftIO $ do
646 , "iss" .= _serviceEmail s
649 , "iss" .= _serviceEmail s
649−queryEncodeScopes :: [OAuthScope] -> Build.Builder
653− . map (urlEncodeBuilder True . Text.encodeUtf8)
656−concatScopes :: [OAuthScope] -> LText.Text
661− . map TBuild.fromText
664accountsURL :: Text
652accountsURL :: Text
665accountsURL = "https://accounts.google.com/o/oauth2/token"
653accountsURL = "https://accounts.google.com/o/oauth2/token"
@@ -737,3 +725,28 @@ buildBody = RequestBodyLBS . Build.toLazyByteString
738buildText :: ToText a => a -> Builder
726buildText :: ToText a => a -> Builder
739buildText = build . toText
727buildText = build . toText
729+queryEncodeScopes :: [OAuthScope] -> Build.Builder
733+ . map (urlEncodeBuilder True . Text.encodeUtf8)
736+concatScopes :: [OAuthScope] -> LText.Text
741+ . map TBuild.fromText
744+data RefreshError = RefreshError
746+ , _description :: !(Maybe Text)
749+instance FromJSON RefreshError where
750+ parseJSON = withObject "refresh_error" $ \o -> RefreshError
752+ <*> o .:? "error_description"