biscuit-haskell 0.4.0.1 → 0.5.0.0
raw patch · 42 files changed
+4797/−2705 lines, 42 filesdep +cryptondep −cryptonitedep ~base16dep ~base64dep ~bytestringbinary-addedPVP ok
version bump matches the API change (PVP)
Dependencies added: crypton
Dependencies removed: cryptonite
Dependency ranges changed: base16, base64, bytestring, containers, megaparsec, random, template-haskell, time
API changes (from Hackage documentation)
Files
- ChangeLog.md +5/−2
- README.md +6/−4
- biscuit-haskell.cabal +14/−14
- src/Auth/Biscuit.hs +25/−18
- src/Auth/Biscuit/Crypto.hs +180/−60
- src/Auth/Biscuit/Datalog/AST.hs +224/−32
- src/Auth/Biscuit/Datalog/Executor.hs +232/−36
- src/Auth/Biscuit/Datalog/Parser.hs +114/−21
- src/Auth/Biscuit/Datalog/ScopedExecutor.hs +7/−0
- src/Auth/Biscuit/Example.hs +8/−1
- src/Auth/Biscuit/Proto.hs +98/−53
- src/Auth/Biscuit/ProtoBufAdapter.hs +242/−122
- src/Auth/Biscuit/Symbols.hs +7/−0
- src/Auth/Biscuit/Timer.hs +1/−1
- src/Auth/Biscuit/Token.hs +42/−40
- src/Auth/Biscuit/Utils.hs +1/−1
- test/Spec.hs +7/−2
- test/Spec/AST.hs +44/−0
- test/Spec/Executor.hs +32/−26
- test/Spec/NewCrypto.hs +14/−9
- test/Spec/Parser.hs +126/−41
- test/Spec/Quasiquoter.hs +4/−0
- test/Spec/Roundtrip.hs +18/−14
- test/Spec/SampleReader.hs +26/−36
- test/Spec/ScopedExecutor.hs +26/−2
- test/Spec/Verification.hs +5/−1
- test/samples/current/samples.json +3289/−2169
- test/samples/current/test017_expressions.bc binary
- test/samples/current/test024_third_party.bc binary
- test/samples/current/test026_public_keys_interning.bc binary
- test/samples/current/test027_integer_wraparound.bc binary
- test/samples/current/test028_expressions_v4.bc binary
- test/samples/current/test029_reject_if.bc binary
- test/samples/current/test030_null.bc binary
- test/samples/current/test031_heterogeneous_equal.bc binary
- test/samples/current/test032_laziness_closures.bc binary
- test/samples/current/test033_typeof.bc binary
- test/samples/current/test034_array_map.bc binary
- test/samples/current/test035_ffi.bc binary
- test/samples/current/test036_secp256r1.bc binary
- test/samples/current/test037_secp256r1_third_party.bc binary
- test/samples/current/test038_try_op.bc binary
@@ -1,8 +1,11 @@ # Changelog for biscuit-haskell -## 0.4.0.1+## 0.5.0.0 -- force datalog evaluation to WHNF when running the maxtime watchdog+- biscuit v3.3 support+- drop support for GHC <9.6+- support for GHC 9.6, 9.8, 9.10, 9.12+- dependencies update (including removal of cryptonite in favor of crypton) ## 0.4.0.0
@@ -6,8 +6,10 @@ ## Supported biscuit versions -The core library supports [`v2` biscuits][v2spec] (both open and sealed).+The core library supports [`v3.0` to `v3.3` biscuits][spec] (both open and sealed). +Only `ed25519` signatures are supported. `p256r1` signatures are not supported yet.+ ## How to use this library This library was designed with the use of [`QuasiQuotes`][quasiquotes] in mind.@@ -103,7 +105,7 @@ [gcouprie]: https://github.com/geal [biscuit]: https://www.clever-cloud.com/blog/engineering/2021/04/12/introduction-to-biscuit/ [biscuittutorial]: https://www.clever-cloud.com/blog/engineering/2021/04/15/biscuit-tutorial/-[v2spec]: https://github.com/CleverCloud/biscuit/blob/2.0/SPECIFICATIONS.md+[spec]: https://github.com/eclipse-biscuit/biscuit/blob/main/SPECIFICATIONS.md [quasiquotes]: https://wiki.haskell.org/Quasiquotation-[biscuitexample]: https://github.com/biscuit-auth/biscuit-haskell/blob/main/biscuit/src/Auth/Biscuit/Example.hs-[packagedoc]: https://hackage.haskell.org/package/biscuit-haskell-0.1.0.0/docs/Auth-Biscuit.html+[biscuitexample]: https://github.com/eclipse-biscuit/biscuit-haskell/blob/main/biscuit/src/Auth/Biscuit/Example.hs+[packagedoc]: https://hackage.haskell.org/package/biscuit-haskell-0.4.0.0/docs/Auth-Biscuit.html
@@ -1,7 +1,7 @@-cabal-version: 2.0+cabal-version: 3.0 name: biscuit-haskell-version: 0.4.0.1+version: 0.5.0.0 category: Security synopsis: Library support for the Biscuit security token description: Please see the README on GitHub at <https://github.com/biscuit-auth/biscuit-haskell#readme>@@ -10,10 +10,10 @@ author: Clément Delafargue maintainer: clement@delafargue.name copyright: 2021 Clément Delafargue-license: BSD3+license: BSD-3-Clause license-file: LICENSE build-type: Simple-tested-with: GHC ==9.0.2 || ==9.2.4 || ==9.6.5 || ==9.8.2+tested-with: GHC ==9.8.4 || ==9.10.3 || ==9.12.4 || ==9.14.1 extra-source-files: README.md ChangeLog.md@@ -50,29 +50,30 @@ base >= 4.7 && <5, async ^>= 2.2, base16 >= 0.3 && <2,- bytestring >= 0.10 && <0.12,+ bytestring >= 0.10 && <0.13, text >= 1.2 && <3,- containers ^>= 0.6,- cryptonite >= 0.27 && < 0.31,+ containers >= 0.6 && <0.9,+ crypton ^>= 1.0, memory >= 0.15 && < 0.19,- template-haskell >= 2.16 && < 2.22,- base64 ^>= 0.4,+ template-haskell >= 2.16 && < 2.25,+ base64 ^>= 1.0, cereal ^>= 0.5, mtl >= 2.2 && < 2.4, parser-combinators >= 1.2 && < 1.4, protobuf ^>= 0.2,- random >= 1.0 && < 1.3,+ random >= 1.0 && < 1.4, regex-tdfa ^>= 1.3, th-lift-instances ^>= 0.1,- time ^>= 1.9,+ time >= 1.9 && < 2, validation-selective >= 0.1 && < 0.3,- megaparsec >= 9.2 && < 9.7+ megaparsec >= 9.2 && < 9.8 default-language: Haskell2010 test-suite biscuit-haskell-test type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ Spec.AST Spec.NewCrypto Spec.Executor Spec.Parser@@ -91,13 +92,12 @@ async , aeson , base >=4.7 && <5- , base16 >=0.3 && <2+ , base16 >=0.3 && <2.0 , base64 , biscuit-haskell , bytestring , cereal , containers- , cryptonite , lens , lens-aeson , megaparsec
@@ -3,7 +3,7 @@ {-| Module : Auth.Biscuit Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name Haskell implementation for the Biscuit token. -}@@ -12,14 +12,14 @@ -- * The biscuit auth token -- $biscuitOverview - -- * Creating keypairs+ -- * Creating key pairs -- $keypairs newSecret , toPublic , SecretKey , PublicKey - -- ** Parsing and serializing keypairs+ -- ** Parsing and serializing key pairs , serializeSecretKeyHex , serializePublicKeyHex , parseSecretKeyHex@@ -83,6 +83,9 @@ , authorizeBiscuitWithLimits , Limits (..) , defaultLimits+ , setExternFuncs+ , withExternFunc+ , withExternFuncs , ParseError (..) , ExecutionError (..) , AuthorizedBiscuit (..)@@ -106,6 +109,7 @@ import Control.Monad ((<=<)) import Control.Monad.Identity (runIdentity)+import qualified Data.Base64.Types as B64 (extractBase64) import Data.Bifunctor (first) import Data.ByteString (ByteString) import qualified Data.ByteString.Base64.URL as B64@@ -127,7 +131,10 @@ 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,@@ -158,7 +165,7 @@ queryAuthorizerFacts, queryRawBiscuitFacts, seal, serializeBiscuit)-import Auth.Biscuit.Utils (decodeHex, encodeHex')+import Auth.Biscuit.Utils (decodeHex, encodeHex') import qualified Data.Text as Text @@ -178,12 +185,12 @@ -- -- > -- Biscuit Open Verified means the token has valid signatures -- > -- and is open to further restriction--- > buildToken :: Keypair -> IO (Biscuit Open Verified)--- > buildToken keypair =+-- > buildToken :: SecretKey -> IO (Biscuit Open Verified)+-- > buildToken secret = -- > -- the logic language has its own syntax, which can be typed directly in haskell -- > -- source code thanks to QuasiQuotes. The datalog snippets are parsed at compile -- > -- time, so a datalog error results in a compilation error, not a runtime error--- > mkBiscuit keypair [block|+-- > mkBiscuit secret [block| -- > // the two first lines describe facts: -- > // the token holder is identified as `user_1234` -- > user("user_1234");@@ -256,8 +263,8 @@ -- Biscuits rely on public key cryptography: biscuits are signed with a secret key only known -- to the party which emits it. Verifying a biscuit, on the other hand, can be done with a -- public key that can be widely distributed. A private key and its corresponding public key--- is called a keypair, but since a public key can be deterministically computed from a--- private key, owning a private key is the same as owning a keypair.+-- is called a key pair, but since a public key can be deterministically computed from a+-- private key, owning a private key is the same as owning a key pair. -- | Generate a new random 'SecretKey' newSecret :: IO SecretKey@@ -351,28 +358,28 @@ -- | Serialize a biscuit to URL-compatible base 64, as recommended by the spec serializeB64 :: BiscuitProof p => Biscuit p Verified -> ByteString-serializeB64 = B64.encodeBase64' . serialize+serializeB64 = B64.extractBase64. B64.encodeBase64' . serialize -- | Generate a base64-encoded third-party block request. It can be used in -- conjunction with 'mkThirdPartyBlockB64' to generate a base64-encoded -- third-party block, which can be then appended to a token with -- 'applyThirdPartyBlockB64'. mkThirdPartyBlockReqB64 :: Biscuit Open c -> ByteString-mkThirdPartyBlockReqB64 = B64.encodeBase64' . mkThirdPartyBlockReq+mkThirdPartyBlockReqB64 = B64.extractBase64 . B64.encodeBase64' . mkThirdPartyBlockReq -- | Given a base64-encoded third-party block request, generate a base64-encoded -- third-party block, which can be then appended to a token with -- 'applyThirdPartyBlockB64'. mkThirdPartyBlockB64 :: SecretKey -> ByteString -> Block -> Either String ByteString mkThirdPartyBlockB64 sk reqB64 b = do- req <- first unpack $ B64.decodeBase64 reqB64+ req <- first unpack $ B64.decodeBase64Untyped reqB64 contents <- mkThirdPartyBlock sk req b- pure $ B64.encodeBase64' contents+ pure . B64.extractBase64 $ B64.encodeBase64' contents -- | Given a base64-encoded third-party block, append it to a token. applyThirdPartyBlockB64 :: Biscuit Open check -> ByteString -> Either String (IO (Biscuit Open check)) applyThirdPartyBlockB64 b contentsB64 = do- contents <- first unpack $ B64.decodeBase64 contentsB64+ contents <- first unpack $ B64.decodeBase64Untyped contentsB64 applyThirdPartyBlock b contents -- $biscuitBlocks@@ -398,11 +405,11 @@ -- they carry are not visible outside themselves, only their checks are evaluated. -- -- Third-party blocks lift this limitation by carrying an extra signature, crafted with a--- dedicated keypair. This way, the token authorizer (as well as blocks themselves) can--- opt-in to trust facts coming from third-party blocks signed with specific keypairs.+-- dedicated key pair. This way, the token authorizer (as well as blocks themselves) can+-- opt-in to trust facts coming from third-party blocks signed with specific key pairs. -- -- For instance, adding `check if group("admin") trusting {publicKey};` to a token will--- make it usable only if it carries a third party-block signed by the corresponding keypair,+-- make it usable only if it carries a third party-block signed by the corresponding key pair, -- and carrying a `group("admin")` fact. -- -- Since it is not desirable to share the token with the external entity providing the third-party
@@ -1,21 +1,31 @@ {-# 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+ License : BSD-3-Clause+ Maintainer : clement@delafargue.name+ Cryptographic helpers for biscuit signatures+-} module Auth.Biscuit.Crypto ( SignedBlock , Blocks- , signBlock+ , signAuthority+ , signAttenuationBlock , signExternalBlock- , sign3rdPartyBlock+ , sign3rdPartyBlockV1 , verifyBlocks , verifySecretProof , verifySignatureProof , getSignatureProof- , verifyExternalSig+ , verifyExternalSigV1 , PublicKey , pkBytes , readEd25519PublicKey@@ -38,8 +48,7 @@ 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 @@ -103,9 +112,22 @@ 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@@ -121,97 +143,195 @@ 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)+ pure ((payload, sig, nextPk, eSig, Nothing), 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)+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) -sign3rdPartyBlock :: SecretKey- -> PublicKey- -> ByteString- -> (Signature, PublicKey)-sign3rdPartyBlock eSk nextPk payload =+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)
@@ -20,7 +20,7 @@ {-| Module : Auth.Biscuit.Datalog.AST Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name The Datalog elements -}@@ -64,6 +64,9 @@ , RuleScope , EvalRuleScope , SetType+ , ArrayType+ , MapType+ , MapKey (..) , Slice (..) , PkOrSlice (..) , SliceType@@ -89,12 +92,17 @@ , queryHasNoV4Operators , ruleHasNoScope , ruleHasNoV4Operators+ , ruleHasNoV6Values+ , predicateHasNoV6Values+ , checkHasNoV6Values , isCheckOne+ , isReject , renderBlock , renderAuthorizer , renderFact , renderRule , valueToSetTerm+ , setValueToValue , toStack , substituteAuthorizer , substituteBlock@@ -187,6 +195,19 @@ 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 @@ | 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 @@ 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,10 +361,27 @@ 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+ valueToTerm :: Value -> Term valueToTerm = \case LInteger i -> LInteger i@@ -334,15 +389,20 @@ 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 @@ 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 @@ Just vs -> Failure vs -data CheckKind = One | All+data CheckKind = CheckOne | CheckAll | Reject deriving (Eq, Show, Ord, Lift) data Check' evalCtx ctx = Check@@ -473,8 +577,11 @@ 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) type Policy' evalCtx ctx = (PolicyType, Query' evalCtx ctx)@@ -516,10 +623,11 @@ 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{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 @@ 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 @@ Negate | Parens | Length+ | TypeOf+ | UnaryFfi Text deriving (Eq, Ord, Show, Lift) data Binary =@@ -680,12 +825,22 @@ | 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 @@ 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 @@ 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 @@ <> "." <> 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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
@@ -10,7 +10,7 @@ {-| Module : Auth.Biscuit.Datalog.Executor Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name The Datalog engine, tasked with deriving new facts from existing facts and rules, as well as matching available facts against checks and policies -}@@ -20,6 +20,8 @@ , ResultError (..) , Bindings , Name+ , ExternFuncs+ , ExternFunc (..) , MatchedQuery (..) , Scoped , FactGroup (..)@@ -28,6 +30,9 @@ , fromScopedFacts , keepAuthorized' , defaultLimits+ , setExternFuncs+ , withExternFunc+ , withExternFuncs , evaluateExpression -- , getFactsForRule@@ -44,11 +49,12 @@ 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 @@ -- | 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 _ = "<extern func>"++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 @@ , 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 @@ -- - 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,14 +222,26 @@ 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 bindings <- fold . fold <$> traverse (isQueryItemSatisfied l blockCount blockCount facts) query@@ -274,7 +317,10 @@ 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 (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 @@ 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 _ _ = 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 _ _ = 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 && i >= 0+ 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 @@ 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 @@ -> 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"
@@ -9,6 +9,13 @@ {-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-}+{-|+ Module : Auth.Biscuit.Datalog.AST+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+ Maintainer : clement@delafargue.name+ Parser for the authorization language+-} module Auth.Biscuit.Datalog.Parser where @@ -28,6 +35,7 @@ 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@@ -51,7 +59,7 @@ data SemanticError = VarInFact Span- | VarInSet Span+ | VarInCollection Span | NestedSet Span | InvalidBs Text Span | InvalidPublicKey Text Span@@ -62,7 +70,7 @@ 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@@ -93,9 +101,12 @@ (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@@ -107,37 +118,92 @@ 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"@@ -226,6 +292,13 @@ 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@@ -235,9 +308,12 @@ 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@@ -254,11 +330,13 @@ , 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)@@ -271,16 +349,30 @@ , 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 @@ -355,8 +447,9 @@ 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{..}
@@ -8,6 +8,13 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-}+{-|+ Module : Auth.Biscuit.Datalog.Executor+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+ Maintainer : clement@delafargue.name+ Wrapper for the executor engine that makes sure facts are properly scoped+-} module Auth.Biscuit.Datalog.ScopedExecutor ( BlockWithRevocationId , runAuthorizer
@@ -1,5 +1,12 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-|+ Module : Auth.Biscuit.Example+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+ Maintainer : clement@delafargue.name+ Working examples of biscuit-haskell+-} module Auth.Biscuit.Example where import Data.ByteString (ByteString)@@ -23,7 +30,7 @@ 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;|]
@@ -6,7 +6,7 @@ {-| Module : Auth.Biscuit.Proto Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name Haskell data structures mapping the biscuit protobuf definitions -}@@ -21,21 +21,25 @@ , Block (..) , Scope (..) , ScopeType (..)- , FactV2 (..)- , RuleV2 (..)+ , Fact (..)+ , Rule (..) , CheckKind (..)- , CheckV2 (..)- , PredicateV2 (..)- , TermV2 (..)- , ExpressionV2 (..)+ , Check (..)+ , Predicate (..)+ , Term (..)+ , Expression (..) , TermSet (..)+ , TermArray (..)+ , TermMap (..)+ , MapKey (..)+ , MapEntry (..)+ , Empty (..) , Op (..) , OpUnary (..) , UnaryKind (..) , OpBinary (..) , BinaryKind (..)- , OpTernary (..)- , TernaryKind (..)+ , OpClosure (..) , ThirdPartyBlockContents (..) , ThirdPartyBlockRequest (..) , getField@@ -83,6 +87,7 @@ , 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)@@ -98,14 +103,14 @@ deriving anyclass (Decode, Encode) data Block = Block {- symbols :: Repeated 1 (Value Text)- , context :: Optional 2 (Value Text)- , version :: Optional 3 (Value Int32)- , facts_v2 :: Repeated 4 (Message FactV2)- , rules_v2 :: Repeated 5 (Message RuleV2)- , checks_v2 :: Repeated 6 (Message CheckV2)- , scope :: Repeated 7 (Message Scope)- , pksTable :: Repeated 8 (Message PublicKey)+ symbols :: Repeated 1 (Value Text)+ , context :: Optional 2 (Value Text)+ , version :: Optional 3 (Value Int32)+ , facts :: Repeated 4 (Message Fact)+ , rules :: Repeated 5 (Message Rule)+ , checks :: Repeated 6 (Message Check)+ , scope :: Repeated 7 (Message Scope)+ , pksTable :: Repeated 8 (Message PublicKey) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -120,70 +125,102 @@ deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -newtype FactV2 = FactV2- { predicate :: Required 1 (Message PredicateV2)+newtype Fact = Fact+ { predicate :: Required 1 (Message Predicate) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data RuleV2 = RuleV2- { head :: Required 1 (Message PredicateV2)- , body :: Repeated 2 (Message PredicateV2)- , expressions :: Repeated 3 (Message ExpressionV2)+data Rule = Rule+ { head :: Required 1 (Message Predicate)+ , body :: Repeated 2 (Message Predicate)+ , expressions :: Repeated 3 (Message Expression) , scope :: Repeated 4 (Message Scope) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) data CheckKind =- One- | All+ CheckOne+ | CheckAll+ | Reject deriving stock (Show, Enum, Bounded) -data CheckV2 = CheckV2- { queries :: Repeated 1 (Message RuleV2)+data Check = Check+ { queries :: Repeated 1 (Message Rule) , kind :: Optional 2 (Enumeration CheckKind) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data PredicateV2 = PredicateV2+data Predicate = Predicate { name :: Required 1 (Value Int64)- , terms :: Repeated 2 (Message TermV2)+ , terms :: Repeated 2 (Message Term) } deriving stock (Generic, Show) 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))+data Term =+ 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) + newtype TermSet = TermSet- { set :: Repeated 1 (Message TermV2)+ { set :: Repeated 1 (Message Term) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -newtype ExpressionV2 = ExpressionV2+newtype TermArray = TermArray+ { array :: Repeated 1 (Message Term)+ } 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 Term)+ } 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 Expression = Expression { ops :: Repeated 1 (Message Op) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) data Op =- OpVValue (Required 1 (Message TermV2))+ OpVValue (Required 1 (Message Term)) | 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 @@ | BitwiseOr | BitwiseXor | NotEqual+ | HeterogeneousEqual+ | HeterogeneousNotEqual+ | LazyAnd+ | LazyOr+ | All+ | Any+ | Get+ | BinaryFfi+ | TryOr 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 @@ 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)
@@ -8,7 +8,7 @@ {-| Module : Auth.Biscuit.Utils Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name Conversion functions between biscuit components and protobuf-encoded components -}@@ -28,9 +28,11 @@ 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 @@ 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 @@ } 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)@@ -125,11 +130,17 @@ bVersion = PB.getField version lift $ do let s = symbolsForCurrentBlock- bFacts <- traverse (pbToFact s) $ PB.getField facts_v2- bRules <- traverse (pbToRule s) $ PB.getField rules_v2- bChecks <- traverse (pbToCheck s) $ PB.getField checks_v2+ bFacts <- traverse (pbToFact s) $ PB.getField facts+ bRules <- traverse (pbToRule s) $ PB.getField rules+ bChecks <- traverse (pbToCheck s) $ PB.getField checks 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 @@ , 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,38 +182,45 @@ , 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- context = PB.putField bContext- facts_v2 = PB.putField $ factToPb s <$> bFacts- rules_v2 = PB.putField $ ruleToPb s <$> bRules- 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 {..})+ symbols = PB.putField $ getSymbolList bSymbols+ context = PB.putField bContext+ facts = PB.putField $ factToPb s <$> bFacts+ rules = PB.putField $ ruleToPb s <$> bRules+ checks = PB.putField $ checkToPb s <$> bChecks+ scope = PB.putField $ scopeToPb s <$> Set.toList bScope+ pksTable = PB.putField $ publicKeyToPb <$> getPkList bSymbols+ 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+pbToFact :: Symbols -> PB.Fact -> Either String Fact+pbToFact s PB.Fact{predicate} = do let pbName = PB.getField $ PB.name $ PB.getField predicate pbTerms = PB.getField $ PB.terms $ PB.getField predicate name <- getSymbol s $ SymbolRef pbName terms <- traverse (pbToValue s) pbTerms pure Predicate{..} -factToPb :: ReverseSymbols -> Fact -> PB.FactV2+factToPb :: ReverseSymbols -> Fact -> PB.Fact factToPb s Predicate{..} = let- predicate = PB.PredicateV2+ predicate = PB.Predicate { name = PB.putField $ getSymbolRef $ getSymbolCode s name , terms = PB.putField $ valueToPb s <$> terms }- in PB.FactV2{predicate = PB.putField predicate}+ in PB.Fact{predicate = PB.putField predicate} -pbToRule :: Symbols -> PB.RuleV2 -> Either String Rule+pbToRule :: Symbols -> PB.Rule -> Either String Rule pbToRule s pbRule = do let pbHead = PB.getField $ PB.head pbRule pbBody = PB.getField $ PB.body pbRule@@ -209,27 +234,28 @@ Failure vs -> Left $ "Unbound variables in rule: " <> T.unpack (T.intercalate ", " $ NE.toList vs) Success r -> pure r -ruleToPb :: ReverseSymbols -> Rule -> PB.RuleV2+ruleToPb :: ReverseSymbols -> Rule -> PB.Rule ruleToPb s Rule{..} =- PB.RuleV2+ PB.Rule { head = PB.putField $ predicateToPb s rhead , body = PB.putField $ predicateToPb s <$> body , expressions = PB.putField $ expressionToPb s <$> expressions , scope = PB.putField $ scopeToPb s <$> Set.toList scope } -pbToCheck :: Symbols -> PB.CheckV2 -> Either String Check-pbToCheck s PB.CheckV2{queries,kind} = do+pbToCheck :: Symbols -> PB.Check -> Either String Check+pbToCheck s PB.Check{queries,kind} = do let toCheck Rule{body,expressions,scope} = QueryItem{qBody = body, qExpressions = expressions, qScope = scope} 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+checkToPb :: ReverseSymbols -> Check -> PB.Check checkToPb s Check{..} = let dummyHead = Predicate "query" [] toQuery QueryItem{..} =@@ -239,9 +265,10 @@ , scope = qScope } pbKind = case cKind of- One -> Nothing- All -> Just PB.All- in PB.CheckV2 { queries = PB.putField $ toQuery <$> cQueries+ CheckOne -> Nothing+ CheckAll -> Just PB.CheckAll+ Reject -> Just PB.Reject+ in PB.Check { queries = PB.putField $ toQuery <$> cQueries , kind = PB.putField pbKind } @@ -259,7 +286,7 @@ Previous -> PB.ScType $ PB.putField PB.ScopePrevious BlockId pk -> PB.ScBlock $ PB.putField $ getPublicKeyCode s pk -pbToPredicate :: Symbols -> PB.PredicateV2 -> Either String (Predicate' 'InPredicate 'Representation)+pbToPredicate :: Symbols -> PB.Predicate -> Either String (Predicate' 'InPredicate 'Representation) pbToPredicate s pbPredicate = do let pbName = PB.getField $ PB.name pbPredicate pbTerms = PB.getField $ PB.terms pbPredicate@@ -267,9 +294,9 @@ terms <- traverse (pbToTerm s) pbTerms pure Predicate{..} -predicateToPb :: ReverseSymbols -> Predicate -> PB.PredicateV2+predicateToPb :: ReverseSymbols -> Predicate -> PB.Predicate predicateToPb s Predicate{..} =- PB.PredicateV2+ PB.Predicate { name = PB.putField $ getSymbolRef $ getSymbolCode s name , terms = PB.putField $ termToPb s <$> terms }@@ -277,7 +304,7 @@ pbTimeToUtcTime :: Int64 -> UTCTime pbTimeToUtcTime = posixSecondsToUTCTime . fromIntegral -pbToTerm :: Symbols -> PB.TermV2 -> Either String Term+pbToTerm :: Symbols -> PB.Term -> Either String Term pbToTerm s = \case PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f PB.TermString f -> LString <$> getSymbol s (SymbolRef $ PB.getField f)@@ -286,8 +313,11 @@ 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 :: ReverseSymbols -> Term -> PB.Term termToPb s = \case Variable n -> PB.TermVariable $ PB.putField $ getSymbolRef $ getSymbolCode s n LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v@@ -296,10 +326,29 @@ 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 -pbToValue :: Symbols -> PB.TermV2 -> Either String Value+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.Term -> Either String Value pbToValue s = \case PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f PB.TermString f -> LString <$> getSymbol s (SymbolRef $ PB.getField f)@@ -308,8 +357,11 @@ 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 :: ReverseSymbols -> Value -> PB.Term valueToPb s = \case LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v LString v -> PB.TermString $ PB.putField $ getSymbolRef $ getSymbolCode s v@@ -317,123 +369,191 @@ 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 -pbToSetValue :: Symbols -> PB.TermV2 -> Either String (Term' 'WithinSet 'InFact 'Representation)+pbToSetValue :: Symbols -> PB.Term -> Either String (Term' 'WithinSet 'InFact 'Representation) pbToSetValue s = \case PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f PB.TermString f -> LString <$> getSymbol s (SymbolRef $ PB.getField f) 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 :: ReverseSymbols -> Term' 'WithinSet 'InFact 'Representation -> PB.Term setValueToPb s = \case LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v LString v -> PB.TermString $ PB.putField $ getSymbolRef $ getSymbolCode s v 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 -pbToExpression :: Symbols -> PB.ExpressionV2 -> Either String Expression-pbToExpression s PB.ExpressionV2{ops} = do+pbToExpression :: Symbols -> PB.Expression -> Either String Expression+pbToExpression s PB.Expression{ops} = do parsedOps <- traverse (pbToOp s) $ PB.getField ops fromStack parsedOps -expressionToPb :: ReverseSymbols -> Expression -> PB.ExpressionV2+expressionToPb :: ReverseSymbols -> Expression -> PB.Expression expressionToPb s e = let ops = opToPb s <$> toStack e- in PB.ExpressionV2 { ops = PB.putField ops }+ in PB.Expression { ops = PB.putField ops } 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+ 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 :: PB.OpUnary -> Unary-pbToUnary PB.OpUnary{kind} = case PB.getField kind of- PB.Negate -> Negate- PB.Parens -> Parens- PB.Length -> Length+pbToUnary :: Symbols -> PB.OpUnary -> Either String Unary+pbToUnary s PB.OpUnary{kind,ffiName} =+ let noFfi = case PB.getField ffiName of+ Just _ -> const $ Left "FFI named set on a regular operation "+ Nothing -> Right+ in case PB.getField kind of+ PB.Negate -> noFfi Negate+ PB.Parens -> noFfi Parens+ PB.Length -> noFfi Length+ PB.TypeOf -> noFfi TypeOf+ PB.UnaryFfi -> do+ nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName+ name' <- getSymbol s $ SymbolRef nameIdx+ pure $ UnaryFfi name' -unaryToPb :: Unary -> PB.OpUnary-unaryToPb = PB.OpUnary . PB.putField . \case- Negate -> PB.Negate- Parens -> PB.Parens- Length -> PB.Length+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 :: 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+pbToBinary :: Symbols -> PB.OpBinary -> Either String Binary+pbToBinary s PB.OpBinary{kind, ffiName} =+ let noFfi = case PB.getField ffiName of+ Just _ -> const $ Left "FFI named set on a regular operation "+ Nothing -> Right+ in case PB.getField kind of+ PB.LessThan -> noFfi LessThan+ PB.GreaterThan -> noFfi GreaterThan+ PB.LessOrEqual -> noFfi LessOrEqual+ PB.GreaterOrEqual -> noFfi GreaterOrEqual+ PB.Equal -> noFfi Equal+ PB.Contains -> noFfi Contains+ PB.Prefix -> noFfi Prefix+ PB.Suffix -> noFfi Suffix+ PB.Regex -> noFfi Regex+ PB.Add -> noFfi Add+ PB.Sub -> noFfi Sub+ PB.Mul -> noFfi Mul+ PB.Div -> noFfi Div+ PB.And -> noFfi And+ PB.Or -> noFfi Or+ PB.Intersection -> noFfi Intersection+ PB.Union -> noFfi Union+ PB.BitwiseAnd -> noFfi BitwiseAnd+ PB.BitwiseOr -> noFfi BitwiseOr+ PB.BitwiseXor -> noFfi BitwiseXor+ PB.NotEqual -> noFfi NotEqual+ PB.HeterogeneousEqual -> noFfi HeterogeneousEqual+ PB.HeterogeneousNotEqual -> noFfi HeterogeneousNotEqual+ PB.LazyAnd -> noFfi LazyAnd+ PB.LazyOr -> noFfi LazyOr+ PB.All -> noFfi All+ PB.Any -> noFfi Any+ PB.Get -> noFfi Get+ PB.TryOr -> noFfi Try+ PB.BinaryFfi -> do+ nameIdx <- maybeToRight "Missing extern call name" $ PB.getField ffiName+ name' <- getSymbol s $ SymbolRef nameIdx+ pure $ BinaryFfi name' -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+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) -pbToThirdPartyBlockRequest :: PB.ThirdPartyBlockRequest -> Either String Crypto.PublicKey-pbToThirdPartyBlockRequest PB.ThirdPartyBlockRequest{previousPk, pkTable} = do+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.TryOr, 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)
@@ -3,6 +3,13 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-}+{-|+ Module : Auth.Biscuit.Symbols+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+ Maintainer : clement@delafargue.name+ Symbol management logic for biscuit serialization+-} module Auth.Biscuit.Symbols ( Symbols , BlockSymbols
@@ -1,7 +1,7 @@ {-| Module : Auth.Biscuit.Timer Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name Helper function making sure an IO action runs in an alloted time -}
@@ -8,7 +8,7 @@ {-| Module : Auth.Biscuit.Token Copyright : © Clément Delafargue, 2021- License : MIT+ License : BSD-3-Clause Maintainer : clement@delafargue.name Module defining the main biscuit-related operations -}@@ -75,12 +75,13 @@ 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 @@ -- 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 @@ -> 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 @@ _ -> 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 @@ -- 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 @@ -> 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 @@ -> 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 @@ -> 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 @@ -- '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 @@ 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 @@ 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 @@ } 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 @@ -> 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 @@ 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 @@ -> 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@@ -536,7 +538,7 @@ parseBiscuitWith ParserConfig{..} bs = let input = case encoding of RawBytes -> Right bs- UrlBase64 -> first (const InvalidB64Encoding) . B64.decodeBase64 $ bs+ UrlBase64 -> first (const InvalidB64Encoding) . B64.decodeBase64Untyped $ bs parsedWrapper = parseBiscuitWrapper =<< input wrapperToBiscuit w@BiscuitWrapper{wRootKeyId} = let pk = getPublicKey wRootKeyId@@ -550,13 +552,13 @@ 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
@@ -3,7 +3,7 @@ -- | -- Module : Auth.Biscuit.Utils -- Copyright : © Clément Delafargue, 2021--- License : MIT+-- License : BSD-3-Clause -- Maintainer : clement@delafargue.name module Auth.Biscuit.Utils ( maybeToRight,
@@ -1,7 +1,12 @@+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} 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@@ -15,8 +20,8 @@ main = do sampleReader <- SampleReader.getSpecs defaultMain $ testGroup "biscuit-haskell"- [- NewCrypto.specs+ [ AST.specs+ , NewCrypto.specs , Executor.specs , Parser.specs , Quasiquoter.specs
@@ -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
@@ -1,6 +1,10 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.Executor (specs) where import Data.Map.Strict as Map@@ -96,7 +100,7 @@ -- ("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)@@ -105,22 +109,22 @@ , ("\"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)@@ -158,14 +162,15 @@ , ("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@@ -182,6 +187,7 @@ , ("\"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@@ -302,11 +308,11 @@ 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")
@@ -3,6 +3,10 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {- HLINT ignore "Reduce duplication" -}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.NewCrypto (specs) where import Data.ByteString (ByteString)@@ -29,7 +33,7 @@ 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@@ -40,7 +44,8 @@ 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@@ -48,8 +53,8 @@ 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@@ -144,7 +149,7 @@ 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@@ -164,7 +169,7 @@ 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@@ -176,7 +181,7 @@ 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@@ -224,7 +229,7 @@ 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@@ -236,7 +241,7 @@ 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
@@ -2,6 +2,10 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.Parser (specs, parseExpression, parseBlock, parseAuthorizer) where import Control.Monad ((<=<))@@ -67,6 +71,7 @@ specs :: TestTree specs = testGroup "datalog parser" [ factWithDate+ , factWithNull , simpleFact , oneLetterFact , simpleRule@@ -109,7 +114,7 @@ [ 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") ]@@ -134,6 +139,11 @@ 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\")" @?=@@ -207,17 +217,29 @@ (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@@ -225,13 +247,13 @@ (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"))@@ -255,33 +277,38 @@ (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@@ -293,7 +320,7 @@ (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@@ -303,6 +330,41 @@ (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 ] @@ -319,18 +381,20 @@ ) , 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" @?=@@ -406,18 +470,24 @@ 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"]]@@ -427,12 +497,12 @@ [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"]]@@ -442,12 +512,27 @@ [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [] ]- , cKind = All+ , 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"]]+ [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]+ []+ , QueryItem [Predicate "other" [Variable "var"]]+ [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))]+ []+ ]+ , 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"]]@@ -457,12 +542,12 @@ [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"]]@@ -472,7 +557,7 @@ [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] [OnlyAuthority] ]- , cKind = All+ , cKind = CheckAll } ] @@ -486,8 +571,8 @@ 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"]]@@ -500,8 +585,8 @@ ) , 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"]]@@ -515,8 +600,8 @@ , 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"]]@@ -529,8 +614,8 @@ ) , 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"]]@@ -592,7 +677,7 @@ \ 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\@@ -681,7 +766,7 @@ \ 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"
@@ -1,6 +1,10 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.Quasiquoter (specs) where import Data.Text (Text)
@@ -3,6 +3,10 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.Roundtrip ( specs ) where@@ -58,7 +62,7 @@ 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)@@ -87,7 +91,7 @@ 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)@@ -109,7 +113,7 @@ |] :| [ [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|@@ -123,25 +127,25 @@ 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
@@ -10,6 +10,10 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.SampleReader where import Control.Arrow ((&&&))@@ -39,6 +43,7 @@ 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)@@ -48,7 +53,7 @@ import Spec.Parser (parseAuthorizer, parseBlock) getB :: ParsedSignedBlock -> Block-getB ((_, b), _, _, _) = b+getB ((_, b), _, _, _, _) = b getAuthority :: Biscuit p Verified -> Block getAuthority = getB . authority@@ -118,7 +123,7 @@ = 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)@@ -147,8 +152,11 @@ 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)@@ -220,11 +228,10 @@ 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@@ -258,6 +265,7 @@ mustMatchEither [ key "Format" . key "Signature" . key "InvalidSignature" , key "Format" . key "InvalidSignatureSize"+ , key "Format" . key "BlockSignatureDeserializationError" ] InvalidProof -> assertFailure $ "InvalidProof can't appear here " <> show re@@ -295,7 +303,15 @@ 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"@@ -318,29 +334,3 @@ 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{..}
@@ -1,14 +1,20 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {- HLINT ignore "Reduce duplication" -}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.ScopedExecutor (specs) where import Control.Arrow ((&&&))+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar) import Data.Either (isRight) import Data.Map.Strict as Map import Data.Set as Set import Data.Text (Text, unpack)-import Test.Tasty+import System.IO.Unsafe (unsafePerformIO)+import Test.Tasty hiding (Timeout) import Test.Tasty.HUnit import Auth.Biscuit (addBlock, addSignedBlock,@@ -21,7 +27,8 @@ import Auth.Biscuit.Datalog.Executor (ExecutionError (..), Limits (..), ResultError (..),- defaultLimits)+ defaultLimits,+ withExternFunc) import Auth.Biscuit.Datalog.Parser (authorizer, block, check, query, run) import Auth.Biscuit.Datalog.ScopedExecutor@@ -36,6 +43,7 @@ , thirdPartyBlocks , iterationCountWorks , maxFactsCountWorks+ , maxTimeWorks , allChecksAreCollected , revocationIdsAreInjected , authorizerFactsAreQueried@@ -219,6 +227,22 @@ allow if true; |] runAuthorizerNoTimeout limits (authority, "", Nothing) [(block1, "", Nothing)] verif @?= Left TooManyFacts++maxTimeWorks :: TestTree+maxTimeWorks = testCase "ScopedExecutions stops when hitting the timeout" $ do+ lateBoolVar <- newEmptyMVar+ let slowBool _ _ = unsafePerformIO $ Right <$> takeMVar lateBoolVar+ limits = withExternFunc "foo" slowBool $ defaultLimits { maxTime = 1 }+ authority =+ [block|+ |]+ verif =+ [authorizer|+ allow if 1.extern::foo();+ |]+ res <- runAuthorizerWithLimits limits (authority, "", Nothing) [] verif+ putMVar lateBoolVar $ LBool True+ res @?= Left Timeout allChecksAreCollected :: TestTree allChecksAreCollected = testCase "ScopedExecutions collects all facts results even after a failure" $ do
@@ -1,6 +1,10 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-}+{-+ Copyright : © Clément Delafargue, 2021+ License : BSD-3-Clause+-} module Spec.Verification ( specs ) where@@ -46,7 +50,7 @@ ifFalse' :: Check ifFalse' = Check { cQueries = matchedQuery ifFalse- , cKind = One+ , cKind = CheckOne } checkAll' :: Check
@@ -13,2175 +13,3295 @@ ], "public_keys": [], "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "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": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 1,- "check_id": 0,- "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",- "revocation_ids": [- "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",- "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"- ]- }- }- },- {- "title": "different root key",- "filename": "test002_different_root_key.bc",- "token": [- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "Format": {- "Signature": {- "InvalidSignature": "signature error: Verification equation was not satisfied"- }- }- }- },- "authorizer_code": "",- "revocation_ids": []- }- }- },- {- "title": "invalid signature format",- "filename": "test003_invalid_signature_format.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "Format": {- "InvalidSignatureSize": 16- }- }- },- "authorizer_code": "",- "revocation_ids": []- }- }- },- {- "title": "random block",- "filename": "test004_random_block.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "Format": {- "Signature": {- "InvalidSignature": "signature error: Verification equation was not satisfied"- }- }- }- },- "authorizer_code": "",- "revocation_ids": []- }- }- },- {- "title": "invalid signature",- "filename": "test005_invalid_signature.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "Format": {- "Signature": {- "InvalidSignature": "signature error: Verification equation was not satisfied"- }- }- }- },- "authorizer_code": "",- "revocation_ids": []- }- }- },- {- "title": "reordered blocks",- "filename": "test006_reordered_blocks.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- },- {- "symbols": [],- "public_keys": [],- "external_key": null,- "code": "check if resource(\"file1\");\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "Format": {- "Signature": {- "InvalidSignature": "signature error: Verification equation was not satisfied"- }- }- }- },- "authorizer_code": "",- "revocation_ids": []- }- }- },- {- "title": "scoped rules",- "filename": "test007_scoped_rules.bc",- "token": [- {- "symbols": [- "user_id",- "alice",- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n"- },- {- "symbols": [- "0",- "1"- ],- "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"- },- {- "symbols": [- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "owner(\"alice\", \"file2\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file2\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "owner(\"alice\", \"file1\")",- "user_id(\"alice\")"- ]- },- {- "origin": [- 2- ],- "facts": [- "owner(\"alice\", \"file2\")"- ]- }- ],- "rules": [- {- "origin": 1,- "rules": [- "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0)"- ]- }- ],- "checks": [- {- "origin": 1,- "checks": [- "check if resource($0), operation(\"read\"), right($0, \"read\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 1,- "check_id": 0,- "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",- "revocation_ids": [- "4d86c9af808dc2e0583f47282e6f5df3e09dc264d5231ec360b4519e15ddaeec60b25a9bbcb22e8d192f4d36a0da3f9243711e30535b00ee55c53cb1395f230a",- "63208c668c66f3ba6927140ba37533593b25e03459447805d4b2a8b75adeef45794c3d7249afe506ed77ccee276160bb4052a4009302bd34871a440f070b4509",- "d8da982888eae8c038e4894a8c06fc57d8e5f06ad2e972b9cf4bde49ad60804558a0d1938192596c702d8e4f7f12ec19201d7c33d0cd77774a0d879a33880d02"- ]- }- }- },- {- "title": "scoped checks",- "filename": "test008_scoped_checks.bc",- "token": [- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- },- {- "symbols": [- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file2\", \"read\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file2\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "right(\"file1\", \"read\")"- ]- },- {- "origin": [- 2- ],- "facts": [- "right(\"file2\", \"read\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 1,- "checks": [- "check if resource($0), operation(\"read\"), right($0, \"read\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 1,- "check_id": 0,- "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",- "revocation_ids": [- "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",- "77df45442be86a416aa02fd9d98d6d4703c634a9e3b1d293b41f5dc97849afbe7faeec8c22a210574888acc008fb64fe691ec9e8d2655586f970d9a6b6577000",- "b31398aefe97d3db41ebc445760f216fb3aa7bf7439adcfc3a07489bfcc163970af3f4e20f5460aa24cf841101a5ab114d21acc0ee8d442bae7793b121284900"- ]- }- }- },- {- "title": "expired token",- "filename": "test009_expired_token.bc",- "token": [- {- "symbols": [],- "public_keys": [],- "external_key": null,- "code": ""- },- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file1\")",- "time(2020-12-21T09:23:12Z)"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 1,- "checks": [- "check if resource(\"file1\")",- "check if time($time), $time <= 2018-12-20T00:00:00Z"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 1,- "check_id": 1,- "rule": "check if time($time), $time <= 2018-12-20T00:00:00Z"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file1\");\noperation(\"read\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",- "revocation_ids": [- "c248907bb6e5f433bbb5edf6367b399ebefca0d321d0b2ea9fc67f66dc1064ce926adb0c05d90c3e8a2833328b3578f79c4e1bca43583d9bcfb2ba6c37303d00",- "a4edf7aaea8658bb9ae19b3ffe2adcc77cc9f16c249aeb0a85a584b5362f89f27f7c67ac0af16d7170673d6d1fb1563d1934b25ec5a461f6c01fa49805cd5e07"- ]- }- }- },- {- "title": "authorizer scope",- "filename": "test010_authorizer_scope.bc",- "token": [- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\n"- },- {- "symbols": [- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file2\", \"read\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file2\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "right(\"file1\", \"read\")"- ]- },- {- "origin": [- 1- ],- "facts": [- "right(\"file2\", \"read\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 18446744073709551615,- "checks": [- "check if right($0, $1), resource($0), operation($1)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Authorizer": {- "check_id": 0,- "rule": "check if right($0, $1), resource($0), operation($1)"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",- "revocation_ids": [- "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",- "966eceb2aa937c41b25368808bab6e0698c02a4038de669d007c9c3d43602638a640083558d1576ac80cf3eb2ac6a7585527e0f6c1a65402f0935cf7f4df8005"- ]- }- }- },- {- "title": "authorizer authority checks",- "filename": "test011_authorizer_authority_caveats.bc",- "token": [- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file2\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "right(\"file1\", \"read\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 18446744073709551615,- "checks": [- "check if right($0, $1), resource($0), operation($1)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Authorizer": {- "check_id": 0,- "rule": "check if right($0, $1), resource($0), operation($1)"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",- "revocation_ids": [- "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00"- ]- }- }- },- {- "title": "authority checks",- "filename": "test012_authority_caveats.bc",- "token": [- {- "symbols": [- "file1"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource(\"file1\");\n"- }- ],- "validations": {- "file1": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file1\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if resource(\"file1\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",- "revocation_ids": [- "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"- ]- },- "file2": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"read\")",- "resource(\"file2\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if resource(\"file1\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 0,- "check_id": 0,- "rule": "check if resource(\"file1\")"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",- "revocation_ids": [- "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"- ]- }- }- },- {- "title": "block rules",- "filename": "test013_block_rules.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n"- },- {- "symbols": [- "valid_date",- "0",- "1"- ],- "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"- }- ],- "validations": {- "file1": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "resource(\"file1\")",- "time(2020-12-21T09:23:12Z)"- ]- },- {- "origin": [- null,- 1- ],- "facts": [- "valid_date(\"file1\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "right(\"file1\", \"read\")",- "right(\"file2\", \"read\")"- ]- }- ],- "rules": [- {- "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)"- ]- }- ],- "checks": [- {- "origin": 1,- "checks": [- "check if valid_date($0), resource($0)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "resource(\"file1\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",- "revocation_ids": [- "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",- "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"- ]- },- "file2": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "resource(\"file2\")",- "time(2020-12-21T09:23:12Z)"- ]- },- {- "origin": [- 0- ],- "facts": [- "right(\"file1\", \"read\")",- "right(\"file2\", \"read\")"- ]- }- ],- "rules": [- {- "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)"- ]- }- ],- "checks": [- {- "origin": 1,- "checks": [- "check if valid_date($0), resource($0)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 1,- "check_id": 0,- "rule": "check if valid_date($0), resource($0)"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file2\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",- "revocation_ids": [- "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",- "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"- ]- }- }- },- {- "title": "regex_constraint",- "filename": "test014_regex_constraint.bc",- "token": [- {- "symbols": [- "0",- "file[0-9]+.txt"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n"- }- ],- "validations": {- "file1": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "resource(\"file1\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if resource($0), $0.matches(\"file[0-9]+.txt\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 0,- "check_id": 0,- "rule": "check if resource($0), $0.matches(\"file[0-9]+.txt\")"- }- }- ]- }- }- }- },- "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",- "revocation_ids": [- "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"- ]- },- "file123": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "resource(\"file123.txt\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if resource($0), $0.matches(\"file[0-9]+.txt\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "resource(\"file123.txt\");\n\nallow if true;\n",- "revocation_ids": [- "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"- ]- }- }- },- {- "title": "multi queries checks",- "filename": "test015_multi_queries_caveats.bc",- "token": [- {- "symbols": [- "must_be_present",- "hello"- ],- "public_keys": [],- "external_key": null,- "code": "must_be_present(\"hello\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "must_be_present(\"hello\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 18446744073709551615,- "checks": [- "check if must_be_present($0) or must_be_present($0)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "check if must_be_present($0) or must_be_present($0);\n\nallow if true;\n",- "revocation_ids": [- "b0d466d31e015fa85a075fa875f7e1c9017edd503fee9f62a5f033e1fcfa811074b6e39dfe5af2f452043db97a3f98650592a370f5685b62c5d6abf9dd10b603"- ]- }- }- },- {- "title": "check head name should be independent from fact names",- "filename": "test016_caveat_head_name.bc",- "token": [- {- "symbols": [- "hello"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource(\"hello\");\n"- },- {- "symbols": [- "test"- ],- "public_keys": [],- "external_key": null,- "code": "query(\"test\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 1- ],- "facts": [- "query(\"test\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if resource(\"hello\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 0,- "check_id": 0,- "rule": "check if resource(\"hello\")"- }- }- ]- }- }- }- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "ce6f804f4390e693a8853d9a4a10bd4f3c94b86b7c6d671993a6e19346bc4d20bbb52cc945e5d0d02e4e75fa5da2caa99764050190353564a0a0b4b276809402",- "916d566cc724e0773046fc5266e9d0d804311435b8d6955b332f823ab296be9a78dfea190447732ac9f6217234cf5726becf88f65169c6de56a766af55451b0f"- ]- }- }- },- {- "title": "test expression syntax and all available operations",- "filename": "test017_expressions.bc",- "token": [- {- "symbols": [- "hello world",- "hello",- "world",- "aaabde",- "a*c?.e",- "abd",- "aaa",- "b",- "de",- "abcD12",- "é",- "abc",- "def"- ],- "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"- }- ],- "validations": {- "": {- "world": {- "facts": [],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if !false",- "check if !false && true",- "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 1 < 2",- "check if 1 <= 1",- "check if 1 <= 2",- "check if 2 > 1",- "check if 2 >= 1",- "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 > 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 true",- "check if true == true"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c"- ]- }- }- },- {- "title": "invalid block rule with unbound_variables",- "filename": "test018_unbound_variables_in_rule.bc",- "token": [- {- "symbols": [],- "public_keys": [],- "external_key": null,- "code": "check if operation(\"read\");\n"- },- {- "symbols": [- "unbound",- "any1",- "any2"- ],- "public_keys": [],- "external_key": null,- "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n"- }- ],- "validations": {- "": {- "world": null,- "result": {- "Err": {- "FailedLogic": {- "InvalidBlockRule": [- 0,- "operation($unbound, \"read\") <- operation($any1, $any2)"- ]- }- }- },- "authorizer_code": "",- "revocation_ids": [- "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",- "b0a33e3f4cd0994c0766c196c4d11c15e5a0f9bfba79a3a2b35ddd04ddb890282a7c63336ada5c680b9f9c940c1fa7127d2699754cbc77c21e1a2d85c5ef700c"- ]- }- }- },- {- "title": "invalid block rule generating an #authority or #ambient symbol with a variable",- "filename": "test019_generating_ambient_from_variables.bc",- "token": [- {- "symbols": [],- "public_keys": [],- "external_key": null,- "code": "check if operation(\"read\");\n"- },- {- "symbols": [- "any"- ],- "public_keys": [],- "external_key": null,- "code": "operation(\"read\") <- operation($any);\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"write\")"- ]- },- {- "origin": [- null,- 1- ],- "facts": [- "operation(\"read\")"- ]- }- ],- "rules": [- {- "origin": 1,- "rules": [- "operation(\"read\") <- operation($any)"- ]- }- ],- "checks": [- {- "origin": 0,- "checks": [- "check if operation(\"read\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 0,- "check_id": 0,- "rule": "check if operation(\"read\")"- }- }- ]- }- }- }- },- "authorizer_code": "operation(\"write\");\n\nallow if true;\n",- "revocation_ids": [- "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",- "d3f8822a9b9bc0ee3933283c493ca9e711be5dd8339b5fe2eba1de3805aad4e84d3e2fb4affb4a743f1289915c167582b9425343635e45b70573ea1ee7a1ea03"- ]- }- }- },- {- "title": "sealed token",- "filename": "test020_sealed.bc",- "token": [- {- "symbols": [- "file1",- "file2"- ],- "public_keys": [],- "external_key": null,- "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"- },- {- "symbols": [- "0"- ],- "public_keys": [],- "external_key": null,- "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"- }- ],- "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": [- "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",- "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"- ]- }- }- },- {- "title": "parsing",- "filename": "test021_parsing.bc",- "token": [- {- "symbols": [- "ns::fact_123",- "hello é\t😁"- ],- "public_keys": [],- "external_key": null,- "code": "ns::fact_123(\"hello é\t😁\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "ns::fact_123(\"hello é\t😁\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 18446744073709551615,- "checks": [- "check if ns::fact_123(\"hello é\t😁\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "check if ns::fact_123(\"hello é\t😁\");\n\nallow if true;\n",- "revocation_ids": [- "d4b2f417b6e906434fdf5058afcabfcb98d3628f814f1c9dd7e64250d9beec4465aff51bd0cb2e85d0e67dc9f613c2a42af6158c678bc6f8b4684cd3a2d0d302"- ]- }- }- },- {- "title": "default_symbols",- "filename": "test022_default_symbols.bc",- "token": [- {- "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"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "admin(13)",- "client(18)",- "client_ip(19)",- "cluster(23)",- "domain(20)",- "email(14)",- "group(15)",- "hostname(25)",- "ip_address(17)",- "member(16)",- "namespace(9)",- "node(24)",- "nonce(26)",- "operation(3)",- "owner(7)",- "path(21)",- "query(27)",- "read(0)",- "resource(2)",- "right(4)",- "role(6)",- "service(12)",- "team(11)",- "tenant(8)",- "time(5)",- "user(10)",- "version(22)",- "write(1)"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 18446744073709551615,- "checks": [- "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27);\n\nallow if true;\n",- "revocation_ids": [- "75ce48d496fd28f99905901783a1ba46d7ff8d69f9d364d1546fd73006026eae51849ad1190a4ae521a0a1269f9c6951e226afba8fcd24fa50f679162439ae09"- ]- }- }- },- {- "title": "execution scope",- "filename": "test023_execution_scope.bc",- "token": [- {- "symbols": [- "authority_fact"- ],- "public_keys": [],- "external_key": null,- "code": "authority_fact(1);\n"- },- {- "symbols": [- "block1_fact"- ],- "public_keys": [],- "external_key": null,- "code": "block1_fact(1);\n"- },- {- "symbols": [- "var"- ],- "public_keys": [],- "external_key": null,- "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "authority_fact(1)"- ]- },- {- "origin": [- 1- ],- "facts": [- "block1_fact(1)"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 2,- "checks": [- "check if authority_fact($var)",- "check if block1_fact($var)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 2,- "check_id": 1,- "rule": "check if block1_fact($var)"- }- }- ]- }- }- }- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "f9b49866caef5ece7be14ec5a9b36d98ca81d06b306eb0b4c57cd7436af176f40ee972f40903f87ec4460ab8b1adfcbfa9b19b20a6955a1e8dae7d88b2076005",- "889054b9119e4440e54da1b63266a98d0f6646cde195fef206efd8b133cfb2ee7be49b32a9a5925ece452e64f9e6f6d80dab422e916c599675dd68cdea053802",- "0a85ffbf27e08aa23665ba0d96a985b274d747556c9f016fd7f590c641ed0e4133291521aa442b320ee9ce80f5ad701b914a0c87b3dfa0cc92629dce94201806"- ]- }- }- },- {- "title": "third party",- "filename": "test024_third_party.bc",- "token": [- {- "symbols": [],- "public_keys": [- "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"- ],- "external_key": null,- "code": "right(\"read\");\ncheck if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"- },- {- "symbols": [],- "public_keys": [],- "external_key": "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",- "code": "group(\"admin\");\ncheck if right(\"read\");\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "right(\"read\")"- ]- },- {- "origin": [- 1- ],- "facts": [- "group(\"admin\")"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"- ]- },- {- "origin": 1,- "checks": [- "check if right(\"read\")"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03",- "342167bc54bc642b6718a276875e55b6d39e9b21e4ce13b926a3d398b6c057fc436385bf4c817a16f9ecdf0b0d950e8b8258a20aeb3fd8896c5e9c1f0a53da03"- ]- }- }- },- {- "title": "block rules",- "filename": "test025_check_all.bc",- "token": [- {- "symbols": [- "allowed_operations",- "A",- "B",- "op",- "allowed"- ],- "public_keys": [],- "external_key": null,- "code": "allowed_operations([\"A\", \"B\"]);\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n"- }- ],- "validations": {- "A, B": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"A\")",- "operation(\"B\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "allowed_operations([\"A\", \"B\"])"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "operation(\"A\");\noperation(\"B\");\n\nallow if true;\n",- "revocation_ids": [- "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"- ]- },- "A, invalid": {- "world": {- "facts": [- {- "origin": [- null- ],- "facts": [- "operation(\"A\")",- "operation(\"invalid\")"- ]- },- {- "origin": [- 0- ],- "facts": [- "allowed_operations([\"A\", \"B\"])"- ]- }- ],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "FailedLogic": {- "Unauthorized": {- "policy": {- "Allow": 0- },- "checks": [- {- "Block": {- "block_id": 0,- "check_id": 0,- "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"- }- }- ]- }- }- }- },- "authorizer_code": "operation(\"A\");\noperation(\"invalid\");\n\nallow if true;\n",- "revocation_ids": [- "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"- ]- }- }- },- {- "title": "public keys interning",- "filename": "test026_public_keys_interning.bc",- "token": [- {- "symbols": [],- "public_keys": [- "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"- ],- "external_key": null,- "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n"- },- {- "symbols": [],- "public_keys": [- "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",- "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"- },- {- "symbols": [],- "public_keys": [- "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",- "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"- },- {- "symbols": [],- "public_keys": [- "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",- "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"- },- {- "symbols": [],- "public_keys": [- "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",- "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"- ],- "external_key": null,- "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n"- }- ],- "validations": {- "": {- "world": {- "facts": [- {- "origin": [- 0- ],- "facts": [- "query(0)"- ]- },- {- "origin": [- 1- ],- "facts": [- "query(1)"- ]- },- {- "origin": [- 1,- 2- ],- "facts": [- "query(1, 2)"- ]- },- {- "origin": [- 2- ],- "facts": [- "query(2)"- ]- },- {- "origin": [- 3- ],- "facts": [- "query(3)"- ]- },- {- "origin": [- 4- ],- "facts": [- "query(4)"- ]- }- ],- "rules": [- {- "origin": 1,- "rules": [- "query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"- ]- }- ],- "checks": [- {- "origin": 0,- "checks": [- "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"- ]- },- {- "origin": 1,- "checks": [- "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",- "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"- ]- },- {- "origin": 2,- "checks": [- "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",- "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"- ]- },- {- "origin": 3,- "checks": [- "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",- "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"- ]- },- {- "origin": 4,- "checks": [- "check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",- "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"- ]- },- {- "origin": 18446744073709551615,- "checks": [- "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"- ]- }- ],- "policies": [- "deny if query(3)",- "deny if query(1, 2)",- "deny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",- "allow if true"- ]- },- "result": {- "Ok": 3- },- "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"- ]- }- }- },- {- "title": "integer wraparound",- "filename": "test027_integer_wraparound.bc",- "token": [- {- "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"- }- ],- "validations": {- "": {- "world": {- "facts": [],- "rules": [],- "checks": [- {- "origin": 0,- "checks": [- "check if true || -9223372036854775808 - 1 != 0",- "check if true || 10000000000 * 10000000000 != 0",- "check if true || 9223372036854775807 + 1 != 0"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Err": {- "Execution": "Overflow"- }- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "3346a22aae0abfc1ffa526f02f7650e90af909e5e519989026441e78cdc245b7fd126503cfdc8831325fc04307edc65238db319724477915f7040a2f6a719a05"- ]- }- }- },- {- "title": "test expression syntax and all available operations (v4 blocks)",- "filename": "test028_expressions_v4.bc",- "token": [- {- "symbols": [- "abcD12x",- "abcD12"- ],- "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"- }- ],- "validations": {- "": {- "world": {- "facts": [],- "rules": [],- "checks": [- {- "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"- ]- }- ],- "policies": [- "allow if true"- ]- },- "result": {- "Ok": 0- },- "authorizer_code": "allow if true;\n",- "revocation_ids": [- "117fa653744c859561555e6a6f5990e3a8e7817f91b87aa6991b6d64297158b4e884c92d10f49f74c96069df722aa676839b72751ca9d1fe83a7025b591de00b"+ "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": [+ "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": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 1,+ "check_id": 0,+ "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",+ "revocation_ids": [+ "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",+ "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"+ ]+ }+ }+ },+ {+ "title": "different root key",+ "filename": "test002_different_root_key.bc",+ "token": [+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file1\", \"read\");\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": null,+ "result": {+ "Err": {+ "Format": {+ "Signature": {+ "InvalidSignature": "signature error: Verification equation was not satisfied"+ }+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": []+ }+ }+ },+ {+ "title": "invalid signature format",+ "filename": "test003_invalid_signature_format.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": null,+ "result": {+ "Err": {+ "Format": {+ "BlockSignatureDeserializationError": "block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]"+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": []+ }+ }+ },+ {+ "title": "random block",+ "filename": "test004_random_block.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": null,+ "result": {+ "Err": {+ "Format": {+ "Signature": {+ "InvalidSignature": "signature error: Verification equation was not satisfied"+ }+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": []+ }+ }+ },+ {+ "title": "invalid signature",+ "filename": "test005_invalid_signature.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": null,+ "result": {+ "Err": {+ "Format": {+ "Signature": {+ "InvalidSignature": "signature error: Verification equation was not satisfied"+ }+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": []+ }+ }+ },+ {+ "title": "reordered blocks",+ "filename": "test006_reordered_blocks.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+ },+ {+ "symbols": [],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource(\"file1\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": null,+ "result": {+ "Err": {+ "Format": {+ "Signature": {+ "InvalidSignature": "signature error: Verification equation was not satisfied"+ }+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": []+ }+ }+ },+ {+ "title": "scoped rules",+ "filename": "test007_scoped_rules.bc",+ "token": [+ {+ "symbols": [+ "user_id",+ "alice",+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "user_id(\"alice\");\nowner(\"alice\", \"file1\");\n",+ "version": 3+ },+ {+ "symbols": [+ "0",+ "1"+ ],+ "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",+ "version": 3+ },+ {+ "symbols": [+ "file2"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "owner(\"alice\", \"file2\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file2\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "owner(\"alice\", \"file1\")",+ "user_id(\"alice\")"+ ]+ },+ {+ "origin": [+ 2+ ],+ "facts": [+ "owner(\"alice\", \"file2\")"+ ]+ }+ ],+ "rules": [+ {+ "origin": 1,+ "rules": [+ "right($0, \"read\") <- resource($0), user_id($1), owner($1, $0)"+ ]+ }+ ],+ "checks": [+ {+ "origin": 1,+ "checks": [+ "check if resource($0), operation(\"read\"), right($0, \"read\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 1,+ "check_id": 0,+ "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",+ "revocation_ids": [+ "4d86c9af808dc2e0583f47282e6f5df3e09dc264d5231ec360b4519e15ddaeec60b25a9bbcb22e8d192f4d36a0da3f9243711e30535b00ee55c53cb1395f230a",+ "63208c668c66f3ba6927140ba37533593b25e03459447805d4b2a8b75adeef45794c3d7249afe506ed77ccee276160bb4052a4009302bd34871a440f070b4509",+ "d8da982888eae8c038e4894a8c06fc57d8e5f06ad2e972b9cf4bde49ad60804558a0d1938192596c702d8e4f7f12ec19201d7c33d0cd77774a0d879a33880d02"+ ]+ }+ }+ },+ {+ "title": "scoped checks",+ "filename": "test008_scoped_checks.bc",+ "token": [+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file1\", \"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "0"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "file2"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file2\", \"read\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file2\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"file1\", \"read\")"+ ]+ },+ {+ "origin": [+ 2+ ],+ "facts": [+ "right(\"file2\", \"read\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 1,+ "checks": [+ "check if resource($0), operation(\"read\"), right($0, \"read\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 1,+ "check_id": 0,+ "rule": "check if resource($0), operation(\"read\"), right($0, \"read\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",+ "revocation_ids": [+ "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",+ "77df45442be86a416aa02fd9d98d6d4703c634a9e3b1d293b41f5dc97849afbe7faeec8c22a210574888acc008fb64fe691ec9e8d2655586f970d9a6b6577000",+ "b31398aefe97d3db41ebc445760f216fb3aa7bf7439adcfc3a07489bfcc163970af3f4e20f5460aa24cf841101a5ab114d21acc0ee8d442bae7793b121284900"+ ]+ }+ }+ },+ {+ "title": "expired token",+ "filename": "test009_expired_token.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [],+ "external_key": null,+ "code": "",+ "version": 3+ },+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource(\"file1\");\ncheck if time($time), $time <= 2018-12-20T00:00:00Z;\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file1\")",+ "time(2020-12-21T09:23:12Z)"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 1,+ "checks": [+ "check if resource(\"file1\")",+ "check if time($time), $time <= 2018-12-20T00:00:00Z"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 1,+ "check_id": 1,+ "rule": "check if time($time), $time <= 2018-12-20T00:00:00Z"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file1\");\noperation(\"read\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",+ "revocation_ids": [+ "c248907bb6e5f433bbb5edf6367b399ebefca0d321d0b2ea9fc67f66dc1064ce926adb0c05d90c3e8a2833328b3578f79c4e1bca43583d9bcfb2ba6c37303d00",+ "a4edf7aaea8658bb9ae19b3ffe2adcc77cc9f16c249aeb0a85a584b5362f89f27f7c67ac0af16d7170673d6d1fb1563d1934b25ec5a461f6c01fa49805cd5e07"+ ]+ }+ }+ },+ {+ "title": "authorizer scope",+ "filename": "test010_authorizer_scope.bc",+ "token": [+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file1\", \"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "file2"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file2\", \"read\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file2\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"file1\", \"read\")"+ ]+ },+ {+ "origin": [+ 1+ ],+ "facts": [+ "right(\"file2\", \"read\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if right($0, $1), resource($0), operation($1)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Authorizer": {+ "check_id": 0,+ "rule": "check if right($0, $1), resource($0), operation($1)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",+ "revocation_ids": [+ "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00",+ "966eceb2aa937c41b25368808bab6e0698c02a4038de669d007c9c3d43602638a640083558d1576ac80cf3eb2ac6a7585527e0f6c1a65402f0935cf7f4df8005"+ ]+ }+ }+ },+ {+ "title": "authorizer authority checks",+ "filename": "test011_authorizer_authority_caveats.bc",+ "token": [+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file1\", \"read\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file2\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"file1\", \"read\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if right($0, $1), resource($0), operation($1)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Authorizer": {+ "check_id": 0,+ "rule": "check if right($0, $1), resource($0), operation($1)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\ncheck if right($0, $1), resource($0), operation($1);\n\nallow if true;\n",+ "revocation_ids": [+ "a80c985ddef895518c216f64c65dcd50a5d97d012a94453d79159aed2981654b1fe9748c686c5667604026a94fb8db8a1d02de747df61e99fa9a63ff2878ad00"+ ]+ }+ }+ },+ {+ "title": "authority checks",+ "filename": "test012_authority_caveats.bc",+ "token": [+ {+ "symbols": [+ "file1"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource(\"file1\");\n",+ "version": 3+ }+ ],+ "validations": {+ "file1": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file1\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if resource(\"file1\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",+ "revocation_ids": [+ "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"+ ]+ },+ "file2": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"read\")",+ "resource(\"file2\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if resource(\"file1\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check if resource(\"file1\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\noperation(\"read\");\n\nallow if true;\n",+ "revocation_ids": [+ "6a8f90dad67ae2ac188460463914ae7326fda431c80785755f4edcc15f1a53911f7366e606ad80cbbeba94672e42713e88632a932128f1d796ce9ba7d7a0b80a"+ ]+ }+ }+ },+ {+ "title": "block rules",+ "filename": "test013_block_rules.bc",+ "token": [+ {+ "symbols": [+ "file1",+ "file2"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "valid_date",+ "0",+ "1"+ ],+ "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",+ "version": 3+ }+ ],+ "validations": {+ "file1": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "resource(\"file1\")",+ "time(2020-12-21T09:23:12Z)"+ ]+ },+ {+ "origin": [+ null,+ 1+ ],+ "facts": [+ "valid_date(\"file1\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"file1\", \"read\")",+ "right(\"file2\", \"read\")"+ ]+ }+ ],+ "rules": [+ {+ "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)"+ ]+ }+ ],+ "checks": [+ {+ "origin": 1,+ "checks": [+ "check if valid_date($0), resource($0)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "resource(\"file1\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",+ "revocation_ids": [+ "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",+ "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"+ ]+ },+ "file2": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "resource(\"file2\")",+ "time(2020-12-21T09:23:12Z)"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"file1\", \"read\")",+ "right(\"file2\", \"read\")"+ ]+ }+ ],+ "rules": [+ {+ "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)"+ ]+ }+ ],+ "checks": [+ {+ "origin": 1,+ "checks": [+ "check if valid_date($0), resource($0)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 1,+ "check_id": 0,+ "rule": "check if valid_date($0), resource($0)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file2\");\ntime(2020-12-21T09:23:12Z);\n\nallow if true;\n",+ "revocation_ids": [+ "c46d071ff3f33434223c8305fdad529f62bf78bb5d9cbfc2a345d4bca6bf314014840e18ba353f86fdb9073d58b12b8c872ac1f8e593c2e9064b90f6c2ede006",+ "a0c4c163a0b3ca406df4ece3d1371356190df04208eccef72f77e875ed0531b5d37e243d6f388b1967776a5dfd16ef228f19c5bdd6d2820f145c5ed3c3dcdc00"+ ]+ }+ }+ },+ {+ "title": "regex_constraint",+ "filename": "test014_regex_constraint.bc",+ "token": [+ {+ "symbols": [+ "0",+ "file[0-9]+.txt"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource($0), $0.matches(\"file[0-9]+.txt\");\n",+ "version": 3+ }+ ],+ "validations": {+ "file1": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "resource(\"file1\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if resource($0), $0.matches(\"file[0-9]+.txt\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check if resource($0), $0.matches(\"file[0-9]+.txt\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "resource(\"file1\");\n\nallow if true;\n",+ "revocation_ids": [+ "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"+ ]+ },+ "file123": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "resource(\"file123.txt\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if resource($0), $0.matches(\"file[0-9]+.txt\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "resource(\"file123.txt\");\n\nallow if true;\n",+ "revocation_ids": [+ "da42718ad2631c12d3a44b7710dcc76c6c7809c6bc3a2d7eb0378c4154eae10e0884a8d54a2cd25ca3dfe01091d816ebbb9d246227baf7a359a787cb2344ad07"+ ]+ }+ }+ },+ {+ "title": "multi queries checks",+ "filename": "test015_multi_queries_caveats.bc",+ "token": [+ {+ "symbols": [+ "must_be_present",+ "hello"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "must_be_present(\"hello\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "must_be_present(\"hello\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if must_be_present($0) or must_be_present($0)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "check if must_be_present($0) or must_be_present($0);\n\nallow if true;\n",+ "revocation_ids": [+ "b0d466d31e015fa85a075fa875f7e1c9017edd503fee9f62a5f033e1fcfa811074b6e39dfe5af2f452043db97a3f98650592a370f5685b62c5d6abf9dd10b603"+ ]+ }+ }+ },+ {+ "title": "check head name should be independent from fact names",+ "filename": "test016_caveat_head_name.bc",+ "token": [+ {+ "symbols": [+ "hello"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if resource(\"hello\");\n",+ "version": 3+ },+ {+ "symbols": [+ "test"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "query(\"test\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 1+ ],+ "facts": [+ "query(\"test\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if resource(\"hello\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check if resource(\"hello\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "ce6f804f4390e693a8853d9a4a10bd4f3c94b86b7c6d671993a6e19346bc4d20bbb52cc945e5d0d02e4e75fa5da2caa99764050190353564a0a0b4b276809402",+ "916d566cc724e0773046fc5266e9d0d804311435b8d6955b332f823ab296be9a78dfea190447732ac9f6217234cf5726becf88f65169c6de56a766af55451b0f"+ ]+ }+ }+ },+ {+ "title": "test expression syntax and all available operations",+ "filename": "test017_expressions.bc",+ "token": [+ {+ "symbols": [+ "hello world",+ "hello",+ "world",+ "aaabde",+ "a*c?.e",+ "abd",+ "aaa",+ "b",+ "de",+ "abcD12",+ "é",+ "abc",+ "def"+ ],+ "public_keys": [],+ "external_key": null,+ "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": {+ "": {+ "world": {+ "facts": [],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if !false",+ "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 1 + 2 * 3 - 4 / 2 === 5",+ "check if 1 < 2",+ "check if 1 <= 1",+ "check if 1 <= 2",+ "check if 2 > 1",+ "check if 2 >= 1",+ "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 > 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 false === false",+ "check if hex:12ab === hex:12ab",+ "check if 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)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505"+ ]+ }+ }+ },+ {+ "title": "invalid block rule with unbound_variables",+ "filename": "test018_unbound_variables_in_rule.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [],+ "external_key": null,+ "code": "check if operation(\"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "unbound",+ "any1",+ "any2"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "operation($unbound, \"read\") <- operation($any1, $any2);\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": null,+ "result": {+ "Err": {+ "FailedLogic": {+ "InvalidBlockRule": [+ 0,+ "operation($unbound, \"read\") <- operation($any1, $any2)"+ ]+ }+ }+ },+ "authorizer_code": "",+ "revocation_ids": [+ "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",+ "b0a33e3f4cd0994c0766c196c4d11c15e5a0f9bfba79a3a2b35ddd04ddb890282a7c63336ada5c680b9f9c940c1fa7127d2699754cbc77c21e1a2d85c5ef700c"+ ]+ }+ }+ },+ {+ "title": "invalid block rule generating an #authority or #ambient symbol with a variable",+ "filename": "test019_generating_ambient_from_variables.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [],+ "external_key": null,+ "code": "check if operation(\"read\");\n",+ "version": 3+ },+ {+ "symbols": [+ "any"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "operation(\"read\") <- operation($any);\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"write\")"+ ]+ },+ {+ "origin": [+ null,+ 1+ ],+ "facts": [+ "operation(\"read\")"+ ]+ }+ ],+ "rules": [+ {+ "origin": 1,+ "rules": [+ "operation(\"read\") <- operation($any)"+ ]+ }+ ],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if operation(\"read\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check if operation(\"read\")"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "operation(\"write\");\n\nallow if true;\n",+ "revocation_ids": [+ "a44210c6a01e55eadefc7d8540c2e6eff80ab6eeedde4751de734f9d780435780680d3f42d826b7e0f0dcf4a5ba303fd4c116984bb30978813d46ed867924307",+ "d3f8822a9b9bc0ee3933283c493ca9e711be5dd8339b5fe2eba1de3805aad4e84d3e2fb4affb4a743f1289915c167582b9425343635e45b70573ea1ee7a1ea03"+ ]+ }+ }+ },+ {+ "title": "sealed token",+ "filename": "test020_sealed.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": [+ "7595a112a1eb5b81a6e398852e6118b7f5b8cbbff452778e655100e5fb4faa8d3a2af52fe2c4f9524879605675fae26adbc4783e0cafc43522fa82385f396c03",+ "45f4c14f9d9e8fa044d68be7a2ec8cddb835f575c7b913ec59bd636c70acae9a90db9064ba0b3084290ed0c422bbb7170092a884f5e0202b31e9235bbcc1650d"+ ]+ }+ }+ },+ {+ "title": "parsing",+ "filename": "test021_parsing.bc",+ "token": [+ {+ "symbols": [+ "ns::fact_123",+ "hello é\t😁"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "ns::fact_123(\"hello é\t😁\");\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "ns::fact_123(\"hello é\t😁\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if ns::fact_123(\"hello é\t😁\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "check if ns::fact_123(\"hello é\t😁\");\n\nallow if true;\n",+ "revocation_ids": [+ "d4b2f417b6e906434fdf5058afcabfcb98d3628f814f1c9dd7e64250d9beec4465aff51bd0cb2e85d0e67dc9f613c2a42af6158c678bc6f8b4684cd3a2d0d302"+ ]+ }+ }+ },+ {+ "title": "default_symbols",+ "filename": "test022_default_symbols.bc",+ "token": [+ {+ "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",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "admin(13)",+ "client(18)",+ "client_ip(19)",+ "cluster(23)",+ "domain(20)",+ "email(14)",+ "group(15)",+ "hostname(25)",+ "ip_address(17)",+ "member(16)",+ "namespace(9)",+ "node(24)",+ "nonce(26)",+ "operation(3)",+ "owner(7)",+ "path(21)",+ "query(27)",+ "read(0)",+ "resource(2)",+ "right(4)",+ "role(6)",+ "service(12)",+ "team(11)",+ "tenant(8)",+ "time(5)",+ "user(10)",+ "version(22)",+ "write(1)"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "check if read(0), write(1), resource(2), operation(3), right(4), time(5), role(6), owner(7), tenant(8), namespace(9), user(10), team(11), service(12), admin(13), email(14), group(15), member(16), ip_address(17), client(18), client_ip(19), domain(20), path(21), version(22), cluster(23), node(24), hostname(25), nonce(26), query(27);\n\nallow if true;\n",+ "revocation_ids": [+ "75ce48d496fd28f99905901783a1ba46d7ff8d69f9d364d1546fd73006026eae51849ad1190a4ae521a0a1269f9c6951e226afba8fcd24fa50f679162439ae09"+ ]+ }+ }+ },+ {+ "title": "execution scope",+ "filename": "test023_execution_scope.bc",+ "token": [+ {+ "symbols": [+ "authority_fact"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "authority_fact(1);\n",+ "version": 3+ },+ {+ "symbols": [+ "block1_fact"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "block1_fact(1);\n",+ "version": 3+ },+ {+ "symbols": [+ "var"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if authority_fact($var);\ncheck if block1_fact($var);\n",+ "version": 3+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "authority_fact(1)"+ ]+ },+ {+ "origin": [+ 1+ ],+ "facts": [+ "block1_fact(1)"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 2,+ "checks": [+ "check if authority_fact($var)",+ "check if block1_fact($var)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 2,+ "check_id": 1,+ "rule": "check if block1_fact($var)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "f9b49866caef5ece7be14ec5a9b36d98ca81d06b306eb0b4c57cd7436af176f40ee972f40903f87ec4460ab8b1adfcbfa9b19b20a6955a1e8dae7d88b2076005",+ "889054b9119e4440e54da1b63266a98d0f6646cde195fef206efd8b133cfb2ee7be49b32a9a5925ece452e64f9e6f6d80dab422e916c599675dd68cdea053802",+ "0a85ffbf27e08aa23665ba0d96a985b274d747556c9f016fd7f590c641ed0e4133291521aa442b320ee9ce80f5ad701b914a0c87b3dfa0cc92629dce94201806"+ ]+ }+ }+ },+ {+ "title": "third party",+ "filename": "test024_third_party.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [+ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"+ ],+ "external_key": null,+ "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",+ "version": 5+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "right(\"read\")"+ ]+ },+ {+ "origin": [+ 1+ ],+ "facts": [+ "group(\"admin\")"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if group(\"admin\") trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"+ ]+ },+ {+ "origin": 1,+ "checks": [+ "check if right(\"read\")"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "470e4bf7aa2a01ab39c98150bd06aa15b4aa5d86509044a8809a8634cd8cf2b42269a51a774b65d10bac9369d013070b00187925196a8e680108473f11cf8f03",+ "901b2af4dacf33458d2d91ac484b60bad948e8d10faa9695b096054d5b46e832a977b60b17464cacf545ad0801f549ea454675f0ac88c413406925e2af83ff08"+ ]+ }+ }+ },+ {+ "title": "block rules",+ "filename": "test025_check_all.bc",+ "token": [+ {+ "symbols": [+ "allowed_operations",+ "A",+ "B",+ "op",+ "allowed"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "allowed_operations({\"A\", \"B\"});\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n",+ "version": 4+ }+ ],+ "validations": {+ "A, B": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"A\")",+ "operation(\"B\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "allowed_operations({\"A\", \"B\"})"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "operation(\"A\");\noperation(\"B\");\n\nallow if true;\n",+ "revocation_ids": [+ "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"+ ]+ },+ "A, invalid": {+ "world": {+ "facts": [+ {+ "origin": [+ null+ ],+ "facts": [+ "operation(\"A\")",+ "operation(\"invalid\")"+ ]+ },+ {+ "origin": [+ 0+ ],+ "facts": [+ "allowed_operations({\"A\", \"B\"})"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "operation(\"A\");\noperation(\"invalid\");\n\nallow if true;\n",+ "revocation_ids": [+ "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"+ ]+ },+ "no matches": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "allowed_operations({\"A\", \"B\"})"+ ]+ }+ ],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "FailedLogic": {+ "Unauthorized": {+ "policy": {+ "Allow": 0+ },+ "checks": [+ {+ "Block": {+ "block_id": 0,+ "check_id": 0,+ "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)"+ }+ }+ ]+ }+ }+ }+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d"+ ]+ }+ }+ },+ {+ "title": "public keys interning",+ "filename": "test026_public_keys_interning.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [+ "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"+ ],+ "external_key": null,+ "code": "query(0);\ncheck if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189;\n",+ "version": 4+ },+ {+ "symbols": [],+ "public_keys": [+ "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",+ "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",+ "version": 5+ },+ {+ "symbols": [],+ "public_keys": [+ "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",+ "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",+ "version": 5+ },+ {+ "symbols": [],+ "public_keys": [+ "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",+ "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",+ "version": 5+ },+ {+ "symbols": [],+ "public_keys": [+ "ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",+ "ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"+ ],+ "external_key": null,+ "code": "query(4);\ncheck if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463;\ncheck if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136;\n",+ "version": 4+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [+ {+ "origin": [+ 0+ ],+ "facts": [+ "query(0)"+ ]+ },+ {+ "origin": [+ 1+ ],+ "facts": [+ "query(1)"+ ]+ },+ {+ "origin": [+ 1,+ 2+ ],+ "facts": [+ "query(1, 2)"+ ]+ },+ {+ "origin": [+ 2+ ],+ "facts": [+ "query(2)"+ ]+ },+ {+ "origin": [+ 3+ ],+ "facts": [+ "query(3)"+ ]+ },+ {+ "origin": [+ 4+ ],+ "facts": [+ "query(4)"+ ]+ }+ ],+ "rules": [+ {+ "origin": 1,+ "rules": [+ "query(1, 2) <- query(1), query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"+ ]+ }+ ],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if true trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"+ ]+ },+ {+ "origin": 1,+ "checks": [+ "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",+ "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"+ ]+ },+ {+ "origin": 2,+ "checks": [+ "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",+ "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"+ ]+ },+ {+ "origin": 3,+ "checks": [+ "check if query(1) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",+ "check if query(2), query(3) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"+ ]+ },+ {+ "origin": 4,+ "checks": [+ "check if query(2) trusting ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463",+ "check if query(4) trusting ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136"+ ]+ },+ {+ "origin": 18446744073709551615,+ "checks": [+ "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"+ ]+ }+ ],+ "policies": [+ "deny if query(3)",+ "deny if query(1, 2)",+ "deny if query(0) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",+ "allow if true"+ ]+ },+ "result": {+ "Ok": 3+ },+ "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",+ "7113d4dbb3b688b80e941f365a2c6342d480c77ed03937bccf85dc5cc3554c7517887b1b0c9021388a71e6ca9047aabaaad5ae5b511a2880902568444a98e50b",+ "d0e3fc4bbd1b7320022800af909585aa906f677c4ca79c275a10b6779f669384c464ee84a1b04f13877a25761a874748362c065f4d15a8cab5c5e16c34074403",+ "29b7e0a1f118a6185814a552660c516c43482044e280e7a8de85b8e7e54947e0ae82eb39d7b524d4b72cb9812a7a4b8871964f8f825b1c1ed85d344c05281d0d",+ "c0a505d4d921a8b2d0b885917d42e2bca87b5302d13249a61af6f3802af44d691c40a624f901d677724740cb974a188aeb1c3992c1565ac0fbec3aa4f68dac0a"+ ]+ }+ }+ },+ {+ "title": "integer wraparound",+ "filename": "test027_integer_wraparound.bc",+ "token": [+ {+ "symbols": [],+ "public_keys": [],+ "external_key": null,+ "code": "check if 10000000000 * 10000000000 !== 0;\ncheck if 9223372036854775807 + 1 !== 0;\ncheck if -9223372036854775808 - 1 !== 0;\n",+ "version": 4+ }+ ],+ "validations": {+ "": {+ "world": {+ "facts": [],+ "rules": [],+ "checks": [+ {+ "origin": 0,+ "checks": [+ "check if -9223372036854775808 - 1 !== 0",+ "check if 10000000000 * 10000000000 !== 0",+ "check if 9223372036854775807 + 1 !== 0"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Err": {+ "Execution": "Overflow"+ }+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c"+ ]+ }+ }+ },+ {+ "title": "test expression syntax and all available operations (v4 blocks)",+ "filename": "test028_expressions_v4.bc",+ "token": [+ {+ "symbols": [+ "abcD12x",+ "abcD12"+ ],+ "public_keys": [],+ "external_key": null,+ "code": "check if true !== false;\ncheck 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": {+ "": {+ "world": {+ "facts": [],+ "rules": [],+ "checks": [+ {+ "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 hex:12abcd !== hex:12ab",+ "check if true !== false",+ "check if {1, 4} !== {1, 2}"+ ]+ }+ ],+ "policies": [+ "allow if true"+ ]+ },+ "result": {+ "Ok": 0+ },+ "authorizer_code": "allow if true;\n",+ "revocation_ids": [+ "9402c07923aa33bc911de80e61f388f5c4533e6b36e45317dc1db1e6bcc7664ed0c1c504d0ca8925208008961d95bbdbc36f6e3d91b3173369cc19ed625e9a0c"+ ]+ }+ }+ },+ {+ "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].get(-1) == 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(-1) == null",+ "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": [+ "0227fc29d6bf85cc52503f497b0b232235ffe5192c68933388136eae3fc3419229e9489b85d1bb57525554edac7cda87c984c96639bfb4171acc499312ffe30e"+ ]+ }+ }+ },+ {+ "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" ] } }
binary file changed (1799 → 1721 bytes)
binary file changed (458 → 460 bytes)
binary file changed (1547 → 1555 bytes)
binary file changed (329 → 293 bytes)
binary file changed (388 → 416 bytes)
binary file changed (absent → 184 bytes)
binary file changed (absent → 250 bytes)
binary file changed (absent → 884 bytes)
binary file changed (absent → 760 bytes)
binary file changed (absent → 1036 bytes)
binary file changed (absent → 2010 bytes)
binary file changed (absent → 236 bytes)
binary file changed (absent → 372 bytes)
binary file changed (absent → 582 bytes)
binary file changed (absent → 336 bytes)