packages feed

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)

- Auth.Biscuit.Crypto: sign3rdPartyBlock :: SecretKey -> PublicKey -> ByteString -> (Signature, PublicKey)
- Auth.Biscuit.Crypto: signBlock :: SecretKey -> ByteString -> Maybe (Signature, PublicKey) -> IO (SignedBlock, SecretKey)
- Auth.Biscuit.Crypto: verifyExternalSig :: PublicKey -> (ByteString, Signature, PublicKey) -> Bool
- Auth.Biscuit.Datalog.AST: One :: CheckKind
- Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.SetType inSet ctx)) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.SetType inSet ctx)) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance (GHC.Show.Show (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Show.Show (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.SetType inSet ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance (Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.VariableType inSet pof), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.SetType inSet ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.SliceType ctx)) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
- Auth.Biscuit.Datalog.Parser: VarInSet :: Span -> SemanticError
- Auth.Biscuit.Proto: CheckV2 :: Repeated 1 (Message RuleV2) -> Optional 2 (Enumeration CheckKind) -> CheckV2
- Auth.Biscuit.Proto: ExpressionV2 :: Repeated 1 (Message Op) -> ExpressionV2
- Auth.Biscuit.Proto: FactV2 :: Required 1 (Message PredicateV2) -> FactV2
- Auth.Biscuit.Proto: One :: CheckKind
- Auth.Biscuit.Proto: OpTernary :: Required 1 (Enumeration TernaryKind) -> OpTernary
- Auth.Biscuit.Proto: PredicateV2 :: Required 1 (Value Int64) -> Repeated 2 (Message TermV2) -> PredicateV2
- Auth.Biscuit.Proto: RuleV2 :: Required 1 (Message PredicateV2) -> Repeated 2 (Message PredicateV2) -> Repeated 3 (Message ExpressionV2) -> Repeated 4 (Message Scope) -> RuleV2
- Auth.Biscuit.Proto: VerifyEd25519Signature :: TernaryKind
- Auth.Biscuit.Proto: [$sel:body:RuleV2] :: RuleV2 -> Repeated 2 (Message PredicateV2)
- Auth.Biscuit.Proto: [$sel:checks_v2:Block] :: Block -> Repeated 6 (Message CheckV2)
- Auth.Biscuit.Proto: [$sel:expressions:RuleV2] :: RuleV2 -> Repeated 3 (Message ExpressionV2)
- Auth.Biscuit.Proto: [$sel:facts_v2:Block] :: Block -> Repeated 4 (Message FactV2)
- Auth.Biscuit.Proto: [$sel:head:RuleV2] :: RuleV2 -> Required 1 (Message PredicateV2)
- Auth.Biscuit.Proto: [$sel:kind:CheckV2] :: CheckV2 -> Optional 2 (Enumeration CheckKind)
- Auth.Biscuit.Proto: [$sel:kind:OpTernary] :: OpTernary -> Required 1 (Enumeration TernaryKind)
- Auth.Biscuit.Proto: [$sel:name:PredicateV2] :: PredicateV2 -> Required 1 (Value Int64)
- Auth.Biscuit.Proto: [$sel:ops:ExpressionV2] :: ExpressionV2 -> Repeated 1 (Message Op)
- Auth.Biscuit.Proto: [$sel:predicate:FactV2] :: FactV2 -> Required 1 (Message PredicateV2)
- Auth.Biscuit.Proto: [$sel:previousPk:ThirdPartyBlockRequest] :: ThirdPartyBlockRequest -> Required 1 (Message PublicKey)
- Auth.Biscuit.Proto: [$sel:queries:CheckV2] :: CheckV2 -> Repeated 1 (Message RuleV2)
- Auth.Biscuit.Proto: [$sel:rules_v2:Block] :: Block -> Repeated 5 (Message RuleV2)
- Auth.Biscuit.Proto: [$sel:scope:RuleV2] :: RuleV2 -> Repeated 4 (Message Scope)
- Auth.Biscuit.Proto: [$sel:terms:PredicateV2] :: PredicateV2 -> Repeated 2 (Message TermV2)
- Auth.Biscuit.Proto: data CheckV2
- Auth.Biscuit.Proto: data PredicateV2
- Auth.Biscuit.Proto: data RuleV2
- Auth.Biscuit.Proto: data TermV2
- Auth.Biscuit.Proto: data TernaryKind
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.CheckV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.ExpressionV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.FactV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.OpTernary
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.PredicateV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.RuleV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.TermV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.CheckV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.ExpressionV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.FactV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.OpTernary
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.PredicateV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.RuleV2
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.TermV2
- Auth.Biscuit.Proto: instance GHC.Enum.Bounded Auth.Biscuit.Proto.TernaryKind
- Auth.Biscuit.Proto: instance GHC.Enum.Enum Auth.Biscuit.Proto.TernaryKind
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.CheckV2
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.ExpressionV2
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.FactV2
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.OpTernary
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.PredicateV2
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.RuleV2
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.TermV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.CheckV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.ExpressionV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.FactV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.OpTernary
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.PredicateV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.RuleV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.TermV2
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.TernaryKind
- Auth.Biscuit.Proto: newtype ExpressionV2
- Auth.Biscuit.Proto: newtype FactV2
- Auth.Biscuit.Proto: newtype OpBinary
- Auth.Biscuit.Proto: newtype OpTernary
- Auth.Biscuit.Proto: newtype OpUnary
+ Auth.Biscuit: LNull :: Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit: TermArray :: ArrayType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit: TermMap :: MapType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit: [externFuncs] :: Limits -> ExternFuncs
+ Auth.Biscuit: setExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit: withExternFunc :: Text -> (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit: withExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit.Crypto: sign3rdPartyBlockV1 :: SecretKey -> Signature -> ByteString -> (Signature, PublicKey)
+ Auth.Biscuit.Crypto: signAttenuationBlock :: SecretKey -> Signature -> (ByteString, Int) -> Maybe (Signature, PublicKey) -> IO (SignedBlock, SecretKey)
+ Auth.Biscuit.Crypto: signAuthority :: SecretKey -> (ByteString, Int) -> IO (SignedBlock, SecretKey)
+ Auth.Biscuit.Crypto: verifyExternalSigV1 :: Signature -> (ByteString, Signature, PublicKey) -> Bool
+ Auth.Biscuit.Datalog.AST: Any :: Binary
+ Auth.Biscuit.Datalog.AST: BinaryFfi :: Text -> Binary
+ Auth.Biscuit.Datalog.AST: COp :: [Text] -> [Op] -> Op
+ Auth.Biscuit.Datalog.AST: CheckAll :: CheckKind
+ Auth.Biscuit.Datalog.AST: CheckOne :: CheckKind
+ Auth.Biscuit.Datalog.AST: EClosure :: [Text] -> Expression' ctx -> Expression' (ctx :: DatalogContext)
+ Auth.Biscuit.Datalog.AST: Get :: Binary
+ Auth.Biscuit.Datalog.AST: HeterogeneousEqual :: Binary
+ Auth.Biscuit.Datalog.AST: HeterogeneousNotEqual :: Binary
+ Auth.Biscuit.Datalog.AST: IntKey :: Int64 -> MapKey
+ Auth.Biscuit.Datalog.AST: LNull :: Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit.Datalog.AST: LazyAnd :: Binary
+ Auth.Biscuit.Datalog.AST: LazyOr :: Binary
+ Auth.Biscuit.Datalog.AST: Reject :: CheckKind
+ Auth.Biscuit.Datalog.AST: StringKey :: Text -> MapKey
+ Auth.Biscuit.Datalog.AST: TermArray :: ArrayType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit.Datalog.AST: TermMap :: MapType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: DatalogContext)
+ Auth.Biscuit.Datalog.AST: Try :: Binary
+ Auth.Biscuit.Datalog.AST: TypeOf :: Unary
+ Auth.Biscuit.Datalog.AST: UnaryFfi :: Text -> Unary
+ Auth.Biscuit.Datalog.AST: checkHasNoV6Values :: Check -> Bool
+ Auth.Biscuit.Datalog.AST: data MapKey
+ Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.SetType inSet ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.ArrayType inSet ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.MapType inSet ctx)) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
+ Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.SetType inSet ctx), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.ArrayType inSet ctx), GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.MapType inSet ctx)) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
+ Auth.Biscuit.Datalog.AST: instance (GHC.Show.Show (Auth.Biscuit.Datalog.AST.VariableType inSet pof), GHC.Show.Show (Auth.Biscuit.Datalog.AST.SliceType ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.SetType inSet ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.ArrayType inSet ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.MapType inSet ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
+ Auth.Biscuit.Datalog.AST: instance (Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.VariableType inSet pof), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.SetType inSet ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.ArrayType inSet ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.MapType inSet ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.SliceType ctx)) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Term' inSet pof ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq Auth.Biscuit.Datalog.AST.MapKey
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq Auth.Biscuit.Datalog.AST.Op
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Ord Auth.Biscuit.Datalog.AST.MapKey
+ Auth.Biscuit.Datalog.AST: instance GHC.Show.Show Auth.Biscuit.Datalog.AST.MapKey
+ Auth.Biscuit.Datalog.AST: instance GHC.Show.Show Auth.Biscuit.Datalog.AST.Op
+ Auth.Biscuit.Datalog.AST: instance Language.Haskell.TH.Syntax.Lift Auth.Biscuit.Datalog.AST.MapKey
+ Auth.Biscuit.Datalog.AST: isReject :: Check' evalCtx ctx -> Bool
+ Auth.Biscuit.Datalog.AST: predicateHasNoV6Values :: Predicate' a b -> Bool
+ Auth.Biscuit.Datalog.AST: ruleHasNoV6Values :: Rule -> Bool
+ Auth.Biscuit.Datalog.AST: setValueToValue :: SetValue -> Value
+ Auth.Biscuit.Datalog.Executor: ExternFunc :: (Value -> Maybe Value -> Either String Value) -> ExternFunc
+ Auth.Biscuit.Datalog.Executor: [externFuncs] :: Limits -> ExternFuncs
+ Auth.Biscuit.Datalog.Executor: instance GHC.Classes.Eq Auth.Biscuit.Datalog.Executor.ExternFunc
+ Auth.Biscuit.Datalog.Executor: instance GHC.Show.Show Auth.Biscuit.Datalog.Executor.ExternFunc
+ Auth.Biscuit.Datalog.Executor: newtype ExternFunc
+ Auth.Biscuit.Datalog.Executor: setExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit.Datalog.Executor: type ExternFuncs = Map Text ExternFunc
+ Auth.Biscuit.Datalog.Executor: withExternFunc :: Text -> (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit.Datalog.Executor: withExternFuncs :: Map Text (Value -> Maybe Value -> Either String Value) -> Limits -> Limits
+ Auth.Biscuit.Datalog.Parser: VarInCollection :: Span -> SemanticError
+ Auth.Biscuit.Datalog.Parser: arrayParser :: Parser [Term' 'NotWithinSet 'InFact 'WithSlices]
+ Auth.Biscuit.Datalog.Parser: closureParser :: Parser (Text, Expression' 'WithSlices)
+ Auth.Biscuit.Datalog.Parser: emptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices))
+ Auth.Biscuit.Datalog.Parser: identifierParser :: Parser Text
+ Auth.Biscuit.Datalog.Parser: mapEntryParser :: Parser (MapKey, Term' 'NotWithinSet 'InFact 'WithSlices)
+ Auth.Biscuit.Datalog.Parser: mapParser :: Parser (Map MapKey (Term' 'NotWithinSet 'InFact 'WithSlices))
+ Auth.Biscuit.Datalog.Parser: nonEmptySetParser :: Parser (Set (Term' 'WithinSet 'InFact 'WithSlices))
+ Auth.Biscuit.Datalog.Parser: setTermParser :: Parser (Term' 'WithinSet 'InFact 'WithSlices)
+ Auth.Biscuit.Datalog.Parser: stringParser :: Parser Text
+ Auth.Biscuit.Datalog.Parser: valueParser :: Parser (Term' 'NotWithinSet 'InFact 'WithSlices)
+ Auth.Biscuit.Proto: Any :: BinaryKind
+ Auth.Biscuit.Proto: BinaryFfi :: BinaryKind
+ Auth.Biscuit.Proto: Check :: Repeated 1 (Message Rule) -> Optional 2 (Enumeration CheckKind) -> Check
+ Auth.Biscuit.Proto: CheckAll :: CheckKind
+ Auth.Biscuit.Proto: CheckOne :: CheckKind
+ Auth.Biscuit.Proto: Empty :: Empty
+ Auth.Biscuit.Proto: Expression :: Repeated 1 (Message Op) -> Expression
+ Auth.Biscuit.Proto: Fact :: Required 1 (Message Predicate) -> Fact
+ Auth.Biscuit.Proto: Get :: BinaryKind
+ Auth.Biscuit.Proto: HeterogeneousEqual :: BinaryKind
+ Auth.Biscuit.Proto: HeterogeneousNotEqual :: BinaryKind
+ Auth.Biscuit.Proto: LazyAnd :: BinaryKind
+ Auth.Biscuit.Proto: LazyOr :: BinaryKind
+ Auth.Biscuit.Proto: MapEntry :: Required 1 (Message MapKey) -> Required 2 (Message Term) -> MapEntry
+ Auth.Biscuit.Proto: MapKeyInt :: Required 1 (Value Int64) -> MapKey
+ Auth.Biscuit.Proto: MapKeyString :: Required 2 (Value Int64) -> MapKey
+ Auth.Biscuit.Proto: OpClosure :: Repeated 1 (Value Int64) -> Repeated 2 (Message Op) -> OpClosure
+ Auth.Biscuit.Proto: OpVClosure :: Required 4 (Message OpClosure) -> Op
+ Auth.Biscuit.Proto: Predicate :: Required 1 (Value Int64) -> Repeated 2 (Message Term) -> Predicate
+ Auth.Biscuit.Proto: Reject :: CheckKind
+ Auth.Biscuit.Proto: Rule :: Required 1 (Message Predicate) -> Repeated 2 (Message Predicate) -> Repeated 3 (Message Expression) -> Repeated 4 (Message Scope) -> Rule
+ Auth.Biscuit.Proto: TermArray :: Repeated 1 (Message Term) -> TermArray
+ Auth.Biscuit.Proto: TermMap :: Repeated 1 (Message MapEntry) -> TermMap
+ Auth.Biscuit.Proto: TermNull :: Required 8 (Message Empty) -> Term
+ Auth.Biscuit.Proto: TermTermArray :: Required 9 (Message TermArray) -> Term
+ Auth.Biscuit.Proto: TermTermMap :: Required 10 (Message TermMap) -> Term
+ Auth.Biscuit.Proto: TryOr :: BinaryKind
+ Auth.Biscuit.Proto: TypeOf :: UnaryKind
+ Auth.Biscuit.Proto: UnaryFfi :: UnaryKind
+ Auth.Biscuit.Proto: [$sel:array:TermArray] :: TermArray -> Repeated 1 (Message Term)
+ Auth.Biscuit.Proto: [$sel:body:Rule] :: Rule -> Repeated 2 (Message Predicate)
+ Auth.Biscuit.Proto: [$sel:checks:Block] :: Block -> Repeated 6 (Message Check)
+ Auth.Biscuit.Proto: [$sel:expressions:Rule] :: Rule -> Repeated 3 (Message Expression)
+ Auth.Biscuit.Proto: [$sel:facts:Block] :: Block -> Repeated 4 (Message Fact)
+ Auth.Biscuit.Proto: [$sel:ffiName:OpBinary] :: OpBinary -> Optional 2 (Value Int64)
+ Auth.Biscuit.Proto: [$sel:ffiName:OpUnary] :: OpUnary -> Optional 2 (Value Int64)
+ Auth.Biscuit.Proto: [$sel:head:Rule] :: Rule -> Required 1 (Message Predicate)
+ Auth.Biscuit.Proto: [$sel:key:MapEntry] :: MapEntry -> Required 1 (Message MapKey)
+ Auth.Biscuit.Proto: [$sel:kind:Check] :: Check -> Optional 2 (Enumeration CheckKind)
+ Auth.Biscuit.Proto: [$sel:legacyPk:ThirdPartyBlockRequest] :: ThirdPartyBlockRequest -> Optional 1 (Message PublicKey)
+ Auth.Biscuit.Proto: [$sel:map:TermMap] :: TermMap -> Repeated 1 (Message MapEntry)
+ Auth.Biscuit.Proto: [$sel:name:Predicate] :: Predicate -> Required 1 (Value Int64)
+ Auth.Biscuit.Proto: [$sel:ops:Expression] :: Expression -> Repeated 1 (Message Op)
+ Auth.Biscuit.Proto: [$sel:ops:OpClosure] :: OpClosure -> Repeated 2 (Message Op)
+ Auth.Biscuit.Proto: [$sel:params:OpClosure] :: OpClosure -> Repeated 1 (Value Int64)
+ Auth.Biscuit.Proto: [$sel:predicate:Fact] :: Fact -> Required 1 (Message Predicate)
+ Auth.Biscuit.Proto: [$sel:prevSig:ThirdPartyBlockRequest] :: ThirdPartyBlockRequest -> Required 3 (Value ByteString)
+ Auth.Biscuit.Proto: [$sel:queries:Check] :: Check -> Repeated 1 (Message Rule)
+ Auth.Biscuit.Proto: [$sel:rules:Block] :: Block -> Repeated 5 (Message Rule)
+ Auth.Biscuit.Proto: [$sel:scope:Rule] :: Rule -> Repeated 4 (Message Scope)
+ Auth.Biscuit.Proto: [$sel:terms:Predicate] :: Predicate -> Repeated 2 (Message Term)
+ Auth.Biscuit.Proto: [$sel:value:MapEntry] :: MapEntry -> Required 2 (Message Term)
+ Auth.Biscuit.Proto: [$sel:version:SignedBlock] :: SignedBlock -> Optional 5 (Value Int32)
+ Auth.Biscuit.Proto: data Check
+ Auth.Biscuit.Proto: data Empty
+ Auth.Biscuit.Proto: data MapEntry
+ Auth.Biscuit.Proto: data MapKey
+ Auth.Biscuit.Proto: data OpBinary
+ Auth.Biscuit.Proto: data OpClosure
+ Auth.Biscuit.Proto: data OpUnary
+ Auth.Biscuit.Proto: data Predicate
+ Auth.Biscuit.Proto: data Rule
+ Auth.Biscuit.Proto: data Term
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Check
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Empty
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Expression
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Fact
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.MapEntry
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.MapKey
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.OpClosure
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Predicate
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Rule
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Term
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.TermArray
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.TermMap
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Check
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Empty
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Expression
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Fact
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.MapEntry
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.MapKey
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.OpClosure
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Predicate
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Rule
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Term
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.TermArray
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.TermMap
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Check
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Empty
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Expression
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Fact
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.MapEntry
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.MapKey
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.OpClosure
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Predicate
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Rule
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Term
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.TermArray
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.TermMap
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Check
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Empty
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Expression
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Fact
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.MapEntry
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.MapKey
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.OpClosure
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Predicate
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Rule
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Term
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.TermArray
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.TermMap
+ Auth.Biscuit.Proto: newtype Expression
+ Auth.Biscuit.Proto: newtype Fact
+ Auth.Biscuit.Proto: newtype TermArray
+ Auth.Biscuit.Proto: newtype TermMap
- Auth.Biscuit: Limits :: Int -> Int -> Int -> Bool -> Limits
+ Auth.Biscuit: Limits :: Int -> Int -> Int -> Bool -> ExternFuncs -> Limits
- Auth.Biscuit.Crypto: signExternalBlock :: SecretKey -> SecretKey -> PublicKey -> ByteString -> IO (SignedBlock, SecretKey)
+ Auth.Biscuit.Crypto: signExternalBlock :: SecretKey -> Signature -> (ByteString, Int) -> SecretKey -> IO (SignedBlock, SecretKey)
- Auth.Biscuit.Crypto: type SignedBlock = (ByteString, Signature, PublicKey, Maybe (Signature, PublicKey))
+ Auth.Biscuit.Crypto: type SignedBlock = (ByteString, Signature, PublicKey, Maybe (Signature, PublicKey), Maybe Int)
- Auth.Biscuit.Datalog.AST: All :: CheckKind
+ Auth.Biscuit.Datalog.AST: All :: Binary
- Auth.Biscuit.Datalog.Executor: Limits :: Int -> Int -> Int -> Bool -> Limits
+ Auth.Biscuit.Datalog.Executor: Limits :: Int -> Int -> Int -> Bool -> ExternFuncs -> Limits
- Auth.Biscuit.Datalog.Parser: termParser :: Parser (VariableType inSet pof) -> Parser (SetType inSet 'WithSlices) -> Parser (Term' inSet pof 'WithSlices)
+ Auth.Biscuit.Datalog.Parser: termParser :: Parser (VariableType inSet pof) -> Parser (SetType inSet 'WithSlices) -> Parser (ArrayType inSet 'WithSlices) -> Parser (MapType inSet 'WithSlices) -> Parser (Term' inSet pof 'WithSlices)
- Auth.Biscuit.Proto: All :: CheckKind
+ Auth.Biscuit.Proto: All :: BinaryKind
- Auth.Biscuit.Proto: Block :: Repeated 1 (Value Text) -> Optional 2 (Value Text) -> Optional 3 (Value Int32) -> Repeated 4 (Message FactV2) -> Repeated 5 (Message RuleV2) -> Repeated 6 (Message CheckV2) -> Repeated 7 (Message Scope) -> Repeated 8 (Message PublicKey) -> Block
+ Auth.Biscuit.Proto: Block :: Repeated 1 (Value Text) -> Optional 2 (Value Text) -> Optional 3 (Value Int32) -> Repeated 4 (Message Fact) -> Repeated 5 (Message Rule) -> Repeated 6 (Message Check) -> Repeated 7 (Message Scope) -> Repeated 8 (Message PublicKey) -> Block
- Auth.Biscuit.Proto: OpBinary :: Required 1 (Enumeration BinaryKind) -> OpBinary
+ Auth.Biscuit.Proto: OpBinary :: Required 1 (Enumeration BinaryKind) -> Optional 2 (Value Int64) -> OpBinary
- Auth.Biscuit.Proto: OpUnary :: Required 1 (Enumeration UnaryKind) -> OpUnary
+ Auth.Biscuit.Proto: OpUnary :: Required 1 (Enumeration UnaryKind) -> Optional 2 (Value Int64) -> OpUnary
- Auth.Biscuit.Proto: OpVValue :: Required 1 (Message TermV2) -> Op
+ Auth.Biscuit.Proto: OpVValue :: Required 1 (Message Term) -> Op
- Auth.Biscuit.Proto: SignedBlock :: Required 1 (Value ByteString) -> Required 2 (Message PublicKey) -> Required 3 (Value ByteString) -> Optional 4 (Message ExternalSig) -> SignedBlock
+ Auth.Biscuit.Proto: SignedBlock :: Required 1 (Value ByteString) -> Required 2 (Message PublicKey) -> Required 3 (Value ByteString) -> Optional 4 (Message ExternalSig) -> Optional 5 (Value Int32) -> SignedBlock
- Auth.Biscuit.Proto: TermBool :: Required 6 (Value Bool) -> TermV2
+ Auth.Biscuit.Proto: TermBool :: Required 6 (Value Bool) -> Term
- Auth.Biscuit.Proto: TermBytes :: Required 5 (Value ByteString) -> TermV2
+ Auth.Biscuit.Proto: TermBytes :: Required 5 (Value ByteString) -> Term
- Auth.Biscuit.Proto: TermDate :: Required 4 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermDate :: Required 4 (Value Int64) -> Term
- Auth.Biscuit.Proto: TermInteger :: Required 2 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermInteger :: Required 2 (Value Int64) -> Term
- Auth.Biscuit.Proto: TermSet :: Repeated 1 (Message TermV2) -> TermSet
+ Auth.Biscuit.Proto: TermSet :: Repeated 1 (Message Term) -> TermSet
- Auth.Biscuit.Proto: TermString :: Required 3 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermString :: Required 3 (Value Int64) -> Term
- Auth.Biscuit.Proto: TermTermSet :: Required 7 (Message TermSet) -> TermV2
+ Auth.Biscuit.Proto: TermTermSet :: Required 7 (Message TermSet) -> Term
- Auth.Biscuit.Proto: TermVariable :: Required 1 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermVariable :: Required 1 (Value Int64) -> Term
- Auth.Biscuit.Proto: ThirdPartyBlockRequest :: Required 1 (Message PublicKey) -> Repeated 2 (Message PublicKey) -> ThirdPartyBlockRequest
+ Auth.Biscuit.Proto: ThirdPartyBlockRequest :: Optional 1 (Message PublicKey) -> Repeated 2 (Message PublicKey) -> Required 3 (Value ByteString) -> ThirdPartyBlockRequest
- Auth.Biscuit.Proto: [$sel:set:TermSet] :: TermSet -> Repeated 1 (Message TermV2)
+ Auth.Biscuit.Proto: [$sel:set:TermSet] :: TermSet -> Repeated 1 (Message Term)
- Auth.Biscuit.ProtoBufAdapter: blockToPb :: Bool -> Symbols -> Block -> (BlockSymbols, Block)
+ Auth.Biscuit.ProtoBufAdapter: blockToPb :: Bool -> Symbols -> Block -> ((BlockSymbols, Int), Block)
- Auth.Biscuit.ProtoBufAdapter: pbToThirdPartyBlockRequest :: ThirdPartyBlockRequest -> Either String PublicKey
+ Auth.Biscuit.ProtoBufAdapter: pbToThirdPartyBlockRequest :: ThirdPartyBlockRequest -> Either String Signature
- Auth.Biscuit.ProtoBufAdapter: thirdPartyBlockRequestToPb :: PublicKey -> ThirdPartyBlockRequest
+ Auth.Biscuit.ProtoBufAdapter: thirdPartyBlockRequestToPb :: Signature -> ThirdPartyBlockRequest
- Auth.Biscuit.Token: type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey))
+ Auth.Biscuit.Token: type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey, Maybe (Signature, PublicKey), Maybe Int)

Files

ChangeLog.md view
@@ -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 
README.md view
@@ -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
biscuit-haskell.cabal view
@@ -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
src/Auth/Biscuit.hs view
@@ -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
src/Auth/Biscuit/Crypto.hs view
@@ -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)
src/Auth/Biscuit/Datalog/AST.hs view
@@ -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
src/Auth/Biscuit/Datalog/Executor.hs view
@@ -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"
src/Auth/Biscuit/Datalog/Parser.hs view
@@ -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{..}
src/Auth/Biscuit/Datalog/ScopedExecutor.hs view
@@ -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
src/Auth/Biscuit/Example.hs view
@@ -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;|]
src/Auth/Biscuit/Proto.hs view
@@ -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) 
src/Auth/Biscuit/ProtoBufAdapter.hs view
@@ -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)
src/Auth/Biscuit/Symbols.hs view
@@ -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
src/Auth/Biscuit/Timer.hs view
@@ -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 -}
src/Auth/Biscuit/Token.hs view
@@ -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
src/Auth/Biscuit/Utils.hs view
@@ -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,
test/Spec.hs view
@@ -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
+ test/Spec/AST.hs view
@@ -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
test/Spec/Executor.hs view
@@ -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")
test/Spec/NewCrypto.hs view
@@ -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
test/Spec/Parser.hs view
@@ -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"
test/Spec/Quasiquoter.hs view
@@ -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)
test/Spec/Roundtrip.hs view
@@ -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
test/Spec/SampleReader.hs view
@@ -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{..}
test/Spec/ScopedExecutor.hs view
@@ -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
test/Spec/Verification.hs view
@@ -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
test/samples/current/samples.json view
@@ -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"           ]         }       }
test/samples/current/test017_expressions.bc view

binary file changed (1799 → 1721 bytes)

test/samples/current/test024_third_party.bc view

binary file changed (458 → 460 bytes)

test/samples/current/test026_public_keys_interning.bc view

binary file changed (1547 → 1555 bytes)

test/samples/current/test027_integer_wraparound.bc view

binary file changed (329 → 293 bytes)

test/samples/current/test028_expressions_v4.bc view

binary file changed (388 → 416 bytes)

+ test/samples/current/test029_reject_if.bc view

binary file changed (absent → 184 bytes)

+ test/samples/current/test030_null.bc view

binary file changed (absent → 250 bytes)

+ test/samples/current/test031_heterogeneous_equal.bc view

binary file changed (absent → 884 bytes)

+ test/samples/current/test032_laziness_closures.bc view

binary file changed (absent → 760 bytes)

+ test/samples/current/test033_typeof.bc view

binary file changed (absent → 1036 bytes)

+ test/samples/current/test034_array_map.bc view

binary file changed (absent → 2010 bytes)

+ test/samples/current/test035_ffi.bc view

binary file changed (absent → 236 bytes)

+ test/samples/current/test036_secp256r1.bc view

binary file changed (absent → 372 bytes)

+ test/samples/current/test037_secp256r1_third_party.bc view

binary file changed (absent → 582 bytes)

+ test/samples/current/test038_try_op.bc view

binary file changed (absent → 336 bytes)