packages feed

libjwt-typed-0.2: bench/Benchmarks/Jose.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Benchmarks.Jose
  ( signing
  , decoding
  )
where

import           Benchmarks.Data
import           Env


import           Control.Lens.Operators         ( (?~)
                                                , (&)
                                                , (.~)
                                                , (^?)
                                                )
import           Control.Lens.Combinators       ( view )

import           Control.Monad.Trans.Except     ( runExceptT
                                                , except
                                                )

import           Criterion                      ( Benchmark
                                                , bench
                                                , env
                                                , nfAppIO
                                                , whnfAppIO
                                                )

import           Crypto.JOSE.JWK
import           Crypto.JWT

import           Data.Aeson                     ( Value
                                                , Result(..)
                                                , toJSON
                                                , fromJSON
                                                )

import           Data.ByteString                ( ByteString )
import           Data.ByteString.Lazy           ( toStrict
                                                , fromStrict
                                                )

import           Data.HashMap.Strict            ( HashMap
                                                , (!)
                                                )

import           Data.Text                      ( Text )
import qualified Data.Text                     as T

import           Data.Time.Clock                ( UTCTime )

import           Data.UUID                      ( UUID )
import           Data.List.NonEmpty             ( NonEmpty )

signing :: [(Alg, JWK) -> Benchmark]
signing =
  [signSimple, signCustomClaims, signCustomClaimsWithNs, signComplexClaims]

decoding :: [(Alg, JWK) -> Benchmark]
decoding =
  [ decodeSimple
  , decodeCustomClaims
  , decodeCustomClaimsWithNs
  , decodeComplexClaims
  ]


doSign :: Alg -> JWK -> ClaimsSet -> IO ByteString
doSign a k claims =
  either (fail . (show :: JWTError -> String))
         (return . toStrict . encodeCompact)
    =<< runExceptT (signClaims k (newJWSHeader ((), a)) claims)

prepareToken :: (BenchEnv -> ClaimsSet) -> Alg -> JWK -> IO ByteString
prepareToken f a k = localEnv >>= doSign a k . f

decodeJson :: (HashMap Text Value -> Result b) -> ClaimsSet -> Either JWTError b
decodeJson f claimsSet = case f (view unregisteredClaims claimsSet) of
  Success d   -> Right d
  Error   str -> Left $ JWTClaimsSetDecodeError str



baseClaimsSet :: BenchEnv -> ClaimsSet
baseClaimsSet LocalEnv {..} =
  let _subject = subject ^? stringOrUri
      _iat     = NumericDate currentTimeUtc
      _exp     = NumericDate someFutureTimeUtc
  in  emptyClaimsSet
        &  claimIss
        ?~ "benchmarks"
        &  claimSub
        .~ _subject
        &  claimAud
        ?~ Audience ["https://example.com"]
        &  claimIat
        ?~ _iat
        &  claimExp
        ?~ _exp
        &  claimNbf
        ?~ _iat

signSimple :: (Alg, JWK) -> Benchmark
signSimple (a, k) = env localEnv $ bench "simple" . nfAppIO benchmark
  where benchmark = doSign a k . baseClaimsSet

decodeSimple :: (Alg, JWK) -> Benchmark
decodeSimple (a, k) =
  env (prepareToken baseClaimsSet a k) $ bench "simple" . whnfAppIO benchmark
 where
  benchmark :: ByteString -> IO (Either JWTError ClaimsSet)
  benchmark token =
    runExceptT
      $   decodeCompact (fromStrict token)
      >>= verifyClaims (validation & issuerPredicate .~ (== "benchmarks")) k



mkCustomClaims :: BenchEnv -> ClaimsSet
mkCustomClaims e@LocalEnv {..} =
  addClaim "scope" (toJSON Login)
    $ addClaim "created"  (toJSON currentTimeUtc)
    $ addClaim "clientId" (toJSON uuid)
    $ addClaim "isRoot"   (toJSON flipBit)
    $ addClaim "userName" (toJSON shortPrintableText)
    $ baseClaimsSet e

signCustomClaims :: (Alg, JWK) -> Benchmark
signCustomClaims (a, k) =
  env localEnv $ bench "custom-claims" . nfAppIO benchmark
  where benchmark = doSign a k . mkCustomClaims

decodeCustomClaims :: (Alg, JWK) -> Benchmark
decodeCustomClaims (a, k) =
  env (prepareToken mkCustomClaims a k)
    $ bench "custom-claims"
    . whnfAppIO benchmark
 where
  benchmark
    :: ByteString -> IO (Either JWTError (Text, Bool, UUID, UTCTime, Scope))
  benchmark token =
    runExceptT
      $   decodeCompact (fromStrict token)
      >>= verifyClaims (validation & issuerPredicate .~ (== "benchmarks")) k
      >>= except
      .   decodeJson decodeData

  decodeData claims =
    (,,,,)
      <$> fromJSON (claims ! "userName")
      <*> fromJSON (claims ! "isRoot")
      <*> fromJSON (claims ! "clientId")
      <*> fromJSON (claims ! "created")
      <*> fromJSON (claims ! "scope")



mkCustomClaimsWithNs :: Text -> BenchEnv -> ClaimsSet
mkCustomClaimsWithNs ns e@LocalEnv {..} =
  addClaim (withNs "scope") (toJSON Login)
    $ addClaim (withNs "created")  (toJSON currentTimeUtc)
    $ addClaim (withNs "clientId") (toJSON uuid)
    $ addClaim (withNs "isRoot")   (toJSON flipBit)
    $ addClaim (withNs "userName") (toJSON shortPrintableText)
    $ baseClaimsSet e
  where withNs = T.append ns

signCustomClaimsWithNs :: (Alg, JWK) -> Benchmark
signCustomClaimsWithNs (a, k) =
  env localEnv $ bench "custom-claims-with-ns" . nfAppIO benchmark
 where
  benchmark = doSign a k . mkCustomClaimsWithNs "https://www.example.com/test"

decodeCustomClaimsWithNs :: (Alg, JWK) -> Benchmark
decodeCustomClaimsWithNs (a, k) =
  env (prepareToken (mkCustomClaimsWithNs "https://www.example.com/test") a k)
    $ bench "custom-claims-with-ns"
    . whnfAppIO benchmark
 where
  benchmark
    :: ByteString -> IO (Either JWTError (Text, Bool, UUID, UTCTime, Scope))
  benchmark token =
    runExceptT
      $   decodeCompact (fromStrict token)
      >>= verifyClaims (validation & issuerPredicate .~ (== "benchmarks")) k
      >>= except
      .   decodeJson (decodeData "https://www.example.com/test")

  decodeData ns claims =
    (,,,,)
      <$> fromJSON (claims ! withNs "userName")
      <*> fromJSON (claims ! withNs "isRoot")
      <*> fromJSON (claims ! withNs "clientId")
      <*> fromJSON (claims ! withNs "created")
      <*> fromJSON (claims ! withNs "scope")
    where withNs = T.append ns



mkComplexClaims :: BenchEnv -> ClaimsSet
mkComplexClaims e@LocalEnv {..} =
  addClaim "emails" (toJSON emailsList)
    $ addClaim
        "scopes"
        (toJSON
          [Login, Extended, UserRead, UserWrite, AccountRead, AccountWrite]
        )
    $ addClaim "accounts"  (toJSON accountList)
    $ addClaim "created"   (toJSON currentTimeUtc)
    $ addClaim "client_id" (toJSON uuid)
    $ addClaim "is_root"   (toJSON flipBit)
    $ addClaim "user_name" (toJSON shortPrintableText)
    $ baseClaimsSet e

signComplexClaims :: (Alg, JWK) -> Benchmark
signComplexClaims (a, k) =
  env localEnv $ bench "complex-claims" . nfAppIO benchmark
  where benchmark = doSign a k . mkComplexClaims

decodeComplexClaims :: (Alg, JWK) -> Benchmark
decodeComplexClaims (a, k) =
  env (prepareToken mkComplexClaims a k)
    $ bench "complex-claims"
    . whnfAppIO benchmark
 where
  benchmark
    :: ByteString
    -> IO
         ( Either
             JWTError
             ( Text
             , Bool
             , UUID
             , UTCTime
             , NonEmpty (UUID, Text)
             , [Scope]
             , [String]
             )
         )
  benchmark token =
    runExceptT
      $   decodeCompact (fromStrict token)
      >>= verifyClaims (validation & issuerPredicate .~ (== "benchmarks")) k
      >>= except
      .   decodeJson decodeData

  decodeData claims =
    (,,,,,,)
      <$> fromJSON (claims ! "user_name")
      <*> fromJSON (claims ! "is_root")
      <*> fromJSON (claims ! "client_id")
      <*> fromJSON (claims ! "created")
      <*> fromJSON (claims ! "accounts")
      <*> fromJSON (claims ! "scopes")
      <*> fromJSON (claims ! "emails")



validation :: JWTValidationSettings
validation =
  defaultJWTValidationSettings (== "https://example.com")
    &  jwtValidationSettingsAllowedSkew
    .~ 5