hoauth2 2.2.0 → 2.3.0
raw patch · 6 files changed
+575/−363 lines, 6 filesdep +containersdep +data-defaultdep −hashabledep −unordered-containersPVP ok
version bump matches the API change (PVP)
Dependencies added: containers, data-default
Dependencies removed: hashable, unordered-containers
API changes (from Hackage documentation)
- Network.OAuth.OAuth2.HttpClient: authRequest :: Request -> (Request -> Request) -> Manager -> ExceptT ByteString IO ByteString
- Network.OAuth.OAuth2.HttpClient: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO ByteString
- Network.OAuth.OAuth2.HttpClient: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
- Network.OAuth.OAuth2.HttpClient: fetchAccessToken2 :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
- Network.OAuth.OAuth2.HttpClient: refreshAccessToken :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
- Network.OAuth.OAuth2.HttpClient: refreshAccessToken2 :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
- Network.OAuth.OAuth2.Internal: accessTokenToParam :: AccessToken -> [(ByteString, ByteString)]
- Network.OAuth.OAuth2.Internal: accessTokenUrl :: OAuth2 -> ExchangeToken -> (URI, PostBody)
- Network.OAuth.OAuth2.Internal: accessTokenUrl' :: OAuth2 -> ExchangeToken -> Maybe Text -> (URI, PostBody)
- Network.OAuth.OAuth2.Internal: appendAccessToken :: URIRef a -> AccessToken -> URIRef a
- Network.OAuth.OAuth2.Internal: authorizationUrl :: OAuth2 -> URI
- Network.OAuth.OAuth2.Internal: instance Data.Hashable.Class.Hashable Network.OAuth.OAuth2.Internal.OAuth2
- Network.OAuth.OAuth2.Internal: refreshAccessTokenUrl :: OAuth2 -> RefreshToken -> (URI, PostBody)
+ Network.OAuth.OAuth2: accessTokenUrl :: OAuth2 -> ExchangeToken -> (URI, PostBody)
+ Network.OAuth.OAuth2: addDefaultRequestHeaders :: Request -> Request
+ Network.OAuth.OAuth2: authorizationUrl :: OAuth2 -> URI
+ Network.OAuth.OAuth2: clientSecretPost :: OAuth2 -> PostBody
+ Network.OAuth.OAuth2: doJSONPostRequest :: (FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO a
+ Network.OAuth.OAuth2: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO ByteString
+ Network.OAuth.OAuth2: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: fetchAccessToken2 :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: fetchAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: handleOAuth2TokenResponse :: FromJSON err => Response ByteString -> Either (OAuth2Error err) ByteString
+ Network.OAuth.OAuth2: parseResponseFlexible :: (FromJSON err, FromJSON a) => ByteString -> Either (OAuth2Error err) a
+ Network.OAuth.OAuth2: parseResponseString :: (FromJSON err, FromJSON a) => ByteString -> Either (OAuth2Error err) a
+ Network.OAuth.OAuth2: refreshAccessToken :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessToken2 :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessTokenUrl :: OAuth2 -> RefreshToken -> (URI, PostBody)
+ Network.OAuth.OAuth2.AuthorizationRequest: authorizationUrl :: OAuth2 -> URI
+ Network.OAuth.OAuth2.HttpClient: authGetBSInternal :: Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authGetJSONInternal :: FromJSON b => Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString IO b
+ Network.OAuth.OAuth2.HttpClient: authPostBS1 :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBSInternal :: Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostJSONInternal :: FromJSON a => Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO a
+ Network.OAuth.OAuth2.Internal: AuthInRequestBody :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: AuthInRequestHeader :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: AuthInRequestQuery :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: ClientSecretBasic :: ClientAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: ClientSecretPost :: ClientAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: data APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: data ClientAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: defaultRequestHeaders :: [(HeaderName, ByteString)]
+ Network.OAuth.OAuth2.Internal: instance Data.Default.Class.Default Network.OAuth.OAuth2.Internal.OAuth2
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.ClientAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Ord Network.OAuth.OAuth2.Internal.APIAuthenticationMethod
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Ord Network.OAuth.OAuth2.Internal.ClientAuthenticationMethod
+ Network.OAuth.OAuth2.TokenRequest: accessTokenUrl :: OAuth2 -> ExchangeToken -> (URI, PostBody)
+ Network.OAuth.OAuth2.TokenRequest: addDefaultRequestHeaders :: Request -> Request
+ Network.OAuth.OAuth2.TokenRequest: clientSecretPost :: OAuth2 -> PostBody
+ Network.OAuth.OAuth2.TokenRequest: doJSONPostRequest :: (FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO a
+ Network.OAuth.OAuth2.TokenRequest: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO ByteString
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessToken2 :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: handleOAuth2TokenResponse :: FromJSON err => Response ByteString -> Either (OAuth2Error err) ByteString
+ Network.OAuth.OAuth2.TokenRequest: parseResponseFlexible :: (FromJSON err, FromJSON a) => ByteString -> Either (OAuth2Error err) a
+ Network.OAuth.OAuth2.TokenRequest: parseResponseString :: (FromJSON err, FromJSON a) => ByteString -> Either (OAuth2Error err) a
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessToken :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessToken2 :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessTokenUrl :: OAuth2 -> RefreshToken -> (URI, PostBody)
- Network.OAuth.OAuth2.HttpClient: authPostBS3 :: Manager -> AccessToken -> URI -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBS3 :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
- Network.OAuth.OAuth2.Internal: OAuth2 :: Text -> Text -> URIRef Absolute -> URIRef Absolute -> Maybe (URIRef Absolute) -> OAuth2
+ Network.OAuth.OAuth2.Internal: OAuth2 :: Text -> Text -> URIRef Absolute -> URIRef Absolute -> URIRef Absolute -> OAuth2
- Network.OAuth.OAuth2.Internal: [oauth2RedirectUri] :: OAuth2 -> Maybe (URIRef Absolute)
+ Network.OAuth.OAuth2.Internal: [oauth2RedirectUri] :: OAuth2 -> URIRef Absolute
Files
- hoauth2.cabal +4/−5
- src/Network/OAuth/OAuth2.hs +18/−9
- src/Network/OAuth/OAuth2/AuthorizationRequest.hs +35/−6
- src/Network/OAuth/OAuth2/HttpClient.hs +219/−246
- src/Network/OAuth/OAuth2/Internal.hs +44/−91
- src/Network/OAuth/OAuth2/TokenRequest.hs +255/−6
hoauth2.cabal view
@@ -1,7 +1,7 @@ Cabal-version: 2.4 Name: hoauth2 -- http://wiki.haskell.org/Package_versioning_policy-Version: 2.2.0+Version: 2.3.0 Synopsis: Haskell OAuth2 authentication client @@ -52,20 +52,19 @@ Network.OAuth.OAuth2.AuthorizationRequest Build-Depends: base >= 4 && < 5,+ data-default >= 0.7 && < 0.8, binary >= 0.8.3 && < 0.8.9,+ containers >= 0.6 && < 0.7, text >= 0.11 && < 1.3, bytestring >= 0.9 && < 0.11, http-conduit >= 2.1 && < 2.4, http-types >= 0.11 && < 0.13, aeson >= 2.0 && < 2.1, transformers >= 0.5 && < 0.6,- unordered-containers >= 0.2.5 && < 0.3, uri-bytestring >= 0.2.3 && < 0.4, uri-bytestring-aeson >= 0.1 && < 0.2, microlens >= 0.4.0 && < 0.5,- hashable >= 1.2.6 && < 1.5.0, exceptions >= 0.8.3 && < 0.11 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields- -fno-warn-unused-do-bind-+ -fno-warn-unused-do-bind -Wunused-packages
src/Network/OAuth/OAuth2.hs view
@@ -1,21 +1,30 @@ ------------------------------------------------------------++------------------------------------------------------------+ -- | -- Module : Network.OAuth.OAuth2 -- Description : OAuth2 client -- Copyright : (c) 2012 Haisheng Wu -- License : BSD-style (see the file LICENSE) -- Maintainer : Haisheng Wu <freizl@gmail.com>--- Stability : alpha+-- Stability : Beta -- Portability : portable -- -- A lightweight oauth2 haskell binding.-------------------------------------------------------------- module Network.OAuth.OAuth2- (module Network.OAuth.OAuth2.HttpClient,- module Network.OAuth.OAuth2.Internal- )- where+ ( module Network.OAuth.OAuth2.HttpClient,+ module Network.OAuth.OAuth2.AuthorizationRequest,+ module Network.OAuth.OAuth2.TokenRequest,+ module Network.OAuth.OAuth2.Internal,+ )+where -import Network.OAuth.OAuth2.HttpClient-import Network.OAuth.OAuth2.Internal+{-+ Hiding Errors data type from default.+ Shall qualified import given the naming conflicts.+-}+import Network.OAuth.OAuth2.AuthorizationRequest hiding (Errors(..))+import Network.OAuth.OAuth2.HttpClient+import Network.OAuth.OAuth2.Internal+import Network.OAuth.OAuth2.TokenRequest hiding (Errors(..))
src/Network/OAuth/OAuth2/AuthorizationRequest.hs view
@@ -1,19 +1,31 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} module Network.OAuth.OAuth2.AuthorizationRequest where -import Data.Aeson-import GHC.Generics+import Data.Aeson+import qualified Data.Text.Encoding as T+import GHC.Generics+import Lens.Micro+import Network.OAuth.OAuth2.Internal+import URI.ByteString +--------------------------------------------------++-- * Errors++--------------------------------------------------+ instance FromJSON Errors where- parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ parseJSON = genericParseJSON defaultOptions {constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True}+ instance ToJSON Errors where- toEncoding = genericToEncoding defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ toEncoding = genericToEncoding defaultOptions {constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True} -- | Authorization Code Grant Error Responses https://tools.ietf.org/html/rfc6749#section-4.1.2.1 -- Implicit Grant Error Responses https://tools.ietf.org/html/rfc6749#section-4.2.2.1-data Errors =- InvalidRequest+data Errors+ = InvalidRequest | UnauthorizedClient | AccessDenied | UnsupportedResponseType@@ -21,3 +33,20 @@ | ServerError | TemporarilyUnavailable deriving (Show, Eq, Generic)++--------------------------------------------------++-- * URLs++--------------------------------------------------++-- | Prepare the authorization URL. Redirect to this URL+-- asking for user interactive authentication.+authorizationUrl :: OAuth2 -> URI+authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)+ where+ queryParts =+ [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),+ ("response_type", "code"),+ ("redirect_uri", serializeURIRef' $ oauth2RedirectUri oa)+ ]
src/Network/OAuth/OAuth2/HttpClient.hs view
@@ -1,281 +1,243 @@-{-# LANGUAGE ExplicitForAll #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -- | A simple http client to request OAuth2 tokens and several utils.--module Network.OAuth.OAuth2.HttpClient (--- * Token management- fetchAccessToken,- fetchAccessToken2,- refreshAccessToken,- refreshAccessToken2,- doSimplePostRequest,--- * AUTH requests- authGetJSON,- authGetBS,- authGetBS2,- authPostJSON,- authPostBS,- authPostBS2,- authPostBS3,- authRequest-) where+module Network.OAuth.OAuth2.HttpClient+ ( -- * AUTH requests+ authGetJSON,+ authGetBS,+ authGetBS2,+ authGetJSONInternal,+ authGetBSInternal,+ authPostJSON,+ authPostBS,+ authPostBS1,+ authPostBS2,+ authPostBS3,+ authPostJSONInternal,+ authPostBSInternal,+ )+where +import qualified Data.Set as Set import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Except-import qualified Data.Aeson.KeyMap as KeyMap-import qualified Data.Aeson.Key as Key-import Data.Aeson-import qualified Data.ByteString.Char8 as BS-import qualified Data.ByteString.Lazy.Char8 as BSL-import Data.Maybe-import qualified Data.Text.Encoding as T-import Network.HTTP.Conduit-import qualified Network.HTTP.Types as HT-import Network.HTTP.Types.URI (parseQuery)-import Network.OAuth.OAuth2.Internal-import qualified Network.OAuth.OAuth2.TokenRequest as TR-import URI.ByteString+import Data.Aeson+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.Maybe+import qualified Data.Text.Encoding as T+import Lens.Micro+import Network.HTTP.Conduit+import qualified Network.HTTP.Types as HT+import Network.OAuth.OAuth2.Internal+import URI.ByteString ----------------------------------------------------- * Token management--------------------------------------------------- --- | Fetch OAuth2 Token with authenticate in request header.------ OAuth2 spec allows `client_id` and `client_secret` to--- either be sent in the header (as basic authentication)--- OR as form/url params.--- The OAuth server can choose to implement only one, or both.--- Unfortunately, there is no way for the OAuth client (i.e. this library) to--- know which method to use. Please take a look at the documentation of the--- service that you are integrating with and either use `fetchAccessToken` or `fetchAccessToken2`-fetchAccessToken :: Manager -- ^ HTTP connection manager- -> OAuth2 -- ^ OAuth Data- -> ExchangeToken -- ^ OAuth2 Code- -> ExceptT (OAuth2Error TR.Errors) IO OAuth2Token -- ^ Access Token-fetchAccessToken manager oa code = doJSONPostRequest manager oa uri body- where (uri, body) = accessTokenUrl oa code---- | Please read the docs of `fetchAccessToken`.----fetchAccessToken2 :: Manager -- ^ HTTP connection manager- -> OAuth2 -- ^ OAuth Data- -> ExchangeToken -- ^ OAuth 2 Tokens- -> ExceptT (OAuth2Error TR.Errors) IO OAuth2Token -- ^ Access Token-fetchAccessToken2 mgr oa code = do- let (url, body1) = accessTokenUrl oa code- let extraBody = [- ("client_id", T.encodeUtf8 $ oauth2ClientId oa),- ("client_secret", T.encodeUtf8 $ oauth2ClientSecret oa)- ]- doJSONPostRequest mgr oa url (extraBody ++ body1)---- | Fetch a new AccessToken with the Refresh Token with authentication in request header.--- OAuth2 spec allows `client_id` and `client_secret` to--- either be sent in the header (as basic authentication)--- OR as form/url params.--- The OAuth server can choose to implement only one, or both.--- Unfortunately, there is no way for the OAuth client (i.e. this library) to--- know which method to use. Please take a look at the documentation of the--- service that you are integrating with and either use `refreshAccessToken` or `refreshAccessToken2`-refreshAccessToken :: Manager -- ^ HTTP connection manager.- -> OAuth2 -- ^ OAuth context- -> RefreshToken -- ^ refresh token gained after authorization- -> ExceptT (OAuth2Error TR.Errors) IO OAuth2Token-refreshAccessToken manager oa token = doJSONPostRequest manager oa uri body- where (uri, body) = refreshAccessTokenUrl oa token---- | Please read the docs of `refreshAccessToken`.----refreshAccessToken2 :: Manager -- ^ HTTP connection manager.- -> OAuth2 -- ^ OAuth context- -> RefreshToken -- ^ refresh token gained after authorization- -> ExceptT (OAuth2Error TR.Errors) IO OAuth2Token-refreshAccessToken2 manager oa token = do- let (uri, body) = refreshAccessTokenUrl oa token- let extraBody = [- ("client_id", T.encodeUtf8 $ oauth2ClientId oa),- ("client_secret", T.encodeUtf8 $ oauth2ClientSecret oa)- ]- doJSONPostRequest manager oa uri (extraBody ++ body)---- | Conduct post request and return response as JSON.-doJSONPostRequest :: (FromJSON err, FromJSON a)- => Manager -- ^ HTTP connection manager.- -> OAuth2 -- ^ OAuth options- -> URI -- ^ The URL- -> PostBody -- ^ request body- -> ExceptT (OAuth2Error err) IO a -- ^ Response as JSON-doJSONPostRequest manager oa uri body = do- resp <- doSimplePostRequest manager oa uri body- case parseResponseFlexible resp of- Right obj -> return obj- Left e -> throwE e---- | Conduct post request.-doSimplePostRequest :: FromJSON err => Manager -- ^ HTTP connection manager.- -> OAuth2 -- ^ OAuth options- -> URI -- ^ URL- -> PostBody -- ^ Request body.- -> ExceptT (OAuth2Error err) IO BSL.ByteString -- ^ Response as ByteString-doSimplePostRequest manager oa url body =- ExceptT $ fmap handleOAuth2TokenResponse go- where- addBasicAuth = applyBasicAuth (T.encodeUtf8 $ oauth2ClientId oa) (T.encodeUtf8 $ oauth2ClientSecret oa)- go = do- req <- uriToRequest url- let req' = (addBasicAuth . updateRequestHeaders Nothing) req- httpLbs (urlEncodedBody body req') manager---- | Parses a @Response@ to to @OAuth2Result@-handleOAuth2TokenResponse :: FromJSON err => Response BSL.ByteString -> Either (OAuth2Error err) BSL.ByteString-handleOAuth2TokenResponse rsp =- if HT.statusIsSuccessful (responseStatus rsp)- then Right $ responseBody rsp- else Left $ parseOAuth2Error (responseBody rsp)---- | Try 'parseResponseJSON', if failed then parses the @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String.-parseResponseFlexible :: (FromJSON err, FromJSON a)- => BSL.ByteString- -> Either (OAuth2Error err) a-parseResponseFlexible r = case eitherDecode r of- Left _ -> parseResponseString r- Right x -> Right x---- | Parses a @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String-parseResponseString :: (FromJSON err, FromJSON a)- => BSL.ByteString- -> Either (OAuth2Error err) a-parseResponseString b = case parseQuery $ BSL.toStrict b of- [] -> Left errorMessage- a -> case fromJSON $ queryToValue a of- Error _ -> Left errorMessage- Success x -> Right x- where- queryToValue = Object . KeyMap.fromList . map paramToPair- paramToPair (k, mv) = (Key.fromText $T.decodeUtf8 k, maybe Null (String . T.decodeUtf8) mv)- errorMessage = parseOAuth2Error b---------------------------------------------------- -- * AUTH requests--- Making request with Access Token injected into header or request body.++-- Making request with Access Token appended to Header, Request body or query string. -- -------------------------------------------------- -- | Conduct an authorized GET request and return response as JSON. -- Inject Access Token to Authorization Header.----authGetJSON :: (FromJSON b)- => Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> ExceptT BSL.ByteString IO b -- ^ Response as JSON-authGetJSON manager t uri = do- resp <- authGetBS manager t uri- case eitherDecode resp of- Right obj -> return obj- Left e -> throwE $ BSL.pack e+authGetJSON ::+ (FromJSON b) =>+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ -- | Response as JSON+ ExceptT BSL.ByteString IO b+authGetJSON = authGetJSONInternal (Set.fromList [AuthInRequestHeader])+{-# DEPRECATED authGetJSON "use authGetJSONInternal" #-} +-- | Conduct an authorized GET request and return response as JSON.+-- Allow to specify how to append AccessToken.+authGetJSONInternal ::+ (FromJSON b) =>+ Set.Set APIAuthenticationMethod ->+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ -- | Response as JSON+ ExceptT BSL.ByteString IO b+authGetJSONInternal authTypes manager t uri = do+ resp <- authGetBSInternal authTypes manager t uri+ either (throwE . BSL.pack) return (eitherDecode resp)+ -- | Conduct an authorized GET request. -- Inject Access Token to Authorization Header.----authGetBS :: Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> ExceptT BSL.ByteString IO BSL.ByteString -- ^ Response as ByteString-authGetBS manager token url = do- req <- uriToRequest url- authRequest req upReq manager- where upReq = updateRequestHeaders (Just token) . setMethod HT.GET+authGetBS ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authGetBS = authGetBSInternal $ Set.fromList [AuthInRequestHeader] -- | Same to 'authGetBS' but set access token to query parameter rather than header----authGetBS2 :: Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> ExceptT BSL.ByteString IO BSL.ByteString -- ^ Response as ByteString-authGetBS2 manager token url = do- req <- liftIO $ uriToRequest (url `appendAccessToken` token)+authGetBS2 ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authGetBS2 = authGetBSInternal $ Set.fromList [AuthInRequestQuery]+{-# DEPRECATED authGetBS2 "use authGetBSInternal" #-}++-- | Conduct an authorized GET request and return response as ByteString.+-- Allow to specify how to append AccessToken.+authGetBSInternal ::+ -- |+ Set.Set APIAuthenticationMethod ->+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authGetBSInternal authTypes manager token url = do+ let appendToUrl = AuthInRequestQuery `Set.member` authTypes+ let appendToHeader = AuthInRequestHeader `Set.member` authTypes+ let uri = if appendToUrl then url `appendAccessToken` token else url+ let upReq = updateRequestHeaders (if appendToHeader then Just token else Nothing) . setMethod HT.GET+ req <- liftIO $ uriToRequest uri authRequest req upReq manager- where upReq = updateRequestHeaders Nothing . setMethod HT.GET -- | Conduct POST request and return response as JSON.--- Inject Access Token to Authorization Header and request body.----authPostJSON :: (FromJSON b)- => Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> PostBody- -> ExceptT BSL.ByteString IO b -- ^ Response as JSON-authPostJSON manager t uri pb = do- resp <- authPostBS manager t uri pb- case eitherDecode resp of- Right obj -> return obj- Left e -> throwE $ BSL.pack e+-- Inject Access Token to Authorization Header.+authPostJSON ::+ (FromJSON b) =>+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as JSON+ ExceptT BSL.ByteString IO b+authPostJSON = authPostJSONInternal $ Set.fromList [AuthInRequestHeader]+{-# DEPRECATED authPostJSON "use authPostJSONInternal" #-} +-- | Conduct POST request and return response as JSON.+-- Allow to specify how to append AccessToken.+authPostJSONInternal ::+ FromJSON a =>+ Set.Set APIAuthenticationMethod ->+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO a+authPostJSONInternal authTypes manager token url body = do+ resp <- authPostBSInternal authTypes manager token url body+ either (throwE . BSL.pack) return (eitherDecode resp)+ -- | Conduct POST request.--- Inject Access Token to http header (Authorization) and request body.----authPostBS :: Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> PostBody- -> ExceptT BSL.ByteString IO BSL.ByteString -- ^ Response as ByteString-authPostBS manager token url pb = do- req <- uriToRequest url- authRequest req upReq manager- where upBody = urlEncodedBody (pb ++ accessTokenToParam token)- upHeaders = updateRequestHeaders (Just token) . setMethod HT.POST- upReq = upHeaders . upBody+-- Inject Access Token to http header (Authorization)+authPostBS ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authPostBS = authPostBSInternal $ Set.fromList [AuthInRequestHeader] +-- | Conduct POST request.+-- Inject Access Token to both http header (Authorization) and request body.+authPostBS1 ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authPostBS1 = authPostBSInternal $ Set.fromList [AuthInRequestBody, AuthInRequestHeader]+{-# DEPRECATED authPostBS1 "use authPostBSInternal" #-}+ -- | Conduct POST request with access token only in the request body but header.----authPostBS2 :: Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> PostBody- -> ExceptT BSL.ByteString IO BSL.ByteString -- ^ Response as ByteString-authPostBS2 manager token url pb = do- req <- uriToRequest url- authRequest req upReq manager- where upBody = urlEncodedBody (pb ++ accessTokenToParam token)- upHeaders = updateRequestHeaders Nothing . setMethod HT.POST- upReq = upHeaders . upBody+authPostBS2 ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authPostBS2 = authPostBSInternal $ Set.fromList [AuthInRequestBody]+{-# DEPRECATED authPostBS2 "use authPostBSInternal" #-} -- | Conduct POST request with access token only in the header and not in body-authPostBS3 :: Manager -- ^ HTTP connection manager.- -> AccessToken- -> URI- -> ExceptT BSL.ByteString IO BSL.ByteString -- ^ Response as ByteString-authPostBS3 manager token url = do+authPostBS3 ::+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authPostBS3 = authPostBSInternal $ Set.fromList [AuthInRequestHeader]+{-# DEPRECATED authPostBS3 "use authPostBSInternal" #-}++-- | Conduct POST request and return response as ByteString.+-- Allow to specify how to append AccessToken.+authPostBSInternal ::+ Set.Set APIAuthenticationMethod ->+ -- | HTTP connection manager.+ Manager ->+ AccessToken ->+ URI ->+ PostBody ->+ -- | Response as ByteString+ ExceptT BSL.ByteString IO BSL.ByteString+authPostBSInternal authTypes manager token url body = do+ let appendToBody = AuthInRequestBody `Set.member` authTypes+ let appendToHeader = AuthInRequestHeader `Set.member` authTypes+ let reqBody = if appendToBody then body ++ accessTokenToParam token else body+ -- TODO: urlEncodedBody send request as 'application/x-www-form-urlencoded'+ -- seems shall go with application/json which is more common?+ let upBody = if null reqBody then id else urlEncodedBody reqBody+ let upHeaders = updateRequestHeaders (if appendToHeader then Just token else Nothing) . setMethod HT.POST+ let upReq = upHeaders . upBody+ req <- uriToRequest url authRequest req upReq manager- where upBody req = req { requestBody = "null" }- upHeaders = updateRequestHeaders (Just token) . setMethod HT.POST- upReq = upHeaders . upBody --- |Send an HTTP request including the Authorization header with the specified--- access token.----authRequest :: Request -- ^ Request to perform- -> (Request -> Request) -- ^ Modify request before sending- -> Manager -- ^ HTTP connection manager.- -> ExceptT BSL.ByteString IO BSL.ByteString-authRequest req upReq manage = ExceptT $ handleResponse <$> httpLbs (upReq req) manage- --------------------------------------------------+ -- * Utilities+ -------------------------------------------------- +-- | Send an HTTP request.+authRequest ::+ -- | Request to perform+ Request ->+ -- | Modify request before sending+ (Request -> Request) ->+ -- | HTTP connection manager.+ Manager ->+ ExceptT BSL.ByteString IO BSL.ByteString+authRequest req upReq manage = ExceptT $ handleResponse <$> httpLbs (upReq req) manage+ -- | Parses a @Response@ to to @OAuth2Result@ handleResponse :: Response BSL.ByteString -> Either BSL.ByteString BSL.ByteString handleResponse rsp =- if HT.statusIsSuccessful (responseStatus rsp)- then Right $ responseBody rsp- else Left $ responseBody rsp+ if HT.statusIsSuccessful (responseStatus rsp)+ then Right $ responseBody rsp+ else -- TODO: better to surface up entire resp so that client can decide what to do when error happens.+ Left $ responseBody rsp -- | Set several header values: -- + userAgennt : `hoauth2`@@ -283,13 +245,24 @@ -- + authorization : 'Bearer' `xxxxx` if 'AccessToken' provided. updateRequestHeaders :: Maybe AccessToken -> Request -> Request updateRequestHeaders t req =- let extras = [ (HT.hUserAgent, "hoauth2")- , (HT.hAccept, "application/json") ]- bearer = [(HT.hAuthorization, "Bearer " `BS.append` T.encodeUtf8 (atoken (fromJust t))) | isJust t]- headers = bearer ++ extras ++ requestHeaders req- in- req { requestHeaders = headers }+ let bearer = [(HT.hAuthorization, "Bearer " `BS.append` T.encodeUtf8 (atoken (fromJust t))) | isJust t]+ headers = bearer ++ defaultRequestHeaders ++ requestHeaders req+ in req {requestHeaders = headers} -- | Set the HTTP method to use. setMethod :: HT.StdMethod -> Request -> Request-setMethod m req = req { method = HT.renderStdMethod m }+setMethod m req = req {method = HT.renderStdMethod m}++-- | For `GET` method API.+appendAccessToken ::+ -- | Base URI+ URIRef a ->+ -- | Authorized Access Token+ AccessToken ->+ -- | Combined Result+ URIRef a+appendAccessToken uri t = over (queryL . queryPairsL) (\query -> query ++ accessTokenToParam t) uri++-- | Create 'QueryParams' with given access token value.+accessTokenToParam :: AccessToken -> [(BS.ByteString, BS.ByteString)]+accessTokenToParam t = [("access_token", T.encodeUtf8 $ atoken t)]
src/Network/OAuth/OAuth2/Internal.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_HADDOCK -ignore-exports #-} @@ -17,17 +17,19 @@ import Data.Binary (Binary) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL+import Data.Default import Data.Maybe import Data.Text (Text, pack, unpack) import Data.Text.Encoding+import GHC.Generics import Lens.Micro import Lens.Micro.Extras import Network.HTTP.Conduit as C import qualified Network.HTTP.Types as H+import qualified Network.HTTP.Types as HT import URI.ByteString import URI.ByteString.Aeson ()-import Data.Hashable-import GHC.Generics+import URI.ByteString.QQ -------------------------------------------------- @@ -41,17 +43,19 @@ oauth2ClientSecret :: Text, oauth2AuthorizeEndpoint :: URIRef Absolute, oauth2TokenEndpoint :: URIRef Absolute,- oauth2RedirectUri :: Maybe ( URIRef Absolute )+ oauth2RedirectUri :: URIRef Absolute } deriving (Show, Eq) -instance Hashable OAuth2 where- hashWithSalt salt OAuth2{..} = salt- `hashWithSalt` hash oauth2ClientId- `hashWithSalt` hash oauth2ClientSecret- `hashWithSalt` hash (show oauth2AuthorizeEndpoint)- `hashWithSalt` hash (show oauth2TokenEndpoint)- `hashWithSalt` hash (show oauth2RedirectUri)+instance Default OAuth2 where+ def =+ OAuth2+ { oauth2ClientId = "",+ oauth2ClientSecret = "",+ oauth2AuthorizeEndpoint = [uri|https://www.example.com/|],+ oauth2TokenEndpoint = [uri|https://www.example.com/|],+ oauth2RedirectUri = [uri|https://www.example.com/|]+ } newtype AccessToken = AccessToken {atoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON) @@ -106,8 +110,8 @@ do err <- (a .: "error") >>= (\str -> Right <$> parseJSON str <|> Left <$> parseJSON str) desc <- a .:? "error_description"- uri <- a .:? "error_uri"- return $ OAuth2Error err desc uri+ errorUri <- a .:? "error_uri"+ return $ OAuth2Error err desc errorUri parseJSON _ = fail "Expected an object" instance ToJSON err => ToJSON (OAuth2Error err) where@@ -125,6 +129,20 @@ (Just $ pack $ "Error: " <> err <> "\n Original Response:\n" <> show (decodeUtf8 $ BSL.toStrict response)) Nothing +data APIAuthenticationMethod+ = -- | Provides in Authorization header+ AuthInRequestHeader+ | -- | Provides in request body+ AuthInRequestBody+ | -- | Provides in request query parameter+ AuthInRequestQuery+ deriving (Eq, Ord)++data ClientAuthenticationMethod+ = ClientSecretBasic+ | ClientSecretPost+ deriving (Eq, Ord)+ -------------------------------------------------- -- * Types Synonym@@ -138,92 +156,27 @@ -------------------------------------------------- --- * URLs+-- * Utilies -------------------------------------------------- --- | Prepare the authorization URL. Redirect to this URL--- asking for user interactive authentication.-authorizationUrl :: OAuth2 -> URI-authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)- where- queryParts =- catMaybes- [ Just ("client_id", encodeUtf8 $ oauth2ClientId oa),- Just ("response_type", "code"),- fmap (("redirect_uri",) . serializeURIRef') (oauth2RedirectUri oa)- ]---- | Prepare the URL and the request body query for fetching an access token.-accessTokenUrl ::- OAuth2 ->- -- | access code gained via authorization URL- ExchangeToken ->- -- | access token request URL plus the request body.- (URI, PostBody)-accessTokenUrl oa code = accessTokenUrl' oa code (Just "authorization_code")---- | Prepare the URL and the request body query for fetching an access token, with--- optional grant type.-accessTokenUrl' ::- OAuth2 ->- -- | access code gained via authorization URL- ExchangeToken ->- -- | Grant Type- Maybe Text ->- -- | access token request URL plus the request body.- (URI, PostBody)-accessTokenUrl' oa code gt = (uri, body)- where- uri = oauth2TokenEndpoint oa- body =- catMaybes- [ Just ("code", encodeUtf8 $ extoken code),- ("redirect_uri",) . serializeURIRef' <$> oauth2RedirectUri oa,- fmap (("grant_type",) . encodeUtf8) gt- ]---- | Using a Refresh Token. Obtain a new access token by--- sending a refresh token to the Authorization server.-refreshAccessTokenUrl ::- OAuth2 ->- -- | refresh token gained via authorization URL- RefreshToken ->- -- | refresh token request URL plus the request body.- (URI, PostBody)-refreshAccessTokenUrl oa token = (uri, body)- where- uri = oauth2TokenEndpoint oa- body =- [ ("grant_type", "refresh_token"),- ("refresh_token", encodeUtf8 $ rtoken token)- ]---- | For `GET` method API.-appendAccessToken ::- -- | Base URI- URIRef a ->- -- | Authorized Access Token- AccessToken ->- -- | Combined Result- URIRef a-appendAccessToken uri t = over (queryL . queryPairsL) (\query -> query ++ accessTokenToParam t) uri---- | Create 'QueryParams' with given access token value.-accessTokenToParam :: AccessToken -> [(BS.ByteString, BS.ByteString)]-accessTokenToParam t = [("access_token", encodeUtf8 $ atoken t)]+defaultRequestHeaders :: [(HT.HeaderName, BS.ByteString)]+defaultRequestHeaders =+ [ (HT.hUserAgent, "hoauth2"),+ (HT.hAccept, "application/json")+ ] appendQueryParams :: [(BS.ByteString, BS.ByteString)] -> URIRef a -> URIRef a appendQueryParams params = over (queryL . queryPairsL) (params ++) uriToRequest :: MonadThrow m => URI -> m Request-uriToRequest uri = do- ssl <- case view (uriSchemeL . schemeBSL) uri of+uriToRequest auri = do+ ssl <- case view (uriSchemeL . schemeBSL) auri of "http" -> return False "https" -> return True- s -> throwM $ InvalidUrlException (show uri) ("Invalid scheme: " ++ show s)- let query = fmap (second Just) (view (queryL . queryPairsL) uri)+ s -> throwM $ InvalidUrlException (show auri) ("Invalid scheme: " ++ show s)+ let query = fmap (second Just) (view (queryL . queryPairsL) auri) hostL = authorityL . _Just . authorityHostL . hostBSL portL = authorityL . _Just . authorityPortL . _Just . portNumberL defaultPort = (if ssl then 443 else 80) :: Int@@ -232,10 +185,10 @@ setQueryString query $ defaultRequest { secure = ssl,- path = view pathL uri+ path = view pathL auri }- req2 = (over hostLens . maybe id const . preview hostL) uri req- req3 = (over portLens . (const . fromMaybe defaultPort) . preview portL) uri req2+ req2 = (over hostLens . maybe id const . preview hostL) auri req+ req3 = (over portLens . (const . fromMaybe defaultPort) . preview portL) auri req2 return req3 requestToUri :: Request -> URI
src/Network/OAuth/OAuth2/TokenRequest.hs view
@@ -1,21 +1,270 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} module Network.OAuth.OAuth2.TokenRequest where -import Data.Aeson-import GHC.Generics+import Control.Monad.Trans.Except+import Data.Aeson+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.KeyMap as KeyMap+import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.Text.Encoding as T+import GHC.Generics+import Network.HTTP.Conduit+import qualified Network.HTTP.Types as HT+import Network.HTTP.Types.URI (parseQuery)+import Network.OAuth.OAuth2.Internal+import URI.ByteString +--------------------------------------------------++-- * Token Request Errors++--------------------------------------------------+ instance FromJSON Errors where- parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ parseJSON = genericParseJSON defaultOptions {constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True}+ instance ToJSON Errors where- toEncoding = genericToEncoding defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ toEncoding = genericToEncoding defaultOptions {constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True} -- | Token Error Responses https://tools.ietf.org/html/rfc6749#section-5.2-data Errors =- InvalidRequest+data Errors+ = InvalidRequest | InvalidClient | InvalidGrant | UnauthorizedClient | UnsupportedGrantType | InvalidScope deriving (Show, Eq, Generic)++--------------------------------------------------++-- * URL++--------------------------------------------------++-- | Prepare the URL and the request body query for fetching an access token.+accessTokenUrl ::+ OAuth2 ->+ -- | access code gained via authorization URL+ ExchangeToken ->+ -- | access token request URL plus the request body.+ (URI, PostBody)+accessTokenUrl oa code =+ let uri = oauth2TokenEndpoint oa+ body =+ [ ("code", T.encodeUtf8 $ extoken code),+ ("redirect_uri", serializeURIRef' $ oauth2RedirectUri oa),+ ("grant_type", "authorization_code")+ ]+ in (uri, body)++-- | Using a Refresh Token. Obtain a new access token by+-- sending a refresh token to the Authorization server.+refreshAccessTokenUrl ::+ OAuth2 ->+ -- | refresh token gained via authorization URL+ RefreshToken ->+ -- | refresh token request URL plus the request body.+ (URI, PostBody)+refreshAccessTokenUrl oa token = (uri, body)+ where+ uri = oauth2TokenEndpoint oa+ body =+ [ ("grant_type", "refresh_token"),+ ("refresh_token", T.encodeUtf8 $ rtoken token)+ ]++clientSecretPost :: OAuth2 -> PostBody+clientSecretPost oa =+ [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),+ ("client_secret", T.encodeUtf8 $ oauth2ClientSecret oa)+ ]++--------------------------------------------------++-- * Token management++--------------------------------------------------++-- | Fetch OAuth2 Token with authenticate in request header.+--+-- OAuth2 spec allows `client_id` and `client_secret` to+-- either be sent in the header (as basic authentication)+-- OR as form/url params.+-- The OAuth server can choose to implement only one, or both.+-- Unfortunately, there is no way for the OAuth client (i.e. this library) to+-- know which method to use.+-- Please take a look at the documentation of the+-- service that you are integrating with and either use `fetchAccessToken` or `fetchAccessToken2`+fetchAccessToken ::+ -- | HTTP connection manager+ Manager ->+ -- | OAuth Data+ OAuth2 ->+ -- | OAuth2 Code+ ExchangeToken ->+ -- | Access Token+ ExceptT (OAuth2Error Errors) IO OAuth2Token+fetchAccessToken = fetchAccessTokenInternal ClientSecretBasic++fetchAccessToken2 ::+ -- | HTTP connection manager+ Manager ->+ -- | OAuth Data+ OAuth2 ->+ -- | OAuth 2 Tokens+ ExchangeToken ->+ -- | Access Token+ ExceptT (OAuth2Error Errors) IO OAuth2Token+fetchAccessToken2 = fetchAccessTokenInternal ClientSecretPost+{-# DEPRECATED fetchAccessToken2 "renamed to fetchAccessTokenInternal" #-}++fetchAccessTokenInternal ::+ ClientAuthenticationMethod ->+ -- | HTTP connection manager+ Manager ->+ -- | OAuth Data+ OAuth2 ->+ -- | OAuth 2 Tokens+ ExchangeToken ->+ -- | Access Token+ ExceptT (OAuth2Error Errors) IO OAuth2Token+fetchAccessTokenInternal authMethod manager oa code = do+ let (uri, body) = accessTokenUrl oa code+ let extraBody = if authMethod == ClientSecretPost then clientSecretPost oa else []+ doJSONPostRequest manager oa uri (body ++ extraBody)++-- doJSONPostRequest append client secret to header which is needed for both+-- client_secret_post and client_secret_basic++-- | Fetch a new AccessToken with the Refresh Token with authentication in request header.+--+-- OAuth2 spec allows `client_id` and `client_secret` to+-- either be sent in the header (as basic authentication)+-- OR as form/url params.+-- The OAuth server can choose to implement only one, or both.+-- Unfortunately, there is no way for the OAuth client (i.e. this library) to+-- know which method to use. Please take a look at the documentation of the+-- service that you are integrating with and either use `refreshAccessToken` or `refreshAccessToken2`+refreshAccessToken ::+ -- | HTTP connection manager.+ Manager ->+ -- | OAuth context+ OAuth2 ->+ -- | refresh token gained after authorization+ RefreshToken ->+ ExceptT (OAuth2Error Errors) IO OAuth2Token+refreshAccessToken = refreshAccessTokenInternal ClientSecretBasic++refreshAccessToken2 ::+ -- | HTTP connection manager.+ Manager ->+ -- | OAuth context+ OAuth2 ->+ -- | refresh token gained after authorization+ RefreshToken ->+ ExceptT (OAuth2Error Errors) IO OAuth2Token+refreshAccessToken2 = refreshAccessTokenInternal ClientSecretPost+{-# DEPRECATED refreshAccessToken2 "renamed to fetchAccessTokenInternal" #-}++refreshAccessTokenInternal ::+ ClientAuthenticationMethod ->+ -- | HTTP connection manager.+ Manager ->+ -- | OAuth context+ OAuth2 ->+ -- | refresh token gained after authorization+ RefreshToken ->+ ExceptT (OAuth2Error Errors) IO OAuth2Token+refreshAccessTokenInternal authMethod manager oa token = do+ let (uri, body) = refreshAccessTokenUrl oa token+ let extraBody = if authMethod == ClientSecretPost then clientSecretPost oa else []+ doJSONPostRequest manager oa uri (body ++ extraBody)++--------------------------------------------------++-- * Utilies++--------------------------------------------------++-- | Conduct post request and return response as JSON.+doJSONPostRequest ::+ (FromJSON err, FromJSON a) =>+ -- | HTTP connection manager.+ Manager ->+ -- | OAuth options+ OAuth2 ->+ -- | The URL+ URI ->+ -- | request body+ PostBody ->+ -- | Response as JSON+ ExceptT (OAuth2Error err) IO a+doJSONPostRequest manager oa uri body = do+ resp <- doSimplePostRequest manager oa uri body+ case parseResponseFlexible resp of+ Right obj -> return obj+ Left e -> throwE e++-- | Conduct post request.+doSimplePostRequest ::+ FromJSON err =>+ -- | HTTP connection manager.+ Manager ->+ -- | OAuth options+ OAuth2 ->+ -- | URL+ URI ->+ -- | Request body.+ PostBody ->+ -- | Response as ByteString+ ExceptT (OAuth2Error err) IO BSL.ByteString+doSimplePostRequest manager oa url body =+ ExceptT $ fmap handleOAuth2TokenResponse go+ where+ addBasicAuth = applyBasicAuth (T.encodeUtf8 $ oauth2ClientId oa) (T.encodeUtf8 $ oauth2ClientSecret oa)+ go = do+ req <- uriToRequest url+ let req' = (addBasicAuth . addDefaultRequestHeaders) req+ httpLbs (urlEncodedBody body req') manager++-- | Parses a @Response@ to to @OAuth2Result@+handleOAuth2TokenResponse :: FromJSON err => Response BSL.ByteString -> Either (OAuth2Error err) BSL.ByteString+handleOAuth2TokenResponse rsp =+ if HT.statusIsSuccessful (responseStatus rsp)+ then Right $ responseBody rsp+ else Left $ parseOAuth2Error (responseBody rsp)++-- | Try 'parseResponseJSON', if failed then parses the @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String.+parseResponseFlexible ::+ (FromJSON err, FromJSON a) =>+ BSL.ByteString ->+ Either (OAuth2Error err) a+parseResponseFlexible r = case eitherDecode r of+ Left _ -> parseResponseString r+ Right x -> Right x++-- | Parses a @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String+parseResponseString ::+ (FromJSON err, FromJSON a) =>+ BSL.ByteString ->+ Either (OAuth2Error err) a+parseResponseString b = case parseQuery $ BSL.toStrict b of+ [] -> Left errorMessage+ a -> case fromJSON $ queryToValue a of+ Error _ -> Left errorMessage+ Success x -> Right x+ where+ queryToValue = Object . KeyMap.fromList . map paramToPair+ paramToPair (k, mv) = (Key.fromText $ T.decodeUtf8 k, maybe Null (String . T.decodeUtf8) mv)+ errorMessage = parseOAuth2Error b++-- | Set several header values:+-- + userAgennt : `hoauth2`+-- + accept : `application/json`+addDefaultRequestHeaders :: Request -> Request+addDefaultRequestHeaders req =+ let headers = defaultRequestHeaders ++ requestHeaders req+ in req {requestHeaders = headers}