packages feed

biscuit-haskell-0.1.0.0: test/Spec/Verification.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE RecordWildCards   #-}
module Spec.Verification
  ( specs
  ) where

import           Data.List.NonEmpty            (NonEmpty ((:|)))
import           Test.Tasty
import           Test.Tasty.HUnit

import           Auth.Biscuit
import           Auth.Biscuit.Datalog.AST      (Expression' (..), ID' (..),
                                                Query, QueryItem' (..))
import           Auth.Biscuit.Datalog.Executor (ResultError (..))
import qualified Auth.Biscuit.Datalog.Executor as Executor
import           Auth.Biscuit.Datalog.Parser   (check)

specs :: TestTree
specs = testGroup "Datalog checks"
  [ singleBlock
  , errorAccumulation
  , unboundVarRule
  , symbolRestrictions
  , factsRestrictions
  ]

ifTrue :: Query
ifTrue = [QueryItem [] [EValue $ LBool True]]

ifFalse :: Query
ifFalse = [QueryItem [] [EValue $ LBool False]]

singleBlock :: TestTree
singleBlock = testCase "Single block" $ do
  keypair <- newKeypair
  biscuit <- mkBiscuit keypair [block|right(#authority, "file1", #read);|]
  res <- verifyBiscuit biscuit [verifier|check if right(#authority, "file1", #read);allow if true;|] (publicKey keypair)
  res @?= Right ifTrue

resultError :: Executor.ResultError
            -> Either VerificationError a
resultError = Left . DatalogError . Executor.ResultError

errorAccumulation :: TestTree
errorAccumulation = testGroup "Error accumulation"
  [ testCase "Only checks" $ do
      keypair <- newKeypair
      biscuit <- mkBiscuit keypair [block|check if false; check if false;|]
      res <- verifyBiscuit biscuit [verifier|allow if true;|] (publicKey keypair)
      res @?= resultError (FailedChecks $ ifFalse :| [ifFalse])
  , testCase "Checks and deny policies" $ do
      keypair <- newKeypair
      biscuit <- mkBiscuit keypair [block|check if false; check if false;|]
      res <- verifyBiscuit biscuit [verifier|deny if true;|] (publicKey keypair)
      res @?= resultError (DenyRuleMatched [ifFalse, ifFalse] ifTrue)
  , testCase "Checks and no policies matched" $ do
      keypair <- newKeypair
      biscuit <- mkBiscuit keypair [block|check if false; check if false;|]
      res <- verifyBiscuit biscuit [verifier|allow if false;|] (publicKey keypair)
      res @?= resultError (NoPoliciesMatched [ifFalse, ifFalse])
  ]

unboundVarRule :: TestTree
unboundVarRule = testCase "Rule with unbound variable" $ do
  keypair <- newKeypair
  b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]
  b2 <- addBlock [block|operation($unbound, #read) <- operation($any1, $any2);|] b1
  res <- verifyBiscuit b2 [verifier|operation(#ambient,#write);allow if true;|] (publicKey keypair)
  res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])

symbolRestrictions :: TestTree
symbolRestrictions = testGroup "Restricted symbols in blocks"
  [ testCase "In facts" $ do
      keypair <- newKeypair
      b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]
      b2 <- addBlock [block|operation(#ambient, #read);|] b1
      res <- verifyBiscuit b2 [verifier|allow if true;|] (publicKey keypair)
      res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])
  , testCase "In rules" $ do
      keypair <- newKeypair
      b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]
      b2 <- addBlock [block|operation($ambient, #read) <- operation($ambient, $any);|] b1
      res <- verifyBiscuit b2 [verifier|operation(#ambient,#write);allow if true;|] (publicKey keypair)
      res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])
  ]

factsRestrictions :: TestTree
factsRestrictions =
  let limits = defaultLimits { allowBlockFacts = False }
   in testGroup "No facts or rules in blocks"
        [ testCase "No facts" $ do
            keypair <- newKeypair
            b1 <- mkBiscuit keypair [block|right(#read);|]
            b2 <- addBlock [block|right(#write);|] b1
            res <- verifyBiscuitWithLimits limits b2 [verifier|allow if right(#write);|] (publicKey keypair)
            res @?= Left (DatalogError $ Executor.ResultError $ Executor.NoPoliciesMatched [])
        , testCase "No rules" $ do
            keypair <- newKeypair
            b1 <- mkBiscuit keypair [block|right(#read);|]
            b2 <- addBlock [block|right(#write) <- right(#read);|] b1
            res <- verifyBiscuitWithLimits limits b2 [verifier|allow if right(#write);|] (publicKey keypair)
            res @?= Left (DatalogError $ Executor.ResultError $ Executor.NoPoliciesMatched [])
        ]