-- | This module bridges the minimal calls in the -- Client module, adds specific headers required by -- the API, and passes them to the more generic calls -- of the OAuth2 module. -- -- It also includes auxiliary funtions for handling -- its main return type: @ResponseData@. module PokitDok.Requests ( pokitdokGetRequest , pokitdokDeleteRequest , pokitdokPostRequest , pokitdokPutRequest , pokitdokMultipartRequest , activateKeyWithAuthCode , refreshExpired , isExpired' , assertValid , getJSONIO ) where import qualified Codec.Binary.Base64.String as B64 import qualified System.IO.Strict as SIO import System.Directory (getCurrentDirectory) import System.Info (os) import Data.Time.Clock import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Time.Calendar (fromGregorian) import Data.Maybe (fromJust) import Data.Hex (hex) import Network.HTTP.Base (httpPackageVersion) import PokitDok.OAuth2 -- * Middleman Network Calls -- | Sends a GET request to the server with the given query parameters. pokitdokGetRequest :: OAuth2 -- ^ An OAuth2 credential. -> String -- ^ The request url path. -> Parameters -- ^ The query parameters. -> IO ResponseData -- ^ The response from the server. pokitdokGetRequest key = getRequest headers where headers = [bearerH . fromJust $ oauthAccessToken key, userAgentH] -- | Sends a DELETE request to the server with the given query parameters. pokitdokDeleteRequest :: OAuth2 -- ^ A credential. -> String -- ^ The request url path. -> IO ResponseData -- ^ Response from the server. pokitdokDeleteRequest key = deleteRequest headers where headers = [bearerH . fromJust $ oauthAccessToken key, userAgentH] -- | Sends a POST request to the server and posts the json string. pokitdokPostRequest :: OAuth2 -- ^ Credentials. -> String -- ^ The request url path. -> String -- ^ JSON object String. -> IO ResponseData -- ^ Response from the server. pokitdokPostRequest key = postRequest headers where headers = [bearerH . fromJust $ oauthAccessToken key, userAgentH] -- | Sends a PUT request with JSON data. pokitdokPutRequest :: OAuth2 -- ^ Credentials. -> String -- ^ The request url path. -> String -- ^ A JSON object String. -> IO ResponseData -- ^ Response from the server. pokitdokPutRequest key = putRequest headers where headers = [bearerH . fromJust $ oauthAccessToken key, userAgentH] -- | A multipart request that uploads a file. pokitdokMultipartRequest :: OAuth2 -- ^ Credentials. -> String -- ^ The request url path. -> Parameters -- ^ Post data. -> String -- ^ File system path of file to be posted. -> IO ResponseData -- ^ Response from the server. pokitdokMultipartRequest auth url params pPath = do postPath <- format pPath boundary <- multipartBoundary multipartRequest headers url params postPath boundary where headers = [bearerH . fromJust $ oauthAccessToken auth, userAgentH] osChar = if os == "mingw" then '\\' else '/' format file@('C':_) = return file format file@('/':_) = return file format file = getCurrentDirectory >>= return . (++ [osChar] ++ file) -- * Auxiliary Functions -- The boundary of the PD multipart file upload. multipartBoundary :: IO String multipartBoundary = do now <- getCurrentTime let day0 = UTCTime { utctDay = fromGregorian 0001 01 01 , utctDayTime = 0 } ticks = floor $ (realToFrac $ diffUTCTime now day0) * 10000000 return $ "----------------------------" ++ (hex $ show ticks) -- The bearer authorization header as a String tuple, given an AccessToken. bearerH :: AccessToken -> (String, String) bearerH (AccessToken token _ _ _ _) = makeAuthHeader $ "Bearer " ++ token -- The basic authorization header as a String tuple, given an OAuth2. basicH :: OAuth2 -> (String, String) basicH (OAuth2 id sec _ _ _ _) = makeAuthHeader $ "Basic "++B64.encode(id++":"++sec) -- The pokitdok-haskell user agent header, as a String tuple. userAgentH :: (String, String) userAgentH = makeUserAgentHeader $ "haskell-pokitdok/" ++ httpPackageVersion -- | Pulls the JSON response @String@ from a @ResponseDat@um, -- and lifts the result to an IO monad. getJSONIO :: ResponseData -> IO String getJSONIO = return . getJSON -- Extracts the JSON response from ResponseData. getJSON :: ResponseData -> String getJSON (ResponseData _ body _) = body -- * Supplementary Client Functions -- | Takes an @OAuth2@ & an authorization code, -- and returns an @OAuth2@ with a refreshed @AccessToken@. activateKeyWithAuthCode :: OAuth2 -> String -> IO OAuth2 activateKeyWithAuthCode auth code = do token <- authenticateAuthorizationCode headers auth code return $ keyModAccessToken auth (Just token) where headers = [basicH auth, userAgentH] -- | Refreshes an @OAuth2@'s token given whether or not it is expired. refreshExpired :: OAuth2 -> Bool -> IO OAuth2 refreshExpired auth False = return auth refreshExpired auth@(OAuth2 _ _ _ _ _ (Just (AccessToken _ (Just _) _ _ _))) _ = do token <- authenticateRefreshToken headers auth return $ keyModAccessToken auth (Just token) where headers = [basicH auth, userAgentH] refreshExpired auth _ = do token <- authenticateClientCredentials headers auth return $ keyModAccessToken auth (Just token) where headers = [basicH auth, userAgentH] -- | Checks if the given @AccessToken@ is expired. isExpired' :: AccessToken -> IO Bool isExpired' (AccessToken _ _ (Just exp) _ _) = getPOSIXTime >>= (\now -> return $ (realToFrac now :: Float) > (fromIntegral exp :: Float) - 5) -- 5 sec timeout isExpired' _ = return False -- | Asserts that the given @OAuth2@ is valid to make a call. assertValid :: OAuth2 -> IO () assertValid (OAuth2 _ _ _ _ _ Nothing) = error "Access token has not been initialized." assertValid (OAuth2 _ _ _ _ _ (Just t)) = do dead <- isExpired' t if dead then error "Access token is expired." else return ()