hoauth2-2.3.0: src/Network/OAuth/OAuth2/HttpClient.hs
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
-- | A simple http client to request OAuth2 tokens and several utils.
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 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
--------------------------------------------------
-- * AUTH requests
-- 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) =>
-- | 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 ::
-- | 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 ::
-- | 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
-- | Conduct POST request and return response as JSON.
-- 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)
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 ::
-- | 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 ::
-- | 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
--------------------------------------------------
-- * 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 -- 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`
-- + accept : `application/json`
-- + authorization : 'Bearer' `xxxxx` if 'AccessToken' provided.
updateRequestHeaders :: Maybe AccessToken -> Request -> Request
updateRequestHeaders t req =
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}
-- | 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)]