packages feed

wai-saml2-0.7.0: tests/Validation.hs

module Validation where

import Control.Monad.Trans.Except
import Crypto.PubKey.RSA (PublicKey)
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as Base64
import Data.Time.Format.ISO8601
import qualified Data.X509 as X509
import qualified Data.X509.Memory as X509
import Network.Wai.SAML2
import Network.Wai.SAML2.Validation
import System.FilePath
import Test.Tasty
import Test.Tasty.HUnit

-- | Get a public key from a X.509 certificate
parseCertificate :: B.ByteString -> PublicKey
parseCertificate certificate = case X509.readSignedObjectFromMemory certificate of
    [signedCert] -> case X509.certPubKey $ X509.signedObject $ X509.getSigned signedCert of
        X509.PubKeyRSA key -> key
        other -> error $ "Expected PubKeyRSA, but got " <> show other
    xs -> error $ show xs

run :: FilePath -> String -> FilePath -> IO ()
run certPath timestamp respPath = do
    cert <- B.readFile $ prefix </> certPath
    xml <- B.readFile $ prefix </> respPath
    now <- iso8601ParseM timestamp

    let pub = parseCertificate cert
        cfg = (saml2ConfigNoEncryption pub) {
            saml2ValidationTarget = ValidateEither
        }

    assertion <- runExceptT $ do
        (responseXmlDoc, samlResponse) <- decodeResponse $ Base64.encode xml
        validateSAMLResponse cfg responseXmlDoc samlResponse now

    case assertion of
        Left err -> assertFailure $ show err
        Right _ -> pure ()

prefix :: FilePath
prefix = "tests/data"

tests :: TestTree
tests = testGroup "Validate SAML2 Response"
    [ testCase "AzureAD signed response"
        $ run "azuread.crt" "2023-05-10T01:20:00Z" "azuread-signed-response.xml"
    , testCase "AzureAD signed assertion"
        $ run "azuread.crt" "2023-05-09T16:00:00Z" "azuread-signed-assertion.xml"
    , testCase "Okta with AttributeStatement"
        $ run "okta.crt" "2023-06-16T06:43:00.000Z" "okta-attributes.xml"
    ]