{-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DataKinds #-} -- | -- Module : Network.Google.Auth.InstalledApplication -- Copyright : (c) 2015-2016 Brendan Hay -- License : Mozilla Public License, v. 2.0. -- Maintainer : Brendan Hay <brendan.g.hay@gmail.com> -- Stability : provisional -- Portability : non-portable (GHC extensions) -- -- Credentials for applications that are installed on devices such as -- computers, cell phones, or a tablet. Installed apps are distributed to -- individual machines, and it is assumed that these apps securely store secrets. -- -- These apps might access a Google service while the user is present at the -- application, or when the application is running in the background. -- -- /See:/ <https://developers.google.com/identity/protocols/OAuth2InstalledApp Installed Application Documentation>. module Network.Google.Auth.InstalledApplication ( installedApplication -- * Forming the URL , AccessType (..) , redirectURI , formURL , formAccessTypeURL , formURLWith , formAccessTypeURLWith -- * Internal Exchange and Refresh , exchangeCode , refreshToken ) where 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 (..), queryEncodeScopes) import Network.Google.Internal.Auth import Network.Google.Internal.Logger (Logger) import Network.Google.Prelude import Network.HTTP.Conduit (Manager) import qualified Network.HTTP.Conduit as Client -- | Create new Installed Application credentials. -- -- Since it is intended that the user opens the URL generated by 'formURL' in a browser -- and the resulting 'OAuthCode' is then received out-of-band, -- you must ensure that the scopes passed to 'formURL' and the type of 'OAuthCode' -- correctly match, otherwise an authorization error will occur. -- -- For example, doing this via 'getLine' and copy-paste: -- -- > {-# LANGUAGE ScopedTypeVariables #-} -- -- > import Data.Proxy (Proxy (..)) -- > import Data.Text as T -- > import Data.Text.IO as T -- > import System.Exit (exitFailure) -- > import System.Info (os) -- > import System.Process (rawSystem) -- -- > redirectPrompt :: AllowScopes (s :: [Symbol]) => OAuthClient -> proxy s -> IO (OAuthCode s) -- > redirectPrompt c p = do -- > let url = formURL c p -- > T.putStrLn $ "Opening URL " `T.append` url -- > _ <- case os of -- > "darwin" -> rawSystem "open" [unpack url] -- > "linux" -> rawSystem "xdg-open" [unpack url] -- > _ -> T.putStrLn "Unsupported OS" >> exitFailure -- > T.putStrLn "Please input the authorisation code: " -- > OAuthCode <$> T.getLine -- -- This ensures the scopes passed to 'formURL' and the type of 'OAuthCode' 's' -- are correct. 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" -- | Given an 'OAuthClient' and a list of scopes to authorize, -- construct a URL that can be used to obtain the 'OAuthCode'. -- -- /See:/ <https://developers.google.com/accounts/docs/OAuth2InstalledApp#formingtheurl Forming the URL>. 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'. formURLWith :: OAuthClient -> [OAuthScope] -> Text formURLWith c ss = accountsURL <> "?response_type=code" <> "&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'. -- -- /See:/ <https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse Exchanging the code>. exchangeCode :: (MonadIO m, MonadCatch m) => OAuthClient -> (OAuthCode s) -> Logger -> Manager -> m (OAuthToken s) exchangeCode c n = refreshRequest $ tokenRequest { Client.requestBody = textBody $ "grant_type=authorization_code" <> "&client_id=" <> toQueryParam (_clientId c) <> "&client_secret=" <> toQueryParam (_clientSecret c) <> "&code=" <> toQueryParam n <> "&redirect_uri=" <> redirectURI } -- | Perform a refresh to obtain a valid 'OAuthToken' with a new expiry time. -- -- /See:/ <https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh Refreshing tokens>. refreshToken :: (MonadIO m, MonadCatch m) => OAuthClient -> (OAuthToken s) -> Logger -> Manager -> m (OAuthToken s) refreshToken c t = refreshRequest $ accountsRequest { Client.requestBody = textBody $ "grant_type=refresh_token" <> "&client_id=" <> toQueryParam (_clientId c) <> "&client_secret=" <> toQueryParam (_clientSecret c) <> maybe mempty ("&refresh_token=" <>) (toQueryParam <$> _tokenRefresh t) }