@@ -57,6 +57,7 @@ module Network.Google.Auth
5757 , ClientId (..)
5858 ) where
5959
60+import Control.Applicative
6061import Control.Concurrent
6162import Control.Exception.Lens
6263import Control.Lens hiding ((.=))
@@ -82,6 +83,7 @@ import Data.Typeable
8283import Data.X509
8384import Data.X509.Memory
8485import Network.Google.Compute.Metadata
86+import Network.Google.Internal.Logger
8587import Network.Google.Prelude
8688import Network.HTTP.Conduit hiding (Request)
8789import qualified Network.HTTP.Conduit as Client
@@ -198,47 +200,64 @@ data Credentials
198200--
199201-- Throws 'AuthError' when environment variables or service account information
200202-- cannot be read, and credentials files are invalid or cannot be found.
201−getAuth :: (MonadIO m, MonadCatch m) => Credentials -> Manager -> m Auth
202−getAuth c m = case c of
203+getAuth :: (MonadIO m, MonadCatch m)
204+ => Credentials
205+ -> Logger
206+ -> Manager
207+ -> m Auth
208+getAuth c l m = case c of
203209 FromToken t -> pure (AuthToken t)
204− FromFile f -> fromFilePath f m
205− FromAccount s -> fromMetadata s m
210+ FromFile f -> fromFilePath f l m
211+ FromAccount s -> fromMetadata s l m
206212 Discover ->
207− catching _MissingFileError (fromFile m) $ \f -> do
213+ catching _MissingFileError (fromFile l m) $ \f -> do
208214 p <- isGCE m
209215 unless p $
210216 throwingM _MissingFileError f
211− fromMetadata "default" m
217+ fromMetadata "default" l m
212218
213219-- | Refresh a token from the local GCE metadata endpoint for the specified
214220-- 'ServiceId'.
215−fromMetadata :: (MonadIO m, MonadCatch m) => ServiceId -> Manager -> m Auth
216−fromMetadata s = refresh s >=> fmap AuthMeta . liftIO . newMVar
221+fromMetadata :: (MonadIO m, MonadCatch m)
222+ => ServiceId
223+ -> Logger
224+ -> Manager
225+ -> m Auth
226+fromMetadata s l = refresh s l >=> fmap AuthMeta . liftIO . newMVar
217227
218228-- | Attempt to load either a @service_account@ or @authorized_user@ formatted
219229-- file to obtain the credentials neccessary to perform a token refresh.
220−fromFile :: (MonadIO m, MonadCatch m) => Manager -> m Auth
221−fromFile m = do
230+fromFile :: (MonadIO m, MonadCatch m) => Logger -> Manager -> m Auth
231+fromFile l m = do
222232 f <- defaultCredentialsPath
223233 case f of
224− Just x -> fromFilePath x m
234+ Just x -> fromFilePath x l m
225235 Nothing -> do
226236 x <- cloudSDKConfigPath
227− fromFilePath x m
237+ fromFilePath x l m
238+
239+data Parse = SA !ServiceAccount | AU !AuthorisedUser
240+
241+instance FromJSON Parse where
242+ parseJSON o = SA <$> parseJSON o <|> AU <$> parseJSON o
228243
229244-- | Attempt to load either a @service_account@ or @authorized_user@ formatted
230245-- file to obtain the credentials neccessary to perform a token refresh from
231246-- the specified file.
232−fromFilePath :: (MonadIO m, MonadCatch m) => FilePath -> Manager -> m Auth
233−fromFilePath f m = do
247+fromFilePath :: (MonadIO m, MonadCatch m)
248+ => FilePath
249+ -> Logger
250+ -> Manager
251+ -> m Auth
252+fromFilePath f l m = do
234253 p <- liftIO (doesFileExist f)
235254 unless p $
236255 throwM (MissingFileError f)
237256 e <- liftIO (LBS.readFile f) >>=
238257 either (throwM . InvalidFileError f) pure . parseLBS
239258 case e of
240− Left a -> refresh a m >>= fmap AuthSign . liftIO . newMVar
241− Right u -> refresh u m >>= fmap AuthUser . liftIO . newMVar
259+ SA a -> refresh a l m >>= fmap AuthSign . liftIO . newMVar
260+ AU u -> refresh u l m >>= fmap AuthUser . liftIO . newMVar
242261
243262-- | Lookup the @GOOGLE_APPLICATION_CREDENTIALS@ environment variable for the
244263-- default application credentials filepath.
@@ -289,15 +308,16 @@ data Auth
289308 | AuthUser !(MVar (Expires AuthorisedUser))
290309
291310authorise :: (MonadIO m, MonadCatch m)
292− => Manager
311+ => Logger
312+ -> Manager
293313 -> Client.Request
294314 -> Auth
295315 -> m Client.Request
296−authorise m rq = \case
316+authorise l m rq = \case
297317 AuthToken t -> pure $! authoriseBearer rq t
298− AuthSign r -> authoriseToken m r rq
299− AuthMeta r -> authoriseToken m r rq
300− AuthUser r -> authoriseToken m r rq
318+ AuthSign r -> authoriseToken l m r rq
319+ AuthMeta r -> authoriseToken l m r rq
320+ AuthUser r -> authoriseToken l m r rq
301321
302322authoriseBearer :: Client.Request -> OAuthToken -> Client.Request
303323authoriseBearer rq t = rq
@@ -306,17 +326,19 @@ authoriseBearer rq t = rq
306326 }
307327
308328authoriseToken :: (MonadIO m, MonadCatch m, Refresh a)
309− => Manager
329+ => Logger
330+ -> Manager
310331 -> MVar (Expires a)
311332 -> Client.Request
312333 -> m Client.Request
313−authoriseToken m r rq = authoriseBearer rq <$> refreshToken m r
334+authoriseToken l m r rq = authoriseBearer rq <$> refreshToken l m r
314335
315336refreshToken :: (MonadIO m, MonadCatch m, Refresh a)
316− => Manager
337+ => Logger
338+ -> Manager
317339 -> MVar (Expires a)
318340 -> m OAuthToken
319−refreshToken m r = do
341+refreshToken l m r = do
320342 x <- liftIO (readMVar r)
321343 xv <- isValid x
322344 if xv
@@ -326,7 +348,7 @@ refreshToken m r = do
326348 if yv
327349 then pure (y, _token y)
328350 else do
329− z <- refresh (_details y) m
351+ z <- refresh (_details y) l m
330352 pure (z, _token z)
331353
332354-- {
@@ -400,10 +422,14 @@ instance FromJSON AuthorisedUser where
400422 <*> o .: "refresh_token"
401423
402424class Refresh a where
403− refresh :: (MonadIO m, MonadCatch m) => a -> Manager -> m (Expires a)
425+ refresh :: (MonadIO m, MonadCatch m)
426+ => a
427+ -> Logger
428+ -> Manager
429+ -> m (Expires a)
404430
405431instance Refresh ServiceAccount where
406− refresh s m = do
432+ refresh s l m = do
407433 b <- jwtEncode s
408434 let rq = accountsRequest
409435 { Client.requestBody = RequestBodyBS $
@@ -411,7 +437,7 @@ instance Refresh ServiceAccount where
411437 <> "&assertion="
412438 <> b
413439 }
414− refreshRequest s rq m
440+ refreshRequest s rq l m
415441
416442jwtEncode :: (MonadIO m, MonadThrow m) => ServiceAccount -> m ByteString
417443jwtEncode s = liftIO $ do
@@ -477,15 +503,22 @@ accountsRequest = def
477503refreshRequest :: (MonadIO m, MonadCatch m)
478504 => a
479505 -> Client.Request
506+ -> Logger
480507 -> Manager
481508 -> m (Expires a)
482−refreshRequest r rq m = do
509+refreshRequest r rq l m = do
510+ logDebug l rq -- debug:ClientRequest
511+
483512 rs <- liftIO (httpLbs rq m) `catch` (throwM . RetrievalError)
513+
514+ logDebug l rs -- debug:ClientResponse
515+
484516 let bs = responseBody rs
485517 s = responseStatus rs
518+
486519 if fromEnum s == 200
487520 then success s bs
488− else failure s bs
521+ else failure s
489522 where
490523 success s bs = do
491524 f <- parseErr s bs
@@ -493,17 +526,24 @@ refreshRequest r rq m = do
493526 let Bearer {..} = f ts
494527 pure $! Expires _bearerExpiry _bearerAccess r
495528
496− failure s = const $
497− refreshErr s ("Failure refreshing token from " <> Text.decodeUtf8 (Client.host rq))
498− Nothing
529+ failure s = do
530+ let e = "Failure refreshing token from " <> host <> path
531+ logError l $ "[Refresh Error] " <> build e
532+ refreshErr s e Nothing
499533
500534 parseErr s bs =
501535 case parseLBS bs of
502− Left e -> refreshErr s e Nothing
503536 Right !x -> pure x
537+ Left e -> do
538+ logError l $ "[Parse Error] Failure parsing token refresh " <> build e
539+ refreshErr s e Nothing
504540
541+ refreshErr :: MonadThrow m => Status -> Text -> Maybe Text -> m a
505542 refreshErr s e = throwM . TokenRefreshError s e
506543
544+ host = Text.decodeUtf8 (Client.host rq)
545+ path = Text.decodeUtf8 (Client.path rq)
546+
507547parseLBS :: FromJSON a => LBS.ByteString -> Either Text a
508548parseLBS = either (Left . Text.pack) Right . eitherDecode'
509549