{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module Main where import Control.Monad (foldM) import Control.Monad.IO.Class (liftIO) import Data.Serialize (Serialize) import qualified Data.Serialize as S import qualified Data.Set as Set import Hedgehog (Gen, Group (..), Property, PropertyT, TestLimit, assert, checkParallel, diff, forAll, property, withTests, (===)) import Hedgehog.Main (defaultMain) import qualified Hedgehog.Range as Range import Authorize.Macaroon (Macaroon, SealedMacaroon (..), VerificationFailure (..), addThirdPartyCaveat, createDischargeMacaroon, sealMacaroon, verify) import Authorize.Macaroon.Crypto (decryptKey, encryptKey) import qualified Authorize.Macaroon.Gen as G import qualified Hedgehog.Gen as Gen main :: IO () main = defaultMain [ checkParallel testSerialization , checkParallel testCrypto , checkParallel testVerification ] testSerialization :: Group testSerialization = Group "Serialization" [ ("Caveat" , testCaveatSerialization) , ("Macaroon" , testMacaroonSerialization) , ("SealedMacaroon" , testSealedMacaroonSerialization) ] testCaveatSerialization :: Property testCaveatSerialization = roundTripProperty 1000 G.caveat testMacaroonSerialization :: Property testMacaroonSerialization = roundTripProperty 500 G.macaroon testSealedMacaroonSerialization :: Property testSealedMacaroonSerialization = roundTripProperty 20 G.sealedMacaroon roundTripProperty :: (Eq a, Show a, Serialize a) => TestLimit -> Gen a -> Property roundTripProperty n g = withTests n . property $ do x <- forAll g diff (roundTrip x) (==) (Right x) roundTrip :: Serialize a => a -> Either String a roundTrip = S.decode . S.encode testCrypto :: Group testCrypto = Group "Cryptography" [ ("Key encryption", testKeyEncryption) ] testKeyEncryption :: Property testKeyEncryption = withTests 10 . property $ do x <- forAll G.key s <- forAll G.signature ct <- liftIO $ encryptKey s x decryptKey s ct === Just x testVerification :: Group testVerification = Group "Verification" [ ("Passes (simple)" , testPasses) , ("Passes (complex)" , testComplexPasses) , ("Fails (missing discharge)" , testMissingDischarge) , ("Fails (invalid key)" , testInvalidKey) , ("Fails (invalid binding)" , testInvalidBinding) ] testPasses :: Property testPasses = withTests 100 . property $ do (k, m, cs) <- forAll G.validMacaroon verify k (SealedMacaroon m []) === Right cs testComplexPasses :: Property testComplexPasses = withTests 100 . property $ do (k, m, cs) <- forAll G.validMacaroon sm <- uncurry sealMacaroon <$> addThirdPartyCaveats m verify k sm === Right cs addThirdPartyCaveats :: Macaroon -> PropertyT IO (Macaroon, [Macaroon]) addThirdPartyCaveats m0 = forAll genThirdPartyCaveats >>= foldM step (m0, []) where genThirdPartyCaveats = Gen.set (Range.constant 1 10) G.content >>= traverse inflateTPData . Set.toList inflateTPData c = (, , c) <$> G.key <*> G.location step (m, ds) (ck, l, c) = do m' <- liftIO $ addThirdPartyCaveat m ck l c let d = createDischargeMacaroon ck l c [] return (m', ds <> [d]) testMissingDischarge :: Property testMissingDischarge = withTests 100 . property $ do (k, m, _) <- forAll G.validMacaroon (m', _) <- addThirdPartyCaveats m assert . isMissingDischarge $ verify k (SealedMacaroon m' []) where isMissingDischarge (Left (MissingDischargeMacaroon _)) = True isMissingDischarge _ = False testInvalidKey :: Property testInvalidKey = withTests 100 . property $ do k <- forAll G.key (_, m, _) <- forAll G.validMacaroon assert . isInvalidSignature $ verify k (SealedMacaroon m []) where isInvalidSignature (Left (InvalidSignature _)) = True isInvalidSignature _ = False testInvalidBinding :: Property testInvalidBinding = withTests 100 . property $ do (k, m, _) <- forAll G.validMacaroon sm <- uncurry SealedMacaroon <$> addThirdPartyCaveats m assert . isInvalidBinding $ verify k sm where isInvalidBinding (Left (InvalidBinding _)) = True isInvalidBinding _ = False