@@ -19,56 +19,75 @@
1919--
2020-- Explicitly specify your Google credentials, or retrieve them
2121-- from the underlying OS.
22−module Network.Google.Auth where
23− -- (
24− -- -- * Authentication
25− -- -- ** Retrieving Authentication
26− -- getAuth
27− -- , Credentials (..)
28− -- , Auth
29−
30− -- -- ** Authorising Requests
31− -- , authorize
32−
33− -- -- ** Default Constants
34− -- -- *** Google Compute Engine
35− -- , checkGCEVar
36−
37− -- -- *** Cloud SDK
38− -- , cloudSDKConfigDir
39− -- , cloudSDKConfigPath
40−
41− -- -- *** Application Default Credentials
42− -- , defaultCredentialsFile
43− -- , defaultCredentialsPath
44−
45− -- -- ** Credential Styles
46− -- , fromMetadata
47− -- , fromFile
48− -- , fromFilePath
49−
50− -- -- *** OAuth Token
51− -- , Bearer (..)
52− -- , fromToken
53−
54− -- -- *** Authorized User
55− -- , AuthorizedUser (..)
56− -- , fromAuthorizedUser
57−
58− -- -- *** Service Account
59− -- , ServiceAccount (..)
60− -- , fromServiceAccount
61−
62− -- -- ** Handling Errors
63− -- , AsAuthError (..)
64− -- , AuthError (..)
65−
66− -- -- ** Re-exported Types
67− -- , Scope (..)
68− -- , OAuthToken (..)
69− -- , ServiceId (..)
70− -- , ClientId (..)
71− -- ) where
22+module Network.Google.Auth
23+ (
24+ -- * Credentials
25+ -- ** Discovery
26+ , discover
27+ , fromFile
28+ , fromFilePath
29+ , fromJSONCredentials
30+
31+ , Credentials (..)
32+ , Auth (..)
33+
34+ -- ** Thread-safe Storage
35+ , Store
36+ , emptyStore
37+
38+ -- ** Authorizing Requests
39+ , authorize
40+
41+ -- ** Constructing Callback URLs
42+ , formURL
43+ , redirectURI
44+ , accountsURL
45+
46+ -- ** Default Constants
47+ -- *** Google Compute Engine
48+ , checkGCEVar
49+
50+ -- *** Cloud SDK
51+ , cloudSDKConfigDir
52+ , cloudSDKConfigPath
53+
54+ -- *** Application Default Credentials
55+ , defaultCredentialsFile
56+ , defaultCredentialsPath
57+
58+ -- ** Handling Errors
59+ , AsAuthError (..)
60+ , AuthError (..)
61+
62+ -- * Credential Types
63+ , ServiceAccount (..)
64+ , AuthorizedUser (..)
65+
66+ -- * OAuth Types
67+ , OAuthClient (..)
68+ , OAuthCode (..)
69+ , OAuthScope (..)
70+ , OAuthToken (..)
71+
72+ -- * Re-exported Types
73+ , ServiceId (..)
74+ , ClientId (..)
75+ , AccessToken (..)
76+ , RefreshToken (..)
77+ , Secret (..)
78+
79+ -- * Exchange and Refresh Internals
80+ , getToken
81+ , validateToken
82+
83+ , exchange
84+ , exchangeCode
85+
86+ , refresh
87+ , refreshMetadata
88+ , refreshToken
89+ , refreshAssertion
90+ ) where
7291
7392import Control.Applicative
7493import Control.Concurrent
@@ -213,15 +232,21 @@ getConfigDirectory = do
213232 then pure h
214233 else pure $! h </> ".config"
215234
216−data RefreshError = RefreshError
217− { _error :: !Text
218− , _description :: !(Maybe Text)
219− }
235+-- | Given a client identifier, client secret, and a list of scopes to authorize,
236+-- construct a URL that can be used to obtain the 'OAuthCode' required to
237+-- instantiate 'FromClient'-style credentials.
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
220246
221−instance FromJSON RefreshError where
222− parseJSON = withObject "refresh_error" $ \o -> RefreshError
223− <$> o .: "error"
224− <*> o .:? "error_description"
247+-- | @urn:ietf:wg:oauth:2.0:oob@.
248+redirectURI :: Text
249+redirectURI = "urn:ietf:wg:oauth:2.0:oob"
225250
226251{-| Service Account credentials which are typically generated/download
227252from the Google Developer console of the following form:
@@ -369,21 +394,11 @@ data Auth
369394
370395type Store = MVar Auth
371396
397+-- | Construct storage containing the credentials which have not yet been
398+-- exchanged or refreshed.
372399emptyStore :: MonadIO m => Credentials -> m Store
373400emptyStore !c = liftIO . newMVar $ Exchange c
374401
375−-- FIXME: support for generating the url using scopes to obtain the OAuthCode.
376−
377−-- FIXME:
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.
383−
384−-- FIXME: Get around to replacing the threading of Logger, Manager through
385−-- every exchange/refresh call.
386−
387402-- | Attempt credentials discovery via the following steps:
388403--
389404-- * Read the default credentials from a file specified by
@@ -521,18 +536,6 @@ exchange c l m =
521536 FromUser AuthorizedUser{..} ->
522537 refreshToken _userId _userSecret (Just _userRefresh) l m
523538
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
532−
533−redirectURI :: Text
534−redirectURI = "urn:ietf:wg:oauth:2.0:oob"
535−
536539exchangeCode :: (MonadIO m, MonadCatch m)
537540 => OAuthClient
538541 -> OAuthCode
@@ -646,21 +649,6 @@ encodeJWTBearer s ss = liftIO $ do
646649 , "iss" .= _serviceEmail s
647650 ]
648651
649−queryEncodeScopes :: [OAuthScope] -> Build.Builder
650−queryEncodeScopes =
651− mconcat
652− . intersperse "+"
653− . map (urlEncodeBuilder True . Text.encodeUtf8)
654− . coerce
655−
656−concatScopes :: [OAuthScope] -> LText.Text
657−concatScopes =
658− TBuild.toLazyText
659− . mconcat
660− . intersperse " "
661− . map TBuild.fromText
662− . coerce
663−
664652accountsURL :: Text
665653accountsURL = "https://accounts.google.com/o/oauth2/token"
666654
@@ -737,3 +725,28 @@ buildBody = RequestBodyLBS . Build.toLazyByteString
737725
738726buildText :: ToText a => a -> Builder
739727buildText = build . toText
728+
729+queryEncodeScopes :: [OAuthScope] -> Build.Builder
730+queryEncodeScopes =
731+ mconcat
732+ . intersperse "+"
733+ . map (urlEncodeBuilder True . Text.encodeUtf8)
734+ . coerce
735+
736+concatScopes :: [OAuthScope] -> LText.Text
737+concatScopes =
738+ TBuild.toLazyText
739+ . mconcat
740+ . intersperse " "
741+ . map TBuild.fromText
742+ . coerce
743+
744+data RefreshError = RefreshError
745+ { _error :: !Text
746+ , _description :: !(Maybe Text)
747+ }
748+
749+instance FromJSON RefreshError where
750+ parseJSON = withObject "refresh_error" $ \o -> RefreshError
751+ <$> o .: "error"
752+ <*> o .:? "error_description"