packages feed

oauth2-jwt-bearer-0.0.1: test/Test/Network/OAuth2/JWT/Client/AuthorizationGrant.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Test.Network.OAuth2.JWT.Client.AuthorizationGrant where

import           Control.Monad.IO.Class (MonadIO (..))

import qualified Crypto.JWT as JWT
import qualified Crypto.PubKey.RSA as Cryptonite

import           Data.Aeson ((.:))
import qualified Data.Aeson as Aeson
import qualified Data.ByteString as ByteString
import qualified Data.Maybe as Maybe
import           Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Text.IO as Text
import qualified Data.X509 as X509
import qualified Data.X509.Memory as X509

import           Hedgehog

import           Network.OAuth2.JWT.Client
import qualified Network.HTTP.Client.TLS as HTTP
import qualified Network.HTTP.Client as HTTP

import           System.IO (IO)
import qualified System.Environment as Environment

import qualified Test.Network.OAuth2.JWT.Client.TestServer as TestServer


prop_success :: Property
prop_success =
  withTests 1 . property $ do
    token <- TestServer.withServer [] $ \endpoint -> do
      (_public, private) <- Cryptonite.generate 512 0x10001
      manager <- HTTP.newManager HTTP.tlsManagerSettings
      let
        iss = Issuer "iss"
        scopes = [Scope "profile"]
        aud = Audience "aud"
        expiry = ExpiresIn 3600
        claims = Claims iss Nothing aud scopes expiry []
        jwk = JWT.fromRSA private
      store <- newStore manager endpoint claims jwk
      grant store
    token === Right (AccessToken "default-token-3600")

prop_cache :: Property
prop_cache =
  withTests 1 . property $ do
    (token1, token2) <- TestServer.withServer [TestServer.good 3600 "a", TestServer.good 3600 "b"] $ \endpoint -> do
      (_public, private) <- Cryptonite.generate 512 0x10001
      manager <- HTTP.newManager HTTP.tlsManagerSettings
      let
        iss = Issuer "iss"
        scopes = [Scope "profile"]
        aud = Audience "aud"
        expiry = ExpiresIn 3600
        claims = Claims iss Nothing aud scopes expiry []
        jwk = JWT.fromRSA private
      store <- newStore manager endpoint claims jwk
      (,) <$> grant store <*> grant store
    token1 === Right (AccessToken "a")
    token1 === token2

prop_refresh :: Property
prop_refresh =
  withTests 1 . property $ do
    (token1, token2) <- TestServer.withServer [TestServer.good 0 "a", TestServer.good 3600 "b"] $ \endpoint -> do
      (_public, private) <- Cryptonite.generate 512 0x10001
      manager <- HTTP.newManager HTTP.tlsManagerSettings
      let
        iss = Issuer "iss"
        scopes = [Scope "profile"]
        aud = Audience "aud"
        expiry = ExpiresIn 0
        claims = Claims iss Nothing aud scopes expiry []
        jwk = JWT.fromRSA private
      store <- newStore manager endpoint claims jwk
      (,) <$> grant store <*> grant store
    token1 === Right (AccessToken "a")
    token2 === Right (AccessToken "b")

data ServiceAccountKey =
  ServiceAccountKey {
      clientEmail :: Text
    , clientId :: Text
    , privateKeyId :: Text
    , privateKey :: Text
    } deriving (Eq, Ord, Show)

instance Aeson.FromJSON ServiceAccountKey where
  parseJSON =
    Aeson.withObject "ServiceAccountKey" $ \o ->
      ServiceAccountKey
        <$> o .: "client_email"
        <*> o .: "client_id"
        <*> o .: "private_key_id"
        <*> o .: "private_key"

prop_google :: Property
prop_google =
  withTests 1 . property $ do
    e <- liftIO $ Environment.lookupEnv "GOOGLE_CREDENTIALS_JSON"
    case e of
      Nothing ->
        success
      Just file -> do
        manager <- liftIO $ HTTP.newManager HTTP.tlsManagerSettings
        blob <- liftIO $ ByteString.readFile file
        key <- evalEither  $ Aeson.eitherDecodeStrict blob
        jwk <- case Maybe.listToMaybe . X509.readKeyFileFromMemory . Text.encodeUtf8 . privateKey $ key of
          Just (X509.PrivKeyRSA k) ->
            pure $ JWT.fromRSA k
          _ ->
            failure
        let
          endpoint = TokenEndpoint "https://www.googleapis.com/oauth2/v4/token"
          iss = Issuer (clientEmail key)
          scopes = [Scope "profile"]
          aud = Audience "https://www.googleapis.com/oauth2/v4/token"
          expiry = ExpiresIn 3600
          claims = Claims iss Nothing aud scopes expiry []
        store <- liftIO $ newStore manager endpoint claims jwk
        token <- liftIO (grant store) >>= evalEither
        liftIO $
          Text.putStrLn "=== BEGIN TOKEN ==="
        liftIO $
          Text.putStrLn . Text.pack . show $ token
        liftIO $
          Text.putStrLn "=== END TOKEN ==="


tests :: IO Bool
tests =
  checkParallel $$(discover)