on develop help sign in
Updating naming of overly long module
commit05f8e7f9161dee5abcaf8eaa5e912140342c31b1 authorBrendan Hay <brendan.g.hay@gmail.com> dateThu 29 Oct 2015, 20:09 (11 years ago) parent9c6d659
7 files changed+442−380
gogol/gogol.cabal+1−1 gogol/src/Network/Google.hs+176−143 gogol/src/Network/Google/Auth.hs+164−150 gogol/src/Network/Google/Auth/ApplicationDefaultCredentials.hs → gogol/src/Network/Google/Auth/ApplicationDefault.hs+16−22 gogol/src/Network/Google/Env.hs+66−58 gogol/src/Network/Google/Internal/Auth.hs+15−2 gogol/src/Network/Google/Internal/HTTP.hs+4−4
gogol/gogol.cabal+1−1
@@ -41,7 +41,7 @@ library
4141 exposed-modules:
4242 Network.Google
4343 , Network.Google.Auth
44− , Network.Google.Auth.ApplicationDefaultCredentials
44+ , Network.Google.Auth.ApplicationDefault
4545 , Network.Google.Auth.Device
4646 , Network.Google.Auth.InstalledApplication
4747 , Network.Google.Auth.ServiceAccount
gogol/src/Network/Google.hs+176−143
@@ -1,3 +1,4 @@
1+{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE FlexibleContexts #-}
23{-# LANGUAGE FlexibleInstances #-}
34{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -23,71 +24,71 @@
2324-- A 'MonadGoogle' typeclass is used as a function constraint to provide automatic
2425-- lifting of functions when embedding 'Google' as a layer inside your own
2526-- application stack.
26−module Network.Google
27− (
28− -- * Running Google Actions
29− Google (..)
30− , MonadGoogle (..)
31− , runGoogle
32− , runResourceT
27+module Network.Google where
28+ -- (
29+ -- -- * Running Google Actions
30+ -- Google (..)
31+ -- , MonadGoogle (..)
32+ -- , runGoogle
33+ -- , runResourceT
3334
34− -- * Authentication and Environment
35− , newEnv
36− , Env
37− , HasEnv (..)
35+ -- -- * Authentication and Environment
36+ -- , newEnv
37+ -- , Env
38+ -- , HasEnv (..)
3839
39− -- * Sending Requests
40− , send
40+ -- -- * Sending Requests
41+ -- , send
4142
42− -- ** Media Downloads
43− , download
43+ -- -- ** Media Downloads
44+ -- , download
4445
45− -- ** Media Uploads
46− , upload
46+ -- -- ** Media Uploads
47+ -- , upload
4748
48− , ToBody (..)
49− , RequestBody
50− , sourceBody
49+ -- , ToBody (..)
50+ -- , RequestBody
51+ -- , sourceBody
5152
52− -- ** Service Configuration
53− -- ** Overriding Defaults
54− , configure
55− , override
53+ -- -- ** Service Configuration
54+ -- -- ** Overriding Defaults
55+ -- , configure
56+ -- , override
5657
57− -- *** Scoped Actions
58− , reconfigure
59− , timeout
58+ -- -- *** Scoped Actions
59+ -- , reconfigure
60+ -- , timeout
6061
61− -- * Running Asynchronous Actions
62− -- $async
62+ -- -- * Running Asynchronous Actions
63+ -- -- $async
6364
64− -- * Handling Errors
65− , AsError (..)
66− , AsAuthError (..)
65+ -- -- * Handling Errors
66+ -- , AsError (..)
67+ -- , AsAuthError (..)
6768
68− , trying
69− , catching
69+ -- , trying
70+ -- , catching
7071
71− -- * Logging
72− -- $logging
72+ -- -- * Logging
73+ -- -- $logging
7374
74− , Logger
75− , LogLevel (..)
75+ -- , Logger
76+ -- , LogLevel (..)
7677
77− -- ** Constructing a Logger
78− , newLogger
78+ -- -- ** Constructing a Logger
79+ -- , newLogger
7980
80− -- * Constructing a HTTP Manager
81− , newManager
82− , tlsManagerSettings
81+ -- -- * Constructing a HTTP Manager
82+ -- , newManager
83+ -- , tlsManagerSettings
8384
84− -- * Re-exported Types
85− , Proxy (..)
86− , OctetStream
87− , PlainText
88− , JSON
89− , module Network.Google.Types
90− ) where
85+ -- -- * Re-exported Types
86+ -- , Proxy (..)
87+ -- , OctetStream
88+ -- , PlainText
89+ -- , JSON
90+ -- , module Network.Google.Types
91+ -- ) where
9192
9293import Control.Applicative
9394import Control.Exception.Lens
@@ -116,7 +117,7 @@ import Network.Google.Prelude
116117import Network.Google.Types
117118import Network.HTTP.Conduit (newManager, tlsManagerSettings)
118119
119−newtype Google a = Google { unGoogle :: ReaderT Env (ResourceT IO) a }
120+newtype Google s a = Google { unGoogle :: ReaderT (Env s) (ResourceT IO) a }
120121 deriving
121122 ( Functor
122123 , Applicative
@@ -128,99 +129,131 @@ newtype Google a = Google { unGoogle :: ReaderT Env (ResourceT IO) a }
128129 , MonadCatch
129130 , MonadMask
130131 , MonadBase IO
131− , MonadReader Env
132+-- , MonadReader (Env s)
132133 , MonadResource
133134 )
134135
135−instance MonadBaseControl IO Google where
136− type StM Google a = StM (ReaderT Env (ResourceT IO)) a
137−
138− liftBaseWith f = Google . liftBaseWith $ \g -> f (g . unGoogle)
139− restoreM = Google . restoreM
140−
141−-- | Monads in which 'Google' actions may be embedded.
142−class ( Functor m
143− , Applicative m
144− , Monad m
145− , MonadIO m
146− , MonadCatch m
147− ) => MonadGoogle m where
148− -- | Lift a computation to the 'Google' monad.
149− liftGoogle :: Google a -> m a
150−
151−instance MonadGoogle Google where
152− liftGoogle = id
153−
154−instance MonadGoogle m => MonadGoogle (IdentityT m) where liftGoogle = lift . liftGoogle
155−instance MonadGoogle m => MonadGoogle (ListT m) where liftGoogle = lift . liftGoogle
156−instance MonadGoogle m => MonadGoogle (MaybeT m) where liftGoogle = lift . liftGoogle
157−instance MonadGoogle m => MonadGoogle (ExceptT e m) where liftGoogle = lift . liftGoogle
158−instance MonadGoogle m => MonadGoogle (ReaderT r m) where liftGoogle = lift . liftGoogle
159−instance MonadGoogle m => MonadGoogle (S.StateT s m) where liftGoogle = lift . liftGoogle
160−instance MonadGoogle m => MonadGoogle (LS.StateT s m) where liftGoogle = lift . liftGoogle
161−
162−instance (Monoid w, MonadGoogle m) => MonadGoogle (W.WriterT w m) where
163− liftGoogle = lift . liftGoogle
164−
165−instance (Monoid w, MonadGoogle m) => MonadGoogle (LW.WriterT w m) where
166− liftGoogle = lift . liftGoogle
167−
168−instance (Monoid w, MonadGoogle m) => MonadGoogle (RW.RWST r w s m) where
169− liftGoogle = lift . liftGoogle
170−
171−instance (Monoid w, MonadGoogle m) => MonadGoogle (LRW.RWST r w s m) where
172− liftGoogle = lift . liftGoogle
173−
174136-- | Run a 'Google' aciton using the specified environment.
175−runGoogle :: (MonadResource m, HasEnv r) => r -> Google a -> m a
176−runGoogle e m = liftResourceT $ runReaderT (unGoogle m) (e ^. environment)
177−
178−-- | Send a request, returning the associated response if successful.
179−--
180−-- Throws 'Error'.
181−send :: (MonadGoogle m, GoogleRequest a) => a -> m (Rs a)
182−send x = liftGoogle $ do
183− e <- ask
184− r <- perform e x
185− hoistError r
186−
187−-- | Send a request returning the associated streaming media response if successful.
188−--
189−-- Some request data types have two possible responses, the JSON metadata and
190−-- a streaming media response. Use 'send' to retrieve the metadata and 'download'
191−-- to retrieve the streaming media.
192−--
193−-- Throws 'Error'.
194−download :: (MonadGoogle m, GoogleRequest (MediaDownload a))
195− => a
196− -> m (Rs (MediaDownload a))
197−download = send . MediaDownload
198−
199−-- | Send a request with an attached multipart/related media upload.
200−--
201−-- Throws 'Error'.
202−upload :: (MonadGoogle m, GoogleRequest (MediaUpload a))
203− => a
204− -> RequestBody
205− -> m (Rs (MediaUpload a))
206−upload x = send . MediaUpload x
207−
208−hoistError :: MonadThrow m => Either Error a -> m a
209−hoistError = either (throwingM _Error) return
210−
211−{- $async
212−Requests can be sent asynchronously, but due to guarantees about resource closure
213−require the use of <http://hackage.haskell.org/package/lifted-async lifted-async>.
214−-}
215−
216−{- $logging
217−The exposed logging interface is a primitive 'Logger' function which gets
218−threaded through service calls and serialisation routines. This allows the
219−library to output useful information and diagnostics.
220−
221−The 'newLogger' function can be used to construct a simple logger which writes
222−output to a 'Handle', but in most production code you should probably consider
223−using a more robust logging library such as
224−<http://hackage.haskell.org/package/tiny-log tiny-log> or
225−<http://hackage.haskell.org/package/fast-logger fast-logger>.
226−-}
137+-- runGoogle :: (MonadResource m, HasEnv s r) => r -> Google s a -> m a
138+-- runGoogle e m = liftResourceT $ runReaderT (unGoogle m) (e ^. environment)
139+
140+runGoogle :: MonadResource m => Env s -> Google s a -> m a
141+runGoogle e m = liftResourceT $ runReaderT (unGoogle m) e
142+
143+test :: IO ()
144+test = runResourceT $ do
145+ e <- newEnv
146+ runGoogle e $ do
147+ Bar <- send Foo
148+ pure ()
149+
150+send :: (GoogleRequest a, IsSubList s (Ss a)) => a -> Google s (Rs a)
151+send = undefined
152+
153+-- do
154+ -- e <- ask
155+ -- r <- perform e x
156+ -- hoistError r
157+
158+data Foo = Foo
159+data Bar = Bar
160+
161+instance GoogleRequest Foo where
162+ type Rs Foo = Bar
163+ type Ss Foo = '[Foo]
164+
165+ requestClient = undefined
166+
167+
168+-- instance MonadBaseControl IO Google where
169+-- type StM Google a = StM (ReaderT Env (ResourceT IO)) a
170+
171+-- liftBaseWith f = Google . liftBaseWith $ \g -> f (g . unGoogle)
172+-- restoreM = Google . restoreM
173+
174+-- -- | Monads in which 'Google' actions may be embedded.
175+-- class ( Functor m
176+-- , Applicative m
177+-- , Monad m
178+-- , MonadIO m
179+-- , MonadCatch m
180+-- ) => MonadGoogle m where
181+-- -- | Lift a computation to the 'Google' monad.
182+-- liftGoogle :: Google a -> m a
183+
184+-- instance MonadGoogle Google where
185+-- liftGoogle = id
186+
187+-- instance MonadGoogle m => MonadGoogle (IdentityT m) where liftGoogle = lift . liftGoogle
188+-- instance MonadGoogle m => MonadGoogle (ListT m) where liftGoogle = lift . liftGoogle
189+-- instance MonadGoogle m => MonadGoogle (MaybeT m) where liftGoogle = lift . liftGoogle
190+-- instance MonadGoogle m => MonadGoogle (ExceptT e m) where liftGoogle = lift . liftGoogle
191+-- instance MonadGoogle m => MonadGoogle (ReaderT r m) where liftGoogle = lift . liftGoogle
192+-- instance MonadGoogle m => MonadGoogle (S.StateT s m) where liftGoogle = lift . liftGoogle
193+-- instance MonadGoogle m => MonadGoogle (LS.StateT s m) where liftGoogle = lift . liftGoogle
194+
195+-- instance (Monoid w, MonadGoogle m) => MonadGoogle (W.WriterT w m) where
196+-- liftGoogle = lift . liftGoogle
197+
198+-- instance (Monoid w, MonadGoogle m) => MonadGoogle (LW.WriterT w m) where
199+-- liftGoogle = lift . liftGoogle
200+
201+-- instance (Monoid w, MonadGoogle m) => MonadGoogle (RW.RWST r w s m) where
202+-- liftGoogle = lift . liftGoogle
203+
204+-- instance (Monoid w, MonadGoogle m) => MonadGoogle (LRW.RWST r w s m) where
205+-- liftGoogle = lift . liftGoogle
206+
207+-- -- | Run a 'Google' aciton using the specified environment.
208+-- runGoogle :: (MonadResource m, HasEnv r) => r -> Google a -> m a
209+-- runGoogle e m = liftResourceT $ runReaderT (unGoogle m) (e ^. environment)
210+
211+-- -- | Send a request, returning the associated response if successful.
212+-- --
213+-- -- Throws 'Error'.
214+-- send :: (MonadGoogle m, GoogleRequest a) => a -> m (Rs a)
215+-- send x = liftGoogle $ do
216+-- e <- ask
217+-- r <- perform e x
218+-- hoistError r
219+
220+-- -- | Send a request returning the associated streaming media response if successful.
221+-- --
222+-- -- Some request data types have two possible responses, the JSON metadata and
223+-- -- a streaming media response. Use 'send' to retrieve the metadata and 'download'
224+-- -- to retrieve the streaming media.
225+-- --
226+-- -- Throws 'Error'.
227+-- download :: (MonadGoogle m, GoogleRequest (MediaDownload a))
228+-- => a
229+-- -> m (Rs (MediaDownload a))
230+-- download = send . MediaDownload
231+
232+-- -- | Send a request with an attached multipart/related media upload.
233+-- --
234+-- -- Throws 'Error'.
235+-- upload :: (MonadGoogle m, GoogleRequest (MediaUpload a))
236+-- => a
237+-- -> RequestBody
238+-- -> m (Rs (MediaUpload a))
239+-- upload x = send . MediaUpload x
240+
241+-- hoistError :: MonadThrow m => Either Error a -> m a
242+-- hoistError = either (throwingM _Error) return
243+
244+-- {- $async
245+-- Requests can be sent asynchronously, but due to guarantees about resource closure
246+-- require the use of <http://hackage.haskell.org/package/lifted-async lifted-async>.
247+-- -}
248+
249+-- {- $logging
250+-- The exposed logging interface is a primitive 'Logger' function which gets
251+-- threaded through service calls and serialisation routines. This allows the
252+-- library to output useful information and diagnostics.
253+
254+-- The 'newLogger' function can be used to construct a simple logger which writes
255+-- output to a 'Handle', but in most production code you should probably consider
256+-- using a more robust logging library such as
257+-- <http://hackage.haskell.org/package/tiny-log tiny-log> or
258+-- <http://hackage.haskell.org/package/fast-logger fast-logger>.
259+-- -}
gogol/src/Network/Google/Auth.hs+164−150
@@ -1,11 +1,20 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# LANGUAGE DataKinds #-}
14{-# LANGUAGE DeriveDataTypeable #-}
25{-# LANGUAGE DeriveGeneric #-}
36{-# LANGUAGE FlexibleContexts #-}
47{-# LANGUAGE FlexibleInstances #-}
8+{-# LANGUAGE FunctionalDependencies #-}
59{-# LANGUAGE GeneralizedNewtypeDeriving #-}
10+{-# LANGUAGE KindSignatures #-}
611{-# LANGUAGE LambdaCase #-}
712{-# LANGUAGE OverloadedStrings #-}
13+{-# LANGUAGE PolyKinds #-}
814{-# LANGUAGE RecordWildCards #-}
15+{-# LANGUAGE ScopedTypeVariables #-}
16+{-# LANGUAGE TypeFamilies #-}
17+{-# LANGUAGE TypeOperators #-}
918
1019-- |
1120-- Module : Network.Google.Auth
@@ -17,86 +26,86 @@
1726--
1827-- Explicitly specify your Google credentials, or retrieve them
1928-- from the underlying OS.
20−module Network.Google.Auth
21− (
22− -- * Credentials
23− Credentials (..)
24−
25− -- ** Application Default Credentials
26− , getApplicationDefault
27− , fromWellKnownPath
28− , fromFilePath
29−
30− -- ** Installed Application Credentials
31− , formURL
32−
33− -- ** Authorizing Requests
34− , authorize
35−
36− -- ** Thread-safe Storage
37− , Store
38− , initStore
39−
40− , Auth (..)
41− , exchange
42− , refresh
43−
44− -- ** Default Constants
45− , checkGCEVar
46− , cloudSDKConfigDir
47− , defaultCredentialsFile
48−
49− -- ** Handling Errors
50− , AsAuthError (..)
51− , AuthError (..)
52−
53− -- * OAuth Types
54− , OAuthClient (..)
55− , OAuthToken (..)
56− , OAuthCode (..)
57− , OAuthScope (..)
58−
59− -- * Re-exported Types
60− , AccessToken (..)
61− , RefreshToken (..)
62− , Secret (..)
63− , ServiceId (..)
64− , ClientId (..)
65− ) where
29+module Network.Google.Auth where
30+ -- (
31+ -- -- * Credentials
32+ -- Credentials (..)
33+
34+ -- -- ** Application Default Credentials
35+ -- , getApplicationDefault
36+ -- , fromWellKnownPath
37+ -- , fromFilePath
38+
39+ -- -- ** Installed Application Credentials
40+ -- , formURL
41+
42+ -- -- ** Authorizing Requests
43+ -- , authorize
44+
45+ -- -- ** Thread-safe Storage
46+ -- , Store
47+ -- , initStore
48+
49+ -- , Auth (..)
50+ -- , exchange
51+ -- , refresh
52+
53+ -- -- ** Default Constants
54+ -- , checkGCEVar
55+ -- , cloudSDKConfigDir
56+ -- , defaultCredentialsFile
57+
58+ -- -- ** Handling Errors
59+ -- , AsAuthError (..)
60+ -- , AuthError (..)
61+
62+ -- -- * OAuth Types
63+ -- , OAuthClient (..)
64+ -- , OAuthToken (..)
65+ -- , OAuthCode (..)
66+ -- , OAuthScope (..)
67+
68+ -- -- * Re-exported Types
69+ -- , AccessToken (..)
70+ -- , RefreshToken (..)
71+ -- , Secret (..)
72+ -- , ServiceId (..)
73+ -- , ClientId (..)
74+ -- ) where
6675
6776import Control.Applicative
6877import Control.Concurrent
6978import Control.Exception.Lens
70−import Control.Lens hiding
71− (coerce,
72− (.=))
79+import Control.Lens hiding (coerce, (.=))
7380import Control.Monad
7481import Control.Monad.Catch
7582import Control.Monad.IO.Class
76−import Crypto.Hash.Algorithms (SHA256 (..))
77−import Crypto.PubKey.RSA.PKCS15 (signSafer)
78−import Crypto.PubKey.RSA.Types (PrivateKey)
83+import Crypto.Hash.Algorithms (SHA256 (..))
84+import Crypto.PubKey.RSA.PKCS15 (signSafer)
85+import Crypto.PubKey.RSA.Types (PrivateKey)
7986import Data.Aeson
8087import Data.Aeson.Types
8188import Data.ByteArray
8289import Data.ByteArray.Encoding
83−import Data.ByteString (ByteString)
84−import Data.ByteString.Builder (Builder)
85−import qualified Data.ByteString.Builder as Build
86−import qualified Data.ByteString.Char8 as BS8
87−import qualified Data.ByteString.Lazy as LBS
90+import Data.ByteString (ByteString)
91+import Data.ByteString.Builder (Builder)
92+import qualified Data.ByteString.Builder as Build
93+import qualified Data.ByteString.Char8 as BS8
94+import qualified Data.ByteString.Lazy as LBS
8895import Data.Coerce
89−import Data.Default.Class (def)
90−import Data.List (intersperse)
96+import Data.Default.Class (def)
97+import Data.List
9198import Data.String
92−import qualified Data.Text as Text
93−import qualified Data.Text.Encoding as Text
99+import qualified Data.Text as Text
100+import qualified Data.Text.Encoding as Text
94101import Data.Time
95102import Data.Time.Clock.POSIX
96103import Data.Typeable
97104import Data.X509
98105import Data.X509.Memory
99−import Network.Google.Auth.ApplicationDefaultCredentials
106+import GHC.Exts (Constraint)
107+import GHC.TypeLits
108+import Network.Google.Auth.ApplicationDefault
100109import Network.Google.Auth.Device
101110import Network.Google.Auth.InstalledApplication
102111import Network.Google.Auth.ServiceAccount
@@ -104,105 +113,110 @@ import Network.Google.Compute.Metadata
104113import Network.Google.Internal.Auth
105114import Network.Google.Internal.Logger
106115import Network.Google.Prelude
107−import Network.HTTP.Conduit hiding
108− (Request)
109−import qualified Network.HTTP.Conduit as Client
116+import Network.HTTP.Conduit hiding (Proxy)
117+import qualified Network.HTTP.Conduit as Client
110118import Network.HTTP.Types
111−import System.Directory (doesFileExist, getHomeDirectory)
112−import System.Environment (lookupEnv)
113−import System.FilePath ((</>))
114−import System.Info (os)
119+
115120
116121-- | An 'OAuthToken' that can potentially be expired, with the original
117122-- credentials that can be used to perform a refresh.
118−data Auth = Auth
119− { _credentials :: !Credentials
123+data Auth (s :: [*]) = Auth
124+ { _credentials :: !(Credentials s)
120125 , _token :: !OAuthToken
121126 }
122127
123−-- | Check if the given token is still valid, ie. younger than the projected
124−-- expiry time.
125−--
126−-- This deliberately makes no external calls due to the absolute construction of
127−-- the '_tokenExpiry' field, unlike the
128−-- <https://developers.google.com/accounts/docs/OAuth2Login#validatingtoken documented>
129−-- validation method.
130−validate :: MonadIO m => Auth -> m Bool
131−validate a = (< _tokenExpiry (_token a)) <$> liftIO getCurrentTime
128+newtype Store (s :: [*]) = Store { store :: MVar (Auth s) }
129+
130+-- type family HasScope s a :: Constraint where
131+-- HasScope s a = IsSubList s (Ss a)
132+
133+type family And (a :: Constraint) (b :: Constraint) :: Constraint where
134+ And () () = ()
132135
133−type Store = MVar Auth
136+type family IsSubList a b :: Constraint where
137+ IsSubList '[] b = ()
138+ IsSubList '[x] (x ': xs) = ()
139+ IsSubList '[x] (y ': ys) = IsSubList '[x] ys
140+ IsSubList (x ': xs) y = IsSubList '[x] y `And` IsSubList xs y
141+
142+-- authorize :: (MonadIO m, MonadCatch m)
143+-- => Credentials s
144+-- -> Logger
145+-- -> Manager
146+-- -> m (Store s)
147+-- authorize = undefined
134148
135149-- | Construct storage containing the credentials which have not yet been
136150-- exchanged or refreshed.
137151initStore :: (MonadIO m, MonadCatch m)
138− => Credentials
139− -> Logger
140− -> Manager
141− -> m Store
142−initStore c l m = exchange c l m >>= liftIO . newMVar
143−
144−-- | Concurrently read the current token, and if expired, then
145−-- safely perform a single serial refresh.
146−getToken :: (MonadIO m, MonadCatch m)
147− => Store
148− -> Logger
149− -> Manager
150− -> m OAuthToken
151−getToken s l m = do
152− x <- liftIO (readMVar s)
153− mx <- validate x
154− if mx
155− then pure (_token x)
156− else liftIO . modifyMVar s $ \y -> do
157− my <- validate y
158− if my
159− then pure (y, _token y)
160− else do
161− z <- refresh y l m
162− pure (z, _token z)
163−
164−-- | Perform the initial credentials exchange to obtain a valid 'OAuthToken'
165−-- suitable for authorizing requests.
166−exchange :: (MonadIO m, MonadCatch m)
167− => Credentials
168− -> Logger
169− -> Manager
170− -> m Auth
171−exchange c l = fmap (Auth c) . action l
172− where
173− action = case c of
174− FromMetadata s -> metadataToken s
175− FromAccount a ss -> serviceAccountToken a ss
176− FromClient c n -> exchangeCode c n
177− FromUser u -> authorizedUserToken u Nothing
178−
179−-- | Refresh an existing 'OAuthToken' using
180−refresh :: (MonadIO m, MonadCatch m)
181− => Auth
182− -> Logger
183− -> Manager
184− -> m Auth
185−refresh (Auth c t) l = fmap (Auth c) . action l
186− where
187− action = case c of
188− FromMetadata s -> metadataToken s
189− FromAccount a ss -> serviceAccountToken a ss
190− FromClient x _ -> refreshToken x t
191− FromUser u -> authorizedUserToken u (_tokenRefresh t)
192−
193−-- | Apply the (by way of possible token refresh) a bearer token to the
194−-- authentication header of a request.
195−authorize :: (MonadIO m, MonadCatch m)
196− => Client.Request
197− -> Store
152+ => Credentials s
198153 -> Logger
199154 -> Manager
200− -> m Client.Request
201−authorize rq s l m = bearer <$> getToken s l m
202− where
203− bearer t = rq
204− { Client.requestHeaders =
205− ( hAuthorization
206− , "Bearer " <> Text.encodeUtf8 (toText (_tokenAccess t))
207− ) : Client.requestHeaders rq
208− }
155+ -> m (Store s)
156+initStore c l m = undefined -- exchange c l m >>= liftIO . newMVar
157+
158+-- -- | Concurrently read the current token, and if expired, then
159+-- -- safely perform a single serial refresh.
160+-- getToken :: (MonadIO m, MonadCatch m)
161+-- => Store
162+-- -> Logger
163+-- -> Manager
164+-- -> m OAuthToken
165+-- getToken s l m = do
166+-- x <- liftIO (readMVar s)
167+-- mx <- validate x
168+-- if mx
169+-- then pure (_token x)
170+-- else liftIO . modifyMVar s $ \y -> do
171+-- my <- validate y
172+-- if my
173+-- then pure (y, _token y)
174+-- else do
175+-- z <- refresh y l m
176+-- pure (z, _token z)
177+
178+-- -- | Perform the initial credentials exchange to obtain a valid 'OAuthToken'
179+-- -- suitable for authorizing requests.
180+-- exchange :: (MonadIO m, MonadCatch m)
181+-- => Credentials
182+-- -> Logger
183+-- -> Manager
184+-- -> m Auth
185+-- exchange c l = fmap (Auth c) . action l
186+-- where
187+-- action = case c of
188+-- FromMetadata s -> metadataToken s
189+-- FromAccount a ss -> serviceAccountToken a ss
190+-- FromClient c n -> exchangeCode c n
191+-- FromUser u -> authorizedUserToken u Nothing
192+
193+-- -- | Refresh an existing 'OAuthToken' using
194+-- refresh :: (MonadIO m, MonadCatch m)
195+-- => Auth
196+-- -> Logger
197+-- -> Manager
198+-- -> m Auth
199+-- refresh (Auth c t) l = fmap (Auth c) . action l
200+-- where
201+-- action = case c of
202+-- FromMetadata s -> metadataToken s
203+-- FromAccount a ss -> serviceAccountToken a ss
204+-- FromClient x _ -> refreshToken x t
205+-- FromUser u -> authorizedUserToken u (_tokenRefresh t)
206+
207+-- -- | Apply the (by way of possible token refresh) a bearer token to the
208+-- -- authentication header of a request.
209+-- authorize :: (MonadIO m, MonadCatch m)
210+-- => Client.Request
211+-- -> Store
212+-- -> Logger
213+-- -> Manager
214+-- -> m Client.Request
215+-- authorize rq s l m = bearer <$> getToken s l m
216+-- where
217+-- bearer t = rq
218+-- { Client.requestHeaders =
219+-- ( hAuthorization
220+-- , "Bearer " <> Text.encodeUtf8 (toText (_tokenAccess t))
221+-- ) : Client.requestHeaders rq
222+-- }
gogol/src/Network/Google/Auth/ApplicationDefaultCredentials.hs → gogol/src/Network/Google/Auth/ApplicationDefault.hsrenamed+16−22
@@ -9,7 +9,7 @@
99-- Stability : provisional
1010-- Portability : non-portable (GHC extensions)
1111
12−module Network.Google.Auth.ApplicationDefaultCredentials where
12+module Network.Google.Auth.ApplicationDefault where
1313
1414import Control.Applicative
1515import Control.Concurrent
@@ -102,11 +102,10 @@ defaultCredentialsPath = liftIO (lookupEnv defaultCredentialsFile)
102102-- top-level module of each individual @gogol-*@ library for a list of available
103103-- scopes, such as @Network.Google.Compute.computeScope@.
104104getApplicationDefault :: (MonadIO m, MonadCatch m)
105− => [OAuthScope]
106− -> Manager
107− -> m Credentials
108−getApplicationDefault ss m =
109− catching _MissingFileError (fromWellKnownPath ss) $ \f -> do
105+ => Manager
106+ -> m (Credentials s)
107+getApplicationDefault m =
108+ catching _MissingFileError fromWellKnownPath $ \f -> do
110109 p <- isGCE m
111110 unless p $
112111 throwingM _MissingFileError f
@@ -121,14 +120,14 @@ getApplicationDefault ss m =
121120-- scopes, such as @Network.Google.Compute.computeScope@.
122121--
123122-- /See:/ 'cloudSDKConfigPath', 'defaultCredentialsPath'.
124−fromWellKnownPath :: (MonadIO m, MonadCatch m) => [OAuthScope] -> m Credentials
125−fromWellKnownPath ss = do
123+fromWellKnownPath :: (MonadIO m, MonadCatch m) => m (Credentials s)
124+fromWellKnownPath = do
126125 f <- defaultCredentialsPath
127126 case f of
128− Just x -> fromFilePath ss x
127+ Just x -> fromFilePath x
129128 Nothing -> do
130129 x <- cloudSDKConfigPath
131− fromFilePath ss x
130+ fromFilePath x
132131
133132-- | Attempt to load either a @service_account@ or @authorized_user@ formatted
134133-- file to obtain the credentials neccessary to perform a token refresh from
@@ -138,27 +137,22 @@ fromWellKnownPath ss = do
138137-- found with the appropriate scopes, otherwise they are not used. See the
139138-- top-level module of each individual @gogol-*@ library for a list of available
140139-- scopes, such as @Network.Google.Compute.computeScope@.
141−fromFilePath :: (MonadIO m, MonadCatch m)
142− => [OAuthScope]
143− -> FilePath
144− -> m Credentials
145−fromFilePath ss f = do
140+fromFilePath :: (MonadIO m, MonadCatch m) => FilePath -> m (Credentials s)
141+fromFilePath f = do
146142 p <- liftIO (doesFileExist f)
147143 unless p $
148144 throwM (MissingFileError f)
149145 bs <- liftIO (LBS.readFile f)
150146 either (throwM . InvalidFileError f . Text.pack) pure
151− (fromJSONCredentials ss bs)
147+ (fromJSONCredentials bs)
152148
153149-- | Attempt to parse either a @service_account@ or @authorized_user@ formatted
154150-- JSON value to obtain credentials.
155−fromJSONCredentials :: [OAuthScope]
156− -> LBS.ByteString
157− -> Either String Credentials
158−fromJSONCredentials ss bs = do
151+fromJSONCredentials :: LBS.ByteString -> Either String (Credentials s)
152+fromJSONCredentials bs = do
159153 v <- eitherDecode' bs
160− let x = (`FromAccount` ss) <$> parseEither parseJSON v
161− y = FromUser <$> parseEither parseJSON v
154+ let x = FromAccount <$> parseEither parseJSON v
155+ y = FromUser <$> parseEither parseJSON v
162156 case (x, y) of
163157 (Left xe, Left ye) -> Left $
164158 "Failed parsing service_account: " ++ xe ++
gogol/src/Network/Google/Env.hs+66−58
@@ -1,3 +1,8 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE FunctionalDependencies #-}
4+{-# LANGUAGE KindSignatures #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
16-- |
27-- Module : Network.Google.Env
38-- Copyright : (c) 2015-2016 Brendan Hay
@@ -13,26 +18,29 @@ import Control.Lens
1318import Control.Monad.Catch
1419import Control.Monad.IO.Class
1520import Control.Monad.Reader
16−import Data.Function (on)
21+import Data.Function (on)
1722import Data.Monoid
23+import GHC.TypeLits
1824import Network.Google.Auth
25+import Network.Google.Auth.ApplicationDefault
26+import Network.Google.Internal.Auth
1927import Network.Google.Internal.Logger
2028import Network.Google.Types
2129import Network.HTTP.Conduit
2230
2331-- | The environment containing the parameters required to make Google requests.
24−data Env = Env
25− { _envOverride :: !(Dual (Endo ServiceConfig))
32+data Env (s :: [*]) = Env
33+ { _envOverride :: !(Dual (Endo Service))
2634 , _envLogger :: !Logger
2735 , _envManager :: !Manager
28− , _envStore :: !Store
36+ , _envStore :: !(Store s)
2937 }
3038
3139-- Note: The strictness annotations aobe are applied to ensure
3240-- total field initialisation.
3341
34−class HasEnv a where
35− environment :: Lens' a Env
42+class HasEnv s a | a -> s where
43+ environment :: Lens' a (Env s)
3644 {-# MINIMAL environment #-}
3745
3846 -- | The currently applied overrides to all 'Service' configuration.
@@ -45,61 +53,61 @@ class HasEnv a where
4553 envManager :: Lens' a Manager
4654
4755 -- | The credential store used to sign requests for authentication with Google.
48− envStore :: Lens' a Store
56+ envStore :: Lens' a (Store s)
4957
5058 envOverride = environment . lens _envOverride (\s a -> s { _envOverride = a })
5159 envLogger = environment . lens _envLogger (\s a -> s { _envLogger = a })
5260 envManager = environment . lens _envManager (\s a -> s { _envManager = a })
5361 envStore = environment . lens _envStore (\s a -> s { _envStore = a })
5462
55−instance HasEnv Env where
63+instance HasEnv s (Env s) where
5664 environment = id
5765
58−-- | Provide a function which will be added to the existing stack
59−-- of overrides applied to all service configuration.
60−--
61−-- To override a specific service, it's suggested you use
62−-- either 'configure' or 'reconfigure' with a modified version of the default
63−-- service, such as @Network.Google.Gmail.gmailService@.
64−override :: HasEnv a => (ServiceConfig -> ServiceConfig) -> a -> a
65−override f = envOverride <>~ Dual (Endo f)
66−
67−-- | Configure a specific service. All requests belonging to the
68−-- supplied service will use this configuration instead of the default.
69−--
70−-- It's suggested you use a modified version of the default service, such
71−-- as @Network.Google.Gmail.gmailService@.
72−--
73−-- /See:/ 'reconfigure'.
74−configure :: HasEnv a => ServiceConfig -> a -> a
75−configure s = override f
76− where
77− f x | on (==) _svcId s x = s
78− | otherwise = x
79−
80−-- | Scope an action such that all requests belonging to the supplied service
81−-- will use this configuration instead of the default.
82−--
83−-- It's suggested you use a modified version of the default service, such
84−-- as @Network.Google.Gmail.gmailService@.
85−--
86−-- /See:/ 'configure'.
87−reconfigure :: (MonadReader r m, HasEnv r) => ServiceConfig -> m a -> m a
88−reconfigure = local . configure
89−
90−-- | Scope an action such that any HTTP response will use this timeout value.
91−--
92−-- Default timeouts are chosen by considering:
93−--
94−-- * This 'timeout', if set.
95−--
96−-- * The related 'Service' timeout for the sent request if set. (Usually 70s)
97−--
98−-- * The 'envManager' timeout if set.
99−--
100−-- * The default 'ClientRequest' timeout. (Approximately 30s)
101−timeout :: (MonadReader r m, HasEnv r) => Seconds -> m a -> m a
102−timeout s = local (override (serviceTimeout ?~ s))
66+-- -- | Provide a function which will be added to the existing stack
67+-- -- of overrides applied to all service configuration.
68+-- --
69+-- -- To override a specific service, it's suggested you use
70+-- -- either 'configure' or 'reconfigure' with a modified version of the default
71+-- -- service, such as @Network.Google.Gmail.gmailService@.
72+-- override :: HasEnv a => (Service -> Service) -> a -> a
73+-- override f = envOverride <>~ Dual (Endo f)
74+
75+-- -- | Configure a specific service. All requests belonging to the
76+-- -- supplied service will use this configuration instead of the default.
77+-- --
78+-- -- It's suggested you use a modified version of the default service, such
79+-- -- as @Network.Google.Gmail.gmailService@.
80+-- --
81+-- -- /See:/ 'reconfigure'.
82+-- configure :: HasEnv a => Service -> a -> a
83+-- configure s = override f
84+-- where
85+-- f x | on (==) _svcId s x = s
86+-- | otherwise = x
87+
88+-- -- | Scope an action such that all requests belonging to the supplied service
89+-- -- will use this configuration instead of the default.
90+-- --
91+-- -- It's suggested you use a modified version of the default service, such
92+-- -- as @Network.Google.Gmail.gmailService@.
93+-- --
94+-- -- /See:/ 'configure'.
95+-- reconfigure :: (MonadReader r m, HasEnv r) => Service -> m a -> m a
96+-- reconfigure = local . configure
97+
98+-- -- | Scope an action such that any HTTP response will use this timeout value.
99+-- --
100+-- -- Default timeouts are chosen by considering:
101+-- --
102+-- -- * This 'timeout', if set.
103+-- --
104+-- -- * The related 'Service' timeout for the sent request if set. (Usually 70s)
105+-- --
106+-- -- * The 'envManager' timeout if set.
107+-- --
108+-- -- * The default 'ClientRequest' timeout. (Approximately 30s)
109+-- timeout :: (MonadReader r m, HasEnv r) => Seconds -> m a -> m a
110+-- timeout s = local (override (serviceTimeout ?~ s))
103111
104112-- | Creates a new environment with a new 'Manager', without logging.
105113-- Credentials are determined by calling 'discover'. Use 'newEnvWith' to supply
@@ -113,10 +121,10 @@ timeout s = local (override (serviceTimeout ?~ s))
113121-- Lenses from 'HasEnv' can be used to further configure the resulting 'Env'.
114122--
115123-- /See:/ 'newEnvWith', 'getApplicationDefault'.
116−newEnv :: (MonadIO m, MonadCatch m) => [OAuthScope] -> m Env
117−newEnv ss = do
124+newEnv :: (MonadIO m, MonadCatch m) => m (Env s)
125+newEnv = do
118126 m <- liftIO (newManager tlsManagerSettings)
119− c <- getApplicationDefault ss m
127+ c <- getApplicationDefault m
120128 newEnvWith c (\_ _ -> pure ()) m
121129
122130-- | Creates a new environment without logging, using the supplied 'Manager' and
@@ -134,8 +142,8 @@ newEnv ss = do
134142--
135143-- /See:/ 'newEnv'.
136144newEnvWith :: (MonadIO m, MonadCatch m)
137− => Credentials
145+ => Credentials s
138146 -> Logger
139147 -> Manager
140− -> m Env
148+ -> m (Env s)
141149newEnvWith c l m = Env mempty l m <$> initStore c l m
gogol/src/Network/Google/Internal/Auth.hs+15−2
@@ -1,8 +1,10 @@
11{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE DataKinds #-}
23{-# LANGUAGE DeriveGeneric #-}
34{-# LANGUAGE FlexibleContexts #-}
45{-# LANGUAGE FlexibleInstances #-}
56{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE KindSignatures #-}
68{-# LANGUAGE LambdaCase #-}
79{-# LANGUAGE OverloadedStrings #-}
810
@@ -49,6 +51,7 @@ import Data.Time.Clock.POSIX
4951import Data.Typeable
5052import Data.X509
5153import Data.X509.Memory
54+import GHC.TypeLits
5255import Network.Google.Compute.Metadata
5356import Network.Google.Internal.Logger
5457import Network.Google.Prelude hiding (buildText)
@@ -62,7 +65,7 @@ import System.FilePath ((</>))
6265import System.Info (os)
6366
6467-- | The supported credential mechanisms.
65−data Credentials
68+data Credentials (s :: [*])
6669 = FromMetadata !ServiceId
6770 -- ^ Obtain and refresh access tokens from the underlying GCE host metadata
6871 -- at @http:\/\/169.254.169.254@.
@@ -74,7 +77,7 @@ data Credentials
7477 -- See the <https://developers.google.com/accounts/docs/OAuth2InstalledApp OAuth2 Installed Application>
7578 -- documentation for more information.
7679
77− | FromAccount !ServiceAccount ![OAuthScope]
80+ | FromAccount !ServiceAccount
7881 -- ^ Use the specified @service_account@ and scopes to sign and request
7982 -- an access token. The 'ServiceAccount' will also be used for subsequent
8083 -- token refreshes.
@@ -189,6 +192,16 @@ instance FromJSON (UTCTime -> OAuthToken) where
189192 e <- o .: "expires_in" <&> fromInteger
190193 pure (OAuthToken t r . addUTCTime e)
191194
195+-- | Check if the given token is still valid, ie. younger than the projected
196+-- expiry time.
197+--
198+-- This deliberately makes no external calls due to the absolute construction of
199+-- the '_tokenExpiry' field, unlike the
200+-- <https://developers.google.com/accounts/docs/OAuth2Login#validatingtoken documented>
201+-- validation method.
202+validate :: MonadIO m => OAuthToken -> m Bool
203+validate t = (< _tokenExpiry t) <$> liftIO getCurrentTime
204+
192205newtype OAuthCode = OAuthCode Text
193206 deriving (Eq, Ord, Show, Read, IsString, Generic, Typeable, ToText, FromJSON, ToJSON)
194207
gogol/src/Network/Google/Internal/HTTP.hs+4−4
@@ -14,7 +14,6 @@
1414--
1515module Network.Google.Internal.HTTP where
1616
17−
1817import Control.Lens
1918import Control.Monad.Catch
2019import Control.Monad.IO.Class
@@ -49,9 +48,10 @@ perform :: (MonadCatch m, MonadResource m, GoogleRequest a)
4948 -> m (Either Error (Rs a))
5049perform Env{..} x = catches go handlers
5150 where
52− Request {..} = _cliRequest
53− ServiceConfig {..} = _cliService
54− Client {..} = requestClient x
51+ Request {..} = _cliRequest
52+ Service {..} = _cliService
53+
54+ Client {..} = requestClient x
5555 & clientService %~ appEndo (getDual _envOverride)
5656
5757 go = liftResourceT $ do