gogol 0.2.0 → 0.3.0
raw patch · 6 files changed
+95/−6 lines, 6 filesdep ~gogol-corePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: gogol-core
API changes (from Hackage documentation)
+ Network.Google.Auth: FileExistError :: FilePath -> AuthError
+ Network.Google.Auth: authToAuthorizedUser :: AllowScopes s => Auth s -> Either Text AuthorizedUser
+ Network.Google.Auth: retrieveAuthFromStore :: (MonadIO m, MonadCatch m, AllowScopes s) => Store s -> m (Auth s)
+ Network.Google.Auth: saveAuthorizedUser :: (MonadIO m, MonadCatch m) => FilePath -> Bool -> AuthorizedUser -> m ()
+ Network.Google.Auth: saveAuthorizedUserToWellKnownPath :: (MonadIO m, MonadCatch m) => Bool -> AuthorizedUser -> m ()
+ Network.Google.Auth.ApplicationDefault: saveAuthorizedUser :: (MonadIO m, MonadCatch m) => FilePath -> Bool -> AuthorizedUser -> m ()
+ Network.Google.Auth.ApplicationDefault: saveAuthorizedUserToWellKnownPath :: (MonadIO m, MonadCatch m) => Bool -> AuthorizedUser -> m ()
+ Network.Google.Auth.InstalledApplication: Offline :: AccessType
+ Network.Google.Auth.InstalledApplication: Online :: AccessType
+ Network.Google.Auth.InstalledApplication: data AccessType
+ Network.Google.Auth.InstalledApplication: formAccessTypeURL :: AllowScopes (s :: [Symbol]) => OAuthClient -> AccessType -> proxy s -> Text
+ Network.Google.Auth.InstalledApplication: formAccessTypeURLWith :: OAuthClient -> AccessType -> [OAuthScope] -> Text
+ Network.Google.Auth.InstalledApplication: instance GHC.Classes.Eq Network.Google.Auth.InstalledApplication.AccessType
+ Network.Google.Auth.InstalledApplication: instance GHC.Show.Show Network.Google.Auth.InstalledApplication.AccessType
Files
- CHANGELOG.md +13/−0
- gogol.cabal +2/−2
- src/Network/Google/Auth.hs +23/−0
- src/Network/Google/Auth/ApplicationDefault.hs +28/−3
- src/Network/Google/Auth/InstalledApplication.hs +20/−0
- src/Network/Google/Internal/Auth.hs +9/−1
CHANGELOG.md view
@@ -1,5 +1,18 @@ # Change Log +## [0.3.0](https://github.com/brendanhay/gogol/tree/0.3.0)++Released: **12 July, 2017**, Compare: [0.3.0](https://github.com/brendanhay/gogol/compare/0.2.0...0.3.0)++### Added++- Addition of offline mode and save + refresh for credentials. [\#66](https://github.com/brendanhay/gogol/pull/66)++### Fixed++- Corrected `Duration` type serialisation. [\#68](https://github.com/brendanhay/gogol/pull/68)++ ## [0.2.0](https://github.com/brendanhay/gogol/tree/0.2.0) Released: **12 February, 2017**, Compare: [0.1.1](https://github.com/brendanhay/gogol/compare/0.1.1...0.2.0)
gogol.cabal view
@@ -1,5 +1,5 @@ name: gogol-version: 0.2.0+version: 0.3.0 synopsis: Comprehensive Google Services SDK. homepage: https://github.com/brendanhay/gogol bug-reports: https://github.com/brendanhay/gogol/issues@@ -66,7 +66,7 @@ , directory >= 1.2 , exceptions >= 0.6 , filepath >= 1.2- , gogol-core == 0.2.0.*+ , gogol-core == 0.3.0.* , http-client >= 0.5 && < 1 , http-conduit >= 2.2 && < 3 , http-media >= 0.6
src/Network/Google/Auth.hs view
@@ -22,6 +22,8 @@ , getApplicationDefault , fromWellKnownPath , fromFilePath+ , saveAuthorizedUserToWellKnownPath+ , saveAuthorizedUser -- ** Installed Application Credentials , installedApplication@@ -33,8 +35,10 @@ -- ** Thread-safe Storage , Store , initStore+ , retrieveAuthFromStore , Auth (..)+ , authToAuthorizedUser , exchange , refresh @@ -81,6 +85,19 @@ import qualified Network.HTTP.Conduit as Client import Network.HTTP.Types (hAuthorization) +-- | 'authToAuthorizedUser' converts 'Auth' into an 'AuthorizedUser'+-- by returning 'Right' if there is a 'FromClient'-constructed+-- Credentials and a refreshed token; otherwise, returning+-- 'Left' with error message.+authToAuthorizedUser :: AllowScopes s => Auth s -> Either Text AuthorizedUser+authToAuthorizedUser a = AuthorizedUser+ <$> (_clientId <$> getClient)+ <*> maybe (Left "no refresh token") Right (_tokenRefresh (_token a))+ <*> (_clientSecret <$> getClient)+ where getClient = case _credentials a of+ FromClient c _ -> Right c+ _ -> Left "not FromClient"+ -- | An 'OAuthToken' that can potentially be expired, with the original -- credentials that can be used to perform a refresh. data Auth (s :: [Symbol]) = Auth@@ -109,6 +126,12 @@ -> Manager -> m (Store s) initStore c l m = exchange c l m >>= fmap Store . liftIO . newMVar++-- | Retrieve auth from storage+retrieveAuthFromStore :: (MonadIO m, MonadCatch m, AllowScopes s)+ => Store s+ -> m (Auth s)+retrieveAuthFromStore (Store s) = liftIO (readMVar s) -- | Concurrently read the current token, and if expired, then -- safely perform a single serial refresh.
src/Network/Google/Auth/ApplicationDefault.hs view
@@ -20,20 +20,21 @@ import Control.Applicative import Control.Exception.Lens (catching, throwingM)-import Control.Monad (unless)+import Control.Monad (unless, when) import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO (..)) import Data.Aeson import Data.Aeson.Types (parseEither) import qualified Data.ByteString.Lazy as LBS+import Data.Maybe (maybe) import qualified Data.Text as Text import Network.Google.Compute.Metadata (isGCE) import Network.Google.Internal.Auth import Network.HTTP.Conduit (Manager)-import System.Directory (doesFileExist, getHomeDirectory)+import System.Directory (doesFileExist, getHomeDirectory, createDirectoryIfMissing) import System.Environment (lookupEnv)-import System.FilePath ((</>))+import System.FilePath ((</>), takeDirectory) import System.Info (os) -- | The environment variable name which is used to specify the directory@@ -125,6 +126,30 @@ bs <- liftIO (LBS.readFile f) either (throwM . InvalidFileError f . Text.pack) pure (fromJSONCredentials bs)++-- | Save 'AuthorizedUser'+-- /See:/ 'cloudSDKConfigPath', 'defaultCredentialsPath'.+saveAuthorizedUserToWellKnownPath :: (MonadIO m, MonadCatch m)+ => Bool -- ^ Force to save if True+ -> AuthorizedUser+ -> m ()+saveAuthorizedUserToWellKnownPath b a = do+ d <- defaultCredentialsPath+ f <- maybe cloudSDKConfigPath pure d+ liftIO $ createDirectoryIfMissing True $ takeDirectory f+ saveAuthorizedUser f b a++-- | Save 'AuthorizedUser'+saveAuthorizedUser :: (MonadIO m, MonadCatch m)+ => FilePath+ -> Bool -- ^ Force to save if True+ -> AuthorizedUser+ -> m ()+saveAuthorizedUser f b a = do+ p <- liftIO (doesFileExist f)+ when (p && not b) $+ throwM (FileExistError f)+ liftIO (LBS.writeFile f $ encode a) -- | Attempt to parse either a @service_account@ or @authorized_user@ formatted -- JSON value to obtain credentials.
src/Network/Google/Auth/InstalledApplication.hs view
@@ -22,9 +22,12 @@ ( installedApplication -- * Forming the URL+ , AccessType (..) , redirectURI , formURL+ , formAccessTypeURL , formURLWith+ , formAccessTypeURLWith -- * Internal Exchange and Refresh , exchangeCode@@ -33,6 +36,7 @@ import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (MonadIO)+import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import GHC.TypeLits (Symbol) import Network.Google.Auth.Scope (AllowScopes (..),@@ -77,6 +81,9 @@ installedApplication :: OAuthClient -> OAuthCode s -> Credentials s installedApplication = FromClient +-- /See:/ <https://developers.google.com/identity/protocols/OAuth2WebServer#offline>+data AccessType = Online | Offline deriving (Show, Eq)+ -- | The redirection URI used in 'formURL': @urn:ietf:wg:oauth:2.0:oob@. redirectURI :: Text redirectURI = "urn:ietf:wg:oauth:2.0:oob"@@ -88,6 +95,12 @@ formURL :: AllowScopes (s :: [Symbol]) => OAuthClient -> proxy s -> Text formURL c = formURLWith c . allowScopes +-- | 'formURL' for 'AccessType'+--+-- /See:/ 'formUrl'.+formAccessTypeURL :: AllowScopes (s :: [Symbol]) => OAuthClient -> AccessType -> proxy s -> Text+formAccessTypeURL c a = formAccessTypeURLWith c a . allowScopes+ -- | Form a URL using 'OAuthScope' values. -- -- /See:/ 'formURL'.@@ -97,6 +110,13 @@ <> "&client_id=" <> toQueryParam (_clientId c) <> "&redirect_uri=" <> redirectURI <> "&scope=" <> Text.decodeUtf8 (queryEncodeScopes ss)++-- | 'formURLWith' for 'AccessType'+--+-- /See:/ 'formURLWith'.+formAccessTypeURLWith :: OAuthClient -> AccessType -> [OAuthScope] -> Text+formAccessTypeURLWith c a ss = formURLWith c ss+ <> "&access_type=" <> (Text.toLower . Text.pack $ show a) -- | Exchange 'OAuthClient' details and the received 'OAuthCode' for a new -- 'OAuthToken'.
src/Network/Google/Internal/Auth.hs view
@@ -129,6 +129,13 @@ , _userSecret :: !Secret } deriving (Eq, Show) +instance ToJSON AuthorizedUser where+ toJSON (AuthorizedUser i r s) =+ object [ "client_id" .= i+ , "refresh_token" .= r+ , "client_secret" .= s+ ]+ instance FromJSON AuthorizedUser where parseJSON = withObject "authorized_user" $ \o -> AuthorizedUser <$> o .: "client_id"@@ -175,12 +182,13 @@ toQueryParam (OAuthCode c) = c toHeader (OAuthCode c) = Text.encodeUtf8 c --- | An error thrown when attempting to read AuthN/AuthZ information.+-- | An error thrown when attempting to read/write AuthN/AuthZ information. data AuthError = RetrievalError HttpException | MissingFileError FilePath | InvalidFileError FilePath Text | TokenRefreshError Status Text (Maybe Text)+ | FileExistError FilePath deriving (Show, Typeable) instance Exception AuthError