diff --git a/biscuit/biscuit-haskell.cabal b/biscuit/biscuit-haskell.cabal index c108de3..60e1dcd 100644 --- a/biscuit/biscuit-haskell.cabal +++ b/biscuit/biscuit-haskell.cabal @@ -73,6 +73,7 @@ test-suite biscuit-haskell-test type: exitcode-stdio-1.0 main-is: Spec.hs other-modules: + Spec.AST Spec.NewCrypto Spec.Executor Spec.Parser diff --git a/biscuit/src/Auth/Biscuit.hs b/biscuit/src/Auth/Biscuit.hs index 8c5276b..73f3156 100644 --- a/biscuit/src/Auth/Biscuit.hs +++ b/biscuit/src/Auth/Biscuit.hs @@ -83,6 +83,9 @@ module Auth.Biscuit , authorizeBiscuitWithLimits , Limits (..) , defaultLimits + , setExternFuncs + , withExternFunc + , withExternFuncs , ParseError (..) , ExecutionError (..) , AuthorizedBiscuit (..) @@ -128,7 +131,10 @@ import Auth.Biscuit.Datalog.AST (Authorizer, Block, import Auth.Biscuit.Datalog.Executor (ExecutionError (..), Limits (..), MatchedQuery (..), - defaultLimits) + defaultLimits, + setExternFuncs, + withExternFunc, + withExternFuncs) import Auth.Biscuit.Datalog.Parser (authorizer, block, query) import Auth.Biscuit.Datalog.ScopedExecutor (AuthorizationSuccess (..), getBindings, diff --git a/biscuit/src/Auth/Biscuit/Crypto.hs b/biscuit/src/Auth/Biscuit/Crypto.hs index 2543754..bf344a7 100644 --- a/biscuit/src/Auth/Biscuit/Crypto.hs +++ b/biscuit/src/Auth/Biscuit/Crypto.hs @@ -1,10 +1,12 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -fno-warn-unused-top-binds #-} {-| Module : Auth.Biscuit.Crypto Copyright : © Clément Delafargue, 2021 @@ -15,14 +17,15 @@ module Auth.Biscuit.Crypto ( SignedBlock , Blocks - , signBlock + , signAuthority + , signAttenuationBlock , signExternalBlock - , sign3rdPartyBlock + , sign3rdPartyBlockV1 , verifyBlocks , verifySecretProof , verifySignatureProof , getSignatureProof - , verifyExternalSig + , verifyExternalSigV1 , PublicKey , pkBytes , readEd25519PublicKey @@ -45,8 +48,7 @@ import Data.ByteString (ByteString) import Data.Function (on) import Data.Int (Int32) import Data.List.NonEmpty (NonEmpty (..)) -import qualified Data.List.NonEmpty as NE -import Data.Maybe (catMaybes, fromJust) +import Data.Maybe (fromJust, fromMaybe, isJust) import Instances.TH.Lift () import Language.Haskell.TH.Syntax @@ -110,9 +112,22 @@ pkBytes (PublicKey pk) = convert pk skBytes :: SecretKey -> ByteString skBytes (SecretKey sk) = convert sk -type SignedBlock = (ByteString, Signature, PublicKey, Maybe (Signature, PublicKey)) +type SignedBlock = + ( ByteString -- payload + , Signature -- signature + , PublicKey -- nextKey + , Maybe (Signature, PublicKey) -- externalKey + , Maybe Int -- version + ) type Blocks = NonEmpty SignedBlock +type AnySignedBlock a = + ( ByteString -- payload + , a + , PublicKey -- nextKey + , Maybe (Signature, PublicKey) -- externalKey + , Maybe Int -- version + ) -- | Biscuit 2.0 allows multiple signature algorithms. -- For now this lib only supports Ed25519, but the spec mandates flagging -- each publicKey with an algorithm identifier when serializing it. The @@ -128,97 +143,195 @@ serializePublicKey pk = algBytes = PB.runPut $ PB.putInt32le algId in algBytes <> keyBytes -signBlock :: SecretKey - -> ByteString - -> Maybe (Signature, PublicKey) - -> IO (SignedBlock, SecretKey) -signBlock sk payload eSig = do +signBlockV0 :: SecretKey + -> ByteString + -> Maybe (Signature, PublicKey) + -> IO (SignedBlock, SecretKey) +signBlockV0 sk payload eSig = do let pk = toPublic sk (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey - let toSign = getToSig (payload, (), nextPk, eSig) + let toSign = getSignaturePayloadV0 (payload, (), nextPk, eSig, Nothing) sig = sign sk pk toSign - pure ((payload, sig, nextPk, eSig), nextSk) - -signExternalBlock :: SecretKey - -> SecretKey - -> PublicKey - -> ByteString - -> IO (SignedBlock, SecretKey) -signExternalBlock sk eSk pk payload = - let eSig = sign3rdPartyBlock eSk pk payload - in signBlock sk payload (Just eSig) - -sign3rdPartyBlock :: SecretKey - -> PublicKey - -> ByteString - -> (Signature, PublicKey) -sign3rdPartyBlock eSk nextPk payload = + pure ((payload, sig, nextPk, eSig, Nothing), nextSk) + +signExternalBlockV0 :: SecretKey + -> SecretKey + -> PublicKey + -> ByteString + -> IO (SignedBlock, SecretKey) +signExternalBlockV0 sk eSk pk payload = + let eSig = sign3rdPartyBlockV0 eSk pk payload + in signBlockV0 sk payload (Just eSig) + +sign3rdPartyBlockV0 :: SecretKey + -> PublicKey + -> ByteString + -> (Signature, PublicKey) +sign3rdPartyBlockV0 eSk nextPk payload = let toSign = payload <> serializePublicKey nextPk ePk = toPublic eSk eSig = sign eSk ePk toSign in (eSig, ePk) getSignatureProof :: SignedBlock -> SecretKey -> Signature -getSignatureProof (lastPayload, Signature lastSig, lastPk, _todo) nextSecret = +getSignatureProof (lastPayload, Signature lastSig, lastPk, _, _) nextSecret = let sk = nextSecret pk = toPublic nextSecret toSign = lastPayload <> serializePublicKey lastPk <> lastSig in sign sk pk toSign -getToSig :: (ByteString, a, PublicKey, Maybe (Signature, PublicKey)) -> ByteString -getToSig (p, _, nextPk, ePk) = +getSignaturePayloadV0 :: AnySignedBlock a -> ByteString +getSignaturePayloadV0 (p, _, nextPk, ePk, _) = p <> foldMap (sigBytes . fst) ePk <> serializePublicKey nextPk -getSignature :: SignedBlock -> Signature -getSignature (_, sig, _, _) = sig - -getPublicKey :: SignedBlock -> PublicKey -getPublicKey (_, _, pk, _) = pk - -- | The data signed by the external key is the payload for the current block + the public key from -- the previous block: this prevents signature reuse (the external signature cannot be used on another -- token) -getExternalSigPayload :: PublicKey -> SignedBlock -> Maybe (PublicKey, ByteString, Signature) -getExternalSigPayload pkN (payload, _, _, Just (eSig, ePk)) = Just (ePk, payload <> serializePublicKey pkN, eSig) -getExternalSigPayload _ _ = Nothing +getExternalSignaturePayloadV0 :: PublicKey -> SignedBlock -> Maybe (PublicKey, ByteString, Signature) +getExternalSignaturePayloadV0 pkN (payload, _, _, Just (eSig, ePk), _) = Just (ePk, payload <> serializePublicKey pkN, eSig) +getExternalSignaturePayloadV0 _ _ = Nothing + +getAuthoritySignaturePayloadV1 :: ByteString -> PublicKey -> ByteString +getAuthoritySignaturePayloadV1 p nextPk = + "\0BLOCK\0" <> + "\0VERSION\0" <> PB.runPut (PB.putInt32le 1) <> + "\0PAYLOAD\0" <> p <> + serializePublicKeyV1 nextPk + +getBlockSignaturePayloadV1 :: ByteString -> PublicKey -> Maybe (Signature, PublicKey) -> Signature -> ByteString +getBlockSignaturePayloadV1 p nextPk ePk prevSig = + getAuthoritySignaturePayloadV1 p nextPk <> + "\0PREVSIG\0" <> sigBytes prevSig <> + foldMap serializeExternalSignatureV1 ePk + +getExternalSignaturePayloadV1 :: ByteString -> Signature -> ByteString +getExternalSignaturePayloadV1 payload prevSig = + "\0EXTERNAL\0" <> + "\0VERSION\0" <> PB.runPut (PB.putInt32le 1) <> + "\0PAYLOAD\0" <> payload <> + "\0PREVSIG\0" <> sigBytes prevSig + +serializePublicKeyV1 :: PublicKey -> ByteString +serializePublicKeyV1 pk = + let keyBytes = pkBytes pk + algId :: Int32 + algId = fromIntegral $ fromEnum PB.Ed25519 + -- The spec mandates that we serialize the algorithm id as a little-endian int32 + algBytes = PB.runPut $ PB.putInt32le algId + in "\0ALGORITHM\0" <> algBytes <> + "\0NEXTKEY\0" <> keyBytes + +serializeExternalSignatureV1 :: (Signature, PublicKey) -> ByteString +serializeExternalSignatureV1 (sig, _) = "\0EXTERNALSIG\0" <> sigBytes sig + +getSignature :: SignedBlock -> Signature +getSignature (_, sig, _, _, _) = sig + +getPublicKey :: SignedBlock -> PublicKey +getPublicKey (_, _, pk, _, _) = pk -- | When adding a pre-signed third-party block to a token, we make sure the third-party block is correctly -- signed (pk-signature match, and the third-party block is pinned to the last biscuit block) -verifyExternalSig :: PublicKey -> (ByteString, Signature, PublicKey) -> Bool -verifyExternalSig previousPk (payload, eSig, ePk) = +verifyExternalSigV0 :: PublicKey -> (ByteString, Signature, PublicKey) -> Bool +verifyExternalSigV0 previousPk (payload, eSig, ePk) = verify ePk (payload <> serializePublicKey previousPk) eSig +-- | When adding a pre-signed third-party block to a token, we make sure the third-party block is correctly +-- signed (pk-signature match, and the third-party block is pinned to the last biscuit block) +verifyExternalSigV1 :: Signature -> (ByteString, Signature, PublicKey) -> Bool +verifyExternalSigV1 prevSig (payload, eSig, ePk) = + verify ePk (getExternalSignaturePayloadV1 payload prevSig) eSig + +verifyAuthorityBlock :: SignedBlock -> PublicKey -> Bool +verifyAuthorityBlock b@(payload, sig, nextPk, _, version) rootPk = + case fromMaybe 0 version of + 0 -> verify rootPk (getSignaturePayloadV0 b) sig + 1 -> verify rootPk (getAuthoritySignaturePayloadV1 payload nextPk) sig + _ -> False + +verifyAttenuationBlock :: SignedBlock -> SignedBlock -> Bool +verifyAttenuationBlock block previousBlock = + let (payload, sig, nextPk, eSig', version) = block + (_, prevSig, pk, _, _) = previousBlock + in case (fromMaybe 0 version, eSig') of + (0, Nothing) -> verify pk (getSignaturePayloadV0 block) sig + (0, Just _) -> False -- reject third-party blocks with v0 signatures + (1, Nothing) -> verify pk (getBlockSignaturePayloadV1 payload nextPk eSig' prevSig) sig + (1, Just (eSig, ePk)) -> + let sv = verify pk (getBlockSignaturePayloadV1 payload nextPk eSig' prevSig) sig + ev = verify ePk (getExternalSignaturePayloadV1 payload prevSig) eSig + in sv && ev + _ -> False + verifyBlocks :: Blocks -> PublicKey -> Bool -verifyBlocks blocks rootPk = - let attachKey pk (payload, sig) = (pk, payload, sig) - uncurry3 f (a, b, c) = f a b c - sigs = getSignature <$> blocks - toSigs = getToSig <$> blocks - -- key for block 0 is the root key - -- key for block n is the key from block (n - 1) - keys = pure rootPk <> (getPublicKey <$> blocks) - keysPayloadsSigs = NE.zipWith attachKey keys (NE.zip toSigs sigs) - - -- external_signature(block_n) = sign(external_key_n, payload_n <> public_key_n-1) - -- so we need to pair each block with the public key carried by the previous block - -- (the authority block can't have an external signature) - previousKeys = getPublicKey <$> NE.init blocks - blocksAfterAuthority = NE.tail blocks - eKeysPayloadsESigs = catMaybes $ zipWith getExternalSigPayload previousKeys blocksAfterAuthority - in all (uncurry3 verify) keysPayloadsSigs - && all (uncurry3 verify) eKeysPayloadsESigs +verifyBlocks (authority :| attenuationBlocks) rootPk = + let attenuationBlocks' = zip attenuationBlocks (authority : attenuationBlocks) + in verifyAuthorityBlock authority rootPk + && all (uncurry verifyAttenuationBlock) attenuationBlocks' verifySecretProof :: SecretKey -> SignedBlock -> Bool -verifySecretProof nextSecret (_, _, lastPk, _) = +verifySecretProof nextSecret (_, _, lastPk, _, _) = lastPk == toPublic nextSecret + verifySignatureProof :: Signature -> SignedBlock -> Bool -verifySignatureProof extraSig (lastPayload, Signature lastSig, lastPk, _) = +verifySignatureProof extraSig (lastPayload, Signature lastSig, lastPk, _, _) = let toSign = lastPayload <> serializePublicKey lastPk <> lastSig in verify lastPk toSign extraSig + +signAuthorityBlockV1 :: SecretKey -> ByteString -> IO (SignedBlock, SecretKey) +signAuthorityBlockV1 sk payload = do + let pk = toPublic sk + (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey + let toSign = getAuthoritySignaturePayloadV1 payload nextPk + sig = sign sk pk toSign + pure ((payload, sig, nextPk, Nothing, Just 1), nextSk) + +signAttenuationBlockV1 :: SecretKey -> Signature -> ByteString -> Maybe (Signature, PublicKey) -> IO (SignedBlock, SecretKey) +signAttenuationBlockV1 sk prevSig payload ePk = do + let pk = toPublic sk + (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey + let toSign = getBlockSignaturePayloadV1 payload nextPk ePk prevSig + sig = sign sk pk toSign + pure ((payload, sig, nextPk, ePk, Just 1), nextSk) + +sign3rdPartyBlockV1 :: SecretKey + -> Signature + -> ByteString + -> (Signature, PublicKey) +sign3rdPartyBlockV1 eSk prevSig payload = + let toSign = getExternalSignaturePayloadV1 payload prevSig + ePk = toPublic eSk + eSig = sign eSk ePk toSign + in (eSig, ePk) + +signAuthority :: SecretKey + -> (ByteString, Int) + -> IO (SignedBlock, SecretKey) +signAuthority secretKey (payload, blockVersion) + | blockVersion >= 6 = signAuthorityBlockV1 secretKey payload + | otherwise = signBlockV0 secretKey payload Nothing + +signAttenuationBlock :: SecretKey + -> Signature + -> (ByteString, Int) + -> Maybe (Signature, PublicKey) + -> IO (SignedBlock, SecretKey) +signAttenuationBlock secretKey prevSig (payload, blockVersion) ePk + | blockVersion >= 6 || isJust ePk = signAttenuationBlockV1 secretKey prevSig payload ePk + | otherwise = signBlockV0 secretKey payload ePk + +signExternalBlock :: SecretKey + -> Signature + -> (ByteString, Int) + -> SecretKey + -> IO (SignedBlock, SecretKey) +signExternalBlock secretKey prevSig (payload, blockVersion) eSk = + let ePk = sign3rdPartyBlockV1 eSk prevSig payload + in signAttenuationBlock secretKey prevSig (payload, blockVersion) (Just ePk) diff --git a/biscuit/src/Auth/Biscuit/Datalog/AST.hs b/biscuit/src/Auth/Biscuit/Datalog/AST.hs index 94dc069..d63654c 100644 --- a/biscuit/src/Auth/Biscuit/Datalog/AST.hs +++ b/biscuit/src/Auth/Biscuit/Datalog/AST.hs @@ -64,6 +64,9 @@ module Auth.Biscuit.Datalog.AST , RuleScope , EvalRuleScope , SetType + , ArrayType + , MapType + , MapKey (..) , Slice (..) , PkOrSlice (..) , SliceType @@ -89,12 +92,17 @@ module Auth.Biscuit.Datalog.AST , queryHasNoV4Operators , ruleHasNoScope , ruleHasNoV4Operators + , ruleHasNoV6Values + , predicateHasNoV6Values + , checkHasNoV6Values , isCheckOne + , isReject , renderBlock , renderAuthorizer , renderFact , renderRule , valueToSetTerm + , setValueToValue , toStack , substituteAuthorizer , substituteBlock @@ -187,6 +195,19 @@ type family SetType (inSet :: IsWithinSet) (ctx :: DatalogContext) where SetType 'NotWithinSet ctx = Set (Term' 'WithinSet 'InFact ctx) SetType 'WithinSet ctx = Void +type family ArrayType (inSet:: IsWithinSet) (ctx :: DatalogContext) where + ArrayType 'NotWithinSet ctx = [Term' 'NotWithinSet 'InFact ctx] + ArrayType 'WithinSet ctx = Void + +data MapKey + = IntKey Int64 + | StringKey Text + deriving (Eq, Show, Ord, Lift) + +type family MapType (inSet:: IsWithinSet) (ctx :: DatalogContext) where + MapType 'NotWithinSet ctx = Map MapKey (Term' 'NotWithinSet 'InFact ctx) + MapType 'WithinSet ctx = Void + type family BlockIdType (evalCtx :: EvaluationContext) (ctx :: DatalogContext) where BlockIdType 'Repr 'WithSlices = PkOrSlice BlockIdType 'Repr 'Representation = PublicKey @@ -211,21 +232,33 @@ data Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContex | Antiquote (SliceType ctx) -- ^ A slice (eg. @{name}@) | TermSet (SetType inSet ctx) - -- ^ A set (eg. @[true, false]@) + -- ^ A set (eg. @{true, false}@) + | LNull + -- ^ @null@ + | TermArray (ArrayType inSet ctx) + -- ^ An array (eg. @[1, true, []]@) + | TermMap (MapType inSet ctx) + -- ^ A map (eg @{"key": true, 1: null}@) deriving instance ( Eq (VariableType inSet pof) , Eq (SliceType ctx) , Eq (SetType inSet ctx) + , Eq (ArrayType inSet ctx) + , Eq (MapType inSet ctx) ) => Eq (Term' inSet pof ctx) deriving instance ( Ord (VariableType inSet pof) , Ord (SliceType ctx) , Ord (SetType inSet ctx) + , Ord (ArrayType inSet ctx) + , Ord (MapType inSet ctx) ) => Ord (Term' inSet pof ctx) deriving instance ( Show (VariableType inSet pof) , Show (SliceType ctx) , Show (SetType inSet ctx) + , Show (ArrayType inSet ctx) + , Show (MapType inSet ctx) ) => Show (Term' inSet pof ctx) -- | In a regular AST, slices have already been eliminated @@ -239,17 +272,22 @@ type SetValue = Term' 'WithinSet 'InFact 'Representation instance ( Lift (VariableType inSet pof) , Lift (SetType inSet ctx) + , Lift (ArrayType inSet ctx) + , Lift (MapType inSet ctx) , Lift (SliceType ctx) ) => Lift (Term' inSet pof ctx) where - lift (Variable n) = [| Variable n |] - lift (LInteger i) = [| LInteger i |] - lift (LString s) = [| LString s |] - lift (LBytes bs) = [| LBytes bs |] - lift (LBool b) = [| LBool b |] - lift (TermSet terms) = [| TermSet terms |] - lift (LDate t) = [| LDate (read $(lift $ show t)) |] - lift (Antiquote s) = [| s |] + lift (Variable n) = [| Variable n |] + lift (LInteger i) = [| LInteger i |] + lift (LString s) = [| LString s |] + lift (LBytes bs) = [| LBytes bs |] + lift (LBool b) = [| LBool b |] + lift (TermSet terms) = [| TermSet terms |] + lift (LDate t) = [| LDate (read $(lift $ show t)) |] + lift LNull = [| LNull |] + lift (Antiquote s) = [| s |] + lift (TermArray terms) = [| TermArray terms |] + lift (TermMap terms) = [| TermMap terms |] #if MIN_VERSION_template_haskell(2,17,0) liftTyped = liftCode . unsafeTExpCoerce . lift @@ -323,7 +361,24 @@ valueToSetTerm = \case LDate i -> Just $ LDate i LBytes i -> Just $ LBytes i LBool i -> Just $ LBool i + LNull -> Just LNull TermSet _ -> Nothing + TermArray _ -> Nothing + TermMap _ -> Nothing + Variable v -> absurd v + Antiquote v -> absurd v + +setValueToValue :: SetValue -> Value +setValueToValue = \case + LInteger i -> LInteger i + LString i -> LString i + LDate i -> LDate i + LBytes i -> LBytes i + LBool i -> LBool i + LNull -> LNull + TermSet v -> absurd v + TermArray v -> absurd v + TermMap v -> absurd v Variable v -> absurd v Antiquote v -> absurd v @@ -334,15 +389,20 @@ valueToTerm = \case LDate i -> LDate i LBytes i -> LBytes i LBool i -> LBool i + LNull -> LNull TermSet i -> TermSet i + TermArray i -> TermArray i + TermMap i -> TermMap i Variable v -> absurd v Antiquote v -> absurd v renderId' :: (VariableType inSet pof -> Text) -> (SetType inSet ctx -> Text) + -> (ArrayType inSet ctx -> Text) + -> (MapType inSet ctx -> Text) -> (SliceType ctx -> Text) -> Term' inSet pof ctx -> Text -renderId' var set slice = \case +renderId' var rset rarray rmap rslice = \case Variable name -> var name LInteger int -> pack $ show int LString str -> pack $ show str @@ -350,20 +410,64 @@ renderId' var set slice = \case LBytes bs -> "hex:" <> encodeHex bs LBool True -> "true" LBool False -> "false" - TermSet terms -> set terms - Antiquote v -> slice v + LNull -> "null" + TermSet terms -> rset terms + TermArray terms -> rarray terms + TermMap terms -> rmap terms + Antiquote v -> rslice v renderSet :: (SliceType ctx -> Text) -> Set (Term' 'WithinSet 'InFact ctx) -> Text -renderSet slice terms = - "[" <> intercalate "," (renderId' absurd absurd slice <$> Set.toList terms) <> "]" +renderSet rslice terms = + if null terms + then "{,}" + else + "{" <> intercalate "," (renderId' absurd absurd absurd absurd rslice <$> Set.toList terms) <> "}" + +renderArray :: (SliceType ctx -> Text) + -> [Term' 'NotWithinSet 'InFact ctx] + -> Text +renderArray rslice terms = + let renderElem = renderId' + absurd + (renderSet rslice) + (renderArray rslice) + (renderMap rslice) + rslice + in "[" <> intercalate "," (renderElem <$> terms) <> "]" + +renderMap :: (SliceType ctx -> Text) + -> Map MapKey (Term' 'NotWithinSet 'InFact ctx) + -> Text +renderMap rslice terms = + let renderElem = renderId' + absurd + (renderSet rslice) + (renderArray rslice) + (renderMap rslice) + rslice + renderKey (IntKey k) = pack $ show k + renderKey (StringKey str) = pack $ show str + renderEntry (k,v) = renderKey k <> ": " <> renderElem v + in "{" <> intercalate "," (renderEntry <$> Map.toList terms) <> "}" renderId :: Term -> Text -renderId = renderId' ("$" <>) (renderSet absurd) absurd +renderId = renderId' + ("$" <>) + (renderSet absurd) + (renderArray absurd) + (renderMap absurd) + absurd renderFactId :: Term' 'NotWithinSet 'InFact 'Representation -> Text -renderFactId = renderId' absurd (renderSet absurd) absurd +renderFactId = + renderId' + absurd + (renderSet absurd) + (renderArray absurd) + (renderMap absurd) + absurd listSymbolsInTerm :: Term -> Set.Set Text listSymbolsInTerm = \case @@ -453,7 +557,7 @@ makeQueryItem qBody qExpressions qScope = Just vs -> Failure vs -data CheckKind = One | All +data CheckKind = CheckOne | CheckAll | Reject deriving (Eq, Show, Ord, Lift) data Check' evalCtx ctx = Check @@ -473,7 +577,10 @@ type Check = Check' 'Repr 'Representation type EvalCheck = Check' 'Eval 'Representation isCheckOne :: Check' evalCtx ctx -> Bool -isCheckOne Check{cKind} = cKind == One +isCheckOne Check{cKind} = cKind == CheckOne + +isReject :: Check' evalCtx ctx -> Bool +isReject Check{cKind} = cKind == Reject data PolicyType = Allow | Deny deriving (Eq, Show, Ord, Lift) @@ -516,10 +623,11 @@ renderQueryItem QueryItem{..} = renderCheck :: Check -> Text renderCheck Check{..} = - let kindToken = case cKind of - One -> "if" - All -> "all" - in "check " <> kindToken <> " " <> + let keyword = case cKind of + CheckOne -> "check if" + CheckAll -> "check all" + Reject -> "reject if" + in keyword <> " " <> intercalate "\n or " (renderQueryItem <$> cQueries) listSymbolsInQueryItem :: QueryItem' evalCtx 'Representation -> Set.Set Text @@ -604,6 +712,40 @@ ruleHasNoV4Operators :: Rule -> Bool ruleHasNoV4Operators Rule{expressions} = all expressionHasNoV4Operators expressions +expressionHasNoV6ValuesOrOperators :: Expression -> Bool +expressionHasNoV6ValuesOrOperators = \case + EClosure _ _ -> False + EBinary HeterogeneousEqual _ _ -> False + EBinary HeterogeneousNotEqual _ _ -> False + EBinary LazyAnd _ _ -> False + EBinary LazyOr _ _ -> False + EBinary All _ _ -> False + EBinary Any _ _ -> False + EBinary _ l r -> expressionHasNoV6ValuesOrOperators l && expressionHasNoV6ValuesOrOperators r + EUnary _ l -> expressionHasNoV6ValuesOrOperators l + EValue LNull -> False + EValue _ -> True + +ruleHasNoV6Values :: Rule -> Bool +ruleHasNoV6Values Rule{rhead, body, expressions} = + predicateHasNoV6Values rhead + && all predicateHasNoV6Values body + && all expressionHasNoV6ValuesOrOperators expressions + +predicateHasNoV6Values :: Predicate' a b -> Bool +predicateHasNoV6Values Predicate{terms} = + let hasV6 = \case + LNull -> True + _ -> False + in not (any hasV6 terms) + +checkHasNoV6Values :: Check -> Bool +checkHasNoV6Values Check{cQueries} = + let hasNoV6 QueryItem{qBody, qExpressions} = + all predicateHasNoV6Values qBody + && all expressionHasNoV6ValuesOrOperators qExpressions + in all hasNoV6 cQueries + renderRule :: Rule -> Text renderRule Rule{rhead,body,expressions,scope} = renderPredicate rhead <> " <- " @@ -634,9 +776,10 @@ extractExprVariables = Variable name -> Set.singleton name _ -> Set.empty in \case - EValue t -> keepVariable t - EUnary _ e -> extractExprVariables e - EBinary _ e e' -> ((<>) `on` extractExprVariables) e e' + EValue t -> keepVariable t + EUnary _ e -> extractExprVariables e + EBinary _ e e' -> ((<>) `on` extractExprVariables) e e' + EClosure params e -> extractExprVariables e Set.\\ Set.fromList params makeRule :: Predicate' 'InPredicate ctx -> [Predicate' 'InPredicate ctx] @@ -656,6 +799,8 @@ data Unary = Negate | Parens | Length + | TypeOf + | UnaryFfi Text deriving (Eq, Ord, Show, Lift) data Binary = @@ -680,12 +825,22 @@ data Binary = | BitwiseOr | BitwiseXor | NotEqual + | HeterogeneousEqual + | HeterogeneousNotEqual + | LazyAnd + | LazyOr + | All + | Any + | Get + | BinaryFfi Text + | Try deriving (Eq, Ord, Show, Lift) data Expression' (ctx :: DatalogContext) = EValue (Term' 'NotWithinSet 'InPredicate ctx) | EUnary Unary (Expression' ctx) | EBinary Binary (Expression' ctx) (Expression' ctx) + | EClosure [Text] (Expression' ctx) deriving instance Eq (Term' 'NotWithinSet 'InPredicate ctx) => Eq (Expression' ctx) deriving instance Ord (Term' 'NotWithinSet 'InPredicate ctx) => Ord (Expression' ctx) @@ -699,21 +854,28 @@ listSymbolsInExpression = \case EValue t -> listSymbolsInTerm t EUnary _ e -> listSymbolsInExpression e EBinary _ e e' -> foldMap listSymbolsInExpression [e, e'] + EClosure ps e -> Set.fromList ps <> listSymbolsInExpression e data Op = VOp Term | UOp Unary | BOp Binary + | COp [Text] [Op] + deriving (Eq, Show) fromStack :: [Op] -> Either String Expression fromStack = - let go stack [] = Right stack + let go :: [Expression] -> [Op] -> Either String [Expression] + go stack [] = Right stack go stack (VOp t : rest) = go (EValue t : stack) rest go (e:stack) (UOp o : rest) = go (EUnary o e : stack) rest go [] (UOp _ : _) = Left "Empty stack on unary op" go (e:e':stack) (BOp o : rest) = go (EBinary o e' e : stack) rest go [_] (BOp _ : _) = Left "Unary stack on binary op" go [] (BOp _ : _) = Left "Empty stack on binary op" + go stack (COp ps ops : rest) = do + e <- fromStack ops + go (EClosure ps e : stack) rest final [] = Left "Empty stack" final [x] = Right x final _ = Left "Stack containing more than one element" @@ -722,9 +884,10 @@ fromStack = toStack :: Expression -> [Op] toStack expr = let go e s = case e of - EValue t -> VOp t : s - EUnary o i -> go i $ UOp o : s - EBinary o l r -> go l $ go r $ BOp o : s + EValue t -> VOp t : s + EUnary o i -> go i $ UOp o : s + EBinary o l r -> go l $ go r $ BOp o : s + EClosure ps ce -> COp ps (toStack ce) : s in go expr [] renderExpression :: Expression -> Text @@ -736,16 +899,21 @@ renderExpression = <> "." <> m <> "(" <> renderExpression e' <> ")" + rC [] e = renderExpression e + rC [p] e = p <> " -> " <> renderExpression e + rC ps e = "(" <> intercalate ", " ps <> ")" <> renderExpression e in \case EValue t -> renderId t EUnary Negate e -> "!" <> renderExpression e EUnary Parens e -> "(" <> renderExpression e <> ")" EUnary Length e -> renderExpression e <> ".length()" + EUnary TypeOf e -> renderExpression e <> ".type()" + EUnary (UnaryFfi n) e -> renderExpression e <> ".extern::" <> n <> "()" EBinary LessThan e e' -> rOp "<" e e' EBinary GreaterThan e e' -> rOp ">" e e' EBinary LessOrEqual e e' -> rOp "<=" e e' EBinary GreaterOrEqual e e' -> rOp ">=" e e' - EBinary Equal e e' -> rOp "==" e e' + EBinary Equal e e' -> rOp "===" e e' EBinary Contains e e' -> rm "contains" e e' EBinary Prefix e e' -> rm "starts_with" e e' EBinary Suffix e e' -> rm "ends_with" e e' @@ -761,7 +929,17 @@ renderExpression = EBinary BitwiseAnd e e' -> rOp "&" e e' EBinary BitwiseOr e e' -> rOp "|" e e' EBinary BitwiseXor e e' -> rOp "^" e e' - EBinary NotEqual e e' -> rOp "!=" e e' + EBinary NotEqual e e' -> rOp "!==" e e' + EBinary HeterogeneousEqual e e' -> rOp "==" e e' + EBinary HeterogeneousNotEqual e e' -> rOp "!=" e e' + EBinary LazyAnd e e' -> rOp "&&" e e' + EBinary LazyOr e e' -> rOp "||" e e' + EBinary All e e' -> rm "all" e e' + EBinary Any e e' -> rm "any" e e' + EBinary Get e e' -> rm "get" e e' + EBinary (BinaryFfi n) e e' -> rm ("extern::" <> n) e e' + EBinary Try e e' -> rm "try_or" e e' + EClosure ps e -> rC ps e -- | A biscuit block, containing facts, rules and checks. -- @@ -1081,8 +1259,13 @@ substitutePTerm termMapping = \case LDate i -> pure $ LDate i LBytes i -> pure $ LBytes i LBool i -> pure $ LBool i + LNull -> pure LNull TermSet i -> TermSet . Set.fromList <$> traverse (substituteSetTerm termMapping) (Set.toList i) + TermArray i -> + TermArray <$> traverse (substituteTerm termMapping) i + TermMap i -> + TermMap <$> traverse (substituteTerm termMapping) i Variable i -> pure $ Variable i Antiquote (Slice v) -> maybe (failure v) (pure . valueToTerm) $ termMapping Map.!? v @@ -1095,8 +1278,13 @@ substituteTerm termMapping = \case LDate i -> pure $ LDate i LBytes i -> pure $ LBytes i LBool i -> pure $ LBool i + LNull -> pure LNull TermSet i -> TermSet . Set.fromList <$> traverse (substituteSetTerm termMapping) (Set.toList i) + TermArray i -> + TermArray <$> traverse (substituteTerm termMapping) i + TermMap i -> + TermMap <$> traverse (substituteTerm termMapping) i Variable v -> absurd v Antiquote (Slice v) -> maybe (failure v) pure $ termMapping Map.!? v @@ -1109,7 +1297,10 @@ substituteSetTerm termMapping = \case LDate i -> pure $ LDate i LBytes i -> pure $ LBytes i LBool i -> pure $ LBool i + LNull -> pure LNull TermSet v -> absurd v + TermArray v -> absurd v + TermMap v -> absurd v Variable v -> absurd v Antiquote (Slice v) -> let setTerm = valueToSetTerm =<< termMapping Map.!? v @@ -1123,6 +1314,7 @@ substituteExpression termMapping = \case EUnary op e -> EUnary op <$> substituteExpression termMapping e EBinary op e e' -> EBinary op <$> substituteExpression termMapping e <*> substituteExpression termMapping e' + EClosure ps e -> EClosure ps <$> substituteExpression termMapping e substituteScope :: Map Text PublicKey -> RuleScope' 'Repr 'WithSlices diff --git a/biscuit/src/Auth/Biscuit/Datalog/Executor.hs b/biscuit/src/Auth/Biscuit/Datalog/Executor.hs index 2889ff2..ad9f24c 100644 --- a/biscuit/src/Auth/Biscuit/Datalog/Executor.hs +++ b/biscuit/src/Auth/Biscuit/Datalog/Executor.hs @@ -20,6 +20,8 @@ module Auth.Biscuit.Datalog.Executor , ResultError (..) , Bindings , Name + , ExternFuncs + , ExternFunc (..) , MatchedQuery (..) , Scoped , FactGroup (..) @@ -28,6 +30,9 @@ module Auth.Biscuit.Datalog.Executor , fromScopedFacts , keepAuthorized' , defaultLimits + , setExternFuncs + , withExternFunc + , withExternFuncs , evaluateExpression -- , getFactsForRule @@ -44,11 +49,12 @@ import qualified Data.ByteString as ByteString import Data.Foldable (fold) import Data.Functor.Compose (Compose (..)) import Data.Int (Int64) +import qualified Data.List as List import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Data.Map.Strict (Map, (!?)) import qualified Data.Map.Strict as Map -import Data.Maybe (isJust, mapMaybe) +import Data.Maybe (fromMaybe, isJust, mapMaybe) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text, isInfixOf, unpack) @@ -69,6 +75,21 @@ type Name = Text -- | A list of bound variables, with the associated value type Bindings = Map Name Value +newtype ExternFunc = ExternFunc (Value -> Maybe Value -> Either String Value) + +instance Show ExternFunc where + show _ = "" + +instance Eq ExternFunc where + _ == _ = True + +type ExternFuncs = Map Text ExternFunc + +runExternFunc :: ExternFuncs -> Text -> Value -> Maybe Value -> Either String Value +runExternFunc ef name a1 a2 = do + ExternFunc func <- maybeToRight ("undefined external func " <> unpack name) $ ef !? name + func a1 a2 + -- | A datalog query that was matched, along with the values -- that matched data MatchedQuery @@ -123,6 +144,7 @@ data Limits , allowRegexes :: Bool -- ^ whether or not allowing `.matches()` during verification (untrusted regex computation -- can enable DoS attacks). This security risk is mitigated by the 'maxTime' setting. + , externFuncs :: ExternFuncs } deriving (Eq, Show) @@ -131,15 +153,24 @@ data Limits -- - 100 iterations -- - 1000μs max -- - regexes are allowed --- - facts and rules are allowed in blocks defaultLimits :: Limits defaultLimits = Limits { maxFacts = 1000 , maxIterations = 100 , maxTime = 1000 , allowRegexes = True + , externFuncs = mempty } +withExternFunc :: Text -> (Value -> Maybe Value -> Either String Value) -> Limits -> Limits +withExternFunc n f l@Limits{externFuncs} = l { externFuncs = Map.insert n (ExternFunc f) externFuncs } + +withExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits +withExternFuncs fs l@Limits{externFuncs} = l { externFuncs = Map.union (ExternFunc <$> fs) externFuncs } + +setExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits +setExternFuncs fs l = l { externFuncs = ExternFunc <$> fs } + type Scoped a = (Set Natural, a) newtype FactGroup = FactGroup { getFactGroup :: Map (Set Natural) (Set Fact) } @@ -191,13 +222,25 @@ countFacts (FactGroup facts) = sum $ Set.size <$> Map.elems facts checkCheck :: Limits -> Natural -> Natural -> FactGroup -> EvalCheck -> Either String (Validation (NonEmpty Check) ()) checkCheck l blockCount checkBlockId facts c@Check{cQueries,cKind} = do - let isQueryItemOk = case cKind of - One -> isQueryItemSatisfied l blockCount checkBlockId facts - All -> isQueryItemSatisfiedForAllMatches l blockCount checkBlockId facts - hasOkQueryItem <- anyM (fmap isJust . isQueryItemOk) cQueries - pure $ if hasOkQueryItem - then Success () - else failure (toRepresentation c) + let queryMatchesOne = isQueryItemSatisfied l blockCount checkBlockId facts + let queryMatchesAll = isQueryItemSatisfiedForAllMatches l blockCount checkBlockId facts + + case cKind of + CheckOne -> do + hasOkQueryItem <- anyM (fmap isJust . queryMatchesOne) cQueries + pure $ if hasOkQueryItem + then Success () + else failure (toRepresentation c) + CheckAll -> do + hasOkQueryItem <- anyM (fmap isJust . queryMatchesAll) cQueries + pure $ if hasOkQueryItem + then Success () + else failure (toRepresentation c) + Reject -> do + hasOkQueryItem <- anyM (fmap isJust . queryMatchesOne) cQueries + pure $ if not hasOkQueryItem + then Success () + else failure (toRepresentation c) checkPolicy :: Limits -> Natural -> FactGroup -> EvalPolicy -> Either String (Maybe (Either MatchedQuery MatchedQuery)) checkPolicy l blockCount facts (pType, query) = do @@ -274,7 +317,10 @@ applyBindings p@Predicate{terms} (origins, bindings) = replaceTerm (LDate t) = Just $ LDate t replaceTerm (LBytes t) = Just $ LBytes t replaceTerm (LBool t) = Just $ LBool t + replaceTerm LNull = Just LNull replaceTerm (TermSet t) = Just $ TermSet t + replaceTerm (TermArray t) = Just $ TermArray t + replaceTerm (TermMap t) = Just $ TermMap t replaceTerm (Antiquote t) = absurd t in (\nt -> (origins, p { terms = nt})) <$> newTerms @@ -329,6 +375,7 @@ isSame (LDate t) (LDate t') = t == t' isSame (LBytes t) (LBytes t') = t == t' isSame (LBool t) (LBool t') = t == t' isSame (TermSet t) (TermSet t') = t == t' +isSame LNull LNull = True isSame _ _ = False -- | Given a predicate and a fact, try to match the fact to the predicate, @@ -366,34 +413,57 @@ applyVariable bindings = \case LDate t -> Right $ LDate t LBytes t -> Right $ LBytes t LBool t -> Right $ LBool t + LNull -> Right LNull TermSet t -> Right $ TermSet t + TermArray t -> Right $ TermArray t + TermMap t -> Right $ TermMap t Antiquote v -> absurd v -evalUnary :: Unary -> Value -> Either String Value -evalUnary Parens t = pure t -evalUnary Negate (LBool b) = pure (LBool $ not b) -evalUnary Negate _ = Left "Only booleans support negation" -evalUnary Length (LString t) = pure . LInteger . fromIntegral $ ByteString.length $ Text.encodeUtf8 t -evalUnary Length (LBytes bs) = pure . LInteger . fromIntegral $ ByteString.length bs -evalUnary Length (TermSet s) = pure . LInteger . fromIntegral $ Set.size s -evalUnary Length _ = Left "Only strings, bytes and sets support `.length()`" +evalUnary :: Limits -> Unary -> Value -> Either String Value +evalUnary _ Parens t = pure t +evalUnary _ Negate (LBool b) = pure (LBool $ not b) +evalUnary _ Negate _ = Left "Only booleans support negation" +evalUnary _ Length (LString t) = pure . LInteger . fromIntegral $ ByteString.length $ Text.encodeUtf8 t +evalUnary _ Length (LBytes bs) = pure . LInteger . fromIntegral $ ByteString.length bs +evalUnary _ Length (TermSet s) = pure . LInteger . fromIntegral $ Set.size s +evalUnary _ Length (TermArray s) = pure . LInteger . fromIntegral $ length s +evalUnary _ Length (TermMap s) = pure . LInteger . fromIntegral $ Map.size s +evalUnary _ Length _ = Left "Only strings, bytes, sets, arrays and maps support `.length()`" +evalUnary _ TypeOf (LInteger _) = pure . LString $ "integer" +evalUnary _ TypeOf (LString _) = pure . LString $ "string" +evalUnary _ TypeOf (LDate _) = pure . LString $ "date" +evalUnary _ TypeOf (LBytes _) = pure . LString $ "bytes" +evalUnary _ TypeOf (LBool _) = pure . LString $ "bool" +evalUnary _ TypeOf (TermSet _) = pure . LString $ "set" +evalUnary _ TypeOf (TermArray _) = pure . LString $ "array" +evalUnary _ TypeOf (TermMap _) = pure . LString $ "map" +evalUnary _ TypeOf LNull = pure . LString $ "null" +evalUnary _ TypeOf (Variable v) = absurd v +evalUnary _ TypeOf (Antiquote v) = absurd v +evalUnary Limits{externFuncs} (UnaryFfi n) v = runExternFunc externFuncs n v Nothing evalBinary :: Limits -> Binary -> Value -> Value -> Either String Value -- eq / ord operations -evalBinary _ Equal (LInteger i) (LInteger i') = pure $ LBool (i == i') -evalBinary _ Equal (LString t) (LString t') = pure $ LBool (t == t') -evalBinary _ Equal (LDate t) (LDate t') = pure $ LBool (t == t') -evalBinary _ Equal (LBytes t) (LBytes t') = pure $ LBool (t == t') -evalBinary _ Equal (LBool t) (LBool t') = pure $ LBool (t == t') -evalBinary _ Equal (TermSet t) (TermSet t') = pure $ LBool (t == t') -evalBinary _ Equal _ _ = Left "Equality mismatch" -evalBinary _ NotEqual (LInteger i) (LInteger i') = pure $ LBool (i /= i') -evalBinary _ NotEqual (LString t) (LString t') = pure $ LBool (t /= t') -evalBinary _ NotEqual (LDate t) (LDate t') = pure $ LBool (t /= t') -evalBinary _ NotEqual (LBytes t) (LBytes t') = pure $ LBool (t /= t') -evalBinary _ NotEqual (LBool t) (LBool t') = pure $ LBool (t /= t') -evalBinary _ NotEqual (TermSet t) (TermSet t') = pure $ LBool (t /= t') -evalBinary _ NotEqual _ _ = Left "Inequity mismatch" +evalBinary _ Equal (LInteger i) (LInteger i') = pure $ LBool (i == i') +evalBinary _ Equal (LString t) (LString t') = pure $ LBool (t == t') +evalBinary _ Equal (LDate t) (LDate t') = pure $ LBool (t == t') +evalBinary _ Equal (LBytes t) (LBytes t') = pure $ LBool (t == t') +evalBinary _ Equal (LBool t) (LBool t') = pure $ LBool (t == t') +evalBinary _ Equal (TermSet t) (TermSet t') = pure $ LBool (t == t') +evalBinary _ Equal (TermArray t) (TermArray t') = pure $ LBool (t == t') +evalBinary _ Equal (TermMap t) (TermMap t') = pure $ LBool (t == t') +evalBinary _ Equal _ _ = Left "Equality mismatch" +evalBinary _ NotEqual (LInteger i) (LInteger i') = pure $ LBool (i /= i') +evalBinary _ NotEqual (LString t) (LString t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (LDate t) (LDate t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (LBytes t) (LBytes t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (LBool t) (LBool t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (TermSet t) (TermSet t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (TermArray t) (TermArray t') = pure $ LBool (t /= t') +evalBinary _ NotEqual (TermMap t) (TermMap t') = pure $ LBool (t /= t') +evalBinary _ NotEqual _ _ = Left "Inequity mismatch" +evalBinary _ HeterogeneousEqual t t' = pure $ LBool (t == t') +evalBinary _ HeterogeneousNotEqual t t' = pure $ LBool (t /= t') evalBinary _ LessThan (LInteger i) (LInteger i') = pure $ LBool (i < i') evalBinary _ LessThan (LDate t) (LDate t') = pure $ LBool (t < t') evalBinary _ LessThan _ _ = Left "< mismatch" @@ -408,8 +478,10 @@ evalBinary _ GreaterOrEqual (LDate t) (LDate t') = pure $ LBool (t >= t') evalBinary _ GreaterOrEqual _ _ = Left ">= mismatch" -- string-related operations evalBinary _ Prefix (LString t) (LString t') = pure $ LBool (t' `Text.isPrefixOf` t) -evalBinary _ Prefix _ _ = Left "Only strings support `.starts_with()`" +evalBinary _ Prefix (TermArray t) (TermArray t') = pure . LBool $ t' `List.isPrefixOf` t +evalBinary _ Prefix _ _ = Left "Only strings and arrays support `.starts_with()`" evalBinary _ Suffix (LString t) (LString t') = pure $ LBool (t' `Text.isSuffixOf` t) +evalBinary _ Suffix (TermArray t) (TermArray t') = pure . LBool $ t' `List.isSuffixOf` t evalBinary _ Suffix _ _ = Left "Only strings support `.ends_with()`" evalBinary Limits{allowRegexes} Regex (LString t) (LString r) | allowRegexes = regexMatch t r | otherwise = Left "Regex evaluation is disabled" @@ -437,17 +509,34 @@ evalBinary _ And (LBool b) (LBool b') = pure $ LBool (b && b') evalBinary _ And _ _ = Left "Only booleans support &&" evalBinary _ Or (LBool b) (LBool b') = pure $ LBool (b || b') evalBinary _ Or _ _ = Left "Only booleans support ||" +evalBinary _ LazyAnd _ _ = Left "internal error: leftover &&" +evalBinary _ LazyOr _ _ = Left "internal error: leftover ||" -- set operations evalBinary _ Contains (TermSet t) (TermSet t') = pure $ LBool (Set.isSubsetOf t' t) evalBinary _ Contains (TermSet t) t' = case valueToSetTerm t' of Just t'' -> pure $ LBool (Set.member t'' t) Nothing -> Left "Sets cannot contain nested sets nor variables" evalBinary _ Contains (LString t) (LString t') = pure $ LBool (t' `isInfixOf` t) +evalBinary _ Contains (TermArray t) t' = pure . LBool $ t' `elem` t +evalBinary _ Contains (TermMap t) (LInteger i) = pure . LBool $ IntKey i `Map.member` t +evalBinary _ Contains (TermMap t) (LString s) = pure . LBool $ StringKey s `Map.member` t +evalBinary _ Contains (TermMap _) _ = pure $ LBool False evalBinary _ Contains _ _ = Left "Only sets and strings support `.contains()`" evalBinary _ Intersection (TermSet t) (TermSet t') = pure $ TermSet (Set.intersection t t') evalBinary _ Intersection _ _ = Left "Only sets support `.intersection()`" evalBinary _ Union (TermSet t) (TermSet t') = pure $ TermSet (Set.union t t') evalBinary _ Union _ _ = Left "Only sets support `.union()`" +evalBinary _ Get (TermArray t) (LInteger i) = pure $ + if i < List.genericLength t + then List.genericIndex t i + else LNull +evalBinary _ Get (TermMap t) (LInteger i) = pure . fromMaybe LNull $ t !? IntKey i +evalBinary _ Get (TermMap t) (LString s) = pure . fromMaybe LNull $ t !? StringKey s +evalBinary _ Get _ _ = Left "Only arrays and maps support `.get()`" +evalBinary _ Any _ _ = Left "internal error: leftover .any()" +evalBinary _ All _ _ = Left "internal error: leftover .all()" +evalBinary _ Try _ _ = Left "internal error: leftover .try_or()" +evalBinary Limits{externFuncs} (BinaryFfi n) l r = runExternFunc externFuncs n l (Just r) checkedOp :: (Integer -> Integer -> Integer) -> Int64 -> Int64 @@ -466,6 +555,100 @@ regexMatch text regexT = do result <- Regex.execute regex text pure . LBool $ isJust result +evaluateAll :: Limits + -> Bindings + -> Value + -> Expression + -> Either String Value +evaluateAll l b xs' (EClosure [p] e) = + let runClosure v = do + if Map.member p b + then Left "Shadowed variable" + else Right () + evaluateExpression l (Map.insert p v b) e >>= \case + LBool x -> Right x + _ -> Left "Expected boolean" + makeArray :: (MapKey, Value) -> Value + makeArray (k,v) = case k of + IntKey i -> TermArray [LInteger i, v] + StringKey s -> TermArray [LString s, v] + in case xs' of + TermSet xs -> LBool <$> allM (runClosure . setValueToValue) xs + TermArray xs -> LBool <$> allM runClosure xs + TermMap xs -> LBool <$> allM (runClosure . makeArray) (Map.toList xs) + _ -> Left "Only sets, arrays and maps support .all()" +evaluateAll _ _ _ _ = Left "Expected closure" + +evaluateAny :: Limits + -> Bindings + -> Value + -> Expression + -> Either String Value +evaluateAny l b xs' (EClosure [p] e) = + let runClosure v = do + if Map.member p b + then Left "Shadowed variable" + else Right () + evaluateExpression l (Map.insert p v b) e >>= \case + LBool x -> Right x + _ -> Left "Expected boolean" + makeArray :: (MapKey, Value) -> Value + makeArray (k,v) = case k of + IntKey i -> TermArray [LInteger i, v] + StringKey s -> TermArray [LString s, v] + in case xs' of + TermSet xs -> LBool <$> anyM (runClosure . setValueToValue) xs + TermArray xs -> LBool <$> anyM runClosure xs + TermMap xs -> LBool <$> anyM (runClosure . makeArray) (Map.toList xs) + _ -> Left "Only sets, arrays and maps support .any()" +evaluateAny _ _ _ _ = Left "Expected closure" + +evaluateLazyAnd :: Limits + -> Bindings + -> Value + -> Expression + -> Either String Value +evaluateLazyAnd l b lhs' (EClosure [] e) = + let runClosure = + evaluateExpression l b e >>= \case + LBool x -> Right x + _ -> Left "Expected boolean" + in case lhs' of + LBool lhs -> if lhs + then LBool <$> runClosure + else Right $ LBool False + _ -> Left "Expected boolean" +evaluateLazyAnd _ _ _ _ = Left "Expected closure" + +evaluateLazyOr :: Limits + -> Bindings + -> Value + -> Expression + -> Either String Value +evaluateLazyOr l b lhs' (EClosure [] e) = + let runClosure = + evaluateExpression l b e >>= \case + LBool x -> Right x + _ -> Left "Expected boolean" + in case lhs' of + LBool lhs -> if lhs + then Right $ LBool True + else LBool <$> runClosure + _ -> Left "Expected boolean" +evaluateLazyOr _ _ _ _ = Left "Expected closure" + +evaluateTry :: Limits + -> Bindings + -> Expression + -> Expression + -> Either String Value +evaluateTry l b (EClosure [] e) e' = do + rhs <- evaluateExpression l b e' + case evaluateExpression l b e of + Right r -> Right r + Left _ -> Right rhs +evaluateTry _ _ _ _ = Left "Expected closure" + -- | Given bindings for variables, reduce an expression to a single -- datalog value evaluateExpression :: Limits @@ -474,6 +657,19 @@ evaluateExpression :: Limits -> Either String Value evaluateExpression l b = \case EValue term -> applyVariable b term - EUnary op e' -> evalUnary op =<< evaluateExpression l b e' - EBinary op e' e'' -> uncurry (evalBinary l op) =<< join bitraverse (evaluateExpression l b) (e', e'') - + EUnary op e -> evalUnary l op =<< evaluateExpression l b e + EBinary LazyAnd e e' -> do + lhs <- evaluateExpression l b e + evaluateLazyAnd l b lhs e' + EBinary LazyOr e e' -> do + lhs <- evaluateExpression l b e + evaluateLazyOr l b lhs e' + EBinary Any e e' -> do + lhs <- evaluateExpression l b e + evaluateAny l b lhs e' + EBinary All e e' -> do + lhs <- evaluateExpression l b e + evaluateAll l b lhs e' + EBinary Try e e' -> evaluateTry l b e e' + EBinary op e e' -> uncurry (evalBinary l op) =<< join bitraverse (evaluateExpression l b) (e, e') + EClosure _ _ -> Left "Unexpected closure" diff --git a/biscuit/src/Auth/Biscuit/Datalog/Parser.hs b/biscuit/src/Auth/Biscuit/Datalog/Parser.hs index 0779bcb..ac44082 100644 --- a/biscuit/src/Auth/Biscuit/Datalog/Parser.hs +++ b/biscuit/src/Auth/Biscuit/Datalog/Parser.hs @@ -35,6 +35,7 @@ import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map import Data.Maybe (isJust) import Data.Set (Set) import qualified Data.Set as Set @@ -58,7 +59,7 @@ type Span = (Int, Int) data SemanticError = VarInFact Span - | VarInSet Span + | VarInCollection Span | NestedSet Span | InvalidBs Text Span | InvalidPublicKey Text Span @@ -69,7 +70,7 @@ data SemanticError = instance ShowErrorComponent SemanticError where showErrorComponent = \case VarInFact _ -> "Variables can't appear in a fact" - VarInSet _ -> "Variables can't appear in a set" + VarInCollection _ -> "Variables can't appear in a collection (set, array, map" NestedSet _ -> "Sets cannot be nested" InvalidBs e _ -> "Invalid bytestring literal: " <> T.unpack e InvalidPublicKey e _ -> "Invalid public key: " <> T.unpack e @@ -100,9 +101,12 @@ forbid mkError p = do (sp, _) <- getSpan p registerError mkError sp +identifierParser :: Parser Text +identifierParser = takeWhile1P (Just "_, :, or any alphanumeric char") (\c -> c == '_' || c == ':' || isAlphaNum c) + variableParser :: Parser Text variableParser = - C.char '$' *> takeWhile1P (Just "_, :, or any alphanumeric char") (\c -> c == '_' || c == ':' || isAlphaNum c) + C.char '$' *> identifierParser haskellVariableParser :: Parser Text haskellVariableParser = l $ do @@ -114,37 +118,92 @@ haskellVariableParser = l $ do pure . maybe id T.cons leadingUS $ T.cons x xs setParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices)) -setParser = do +setParser = choice [emptySetParser, nonEmptySetParser] + +emptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices)) +emptySetParser = mempty <$ chunk "{,}" + +setTermParser :: Parser (Term' 'WithinSet 'InFact 'WithSlices) +setTermParser = termParser + (forbid VarInCollection variableParser) + (forbid NestedSet setParser) + (forbid NestedSet arrayParser) + (forbid NestedSet mapParser) + +nonEmptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices)) +nonEmptySetParser = do + _ <- l $ C.char '{' + ts <- sepBy1 setTermParser (l $ C.char ',') + _ <- l $ C.char '}' + pure $ Set.fromList ts + +valueParser :: Parser (Term' 'NotWithinSet 'InFact 'WithSlices) +valueParser = termParser + (forbid VarInCollection variableParser) + setParser + arrayParser + mapParser + +arrayParser :: Parser [Term' 'NotWithinSet 'InFact 'WithSlices] +arrayParser = do _ <- l $ C.char '[' - ts <- sepBy (termParser (forbid VarInSet variableParser) (forbid NestedSet setParser)) (l $ C.char ',') + ts <- sepBy1 valueParser (l $ C.char ',') _ <- l $ C.char ']' - pure $ Set.fromList ts + pure ts + +mapEntryParser :: Parser (MapKey, Term' 'NotWithinSet 'InFact 'WithSlices) +mapEntryParser = do + k <- l $ choice [ IntKey <$> intParser + , StringKey <$> stringParser + ] + _ <- l $ C.char ':' + v <- l valueParser + pure (k, v) + +mapParser :: Parser (Map MapKey (Term' 'NotWithinSet 'InFact 'WithSlices)) +mapParser = do + _ <- l $ C.char '{' + ts <- sepBy1 mapEntryParser (l $ C.char ',') + _ <- l $ C.char '}' + pure $ Map.fromList ts factTermParser :: Parser (Term' 'NotWithinSet 'InFact 'WithSlices) factTermParser = termParser (forbid VarInFact variableParser) setParser + arrayParser + mapParser predicateTermParser :: Parser (Term' 'NotWithinSet 'InPredicate 'WithSlices) predicateTermParser = termParser variableParser setParser + arrayParser + mapParser termParser :: Parser (VariableType inSet pof) -> Parser (SetType inSet 'WithSlices) + -> Parser (ArrayType inSet 'WithSlices) + -> Parser (MapType inSet 'WithSlices) -> Parser (Term' inSet pof 'WithSlices) -termParser parseVar parseSet = l $ choice - [ Antiquote . Slice <$> haskellVariableParser "parameter (eg. {paramName})" +termParser parseVar parseSet parseArray parseMap = l $ choice + [ TermSet <$> try parseSet "set (eg. {1,2,3})" + , TermArray <$> try parseArray "array (eg. [1, 2, 3])" + , TermMap <$> try parseMap "map (eg. {\"key\": 1})" + , Antiquote . Slice <$> haskellVariableParser "parameter (eg. {paramName})" , Variable <$> parseVar "datalog variable (eg. $variable)" - , TermSet <$> parseSet "set (eg. [1,2,3])" , LBytes <$> (chunk "hex:" *> hexParser) "hex-encoded bytestring (eg. hex:00ff99)" , LDate <$> rfc3339DateParser "RFC3339-formatted timestamp (eg. 2022-11-29T00:00:00Z)" , LInteger <$> intParser "(signed) integer" - , LString . T.pack <$> (C.char '"' *> manyTill L.charLiteral (C.char '"')) "string literal" + , LString <$> stringParser "string literal" , LBool <$> choice [ True <$ chunk "true" , False <$ chunk "false" ] "boolean value (eg. true or false)" + , LNull <$ chunk "null" "null value" ] +stringParser :: Parser Text +stringParser = T.pack <$> (C.char '"' *> manyTill L.charLiteral (C.char '"')) + intParser :: Parser Int64 intParser = do integer :: Integer <- L.signed C.space L.decimal "(signed) integer" @@ -233,6 +292,13 @@ factParser = predicateParser' factTermParser predicateParser :: Parser (Predicate' 'InPredicate 'WithSlices) predicateParser = predicateParser' predicateTermParser +closureParser :: Parser (Text, Expression' 'WithSlices) +closureParser = do + param <- l variableParser + _ <- l $ chunk "->" + body <- l expressionParser + pure (param, body) + expressionParser :: Parser (Expression' 'WithSlices) expressionParser = let base = choice [ try methodsParser @@ -242,9 +308,12 @@ expressionParser = table :: [[Expr.Operator Parser (Expression' 'WithSlices)]] table = - let infixL name op = Expr.InfixL (EBinary op <$ l (chunk name) "infix operator") - infixN name op = Expr.InfixN (EBinary op <$ l (chunk name) "infix operator") + let infixL name op = Expr.InfixL (makeBinary op <$ l (chunk name) "infix operator") + infixN name op = Expr.InfixN (makeBinary op <$ l (chunk name) "infix operator") prefix name op = Expr.Prefix (EUnary op <$ l (chunk name) "prefix operator") + makeBinary LazyOr e e' = EBinary LazyOr e (EClosure [] e') + makeBinary LazyAnd e e' = EBinary LazyAnd e (EClosure [] e') + makeBinary op e e' = EBinary op e e' in [ [ prefix "!" Negate] , [ infixL "*" Mul , infixL "/" Div @@ -261,11 +330,13 @@ table = , infixN ">=" GreaterOrEqual , infixN "<" LessThan , infixN ">" GreaterThan - , infixN "==" Equal - , infixN "!=" NotEqual + , infixN "===" Equal + , infixN "!==" NotEqual + , infixN "==" HeterogeneousEqual + , infixN "!=" HeterogeneousNotEqual ] - , [ infixL "&&" And ] - , [ infixL "||" Or ] + , [ infixL "&&" LazyAnd ] + , [ infixL "||" LazyOr ] ] binaryMethodParser :: Parser (Expression' 'WithSlices -> Expression' 'WithSlices) @@ -278,16 +349,30 @@ binaryMethodParser = do , Prefix <$ chunk "starts_with" , Suffix <$ chunk "ends_with" , Regex <$ chunk "matches" + , Any <$ chunk "any" + , All <$ chunk "all" + , Get <$ chunk "get" + , BinaryFfi <$> (chunk "extern::" *> identifierParser) + , Try <$ chunk "try_or" ] _ <- l $ C.char '(' - e2 <- l expressionParser + e2 <- case method of + Any -> uncurry EClosure . first pure <$> l closureParser + All -> uncurry EClosure . first pure <$> l closureParser + _ -> l expressionParser _ <- l $ C.char ')' - pure $ \e1 -> EBinary method e1 e2 + pure $ \e1 -> case method of + Try -> EBinary method (EClosure [] e1) e2 + _ -> EBinary method e1 e2 unaryMethodParser :: Parser (Expression' 'WithSlices -> Expression' 'WithSlices) unaryMethodParser = do _ <- C.char '.' - method <- Length <$ chunk "length" + method <- choice + [ Length <$ chunk "length" + , TypeOf <$ chunk "type" + , UnaryFfi <$> (chunk "extern::" *> identifierParser) + ] _ <- l $ chunk "()" pure $ EUnary method @@ -362,8 +447,9 @@ queryParser inAuthorizer = checkParser :: Bool -> Parser (Check' 'Repr 'WithSlices) checkParser inAuthorizer = do - cKind <- l $ choice [ One <$ chunk "check if" - , All <$ chunk "check all" + cKind <- l $ choice [ CheckOne <$ chunk "check if" + , CheckAll <$ chunk "check all" + , Reject <$ chunk "reject if" ] cQueries <- queryParser inAuthorizer pure Check{..} diff --git a/biscuit/src/Auth/Biscuit/Example.hs b/biscuit/src/Auth/Biscuit/Example.hs index 398bb45..0c0abbb 100644 --- a/biscuit/src/Auth/Biscuit/Example.hs +++ b/biscuit/src/Auth/Biscuit/Example.hs @@ -30,7 +30,7 @@ creation = do let authority = [block| // this is a comment right("file1", {allowedOperations}); - check if source_ip($source_ip), ["127.0.0.1", {networkLocal}].contains($source_ip); + check if source_ip($source_ip), {"127.0.0.1", {networkLocal}}.contains($source_ip); |] biscuit <- mkBiscuit privateKey' authority let block1 = [block|check if time($time), $time < 2025-05-08T00:00:00Z;|] diff --git a/biscuit/src/Auth/Biscuit/Proto.hs b/biscuit/src/Auth/Biscuit/Proto.hs index 33de4c9..ca8ce22 100644 --- a/biscuit/src/Auth/Biscuit/Proto.hs +++ b/biscuit/src/Auth/Biscuit/Proto.hs @@ -29,13 +29,17 @@ module Auth.Biscuit.Proto , TermV2 (..) , ExpressionV2 (..) , TermSet (..) + , TermArray (..) + , TermMap (..) + , MapKey (..) + , MapEntry (..) + , Empty (..) , Op (..) , OpUnary (..) , UnaryKind (..) , OpBinary (..) , BinaryKind (..) - , OpTernary (..) - , TernaryKind (..) + , OpClosure (..) , ThirdPartyBlockContents (..) , ThirdPartyBlockRequest (..) , getField @@ -83,6 +87,7 @@ data SignedBlock = SignedBlock , nextKey :: Required 2 (Message PublicKey) , signature :: Required 3 (Value ByteString) , externalSig :: Optional 4 (Message ExternalSig) + , version :: Optional 5 (Value Int32) } deriving (Generic, Show) deriving anyclass (Decode, Encode) @@ -134,8 +139,9 @@ data RuleV2 = RuleV2 deriving anyclass (Decode, Encode) data CheckKind = - One - | All + CheckOne + | CheckAll + | Reject deriving stock (Show, Enum, Bounded) data CheckV2 = CheckV2 @@ -151,13 +157,20 @@ data PredicateV2 = PredicateV2 deriving anyclass (Decode, Encode) data TermV2 = - TermVariable (Required 1 (Value Int64)) - | TermInteger (Required 2 (Value Int64)) - | TermString (Required 3 (Value Int64)) - | TermDate (Required 4 (Value Int64)) - | TermBytes (Required 5 (Value ByteString)) - | TermBool (Required 6 (Value Bool)) - | TermTermSet (Required 7 (Message TermSet)) + TermVariable (Required 1 (Value Int64)) + | TermInteger (Required 2 (Value Int64)) + | TermString (Required 3 (Value Int64)) + | TermDate (Required 4 (Value Int64)) + | TermBytes (Required 5 (Value ByteString)) + | TermBool (Required 6 (Value Bool)) + | TermTermSet (Required 7 (Message TermSet)) + | TermNull (Required 8 (Message Empty)) + | TermTermArray (Required 9 (Message TermArray)) + | TermTermMap (Required 10 (Message TermMap)) + deriving stock (Generic, Show) + deriving anyclass (Decode, Encode) + +data Empty = Empty {} deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -167,6 +180,28 @@ newtype TermSet = TermSet } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) +newtype TermArray = TermArray + { array :: Repeated 1 (Message TermV2) + } deriving stock (Generic, Show) + deriving anyclass (Decode, Encode) + +data MapKey = + MapKeyInt (Required 1 (Value Int64)) + | MapKeyString (Required 2 (Value Int64)) + deriving stock (Generic, Show) + deriving anyclass (Decode, Encode) + +data MapEntry = MapEntry + { key :: Required 1 (Message MapKey) + , value :: Required 2 (Message TermV2) + } deriving stock (Generic, Show) + deriving anyclass (Decode, Encode) + +newtype TermMap = TermMap + { map :: Repeated 1 (Message MapEntry) + } deriving stock (Generic, Show) + deriving anyclass (Decode, Encode) + newtype ExpressionV2 = ExpressionV2 { ops :: Repeated 1 (Message Op) } deriving stock (Generic, Show) @@ -176,14 +211,16 @@ data Op = OpVValue (Required 1 (Message TermV2)) | OpVUnary (Required 2 (Message OpUnary)) | OpVBinary (Required 3 (Message OpBinary)) + | OpVClosure (Required 4 (Message OpClosure)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data UnaryKind = Negate | Parens | Length +data UnaryKind = Negate | Parens | Length | TypeOf | UnaryFfi deriving stock (Show, Enum, Bounded) -newtype OpUnary = OpUnary - { kind :: Required 1 (Enumeration UnaryKind) +data OpUnary = OpUnary + { kind :: Required 1 (Enumeration UnaryKind) + , ffiName :: Optional 2 (Value Int64) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -209,19 +246,26 @@ data BinaryKind = | BitwiseOr | BitwiseXor | NotEqual + | HeterogeneousEqual + | HeterogeneousNotEqual + | LazyAnd + | LazyOr + | All + | Any + | Get + | BinaryFfi + | Try deriving stock (Show, Enum, Bounded) -newtype OpBinary = OpBinary - { kind :: Required 1 (Enumeration BinaryKind) +data OpBinary = OpBinary + { kind :: Required 1 (Enumeration BinaryKind) + , ffiName :: Optional 2 (Value Int64) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data TernaryKind = - VerifyEd25519Signature - deriving stock (Show, Enum, Bounded) - -newtype OpTernary = OpTernary - { kind :: Required 1 (Enumeration TernaryKind) +data OpClosure = OpClosure + { params :: Repeated 1 (Value Int64) + , ops :: Repeated 2 (Message Op) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -253,8 +297,9 @@ decodeThirdPartyBlockContents = runGet decodeMessage data ThirdPartyBlockRequest = ThirdPartyBlockRequest - { previousPk :: Required 1 (Message PublicKey) - , pkTable :: Repeated 2 (Message PublicKey) + { legacyPk :: Optional 1 (Message PublicKey) + , pkTable :: Repeated 2 (Message PublicKey) + , prevSig :: Required 3 (Value ByteString) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) diff --git a/biscuit/src/Auth/Biscuit/ProtoBufAdapter.hs b/biscuit/src/Auth/Biscuit/ProtoBufAdapter.hs index 98482dc..7a0a5ae 100644 --- a/biscuit/src/Auth/Biscuit/ProtoBufAdapter.hs +++ b/biscuit/src/Auth/Biscuit/ProtoBufAdapter.hs @@ -28,9 +28,11 @@ module Auth.Biscuit.ProtoBufAdapter import Control.Monad (unless, when) import Control.Monad.State (StateT, get, lift, modify) +import Data.Bitraversable (bitraverse) import Data.ByteString (ByteString) import Data.Int (Int64) import qualified Data.List.NonEmpty as NE +import qualified Data.Map as Map import Data.Maybe (isJust, isNothing) import qualified Data.Set as Set import qualified Data.Text as T @@ -72,10 +74,12 @@ pbToSignedBlock PB.SignedBlock{..} = do let sig = Crypto.signature $ PB.getField signature mSig <- traverse pbToOptionalSignature $ PB.getField externalSig pk <- pbToPublicKey $ PB.getField nextKey + let sigVersion = fromIntegral <$> PB.getField version pure ( PB.getField block , sig , pk , mSig + , sigVersion ) publicKeyToPb :: Crypto.PublicKey -> PB.PublicKey @@ -91,11 +95,12 @@ externalSigToPb (sig, pk) = PB.ExternalSig } signedBlockToPb :: Crypto.SignedBlock -> PB.SignedBlock -signedBlockToPb (block, sig, pk, eSig) = PB.SignedBlock +signedBlockToPb (block, sig, pk, eSig, sigVersion) = PB.SignedBlock { block = PB.putField block , signature = PB.putField $ Crypto.sigBytes sig , nextKey = PB.putField $ publicKeyToPb pk , externalSig = PB.putField $ externalSigToPb <$> eSig + , version = PB.putField $ fromIntegral <$> sigVersion } pbToProof :: PB.Proof -> Either String (Either Crypto.Signature Crypto.SecretKey) @@ -129,7 +134,13 @@ pbToBlock ePk PB.Block{..} = do bRules <- traverse (pbToRule s) $ PB.getField rules_v2 bChecks <- traverse (pbToCheck s) $ PB.getField checks_v2 bScope <- Set.fromList <$> traverse (pbToScope s) (PB.getField scope) - let v5Plus = isJust ePk + let v6Plus = or + [ any isReject bChecks + , not (all predicateHasNoV6Values bFacts) + , not (all ruleHasNoV6Values bRules) + , not (all checkHasNoV6Values bChecks) + ] + v5Plus = isJust ePk v4Plus = not $ and [ Set.null bScope , all ruleHasNoScope bRules @@ -138,22 +149,29 @@ pbToBlock ePk PB.Block{..} = do , all ruleHasNoV4Operators bRules , all (queryHasNoV4Operators . cQueries) bChecks ] - case (bVersion, v4Plus, v5Plus) of - (Just 5, _, _) -> pure Block {..} - (Just 4, _, False) -> pure Block {..} - (Just 4, _, True) -> + case (bVersion, v4Plus, v5Plus, v6Plus) of + (Just 6, _, _, _) -> pure Block {..} + (Just 5, _, _, True) -> + Left "Biscuit v6 features are present, but the block version is 5." + (Just 5, _, _, _) -> pure Block {..} + (Just 4, _, False, False) -> pure Block {..} + (Just 4, _, _, True) -> + Left "Biscuit v6 features are present, but the block version is 4." + (Just 4, _, True, False) -> Left "Biscuit v5 features are present, but the block version is 4." - (Just 3, False, False) -> pure Block {..} - (Just 3, True, False) -> + (Just 3, False, False, False) -> pure Block {..} + (Just 3, True, False, False) -> Left "Biscuit v4 features are present, but the block version is 3." - (Just 3, _, True) -> + (Just 3, _, True, False) -> Left "Biscuit v5 features are present, but the block version is 3." + (Just 3, _, _, True) -> + Left "Biscuit v6 features are present, but the block version is 3." _ -> - Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only versions 3 and 4 are supported" + Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only versions 3 to 6 are supported" -- | Turn a biscuit block into a protobuf block, for serialization, -- along with the newly defined symbols -blockToPb :: Bool -> Symbols -> Block -> (BlockSymbols, PB.Block) +blockToPb :: Bool -> Symbols -> Block -> ((BlockSymbols, Int), PB.Block) blockToPb hasExternalPk existingSymbols b@Block{..} = let v4Plus = not $ and [Set.null bScope @@ -164,6 +182,12 @@ blockToPb hasExternalPk existingSymbols b@Block{..} = , all (queryHasNoV4Operators . cQueries) bChecks ] v5Plus = hasExternalPk + v6Plus = or + [ any isReject bChecks + , not (all predicateHasNoV6Values bFacts) + , not (all ruleHasNoV6Values bRules) + , not (all checkHasNoV6Values bChecks) + ] bSymbols = buildSymbolTable existingSymbols b s = reverseSymbols $ addFromBlock existingSymbols bSymbols symbols = PB.putField $ getSymbolList bSymbols @@ -173,10 +197,11 @@ blockToPb hasExternalPk existingSymbols b@Block{..} = checks_v2 = PB.putField $ checkToPb s <$> bChecks scope = PB.putField $ scopeToPb s <$> Set.toList bScope pksTable = PB.putField $ publicKeyToPb <$> getPkList bSymbols - version = PB.putField $ if | v5Plus -> Just 5 - | v4Plus -> Just 4 - | otherwise -> Just 3 - in (bSymbols, PB.Block {..}) + version = if | v6Plus -> 6 + | v5Plus -> 5 + | v4Plus -> 4 + | otherwise -> 3 + in ((bSymbols, version), PB.Block {version = PB.putField $ Just $ fromIntegral version, ..}) pbToFact :: Symbols -> PB.FactV2 -> Either String Fact pbToFact s PB.FactV2{predicate} = do @@ -224,9 +249,10 @@ pbToCheck s PB.CheckV2{queries,kind} = do rules <- traverse (pbToRule s) $ PB.getField queries let cQueries = toCheck <$> rules let cKind = case PB.getField kind of - Just PB.All -> All - Just PB.One -> One - Nothing -> One + Just PB.CheckAll -> CheckAll + Just PB.CheckOne -> CheckOne + Just PB.Reject -> Reject + Nothing -> CheckOne pure Check{..} checkToPb :: ReverseSymbols -> Check -> PB.CheckV2 @@ -239,8 +265,9 @@ checkToPb s Check{..} = , scope = qScope } pbKind = case cKind of - One -> Nothing - All -> Just PB.All + CheckOne -> Nothing + CheckAll -> Just PB.CheckAll + Reject -> Just PB.Reject in PB.CheckV2 { queries = PB.putField $ toQuery <$> cQueries , kind = PB.putField pbKind } @@ -286,6 +313,9 @@ pbToTerm s = \case PB.TermBool f -> pure $ LBool $ PB.getField f PB.TermVariable f -> Variable <$> getSymbol s (SymbolRef $ PB.getField f) PB.TermTermSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f) + PB.TermTermArray f -> TermArray <$> traverse (pbToValue s) (PB.getField . PB.array $ PB.getField f) + PB.TermTermMap f -> TermMap . Map.fromList <$> traverse (pbToMapEntry s) (PB.getField . PB.map $ PB.getField f) + PB.TermNull _ -> pure LNull termToPb :: ReverseSymbols -> Term -> PB.TermV2 termToPb s = \case @@ -296,9 +326,28 @@ termToPb s = \case LBytes v -> PB.TermBytes $ PB.putField v LBool v -> PB.TermBool $ PB.putField v TermSet vs -> PB.TermTermSet $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs + TermArray vs -> PB.TermTermArray $ PB.putField $ PB.TermArray $ PB.putField $ valueToPb s <$> vs + TermMap vs -> PB.TermTermMap $ PB.putField $ PB.TermMap $ PB.putField $ uncurry (mapEntryToPb s) <$> Map.toList vs + LNull -> PB.TermNull $ PB.putField $ PB.Empty {} Antiquote v -> absurd v +mapEntryToPb :: ReverseSymbols -> MapKey -> Value -> PB.MapEntry +mapEntryToPb s k v = PB.MapEntry + { key = PB.putField $ case k of + IntKey i -> PB.MapKeyInt . PB.putField $ fromIntegral i + StringKey n -> PB.MapKeyString . PB.putField $ getSymbolRef $ getSymbolCode s n + , value = PB.putField $ valueToPb s v + } + +pbToMapEntry :: Symbols -> PB.MapEntry -> Either String (MapKey, Value) +pbToMapEntry s PB.MapEntry{key,value} = do + k <- case PB.getField key of + PB.MapKeyInt i -> pure . IntKey . fromIntegral $ PB.getField i + PB.MapKeyString i -> StringKey <$> getSymbol s (SymbolRef $ PB.getField i) + v <- pbToValue s $ PB.getField value + pure (k, v) + pbToValue :: Symbols -> PB.TermV2 -> Either String Value pbToValue s = \case PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f @@ -308,6 +357,9 @@ pbToValue s = \case PB.TermBool f -> pure $ LBool $ PB.getField f PB.TermVariable _ -> Left "Variables can't appear in facts" PB.TermTermSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f) + PB.TermTermArray f -> TermArray <$> traverse (pbToValue s) (PB.getField . PB.array $ PB.getField f) + PB.TermTermMap f -> TermMap . Map.fromList <$> traverse (pbToMapEntry s) (PB.getField . PB.map $ PB.getField f) + PB.TermNull _ -> pure LNull valueToPb :: ReverseSymbols -> Value -> PB.TermV2 valueToPb s = \case @@ -317,6 +369,9 @@ valueToPb s = \case LBytes v -> PB.TermBytes $ PB.putField v LBool v -> PB.TermBool $ PB.putField v TermSet vs -> PB.TermTermSet $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs + TermArray vs -> PB.TermTermArray $ PB.putField $ PB.TermArray $ PB.putField $ valueToPb s <$> vs + TermMap vs -> PB.TermTermMap $ PB.putField $ PB.TermMap $ PB.putField $ uncurry (mapEntryToPb s) <$> Map.toList vs + LNull -> PB.TermNull $ PB.putField PB.Empty Variable v -> absurd v Antiquote v -> absurd v @@ -328,8 +383,11 @@ pbToSetValue s = \case PB.TermDate f -> pure $ LDate $ pbTimeToUtcTime $ PB.getField f PB.TermBytes f -> pure $ LBytes $ PB.getField f PB.TermBool f -> pure $ LBool $ PB.getField f + PB.TermNull _ -> pure LNull PB.TermVariable _ -> Left "Variables can't appear in facts or sets" PB.TermTermSet _ -> Left "Sets can't be nested" + PB.TermTermArray _ -> Left "Arrays can’t appear in sets" + PB.TermTermMap _ -> Left "Maps can’t appear in sets" setValueToPb :: ReverseSymbols -> Term' 'WithinSet 'InFact 'Representation -> PB.TermV2 setValueToPb s = \case @@ -338,8 +396,11 @@ setValueToPb s = \case LDate v -> PB.TermDate $ PB.putField $ round $ utcTimeToPOSIXSeconds v LBytes v -> PB.TermBytes $ PB.putField v LBool v -> PB.TermBool $ PB.putField v + LNull -> PB.TermNull $ PB.putField $ PB.Empty {} TermSet v -> absurd v + TermArray v -> absurd v + TermMap v -> absurd v Variable v -> absurd v Antiquote v -> absurd v @@ -356,84 +417,136 @@ expressionToPb s e = pbToOp :: Symbols -> PB.Op -> Either String Op pbToOp s = \case PB.OpVValue v -> VOp <$> pbToTerm s (PB.getField v) - PB.OpVUnary v -> pure . UOp . pbToUnary $ PB.getField v - PB.OpVBinary v -> pure . BOp . pbToBinary $ PB.getField v + PB.OpVUnary v -> UOp <$> pbToUnary s (PB.getField v) + PB.OpVBinary v -> BOp <$> pbToBinary s (PB.getField v) + PB.OpVClosure v -> uncurry COp <$> pbToClosure s (PB.getField v) opToPb :: ReverseSymbols -> Op -> PB.Op opToPb s = \case VOp t -> PB.OpVValue $ PB.putField $ termToPb s t - UOp o -> PB.OpVUnary $ PB.putField $ unaryToPb o - BOp o -> PB.OpVBinary $ PB.putField $ binaryToPb o - -pbToUnary :: PB.OpUnary -> Unary -pbToUnary PB.OpUnary{kind} = case PB.getField kind of - PB.Negate -> Negate - PB.Parens -> Parens - PB.Length -> Length - -unaryToPb :: Unary -> PB.OpUnary -unaryToPb = PB.OpUnary . PB.putField . \case - Negate -> PB.Negate - Parens -> PB.Parens - Length -> PB.Length - -pbToBinary :: PB.OpBinary -> Binary -pbToBinary PB.OpBinary{kind} = case PB.getField kind of - PB.LessThan -> LessThan - PB.GreaterThan -> GreaterThan - PB.LessOrEqual -> LessOrEqual - PB.GreaterOrEqual -> GreaterOrEqual - PB.Equal -> Equal - PB.Contains -> Contains - PB.Prefix -> Prefix - PB.Suffix -> Suffix - PB.Regex -> Regex - PB.Add -> Add - PB.Sub -> Sub - PB.Mul -> Mul - PB.Div -> Div - PB.And -> And - PB.Or -> Or - PB.Intersection -> Intersection - PB.Union -> Union - PB.BitwiseAnd -> BitwiseAnd - PB.BitwiseOr -> BitwiseOr - PB.BitwiseXor -> BitwiseXor - PB.NotEqual -> NotEqual - -binaryToPb :: Binary -> PB.OpBinary -binaryToPb = PB.OpBinary . PB.putField . \case - LessThan -> PB.LessThan - GreaterThan -> PB.GreaterThan - LessOrEqual -> PB.LessOrEqual - GreaterOrEqual -> PB.GreaterOrEqual - Equal -> PB.Equal - Contains -> PB.Contains - Prefix -> PB.Prefix - Suffix -> PB.Suffix - Regex -> PB.Regex - Add -> PB.Add - Sub -> PB.Sub - Mul -> PB.Mul - Div -> PB.Div - And -> PB.And - Or -> PB.Or - Intersection -> PB.Intersection - Union -> PB.Union - BitwiseAnd -> PB.BitwiseAnd - BitwiseOr -> PB.BitwiseOr - BitwiseXor -> PB.BitwiseXor - NotEqual -> PB.NotEqual - -pbToThirdPartyBlockRequest :: PB.ThirdPartyBlockRequest -> Either String Crypto.PublicKey -pbToThirdPartyBlockRequest PB.ThirdPartyBlockRequest{previousPk, pkTable} = do + UOp o -> PB.OpVUnary $ PB.putField $ unaryToPb s o + BOp o -> PB.OpVBinary $ PB.putField $ binaryToPb s o + COp p os -> PB.OpVClosure $ PB.putField $ closureToPb s p os + +pbToUnary :: Symbols -> PB.OpUnary -> Either String Unary +pbToUnary s PB.OpUnary{kind,ffiName} = case PB.getField kind of + PB.Negate -> Right Negate + PB.Parens -> Right Parens + PB.Length -> Right Length + PB.TypeOf -> Right TypeOf + PB.UnaryFfi -> do + nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName + name' <- getSymbol s $ SymbolRef nameIdx + pure $ UnaryFfi name' + +unaryToPb :: ReverseSymbols -> Unary -> PB.OpUnary +unaryToPb s = \case + Negate -> PB.OpUnary { kind = PB.putField PB.Negate, ffiName = PB.putField Nothing } + Parens -> PB.OpUnary { kind = PB.putField PB.Parens, ffiName = PB.putField Nothing } + Length -> PB.OpUnary { kind = PB.putField PB.Length, ffiName = PB.putField Nothing } + TypeOf -> PB.OpUnary { kind = PB.putField PB.TypeOf, ffiName = PB.putField Nothing } + UnaryFfi name -> + PB.OpUnary { + kind = PB.putField PB.UnaryFfi, + ffiName = PB.putField . Just . getSymbolRef $ getSymbolCode s name + } + +pbToBinary :: Symbols -> PB.OpBinary -> Either String Binary +pbToBinary s PB.OpBinary{kind, ffiName} = + case PB.getField kind of + PB.LessThan -> Right LessThan + PB.GreaterThan -> Right GreaterThan + PB.LessOrEqual -> Right LessOrEqual + PB.GreaterOrEqual -> Right GreaterOrEqual + PB.Equal -> Right Equal + PB.Contains -> Right Contains + PB.Prefix -> Right Prefix + PB.Suffix -> Right Suffix + PB.Regex -> Right Regex + PB.Add -> Right Add + PB.Sub -> Right Sub + PB.Mul -> Right Mul + PB.Div -> Right Div + PB.And -> Right And + PB.Or -> Right Or + PB.Intersection -> Right Intersection + PB.Union -> Right Union + PB.BitwiseAnd -> Right BitwiseAnd + PB.BitwiseOr -> Right BitwiseOr + PB.BitwiseXor -> Right BitwiseXor + PB.NotEqual -> Right NotEqual + PB.HeterogeneousEqual -> Right HeterogeneousEqual + PB.HeterogeneousNotEqual -> Right HeterogeneousNotEqual + PB.LazyAnd -> Right LazyAnd + PB.LazyOr -> Right LazyOr + PB.All -> Right All + PB.Any -> Right Any + PB.Get -> Right Get + PB.Try -> Right Try + PB.BinaryFfi -> do + nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName + name' <- getSymbol s $ SymbolRef nameIdx + pure $ BinaryFfi name' + +pbToClosure :: Symbols -> PB.OpClosure -> Either String ([T.Text], [Op]) +pbToClosure s PB.OpClosure{..} = + let getParams = traverse (getSymbol s . SymbolRef) . PB.getField + getOps = traverse (pbToOp s) . PB.getField + in bitraverse getParams getOps (params, ops) + +closureToPb :: ReverseSymbols -> [T.Text] -> [Op] -> PB.OpClosure +closureToPb s params' ops' = + let params = PB.putField $ fmap (getSymbolRef . getSymbolCode s) params' + ops = PB.putField $ fmap (opToPb s) ops' + in PB.OpClosure{..} + +binaryToPb :: ReverseSymbols -> Binary -> PB.OpBinary +binaryToPb s = \case + LessThan -> PB.OpBinary { kind = PB.putField PB.LessThan, ffiName = PB.putField Nothing } + GreaterThan -> PB.OpBinary { kind = PB.putField PB.GreaterThan, ffiName = PB.putField Nothing } + LessOrEqual -> PB.OpBinary { kind = PB.putField PB.LessOrEqual, ffiName = PB.putField Nothing } + GreaterOrEqual -> PB.OpBinary { kind = PB.putField PB.GreaterOrEqual, ffiName = PB.putField Nothing } + Equal -> PB.OpBinary { kind = PB.putField PB.Equal, ffiName = PB.putField Nothing } + Contains -> PB.OpBinary { kind = PB.putField PB.Contains, ffiName = PB.putField Nothing } + Prefix -> PB.OpBinary { kind = PB.putField PB.Prefix, ffiName = PB.putField Nothing } + Suffix -> PB.OpBinary { kind = PB.putField PB.Suffix, ffiName = PB.putField Nothing } + Regex -> PB.OpBinary { kind = PB.putField PB.Regex, ffiName = PB.putField Nothing } + Add -> PB.OpBinary { kind = PB.putField PB.Add, ffiName = PB.putField Nothing } + Sub -> PB.OpBinary { kind = PB.putField PB.Sub, ffiName = PB.putField Nothing } + Mul -> PB.OpBinary { kind = PB.putField PB.Mul, ffiName = PB.putField Nothing } + Div -> PB.OpBinary { kind = PB.putField PB.Div, ffiName = PB.putField Nothing } + And -> PB.OpBinary { kind = PB.putField PB.And, ffiName = PB.putField Nothing } + Or -> PB.OpBinary { kind = PB.putField PB.Or, ffiName = PB.putField Nothing } + Intersection -> PB.OpBinary { kind = PB.putField PB.Intersection, ffiName = PB.putField Nothing } + Union -> PB.OpBinary { kind = PB.putField PB.Union, ffiName = PB.putField Nothing } + BitwiseAnd -> PB.OpBinary { kind = PB.putField PB.BitwiseAnd, ffiName = PB.putField Nothing } + BitwiseOr -> PB.OpBinary { kind = PB.putField PB.BitwiseOr, ffiName = PB.putField Nothing } + BitwiseXor -> PB.OpBinary { kind = PB.putField PB.BitwiseXor, ffiName = PB.putField Nothing } + NotEqual -> PB.OpBinary { kind = PB.putField PB.NotEqual, ffiName = PB.putField Nothing } + HeterogeneousEqual -> PB.OpBinary { kind = PB.putField PB.HeterogeneousEqual, ffiName = PB.putField Nothing } + HeterogeneousNotEqual -> PB.OpBinary { kind = PB.putField PB.HeterogeneousNotEqual, ffiName = PB.putField Nothing } + LazyAnd ->PB.OpBinary { kind = PB.putField PB.LazyAnd, ffiName = PB.putField Nothing } + LazyOr -> PB.OpBinary { kind = PB.putField PB.LazyOr, ffiName = PB.putField Nothing } + Any -> PB.OpBinary { kind = PB.putField PB.Any, ffiName = PB.putField Nothing } + All -> PB.OpBinary { kind = PB.putField PB.All, ffiName = PB.putField Nothing } + Get -> PB.OpBinary { kind = PB.putField PB.Get, ffiName = PB.putField Nothing } + Try -> PB.OpBinary { kind = PB.putField PB.Try, ffiName = PB.putField Nothing } + BinaryFfi n -> PB.OpBinary + { kind = PB.putField PB.BinaryFfi + , ffiName = PB.putField . Just . getSymbolRef $ getSymbolCode s n + } + +pbToThirdPartyBlockRequest :: PB.ThirdPartyBlockRequest -> Either String Crypto.Signature +pbToThirdPartyBlockRequest PB.ThirdPartyBlockRequest{legacyPk, pkTable, prevSig} = do + unless (isNothing $ PB.getField legacyPk) $ Left "Public key provided in third-party block request" unless (null $ PB.getField pkTable) $ Left "Public key table provided in third-party block request" - pbToPublicKey $ PB.getField previousPk + pure . Crypto.signature $ PB.getField prevSig -thirdPartyBlockRequestToPb :: Crypto.PublicKey -> PB.ThirdPartyBlockRequest -thirdPartyBlockRequestToPb previousPk = PB.ThirdPartyBlockRequest - { previousPk = PB.putField $ publicKeyToPb previousPk +thirdPartyBlockRequestToPb :: Crypto.Signature -> PB.ThirdPartyBlockRequest +thirdPartyBlockRequestToPb prevSig = PB.ThirdPartyBlockRequest + { legacyPk = PB.putField Nothing , pkTable = PB.putField [] + , prevSig = PB.putField $ Crypto.sigBytes prevSig } pbToThirdPartyBlockContents :: PB.ThirdPartyBlockContents -> Either String (ByteString, Crypto.Signature, Crypto.PublicKey) diff --git a/biscuit/src/Auth/Biscuit/Token.hs b/biscuit/src/Auth/Biscuit/Token.hs index b8202f8..4cc2eb6 100644 --- a/biscuit/src/Auth/Biscuit/Token.hs +++ b/biscuit/src/Auth/Biscuit/Token.hs @@ -75,12 +75,13 @@ import Auth.Biscuit.Crypto (PublicKey, SecretKey, Signature, SignedBlock, getSignatureProof, sigBytes, - sign3rdPartyBlock, - signBlock, + sign3rdPartyBlockV1, + signAttenuationBlock, + signAuthority, signExternalBlock, skBytes, toPublic, verifyBlocks, - verifyExternalSig, + verifyExternalSigV1, verifySecretProof, verifySignatureProof) import Auth.Biscuit.Datalog.AST (Authorizer, Block, Query, @@ -107,7 +108,7 @@ import Auth.Biscuit.Symbols -- so we need to keep the initial serialized payload around in order to compute -- a new signature when adding a block. type ExistingBlock = (ByteString, Block) -type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey)) +type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey), Maybe Int) -- $openOrSealed -- @@ -218,7 +219,7 @@ queryRawBiscuitFactsWithLimits :: Biscuit openOrSealed check -> Limits -> Query -> Either String (Set Bindings) queryRawBiscuitFactsWithLimits b@Biscuit{authority,blocks} = let ePks = externalKeys b - getBlock ((_, block), _, _, _) = block + getBlock ((_, block), _, _, _, _) = block allBlocks = zip [0..] $ getBlock <$> authority : blocks (_, sFacts) = foldMap (uncurry collectWorld . fmap (toEvaluation ePks)) allBlocks in queryAvailableFacts ePks sFacts @@ -263,7 +264,7 @@ asOpen b@Biscuit{proof} = case proof of _ -> Nothing toParsedSignedBlock :: Block -> SignedBlock -> ParsedSignedBlock -toParsedSignedBlock block (serializedBlock, sig, pk, eSig) = ((serializedBlock, block), sig, pk, eSig) +toParsedSignedBlock block (serializedBlock, sig, pk, eSig, sigVersion) = ((serializedBlock, block), sig, pk, eSig, sigVersion) -- | Create a new biscuit with the provided authority block. Such a biscuit is 'Open' to -- further attenuation. @@ -274,8 +275,8 @@ mkBiscuit = mkBiscuitWith Nothing -- further attenuation. mkBiscuitWith :: Maybe Int -> SecretKey -> Block -> IO (Biscuit Open Verified) mkBiscuitWith rootKeyId sk authority = do - let (authoritySymbols, authoritySerialized) = PB.encodeBlock <$> blockToPb False newSymbolTable authority - (signedBlock, nextSk) <- signBlock sk authoritySerialized Nothing + let ((authoritySymbols, authorityVersion), authoritySerialized) = PB.encodeBlock <$> blockToPb False newSymbolTable authority + (signedBlock, nextSk) <- signAuthority sk (authoritySerialized, authorityVersion) pure Biscuit { rootKeyId , authority = toParsedSignedBlock authority signedBlock , blocks = [] @@ -290,9 +291,10 @@ addBlock :: Block -> Biscuit Open check -> IO (Biscuit Open check) addBlock block b@Biscuit{..} = do - let (blockSymbols, blockSerialized) = PB.encodeBlock <$> blockToPb False symbols block - Open p = proof - (signedBlock, nextSk) <- signBlock p blockSerialized Nothing + let ((blockSymbols, version), blockSerialized) = PB.encodeBlock <$> blockToPb False symbols block + Open sk = proof + (_, prevSig, _, _,_) = NE.last $ authority :| blocks + (signedBlock, nextSk) <- signAttenuationBlock sk prevSig (blockSerialized, version) Nothing pure $ b { blocks = blocks <> [toParsedSignedBlock block signedBlock] , symbols = addFromBlock symbols blockSymbols , proof = Open nextSk @@ -306,22 +308,22 @@ addSignedBlock :: SecretKey -> Biscuit Open check -> IO (Biscuit Open check) addSignedBlock eSk block b@Biscuit{..} = do - let (_, blockSerialized) = PB.encodeBlock <$> blockToPb True newSymbolTable block + let ((_, version), blockSerialized) = PB.encodeBlock <$> blockToPb True newSymbolTable block lastBlock = NE.last (authority :| blocks) - (_, _, lastPublicKey, _) = lastBlock - Open p = proof - (signedBlock, nextSk) <- signExternalBlock p eSk lastPublicKey blockSerialized + (_, prevSig, _, _, _) = lastBlock + Open sk = proof + (signedBlock, nextSk) <- signExternalBlock sk prevSig (blockSerialized, version) eSk pure $ b { blocks = blocks <> [toParsedSignedBlock block signedBlock] , proof = Open nextSk } mkThirdPartyBlock' :: SecretKey - -> PublicKey + -> Signature -> Block -> (ByteString, Signature, PublicKey) -mkThirdPartyBlock' eSk lastPublicKey block = +mkThirdPartyBlock' eSk prevSig block = let (_, payload) = PB.encodeBlock <$> blockToPb True newSymbolTable block - (eSig, ePk) = sign3rdPartyBlock eSk lastPublicKey payload + (eSig, ePk) = sign3rdPartyBlockV1 eSk prevSig payload in (payload, eSig, ePk) -- | Given a third-party block request, generate a third-party block, @@ -331,8 +333,8 @@ mkThirdPartyBlock :: SecretKey -> Block -> Either String ByteString mkThirdPartyBlock eSk req block = do - previousPk<- pbToThirdPartyBlockRequest =<< PB.decodeThirdPartyBlockRequest req - pure $ PB.encodeThirdPartyBlockContents . thirdPartyBlockContentsToPb $ mkThirdPartyBlock' eSk previousPk block + prevSig <- pbToThirdPartyBlockRequest =<< PB.decodeThirdPartyBlockRequest req + pure $ PB.encodeThirdPartyBlockContents . thirdPartyBlockContentsToPb $ mkThirdPartyBlock' eSk prevSig block -- | Generate a third-party block request. It can be used in -- conjunction with 'mkThirdPartyBlock' to generate a @@ -340,22 +342,22 @@ mkThirdPartyBlock eSk req block = do -- 'applyThirdPartyBlock'. mkThirdPartyBlockReq :: Biscuit proof check -> ByteString mkThirdPartyBlockReq Biscuit{authority,blocks} = - let (_, _ , lastPk, _) = NE.last $ authority :| blocks - in PB.encodeThirdPartyBlockRequest $ thirdPartyBlockRequestToPb lastPk + let (_, prevSig , _, _, _) = NE.last $ authority :| blocks + in PB.encodeThirdPartyBlockRequest $ thirdPartyBlockRequestToPb prevSig -- | Given a base64-encoded third-party block, append it to a token. applyThirdPartyBlock :: Biscuit Open check -> ByteString -> Either String (IO (Biscuit Open check)) applyThirdPartyBlock b@Biscuit{..} contents = do (payload, eSig, ePk) <- pbToThirdPartyBlockContents =<< PB.decodeThirdPartyBlockContents contents - let Open p = proof - addESig (a,b',c,_) = (a,b',c, Just (eSig, ePk)) - (_, _, lastPk, _) = NE.last $ authority :| blocks + let Open sk = proof + addESig (a,b',c,_, d) = (a,b',c, Just (eSig, ePk), d) + (_, prevSig, _, _, _) = NE.last $ authority :| blocks pbBlock <- PB.decodeBlock payload (block, newSymbols) <- (`runStateT` symbols) $ pbToBlock (Just ePk) pbBlock - unless (verifyExternalSig lastPk (payload, eSig, ePk)) $ + unless (verifyExternalSigV1 prevSig (payload, eSig, ePk)) $ Left "Invalid 3rd party signature" pure $ do - (signedBlock, nextSk) <- signBlock p payload (Just (eSig, ePk)) + (signedBlock, nextSk) <- signAttenuationBlock sk prevSig (payload, 3) (Just (eSig, ePk)) pure $ b { blocks = blocks <> [toParsedSignedBlock block (addESig signedBlock)] , proof = Open nextSk , symbols = newSymbols @@ -363,8 +365,8 @@ applyThirdPartyBlock b@Biscuit{..} contents = do externalKeys :: Biscuit openOrSealed check -> [Maybe PublicKey] externalKeys Biscuit{blocks} = - let getEpk (_, _, _, Just (_, ePk)) = Just ePk - getEpk _ = Nothing + let getEpk (_, _, _, Just (_, ePk), _) = Just ePk + getEpk _ = Nothing in Nothing : (getEpk <$> blocks) -- | Turn an 'Open' biscuit into a 'Sealed' one, preventing it from being attenuated @@ -372,8 +374,8 @@ externalKeys Biscuit{blocks} = seal :: Biscuit Open check -> Biscuit Sealed check seal b@Biscuit{..} = let Open sk = proof - ((lastPayload, _), lastSig, lastPk, eSig) = NE.last $ authority :| blocks - newProof = Sealed $ getSignatureProof (lastPayload, lastSig, lastPk, eSig) sk + ((lastPayload, _), lastSig, lastPk, eSig, _) = NE.last $ authority :| blocks + newProof = Sealed $ getSignatureProof (lastPayload, lastSig, lastPk, eSig, Nothing) sk in b { proof = newProof } -- | Serialize a biscuit to a raw bytestring @@ -390,7 +392,7 @@ serializeBiscuit Biscuit{..} = } toPBSignedBlock :: ParsedSignedBlock -> PB.SignedBlock -toPBSignedBlock ((block, _), sig, pk, eSig) = signedBlockToPb (block, sig, pk, eSig) +toPBSignedBlock ((block, _), sig, pk, eSig, sigVersion) = signedBlockToPb (block, sig, pk, eSig, sigVersion) -- | Errors that can happen when parsing a biscuit. Since complete parsing of a biscuit -- requires a signature check, an invalid signature check is a parsing error @@ -444,7 +446,7 @@ checkRevocation :: Applicative m -> BiscuitWrapper -> m (Either ParseError BiscuitWrapper) checkRevocation isRevoked bw@BiscuitWrapper{wAuthority,wBlocks} = - let getRevocationId (_, sig, _, _) = sigBytes sig + let getRevocationId (_, sig, _, _, _) = sigBytes sig revocationIds = getRevocationId <$> wAuthority :| wBlocks keepIfNotRevoked True = Left RevokedBiscuit keepIfNotRevoked False = Right bw @@ -452,10 +454,10 @@ checkRevocation isRevoked bw@BiscuitWrapper{wAuthority,wBlocks} = parseBlocks :: BiscuitWrapper -> Either ParseError (Symbols, NonEmpty ParsedSignedBlock) parseBlocks BiscuitWrapper{..} = do - let parseBlock (payload, sig, pk, eSig) = do + let parseBlock (payload, sig, pk, eSig, sigVersion) = do pbBlock <- lift $ first (InvalidProtobufSer False) $ PB.decodeBlock payload block <- mapStateT (first (InvalidProtobuf False)) $ pbToBlock (snd <$> eSig) pbBlock - pure ((payload, block), sig, pk, eSig) + pure ((payload, block), sig, pk, eSig,sigVersion) (allBlocks, symbols) <- (`runStateT` newSymbolTable) $ do traverse parseBlock (wAuthority :| wBlocks) @@ -502,7 +504,7 @@ checkBiscuitSignatures :: BiscuitProof proof -> Either ParseError (Biscuit proof Verified) checkBiscuitSignatures getPublicKey b@Biscuit{..} = do let pk = getPublicKey rootKeyId - toSignedBlock ((payload, _), sig, nextPk, eSig) = (payload, sig, nextPk, eSig) + toSignedBlock ((payload, _), sig, nextPk, eSig, sigVersion) = (payload, sig, nextPk, eSig, sigVersion) allBlocks = toSignedBlock <$> (authority :| blocks) blocksResult = verifyBlocks allBlocks pk proofResult = case toPossibleProofs proof of @@ -550,13 +552,13 @@ parseBiscuitWith ParserConfig{..} bs = getRevocationIds :: Biscuit proof check -> NonEmpty ByteString getRevocationIds Biscuit{authority, blocks} = let allBlocks = authority :| blocks - getRevocationId (_, sig, _, _) = sigBytes sig + getRevocationId (_, sig, _, _, _) = sigBytes sig in getRevocationId <$> allBlocks -- | Generic version of 'authorizeBiscuitWithLimits' which takes custom 'Limits'. authorizeBiscuitWithLimits :: Limits -> Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError (AuthorizedBiscuit proof)) authorizeBiscuitWithLimits l biscuit@Biscuit{..} authorizer = - let toBlockWithRevocationId ((_, block), sig, _, eSig) = (block, sigBytes sig, snd <$> eSig) + let toBlockWithRevocationId ((_, block), sig, _, eSig, _) = (block, sigBytes sig, snd <$> eSig) -- the authority block can't be externally signed. If it carries a signature, it won't be -- verified. So we need to make sure there is none, to avoid having facts trusted without -- a proper signature check diff --git a/biscuit/test/Spec.hs b/biscuit/test/Spec.hs index 35f771e..5a492d9 100644 --- a/biscuit/test/Spec.hs +++ b/biscuit/test/Spec.hs @@ -6,6 +6,7 @@ module Main (main) where import Test.Tasty +import qualified Spec.AST as AST import qualified Spec.Executor as Executor import qualified Spec.NewCrypto as NewCrypto import qualified Spec.Parser as Parser @@ -19,8 +20,8 @@ main :: IO () main = do sampleReader <- SampleReader.getSpecs defaultMain $ testGroup "biscuit-haskell" - [ - NewCrypto.specs + [ AST.specs + , NewCrypto.specs , Executor.specs , Parser.specs , Quasiquoter.specs diff --git a/biscuit/test/Spec/AST.hs b/biscuit/test/Spec/AST.hs new file mode 100644 index 0000000..6e78661 --- /dev/null +++ b/biscuit/test/Spec/AST.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE OverloadedStrings #-} +module Spec.AST (specs) where + +import Test.Tasty +import Test.Tasty.HUnit + +import Auth.Biscuit.Datalog.AST + +specs :: TestTree +specs = testGroup "datalog AST" + [ toStackClosure + , fromStackClosure + ] + +toStackClosure :: TestTree +toStackClosure = testCase "Turn a closure expr into a stack" $ + let cE = EClosure + ["x"] + (EBinary + Equal + (EValue (Variable "x")) + (EValue (LInteger 42))) + cO = [COp ["x"] + [ VOp (Variable "x") + , VOp (LInteger 42) + , BOp Equal + ]] + in toStack cE @?= cO + + +fromStackClosure :: TestTree +fromStackClosure = testCase "Turn a closure op into an expression" $ + let cO = [COp ["x"] + [ VOp (Variable "x") + , VOp (LInteger 42) + , BOp Equal + ]] + cE = EClosure + ["x"] + (EBinary + Equal + (EValue (Variable "x")) + (EValue (LInteger 42))) + in fromStack cO @?= Right cE diff --git a/biscuit/test/Spec/Executor.hs b/biscuit/test/Spec/Executor.hs index bf44e00..9bb4b53 100644 --- a/biscuit/test/Spec/Executor.hs +++ b/biscuit/test/Spec/Executor.hs @@ -100,7 +100,7 @@ exprEval = do -- ("1 / 0") @?= Left "Divide by 0" testGroup "Expressions evaluation" $ eval <$> [ ("!(1 < $var1)", LBool True) - , ("[0].contains($var1)", LBool True) + , ("{0}.contains($var1)", LBool True) , ("1 + 2 * 3", LInteger 7) , ("!(1 + 2 * 3 > 4)", LBool False) , ("!true", LBool False) @@ -109,22 +109,22 @@ exprEval = do , ("\"test\".length()", LInteger 4) , ("\"é\".length()", LInteger 2) , ("hex:ababab.length()", LInteger 3) - , ("[].length()", LInteger 0) - , ("[\"test\", \"test\"].length()", LInteger 1) - , ("1 == 1", LBool True) - , ("2 == 1", LBool False) - , ("\"toto\" == \"toto\"", LBool True) - , ("\"toto\" == \"truc\"", LBool False) + , ("{,}.length()", LInteger 0) + , ("{\"test\", \"test\"}.length()", LInteger 1) + , ("1 === 1", LBool True) + , ("2 === 1", LBool False) + , ("\"toto\" === \"toto\"", LBool True) + , ("\"toto\" === \"truc\"", LBool False) , ("\"toto\".matches(\"to(to)?\")", LBool True) , ("\"toto\".matches(\"^to$\")", LBool False) - , ("2021-05-07T18:00:00Z == 2021-05-07T18:00:00Z", LBool True) - , ("2021-05-07T18:00:00Z == 2021-05-07T19:00:00Z", LBool False) - , ("hex:ababab == hex:ababab", LBool True) - , ("hex:ababab == hex:ababac", LBool False) - , ("true == true", LBool True) - , ("true == false", LBool False) - , ("[1,2,3] == [1,2,3]", LBool True) - , ("[1,2,3] == [1,2,4]", LBool False) + , ("2021-05-07T18:00:00Z === 2021-05-07T18:00:00Z", LBool True) + , ("2021-05-07T18:00:00Z === 2021-05-07T19:00:00Z", LBool False) + , ("hex:ababab === hex:ababab", LBool True) + , ("hex:ababab === hex:ababac", LBool False) + , ("true === true", LBool True) + , ("true === false", LBool False) + , ("{1,2,3} === {1,2,3}", LBool True) + , ("{1,2,3} === {1,2,4}", LBool False) , ("1 < 2", LBool True) , ("2 < 1", LBool False) , ("2021-05-07T18:00:00Z < 2021-05-07T19:00:00Z", LBool True) @@ -162,14 +162,15 @@ exprEval = do , ("true || false", LBool True) , ("false || true", LBool True) , ("false || false", LBool False) - , ("[1].contains([1])", LBool True) - , ("[1].contains(1)", LBool True) - , ("[].contains(1)", LBool False) - , ("[\"test\"].contains(2)", LBool False) - , ("[1].intersection([1])", TermSet (Set.fromList [LInteger 1])) - , ("[1].intersection([\"test\"])", TermSet (Set.fromList [])) - , ("[1].union([1])", TermSet (Set.fromList [LInteger 1])) - , ("[1].union([\"test\"])", TermSet (Set.fromList [LInteger 1, LString "test"])) + , ("{1}.contains({1})", LBool True) + , ("{1}.contains(1)", LBool True) + , ("{,}.contains(1)", LBool False) + , ("{\"test\"}.contains(2)", LBool False) + , ("{1}.intersection({1})", TermSet (Set.fromList [LInteger 1])) + , ("{1}.intersection({\"test\"})", TermSet (Set.fromList [])) + , ("{1}.union({1})", TermSet (Set.fromList [LInteger 1])) + , ("{1}.union({\"test\"})", TermSet (Set.fromList [LInteger 1, LString "test"])) + , ("(true === 12).try_or(42)", LInteger 42) ] exprEvalError :: TestTree @@ -186,6 +187,7 @@ exprEvalError = do , ("\"toto\".matches(\"to\")", "Regex evaluation is disabled") , ("9223372036854775807 + 1", "integer overflow") , ("-9223372036854775808 - 1", "integer underflow") + , ("true.try_or(true === 12)", "Equality mismatch") -- the right-hand-side of try_or is eager ] rulesWithConstraints :: TestTree @@ -306,11 +308,11 @@ scopedRules = testGroup "Rules and facts in different scopes" overflow :: TestTree overflow = let subtraction = authRulesGroup $ Set.singleton - [rule|test(true) <- -9223372036854775808 - 1 != 0|] + [rule|test(true) <- -9223372036854775808 - 1 !== 0|] multiplication = authRulesGroup $ Set.singleton - [rule|test(true) <- 10000000000 * 10000000000 != 0|] + [rule|test(true) <- 10000000000 * 10000000000 !== 0|] addition = authRulesGroup $ Set.singleton - [rule|test(true) <- 9223372036854775807 + 1 != 0|] + [rule|test(true) <- 9223372036854775807 + 1 !== 0|] in testGroup "Arithmetic overflow" [ testCase "subtraction" $ runFactGeneration defaultLimits 1 subtraction mempty @?= Left (BadExpression "integer underflow") diff --git a/biscuit/test/Spec/NewCrypto.hs b/biscuit/test/Spec/NewCrypto.hs index ecd789e..215dac5 100644 --- a/biscuit/test/Spec/NewCrypto.hs +++ b/biscuit/test/Spec/NewCrypto.hs @@ -33,7 +33,7 @@ data SealedToken = SealedToken signToken :: ByteString -> SecretKey -> IO Token signToken p sk = do - (signedBlock, privKey) <- signBlock sk p Nothing + (signedBlock, privKey) <- signAuthority sk (p, 3) pure Token { payload = pure signedBlock , privKey @@ -44,7 +44,8 @@ snocNE (h :| t) e = h :| (t <> [e]) append :: Token -> ByteString -> IO Token append t@Token{payload} p = do - (signedBlock, privKey) <- signBlock (privKey t) p Nothing + let (_, lastSig, _, _, _) = NE.last payload + (signedBlock, privKey) <- signAttenuationBlock (privKey t) lastSig (p, 3) Nothing pure Token { payload = snocNE payload signedBlock , privKey @@ -52,8 +53,8 @@ append t@Token{payload} p = do appendSigned :: Token -> SecretKey -> ByteString -> IO Token appendSigned t@Token{payload} eSk p = do - let (_, _, lastPk, _) = NE.last payload - (signedBlock, privKey) <- signExternalBlock (privKey t) eSk lastPk p + let (_, lastSig, lastPk, _, _) = NE.last payload + (signedBlock, privKey) <- signExternalBlock (privKey t) lastSig (p, 3) eSk pure Token { payload = snocNE payload signedBlock , privKey @@ -148,7 +149,7 @@ invalidExternalSig = testCase "Invalid external signature" $ do attenuated <- appendSigned token eSk "block1" let bogusSignature = sign eSk ePk ("yolo yolo" :: ByteString) replaceExternalSig :: SignedBlock -> SignedBlock - replaceExternalSig (p, s, pk, Just (_, ePk)) = (p, s, pk, Just (bogusSignature, ePk)) + replaceExternalSig (p, s, pk, Just (_, ePk), v) = (p, s, pk, Just (bogusSignature, ePk), v) replaceExternalSig sb = sb tamper :: Blocks -> Blocks tamper = fmap replaceExternalSig @@ -168,7 +169,7 @@ tamperedAuthority = testCase "Tampered authority" $ do content = "content" token <- signToken content sk attenuated <- append token "block1" - let tamper ((_, s, pk, eS) :| o) = ("tampered", s, pk, eS) :| o + let tamper ((_, s, pk, eS, v) :| o) = ("tampered", s, pk, eS, v) :| o tampered = alterPayload tamper attenuated let res = verifyToken tampered pk res @?= False @@ -180,7 +181,7 @@ tamperedBlock = testCase "Tampered block" $ do content = "content" token <- signToken content sk attenuated <- append token "block1" - let tamper (h :| ((_, s, pk, eS): t)) = h :| (("tampered", s, pk, eS) : t) + let tamper (h :| ((_, s, pk, eS, v): t)) = h :| (("tampered", s, pk, eS, v) : t) tampered = alterPayload tamper attenuated let res = verifyToken tampered pk res @?= False @@ -228,7 +229,7 @@ tamperedAuthoritySealed = testCase "Tampered authority" $ do content = "content" token <- signToken content sk attenuated <- seal <$> append token "block1" - let tamper ((_, s, pk, eS) :| o) = ("tampered", s, pk, eS) :| o + let tamper ((_, s, pk, eS, v) :| o) = ("tampered", s, pk, eS, v) :| o tampered = alterPayloadSealed tamper attenuated let res = verifySealedToken tampered pk res @?= False @@ -240,7 +241,7 @@ tamperedBlockSealed = testCase "Tampered block" $ do content = "content" token <- signToken content sk attenuated <- seal <$> append token "block1" - let tamper (h :| ((_, s, pk, eS): t)) = h :| (("tampered", s, pk, eS) : t) + let tamper (h :| ((_, s, pk, eS, v): t)) = h :| (("tampered", s, pk, eS, v) : t) tampered = alterPayloadSealed tamper attenuated let res = verifySealedToken tampered pk res @?= False diff --git a/biscuit/test/Spec/Parser.hs b/biscuit/test/Spec/Parser.hs index c4ae7e2..1dc6c84 100644 --- a/biscuit/test/Spec/Parser.hs +++ b/biscuit/test/Spec/Parser.hs @@ -71,6 +71,7 @@ parseBlock = runWithNoParams blockParser $ substituteBlock mempty mempty specs :: TestTree specs = testGroup "datalog parser" [ factWithDate + , factWithNull , simpleFact , oneLetterFact , simpleRule @@ -113,7 +114,7 @@ termsGroupQQ = testGroup "Parse terms (in a QQ setting)" [ testCase "Variable name" $ parseTermQQ "{toto2_'}" @?= Right (Antiquote "toto2_'") , testCase "Leading underscore" $ parseTermQQ "{_toto}" @?= Right (Antiquote "_toto") , testCase "`_` is reserved" $ parseTermQQ "{_}" @?= Left "1:3:\n |\n1 | {_}\n | ^\nunexpected '}'\nexpecting letter\n" - , testCase "Variables are lower-cased" $ parseTermQQ "{Toto}" @?= Left "1:2:\n |\n1 | {Toto}\n | ^\nunexpected 'T'\nexpecting '_' or lowercase letter\n" + , testCase "Variables are lower-cased" $ parseTermQQ "{Toto}" @?= Left "1:2:\n |\n1 | {Toto}\n | ^^^^^\nunexpected \"Toto}\"\nexpecting '_', lowercase letter, map (eg. {\"key\": 1}), or set (eg. {1,2,3})\n" , testCase "_ is lower-case" $ parseTermQQ "{_Toto}" @?= Right (Antiquote "_Toto") , testCase "unicode is allowed" $ parseTermQQ "{éllo}" @?= Right (Antiquote "éllo") ] @@ -138,6 +139,11 @@ factWithDate = testCase "Parse fact containing a date" $ do parsePredicate "date(2019-12-02T13:49:53+00:00)" @?= Right (Predicate "date" [LDate $ read "2019-12-02 13:49:53 UTC"]) +factWithNull :: TestTree +factWithNull = testCase "Parse fact containing a null value" $ do + parsePredicate "date(null)" @?= + Right (Predicate "date" [LNull]) + simpleRule :: TestTree simpleRule = testCase "Parse simple rule" $ parseRule "right($0, \"read\") <- resource( $0), operation(\"read\")" @?= @@ -211,17 +217,29 @@ constraints = testGroup "Parse expressions" (EValue (LInteger 1234)) ) , testCase "int comparison (EQ)" $ - parseExpression "$0 == 1" @?= + parseExpression "$0 === 1" @?= Right (EBinary Equal (EValue (Variable "0")) (EValue (LInteger 1)) ) , testCase "int comparison (NEQ)" $ - parseExpression "$0 != 1" @?= + parseExpression "$0 !== 1" @?= Right (EBinary NotEqual (EValue (Variable "0")) (EValue (LInteger 1)) ) + , testCase "int comparison (HEQ)" $ + parseExpression "$0 == 1" @?= + Right (EBinary HeterogeneousEqual + (EValue (Variable "0")) + (EValue (LInteger 1)) + ) + , testCase "int comparison (HNEQ)" $ + parseExpression "$0 != 1" @?= + Right (EBinary HeterogeneousNotEqual + (EValue (Variable "0")) + (EValue (LInteger 1)) + ) , testCase "negative int comparison (GTE)" $ parseExpression "$0 >= -1234" @?= Right (EBinary GreaterOrEqual @@ -229,13 +247,13 @@ constraints = testGroup "Parse expressions" (EValue (LInteger (-1234))) ) , testCase "string comparison" $ - parseExpression "$0 == \"abc\"" @?= + parseExpression "$0 === \"abc\"" @?= Right (EBinary Equal (EValue (Variable "0")) (EValue (LString "abc")) ) , testCase "string comparison (NEQ)" $ - parseExpression "$0 != \"abc\"" @?= + parseExpression "$0 !== \"abc\"" @?= Right (EBinary NotEqual (EValue (Variable "0")) (EValue (LString "abc")) @@ -259,33 +277,38 @@ constraints = testGroup "Parse expressions" (EValue (LString "abc")) ) , testCase "int set operation" $ - parseExpression "[1, 2].contains($0)" @?= + parseExpression "{1, 2}.contains($0)" @?= Right (EBinary Contains (EValue (TermSet $ Set.fromList [LInteger 1, LInteger 2])) (EValue (Variable "0")) ) , testCase "negated int set operation" $ - parseExpression "![1, 2].contains($0)" @?= + parseExpression "!{1, 2}.contains($0)" @?= Right (EUnary Negate (EBinary Contains (EValue (TermSet $ Set.fromList [LInteger 1, LInteger 2])) (EValue (Variable "0")) )) , testCase "string set operation" $ - parseExpression "[\"abc\", \"def\"].contains($0)" @?= + parseExpression "{\"abc\", \"def\"}.contains($0)" @?= Right (EBinary Contains (EValue (TermSet $ Set.fromList [LString "abc", LString "def"])) (EValue (Variable "0")) ) , testCase "negated string set operation" $ - parseExpression "![\"abc\", \"def\"].contains($0)" @?= + parseExpression "!{\"abc\", \"def\"}.contains($0)" @?= Right (EUnary Negate (EBinary Contains (EValue (TermSet $ Set.fromList [LString "abc", LString "def"])) (EValue (Variable "0")) )) + , testCase "empty set operation" $ + parseExpression "{,}.length()" @?= + Right (EUnary Length + (EValue (TermSet $ Set.fromList [])) + ) , testCase "arithmetic operation that looks like the beginning of a RFC3339 date" $ - parseExpression "2022-12-10==2000" @?= + parseExpression "2022-12-10===2000" @?= Right (EBinary Equal (EBinary Sub (EBinary Sub @@ -297,7 +320,7 @@ constraints = testGroup "Parse expressions" (EValue $ LInteger 2000) ) , testCase "chained method calls" $ - parseExpression "$var.intersection([1]).union([2]).length()" @?= + parseExpression "$var.intersection({1}).union({2}).length()" @?= Right (EUnary Length (EBinary Union ( EBinary Intersection @@ -307,6 +330,41 @@ constraints = testGroup "Parse expressions" (EValue $ TermSet [LInteger 2]) ) ) + , testCase "unary extern method call" $ + parseExpression "$var.extern::test() == true" @?= + Right (EBinary HeterogeneousEqual + (EUnary (UnaryFfi "test") (EValue $ Variable "var")) + (EValue $ LBool True)) + , testCase "binary extern method call" $ + parseExpression "$var.extern::test(1) == true" @?= + Right (EBinary HeterogeneousEqual + (EBinary + (BinaryFfi "test") + (EValue $ Variable "var") + (EValue $ LInteger 1)) + (EValue $ LBool True)) + , testCase "nullary closures" $ + parseExpression "true || 1 === 2" @?= + Right (EBinary LazyOr + (EValue $ LBool True) + (EClosure [] + (EBinary Equal + (EValue $ LInteger 1) + (EValue $ LInteger 2) + ) + ) + ) + , testCase "unary closures" $ + parseExpression "{1}.all($p -> $p === 1)" @?= + Right (EBinary All + (EValue . TermSet . Set.singleton $ LInteger 1) + (EClosure ["p"] + (EBinary Equal + (EValue $ Variable "p") + (EValue $ LInteger 1) + ) + ) + ) , operatorPrecedences ] @@ -323,18 +381,20 @@ operatorPrecedences = testGroup "mixed-precedence operators" ) , testCase "< && starts_with" $ parseExpression " 2 < $test && $var2.starts_with(\"test\") && true " @?= - Right (EBinary And - (EBinary And + Right (EBinary LazyAnd + (EBinary LazyAnd (EBinary LessThan (EValue $ LInteger 2) (EValue $ Variable "test") ) - (EBinary Prefix - (EValue $ Variable "var2") - (EValue $ LString "test") + (EClosure [] + (EBinary Prefix + (EValue $ Variable "var2") + (EValue $ LString "test") + ) ) ) - (EValue $ LBool True) + (EClosure [] (EValue $ LBool True)) ) , testCase "+ *" $ parseExpression "1 + 2 * 3" @?= @@ -410,18 +470,24 @@ checkParsing = testGroup "check blocks" parseCheck "check if true" @?= Right Check { cQueries = [QueryItem [] [EValue $ LBool True] []] - , cKind = One + , cKind = CheckOne } , testCase "Simple check all" $ parseCheck "check all true" @?= Right Check { cQueries = [QueryItem [] [EValue $ LBool True] []] - , cKind = All + , cKind = CheckAll + } + , testCase "Simple reject if" $ + parseCheck "reject if true" @?= + Right Check + { cQueries = [QueryItem [] [EValue $ LBool True] []] + , cKind = Reject } , testCase "Multiple groups" $ parseCheck - "check if fact($var), $var == true or \ - \other($var), $var == 2" @?= + "check if fact($var), $var === true or \ + \other($var), $var === 2" @?= Right Check { cQueries = [ QueryItem [Predicate "fact" [Variable "var"]] @@ -431,12 +497,27 @@ checkParsing = testGroup "check blocks" [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [] ] - , cKind = One + , cKind = CheckOne } , testCase "Multiple check all groups" $ parseCheck - "check all fact($var), $var == true or \ - \other($var), $var == 2" @?= + "check all fact($var), $var === true or \ + \other($var), $var === 2" @?= + Right Check + { cQueries = + [ QueryItem [Predicate "fact" [Variable "var"]] + [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))] + [] + , QueryItem [Predicate "other" [Variable "var"]] + [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] + [] + ] + , cKind = CheckAll + } + , testCase "Multiple reject if groups" $ + parseCheck + "reject if fact($var), $var === true or \ + \other($var), $var === 2" @?= Right Check { cQueries = [ QueryItem [Predicate "fact" [Variable "var"]] @@ -446,12 +527,12 @@ checkParsing = testGroup "check blocks" [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [] ] - , cKind = All + , cKind = Reject } , testCase "Multiple groups, scoped" $ parseCheck - "check if fact($var), $var == true trusting previous or \ - \other($var), $var == 2 trusting authority" @?= + "check if fact($var), $var === true trusting previous or \ + \other($var), $var === 2 trusting authority" @?= Right Check { cQueries = [ QueryItem [Predicate "fact" [Variable "var"]] @@ -461,12 +542,12 @@ checkParsing = testGroup "check blocks" [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [OnlyAuthority] ] - , cKind = One + , cKind = CheckOne } , testCase "Multiple check all groups, scoped" $ parseCheck - "check all fact($var), $var == true trusting previous or \ - \other($var), $var == 2 trusting authority" @?= + "check all fact($var), $var === true trusting previous or \ + \other($var), $var === 2 trusting authority" @?= Right Check { cQueries = [ QueryItem [Predicate "fact" [Variable "var"]] @@ -476,7 +557,7 @@ checkParsing = testGroup "check blocks" [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [OnlyAuthority] ] - , cKind = All + , cKind = CheckAll } ] @@ -490,8 +571,8 @@ policyParsing = testGroup "policy blocks" Right (Deny, [QueryItem [] [EValue $ LBool True] []]) , testCase "Allow with multiple groups" $ parsePolicy - "allow if fact($var), $var == true or \ - \other($var), $var == 2" @?= + "allow if fact($var), $var === true or \ + \other($var), $var === 2" @?= Right ( Allow , [ QueryItem [Predicate "fact" [Variable "var"]] @@ -504,8 +585,8 @@ policyParsing = testGroup "policy blocks" ) , testCase "Deny with multiple groups" $ parsePolicy - "deny if fact($var), $var == true or \ - \other($var), $var == 2" @?= + "deny if fact($var), $var === true or \ + \other($var), $var === 2" @?= Right ( Deny , [ QueryItem [Predicate "fact" [Variable "var"]] @@ -519,8 +600,8 @@ policyParsing = testGroup "policy blocks" , testCase "Deny with multiple groups, multiline" $ parsePolicy "deny if\n\ - \fact($var), $var == true or //comment\n\ - \other($var), $var == 2" @?= + \fact($var), $var === true or //comment\n\ + \other($var), $var === 2" @?= Right ( Deny , [ QueryItem [Predicate "fact" [Variable "var"]] @@ -533,8 +614,8 @@ policyParsing = testGroup "policy blocks" ) , testCase "Allow with multiple groups, scoped" $ parsePolicy - "allow if fact($var), $var == true trusting authority or \ - \other($var), $var == 2 trusting ed25519/a1b712761c609039f878edad694d762652f1548a68acccc96735b3196a240e8b,ed25519/083aae4ba29a9a3781cdee7a800f4f8ab90591f65ca983fc429687628311aedd,ed25519/c6864578bc03596d52878bd70025ec966c95c60727cb6573198453e82132510d " @?= + "allow if fact($var), $var === true trusting authority or \ + \other($var), $var === 2 trusting ed25519/a1b712761c609039f878edad694d762652f1548a68acccc96735b3196a240e8b,ed25519/083aae4ba29a9a3781cdee7a800f4f8ab90591f65ca983fc429687628311aedd,ed25519/c6864578bc03596d52878bd70025ec966c95c60727cb6573198453e82132510d " @?= Right ( Allow , [ QueryItem [Predicate "fact" [Variable "var"]] @@ -596,7 +677,7 @@ authorizerParsing = testGroup "Simple authorizers" \ user($user_id),\n\ \ member($user_id, $team_id),\n\ \ team_role($team_id, $blog_id, \"contributor\"),\n\ - \ [\"read\", \"write\"].contains($operation);\n\ + \ {\"read\", \"write\"}.contains($operation);\n\ \// unauthenticated users have read access on published articles\n\ \allow if\n\ \ operation(\"read\"),\n\ @@ -685,7 +766,7 @@ blockParsing = testCase "Full block" $ do \ user($user_id),\n\ \ member($user_id, $team_id),\n\ \ team_role($team_id, $blog_id, \"contributor\"),\n\ - \ [\"read\", \"write\"].contains($operation);\n\ + \ {\"read\", \"write\"}.contains($operation);\n\ \ " p = Predicate sRead = LString "read" diff --git a/biscuit/test/Spec/Roundtrip.hs b/biscuit/test/Spec/Roundtrip.hs index 3ed4c92..045ab25 100644 --- a/biscuit/test/Spec/Roundtrip.hs +++ b/biscuit/test/Spec/Roundtrip.hs @@ -62,7 +62,7 @@ roundtrip' (s,p) i@(authority' :| blocks') = do final <- addBlocks blocks' init' let serialized = s final parsed = p pk serialized - getBlock ((_, b), _, _, _) = b + getBlock ((_, b), _, _, _, _) = b getBlocks b = getBlock <$> authority b :| blocks b getBlocks <$> parsed @?= Right (snd <$> i) rootKeyId <$> parsed @?= Right (Just 1) @@ -91,7 +91,7 @@ roundtrip'' direct (s,p) i@(authority' :| blocks') = do final <- addBlocks blocks' init' let serialized = s final parsed = p pk serialized - getBlock ((_, b), _, _, _) = b + getBlock ((_, b), _, _, _, _) = b getBlocks b = getBlock <$> authority b :| blocks b getBlocks <$> parsed @?= Right (snd <$> i) rootKeyId <$> parsed @?= Right (Just 1) @@ -113,7 +113,7 @@ multipleBlocks r = testCase "Multiple block" $ roundtrip r $ |] :| [ [block| valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59+00:00; - valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59+00:00, !["file1"].contains($1); + valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59+00:00, !{"file1"}.contains($1); check if valid_date($0), resource($0); |] , [block| @@ -127,25 +127,25 @@ multipleBlocks r = testCase "Multiple block" $ roundtrip r $ check if 1 <= 1; check if 2 >= 1; check if 2 >= 2; - check if 3 == 3; - check if 1 + 2 * 3 - 4 / 2 == 5; + check if 3 === 3; + check if 1 + 2 * 3 - 4 / 2 === 5; check if "hello world".starts_with("hello") && "hello world".ends_with("world"); check if "aaabde".matches("a*c?.e"); - check if "abcD12" == "abcD12"; + check if "abcD12" === "abcD12"; check if 2019-12-04T09:46:41+00:00 < 2020-12-04T09:46:41+00:00; check if 2020-12-04T09:46:41+00:00 > 2019-12-04T09:46:41+00:00; check if 2019-12-04T09:46:41+00:00 <= 2020-12-04T09:46:41+00:00; check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00; check if 2020-12-04T09:46:41+00:00 >= 2019-12-04T09:46:41+00:00; check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00; - check if 2020-12-04T09:46:41+00:00 == 2020-12-04T09:46:41+00:00; - check if hex:12ab == hex:12ab; - check if [1, 2].contains(2); - check if [2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00].contains(2020-12-04T09:46:41+00:00); - check if [false, true].contains(true); - check if ["abc", "def"].contains("abc"); - check if [hex:12ab, hex:34de].contains(hex:34de); - check if ["hello", "world"].contains("hello"); + check if 2020-12-04T09:46:41+00:00 === 2020-12-04T09:46:41+00:00; + check if hex:12ab === hex:12ab; + check if {1, 2}.contains(2); + check if {2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00}.contains(2020-12-04T09:46:41+00:00); + check if {false, true}.contains(true); + check if {"abc", "def"}.contains("abc"); + check if {hex:12ab, hex:34de}.contains(hex:34de); + check if {"hello", "world"}.contains("hello"); |] , [block| check if diff --git a/biscuit/test/Spec/SampleReader.hs b/biscuit/test/Spec/SampleReader.hs index 5387493..44d94e9 100644 --- a/biscuit/test/Spec/SampleReader.hs +++ b/biscuit/test/Spec/SampleReader.hs @@ -43,6 +43,7 @@ import Test.Tasty.HUnit import Auth.Biscuit import Auth.Biscuit.Datalog.AST (renderAuthorizer, renderBlock) +import qualified Auth.Biscuit.Datalog.AST as AST import Auth.Biscuit.Datalog.Executor (ExecutionError (..), ResultError (..)) import Auth.Biscuit.Datalog.Parser (authorizerParser, blockParser) @@ -52,7 +53,7 @@ import Auth.Biscuit.Utils (encodeHex) import Spec.Parser (parseAuthorizer, parseBlock) getB :: ParsedSignedBlock -> Block -getB ((_, b), _, _, _) = b +getB ((_, b), _, _, _, _) = b getAuthority :: Biscuit p Verified -> Block getAuthority = getB . authority @@ -122,7 +123,7 @@ data ValidationR = ValidationR { world :: Maybe WorldDesc , result :: RustResult RustError Int - , authorizer_code :: Authorizer + , authorizer_code :: Text , revocation_ids :: [Text] } deriving stock (Eq, Show, Generic) deriving anyclass (FromJSON, ToJSON) @@ -151,8 +152,11 @@ data TestCase a data BlockDesc = BlockDesc - { symbols :: [Text] - , code :: Text + { symbols :: [Text] + , code :: Text + , public_keys :: [Text] + , external_key :: Maybe Text + , version :: Int } deriving stock (Eq, Show, Generic) deriving anyclass (FromJSON, ToJSON) @@ -224,11 +228,10 @@ checkTokenBlocks step b blockDescs = do processTestCase :: (String -> IO ()) -> PublicKey -> TestCase (FilePath, ByteString) -> Assertion -processTestCase step rootPk TestCase{..} = - if fst filename == "test018_unbound_variables_in_rule.bc" - then - step "Skipping for now (unbound variables are now caught before evaluation)" - else do +processTestCase step rootPk TestCase{..} + | fst filename == "test018_unbound_variables_in_rule.bc" = step "Skipping for now (unbound variables are now caught before evaluation)" + | fst filename `elem` ["test036_secp256r1.bc", "test037_secp256r1_third_party.bc"] = step "Skipping for now (not supported yet)" + | otherwise = do step "Parsing " let vList = Map.toList validations case parse rootPk (snd filename) of @@ -262,6 +265,7 @@ compareParseErrors pe re = mustMatchEither [ key "Format" . key "Signature" . key "InvalidSignature" , key "Format" . key "InvalidSignatureSize" + , key "Format" . key "BlockSignatureDeserializationError" ] InvalidProof -> assertFailure $ "InvalidProof can't appear here " <> show re @@ -299,7 +303,15 @@ processValidation step b (name, ValidationR{..}) = do when (name /= "") $ step ("Checking " <> name) let w = fold world pols <- either (assertFailure . show) pure $ parseAuthorizer $ foldMap (<> ";") (policies w) - res <- authorizeBiscuit b (authorizer_code <> pols) + authorizer <- either (assertFailure . show) pure $ parseAuthorizer authorizer_code + let testfn :: AST.Value -> Maybe AST.Value -> Either String AST.Value + testfn v Nothing = Right v + testfn (LString x) (Just (LString y)) + | x == y = Right $ LString "equal strings" + | otherwise = Right $ LString "different strings" + testfn _ _ = Left "unsupported operands" + let limits = withExternFunc "test" testfn defaultLimits + res <- authorizeBiscuitWithLimits limits b (authorizer <> pols) checkResult compareExecErrors result res let revocationIds = encodeHex <$> toList (getRevocationIds b) step "Comparing revocation ids" @@ -322,29 +334,3 @@ getSpecs = do SampleFile{..} <- readSamplesFile pure $ testGroup "Biscuit samples - compliance checks" $ mkTestCase root_public_key <$> testcases -mkTestCaseFromBiscuit - :: String - -> FilePath - -> Biscuit Open Verified - -> [(String, Authorizer)] - -> IO (TestCase FilePath) -mkTestCaseFromBiscuit title filename biscuit authorizers = do - let mkBlockDesc :: Block -> BlockDesc - mkBlockDesc b = BlockDesc - { code = renderBlock b - , symbols = [] - } - mkValidation :: Authorizer -> IO ValidationR - mkValidation authorizer = do - Right success <- authorizeBiscuit biscuit authorizer - pure ValidationR - { world = Just mempty - , result = Ok 0 - , authorizer_code = authorizer - , revocation_ids = encodeHex <$> toList (getRevocationIds biscuit) - } - BS.writeFile ("test/samples/current/" <> filename) (serialize biscuit) - let token = mkBlockDesc <$> getAuthority biscuit :| getBlocks biscuit - validations <- Map.fromList <$> traverse (traverse mkValidation) authorizers - - pure TestCase{..} diff --git a/biscuit/test/Spec/Verification.hs b/biscuit/test/Spec/Verification.hs index 039ad94..844dbaa 100644 --- a/biscuit/test/Spec/Verification.hs +++ b/biscuit/test/Spec/Verification.hs @@ -50,7 +50,7 @@ ifFalse = MatchedQuery ifFalse' :: Check ifFalse' = Check { cQueries = matchedQuery ifFalse - , cKind = One + , cKind = CheckOne } checkAll' :: Check diff --git a/biscuit/test/samples/current/README.md b/biscuit/test/samples/current/README.md index e5fbafe..9b319e6 100644 --- a/biscuit/test/samples/current/README.md +++ b/biscuit/test/samples/current/README.md @@ -13,6 +13,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -24,6 +26,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -45,7 +49,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -53,7 +57,7 @@ World { "resource(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -68,7 +72,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -96,6 +100,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -105,6 +111,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -124,6 +132,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -135,13 +145,15 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` ### validation -result: `Err(Format(InvalidSignatureSize(16)))` +result: `Err(Format(BlockSignatureDeserializationError("block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]")))` ------------------------------ @@ -154,6 +166,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -165,6 +179,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -184,6 +200,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -195,6 +213,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -214,6 +234,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -225,6 +247,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -234,6 +258,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if resource("file1"); ``` @@ -253,6 +279,8 @@ symbols: ["user_id", "alice", "file1"] public keys: [] +block version: 3 + ``` user_id("alice"); owner("alice", "file1"); @@ -263,6 +291,8 @@ symbols: ["0", "1"] public keys: [] +block version: 3 + ``` right($0, "read") <- resource($0), user_id($1), owner($1, $0); check if resource($0), operation("read"), right($0, "read"); @@ -273,6 +303,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` owner("alice", "file2"); ``` @@ -296,7 +328,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -305,7 +337,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -316,7 +348,7 @@ World { "user_id(\"alice\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -328,7 +360,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -338,7 +370,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -366,6 +398,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -375,6 +409,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -384,6 +420,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` right("file2", "read"); ``` @@ -407,7 +445,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -416,7 +454,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -426,7 +464,7 @@ World { "right(\"file1\", \"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -439,7 +477,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -467,6 +505,8 @@ symbols: [] public keys: [] +block version: 3 + ``` ``` @@ -475,6 +515,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` check if resource("file1"); check if time($time), $time <= 2018-12-20T00:00:00Z; @@ -499,7 +541,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -512,7 +554,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -541,6 +583,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -550,6 +594,8 @@ symbols: ["file2"] public keys: [] +block version: 3 + ``` right("file2", "read"); ``` @@ -574,7 +620,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -583,7 +629,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -593,7 +639,7 @@ World { "right(\"file1\", \"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -606,7 +652,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -634,6 +680,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` right("file1", "read"); ``` @@ -657,7 +705,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -666,7 +714,7 @@ World { "resource(\"file2\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -679,7 +727,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -707,6 +755,8 @@ symbols: ["file1"] public keys: [] +block version: 3 + ``` check if resource("file1"); ``` @@ -728,7 +778,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -740,7 +790,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -773,7 +823,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -785,7 +835,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -813,6 +863,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -823,9 +875,11 @@ symbols: ["valid_date", "0", "1"] public keys: [] +block version: 3 + ``` valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59Z; -valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !["file1"].contains($1); +valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{"file1"}.contains($1); check if valid_date($0), resource($0); ``` @@ -847,7 +901,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -856,7 +910,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { None, Some( @@ -867,7 +921,7 @@ World { "valid_date(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -880,18 +934,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -925,7 +979,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -934,7 +988,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -947,18 +1001,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -986,6 +1040,8 @@ symbols: ["0", "file[0-9]+.txt"] public keys: [] +block version: 3 + ``` check if resource($0), $0.matches("file[0-9]+.txt"); ``` @@ -1006,7 +1062,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1017,7 +1073,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1049,7 +1105,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1060,7 +1116,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1088,6 +1144,8 @@ symbols: ["must_be_present", "hello"] public keys: [] +block version: 3 + ``` must_be_present("hello"); ``` @@ -1108,7 +1166,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1121,7 +1179,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1149,6 +1207,8 @@ symbols: ["hello"] public keys: [] +block version: 3 + ``` check if resource("hello"); ``` @@ -1158,6 +1218,8 @@ symbols: ["test"] public keys: [] +block version: 3 + ``` query("test"); ``` @@ -1177,7 +1239,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1190,7 +1252,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1218,48 +1280,48 @@ symbols: ["hello world", "hello", "world", "aaabde", "a*c?.e", "abd", "aaa", "b" public keys: [] +block version: 3 + ``` check if true; check if !false; -check if !false && true; -check if false || true; -check if (true || false) && true; -check if true == true; -check if false == false; +check if true === true; +check if false === false; check if 1 < 2; check if 2 > 1; check if 1 <= 2; check if 1 <= 1; check if 2 >= 1; check if 2 >= 2; -check if 3 == 3; -check if 1 + 2 * 3 - 4 / 2 == 5; -check if "hello world".starts_with("hello") && "hello world".ends_with("world"); +check if 3 === 3; +check if 1 + 2 * 3 - 4 / 2 === 5; +check if "hello world".starts_with("hello"), "hello world".ends_with("world"); check if "aaabde".matches("a*c?.e"); check if "aaabde".contains("abd"); -check if "aaabde" == "aaa" + "b" + "de"; -check if "abcD12" == "abcD12"; -check if "abcD12".length() == 6; -check if "é".length() == 2; +check if "aaabde" === "aaa" + "b" + "de"; +check if "abcD12" === "abcD12"; +check if "abcD12".length() === 6; +check if "é".length() === 2; check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z; check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; -check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z; -check if hex:12ab == hex:12ab; -check if [1, 2].contains(2); -check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z); -check if [false, true].contains(true); -check if ["abc", "def"].contains("abc"); -check if [hex:12ab, hex:34de].contains(hex:34de); -check if [1, 2].contains([2]); -check if [1, 2] == [1, 2]; -check if [1, 2].intersection([2, 3]) == [2]; -check if [1, 2].union([2, 3]) == [1, 2, 3]; -check if [1, 2, 3].intersection([1, 2]).contains(1); -check if [1, 2, 3].intersection([1, 2]).length() == 2; +check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z; +check if hex:12ab === hex:12ab; +check if {1, 2}.contains(2); +check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z); +check if {false, true}.contains(true); +check if {"abc", "def"}.contains("abc"); +check if {hex:12ab, hex:34de}.contains(hex:34de); +check if {1, 2}.contains({2}); +check if {1, 2} === {1, 2}; +check if {1, 2}.intersection({2, 3}) === {2}; +check if {1, 2}.union({2, 3}) === {1, 2, 3}; +check if {1, 2, 3}.intersection({1, 2}).contains(1); +check if {1, 2, 3}.intersection({1, 2}).length() === 2; +check if {,}.length() === 0; ``` ### validation @@ -1270,7 +1332,7 @@ allow if true; ``` revocation ids: -- `3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c` +- `fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505` authorizer world: ``` @@ -1278,22 +1340,20 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), checks: [ "check if !false", - "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", - "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", + "check if \"é\".length() === 2", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1302,28 +1362,28 @@ World { "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", - "check if [\"abc\", \"def\"].contains(\"abc\")", - "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", - "check if [1, 2].contains(2)", - "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", - "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", - "check if [false, true].contains(true)", - "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", - "check if false || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true", + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "check if {1, 2, 3}.intersection({1, 2}).contains(1)", + "check if {1, 2, 3}.intersection({1, 2}).length() === 2", + "check if {1, 2} === {1, 2}", + "check if {1, 2}.contains(2)", + "check if {1, 2}.contains({2})", + "check if {1, 2}.intersection({2, 3}) === {2}", + "check if {1, 2}.union({2, 3}) === {1, 2, 3}", + "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)", + "check if {false, true}.contains(true)", + "check if {hex:12ab, hex:34de}.contains(hex:34de)", ], }, ] @@ -1346,6 +1406,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if operation("read"); ``` @@ -1355,6 +1417,8 @@ symbols: ["unbound", "any1", "any2"] public keys: [] +block version: 3 + ``` operation($unbound, "read") <- operation($any1, $any2); ``` @@ -1374,6 +1438,8 @@ symbols: [] public keys: [] +block version: 3 + ``` check if operation("read"); ``` @@ -1383,6 +1449,8 @@ symbols: ["any"] public keys: [] +block version: 3 + ``` operation("read") <- operation($any); ``` @@ -1404,7 +1472,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1412,7 +1480,7 @@ World { "operation(\"write\")", ], }, - AuthorizerFactSet { + Facts { origin: { None, Some( @@ -1425,7 +1493,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -1435,7 +1503,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1463,6 +1531,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -1474,6 +1544,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -1496,7 +1568,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1505,7 +1577,7 @@ World { "resource(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1520,7 +1592,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -1548,6 +1620,8 @@ symbols: ["ns::fact_123", "hello é\t😁"] public keys: [] +block version: 3 + ``` ns::fact_123("hello é 😁"); ``` @@ -1568,7 +1642,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1581,7 +1655,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1609,6 +1683,8 @@ symbols: [] public keys: [] +block version: 3 + ``` read(0); write(1); @@ -1656,7 +1732,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1696,7 +1772,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -1724,6 +1800,8 @@ symbols: ["authority_fact"] public keys: [] +block version: 3 + ``` authority_fact(1); ``` @@ -1733,6 +1811,8 @@ symbols: ["block1_fact"] public keys: [] +block version: 3 + ``` block1_fact(1); ``` @@ -1742,6 +1822,8 @@ symbols: ["var"] public keys: [] +block version: 3 + ``` check if authority_fact($var); check if block1_fact($var); @@ -1763,7 +1845,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1773,7 +1855,7 @@ World { "authority_fact(1)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1786,7 +1868,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 2, ), @@ -1815,6 +1897,8 @@ symbols: [] public keys: ["ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"] +block version: 4 + ``` right("read"); check if group("admin") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; @@ -1827,6 +1911,8 @@ public keys: [] external signature by: "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" +block version: 5 + ``` group("admin"); check if right("read"); @@ -1841,13 +1927,13 @@ allow if true; revocation ids: - `470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03` -- `342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03` +- `901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08` authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -1857,7 +1943,7 @@ World { "right(\"read\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -1870,7 +1956,7 @@ World { ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1878,7 +1964,7 @@ World { "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -1906,8 +1992,10 @@ symbols: ["allowed_operations", "A", "B", "op", "allowed"] public keys: [] +block version: 4 + ``` -allowed_operations(["A", "B"]); +allowed_operations({"A", "B"}); check all operation($op), allowed_operations($allowed), $allowed.contains($op); ``` @@ -1928,7 +2016,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1937,20 +2025,20 @@ World { "operation(\"B\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -1983,7 +2071,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -1992,20 +2080,20 @@ World { "operation(\"invalid\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2033,6 +2121,8 @@ symbols: [] public keys: ["ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"] +block version: 4 + ``` query(0); check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189; @@ -2045,6 +2135,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" +block version: 5 + ``` query(1); query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2059,6 +2151,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463" +block version: 5 + ``` query(2); check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2072,6 +2166,8 @@ public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7 external signature by: "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463" +block version: 5 + ``` query(3); check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2083,6 +2179,8 @@ symbols: [] public keys: ["ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"] +block version: 4 + ``` query(4); check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463; @@ -2103,16 +2201,16 @@ allow if true; revocation ids: - `3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04` -- `6528db2c9a561ada9086268549a600a8a52ff434ea8183812623eec0e9b6c5d3c41ab7868808623021d92294d583afdf92f4354bcdaa1bc50453e1b89afd630d` -- `5d5679fe69bfe74b7919323515e9ecba9d01422b16be9341b57f88e695b2bb0bd7966b781001d2b9e00ee618fdc239c96e17e32cb379f13f12d6bd7b1b47ad04` -- `c37bf24c063f0310eccab8864e48dbeffcdd7240b4f8d1e01eba4fc703e6c9082b845bb55543b10f008dc7f4e78540411912ac1f36fa2aa90011dca40f323b09` -- `3f675d6c364e06405d4868c904e40f3d81c32b083d91586db814d4cb4bf536b4ba209d82f11b4cb6da293b60b20d6122fc3e0e08e80c381dee83edd848211900` +- `7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b` +- `d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403` +- `29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d` +- `c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a` authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -2122,7 +2220,7 @@ World { "query(0)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -2132,7 +2230,7 @@ World { "query(1)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 1, @@ -2145,7 +2243,7 @@ World { "query(1, 2)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 2, @@ -2155,7 +2253,7 @@ World { "query(2)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 3, @@ -2165,7 +2263,7 @@ World { "query(3)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 4, @@ -2177,7 +2275,7 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), @@ -2187,7 +2285,7 @@ World { }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2195,7 +2293,7 @@ World { "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -2204,7 +2302,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 2, ), @@ -2213,7 +2311,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 3, ), @@ -2222,7 +2320,7 @@ World { "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 4, ), @@ -2231,7 +2329,7 @@ World { "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136", ], }, - AuthorizerCheckSet { + Checks { origin: Some( 18446744073709551615, ), @@ -2262,10 +2360,12 @@ symbols: [] public keys: [] +block version: 4 + ``` -check if true || 10000000000 * 10000000000 != 0; -check if true || 9223372036854775807 + 1 != 0; -check if true || -9223372036854775808 - 1 != 0; +check if 10000000000 * 10000000000 !== 0; +check if 9223372036854775807 + 1 !== 0; +check if -9223372036854775808 - 1 !== 0; ``` ### validation @@ -2276,7 +2376,7 @@ allow if true; ``` revocation ids: -- `3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05` +- `fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c` authorizer world: ``` @@ -2284,14 +2384,14 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), checks: [ - "check if true || -9223372036854775808 - 1 != 0", - "check if true || 10000000000 * 10000000000 != 0", - "check if true || 9223372036854775807 + 1 != 0", + "check if -9223372036854775808 - 1 !== 0", + "check if 10000000000 * 10000000000 !== 0", + "check if 9223372036854775807 + 1 !== 0", ], }, ] @@ -2314,13 +2414,15 @@ symbols: ["abcD12x", "abcD12"] public keys: [] +block version: 4 + ``` -check if 1 != 3; -check if 1 | 2 ^ 3 == 0; -check if "abcD12x" != "abcD12"; -check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z; -check if hex:12abcd != hex:12ab; -check if [1, 4] != [1, 2]; +check if 1 !== 3; +check if 1 | 2 ^ 3 === 0; +check if "abcD12x" !== "abcD12"; +check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z; +check if hex:12abcd !== hex:12ab; +check if {1, 4} !== {1, 2}; ``` ### validation @@ -2339,17 +2441,184 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), checks: [ - "check if \"abcD12x\" != \"abcD12\"", - "check if 1 != 3", - "check if 1 | 2 ^ 3 == 0", - "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", - "check if [1, 4] != [1, 2]", - "check if hex:12abcd != hex:12ab", + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if hex:12abcd !== hex:12ab", + "check if {1, 4} !== {1, 2}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test reject if: test029_reject_if.bc +### token + +authority: +symbols: ["test"] + +public keys: [] + +block version: 6 + +``` +reject if test($test), $test; +``` + +### validation + +authorizer code: +``` +test(false); + +allow if true; +``` + +revocation ids: +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "test(false)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "reject if test($test), $test", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "rejection" + +authorizer code: +``` +test(true); + +allow if true; +``` + +revocation ids: +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "test(true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "reject if test($test), $test", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "reject if test($test), $test" })] }))` + + +------------------------------ + +## test null: test030_null.bc +### token + +authority: +symbols: ["fact", "value"] + +public keys: [] + +block version: 6 + +``` +check if fact(null, $value), $value == null; +reject if fact(null, $value), $value != null; +``` + +### validation + +authorizer code: +``` +fact(null, null); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, null)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", ], }, ] @@ -2360,4 +2629,981 @@ World { ``` result: `Ok(0)` +### validation for "rejection1" + +authorizer code: +``` +fact(null, 1); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` +### validation for "rejection2" + +authorizer code: +``` +fact(null, true); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` +### validation for "rejection3" + +authorizer code: +``` +fact(null, "abcd"); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, \"abcd\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` + + +------------------------------ + +## test heterogeneous equal: test031_heterogeneous_equal.bc +### token + +authority: +symbols: ["abcD12", "abcD12x", "fact", "value", "fact2"] + +public keys: [] + +block version: 6 + +``` +check if true == true; +check if false == false; +check if false != true; +check if 1 != true; +check if 1 == 1; +check if 1 != 3; +check if 1 != true; +check if "abcD12" == "abcD12"; +check if "abcD12x" != "abcD12"; +check if "abcD12x" != true; +check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != true; +check if hex:12abcd == hex:12abcd; +check if hex:12abcd != hex:12ab; +check if hex:12abcd != true; +check if {1, 2} == {1, 2}; +check if {1, 4} != {1, 2}; +check if {1, 4} != true; +check if fact(1, $value), 1 == $value; +check if fact2(1, $value), 1 != $value; +``` + +### validation + +authorizer code: +``` +fact(1, 1); +fact2(1, 2); + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 1)", + "fact2(1, 2)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "evaluate to false" + +authorizer code: +``` +fact(1, 2); +fact2(1, 1); + +check if false != false; + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 2)", + "fact2(1, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, + Checks { + origin: Some( + 18446744073709551615, + ), + checks: [ + "check if false != false", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Authorizer(FailedAuthorizerCheck { check_id: 0, rule: "check if false != false" }), Block(FailedBlockCheck { block_id: 0, check_id: 19, rule: "check if fact(1, $value), 1 == $value" }), Block(FailedBlockCheck { block_id: 0, check_id: 20, rule: "check if fact2(1, $value), 1 != $value" })] }))` + + +------------------------------ + +## test laziness and closures: test032_laziness_closures.bc +### token + +authority: +symbols: ["x", "p", "q"] + +public keys: [] + +block version: 6 + +``` +check if !false && true; +check if false || true; +check if (true || false) && true; +check if !(false && "x".intersection("x")); +check if true || "x".intersection("x"); +check if {1, 2, 3}.all($p -> $p > 0); +check if !{1, 2, 3}.all($p -> $p == 2); +check if {1, 2, 3}.any($p -> $p > 2); +check if !{1, 2, 3}.any($p -> $p > 3); +check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q)); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "shadowing" + +authorizer code: +``` +allow if [true].any($p -> [true].all($p -> $p)); +``` + +revocation ids: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)", + ], + }, +] + policies: [ + "allow if [true].any($p -> [true].all($p -> $p))", +] +} +``` + +result: `Err(Execution(ShadowedVariable))` + + +------------------------------ + +## test .type(): test033_typeof.bc +### token + +authority: +symbols: ["integer", "string", "test", "date", "bytes", "bool", "set", "null", "array", "map", "a", "t"] + +public keys: [] + +block version: 6 + +``` +integer(1); +string("test"); +date(2023-12-28T00:00:00Z); +bytes(hex:aa); +bool(true); +set({false, true}); +null(null); +array([1, 2, 3]); +map({"a": true}); +check if 1.type() == "integer"; +check if integer($t), $t.type() == "integer"; +check if "test".type() == "string"; +check if string($t), $t.type() == "string"; +check if (2023-12-28T00:00:00Z).type() == "date"; +check if date($t), $t.type() == "date"; +check if hex:aa.type() == "bytes"; +check if bytes($t), $t.type() == "bytes"; +check if true.type() == "bool"; +check if bool($t), $t.type() == "bool"; +check if {false, true}.type() == "set"; +check if set($t), $t.type() == "set"; +check if null.type() == "null"; +check if null($t), $t.type() == "null"; +check if array($t), $t.type() == "array"; +check if map($t), $t.type() == "map"; +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test array and map operations: test034_array_map.bc +### token + +authority: +symbols: ["a", "b", "c", "p", "d", "A", "kv", "id", "roles"] + +public keys: [] + +block version: 6 + +``` +check if [1, 2, 1].length() == 3; +check if ["a", "b"] != true; +check if ["a", "b"] != [1, 2, 3]; +check if ["a", "b"] == ["a", "b"]; +check if ["a", "b"] === ["a", "b"]; +check if ["a", "b"] !== ["a", "c"]; +check if ["a", "b", "c"].contains("c"); +check if [1, 2, 3].starts_with([1, 2]); +check if [4, 5, 6].ends_with([6]); +check if [1, 2, "a"].get(2) == "a"; +check if [1, 2].get(3) == null; +check if [1, 2, 3].all($p -> $p > 0); +check if [1, 2, 3].any($p -> $p > 2); +check if {"a": 1, "b": 2, "c": 3, "d": 4}.length() == 4; +check if {1: "a", 2: "b"} != true; +check if {1: "a", 2: "b"} != {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} == {1: "a", 2: "b"}; +check if {1: "a", 2: "b"} !== {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} === {1: "a", 2: "b"}; +check if {"a": 1, "b": 2, "c": 3, "d": 4}.contains("d"); +check if {1: "A", "a": 1, "b": 2}.get("a") == 1; +check if {1: "A", "a": 1, "b": 2}.get(1) == "A"; +check if {1: "A", "a": 1, "b": 2}.get("c") == null; +check if {1: "A", "a": 1, "b": 2}.get(2) == null; +check if {"a": 1, "b": 2}.all($kv -> $kv.get(0) != "c" && $kv.get(1) < 3); +check if {1: "A", "a": 1, "b": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == "A"); +check if {"user": {"id": 1, "roles": ["admin"]}}.get("user").get("roles").contains("admin"); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test ffi calls (v6 blocks): test035_ffi.bc +### token + +authority: +symbols: ["test", "a", "equal strings"] + +public keys: [] + +block version: 6 + +``` +check if true.extern::test(), "a".extern::test("a") == "equal strings"; +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `d1719fd101c2695d2dac4df67569918363f691b6167670e1dbbf8026f639a7aa1ec2e13707f4d34cadbb2adce5c6e8a816577dd069a8717e0f5cb4ea3cec5b04` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\"", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## ECDSA secp256r1 signatures: test036_secp256r1.bc +### token + +authority: +symbols: ["file1", "file2"] + +public keys: [] + +block version: 3 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +``` + +1: +symbols: ["0"] + +public keys: [] + +block version: 3 + +``` +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d` +- `3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## ECDSA secp256r1 signature on third-party block: test037_secp256r1_third_party.bc +### token + +authority: +symbols: ["file1", "file2", "from_third"] + +public keys: ["secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"] + +block version: 4 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf; +``` + +1: +symbols: ["from_third", "0"] + +public keys: [] + +external signature by: "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + +block version: 5 + +``` +from_third(true); +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f` +- `30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, + Facts { + origin: { + Some( + 1, + ), + }, + facts: [ + "from_third(true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + ], + }, + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test try operation: test038_try_op.bc +### token + +authority: +symbols: [] + +public keys: [] + +block version: 6 + +``` +check if (true === 12).try_or(true); +check if ((true === 12).try_or(true === 12)).try_or(true); +reject if (true == 12).try_or(true); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "right-hand side does not catch errors" + +authorizer code: +``` +check if true.try_or(true === 12); + +allow if true; +``` + +revocation ids: +- `79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)", + ], + }, + Checks { + origin: Some( + 18446744073709551615, + ), + checks: [ + "check if true.try_or(true === 12)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(Execution(InvalidType))` diff --git a/biscuit/test/samples/current/samples.json b/biscuit/test/samples/current/samples.json index d97790a..c457086 100644 --- a/biscuit/test/samples/current/samples.json +++ b/biscuit/test/samples/current/samples.json @@ -13,7 +13,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -21,7 +22,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -98,7 +100,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -106,7 +109,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -137,7 +141,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -145,7 +150,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -154,7 +160,7 @@ "result": { "Err": { "Format": { - "InvalidSignatureSize": 16 + "BlockSignatureDeserializationError": "block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]" } } }, @@ -174,7 +180,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -182,7 +189,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -213,7 +221,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -221,7 +230,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -252,7 +262,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -260,13 +271,15 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\n" + "code": "check if resource(\"file1\");\n", + "version": 3 } ], "validations": { @@ -298,7 +311,8 @@ ], "public_keys": [], "external_key": null, - "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n" + "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n", + "version": 3 }, { "symbols": [ @@ -307,7 +321,8 @@ ], "public_keys": [], "external_key": null, - "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [ @@ -315,7 +330,8 @@ ], "public_keys": [], "external_key": null, - "code": "owner(\"alice\", \"file2\");\n" + "code": "owner(\"alice\", \"file2\");\n", + "version": 3 } ], "validations": { @@ -408,7 +424,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -416,7 +433,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 }, { "symbols": [ @@ -424,7 +442,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file2\", \"read\");\n" + "code": "right(\"file2\", \"read\");\n", + "version": 3 } ], "validations": { @@ -507,7 +526,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "" + "code": "", + "version": 3 }, { "symbols": [ @@ -515,7 +535,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n" + "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n", + "version": 3 } ], "validations": { @@ -585,7 +606,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -593,7 +615,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file2\", \"read\");\n" + "code": "right(\"file2\", \"read\");\n", + "version": 3 } ], "validations": { @@ -676,7 +699,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\n" + "code": "right(\"file1\", \"read\");\n", + "version": 3 } ], "validations": { @@ -750,7 +774,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"file1\");\n" + "code": "check if resource(\"file1\");\n", + "version": 3 } ], "validations": { @@ -852,7 +877,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -862,7 +888,8 @@ ], "public_keys": [], "external_key": null, - "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1);\ncheck if valid_date($0), resource($0);\n" + "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1);\ncheck if valid_date($0), resource($0);\n", + "version": 3 } ], "validations": { @@ -902,7 +929,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -954,7 +981,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -1009,7 +1036,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n" + "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n", + "version": 3 } ], "validations": { @@ -1109,7 +1137,8 @@ ], "public_keys": [], "external_key": null, - "code": "must_be_present(\"hello\");\n" + "code": "must_be_present(\"hello\");\n", + "version": 3 } ], "validations": { @@ -1158,7 +1187,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource(\"hello\");\n" + "code": "check if resource(\"hello\");\n", + "version": 3 }, { "symbols": [ @@ -1166,7 +1196,8 @@ ], "public_keys": [], "external_key": null, - "code": "query(\"test\");\n" + "code": "query(\"test\");\n", + "version": 3 } ], "validations": { @@ -1245,7 +1276,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12\".length() == 6;\ncheck if \"é\".length() == 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" + "code": "check if true;\ncheck if !false;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if {1, 2}.contains(2);\ncheck if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z);\ncheck if {false, true}.contains(true);\ncheck if {\"abc\", \"def\"}.contains(\"abc\");\ncheck if {hex:12ab, hex:34de}.contains(hex:34de);\ncheck if {1, 2}.contains({2});\ncheck if {1, 2} === {1, 2};\ncheck if {1, 2}.intersection({2, 3}) === {2};\ncheck if {1, 2}.union({2, 3}) === {1, 2, 3};\ncheck if {1, 2, 3}.intersection({1, 2}).contains(1);\ncheck if {1, 2, 3}.intersection({1, 2}).length() === 2;\ncheck if {,}.length() === 0;\n", + "version": 3 } ], "validations": { @@ -1258,16 +1290,14 @@ "origin": 0, "checks": [ "check if !false", - "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", - "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", + "check if \"é\".length() === 2", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1276,28 +1306,28 @@ "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", - "check if [\"abc\", \"def\"].contains(\"abc\")", - "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", - "check if [1, 2].contains(2)", - "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", - "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", - "check if [false, true].contains(true)", - "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", - "check if false || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true" + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "check if {1, 2, 3}.intersection({1, 2}).contains(1)", + "check if {1, 2, 3}.intersection({1, 2}).length() === 2", + "check if {1, 2} === {1, 2}", + "check if {1, 2}.contains(2)", + "check if {1, 2}.contains({2})", + "check if {1, 2}.intersection({2, 3}) === {2}", + "check if {1, 2}.union({2, 3}) === {1, 2, 3}", + "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)", + "check if {false, true}.contains(true)", + "check if {hex:12ab, hex:34de}.contains(hex:34de)" ] } ], @@ -1310,7 +1340,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c" + "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505" ] } } @@ -1323,7 +1353,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if operation(\"read\");\n" + "code": "check if operation(\"read\");\n", + "version": 3 }, { "symbols": [ @@ -1333,7 +1364,8 @@ ], "public_keys": [], "external_key": null, - "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n" + "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n", + "version": 3 } ], "validations": { @@ -1365,7 +1397,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if operation(\"read\");\n" + "code": "check if operation(\"read\");\n", + "version": 3 }, { "symbols": [ @@ -1373,7 +1406,8 @@ ], "public_keys": [], "external_key": null, - "code": "operation(\"read\") <- operation($any);\n" + "code": "operation(\"read\") <- operation($any);\n", + "version": 3 } ], "validations": { @@ -1457,7 +1491,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -1465,7 +1500,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -1527,7 +1563,8 @@ ], "public_keys": [], "external_key": null, - "code": "ns::fact_123(\"hello é\t😁\");\n" + "code": "ns::fact_123(\"hello é\t😁\");\n", + "version": 3 } ], "validations": { @@ -1574,7 +1611,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n" + "code": "read(0);\nwrite(1);\nresource(2);\noperation(3);\nright(4);\ntime(5);\nrole(6);\nowner(7);\ntenant(8);\nnamespace(9);\nuser(10);\nteam(11);\nservice(12);\nadmin(13);\nemail(14);\ngroup(15);\nmember(16);\nip_address(17);\nclient(18);\nclient_ip(19);\ndomain(20);\npath(21);\nversion(22);\ncluster(23);\nnode(24);\nhostname(25);\nnonce(26);\nquery(27);\n", + "version": 3 } ], "validations": { @@ -1650,7 +1688,8 @@ ], "public_keys": [], "external_key": null, - "code": "authority_fact(1);\n" + "code": "authority_fact(1);\n", + "version": 3 }, { "symbols": [ @@ -1658,7 +1697,8 @@ ], "public_keys": [], "external_key": null, - "code": "block1_fact(1);\n" + "code": "block1_fact(1);\n", + "version": 3 }, { "symbols": [ @@ -1666,7 +1706,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n" + "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n", + "version": 3 } ], "validations": { @@ -1743,13 +1784,15 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": null, - "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 4 }, { "symbols": [], "public_keys": [], "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", - "code": "group(\"admin\");\ncheck if right(\"read\");\n" + "code": "group(\"admin\");\ncheck if right(\"read\");\n", + "version": 5 } ], "validations": { @@ -1798,7 +1841,7 @@ "authorizer_code": "allow if true;\n", "revocation_ids": [ "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03", - "342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03" + "901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08" ] } } @@ -1817,7 +1860,8 @@ ], "public_keys": [], "external_key": null, - "code": "allowed_operations([\"A\", \"B\"]);\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n" + "code": "allowed_operations({\"A\", \"B\"});\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n", + "version": 4 } ], "validations": { @@ -1838,7 +1882,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1880,7 +1924,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1934,7 +1978,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": null, - "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 4 }, { "symbols": [], @@ -1943,7 +1988,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", - "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(1);\nquery(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1952,7 +1998,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", - "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(2);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1961,7 +2008,8 @@ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" ], "external_key": "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", - "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n" + "code": "query(3);\ncheck if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n", + "version": 5 }, { "symbols": [], @@ -1970,7 +2018,8 @@ "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136" ], "external_key": null, - "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n" + "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n", + "version": 4 } ], "validations": { @@ -2090,10 +2139,10 @@ "authorizer_code": "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\n\ndeny if query(3);\ndeny if query(1, 2);\ndeny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\nallow if true;\n", "revocation_ids": [ "3771cefe71beb21ead35a59c8116ee82627a5717c0295f35980662abccb159fe1b37848cb1818e548656bd4fd882d0094a2daab631c76b2b72e3a093914bfe04", - "6528db2c9a561ada9086268549a600a8a52ff434ea8183812623eec0e9b6c5d3c41ab7868808623021d92294d583afdf92f4354bcdaa1bc50453e1b89afd630d", - "5d5679fe69bfe74b7919323515e9ecba9d01422b16be9341b57f88e695b2bb0bd7966b781001d2b9e00ee618fdc239c96e17e32cb379f13f12d6bd7b1b47ad04", - "c37bf24c063f0310eccab8864e48dbeffcdd7240b4f8d1e01eba4fc703e6c9082b845bb55543b10f008dc7f4e78540411912ac1f36fa2aa90011dca40f323b09", - "3f675d6c364e06405d4868c904e40f3d81c32b083d91586db814d4cb4bf536b4ba209d82f11b4cb6da293b60b20d6122fc3e0e08e80c381dee83edd848211900" + "7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b", + "d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403", + "29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d", + "c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a" ] } } @@ -2106,7 +2155,8 @@ "symbols": [], "public_keys": [], "external_key": null, - "code": "check if true || 10000000000 * 10000000000 != 0;\ncheck if true || 9223372036854775807 + 1 != 0;\ncheck if true || -9223372036854775808 - 1 != 0;\n" + "code": "check if 10000000000 * 10000000000 !== 0;\ncheck if 9223372036854775807 + 1 !== 0;\ncheck if -9223372036854775808 - 1 !== 0;\n", + "version": 4 } ], "validations": { @@ -2118,9 +2168,9 @@ { "origin": 0, "checks": [ - "check if true || -9223372036854775808 - 1 != 0", - "check if true || 10000000000 * 10000000000 != 0", - "check if true || 9223372036854775807 + 1 != 0" + "check if -9223372036854775808 - 1 !== 0", + "check if 10000000000 * 10000000000 !== 0", + "check if 9223372036854775807 + 1 !== 0" ] } ], @@ -2135,7 +2185,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05" + "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c" ] } } @@ -2151,7 +2201,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if 1 != 3;\ncheck if 1 | 2 ^ 3 == 0;\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if hex:12abcd != hex:12ab;\ncheck if [1, 4] != [1, 2];\n" + "code": "check if 1 !== 3;\ncheck if 1 | 2 ^ 3 === 0;\ncheck if \"abcD12x\" !== \"abcD12\";\ncheck if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z;\ncheck if hex:12abcd !== hex:12ab;\ncheck if {1, 4} !== {1, 2};\n", + "version": 4 } ], "validations": { @@ -2163,12 +2214,12 @@ { "origin": 0, "checks": [ - "check if \"abcD12x\" != \"abcD12\"", - "check if 1 != 3", - "check if 1 | 2 ^ 3 == 0", - "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", - "check if [1, 4] != [1, 2]", - "check if hex:12abcd != hex:12ab" + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if hex:12abcd !== hex:12ab", + "check if {1, 4} !== {1, 2}" ] } ], @@ -2185,6 +2236,1023 @@ ] } } + }, + { + "title": "test reject if", + "filename": "test029_reject_if.bc", + "token": [ + { + "symbols": [ + "test" + ], + "public_keys": [], + "external_key": null, + "code": "reject if test($test), $test;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "test(false)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "reject if test($test), $test" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "test(false);\n\nallow if true;\n", + "revocation_ids": [ + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" + ] + }, + "rejection": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "test(true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "reject if test($test), $test" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "reject if test($test), $test" + } + } + ] + } + } + } + }, + "authorizer_code": "test(true);\n\nallow if true;\n", + "revocation_ids": [ + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" + ] + } + } + }, + { + "title": "test null", + "filename": "test030_null.bc", + "token": [ + { + "symbols": [ + "fact", + "value" + ], + "public_keys": [], + "external_key": null, + "code": "check if fact(null, $value), $value == null;\nreject if fact(null, $value), $value != null;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, null)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(null, null);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection1": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, 1)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, 1);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection2": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, true);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection3": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, \"abcd\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, \"abcd\");\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + } + } + }, + { + "title": "test heterogeneous equal", + "filename": "test031_heterogeneous_equal.bc", + "token": [ + { + "symbols": [ + "abcD12", + "abcD12x", + "fact", + "value", + "fact2" + ], + "public_keys": [], + "external_key": null, + "code": "check if true == true;\ncheck if false == false;\ncheck if false != true;\ncheck if 1 != true;\ncheck if 1 == 1;\ncheck if 1 != 3;\ncheck if 1 != true;\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if \"abcD12x\" != true;\ncheck if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != true;\ncheck if hex:12abcd == hex:12abcd;\ncheck if hex:12abcd != hex:12ab;\ncheck if hex:12abcd != true;\ncheck if {1, 2} == {1, 2};\ncheck if {1, 4} != {1, 2};\ncheck if {1, 4} != true;\ncheck if fact(1, $value), 1 == $value;\ncheck if fact2(1, $value), 1 != $value;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 1)", + "fact2(1, 2)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(1, 1);\nfact2(1, 2);\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + }, + "evaluate to false": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 2)", + "fact2(1, 1)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + }, + { + "origin": 18446744073709551615, + "checks": [ + "check if false != false" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Authorizer": { + "check_id": 0, + "rule": "check if false != false" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 19, + "rule": "check if fact(1, $value), 1 == $value" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 20, + "rule": "check if fact2(1, $value), 1 != $value" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(1, 2);\nfact2(1, 1);\n\ncheck if false != false;\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + } + } + }, + { + "title": "test laziness and closures", + "filename": "test032_laziness_closures.bc", + "token": [ + { + "symbols": [ + "x", + "p", + "q" + ], + "public_keys": [], + "external_key": null, + "code": "check if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if !(false && \"x\".intersection(\"x\"));\ncheck if true || \"x\".intersection(\"x\");\ncheck if {1, 2, 3}.all($p -> $p > 0);\ncheck if !{1, 2, 3}.all($p -> $p == 2);\ncheck if {1, 2, 3}.any($p -> $p > 2);\ncheck if !{1, 2, 3}.any($p -> $p > 3);\ncheck if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q));\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + }, + "shadowing": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)" + ] + } + ], + "policies": [ + "allow if {true}.any($p -> {true}.all($p -> $p))" + ] + }, + "result": { + "Err": { + "Execution": "ShadowedVariable" + } + }, + "authorizer_code": "allow if {true}.any($p -> {true}.all($p -> $p));\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + } + } + }, + { + "title": "test .type()", + "filename": "test033_typeof.bc", + "token": [ + { + "symbols": [ + "integer", + "string", + "test", + "date", + "bytes", + "bool", + "set", + "null", + "array", + "map", + "a", + "t" + ], + "public_keys": [], + "external_key": null, + "code": "integer(1);\nstring(\"test\");\ndate(2023-12-28T00:00:00Z);\nbytes(hex:aa);\nbool(true);\nset({false, true});\nnull(null);\narray([1, 2, 3]);\nmap({\"a\": true});\ncheck if 1.type() == \"integer\";\ncheck if integer($t), $t.type() == \"integer\";\ncheck if \"test\".type() == \"string\";\ncheck if string($t), $t.type() == \"string\";\ncheck if (2023-12-28T00:00:00Z).type() == \"date\";\ncheck if date($t), $t.type() == \"date\";\ncheck if hex:aa.type() == \"bytes\";\ncheck if bytes($t), $t.type() == \"bytes\";\ncheck if true.type() == \"bool\";\ncheck if bool($t), $t.type() == \"bool\";\ncheck if {false, true}.type() == \"set\";\ncheck if set($t), $t.type() == \"set\";\ncheck if null.type() == \"null\";\ncheck if null($t), $t.type() == \"null\";\ncheck if array($t), $t.type() == \"array\";\ncheck if map($t), $t.type() == \"map\";\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + 0 + ], + "facts": [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402" + ] + } + } + }, + { + "title": "test array and map operations", + "filename": "test034_array_map.bc", + "token": [ + { + "symbols": [ + "a", + "b", + "c", + "p", + "d", + "A", + "kv", + "id", + "roles" + ], + "public_keys": [], + "external_key": null, + "code": "check if [1, 2, 1].length() == 3;\ncheck if [\"a\", \"b\"] != true;\ncheck if [\"a\", \"b\"] != [1, 2, 3];\ncheck if [\"a\", \"b\"] == [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] === [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] !== [\"a\", \"c\"];\ncheck if [\"a\", \"b\", \"c\"].contains(\"c\");\ncheck if [1, 2, 3].starts_with([1, 2]);\ncheck if [4, 5, 6].ends_with([6]);\ncheck if [1, 2, \"a\"].get(2) == \"a\";\ncheck if [1, 2].get(3) == null;\ncheck if [1, 2, 3].all($p -> $p > 0);\ncheck if [1, 2, 3].any($p -> $p > 2);\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4;\ncheck if {1: \"a\", 2: \"b\"} != true;\ncheck if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"};\ncheck if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"};\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\");\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\";\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null;\ncheck if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3);\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\");\ncheck if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\");\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d" + ] + } + } + }, + { + "title": "test ffi calls (v6 blocks)", + "filename": "test035_ffi.bc", + "token": [ + { + "symbols": [ + "test", + "a", + "equal strings" + ], + "public_keys": [], + "external_key": null, + "code": "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\";\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if true.extern::test(), \"a\".extern::test(\"a\") == \"equal strings\"" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "d1719fd101c2695d2dac4df67569918363f691b6167670e1dbbf8026f639a7aa1ec2e13707f4d34cadbb2adce5c6e8a816577dd069a8717e0f5cb4ea3cec5b04" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signatures", + "filename": "test036_secp256r1.bc", + "token": [ + { + "symbols": [ + "file1", + "file2" + ], + "public_keys": [], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 + }, + { + "symbols": [ + "0" + ], + "public_keys": [], + "external_key": null, + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "revocation_ids": [ + "628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d", + "3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signature on third-party block", + "filename": "test037_secp256r1_third_party.bc", + "token": [ + { + "symbols": [ + "file1", + "file2", + "from_third" + ], + "public_keys": [ + "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\ncheck if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf;\n", + "version": 4 + }, + { + "symbols": [ + "from_third", + "0" + ], + "public_keys": [], + "external_key": "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + "code": "from_third(true);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 5 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + }, + { + "origin": [ + 1 + ], + "facts": [ + "from_third(true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ] + }, + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "revocation_ids": [ + "70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f", + "30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf" + ] + } + } + }, + { + "title": "test try operation", + "filename": "test038_try_op.bc", + "token": [ + { + "symbols": [], + "public_keys": [], + "external_key": null, + "code": "check if (true === 12).try_or(true);\ncheck if ((true === 12).try_or(true === 12)).try_or(true);\nreject if (true == 12).try_or(true);\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600" + ] + }, + "right-hand side does not catch errors": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)" + ] + }, + { + "origin": 18446744073709551615, + "checks": [ + "check if true.try_or(true === 12)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "Execution": "InvalidType" + } + }, + "authorizer_code": "check if true.try_or(true === 12);\n\nallow if true;\n", + "revocation_ids": [ + "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600" + ] + } + } } ] } diff --git a/biscuit/test/samples/current/test017_expressions.bc b/biscuit/test/samples/current/test017_expressions.bc index 1f3234c..5b28d36 100644 Binary files a/biscuit/test/samples/current/test017_expressions.bc and b/biscuit/test/samples/current/test017_expressions.bc differ diff --git a/biscuit/test/samples/current/test024_third_party.bc b/biscuit/test/samples/current/test024_third_party.bc index d2aef52..8b7b864 100644 Binary files a/biscuit/test/samples/current/test024_third_party.bc and b/biscuit/test/samples/current/test024_third_party.bc differ diff --git a/biscuit/test/samples/current/test026_public_keys_interning.bc b/biscuit/test/samples/current/test026_public_keys_interning.bc index 392d649..89a87c7 100644 Binary files a/biscuit/test/samples/current/test026_public_keys_interning.bc and b/biscuit/test/samples/current/test026_public_keys_interning.bc differ diff --git a/biscuit/test/samples/current/test027_integer_wraparound.bc b/biscuit/test/samples/current/test027_integer_wraparound.bc index 50aa63b..463a6c5 100644 Binary files a/biscuit/test/samples/current/test027_integer_wraparound.bc and b/biscuit/test/samples/current/test027_integer_wraparound.bc differ diff --git a/biscuit/test/samples/current/test029_reject_if.bc b/biscuit/test/samples/current/test029_reject_if.bc new file mode 100644 index 0000000..86ef918 Binary files /dev/null and b/biscuit/test/samples/current/test029_reject_if.bc differ diff --git a/biscuit/test/samples/current/test030_null.bc b/biscuit/test/samples/current/test030_null.bc new file mode 100644 index 0000000..2868715 Binary files /dev/null and b/biscuit/test/samples/current/test030_null.bc differ diff --git a/biscuit/test/samples/current/test031_heterogeneous_equal.bc b/biscuit/test/samples/current/test031_heterogeneous_equal.bc new file mode 100644 index 0000000..2f6cb41 Binary files /dev/null and b/biscuit/test/samples/current/test031_heterogeneous_equal.bc differ diff --git a/biscuit/test/samples/current/test032_laziness_closures.bc b/biscuit/test/samples/current/test032_laziness_closures.bc new file mode 100644 index 0000000..9a98b87 Binary files /dev/null and b/biscuit/test/samples/current/test032_laziness_closures.bc differ diff --git a/biscuit/test/samples/current/test033_typeof.bc b/biscuit/test/samples/current/test033_typeof.bc new file mode 100644 index 0000000..517ae4e Binary files /dev/null and b/biscuit/test/samples/current/test033_typeof.bc differ diff --git a/biscuit/test/samples/current/test034_array_map.bc b/biscuit/test/samples/current/test034_array_map.bc new file mode 100644 index 0000000..9deb150 Binary files /dev/null and b/biscuit/test/samples/current/test034_array_map.bc differ diff --git a/biscuit/test/samples/current/test035_ffi.bc b/biscuit/test/samples/current/test035_ffi.bc new file mode 100644 index 0000000..a65c1c4 Binary files /dev/null and b/biscuit/test/samples/current/test035_ffi.bc differ diff --git a/biscuit/test/samples/current/test036_secp256r1.bc b/biscuit/test/samples/current/test036_secp256r1.bc new file mode 100644 index 0000000..8c4c772 Binary files /dev/null and b/biscuit/test/samples/current/test036_secp256r1.bc differ diff --git a/biscuit/test/samples/current/test037_secp256r1_third_party.bc b/biscuit/test/samples/current/test037_secp256r1_third_party.bc new file mode 100644 index 0000000..d40456e Binary files /dev/null and b/biscuit/test/samples/current/test037_secp256r1_third_party.bc differ diff --git a/biscuit/test/samples/current/test038_try_op.bc b/biscuit/test/samples/current/test038_try_op.bc new file mode 100644 index 0000000..4155d44 Binary files /dev/null and b/biscuit/test/samples/current/test038_try_op.bc differ