biscuit-haskell 0.1.1.0 → 0.2.0.0
raw patch · 27 files changed
+2784/−1745 lines, 27 filesdep +aesondep +criteriondep +cryptonitedep −libsodiumdep −primitivedep ~basedep ~base16-bytestringdep ~randomPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, criterion, cryptonite, memory
Dependencies removed: libsodium, primitive
Dependency ranges changed: base, base16-bytestring, random
API changes (from Hackage documentation)
- Auth.Biscuit: DatalogError :: ExecutionError -> VerificationError
- Auth.Biscuit: Keypair :: PrivateKey -> PublicKey -> Keypair
- Auth.Biscuit: SignatureError :: VerificationError
- Auth.Biscuit: [checkRevocationId] :: Limits -> ByteString -> IO (Either () ())
- Auth.Biscuit: [privateKey] :: Keypair -> PrivateKey
- Auth.Biscuit: [publicKey] :: Keypair -> PublicKey
- Auth.Biscuit: checkBiscuitSignature :: Biscuit -> PublicKey -> IO Bool
- Auth.Biscuit: data Keypair
- Auth.Biscuit: data PrivateKey
- Auth.Biscuit: data VerificationError
- Auth.Biscuit: fromPrivateKey :: PrivateKey -> IO Keypair
- Auth.Biscuit: newKeypair :: IO Keypair
- Auth.Biscuit: parseHex :: ByteString -> Either ParseError Biscuit
- Auth.Biscuit: parsePrivateKey :: ByteString -> Maybe PrivateKey
- Auth.Biscuit: parsePrivateKeyHex :: ByteString -> Maybe PrivateKey
- Auth.Biscuit: serializeHex :: Biscuit -> ByteString
- Auth.Biscuit: serializePrivateKey :: PrivateKey -> ByteString
- Auth.Biscuit: serializePrivateKeyHex :: PrivateKey -> ByteString
- Auth.Biscuit: type Verifier = Verifier' 'RegularString
- Auth.Biscuit: verifier :: QuasiQuoter
- Auth.Biscuit: verifyBiscuit :: Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)
- Auth.Biscuit: verifyBiscuitWithLimits :: Limits -> Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)
- Auth.Biscuit.Datalog.AST: Symbol :: Text -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: Verifier :: [Policy' ctx] -> Block' ctx -> Verifier' (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: VerifierPolicy :: Policy' ctx -> VerifierElement' ctx
- Auth.Biscuit.Datalog.AST: data ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: data Verifier' (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: data VerifierElement' ctx
- Auth.Biscuit.Datalog.AST: elementToVerifier :: VerifierElement' ctx -> Verifier' ctx
- Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Block' ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Verifier' ctx)
- 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.ID' 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.ID' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance (GHC.Show.Show (Auth.Biscuit.Datalog.AST.Block' ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Verifier' ctx)
- Auth.Biscuit.Datalog.AST: instance (GHC.Show.Show (Auth.Biscuit.Datalog.AST.Predicate' 'Auth.Biscuit.Datalog.AST.InFact ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.Rule' ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.VerifierElement' 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.ID' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance (Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Block' ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Verifier' 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.ID' inSet pof ctx)
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId Data.ByteString.Internal.ByteString
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId Data.Text.Internal.Text
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId Data.Time.Clock.Internal.UTCTime.UTCTime
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId GHC.Integer.Type.Integer
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId GHC.Types.Bool
- Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToLiteralId GHC.Types.Int
- Auth.Biscuit.Datalog.AST: instance GHC.Base.Monoid (Auth.Biscuit.Datalog.AST.Verifier' ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Base.Semigroup (Auth.Biscuit.Datalog.AST.Verifier' ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Expression' ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Expression' ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Show.Show (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Expression' ctx)
- Auth.Biscuit.Datalog.AST: instance GHC.Show.Show (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
- Auth.Biscuit.Datalog.AST: instance Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Expression' ctx)
- Auth.Biscuit.Datalog.AST: instance Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.ID' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
- Auth.Biscuit.Datalog.AST: type ID = ID' 'NotWithinSet 'InPredicate 'RegularString
- Auth.Biscuit.Datalog.AST: type QQID = ID' 'NotWithinSet 'InPredicate 'QuasiQuote
- Auth.Biscuit.Datalog.AST: type Verifier = Verifier' 'RegularString
- Auth.Biscuit.Datalog.Executor: BlockWithRevocationIds :: Block -> ByteString -> ByteString -> BlockWithRevocationIds
- Auth.Biscuit.Datalog.Executor: World :: Set Rule -> Set Rule -> Set Fact -> World
- Auth.Biscuit.Datalog.Executor: [bBlock] :: BlockWithRevocationIds -> Block
- Auth.Biscuit.Datalog.Executor: [blockRules] :: World -> Set Rule
- Auth.Biscuit.Datalog.Executor: [checkRevocationId] :: Limits -> ByteString -> IO (Either () ())
- Auth.Biscuit.Datalog.Executor: [facts] :: World -> Set Fact
- Auth.Biscuit.Datalog.Executor: [genericRevocationId] :: BlockWithRevocationIds -> ByteString
- Auth.Biscuit.Datalog.Executor: [rules] :: World -> Set Rule
- Auth.Biscuit.Datalog.Executor: [uniqueRevocationId] :: BlockWithRevocationIds -> ByteString
- Auth.Biscuit.Datalog.Executor: computeAllFacts :: Limits -> World -> Either ExecutionError (Set Fact)
- Auth.Biscuit.Datalog.Executor: data BlockWithRevocationIds
- Auth.Biscuit.Datalog.Executor: data World
- Auth.Biscuit.Datalog.Executor: instance GHC.Base.Monoid Auth.Biscuit.Datalog.Executor.World
- Auth.Biscuit.Datalog.Executor: instance GHC.Base.Semigroup Auth.Biscuit.Datalog.Executor.World
- Auth.Biscuit.Datalog.Executor: instance GHC.Show.Show Auth.Biscuit.Datalog.Executor.World
- Auth.Biscuit.Datalog.Executor: runVerifier :: BlockWithRevocationIds -> [BlockWithRevocationIds] -> Verifier -> IO (Either ExecutionError Query)
- Auth.Biscuit.Datalog.Executor: runVerifierWithLimits :: Limits -> BlockWithRevocationIds -> [BlockWithRevocationIds] -> Verifier -> IO (Either ExecutionError Query)
- Auth.Biscuit.Datalog.Parser: verifier :: QuasiQuoter
- Auth.Biscuit.Datalog.Parser: verifierParser :: (HasParsers 'InPredicate ctx, HasParsers 'InFact ctx, Show (VerifierElement' ctx)) => Parser (Verifier' ctx)
- Auth.Biscuit.Proto: CheckV1 :: Repeated 1 (Message RuleV1) -> CheckV1
- Auth.Biscuit.Proto: ExpressionV1 :: Repeated 1 (Message Op) -> ExpressionV1
- Auth.Biscuit.Proto: FactV1 :: Required 1 (Message PredicateV1) -> FactV1
- Auth.Biscuit.Proto: IDBool :: Required 7 (Value Bool) -> IDV1
- Auth.Biscuit.Proto: IDBytes :: Required 6 (Value ByteString) -> IDV1
- Auth.Biscuit.Proto: IDDate :: Required 5 (Value Int64) -> IDV1
- Auth.Biscuit.Proto: IDIDSet :: Required 8 (Message IDSet) -> IDV1
- Auth.Biscuit.Proto: IDInteger :: Required 3 (Value Int64) -> IDV1
- Auth.Biscuit.Proto: IDSet :: Repeated 1 (Message IDV1) -> IDSet
- Auth.Biscuit.Proto: IDString :: Required 4 (Value Text) -> IDV1
- Auth.Biscuit.Proto: IDSymbol :: Required 1 (Value Int64) -> IDV1
- Auth.Biscuit.Proto: IDVariable :: Required 2 (Value Int32) -> IDV1
- Auth.Biscuit.Proto: PredicateV1 :: Required 1 (Value Int64) -> Repeated 2 (Message IDV1) -> PredicateV1
- Auth.Biscuit.Proto: RuleV1 :: Required 1 (Message PredicateV1) -> Repeated 2 (Message PredicateV1) -> Repeated 3 (Message ExpressionV1) -> RuleV1
- Auth.Biscuit.Proto: Signature :: Repeated 1 (Value ByteString) -> Required 2 (Value ByteString) -> Signature
- Auth.Biscuit.Proto: [$sel:body:RuleV1] :: RuleV1 -> Repeated 2 (Message PredicateV1)
- Auth.Biscuit.Proto: [$sel:checks_v1:Block] :: Block -> Repeated 10 (Message CheckV1)
- Auth.Biscuit.Proto: [$sel:expressions:RuleV1] :: RuleV1 -> Repeated 3 (Message ExpressionV1)
- Auth.Biscuit.Proto: [$sel:facts_v1:Block] :: Block -> Repeated 8 (Message FactV1)
- Auth.Biscuit.Proto: [$sel:head:RuleV1] :: RuleV1 -> Required 1 (Message PredicateV1)
- Auth.Biscuit.Proto: [$sel:ids:PredicateV1] :: PredicateV1 -> Repeated 2 (Message IDV1)
- Auth.Biscuit.Proto: [$sel:index:Block] :: Block -> Required 1 (Value Int32)
- Auth.Biscuit.Proto: [$sel:keys:Biscuit] :: Biscuit -> Repeated 3 (Value ByteString)
- Auth.Biscuit.Proto: [$sel:name:PredicateV1] :: PredicateV1 -> Required 1 (Value Int64)
- Auth.Biscuit.Proto: [$sel:ops:ExpressionV1] :: ExpressionV1 -> Repeated 1 (Message Op)
- Auth.Biscuit.Proto: [$sel:parameters:Signature] :: Signature -> Repeated 1 (Value ByteString)
- Auth.Biscuit.Proto: [$sel:predicate:FactV1] :: FactV1 -> Required 1 (Message PredicateV1)
- Auth.Biscuit.Proto: [$sel:queries:CheckV1] :: CheckV1 -> Repeated 1 (Message RuleV1)
- Auth.Biscuit.Proto: [$sel:rules_v1:Block] :: Block -> Repeated 9 (Message RuleV1)
- Auth.Biscuit.Proto: [$sel:set:IDSet] :: IDSet -> Repeated 1 (Message IDV1)
- Auth.Biscuit.Proto: [$sel:signature:Biscuit] :: Biscuit -> Required 4 (Message Signature)
- Auth.Biscuit.Proto: [$sel:z:Signature] :: Signature -> Required 2 (Value ByteString)
- Auth.Biscuit.Proto: data IDV1
- Auth.Biscuit.Proto: data PredicateV1
- Auth.Biscuit.Proto: data RuleV1
- Auth.Biscuit.Proto: data Signature
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.BytesConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.CBiscuit
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.CheckV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.ConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.DateConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.ExpressionV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.FactV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.IDSet
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.IDV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.IntConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Policy
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.PredicateV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.RuleV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.SealedBiscuit
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.Signature
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.StringConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.SymbolConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.VerifierPolicies
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.BytesConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.CBiscuit
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.CheckV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.ConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.DateConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.ExpressionV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.FactV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.IDSet
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.IDV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.IntConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Policy
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.PredicateV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.RuleV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.SealedBiscuit
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.Signature
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.StringConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.SymbolConstraintV1
- Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.VerifierPolicies
- Auth.Biscuit.Proto: instance GHC.Enum.Bounded Auth.Biscuit.Proto.PolicyKind
- Auth.Biscuit.Proto: instance GHC.Enum.Enum Auth.Biscuit.Proto.PolicyKind
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.BytesConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.CBiscuit
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.CheckV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.ConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.DateConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.ExpressionV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.FactV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.IDSet
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.IDV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.IntConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Policy
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.PredicateV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.RuleV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.SealedBiscuit
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.Signature
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.StringConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.SymbolConstraintV1
- Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.VerifierPolicies
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.BytesConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.CBiscuit
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.CheckV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.ConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.DateConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.ExpressionV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.FactV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.IDSet
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.IDV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.IntConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Policy
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.PolicyKind
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.PredicateV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.RuleV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.SealedBiscuit
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Signature
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.StringConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.SymbolConstraintV1
- Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.VerifierPolicies
- Auth.Biscuit.Proto: newtype CheckV1
- Auth.Biscuit.Proto: newtype ExpressionV1
- Auth.Biscuit.Proto: newtype FactV1
- Auth.Biscuit.Proto: newtype IDSet
- Auth.Biscuit.Sel: Keypair :: PrivateKey -> PublicKey -> Keypair
- Auth.Biscuit.Sel: Signature :: [ByteString] -> ByteString -> Signature
- Auth.Biscuit.Sel: [parameters] :: Signature -> [ByteString]
- Auth.Biscuit.Sel: [privateKey] :: Keypair -> PrivateKey
- Auth.Biscuit.Sel: [publicKey] :: Keypair -> PublicKey
- Auth.Biscuit.Sel: [z] :: Signature -> ByteString
- Auth.Biscuit.Sel: aggregate :: Signature -> Signature -> IO Signature
- Auth.Biscuit.Sel: data Keypair
- Auth.Biscuit.Sel: data PrivateKey
- Auth.Biscuit.Sel: data PublicKey
- Auth.Biscuit.Sel: data Signature
- Auth.Biscuit.Sel: fromPrivateKey :: PrivateKey -> IO Keypair
- Auth.Biscuit.Sel: hashBytes :: ByteString -> IO ByteString
- Auth.Biscuit.Sel: instance GHC.Classes.Eq Auth.Biscuit.Sel.Keypair
- Auth.Biscuit.Sel: instance GHC.Classes.Eq Auth.Biscuit.Sel.PrivateKey
- Auth.Biscuit.Sel: instance GHC.Classes.Eq Auth.Biscuit.Sel.PublicKey
- Auth.Biscuit.Sel: instance GHC.Classes.Eq Auth.Biscuit.Sel.Signature
- Auth.Biscuit.Sel: instance GHC.Classes.Ord Auth.Biscuit.Sel.Keypair
- Auth.Biscuit.Sel: instance GHC.Classes.Ord Auth.Biscuit.Sel.PrivateKey
- Auth.Biscuit.Sel: instance GHC.Classes.Ord Auth.Biscuit.Sel.PublicKey
- Auth.Biscuit.Sel: instance GHC.Show.Show Auth.Biscuit.Sel.Keypair
- Auth.Biscuit.Sel: instance GHC.Show.Show Auth.Biscuit.Sel.PrivateKey
- Auth.Biscuit.Sel: instance GHC.Show.Show Auth.Biscuit.Sel.PublicKey
- Auth.Biscuit.Sel: instance GHC.Show.Show Auth.Biscuit.Sel.Signature
- Auth.Biscuit.Sel: newKeypair :: IO Keypair
- Auth.Biscuit.Sel: parsePrivateKey :: ByteString -> Maybe PrivateKey
- Auth.Biscuit.Sel: parsePublicKey :: ByteString -> Maybe PublicKey
- Auth.Biscuit.Sel: serializePrivateKey :: PrivateKey -> ByteString
- Auth.Biscuit.Sel: serializePublicKey :: PublicKey -> ByteString
- Auth.Biscuit.Sel: signBlock :: Keypair -> ByteString -> IO Signature
- Auth.Biscuit.Sel: verifySignature :: NonEmpty (PublicKey, ByteString) -> Signature -> IO Bool
- Auth.Biscuit.Token: Biscuit :: Symbols -> (PublicKey, ExistingBlock) -> [(PublicKey, ExistingBlock)] -> Signature -> Biscuit
- Auth.Biscuit.Token: BlockWithRevocationIds :: Block -> ByteString -> ByteString -> BlockWithRevocationIds
- Auth.Biscuit.Token: DatalogError :: ExecutionError -> VerificationError
- Auth.Biscuit.Token: SignatureError :: VerificationError
- Auth.Biscuit.Token: [authority] :: Biscuit -> (PublicKey, ExistingBlock)
- Auth.Biscuit.Token: [bBlock] :: BlockWithRevocationIds -> Block
- Auth.Biscuit.Token: [blocks] :: Biscuit -> [(PublicKey, ExistingBlock)]
- Auth.Biscuit.Token: [genericRevocationId] :: BlockWithRevocationIds -> ByteString
- Auth.Biscuit.Token: [signature] :: Biscuit -> Signature
- Auth.Biscuit.Token: [symbols] :: Biscuit -> Symbols
- Auth.Biscuit.Token: [uniqueRevocationId] :: BlockWithRevocationIds -> ByteString
- Auth.Biscuit.Token: checkBiscuitSignature :: Biscuit -> PublicKey -> IO Bool
- Auth.Biscuit.Token: data BlockWithRevocationIds
- Auth.Biscuit.Token: data VerificationError
- Auth.Biscuit.Token: instance GHC.Classes.Eq Auth.Biscuit.Token.Biscuit
- Auth.Biscuit.Token: instance GHC.Classes.Eq Auth.Biscuit.Token.VerificationError
- Auth.Biscuit.Token: instance GHC.Show.Show Auth.Biscuit.Token.Biscuit
- Auth.Biscuit.Token: instance GHC.Show.Show Auth.Biscuit.Token.VerificationError
- Auth.Biscuit.Token: parseBiscuit :: ByteString -> Either ParseError Biscuit
- Auth.Biscuit.Token: verifyBiscuit :: Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)
- Auth.Biscuit.Token: verifyBiscuitWithLimits :: Limits -> Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)
+ Auth.Biscuit: Antiquote :: SliceType ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: AuthorizationSuccess :: MatchedQuery -> Set Fact -> Set Fact -> Limits -> AuthorizationSuccess
+ Auth.Biscuit: InvalidProof :: ParseError
+ Auth.Biscuit: InvalidSignatures :: ParseError
+ Auth.Biscuit: LBool :: Bool -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: LBytes :: ByteString -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: LDate :: UTCTime -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: LInteger :: Int -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: LString :: Text -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: MatchedQuery :: Query -> Set Bindings -> MatchedQuery
+ Auth.Biscuit: ParserConfig :: BiscuitEncoding -> (Set ByteString -> m Bool) -> (Maybe Int -> PublicKey) -> ParserConfig m
+ Auth.Biscuit: RawBytes :: BiscuitEncoding
+ Auth.Biscuit: RevokedBiscuit :: ParseError
+ Auth.Biscuit: TermSet :: SetType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: UrlBase64 :: BiscuitEncoding
+ Auth.Biscuit: Variable :: VariableType inSet pof -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: [allGeneratedFacts] :: AuthorizationSuccess -> Set Fact
+ Auth.Biscuit: [authorityFacts] :: AuthorizationSuccess -> Set Fact
+ Auth.Biscuit: [bindings] :: MatchedQuery -> Set Bindings
+ Auth.Biscuit: [encoding] :: ParserConfig m -> BiscuitEncoding
+ Auth.Biscuit: [getPublicKey] :: ParserConfig m -> Maybe Int -> PublicKey
+ Auth.Biscuit: [isRevoked] :: ParserConfig m -> Set ByteString -> m Bool
+ Auth.Biscuit: [limits] :: AuthorizationSuccess -> Limits
+ Auth.Biscuit: [matchedAllowQuery] :: AuthorizationSuccess -> MatchedQuery
+ Auth.Biscuit: [matchedQuery] :: MatchedQuery -> Query
+ Auth.Biscuit: asOpen :: Biscuit OpenOrSealed check -> Maybe (Biscuit Open check)
+ Auth.Biscuit: asSealed :: Biscuit OpenOrSealed check -> Maybe (Biscuit Sealed check)
+ Auth.Biscuit: authorizeBiscuit :: Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit: authorizeBiscuitWithLimits :: Limits -> Biscuit a Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit: authorizer :: QuasiQuoter
+ Auth.Biscuit: checkBiscuitSignatures :: BiscuitProof proof => (Maybe Int -> PublicKey) -> Biscuit proof Unverified -> Either ParseError (Biscuit proof Verified)
+ Auth.Biscuit: class BiscuitProof a
+ Auth.Biscuit: class FromValue t
+ Auth.Biscuit: class ToTerm t
+ Auth.Biscuit: data AuthorizationSuccess
+ Auth.Biscuit: data BiscuitEncoding
+ Auth.Biscuit: data MatchedQuery
+ Auth.Biscuit: data Open
+ Auth.Biscuit: data OpenOrSealed
+ Auth.Biscuit: data ParserConfig m
+ Auth.Biscuit: data Sealed
+ Auth.Biscuit: data SecretKey
+ Auth.Biscuit: data Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit: data Unverified
+ Auth.Biscuit: data Verified
+ Auth.Biscuit: fromHex :: MonadFail m => ByteString -> m ByteString
+ Auth.Biscuit: fromOpen :: Biscuit Open check -> Biscuit OpenOrSealed check
+ Auth.Biscuit: fromRevocationList :: (Applicative m, Foldable t) => t ByteString -> Set ByteString -> m Bool
+ Auth.Biscuit: fromSealed :: Biscuit Sealed check -> Biscuit OpenOrSealed check
+ Auth.Biscuit: fromValue :: FromValue t => Value -> Maybe t
+ Auth.Biscuit: getBindings :: AuthorizationSuccess -> Set Bindings
+ Auth.Biscuit: getRevocationIds :: Biscuit proof check -> NonEmpty ByteString
+ Auth.Biscuit: getSingleVariableValue :: (Ord t, FromValue t) => Set Bindings -> Text -> Maybe t
+ Auth.Biscuit: getVariableValues :: (Ord t, FromValue t) => Set Bindings -> Text -> Set t
+ Auth.Biscuit: getVerifiedBiscuitPublicKey :: Biscuit a Verified -> PublicKey
+ Auth.Biscuit: newSecret :: IO SecretKey
+ Auth.Biscuit: parseBiscuitUnverified :: ByteString -> Either ParseError (Biscuit OpenOrSealed Unverified)
+ Auth.Biscuit: parseSecretKey :: ByteString -> Maybe SecretKey
+ Auth.Biscuit: parseSecretKeyHex :: ByteString -> Maybe SecretKey
+ Auth.Biscuit: parseWith :: Applicative m => ParserConfig m -> ByteString -> m (Either ParseError (Biscuit OpenOrSealed Verified))
+ Auth.Biscuit: query :: QuasiQuoter
+ Auth.Biscuit: queryAuthorizerFacts :: AuthorizationSuccess -> Query -> Set Bindings
+ Auth.Biscuit: seal :: Biscuit Open check -> Biscuit Sealed check
+ Auth.Biscuit: serializeSecretKey :: SecretKey -> ByteString
+ Auth.Biscuit: serializeSecretKeyHex :: SecretKey -> ByteString
+ Auth.Biscuit: toPublic :: SecretKey -> PublicKey
+ Auth.Biscuit: toTerm :: ToTerm t => t -> Term' inSet pof 'RegularString
+ Auth.Biscuit: type Authorizer = Authorizer' 'RegularString
+ Auth.Biscuit: type Term = Term' 'NotWithinSet 'InPredicate 'RegularString
+ Auth.Biscuit.Crypto: convert :: (ByteArrayAccess bin, ByteArray bout) => bin -> bout
+ Auth.Biscuit.Crypto: data PublicKey
+ Auth.Biscuit.Crypto: data SecretKey
+ Auth.Biscuit.Crypto: data Signature
+ Auth.Biscuit.Crypto: eitherCryptoError :: CryptoFailable a -> Either CryptoError a
+ Auth.Biscuit.Crypto: generateSecretKey :: MonadRandom m => m SecretKey
+ Auth.Biscuit.Crypto: getSignatureProof :: SignedBlock -> SecretKey -> Signature
+ Auth.Biscuit.Crypto: maybeCryptoError :: CryptoFailable a -> Maybe a
+ Auth.Biscuit.Crypto: publicKey :: ByteArrayAccess ba => ba -> CryptoFailable PublicKey
+ Auth.Biscuit.Crypto: secretKey :: ByteArrayAccess ba => ba -> CryptoFailable SecretKey
+ Auth.Biscuit.Crypto: signBlock :: SecretKey -> ByteString -> IO (SignedBlock, SecretKey)
+ Auth.Biscuit.Crypto: signature :: ByteArrayAccess ba => ba -> CryptoFailable Signature
+ Auth.Biscuit.Crypto: toPublic :: SecretKey -> PublicKey
+ Auth.Biscuit.Crypto: type Blocks = NonEmpty SignedBlock
+ Auth.Biscuit.Crypto: type SignedBlock = (ByteString, Signature, PublicKey)
+ Auth.Biscuit.Crypto: verifyBlocks :: Blocks -> PublicKey -> Bool
+ Auth.Biscuit.Crypto: verifySecretProof :: SecretKey -> SignedBlock -> Bool
+ Auth.Biscuit.Crypto: verifySignatureProof :: Signature -> SignedBlock -> Bool
+ Auth.Biscuit.Datalog.AST: Authorizer :: [Policy' ctx] -> Block' ctx -> Authorizer' (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: AuthorizerPolicy :: Policy' ctx -> AuthorizerElement' ctx
+ Auth.Biscuit.Datalog.AST: class FromValue t
+ Auth.Biscuit.Datalog.AST: class ToTerm t
+ Auth.Biscuit.Datalog.AST: data Authorizer' (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: data AuthorizerElement' ctx
+ Auth.Biscuit.Datalog.AST: data Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: elementToAuthorizer :: AuthorizerElement' ctx -> Authorizer' ctx
+ Auth.Biscuit.Datalog.AST: fromValue :: FromValue t => Value -> Maybe t
+ Auth.Biscuit.Datalog.AST: instance (GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Block' ctx), GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Authorizer' ctx)
+ 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.Block' ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Authorizer' ctx)
+ Auth.Biscuit.Datalog.AST: instance (GHC.Show.Show (Auth.Biscuit.Datalog.AST.Predicate' 'Auth.Biscuit.Datalog.AST.InFact ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.Rule' ctx), GHC.Show.Show (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.AuthorizerElement' 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.Block' ctx), Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.QueryItem' ctx)) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Authorizer' 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.AST: instance Auth.Biscuit.Datalog.AST.FromValue Auth.Biscuit.Datalog.AST.Value
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue Data.ByteString.Internal.ByteString
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue Data.Text.Internal.Text
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue Data.Time.Clock.Internal.UTCTime.UTCTime
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue GHC.Integer.Type.Integer
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue GHC.Types.Bool
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.FromValue GHC.Types.Int
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm Data.ByteString.Internal.ByteString
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm Data.Text.Internal.Text
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm Data.Time.Clock.Internal.UTCTime.UTCTime
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm GHC.Integer.Type.Integer
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm GHC.Types.Bool
+ Auth.Biscuit.Datalog.AST: instance Auth.Biscuit.Datalog.AST.ToTerm GHC.Types.Int
+ Auth.Biscuit.Datalog.AST: instance GHC.Base.Monoid (Auth.Biscuit.Datalog.AST.Authorizer' ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Base.Semigroup (Auth.Biscuit.Datalog.AST.Authorizer' ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Expression' ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Classes.Eq (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Expression' ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Classes.Ord (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Show.Show (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Expression' ctx)
+ Auth.Biscuit.Datalog.AST: instance GHC.Show.Show (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => GHC.Show.Show (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
+ Auth.Biscuit.Datalog.AST: instance Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet 'Auth.Biscuit.Datalog.AST.InPredicate ctx) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Expression' ctx)
+ Auth.Biscuit.Datalog.AST: instance Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Term' 'Auth.Biscuit.Datalog.AST.NotWithinSet pof ctx) => Language.Haskell.TH.Syntax.Lift (Auth.Biscuit.Datalog.AST.Predicate' pof ctx)
+ Auth.Biscuit.Datalog.AST: toTerm :: ToTerm t => t -> Term' inSet pof 'RegularString
+ Auth.Biscuit.Datalog.AST: type Authorizer = Authorizer' 'RegularString
+ Auth.Biscuit.Datalog.AST: type QQTerm = Term' 'NotWithinSet 'InPredicate 'QuasiQuote
+ Auth.Biscuit.Datalog.AST: type Term = Term' 'NotWithinSet 'InPredicate 'RegularString
+ Auth.Biscuit.Datalog.Executor: MatchedQuery :: Query -> Set Bindings -> MatchedQuery
+ Auth.Biscuit.Datalog.Executor: [bindings] :: MatchedQuery -> Set Bindings
+ Auth.Biscuit.Datalog.Executor: [matchedQuery] :: MatchedQuery -> Query
+ Auth.Biscuit.Datalog.Executor: checkCheck :: Limits -> Set Fact -> Check -> Validation (NonEmpty Check) ()
+ Auth.Biscuit.Datalog.Executor: checkPolicy :: Limits -> Set Fact -> Policy -> Maybe (Either MatchedQuery MatchedQuery)
+ Auth.Biscuit.Datalog.Executor: data MatchedQuery
+ Auth.Biscuit.Datalog.Executor: getBindingsForRuleBody :: Limits -> Set Fact -> [Predicate] -> [Expression] -> Set Bindings
+ Auth.Biscuit.Datalog.Executor: getFactsForRule :: Limits -> Set Fact -> Rule -> Set Fact
+ Auth.Biscuit.Datalog.Executor: instance GHC.Classes.Eq Auth.Biscuit.Datalog.Executor.Limits
+ Auth.Biscuit.Datalog.Executor: instance GHC.Classes.Eq Auth.Biscuit.Datalog.Executor.MatchedQuery
+ Auth.Biscuit.Datalog.Executor: instance GHC.Show.Show Auth.Biscuit.Datalog.Executor.Limits
+ Auth.Biscuit.Datalog.Executor: instance GHC.Show.Show Auth.Biscuit.Datalog.Executor.MatchedQuery
+ Auth.Biscuit.Datalog.Parser: authorizer :: QuasiQuoter
+ Auth.Biscuit.Datalog.Parser: authorizerParser :: (HasParsers 'InPredicate ctx, HasParsers 'InFact ctx, Show (AuthorizerElement' ctx)) => Parser (Authorizer' ctx)
+ Auth.Biscuit.Datalog.Parser: blockParser :: (HasParsers 'InPredicate ctx, HasParsers 'InFact ctx, Show (BlockElement' ctx)) => Parser (Block' ctx)
+ Auth.Biscuit.Datalog.Parser: query :: QuasiQuoter
+ Auth.Biscuit.Datalog.ScopedExecutor: AuthorizationSuccess :: MatchedQuery -> Set Fact -> Set Fact -> Limits -> AuthorizationSuccess
+ Auth.Biscuit.Datalog.ScopedExecutor: Facts :: PureExecError
+ Auth.Biscuit.Datalog.ScopedExecutor: Iterations :: PureExecError
+ Auth.Biscuit.Datalog.ScopedExecutor: World :: Set Fact -> Set Rule -> World
+ Auth.Biscuit.Datalog.ScopedExecutor: [allGeneratedFacts] :: AuthorizationSuccess -> Set Fact
+ Auth.Biscuit.Datalog.ScopedExecutor: [authorityFacts] :: AuthorizationSuccess -> Set Fact
+ Auth.Biscuit.Datalog.ScopedExecutor: [facts] :: World -> Set Fact
+ Auth.Biscuit.Datalog.ScopedExecutor: [limits] :: AuthorizationSuccess -> Limits
+ Auth.Biscuit.Datalog.ScopedExecutor: [matchedAllowQuery] :: AuthorizationSuccess -> MatchedQuery
+ Auth.Biscuit.Datalog.ScopedExecutor: [rules] :: World -> Set Rule
+ Auth.Biscuit.Datalog.ScopedExecutor: computeAllFacts :: World -> StateT ComputeState (Either PureExecError) ()
+ Auth.Biscuit.Datalog.ScopedExecutor: data AuthorizationSuccess
+ Auth.Biscuit.Datalog.ScopedExecutor: data PureExecError
+ Auth.Biscuit.Datalog.ScopedExecutor: data World
+ Auth.Biscuit.Datalog.ScopedExecutor: getBindings :: AuthorizationSuccess -> Set Bindings
+ Auth.Biscuit.Datalog.ScopedExecutor: getSingleVariableValue :: (Ord t, FromValue t) => Set Bindings -> Text -> Maybe t
+ Auth.Biscuit.Datalog.ScopedExecutor: getVariableValues :: (Ord t, FromValue t) => Set Bindings -> Text -> Set t
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Base.Monoid Auth.Biscuit.Datalog.ScopedExecutor.World
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Base.Semigroup Auth.Biscuit.Datalog.ScopedExecutor.World
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Classes.Eq Auth.Biscuit.Datalog.ScopedExecutor.AuthorizationSuccess
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Classes.Eq Auth.Biscuit.Datalog.ScopedExecutor.PureExecError
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Show.Show Auth.Biscuit.Datalog.ScopedExecutor.AuthorizationSuccess
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Show.Show Auth.Biscuit.Datalog.ScopedExecutor.PureExecError
+ Auth.Biscuit.Datalog.ScopedExecutor: instance GHC.Show.Show Auth.Biscuit.Datalog.ScopedExecutor.World
+ Auth.Biscuit.Datalog.ScopedExecutor: queryAuthorizerFacts :: AuthorizationSuccess -> Query -> Set Bindings
+ Auth.Biscuit.Datalog.ScopedExecutor: runAuthorizer :: BlockWithRevocationId -> [BlockWithRevocationId] -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit.Datalog.ScopedExecutor: runAuthorizerNoTimeout :: Limits -> BlockWithRevocationId -> [BlockWithRevocationId] -> Authorizer -> Either ExecutionError AuthorizationSuccess
+ Auth.Biscuit.Datalog.ScopedExecutor: runAuthorizerWithLimits :: Limits -> BlockWithRevocationId -> [BlockWithRevocationId] -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit.Datalog.ScopedExecutor: runFactGeneration :: Limits -> World -> Either PureExecError (Set Fact)
+ Auth.Biscuit.Datalog.ScopedExecutor: type BlockWithRevocationId = (Block, ByteString)
+ Auth.Biscuit.Proto: CheckV2 :: Repeated 1 (Message RuleV2) -> CheckV2
+ Auth.Biscuit.Proto: Ed25519 :: Algorithm
+ Auth.Biscuit.Proto: ExpressionV2 :: Repeated 1 (Message Op) -> ExpressionV2
+ Auth.Biscuit.Proto: FactV2 :: Required 1 (Message PredicateV2) -> FactV2
+ 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: ProofSecret :: Required 1 (Value ByteString) -> Proof
+ Auth.Biscuit.Proto: ProofSignature :: Required 2 (Value ByteString) -> Proof
+ Auth.Biscuit.Proto: PublicKey :: Required 1 (Enumeration Algorithm) -> Required 2 (Value ByteString) -> PublicKey
+ Auth.Biscuit.Proto: RuleV2 :: Required 1 (Message PredicateV2) -> Repeated 2 (Message PredicateV2) -> Repeated 3 (Message ExpressionV2) -> RuleV2
+ Auth.Biscuit.Proto: SignedBlock :: Required 1 (Value ByteString) -> Required 2 (Message PublicKey) -> Required 3 (Value ByteString) -> SignedBlock
+ Auth.Biscuit.Proto: TermBool :: Required 6 (Value Bool) -> TermV2
+ Auth.Biscuit.Proto: TermBytes :: Required 5 (Value ByteString) -> TermV2
+ Auth.Biscuit.Proto: TermDate :: Required 4 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermInteger :: Required 2 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermSet :: Repeated 1 (Message TermV2) -> TermSet
+ Auth.Biscuit.Proto: TermString :: Required 3 (Value Int64) -> TermV2
+ Auth.Biscuit.Proto: TermTermSet :: Required 7 (Message TermSet) -> TermV2
+ Auth.Biscuit.Proto: TermVariable :: Required 1 (Value Int32) -> TermV2
+ Auth.Biscuit.Proto: VerifyEd25519Signature :: TernaryKind
+ Auth.Biscuit.Proto: [$sel:algorithm:PublicKey] :: PublicKey -> Required 1 (Enumeration Algorithm)
+ Auth.Biscuit.Proto: [$sel:block:SignedBlock] :: SignedBlock -> Required 1 (Value ByteString)
+ 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:key:PublicKey] :: PublicKey -> Required 2 (Value ByteString)
+ 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:nextKey:SignedBlock] :: SignedBlock -> Required 2 (Message PublicKey)
+ 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:proof:Biscuit] :: Biscuit -> Required 4 (Message Proof)
+ Auth.Biscuit.Proto: [$sel:queries:CheckV2] :: CheckV2 -> Repeated 1 (Message RuleV2)
+ Auth.Biscuit.Proto: [$sel:rootKeyId:Biscuit] :: Biscuit -> Optional 1 (Value Int32)
+ Auth.Biscuit.Proto: [$sel:rules_v2:Block] :: Block -> Repeated 5 (Message RuleV2)
+ Auth.Biscuit.Proto: [$sel:set:TermSet] :: TermSet -> Repeated 1 (Message TermV2)
+ Auth.Biscuit.Proto: [$sel:signature:SignedBlock] :: SignedBlock -> Required 3 (Value ByteString)
+ Auth.Biscuit.Proto: [$sel:terms:PredicateV2] :: PredicateV2 -> Repeated 2 (Message TermV2)
+ Auth.Biscuit.Proto: data Algorithm
+ Auth.Biscuit.Proto: data PredicateV2
+ Auth.Biscuit.Proto: data Proof
+ Auth.Biscuit.Proto: data PublicKey
+ Auth.Biscuit.Proto: data RuleV2
+ Auth.Biscuit.Proto: data SignedBlock
+ Auth.Biscuit.Proto: data TermV2
+ Auth.Biscuit.Proto: data TernaryKind
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.BytesConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.CheckV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.ConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.DateConstraintV2
+ 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.IntConstraintV2
+ 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.Proof
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.PublicKey
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.RuleV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.SignedBlock
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.StringConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.SymbolConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.TermSet
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Decode.Decode Auth.Biscuit.Proto.TermV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.BytesConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.CheckV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.ConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.DateConstraintV2
+ 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.IntConstraintV2
+ 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.Proof
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.PublicKey
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.RuleV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.SignedBlock
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.StringConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.SymbolConstraintV2
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.TermSet
+ Auth.Biscuit.Proto: instance Data.ProtocolBuffers.Encode.Encode Auth.Biscuit.Proto.TermV2
+ Auth.Biscuit.Proto: instance GHC.Enum.Bounded Auth.Biscuit.Proto.Algorithm
+ Auth.Biscuit.Proto: instance GHC.Enum.Bounded Auth.Biscuit.Proto.TernaryKind
+ Auth.Biscuit.Proto: instance GHC.Enum.Enum Auth.Biscuit.Proto.Algorithm
+ Auth.Biscuit.Proto: instance GHC.Enum.Enum Auth.Biscuit.Proto.TernaryKind
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.BytesConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.CheckV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.ConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.DateConstraintV2
+ 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.IntConstraintV2
+ 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.Proof
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.PublicKey
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.RuleV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.SignedBlock
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.StringConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.SymbolConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.TermSet
+ Auth.Biscuit.Proto: instance GHC.Generics.Generic Auth.Biscuit.Proto.TermV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.Algorithm
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.BytesConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.CheckV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.ConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.DateConstraintV2
+ 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.IntConstraintV2
+ 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.Proof
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.PublicKey
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.RuleV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.SignedBlock
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.StringConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.SymbolConstraintV2
+ Auth.Biscuit.Proto: instance GHC.Show.Show Auth.Biscuit.Proto.TermSet
+ 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 CheckV2
+ Auth.Biscuit.Proto: newtype ExpressionV2
+ Auth.Biscuit.Proto: newtype FactV2
+ Auth.Biscuit.Proto: newtype OpTernary
+ Auth.Biscuit.Proto: newtype TermSet
+ Auth.Biscuit.ProtoBufAdapter: pbToProof :: Proof -> Either String (Either Signature SecretKey)
+ Auth.Biscuit.ProtoBufAdapter: pbToSignedBlock :: SignedBlock -> Either String SignedBlock
+ Auth.Biscuit.ProtoBufAdapter: signedBlockToPb :: SignedBlock -> SignedBlock
+ Auth.Biscuit.Token: InvalidProof :: ParseError
+ Auth.Biscuit.Token: InvalidSignatures :: ParseError
+ Auth.Biscuit.Token: ParserConfig :: BiscuitEncoding -> (Set ByteString -> m Bool) -> (Maybe Int -> PublicKey) -> ParserConfig m
+ Auth.Biscuit.Token: RawBytes :: BiscuitEncoding
+ Auth.Biscuit.Token: RevokedBiscuit :: ParseError
+ Auth.Biscuit.Token: UrlBase64 :: BiscuitEncoding
+ Auth.Biscuit.Token: [encoding] :: ParserConfig m -> BiscuitEncoding
+ Auth.Biscuit.Token: [getPublicKey] :: ParserConfig m -> Maybe Int -> PublicKey
+ Auth.Biscuit.Token: [isRevoked] :: ParserConfig m -> Set ByteString -> m Bool
+ Auth.Biscuit.Token: asOpen :: Biscuit OpenOrSealed check -> Maybe (Biscuit Open check)
+ Auth.Biscuit.Token: asSealed :: Biscuit OpenOrSealed check -> Maybe (Biscuit Sealed check)
+ Auth.Biscuit.Token: authority :: Biscuit proof check -> ParsedSignedBlock
+ Auth.Biscuit.Token: authorizeBiscuit :: Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit.Token: authorizeBiscuitWithLimits :: Limits -> Biscuit a Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)
+ Auth.Biscuit.Token: blocks :: Biscuit proof check -> [ParsedSignedBlock]
+ Auth.Biscuit.Token: checkBiscuitSignatures :: BiscuitProof proof => (Maybe Int -> PublicKey) -> Biscuit proof Unverified -> Either ParseError (Biscuit proof Verified)
+ Auth.Biscuit.Token: class BiscuitProof a
+ Auth.Biscuit.Token: data BiscuitEncoding
+ Auth.Biscuit.Token: data Open
+ Auth.Biscuit.Token: data OpenOrSealed
+ Auth.Biscuit.Token: data ParserConfig m
+ Auth.Biscuit.Token: data Sealed
+ Auth.Biscuit.Token: data Unverified
+ Auth.Biscuit.Token: data Verified
+ Auth.Biscuit.Token: fromOpen :: Biscuit Open check -> Biscuit OpenOrSealed check
+ Auth.Biscuit.Token: fromSealed :: Biscuit Sealed check -> Biscuit OpenOrSealed check
+ Auth.Biscuit.Token: getVerifiedBiscuitPublicKey :: Biscuit a Verified -> PublicKey
+ Auth.Biscuit.Token: instance (GHC.Classes.Eq proof, GHC.Classes.Eq check) => GHC.Classes.Eq (Auth.Biscuit.Token.Biscuit proof check)
+ Auth.Biscuit.Token: instance (GHC.Show.Show proof, GHC.Show.Show check) => GHC.Show.Show (Auth.Biscuit.Token.Biscuit proof check)
+ Auth.Biscuit.Token: instance Auth.Biscuit.Token.BiscuitProof Auth.Biscuit.Token.Open
+ Auth.Biscuit.Token: instance Auth.Biscuit.Token.BiscuitProof Auth.Biscuit.Token.OpenOrSealed
+ Auth.Biscuit.Token: instance Auth.Biscuit.Token.BiscuitProof Auth.Biscuit.Token.Sealed
+ Auth.Biscuit.Token: instance GHC.Classes.Eq Auth.Biscuit.Token.OpenOrSealed
+ Auth.Biscuit.Token: instance GHC.Classes.Eq Auth.Biscuit.Token.Unverified
+ Auth.Biscuit.Token: instance GHC.Classes.Eq Auth.Biscuit.Token.Verified
+ Auth.Biscuit.Token: instance GHC.Show.Show Auth.Biscuit.Token.OpenOrSealed
+ Auth.Biscuit.Token: instance GHC.Show.Show Auth.Biscuit.Token.Unverified
+ Auth.Biscuit.Token: instance GHC.Show.Show Auth.Biscuit.Token.Verified
+ Auth.Biscuit.Token: parseBiscuitUnverified :: ByteString -> Either ParseError (Biscuit OpenOrSealed Unverified)
+ Auth.Biscuit.Token: parseBiscuitWith :: Applicative m => ParserConfig m -> ByteString -> m (Either ParseError (Biscuit OpenOrSealed Verified))
+ Auth.Biscuit.Token: proof :: Biscuit proof check -> proof
+ Auth.Biscuit.Token: proofCheck :: Biscuit proof check -> check
+ Auth.Biscuit.Token: rootKeyId :: Biscuit proof check -> Maybe Int
+ Auth.Biscuit.Token: seal :: Biscuit Open check -> Biscuit Sealed check
+ Auth.Biscuit.Token: symbols :: Biscuit proof check -> Symbols
+ Auth.Biscuit.Token: toPossibleProofs :: BiscuitProof a => a -> OpenOrSealed
+ Auth.Biscuit.Token: type ParsedSignedBlock = (ExistingBlock, Signature, PublicKey)
- Auth.Biscuit: InvalidProtobuf :: String -> ParseError
+ Auth.Biscuit: InvalidProtobuf :: Bool -> String -> ParseError
- Auth.Biscuit: InvalidProtobufSer :: String -> ParseError
+ Auth.Biscuit: InvalidProtobufSer :: Bool -> String -> ParseError
- Auth.Biscuit: Limits :: Int -> Int -> Int -> Bool -> Bool -> (ByteString -> IO (Either () ())) -> Limits
+ Auth.Biscuit: Limits :: Int -> Int -> Int -> Bool -> Bool -> Limits
- Auth.Biscuit: addBlock :: Block -> Biscuit -> IO Biscuit
+ Auth.Biscuit: addBlock :: Block -> Biscuit Open check -> IO (Biscuit Open check)
- Auth.Biscuit: data Biscuit
+ Auth.Biscuit: data Biscuit proof check
- Auth.Biscuit: mkBiscuit :: Keypair -> Block -> IO Biscuit
+ Auth.Biscuit: mkBiscuit :: SecretKey -> Block -> IO (Biscuit Open Verified)
- Auth.Biscuit: parse :: ByteString -> Either ParseError Biscuit
+ Auth.Biscuit: parse :: PublicKey -> ByteString -> Either ParseError (Biscuit OpenOrSealed Verified)
- Auth.Biscuit: parseB64 :: ByteString -> Either ParseError Biscuit
+ Auth.Biscuit: parseB64 :: PublicKey -> ByteString -> Either ParseError (Biscuit OpenOrSealed Verified)
- Auth.Biscuit: serialize :: Biscuit -> ByteString
+ Auth.Biscuit: serialize :: BiscuitProof p => Biscuit p Verified -> ByteString
- Auth.Biscuit: serializeB64 :: Biscuit -> ByteString
+ Auth.Biscuit: serializeB64 :: BiscuitProof p => Biscuit p Verified -> ByteString
- Auth.Biscuit.Datalog.AST: Antiquote :: SliceType ctx -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: Antiquote :: SliceType ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: BlockElement :: BlockElement' ctx -> VerifierElement' ctx
+ Auth.Biscuit.Datalog.AST: BlockElement :: BlockElement' ctx -> AuthorizerElement' ctx
- Auth.Biscuit.Datalog.AST: EValue :: ID' 'NotWithinSet 'InPredicate ctx -> Expression' (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: EValue :: Term' 'NotWithinSet 'InPredicate ctx -> Expression' (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: LBool :: Bool -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: LBool :: Bool -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: LBytes :: ByteString -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: LBytes :: ByteString -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: LDate :: UTCTime -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: LDate :: UTCTime -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: LInteger :: Int -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: LInteger :: Int -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: LString :: Text -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: LString :: Text -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: Predicate :: Text -> [ID' 'NotWithinSet pof ctx] -> Predicate' (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: Predicate :: Text -> [Term' 'NotWithinSet pof ctx] -> Predicate' (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: TermSet :: SetType inSet ctx -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: TermSet :: SetType inSet ctx -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: VOp :: ID -> Op
+ Auth.Biscuit.Datalog.AST: VOp :: Term -> Op
- Auth.Biscuit.Datalog.AST: Variable :: VariableType inSet pof -> ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
+ Auth.Biscuit.Datalog.AST: Variable :: VariableType inSet pof -> Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs)
- Auth.Biscuit.Datalog.AST: [terms] :: Predicate' (pof :: PredicateOrFact) (ctx :: ParsedAs) -> [ID' 'NotWithinSet pof ctx]
+ Auth.Biscuit.Datalog.AST: [terms] :: Predicate' (pof :: PredicateOrFact) (ctx :: ParsedAs) -> [Term' 'NotWithinSet pof ctx]
- Auth.Biscuit.Datalog.AST: [vBlock] :: Verifier' (ctx :: ParsedAs) -> Block' ctx
+ Auth.Biscuit.Datalog.AST: [vBlock] :: Authorizer' (ctx :: ParsedAs) -> Block' ctx
- Auth.Biscuit.Datalog.AST: [vPolicies] :: Verifier' (ctx :: ParsedAs) -> [Policy' ctx]
+ Auth.Biscuit.Datalog.AST: [vPolicies] :: Authorizer' (ctx :: ParsedAs) -> [Policy' ctx]
- Auth.Biscuit.Datalog.AST: toSetTerm :: Value -> Maybe (ID' 'WithinSet 'InFact 'RegularString)
+ Auth.Biscuit.Datalog.AST: toSetTerm :: Value -> Maybe (Term' 'WithinSet 'InFact 'RegularString)
- Auth.Biscuit.Datalog.AST: type Value = ID' 'NotWithinSet 'InFact 'RegularString
+ Auth.Biscuit.Datalog.AST: type Value = Term' 'NotWithinSet 'InFact 'RegularString
- Auth.Biscuit.Datalog.Executor: DenyRuleMatched :: [Check] -> Query -> ResultError
+ Auth.Biscuit.Datalog.Executor: DenyRuleMatched :: [Check] -> MatchedQuery -> ResultError
- Auth.Biscuit.Datalog.Executor: Limits :: Int -> Int -> Int -> Bool -> Bool -> (ByteString -> IO (Either () ())) -> Limits
+ Auth.Biscuit.Datalog.Executor: Limits :: Int -> Int -> Int -> Bool -> Bool -> Limits
- Auth.Biscuit.Datalog.Parser: termParser :: forall inSet pof ctx. HasTermParsers inSet pof ctx => Parser (ID' inSet pof ctx)
+ Auth.Biscuit.Datalog.Parser: termParser :: forall inSet pof ctx. HasTermParsers inSet pof ctx => Parser (Term' inSet pof ctx)
- Auth.Biscuit.Example: privateKey' :: PrivateKey
+ Auth.Biscuit.Example: privateKey' :: SecretKey
- Auth.Biscuit.Proto: Biscuit :: Required 1 (Value ByteString) -> Repeated 2 (Value ByteString) -> Repeated 3 (Value ByteString) -> Required 4 (Message Signature) -> Biscuit
+ Auth.Biscuit.Proto: Biscuit :: Optional 1 (Value Int32) -> Required 2 (Message SignedBlock) -> Repeated 3 (Message SignedBlock) -> Required 4 (Message Proof) -> Biscuit
- Auth.Biscuit.Proto: Block :: Required 1 (Value Int32) -> Repeated 2 (Value Text) -> Optional 6 (Value Text) -> Optional 7 (Value Int32) -> Repeated 8 (Message FactV1) -> Repeated 9 (Message RuleV1) -> Repeated 10 (Message CheckV1) -> Block
+ 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) -> Block
- Auth.Biscuit.Proto: OpVValue :: Required 1 (Message IDV1) -> Op
+ Auth.Biscuit.Proto: OpVValue :: Required 1 (Message TermV2) -> Op
- Auth.Biscuit.Proto: [$sel:authority:Biscuit] :: Biscuit -> Required 1 (Value ByteString)
+ Auth.Biscuit.Proto: [$sel:authority:Biscuit] :: Biscuit -> Required 2 (Message SignedBlock)
- Auth.Biscuit.Proto: [$sel:blocks:Biscuit] :: Biscuit -> Repeated 2 (Value ByteString)
+ Auth.Biscuit.Proto: [$sel:blocks:Biscuit] :: Biscuit -> Repeated 3 (Message SignedBlock)
- Auth.Biscuit.Proto: [$sel:context:Block] :: Block -> Optional 6 (Value Text)
+ Auth.Biscuit.Proto: [$sel:context:Block] :: Block -> Optional 2 (Value Text)
- Auth.Biscuit.Proto: [$sel:symbols:Block] :: Block -> Repeated 2 (Value Text)
+ Auth.Biscuit.Proto: [$sel:symbols:Block] :: Block -> Repeated 1 (Value Text)
- Auth.Biscuit.Proto: [$sel:version:Block] :: Block -> Optional 7 (Value Int32)
+ Auth.Biscuit.Proto: [$sel:version:Block] :: Block -> Optional 3 (Value Int32)
- Auth.Biscuit.ProtoBufAdapter: blockToPb :: Symbols -> Int -> Block -> (Symbols, Block)
+ Auth.Biscuit.ProtoBufAdapter: blockToPb :: Symbols -> Block -> (Symbols, Block)
- Auth.Biscuit.Token: InvalidProtobuf :: String -> ParseError
+ Auth.Biscuit.Token: InvalidProtobuf :: Bool -> String -> ParseError
- Auth.Biscuit.Token: InvalidProtobufSer :: String -> ParseError
+ Auth.Biscuit.Token: InvalidProtobufSer :: Bool -> String -> ParseError
- Auth.Biscuit.Token: addBlock :: Block -> Biscuit -> IO Biscuit
+ Auth.Biscuit.Token: addBlock :: Block -> Biscuit Open check -> IO (Biscuit Open check)
- Auth.Biscuit.Token: data Biscuit
+ Auth.Biscuit.Token: data Biscuit proof check
- Auth.Biscuit.Token: getRevocationIds :: Biscuit -> IO (NonEmpty BlockWithRevocationIds)
+ Auth.Biscuit.Token: getRevocationIds :: Biscuit proof check -> NonEmpty ByteString
- Auth.Biscuit.Token: mkBiscuit :: Keypair -> Block -> IO Biscuit
+ Auth.Biscuit.Token: mkBiscuit :: SecretKey -> Block -> IO (Biscuit Open Verified)
- Auth.Biscuit.Token: serializeBiscuit :: Biscuit -> ByteString
+ Auth.Biscuit.Token: serializeBiscuit :: BiscuitProof p => Biscuit p Verified -> ByteString
Files
- ChangeLog.md +7/−1
- README.md +109/−1
- benchmarks/Bench.hs +23/−0
- biscuit-haskell.cabal +24/−14
- src/Auth/Biscuit.hs +283/−96
- src/Auth/Biscuit/Crypto.hs +106/−0
- src/Auth/Biscuit/Datalog/AST.hs +139/−109
- src/Auth/Biscuit/Datalog/Executor.hs +84/−237
- src/Auth/Biscuit/Datalog/Parser.hs +77/−47
- src/Auth/Biscuit/Datalog/ScopedExecutor.hs +312/−0
- src/Auth/Biscuit/Example.hs +10/−10
- src/Auth/Biscuit/Proto.hs +103/−110
- src/Auth/Biscuit/ProtoBufAdapter.hs +126/−93
- src/Auth/Biscuit/Sel.hs +0/−334
- src/Auth/Biscuit/Token.hs +402/−162
- test/Spec.hs +23/−18
- test/Spec/Crypto.hs +0/−129
- test/Spec/Executor.hs +36/−37
- test/Spec/NewCrypto.hs +212/−0
- test/Spec/Parser.hs +107/−118
- test/Spec/Quasiquoter.hs +14/−16
- test/Spec/RevocationIds.hs +42/−19
- test/Spec/Roundtrip.hs +40/−38
- test/Spec/SampleReader.hs +234/−0
- test/Spec/Samples.hs +0/−103
- test/Spec/ScopedExecutor.hs +211/−0
- test/Spec/Verification.hs +60/−53
ChangeLog.md view
@@ -1,3 +1,9 @@ # Changelog for biscuit-haskell -## Unreleased changes+## 0.1.1.0++Bugfix for `serializeB64` and `serializeHex`.++## 0.1.0.0++Basic biscuit support.
README.md view
@@ -1,1 +1,109 @@-# biscuit-haskell+# biscuit-haskell [![CI-badge][CI-badge]][CI-url] [![Hackage][hackage]][hackage-url]++<img src="https://raw.githubusercontent.com/divarvel/biscuit-haskell/main/assets/biscuit-logo.png" align=right>++Main library for biscuit tokens support, providing minting and signature verification of biscuit tokens, as well as a datalog engine allowing to compute the validity of a token in a given context.++## Supported biscuit versions++The core library supports [`v2` biscuits][v2spec] (both open and sealed).++## How to use this library++This library was designed with the use of [`QuasiQuotes`][quasiquotes] in mind.++A [minimal example][biscuitexample] is provided in the library itself, and the [package documentation][packagedoc] contains comprehensive examples and explanations for all the library features.++Familiarity with biscuit tokens will make the examples easier to follow.+Reading the [biscuit presentation][biscuit] and the [biscuit tutorial][biscuittutorial] is advised.++### Checking a biscuit token++To make sure a biscuit token is valid, two checks have to take place:++- a signature check with a public key, making sure the token is authentic+- a datalog check making sure the token is authorized for the given context++```haskell+-- public keys are typically serialized as hex-encoded strings.+-- In most cases they will be read from a config file or an environment+-- variable+publicKey' :: PublicKey+publicKey' = case parsePublicKeyHex "todo" of+ Nothing -> error "Error parsing public key"+ Just k -> k++-- this function takes a base64-encoded biscuit in a bytestring, parses it,+-- checks it signature and its validity. Here the provided context is just+-- the current time (useful for TTL checks). In most cases, the provided context+-- will carry a permissions check for the endpoint being accessed.+verification :: ByteString -> IO Bool+verification serialized = do+ now <- getCurrentTime+ -- biscuits are typically serialized as base64 bytestrings. The publicKey is needed+ -- to check the biscuit integrity before completely deserializing it+ biscuit <- either (fail . show) pure $ parseB64 publicKey' serialized+ -- the verifier can carry facts (like here), but also checks or policies.+ -- verifiers are defined inline, directly in datalog, through the `verifier`+ -- quasiquoter. datalog parsing and validation happens at compile time, but+ -- can still reference haskell variables.+ let authorizer' = [authorizer|time(${now});+ allow if true;+ |]+ -- `authorizeBiscuit` only works on valid biscuits, and runs the datalog verifications+ -- ensuring the biscuit is authorized in a given context+ result <- authorizeBiscuit biscuit authorizer'+ case result of+ Left e -> print e $> False+ Right _ -> pure True+```++### Creating (and attenuating) biscuit tokens++Biscuit tokens are created from a secret key, and can be attenuated without it.++```haskell+-- secret keys are typically serialized as hex-encoded strings.+-- In most cases they will be read from a config file or an environment+-- variable (env vars or another secret management system are favored,+-- since the secret key is sensitive information).+-- A random secret key can be generated with `generateSecretKey`+secretKey' :: SecretKey+secretKey' = case parseSecretPrivateKeyHex "todo" of+ Nothing -> error "Error parsing secret key"+ Just k -> k++creation :: IO ByteString+creation = do+ -- biscuit tokens carry an authority block, which contents are guaranteed by the+ -- secret key.+ -- Blocks are defined inline, directly in datalog, through the `block`+ -- quasiquoter. datalog parsing and validation happens at compile time, but+ -- can still reference haskell variables.+ let authority = [block|+ // toto+ resource("file1");+ |]+ biscuit <- mkBiscuit secretKey authority+ -- biscuits can be attenuated with blocks. blocks are not guaranteed by the secret key and+ -- should only restrict the token use. This property is guaranteed by the datalog evaluation:+ -- facts and rules declared in a block cannot interact with previous blocks.+ -- Here, the block only adds a TTL check.+ let block1 = [block|check if time($time), $time < 2021-05-08T00:00:00Z;|]+ -- `addBlock` only takes a block and a biscuit, the secret key is not needed:+ -- any biscuit can be attenuated by its holder.+ newBiscuit <- addBlock block1 biscuit+ pure $ serializeB64 newBiscuit+```++[CI-badge]: https://img.shields.io/github/workflow/status/Divarvel/biscuit-haskell/CI?style=flat-square+[CI-url]: https://github.com/Divarvel/biscuit-haskell/actions+[Hackage]: https://img.shields.io/hackage/v/biscuit-haskell?color=purple&style=flat-square+[hackage-url]: https://hackage.haskell.org/package/biscuit-haskell+[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+[quasiquotes]: https://wiki.haskell.org/Quasiquotation+[biscuitexample]: https://github.com/divarvel/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
+ benchmarks/Bench.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE QuasiQuotes #-}+import Criterion.Main++import Auth.Biscuit+import Data.Maybe (fromJust)++buildToken :: SecretKey -> IO (Biscuit Open Verified)+buildToken sk = do+ mkBiscuit sk [block|user_id("user_1234");|]++-- Our benchmark harness.+main = do+ sk <- newSecret+ biscuit <- buildToken sk+ let pk = toPublic sk+ let biscuitBs = serialize biscuit+ defaultMain [+ bgroup "biscuit" [ bench "mkBiscuit" $ whnfIO (buildToken sk)+ , bench "parse" $ whnf (parse pk) biscuitBs+ , bench "serialize" $ whnf serialize biscuit+ , bench "verify" $ whnfIO (authorizeBiscuit biscuit [authorizer|allow if user_id("user_1234");|])+ ]+ ]
biscuit-haskell.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.0 name: biscuit-haskell-version: 0.1.1.0+version: 0.2.0.0 category: Security synopsis: Library support for the Biscuit security token description: Please see the README on GitHub at <https://github.com/divarvel/biscuit-haskell#readme>@@ -25,13 +25,14 @@ exposed-modules: Auth.Biscuit Auth.Biscuit.Utils+ Auth.Biscuit.Crypto Auth.Biscuit.Datalog.AST Auth.Biscuit.Datalog.Executor Auth.Biscuit.Datalog.Parser+ Auth.Biscuit.Datalog.ScopedExecutor Auth.Biscuit.Example Auth.Biscuit.Proto Auth.Biscuit.ProtoBufAdapter- Auth.Biscuit.Sel Auth.Biscuit.Timer Auth.Biscuit.Token other-modules:@@ -44,20 +45,20 @@ build-depends: base >= 4.7 && <5, async ^>= 2.2,- base16-bytestring ^>= 0.1.0,+ base16-bytestring ^>= 1.0, bytestring ^>= 0.10, text ^>= 1.2, containers ^>= 0.6,+ cryptonite >= 0.27 && < 0.30,+ memory ^>= 0.15, template-haskell ^>= 2.16, attoparsec ^>= 0.13,- primitive ^>= 0.7, base64 ^>= 0.4, cereal ^>= 0.5,- libsodium ^>= 1.0, mtl ^>= 2.2, parser-combinators ^>= 1.2, protobuf ^>= 0.2,- random ^>= 1.1,+ random >= 1.0 && < 1.3, regex-tdfa ^>= 1.3, th-lift-instances ^>= 0.1, time ^>= 1.9,@@ -75,16 +76,14 @@ async , attoparsec , base >=4.7 && <5- , base16-bytestring ^>=0.1.0+ , base16-bytestring ^>=1.0 , base64 , biscuit-haskell , bytestring , cereal , containers- , libsodium , mtl , parser-combinators- , primitive , protobuf , random , template-haskell@@ -98,13 +97,14 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:- Spec.Crypto+ Spec.NewCrypto Spec.Executor Spec.Parser Spec.Quasiquoter Spec.RevocationIds Spec.Roundtrip- Spec.Samples+ Spec.SampleReader+ Spec.ScopedExecutor Spec.Verification Paths_biscuit_haskell hs-source-dirs:@@ -112,18 +112,18 @@ ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: async+ , aeson , attoparsec , base >=4.7 && <5- , base16-bytestring ^>=0.1+ , base16-bytestring ^>=1.0 , base64 , biscuit-haskell , bytestring , cereal , containers- , libsodium+ , cryptonite , mtl , parser-combinators- , primitive , protobuf , random , tasty@@ -134,3 +134,13 @@ , time , validation-selective default-language: Haskell2010++benchmark biscuit-bench+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ hs-source-dirs: benchmarks+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T+ build-depends: base+ , criterion+ , biscuit-haskell
src/Auth/Biscuit.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE EmptyDataDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-|@@ -13,178 +14,364 @@ -- $biscuitOverview -- * Creating keypairs- newKeypair- , fromPrivateKey- , PrivateKey+ -- $keypairs+ newSecret+ , toPublic+ , SecretKey , PublicKey- , Keypair (..) -- ** Parsing and serializing keypairs- , serializePrivateKeyHex+ , serializeSecretKeyHex , serializePublicKeyHex- , parsePrivateKeyHex+ , parseSecretKeyHex , parsePublicKeyHex- , serializePrivateKey+ , serializeSecretKey , serializePublicKey- , parsePrivateKey+ , parseSecretKey , parsePublicKey -- * Creating a biscuit+ -- $biscuitBlocks+ , mkBiscuit , block , blockContext- , mkBiscuit- , addBlock , Biscuit+ , OpenOrSealed+ , Open+ , Sealed+ , Verified+ , Unverified+ , BiscuitProof , Block -- ** Parsing and serializing biscuits- , serializeB64 , parseB64 , parse+ , parseWith+ , parseBiscuitUnverified+ , checkBiscuitSignatures+ , BiscuitEncoding (..)+ , ParserConfig (..)+ , fromRevocationList+ , serializeB64 , serialize- , parseHex- , serializeHex+ , fromHex+ -- ** Attenuating biscuits+ -- $attenuatingBiscuits+ , addBlock+ -- $sealedBiscuits+ , seal+ , fromOpen+ , fromSealed+ , asOpen+ , asSealed -- * Verifying a biscuit- , verifier- , verifyBiscuit- , verifyBiscuitWithLimits- , checkBiscuitSignature+ -- $verifying+ , authorizer+ , Authorizer+ , authorizeBiscuit+ , authorizeBiscuitWithLimits+ , Limits (..) , defaultLimits- , Verifier , ParseError (..)- , VerificationError (..) , ExecutionError (..)- , Limits (..)+ , AuthorizationSuccess (..)+ , MatchedQuery (..)+ , query+ , queryAuthorizerFacts+ , getBindings+ , getVariableValues+ , getSingleVariableValue+ , ToTerm (..)+ , FromValue (..)+ , Term+ , Term' (..)++ -- * Retrieving information from a biscuit+ , getRevocationIds+ , getVerifiedBiscuitPublicKey ) where -import Control.Monad ((<=<))-import Data.Bifunctor (first)-import Data.ByteString (ByteString)-import qualified Data.ByteString.Base16 as Hex-import qualified Data.ByteString.Base64.URL as B64-import Data.Text (Text)+import Control.Monad ((<=<))+import Control.Monad.Identity (runIdentity)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base16 as Hex+import qualified Data.ByteString.Base64.URL as B64+import Data.Foldable (toList)+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text) -import Auth.Biscuit.Datalog.AST (Block, Verifier, bContext)-import Auth.Biscuit.Datalog.Executor (ExecutionError (..),- Limits (..), defaultLimits)-import Auth.Biscuit.Datalog.Parser (block, verifier)-import Auth.Biscuit.Sel (Keypair (..), PrivateKey,- PublicKey, fromPrivateKey,- newKeypair, parsePrivateKey,- parsePublicKey,- serializePrivateKey,- serializePublicKey)-import Auth.Biscuit.Token (Biscuit, ParseError (..),- VerificationError (..),- addBlock, checkBiscuitSignature,- mkBiscuit, parseBiscuit,- serializeBiscuit, verifyBiscuit,- verifyBiscuitWithLimits)-import Auth.Biscuit.Utils (maybeToRight)+import Auth.Biscuit.Crypto (PublicKey, SecretKey,+ convert,+ generateSecretKey,+ maybeCryptoError,+ publicKey, secretKey,+ toPublic)+import Auth.Biscuit.Datalog.AST (Authorizer, Block,+ FromValue (..), Term,+ Term' (..), ToTerm (..),+ bContext)+import Auth.Biscuit.Datalog.Executor (ExecutionError (..),+ Limits (..),+ MatchedQuery (..),+ defaultLimits)+import Auth.Biscuit.Datalog.Parser (authorizer, block, query)+import Auth.Biscuit.Datalog.ScopedExecutor (AuthorizationSuccess (..),+ getBindings,+ getSingleVariableValue,+ getVariableValues,+ queryAuthorizerFacts)+import Auth.Biscuit.Token (Biscuit,+ BiscuitEncoding (..),+ BiscuitProof (..), Open,+ OpenOrSealed,+ ParseError (..),+ ParserConfig (..), Sealed,+ Unverified, Verified,+ addBlock, asOpen,+ asSealed,+ authorizeBiscuit,+ authorizeBiscuitWithLimits,+ checkBiscuitSignatures,+ fromOpen, fromSealed,+ getRevocationIds,+ getVerifiedBiscuitPublicKey,+ mkBiscuit,+ parseBiscuitUnverified,+ parseBiscuitWith, seal,+ serializeBiscuit) + -- $biscuitOverview -- -- <https://github.com/CleverCloud/biscuit/blob/master/SUMMARY.md Biscuit> is a /bearer token/,--- allowing /offline attenuation/ (meaning that anyone having a token can restrict its use),--- and /public key verification/. Token rights and attenuation are expressed using a logic language.+-- allowing /offline attenuation/ (meaning that anyone having a token can craft a new, more+-- restricted token),+-- and /'PublicKey' verification/. Token rights and attenuation are expressed using a logic+-- language, derived from <todo datalog>. Such a language can describe facts (things we know+-- about the world), rules (describing how to derive new facts from existing ones) and checks+-- (ensuring that facts hold). Facts and checks let you describe access control rules, while+-- rules make them modular. /Authorizer policies/ lets the verifying party ensure that a+-- provided biscuit grants access to the required operations. -- -- Here's how to create a biscuit token: ----- > buildToken :: Keypair -> IO Biscuit+-- > -- Biscuit Open Verified means the token has valid signatures+-- > -- and is open to further restriction+-- > buildToken :: Keypair -> IO (Biscuit Open Verified) -- > buildToken keypair =+-- > -- 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|+-- > // the two first lines describe facts: -- > // the token holder is identified as `user_1234`--- > user(#authority, "user_1234");+-- > user("user_1234"); -- > // the token holder is granted access to resource `file1`--- > resource(#authority, "file1");+-- > resource("file1");+-- > // this last line defines a restriction: properties that need+-- > // to be verified for the token to be verified: -- > // the token can only be used before a specified date--- > check if time(#ambient, $time), $time < 2021-05-08T00:00:00Z;+-- > check if time($time), $time < 2021-05-08T00:00:00Z; -- > |] -- -- Here's how to attenuate a biscuit token: ----- > restrictToken :: Biscuit -> IO Biscuit+-- > restrictToken :: Biscuit Open Verified -> IO Biscuit Open Verified -- > restrictToken = -- > addBlock [block| -- > // restrict the token to local use only--- > check if user_ip_address(#ambient, "127.0.0.1");+-- > check if user_ip_address("127.0.0.1"); -- > |] ----- Here's how to verify a biscuit token:+-- To verify a biscuit token, we need two things: ----- > verifyToken :: PublicKey -> Biscuit -> IO Bool--- > verifyToken publicKey biscuit = do--- > now <- getCurrentTime--- > let verif = [verifier|--- > // the datalog snippets can reference haskell variables--- > current_time(#ambient, ${now});--- >--- > // policies are tried in order--- > allow if resource(#authority, "file1");--- > // catch-all policy if the previous ones did not match--- > deny if true;--- > |]--- > result <- verifyBiscuit biscuit [verifier|current_time()|]--- > case result of+-- - a public key, that will let us verify the token has been emitted by+-- a trusted authority+-- - an authorizer, that will make sure all the checks declared in the token are fulfilled,+-- as well as providing its own checks, and policies which decide if the token is+-- verified or not+--+-- Here's how to verify a base64-serialized biscuit token:+--+-- > verifyToken :: PublicKey -> ByteString -> IO Bool+-- > verifyToken publicKey token = do+-- > -- complete parsing is only attempted if signatures can be verified,+-- > -- that's the reason why 'parseB64' takes a public key as a parameter+-- > parseResult <- parseB64 publicKey token+-- > case parseResult of -- > Left e -> print e $> False--- > Right _ -> pure True+-- > Right biscuit -> do+-- > now <- getCurrentTime+-- > let verif = [authorizer|+-- > // the datalog snippets can reference haskell variables+-- > // with the ${variableName} syntax+-- > time(${now});+-- >+-- > // policies are tried in order. The first matching policy+-- > // will decide if the token is valid or not. If no policies+-- > // match, the token will fail validation+-- > allow if resource("file1");+-- > // catch-all policy if the previous ones did not match+-- > deny if true;+-- > |]+-- > result <- authorizeBiscuit biscuit [authorizer|current_time()|]+-- > case result of+-- > Left e -> print e $> False+-- > Right _ -> pure True -- | Build a block containing an explicit context value. -- The context of a block can't be parsed from datalog currently, -- so you'll need an explicit call to `blockContext` to add it ----- > [block|check if time(#ambient, $t), $t < 2021-01-01;|]+-- > [block|check if time($t), $t < 2021-01-01;|] -- > <> blockContext "ttl-check" blockContext :: Text -> Block blockContext c = mempty { bContext = Just c } -- | Decode a base16-encoded bytestring, reporting errors via `MonadFail` fromHex :: MonadFail m => ByteString -> m ByteString-fromHex input = do- (decoded, "") <- pure $ Hex.decode input- pure decoded+fromHex = either fail pure . Hex.decode --- | Get an hex bytestring from a private key-serializePrivateKeyHex :: PrivateKey -> ByteString-serializePrivateKeyHex = Hex.encode . serializePrivateKey+-- $keypairs+--+-- 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. --- | Get an hex bytestring from a public key+-- | Generate a new random 'SecretKey'+newSecret :: IO SecretKey+newSecret = generateSecretKey++-- | Serialize a 'SecretKey' to raw bytes, without any encoding+serializeSecretKey :: SecretKey -> ByteString+serializeSecretKey = convert++-- | Serialize a 'PublicKey' to raw bytes, without any encoding+serializePublicKey :: PublicKey -> ByteString+serializePublicKey = convert++-- | Serialize a 'SecretKey' to a hex-encoded bytestring+serializeSecretKeyHex :: SecretKey -> ByteString+serializeSecretKeyHex = Hex.encode . convert++-- | Serialize a 'PublicKey' to a hex-encoded bytestring serializePublicKeyHex :: PublicKey -> ByteString-serializePublicKeyHex = Hex.encode . serializePublicKey+serializePublicKeyHex = Hex.encode . convert --- | Read a private key from an hex bytestring-parsePrivateKeyHex :: ByteString -> Maybe PrivateKey-parsePrivateKeyHex = parsePrivateKey <=< fromHex+-- | Read a 'SecretKey' from raw bytes+parseSecretKey :: ByteString -> Maybe SecretKey+parseSecretKey = maybeCryptoError . secretKey --- | Read a public key from an hex bytestring+-- | Read a 'SecretKey' from an hex bytestring+parseSecretKeyHex :: ByteString -> Maybe SecretKey+parseSecretKeyHex = parseSecretKey <=< fromHex++-- | Read a 'PublicKey' from raw bytes+parsePublicKey :: ByteString -> Maybe PublicKey+parsePublicKey = maybeCryptoError . publicKey++-- | Read a 'PublicKey' from an hex bytestring parsePublicKeyHex :: ByteString -> Maybe PublicKey parsePublicKeyHex = parsePublicKey <=< fromHex -- | Parse a biscuit from a raw bytestring. If you want to parse -- from a URL-compatible base 64 bytestring, consider using `parseB64`--- instead-parse :: ByteString -> Either ParseError Biscuit-parse = parseBiscuit+-- instead.+-- The biscuit signature is verified with the provided 'PublicKey' before+-- completely decoding blocks+-- The revocation ids are /not/ verified before completely decoding blocks.+-- If you need to check revocation ids before decoding blocks, use 'parseWith'+-- (or 'parseB64With' instead).+parse :: PublicKey -> ByteString -> Either ParseError (Biscuit OpenOrSealed Verified)+parse pk = runIdentity . parseBiscuitWith ParserConfig+ { encoding = RawBytes+ , isRevoked = const $ pure False+ , getPublicKey = pure pk+ } -- | Parse a biscuit from a URL-compatible base 64 encoded bytestring-parseB64 :: ByteString -> Either ParseError Biscuit-parseB64 = parse <=< first (const InvalidB64Encoding) . B64.decodeBase64+parseB64 :: PublicKey -> ByteString -> Either ParseError (Biscuit OpenOrSealed Verified)+parseB64 pk = runIdentity . parseBiscuitWith ParserConfig+ { encoding = UrlBase64+ , isRevoked = const $ pure False+ , getPublicKey = pure pk+ } --- | Parse a biscuit from an hex-encoded bytestring-parseHex :: ByteString -> Either ParseError Biscuit-parseHex = parse <=< maybeToRight InvalidHexEncoding . fromHex+-- | Parse a biscuit, with explicitly supplied parsing options:+--+-- - encoding ('RawBytes' or 'UrlBase64')+-- - revocation check+-- - public key (based on the token's @rootKeyId@ field)+--+-- If you don't need dynamic public key selection or revocation checks, you can use+-- 'parse' or 'parseB64' instead.+--+-- The biscuit signature is verified with the selected 'PublicKey' before+-- completely decoding blocks+parseWith :: Applicative m+ => ParserConfig m+ -> ByteString+ -> m (Either ParseError (Biscuit OpenOrSealed Verified))+parseWith = parseBiscuitWith +-- | Helper for building a revocation check from a static list, suitable for use with+-- 'parseWith' and 'ParserConfig'.+fromRevocationList :: (Applicative m, Foldable t)+ => t ByteString+ -> Set ByteString+ -> m Bool+fromRevocationList revokedIds tokenIds =+ pure . not . null $ Set.intersection (Set.fromList $ toList revokedIds) tokenIds+ -- | Serialize a biscuit to a binary format. If you intend to send--- the biscuit over a text channel, consider using `serializeB64` or--- `serializeHex` instead-serialize :: Biscuit -> ByteString+-- the biscuit over a text channel, consider using `serializeB64` instead+serialize :: BiscuitProof p => Biscuit p Verified -> ByteString serialize = serializeBiscuit -- | Serialize a biscuit to URL-compatible base 64, as recommended by the spec-serializeB64 :: Biscuit -> ByteString+serializeB64 :: BiscuitProof p => Biscuit p Verified -> ByteString serializeB64 = B64.encodeBase64' . serialize --- | Serialize a biscuit to a hex (base 16) string. Be advised that the specs--- recommends base 64 instead.-serializeHex :: Biscuit -> ByteString-serializeHex = Hex.encode . serialize+-- $biscuitBlocks+--+-- The core of a biscuit is its authority block. This block declares facts and rules and+-- is signed by its creator with a secret key. In addition to this trusted, authority+-- block, a biscuit may carry extra blocks that can only restrict what it can do. By+-- default, biscuits can be restricted, but it's possible to seal a biscuit and prevent+-- further modifications.+--+-- Blocks are defined with a logic language (datalog) that can be used directly from haskell+-- with the `QuasiQuotes` extension.++-- $attenuatingBiscuits+--+-- By default, biscuits can be /attenuated/. It means that any party that holds a biscuit can+-- craft a new biscuit with fewer rights. A common example is taking a long-lived biscuit and+-- adding a short TTL right before sending it over the wire.++-- $sealedBiscuits+--+-- An 'Open' biscuit can be turned into a 'Sealed' one, meaning it won't be possible+-- to attenuate it further.+--+-- 'mkBiscuit' creates 'Open' biscuits, while 'parse' returns an 'OpenOrSealed' biscuit (since+-- when you're verifying a biscuit, you're not caring about whether it can be extended further+-- or not). 'authorizeBiscuit' does not care whether a biscuit is 'Open' or 'Sealed' and can be+-- used with both. 'addBlock' and 'seal' only work with 'Open' biscuits.++-- $verifying+--+-- Verifying a biscuit requires providing a list of policies (/allow/ or /deny/), which will+-- decide if the biscuit is accepted. Policies are tried in order, and the first one to match+-- decides whether the biscuit is accepted.+--+-- In addition to policies, an authorizer typically provides facts (such as the current time) so+-- that checks and policies can be verified.+--+-- The authorizer checks and policies only see the content of the authority (first) block. Extra+-- blocks can only carry restrictions and cannot interfere with the authority facts.+
+ src/Auth/Biscuit/Crypto.hs view
@@ -0,0 +1,106 @@+module Auth.Biscuit.Crypto+ ( SignedBlock+ , Blocks+ , signBlock+ , verifyBlocks+ , verifySecretProof+ , verifySignatureProof+ , getSignatureProof++ -- Ed25519 reexports+ , PublicKey+ , SecretKey+ , Signature+ , convert+ , publicKey+ , secretKey+ , signature+ , eitherCryptoError+ , maybeCryptoError+ , generateSecretKey+ , toPublic+ ) where++import Control.Arrow ((&&&))+import Crypto.Error (eitherCryptoError, maybeCryptoError)+import Crypto.PubKey.Ed25519+import Data.ByteArray (convert)+import Data.ByteString (ByteString)+import Data.Int (Int32)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE++import qualified Auth.Biscuit.Proto as PB+import qualified Data.Serialize as PB++type SignedBlock = (ByteString, Signature, PublicKey)+type Blocks = NonEmpty SignedBlock++-- | 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+-- serializing itself is handled by protobuf, but we still need to manually+-- serialize keys when we include them in something we want sign (block+-- signatures, and the final signature for sealed tokens).+serializePublicKey :: PublicKey -> ByteString+serializePublicKey pk =+ let keyBytes = convert 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 algBytes <> keyBytes++signBlock :: SecretKey+ -> ByteString+ -> IO (SignedBlock, SecretKey)+signBlock sk payload = do+ let pk = toPublic sk+ (nextPk, nextSk) <- (toPublic &&& id) <$> generateSecretKey+ let toSign = payload <> serializePublicKey nextPk+ sig = sign sk pk toSign+ pure ((payload, sig, nextPk), nextSk)++getSignatureProof :: SignedBlock -> SecretKey -> Signature+getSignatureProof (lastPayload, lastSig, lastPk) nextSecret =+ let sk = nextSecret+ pk = toPublic nextSecret+ toSign = lastPayload <> serializePublicKey lastPk <> convert lastSig+ in sign sk pk toSign++getToSig :: (ByteString, a, PublicKey) -> ByteString+getToSig (p, _, nextPk) =+ p <> serializePublicKey nextPk++getSignature :: SignedBlock -> Signature+getSignature (_, sig, _) = sig++getPublicKey :: SignedBlock -> PublicKey+getPublicKey (_, _, pk) = pk++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 = rootPk :| NE.init (getPublicKey <$> blocks)+ keysPayloadsSigs = NE.zipWith attachKey keys (NE.zip toSigs sigs)+ in all (uncurry3 verify) keysPayloadsSigs++verifySecretProof :: SecretKey+ -> SignedBlock+ -> Bool+verifySecretProof nextSecret (_, _, lastPk) =+ lastPk == toPublic nextSecret++verifySignatureProof :: Signature+ -> SignedBlock+ -> Bool+verifySignatureProof extraSig (lastPayload, lastSig, lastPk) =+ let toSign = lastPayload <> serializePublicKey lastPk <> convert lastSig+ in verify lastPk toSign extraSig
src/Auth/Biscuit/Datalog/AST.hs view
@@ -31,8 +31,10 @@ , Expression , Expression' (..) , Fact- , ID- , ID' (..)+ , ToTerm (..)+ , FromValue (..)+ , Term+ , Term' (..) , IsWithinSet (..) , Op (..) , ParsedAs (..)@@ -42,7 +44,7 @@ , Predicate , Predicate' (..) , PredicateOrFact (..)- , QQID+ , QQTerm , Query , Query' , QueryItem' (..)@@ -54,11 +56,11 @@ , Unary (..) , Value , VariableType- , Verifier- , Verifier' (..)- , VerifierElement' (..)+ , Authorizer+ , Authorizer' (..)+ , AuthorizerElement' (..) , elementToBlock- , elementToVerifier+ , elementToAuthorizer , fromStack , listSymbolsInBlock , renderBlock@@ -96,7 +98,7 @@ deriving newtype (Eq, Show, Ord, IsString) instance Lift Slice where- lift (Slice name) = [| toLiteralId $(varE $ mkName name) |]+ lift (Slice name) = [| toTerm $(varE $ mkName name) |] liftTyped = unsafeTExpCoerce . lift type family SliceType (ctx :: ParsedAs) where@@ -104,16 +106,14 @@ SliceType 'QuasiQuote = Slice type family SetType (inSet :: IsWithinSet) (ctx :: ParsedAs) where- SetType 'NotWithinSet ctx = Set (ID' 'WithinSet 'InFact ctx)+ SetType 'NotWithinSet ctx = Set (Term' 'WithinSet 'InFact ctx) SetType 'WithinSet ctx = Void -- | A single datalog item. -- | This can be a value, a set of items, or a slice (a value that will be injected later), -- | depending on the context-data ID' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs) =- Symbol Text- -- ^ A symbol (eg. @#authority@)- | Variable (VariableType inSet pof)+data Term' (inSet :: IsWithinSet) (pof :: PredicateOrFact) (ctx :: ParsedAs) =+ Variable (VariableType inSet pof) -- ^ A variable (eg. @$0@) | LInteger Int -- ^ An integer literal (eg. @42@)@@ -133,33 +133,32 @@ deriving instance ( Eq (VariableType inSet pof) , Eq (SliceType ctx) , Eq (SetType inSet ctx)- ) => Eq (ID' inSet pof ctx)+ ) => Eq (Term' inSet pof ctx) deriving instance ( Ord (VariableType inSet pof) , Ord (SliceType ctx) , Ord (SetType inSet ctx)- ) => Ord (ID' inSet pof ctx)+ ) => Ord (Term' inSet pof ctx) deriving instance ( Show (VariableType inSet pof) , Show (SliceType ctx) , Show (SetType inSet ctx)- ) => Show (ID' inSet pof ctx)+ ) => Show (Term' inSet pof ctx) -- | In a regular AST, slices have already been eliminated-type ID = ID' 'NotWithinSet 'InPredicate 'RegularString+type Term = Term' 'NotWithinSet 'InPredicate 'RegularString -- | In an AST parsed from a QuasiQuoter, there might be references to haskell variables-type QQID = ID' 'NotWithinSet 'InPredicate 'QuasiQuote+type QQTerm = Term' 'NotWithinSet 'InPredicate 'QuasiQuote -- | A term that is not a variable-type Value = ID' 'NotWithinSet 'InFact 'RegularString+type Value = Term' 'NotWithinSet 'InFact 'RegularString -- | An element of a set-type SetValue = ID' 'WithinSet 'InFact 'RegularString+type SetValue = Term' 'WithinSet 'InFact 'RegularString instance ( Lift (VariableType inSet pof) , Lift (SetType inSet ctx) , Lift (SliceType ctx) )- => Lift (ID' inSet pof ctx) where- lift (Symbol n) = [| Symbol n |]+ => Lift (Term' inSet pof ctx) where lift (Variable n) = [| Variable n |] lift (LInteger i) = [| LInteger i |] lift (LString s) = [| LString s |]@@ -173,47 +172,76 @@ -- | This class describes how to turn a haskell value into a datalog value. -- | This is used when slicing a haskell variable in a datalog expression-class ToLiteralId t where+class ToTerm t where -- | How to turn a value into a datalog item- toLiteralId :: t -> ID' inSet pof 'RegularString+ toTerm :: t -> Term' inSet pof 'RegularString -instance ToLiteralId Int where- toLiteralId = LInteger+-- | This class describes how to turn a datalog value into a regular haskell value.+class FromValue t where+ fromValue :: Value -> Maybe t -instance ToLiteralId Integer where- toLiteralId = LInteger . fromIntegral+instance ToTerm Int where+ toTerm = LInteger -instance ToLiteralId Text where- toLiteralId = LString+instance FromValue Int where+ fromValue (LInteger v) = Just v+ fromValue _ = Nothing -instance ToLiteralId Bool where- toLiteralId = LBool+instance ToTerm Integer where+ toTerm = LInteger . fromIntegral -instance ToLiteralId ByteString where- toLiteralId = LBytes+instance FromValue Integer where+ fromValue (LInteger v) = Just (fromIntegral v)+ fromValue _ = Nothing -instance ToLiteralId UTCTime where- toLiteralId = LDate+instance ToTerm Text where+ toTerm = LString +instance FromValue Text where+ fromValue (LString t) = Just t+ fromValue _ = Nothing++instance ToTerm Bool where+ toTerm = LBool++instance FromValue Bool where+ fromValue (LBool b) = Just b+ fromValue _ = Nothing++instance ToTerm ByteString where+ toTerm = LBytes++instance FromValue ByteString where+ fromValue (LBytes bs) = Just bs+ fromValue _ = Nothing++instance ToTerm UTCTime where+ toTerm = LDate++instance FromValue UTCTime where+ fromValue (LDate t) = Just t+ fromValue _ = Nothing++instance FromValue Value where+ fromValue = Just+ toSetTerm :: Value- -> Maybe (ID' 'WithinSet 'InFact 'RegularString)+ -> Maybe (Term' 'WithinSet 'InFact 'RegularString) toSetTerm = \case- Symbol i -> Just $ Symbol i- LInteger i -> Just $ LInteger i- LString i -> Just $ LString i- LDate i -> Just $ LDate i- LBytes i -> Just $ LBytes i- LBool i -> Just $ LBool i- TermSet _ -> Nothing- Variable v -> absurd v+ LInteger i -> Just $ LInteger i+ LString i -> Just $ LString i+ LDate i -> Just $ LDate i+ LBytes i -> Just $ LBytes i+ LBool i -> Just $ LBool i+ TermSet _ -> Nothing+ Variable v -> absurd v Antiquote v -> absurd v renderId' :: (VariableType inSet pof -> Text) -> (SetType inSet ctx -> Text) -> (SliceType ctx -> Text)- -> ID' inSet pof ctx -> Text+ -> Term' inSet pof ctx -> Text renderId' var set slice = \case- Symbol name -> "#" <> name Variable name -> var name LInteger int -> pack $ show int LString str -> pack $ show str@@ -225,20 +253,20 @@ Antiquote v -> slice v renderSet :: (SliceType ctx -> Text)- -> Set (ID' 'WithinSet 'InFact ctx)+ -> Set (Term' 'WithinSet 'InFact ctx) -> Text renderSet slice terms = "[" <> intercalate "," (renderId' absurd absurd slice <$> Set.toList terms) <> "]" -renderId :: ID -> Text+renderId :: Term -> Text renderId = renderId' ("$" <>) (renderSet absurd) absurd -renderFactId :: ID' 'NotWithinSet 'InFact 'RegularString -> Text+renderFactId :: Term' 'NotWithinSet 'InFact 'RegularString -> Text renderFactId = renderId' absurd (renderSet absurd) absurd -listSymbolsInTerm :: ID -> Set.Set Text+listSymbolsInTerm :: Term -> Set.Set Text listSymbolsInTerm = \case- Symbol name -> Set.singleton name+ LString v -> Set.singleton v Variable name -> Set.singleton name TermSet terms -> foldMap listSymbolsInSetValue terms Antiquote v -> absurd v@@ -246,7 +274,7 @@ listSymbolsInValue :: Value -> Set.Set Text listSymbolsInValue = \case- Symbol name -> Set.singleton name+ LString v -> Set.singleton v TermSet terms -> foldMap listSymbolsInSetValue terms Variable v -> absurd v Antiquote v -> absurd v@@ -254,25 +282,25 @@ listSymbolsInSetValue :: SetValue -> Set.Set Text listSymbolsInSetValue = \case- Symbol name -> Set.singleton name- TermSet v -> absurd v- Variable v -> absurd v- Antiquote v -> absurd v- _ -> mempty+ LString v -> Set.singleton v+ TermSet v -> absurd v+ Variable v -> absurd v+ Antiquote v -> absurd v+ _ -> mempty data Predicate' (pof :: PredicateOrFact) (ctx :: ParsedAs) = Predicate { name :: Text- , terms :: [ID' 'NotWithinSet pof ctx]+ , terms :: [Term' 'NotWithinSet pof ctx] } -deriving instance ( Eq (ID' 'NotWithinSet pof ctx)+deriving instance ( Eq (Term' 'NotWithinSet pof ctx) ) => Eq (Predicate' pof ctx)-deriving instance ( Ord (ID' 'NotWithinSet pof ctx)+deriving instance ( Ord (Term' 'NotWithinSet pof ctx) ) => Ord (Predicate' pof ctx)-deriving instance ( Show (ID' 'NotWithinSet pof ctx)+deriving instance ( Show (Term' 'NotWithinSet pof ctx) ) => Show (Predicate' pof ctx) -deriving instance Lift (ID' 'NotWithinSet pof ctx) => Lift (Predicate' pof ctx)+deriving instance Lift (Term' 'NotWithinSet pof ctx) => Lift (Predicate' pof ctx) type Predicate = Predicate' 'InPredicate 'RegularString type Fact = Predicate' 'InFact 'RegularString@@ -403,25 +431,25 @@ deriving (Eq, Ord, Show, Lift) data Expression' (ctx :: ParsedAs) =- EValue (ID' 'NotWithinSet 'InPredicate ctx)+ EValue (Term' 'NotWithinSet 'InPredicate ctx) | EUnary Unary (Expression' ctx) | EBinary Binary (Expression' ctx) (Expression' ctx) -deriving instance Eq (ID' 'NotWithinSet 'InPredicate ctx) => Eq (Expression' ctx)-deriving instance Ord (ID' 'NotWithinSet 'InPredicate ctx) => Ord (Expression' ctx)-deriving instance Lift (ID' 'NotWithinSet 'InPredicate ctx) => Lift (Expression' ctx)-deriving instance Show (ID' 'NotWithinSet 'InPredicate ctx) => Show (Expression' ctx)+deriving instance Eq (Term' 'NotWithinSet 'InPredicate ctx) => Eq (Expression' ctx)+deriving instance Ord (Term' 'NotWithinSet 'InPredicate ctx) => Ord (Expression' ctx)+deriving instance Lift (Term' 'NotWithinSet 'InPredicate ctx) => Lift (Expression' ctx)+deriving instance Show (Term' 'NotWithinSet 'InPredicate ctx) => Show (Expression' ctx) type Expression = Expression' 'RegularString listSymbolsInExpression :: Expression -> Set.Set Text listSymbolsInExpression = \case- EValue t -> listSymbolsInTerm t- EUnary _ e -> listSymbolsInExpression e+ EValue t -> listSymbolsInTerm t+ EUnary _ e -> listSymbolsInExpression e EBinary _ e e' -> foldMap listSymbolsInExpression [e, e'] data Op =- VOp ID+ VOp Term | UOp Unary | BOp Binary @@ -457,35 +485,37 @@ <> renderExpression e' <> ")" in \case- EValue t -> renderId t- EUnary Negate e -> "!" <> renderExpression e- EUnary Parens e -> "(" <> renderExpression e <> ")"- EUnary Length e -> renderExpression e <> ".length()"+ EValue t -> renderId t+ EUnary Negate e -> "!" <> renderExpression e+ EUnary Parens e -> "(" <> renderExpression e <> ")"+ EUnary Length e -> renderExpression e <> ".length()" 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 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'- EBinary Regex e e' -> rm "matches" e e'- EBinary Intersection e e' -> rm "intersection" e e'- EBinary Union e e' -> rm "union" e e'- EBinary Add e e' -> rOp "+" e e'- EBinary Sub e e' -> rOp "-" e e'- EBinary Mul e e' -> rOp "*" e e'- EBinary Div e e' -> rOp "/" e e'- EBinary And e e' -> rOp "&&" e e'- EBinary Or 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'+ EBinary Regex e e' -> rm "matches" e e'+ EBinary Intersection e e' -> rm "intersection" e e'+ EBinary Union e e' -> rm "union" e e'+ EBinary Add e e' -> rOp "+" e e'+ EBinary Sub e e' -> rOp "-" e e'+ EBinary Mul e e' -> rOp "*" e e'+ EBinary Div e e' -> rOp "/" e e'+ EBinary And e e' -> rOp "&&" e e'+ EBinary Or e e' -> rOp "||" e e' -- | A biscuit block, containing facts, rules and checks. ----- 'Block' has a 'Monoid' instance, this is the expected way+-- 'Block' has a 'Monoid' instance, which is the expected way -- to build composite blocks (eg if you need to generate a list of facts): ----- > -- build a block containing a list of facts `value("a"); value("b"); value("c");`.--- > foldMap (\v -> [block| value(${v}) |]) ["a", "b", "c"]+-- > -- build a block from multiple variables v1, v2, v3+-- > [block| value(${v1}); |] <>+-- > [block| value(${v2}); |] <>+-- > [block| value(${v3}); |] type Block = Block' 'RegularString -- | A biscuit block, that may or may not contain slices referencing@@ -543,40 +573,40 @@ , foldMap listSymbolsInCheck bChecks ] --- | A biscuit verifier, containing, facts, rules, checks and policies-type Verifier = Verifier' 'RegularString+-- | A biscuit authorizer, containing, facts, rules, checks and policies+type Authorizer = Authorizer' 'RegularString -- | The context in which a biscuit policies and checks are verified.--- A verifier may add policies (`deny if` / `allow if` conditions), as well as rules, facts, and checks.--- A verifier may or may not contain slices referencing haskell variables.-data Verifier' (ctx :: ParsedAs) = Verifier+-- A authorizer may add policies (`deny if` / `allow if` conditions), as well as rules, facts, and checks.+-- A authorizer may or may not contain slices referencing haskell variables.+data Authorizer' (ctx :: ParsedAs) = Authorizer { vPolicies :: [Policy' ctx] -- ^ the allow / deny policies. , vBlock :: Block' ctx -- ^ the facts, rules and checks } -instance Semigroup (Verifier' ctx) where- v1 <> v2 = Verifier { vPolicies = vPolicies v1 <> vPolicies v2+instance Semigroup (Authorizer' ctx) where+ v1 <> v2 = Authorizer { vPolicies = vPolicies v1 <> vPolicies v2 , vBlock = vBlock v1 <> vBlock v2 } -instance Monoid (Verifier' ctx) where- mempty = Verifier { vPolicies = []+instance Monoid (Authorizer' ctx) where+ mempty = Authorizer { vPolicies = [] , vBlock = mempty } deriving instance ( Eq (Block' ctx) , Eq (QueryItem' ctx)- ) => Eq (Verifier' ctx)+ ) => Eq (Authorizer' ctx) deriving instance ( Show (Block' ctx) , Show (QueryItem' ctx)- ) => Show (Verifier' ctx)+ ) => Show (Authorizer' ctx) deriving instance ( Lift (Block' ctx) , Lift (QueryItem' ctx)- ) => Lift (Verifier' ctx)+ ) => Lift (Authorizer' ctx) data BlockElement' ctx = BlockFact (Predicate' 'InFact ctx)@@ -596,16 +626,16 @@ BlockCheck c -> Block [] [] [c] Nothing BlockComment -> mempty -data VerifierElement' ctx- = VerifierPolicy (Policy' ctx)+data AuthorizerElement' ctx+ = AuthorizerPolicy (Policy' ctx) | BlockElement (BlockElement' ctx) deriving instance ( Show (Predicate' 'InFact ctx) , Show (Rule' ctx) , Show (QueryItem' ctx)- ) => Show (VerifierElement' ctx)+ ) => Show (AuthorizerElement' ctx) -elementToVerifier :: VerifierElement' ctx -> Verifier' ctx-elementToVerifier = \case- VerifierPolicy p -> Verifier [p] mempty- BlockElement be -> Verifier [] (elementToBlock be)+elementToAuthorizer :: AuthorizerElement' ctx -> Authorizer' ctx+elementToAuthorizer = \case+ AuthorizerPolicy p -> Authorizer [p] mempty+ BlockElement be -> Authorizer [] (elementToBlock be)
src/Auth/Biscuit/Datalog/Executor.hs view
@@ -3,8 +3,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-} {-| Module : Auth.Biscuit.Datalog.Executor Copyright : © Clément Delafargue, 2021@@ -13,44 +11,42 @@ The Datalog engine, tasked with deriving new facts from existing facts and rules, as well as matching available facts against checks and policies -} module Auth.Biscuit.Datalog.Executor- ( BlockWithRevocationIds (..)- , ExecutionError (..)+ ( ExecutionError (..) , Limits (..) , ResultError (..)- , World (..) , Bindings , Name- , computeAllFacts+ , MatchedQuery (..) , defaultLimits , evaluateExpression- , runVerifier- , runVerifierWithLimits++ --+ , getFactsForRule+ , checkCheck+ , checkPolicy+ , getBindingsForRuleBody ) where -import Control.Monad (join, mfilter, when)-import Data.Bifunctor (first)-import Data.Bitraversable (bitraverse)-import Data.ByteString (ByteString)-import qualified Data.ByteString as ByteString-import Data.Foldable (traverse_)-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.Set (Set)-import qualified Data.Set as Set-import Data.Text (Text, intercalate, unpack)-import qualified Data.Text as Text-import Data.Void (absurd)-import qualified Text.Regex.TDFA as Regex-import qualified Text.Regex.TDFA.Text as Regex-import Validation (Validation (..), failure)+import Control.Monad (join, mfilter, zipWithM)+import Data.Bitraversable (bitraverse)+import qualified Data.ByteString as ByteString+import Data.Foldable (fold)+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.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Void (absurd)+import qualified Text.Regex.TDFA as Regex+import qualified Text.Regex.TDFA.Text as Regex+import Validation (Validation (..), failure) import Auth.Biscuit.Datalog.AST-import Auth.Biscuit.Datalog.Parser (fact)-import Auth.Biscuit.Timer (timer)-import Auth.Biscuit.Utils (maybeToRight)+import Auth.Biscuit.Utils (maybeToRight) -- | A variable name type Name = Text@@ -58,6 +54,15 @@ -- | A list of bound variables, with the associated value type Bindings = Map Name Value +-- | A datalog query that was matched, along with the values+-- that matched+data MatchedQuery+ = MatchedQuery+ { matchedQuery :: Query+ , bindings :: Set Bindings+ }+ deriving (Eq, Show)+ -- | The result of matching the checks and policies against all the available -- facts. data ResultError@@ -65,11 +70,14 @@ -- ^ No policy matched. additionally some checks may have failed | FailedChecks (NonEmpty Check) -- ^ An allow rule matched, but at least one check failed- | DenyRuleMatched [Check] Query+ | DenyRuleMatched [Check] MatchedQuery -- ^ A deny rule matched. additionally some checks may have failed deriving (Eq, Show) --- | The result of running verification+-- | An error that can happen while running a datalog verification.+-- The datalog computation itself can be aborted by runtime failsafe+-- mechanisms, or it can run to completion but fail to fullfil checks+-- and policies ('ResultError'). data ExecutionError = Timeout -- ^ Verification took too much time@@ -80,31 +88,34 @@ | FactsInBlocks -- ^ Some blocks contained either rules or facts while it was forbidden | ResultError ResultError- -- ^ The checks and policies were not fulfilled after evaluation+ -- ^ The evaluation ran to completion, but checks and policies were not+ -- fulfilled. deriving (Eq, Show) --- | Settings for the executor restrictions+-- | Settings for the executor runtime restrictions. -- See `defaultLimits` for default values. data Limits = Limits- { maxFacts :: Int- -- ^ maximum number of facts that can be produced (else `TooManyFacts` is thrown)- , maxIterations :: Int+ { maxFacts :: Int+ -- ^ maximum number of facts that can be produced before throwing `TooManyFacts`+ , maxIterations :: Int -- ^ maximum number of iterations before throwing `TooManyIterations`- , maxTime :: Int+ , maxTime :: Int -- ^ maximum duration the verification can take (in μs)- , allowRegexes :: Bool- -- ^ whether or not allowing `.matches()` during verification- , allowBlockFacts :: Bool- -- ^ wheter or not accept facts and rules in blocks. Even when they are enabled, they- -- can’t give rise to facts containing `#authority` or `#ambient` symbols- , checkRevocationId :: ByteString -> IO (Either () ())- -- ^ how to check for token revocation `Left ()` means that the given id is revoked,- -- `Right ()` means it’s not revoked.+ , 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.+ , allowBlockFacts :: Bool+ -- ^ whether or not accept facts and rules in blocks }+ deriving (Eq, Show) -- | Default settings for the executor restrictions.--- (1000 facts, 100 iterations, 1000μs max, regexes are allowed, facts and rules are allowed in blocks)+-- - 1000 facts+-- - 100 iterations+-- - 1000μs max+-- - regexes are allowed+-- - facts and rules are allowed in blocks defaultLimits :: Limits defaultLimits = Limits { maxFacts = 1000@@ -112,189 +123,29 @@ , maxTime = 1000 , allowRegexes = True , allowBlockFacts = True- , checkRevocationId = const . pure $ Right () } --- | A parsed block, along with the associated revocation ids.-data BlockWithRevocationIds- = BlockWithRevocationIds- { bBlock :: Block- -- ^ The parsed block- , genericRevocationId :: ByteString- -- ^ Generic revocation id (depends on the block contents and its primary key)- , uniqueRevocationId :: ByteString- -- ^ Unique revocation id (specific to the token)- }---- | A collection of facts and rules used to derive new facts.--- Rules coming from blocks are stored separately since they are subject to specific--- restrictions regarding the facts they can generate.-data World- = World- { rules :: Set Rule- , blockRules :: Set Rule- , facts :: Set Fact- }--instance Semigroup World where- w1 <> w2 = World- { rules = rules w1 <> rules w2- , blockRules = blockRules w1 <> blockRules w2- , facts = facts w1 <> facts w2- }--instance Monoid World where- mempty = World mempty mempty mempty--instance Show World where- show World{..} = unpack . intercalate "\n" $ join- [ [ "Authority & Verifier Rules" ]- , renderRule <$> Set.toList rules- , [ "Block Rules" ]- , renderRule <$> Set.toList blockRules- , [ "Facts" ]- , renderFact <$> Set.toList facts- ]---- | Is the fact "restricted" (meaning it is not allowed to come from a block, or generated by a block rule).--- In practice, only authority blocks can contain the symbols `#ambient` and `#authority`-isRestricted :: Fact -> Bool-isRestricted Predicate{terms} =- let restrictedSymbol (Symbol s ) = s == "ambient" || s == "authority"- restrictedSymbol _ = False- in any restrictedSymbol terms---- | Expose the block revocation ids through facts-revocationIdFacts :: Integer- -- ^ The block index (0 for authority, 1-n for blocks)- -> BlockWithRevocationIds- -> [Fact]-revocationIdFacts index BlockWithRevocationIds{genericRevocationId, uniqueRevocationId} =- [ [fact|revocation_id(${index}, ${genericRevocationId})|]- , [fact|unique_revocation_id(${index}, ${uniqueRevocationId})|]- ]--collectWorld :: Limits -> Verifier -> BlockWithRevocationIds -> [BlockWithRevocationIds] -> World-collectWorld Limits{allowBlockFacts} Verifier{vBlock} authority blocks =- let getRules = bRules . bBlock- getFacts = bFacts . bBlock- revocationIds = join $ zipWith revocationIdFacts [0..] (authority : blocks)- in World- { rules = Set.fromList $ bRules vBlock <> getRules authority- , blockRules = if allowBlockFacts- then Set.fromList $ foldMap getRules blocks- else mempty- , facts = Set.fromList $- bFacts vBlock- <> getFacts authority- <> filter ((allowBlockFacts &&) . not . isRestricted) (getFacts =<< blocks)- <> revocationIds- }---- | Given a series of blocks and a verifier, ensure that all--- the checks and policies match-runVerifier :: BlockWithRevocationIds- -- ^ The authority block- -> [BlockWithRevocationIds]- -- ^ The extra blocks- -> Verifier- -- ^ A verifier- -> IO (Either ExecutionError Query)-runVerifier = runVerifierWithLimits defaultLimits---- | Given a series of blocks and a verifier, ensure that all--- the checks and policies match, with provided execution--- constraints-runVerifierWithLimits :: Limits- -- ^ custom limits- -> BlockWithRevocationIds- -- ^ The authority block- -> [BlockWithRevocationIds]- -- ^ The extra blocks- -> Verifier- -- ^ A verifier- -> IO (Either ExecutionError Query)-runVerifierWithLimits l@Limits{..} authority blocks v = do- resultOrTimeout <- timer maxTime $ runVerifier' l authority blocks v- pure $ case resultOrTimeout of- Nothing -> Left Timeout- Just r -> r--runVerifier' :: Limits- -> BlockWithRevocationIds- -> [BlockWithRevocationIds]- -> Verifier- -> IO (Either ExecutionError Query)-runVerifier' l authority blocks v@Verifier{..} = do- let initialWorld = collectWorld l v authority blocks- allFacts' = computeAllFacts l initialWorld- case allFacts' of- Left e -> pure $ Left e- Right allFacts -> do- let allChecks = foldMap bChecks $ vBlock : (bBlock <$> authority : blocks)- checkResults = traverse_ (checkCheck l allFacts) allChecks- policiesResults = mapMaybe (checkPolicy l allFacts) vPolicies- policyResult = case policiesResults of- p : _ -> first Just p- [] -> Left Nothing- pure $ case (checkResults, policyResult) of- (Success (), Right p) -> Right p- (Success (), Left Nothing) -> Left $ ResultError $ NoPoliciesMatched []- (Success (), Left (Just p)) -> Left $ ResultError $ DenyRuleMatched [] p- (Failure cs, Left Nothing) -> Left $ ResultError $ NoPoliciesMatched (NE.toList cs)- (Failure cs, Left (Just p)) -> Left $ ResultError $ DenyRuleMatched (NE.toList cs) p- (Failure cs, Right _) -> Left $ ResultError $ FailedChecks cs- checkCheck :: Limits -> Set Fact -> Check -> Validation (NonEmpty Check) () checkCheck l facts items =- if any (isQueryItemSatisfied l facts) items+ if any (isJust . isQueryItemSatisfied l facts) items then Success () else failure items -checkPolicy :: Limits -> Set Fact -> Policy -> Maybe (Either Query Query)-checkPolicy l facts (pType, items) =- if any (isQueryItemSatisfied l facts) items- then Just $ case pType of- Allow -> Right items- Deny -> Left items- else Nothing+checkPolicy :: Limits -> Set Fact -> Policy -> Maybe (Either MatchedQuery MatchedQuery)+checkPolicy l facts (pType, query) =+ let bindings = fold $ mapMaybe (isQueryItemSatisfied l facts) query+ in if not (null bindings)+ then Just $ case pType of+ Allow -> Right $ MatchedQuery{matchedQuery = query, bindings}+ Deny -> Left $ MatchedQuery{matchedQuery = query, bindings}+ else Nothing -isQueryItemSatisfied :: Limits -> Set Fact -> QueryItem' 'RegularString -> Bool+isQueryItemSatisfied :: Limits -> Set Fact -> QueryItem' 'RegularString -> Maybe (Set Bindings) isQueryItemSatisfied l facts QueryItem{qBody, qExpressions} = let bindings = getBindingsForRuleBody l facts qBody qExpressions- in Set.size bindings > 0---- | Compute all possible facts, recursively calling itself--- until it can't generate new facts or a limit is reached-computeAllFacts :: Limits- -- ^ The maximum amount of iterations that can be reached- -> World- -- ^ The initial rules and facts- -> Either ExecutionError (Set Fact)-computeAllFacts l@Limits{..} = computeAllFacts' l maxIterations----- | Compute all possible facts, recursively calling itself--- until it can't generate new facts or a limit is reached-computeAllFacts' :: Limits- -> Int- -> World- -> Either ExecutionError (Set Fact)-computeAllFacts' l@Limits{..} remainingIterations w@World{facts} = do- let newFacts = extend l w- allFacts = facts <> newFacts- when (Set.size allFacts >= maxFacts) $ Left TooManyFacts- when (remainingIterations - 1 <= 0) $ Left TooManyIterations- if null newFacts- then pure allFacts- else computeAllFacts' l (remainingIterations - 1) (w { facts = allFacts })--extend :: Limits -> World -> Set Fact-extend l World{..} =- let buildFacts = foldMap (getFactsForRule l facts)- allNewFacts = buildFacts rules- allNewBlockFacts = Set.filter (not . isRestricted) $ buildFacts blockRules- in Set.difference (allNewFacts <> allNewBlockFacts) facts+ in if Set.size bindings > 0+ then Just bindings+ else Nothing getFactsForRule :: Limits -> Set Fact -> Rule -> Set Fact getFactsForRule l facts Rule{rhead, body, expressions} =@@ -319,7 +170,7 @@ extractVariables predicates = let keepVariable = \case Variable name -> Just name- _ -> Nothing+ _ -> Nothing extractVariables' Predicate{terms} = mapMaybe keepVariable terms in Set.fromList $ extractVariables' =<< predicates @@ -327,9 +178,8 @@ applyBindings :: Predicate -> Bindings -> Maybe Fact applyBindings p@Predicate{terms} bindings = let newTerms = traverse replaceTerm terms- replaceTerm :: ID -> Maybe Value+ replaceTerm :: Term -> Maybe Value replaceTerm (Variable n) = Map.lookup n bindings- replaceTerm (Symbol t) = Just $ Symbol t replaceTerm (LInteger t) = Just $ LInteger t replaceTerm (LString t) = Just $ LString t replaceTerm (LDate t) = Just $ LDate t@@ -372,8 +222,7 @@ keepFacts p = mapMaybeS (factMatchesPredicate p) facts in keepFacts <$> predicates -isSame :: ID -> Value -> Bool-isSame (Symbol t) (Symbol t') = t == t'+isSame :: Term -> Value -> Bool isSame (LInteger t) (LInteger t') = t == t' isSame (LString t) (LString t') = t == t' isSame (LDate t) (LDate t') = t == t'@@ -387,8 +236,8 @@ Predicate{name = factName, terms = factTerms } = let namesMatch = predicateName == factName lengthsMatch = length predicateTerms == length factTerms- allMatches = sequenceA $ zipWith yolo predicateTerms factTerms- yolo :: ID -> Value -> Maybe Bindings+ allMatches = zipWithM yolo predicateTerms factTerms+ yolo :: Term -> Value -> Maybe Bindings yolo (Variable vname) value = Just (Map.singleton vname value) yolo t t' | isSame t t' = Just mempty | otherwise = Nothing@@ -397,17 +246,16 @@ else Nothing applyVariable :: Bindings- -> ID+ -> Term -> Either String Value applyVariable bindings = \case- Variable n -> maybeToRight "Unbound variable" $ bindings !? n- Symbol t -> Right $ Symbol t- LInteger t -> Right $ LInteger t- LString t -> Right $ LString t- LDate t -> Right $ LDate t- LBytes t -> Right $ LBytes t- LBool t -> Right $ LBool t- TermSet t -> Right $ TermSet t+ Variable n -> maybeToRight "Unbound variable" $ bindings !? n+ LInteger t -> Right $ LInteger t+ LString t -> Right $ LString t+ LDate t -> Right $ LDate t+ LBytes t -> Right $ LBytes t+ LBool t -> Right $ LBool t+ TermSet t -> Right $ TermSet t Antiquote v -> absurd v evalUnary :: Unary -> Value -> Either String Value@@ -421,7 +269,6 @@ evalBinary :: Limits -> Binary -> Value -> Value -> Either String Value -- eq / ord operations-evalBinary _ Equal (Symbol s) (Symbol s') = pure $ LBool (s == s') 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')
src/Auth/Biscuit/Datalog/Parser.hs view
@@ -21,7 +21,8 @@ , fact , predicate , rule- , verifier+ , authorizer+ , query -- these are only exported for testing purposes , checkParser , expressionParser@@ -29,7 +30,8 @@ , predicateParser , ruleParser , termParser- , verifierParser+ , blockParser+ , authorizerParser , HasParsers , HasTermParsers ) where@@ -37,14 +39,15 @@ import Control.Applicative (liftA2, optional, (<|>)) import qualified Control.Monad.Combinators.Expr as Expr import Data.Attoparsec.Text+import qualified Data.Attoparsec.Text as A import Data.ByteString (ByteString) import Data.ByteString.Base16 as Hex-import Data.Char (isSpace)+import Data.Char (isAlphaNum, isLetter, isSpace) import Data.Either (partitionEithers) import Data.Foldable (fold) import Data.Functor (void, ($>)) import qualified Data.Set as Set-import Data.Text (Text, pack, unpack)+import Data.Text (Text, pack, singleton, unpack) import Data.Text.Encoding (encodeUtf8) import Data.Time (UTCTime, defaultTimeLocale, parseTimeM)@@ -84,10 +87,16 @@ ) type HasParsers pof ctx = HasTermParsers 'NotWithinSet pof ctx --- | Parser for an identifier (predicate name, variable name, symbol name, …)-nameParser :: Parser Text-nameParser = takeWhile1 $ inClass "a-zA-Z0-9_"+-- | Parser for a datalog predicate name+predicateNameParser :: Parser Text+predicateNameParser = do+ first <- satisfy isLetter+ rest <- A.takeWhile $ \c -> c == '_' || c == ':' || isAlphaNum c+ pure $ singleton first <> rest +variableNameParser :: Parser Text+variableNameParser = char '$' *> takeWhile1 (\c -> c == '_' || c == ':' || isAlphaNum c)+ delimited :: Parser x -> Parser y -> Parser a@@ -108,7 +117,7 @@ predicateParser :: HasParsers pof ctx => Parser (Predicate' pof ctx) predicateParser = do skipSpace- name <- nameParser+ name <- predicateNameParser skipSpace terms <- parens (commaList termParser) pure Predicate{name,terms}@@ -204,8 +213,7 @@ hexBsParser :: Parser ByteString hexBsParser = do void $ string "hex:"- (digits, "") <- Hex.decode . encodeUtf8 <$> takeWhile1 (inClass "0-9a-fA-F")- pure digits+ either fail pure =<< Hex.decode . encodeUtf8 <$> takeWhile1 (inClass "0-9a-fA-F") litStringParser :: Parser Text litStringParser =@@ -236,12 +244,11 @@ termParser :: forall inSet pof ctx . ( HasTermParsers inSet pof ctx )- => Parser (ID' inSet pof ctx)+ => Parser (Term' inSet pof ctx) termParser = skipSpace *> choice [ Antiquote <$> ifPresent "slice" (Slice <$> (string "${" *> many1 letter <* char '}'))- , Variable <$> ifPresent "var" (char '$' *> nameParser)+ , Variable <$> ifPresent "var" variableNameParser , TermSet <$> parseSet @inSet @ctx- , Symbol <$> (char '#' *> nameParser) , LBytes <$> hexBsParser , LDate <$> rfc3339DateParser , LInteger <$> signed decimal@@ -256,7 +263,7 @@ ruleHeadParser :: HasParsers 'InPredicate ctx => Parser (Predicate' 'InPredicate ctx) ruleHeadParser = do skipSpace- name <- nameParser+ name <- predicateNameParser skipSpace terms <- parens (commaList0 termParser) pure Predicate{name,terms}@@ -304,20 +311,20 @@ , BlockComment <$ commentParser ] -verifierElementParser :: HasParsers 'InPredicate ctx => Parser (VerifierElement' ctx)-verifierElementParser = choice- [ VerifierPolicy <$> policyParser <* skipSpace <* char ';'+authorizerElementParser :: HasParsers 'InPredicate ctx => Parser (AuthorizerElement' ctx)+authorizerElementParser = choice+ [ AuthorizerPolicy <$> policyParser <* skipSpace <* char ';' , BlockElement <$> blockElementParser ] -verifierParser :: ( HasParsers 'InPredicate ctx+authorizerParser :: ( HasParsers 'InPredicate ctx , HasParsers 'InFact ctx- , Show (VerifierElement' ctx)+ , Show (AuthorizerElement' ctx) )- => Parser (Verifier' ctx)-verifierParser = do- elems <- many1 (skipSpace *> verifierElementParser)- pure $ foldMap elementToVerifier elems+ => Parser (Authorizer' ctx)+authorizerParser = do+ elems <- many' (skipSpace *> authorizerElementParser)+ pure $ foldMap elementToAuthorizer elems blockParser :: ( HasParsers 'InPredicate ctx , HasParsers 'InFact ctx@@ -325,7 +332,7 @@ ) => Parser (Block' ctx) blockParser = do- elems <- many1 (skipSpace *> blockElementParser)+ elems <- many' (skipSpace *> blockElementParser) pure $ foldMap elementToBlock elems policyParser :: HasParsers 'InPredicate ctx => Parser (Policy' ctx)@@ -337,14 +344,14 @@ (policy, ) <$> queryParser compileParser :: Lift a => Parser a -> String -> Q Exp-compileParser p str = case parseOnly p (pack str) of+compileParser p str = case parseOnly (p <* skipSpace <* endOfInput) (pack str) of Right result -> [| result |] Left e -> fail e -- | Quasiquoter for a rule expression. You can reference haskell variables -- like this: @${variableName}@. ----- You most likely want to directly use 'block' or 'verifier' instead.+-- You most likely want to directly use 'block' or 'authorizer' instead. rule :: QuasiQuoter rule = QuasiQuoter { quoteExp = compileParser (ruleParser @'QuasiQuote)@@ -356,7 +363,7 @@ -- | Quasiquoter for a predicate expression. You can reference haskell variables -- like this: @${variableName}@. ----- You most likely want to directly use 'block' or 'verifier' instead.+-- You most likely want to directly use 'block' or 'authorizer' instead. predicate :: QuasiQuoter predicate = QuasiQuoter { quoteExp = compileParser (predicateParser @'InPredicate @'QuasiQuote)@@ -368,7 +375,7 @@ -- | Quasiquoter for a fact expression. You can reference haskell variables -- like this: @${variableName}@. ----- You most likely want to directly use 'block' or 'verifier' instead.+-- You most likely want to directly use 'block' or 'authorizer' instead. fact :: QuasiQuoter fact = QuasiQuoter { quoteExp = compileParser (predicateParser @'InFact @'QuasiQuote)@@ -380,7 +387,7 @@ -- | Quasiquoter for a check expression. You can reference haskell variables -- like this: @${variableName}@. ----- You most likely want to directly use 'block' or 'verifier' instead.+-- You most likely want to directly use 'block' or 'authorizer' instead. check :: QuasiQuoter check = QuasiQuoter { quoteExp = compileParser (checkParser @'QuasiQuote)@@ -389,16 +396,18 @@ , quoteDec = error "not supported" } --- | Quasiquoter for a block expression. You can reference haskell variables--- like this: @${variableName}@.+-- | Compile-time parser for a block expression, intended to be used with the+-- @QuasiQuotes@ extension. -- -- A typical use of 'block' looks like this: ----- > [block|--- > resource(#authority, ${fileName});--- > rule($variable) <- fact($value), other_fact($value);--- > check if operation(#ambient, #read);--- > |]+-- > let fileName = "data.pdf"+-- > in [block|+-- > // datalog can reference haskell variables with ${variableName}+-- > resource(${fileName});+-- > rule($variable) <- fact($value), other_fact($value);+-- > check if operation("read");+-- > |] block :: QuasiQuoter block = QuasiQuoter { quoteExp = compileParser (blockParser @'QuasiQuote)@@ -407,19 +416,40 @@ , quoteDec = error "not supported" } --- | Quasiquoter for a verifier expression. You can reference haskell variables--- like this: @${variableName}@.+-- | Compile-time parser for an authorizer expression, intended to be used with the+-- @QuasiQuotes@ extension. ----- A typical use of 'block' looks like this:+-- A typical use of 'authorizer' looks like this: ----- > [verifier|--- > current_time(#ambient, ${now});--- > allow if resource(#authority, "file1");--- > deny if true;--- > |]-verifier :: QuasiQuoter-verifier = QuasiQuoter- { quoteExp = compileParser (verifierParser @'QuasiQuote)+-- > do+-- > now <- getCurrentTime+-- > pure [authorizer|+-- > // datalog can reference haskell variables with ${variableName}+-- > current_time(${now});+-- > // authorizers can contain facts, rules and checks like blocks, but+-- > // also declare policies. While every check has to pass for a biscuit to+-- > // be valid, policies are tried in order. The first one to match decides+-- > // if the token is valid or not+-- > allow if resource("file1");+-- > deny if true;+-- > |]+authorizer :: QuasiQuoter+authorizer = QuasiQuoter+ { quoteExp = compileParser (authorizerParser @'QuasiQuote)+ , quotePat = error "not supported"+ , quoteType = error "not supported"+ , quoteDec = error "not supported"+ }++-- | Compile-time parser for a query expression, intended to be used with the+-- @QuasiQuotes@ extension.+--+-- A typical use of 'query' looks like this:+--+-- > [query|user($user_id) or group($group_id)|]+query :: QuasiQuoter+query = QuasiQuoter+ { quoteExp = compileParser (queryParser @'QuasiQuote) , quotePat = error "not supported" , quoteType = error "not supported" , quoteDec = error "not supported"
+ src/Auth/Biscuit/Datalog/ScopedExecutor.hs view
@@ -0,0 +1,312 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+module Auth.Biscuit.Datalog.ScopedExecutor+ ( BlockWithRevocationId+ , runAuthorizer+ , runAuthorizerWithLimits+ , runAuthorizerNoTimeout+ , World (..)+ , computeAllFacts+ , runFactGeneration+ , PureExecError (..)+ , AuthorizationSuccess (..)+ , getBindings+ , queryAuthorizerFacts+ , getVariableValues+ , getSingleVariableValue+ ) where++import Control.Monad (join, when)+import Control.Monad.State (StateT (..), get, lift, modify,+ put, runStateT)+import Data.Bifunctor (first)+import Data.ByteString (ByteString)+import Data.Foldable (traverse_)+import Data.List.NonEmpty (NonEmpty, nonEmpty)+import qualified Data.List.NonEmpty as NE+import Data.Map.Strict ((!?))+import Data.Maybe (mapMaybe)+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text, intercalate, unpack)+import Validation (Validation (..), validation)++import Auth.Biscuit.Datalog.AST+import Auth.Biscuit.Datalog.Executor (Bindings, ExecutionError (..),+ Limits (..), MatchedQuery (..),+ ResultError (..), checkCheck,+ checkPolicy, defaultLimits,+ getBindingsForRuleBody,+ getFactsForRule)+import Auth.Biscuit.Datalog.Parser (fact)+import Auth.Biscuit.Timer (timer)++type BlockWithRevocationId = (Block, ByteString)++-- | A subset of 'ExecutionError' that can only happen during fact generation+data PureExecError = Facts | Iterations+ deriving (Eq, Show)++-- | State maintained by the datalog computation.+data ComputeState+ = ComputeState+ { sFacts :: Set Fact+ -- ^ All the facts generated so far+ , sAuthorityFacts :: Set Fact+ -- ^ Facts generated by the authority block (and the authorizer). Those are kept separate+ -- because they are provided by a trusted party (the one which has the root 'SecretKey').+ -- Block facts are not as trustworthy as they can be added by anyone.+ , sIterations :: Int+ -- ^ The current count of iterations+ , sLimits :: Limits+ -- ^ The configured limits for this computation. This field is effectively read-only+ , sFailedChecks :: [Check]+ -- ^ The failed checks gathered so far. The computation carries on even if some checks+ -- fail, in order to be able to report all the failing checks in one go+ , sPolicyResult :: Either (Maybe MatchedQuery) MatchedQuery+ -- ^ The result of the authorizer-defined policies. 'Left' represents failure:+ -- - @Left Nothing@ if no policies matched+ -- - @Left (Just q)@ if a deny policy matched+ -- - @Right q@ if an allow policy matched+ }++mkInitState :: Limits -> ComputeState+mkInitState sLimits = ComputeState+ { sFacts = Set.empty -- no facts have been generated yet+ , sAuthorityFacts = Set.empty -- no authority facts have been generated yet+ , sIterations = 0 -- no evaluation iteration has taken place yet+ , sLimits -- this field is read-only+ , sFailedChecks = [] -- no checks have failed yet+ , sPolicyResult = Left Nothing -- no policies have matched yet+ }++data World+ = World+ { facts :: Set Fact+ , rules :: Set Rule+ }++instance Semigroup World where+ w1 <> w2 = World+ { rules = rules w1 <> rules w2+ , facts = facts w1 <> facts w2+ }++instance Monoid World where+ mempty = World mempty mempty++instance Show World where+ show World{..} = unpack . intercalate "\n" $ join+ [ [ "Block Rules" ]+ , renderRule <$> Set.toList rules+ , [ "Facts" ]+ , renderFact <$> Set.toList facts+ ]++-- | Proof that a biscuit was authorized successfully. In addition to the matched+-- @allow query@, the generated facts are kept around for further querying.+-- Since only authority facts can be trusted, they are kept separate.+data AuthorizationSuccess+ = AuthorizationSuccess+ { matchedAllowQuery :: MatchedQuery+ -- ^ The allow query that matched+ , authorityFacts :: Set Fact+ -- ^ All the facts generated by the authority block (and the authorizer)+ , allGeneratedFacts :: Set Fact+ -- ^ All the facts that were generated by the biscuit. Be careful, the+ -- biscuit signature check only guarantees that 'authorityFacts' are+ -- signed with the corresponding 'SecretKey'.+ , limits :: Limits+ -- ^ Limits used when running datalog. It is kept around to allow further+ -- datalog computation when querying facts+ }+ deriving (Eq, Show)++-- | Get the matched variables from the @allow@ query used to authorize the biscuit.+-- This can be used in conjuction with 'getVariableValues' or 'getSingleVariableValue'+-- to extract the actual values+getBindings :: AuthorizationSuccess -> Set Bindings+getBindings AuthorizationSuccess{matchedAllowQuery=MatchedQuery{bindings}} = bindings++withFacts :: World -> Set Fact -> World+withFacts w@World{facts} newFacts = w { facts = newFacts <> facts }++-- | Given a series of blocks and an authorizer, ensure that all+-- the checks and policies match+runAuthorizer :: BlockWithRevocationId+ -- ^ The authority block+ -> [BlockWithRevocationId]+ -- ^ The extra blocks+ -> Authorizer+ -- ^ A authorizer+ -> IO (Either ExecutionError AuthorizationSuccess)+runAuthorizer = runAuthorizerWithLimits defaultLimits++-- | Given a series of blocks and an authorizer, ensure that all+-- the checks and policies match, with provided execution+-- constraints+runAuthorizerWithLimits :: Limits+ -- ^ custom limits+ -> BlockWithRevocationId+ -- ^ The authority block+ -> [BlockWithRevocationId]+ -- ^ The extra blocks+ -> Authorizer+ -- ^ A authorizer+ -> IO (Either ExecutionError AuthorizationSuccess)+runAuthorizerWithLimits l@Limits{..} authority blocks v = do+ resultOrTimeout <- timer maxTime $ pure $ runAuthorizerNoTimeout l authority blocks v+ pure $ case resultOrTimeout of+ Nothing -> Left Timeout+ Just r -> r+++runAllBlocks :: BlockWithRevocationId+ -> [BlockWithRevocationId]+ -> Authorizer+ -> StateT ComputeState (Either PureExecError) ()+runAllBlocks authority blocks authorizer = do+ modify $ \state -> state { sFacts = mkRevocationIdFacts authority blocks }+ runAuthority authority authorizer+ traverse_ runBlock blocks++mkRevocationIdFacts :: BlockWithRevocationId -> [BlockWithRevocationId]+ -> Set Fact+mkRevocationIdFacts authority blocks =+ let allIds :: [(Int, ByteString)]+ allIds = zip [0..] $ snd <$> authority : blocks+ mkFact (index, rid) = [fact|revocation_id(${index}, ${rid})|]+ in Set.fromList $ mkFact <$> allIds++runAuthorizerNoTimeout :: Limits+ -> BlockWithRevocationId+ -> [BlockWithRevocationId]+ -> Authorizer+ -> Either ExecutionError AuthorizationSuccess+runAuthorizerNoTimeout limits authority blocks authorizer = do+ let result = (`runStateT` mkInitState limits) $ runAllBlocks authority blocks authorizer+ case result of+ Left Facts -> Left TooManyFacts+ Left Iterations -> Left TooManyIterations+ Right ((), ComputeState{..}) -> case (nonEmpty sFailedChecks, sPolicyResult) of+ (Nothing, Right p) -> Right $ AuthorizationSuccess { matchedAllowQuery = p+ , authorityFacts = sAuthorityFacts+ , allGeneratedFacts = sFacts+ , limits+ }+ (Nothing, Left Nothing) -> Left $ ResultError $ NoPoliciesMatched []+ (Nothing, Left (Just p)) -> Left $ ResultError $ DenyRuleMatched [] p+ (Just cs, Left Nothing) -> Left $ ResultError $ NoPoliciesMatched (NE.toList cs)+ (Just cs, Left (Just p)) -> Left $ ResultError $ DenyRuleMatched (NE.toList cs) p+ (Just cs, Right _) -> Left $ ResultError $ FailedChecks cs+++runFactGeneration :: Limits -> World -> Either PureExecError (Set Fact)+runFactGeneration limits w =+ let getFacts = sFacts . snd+ in getFacts <$> runStateT (computeAllFacts w) (mkInitState limits)++runAuthority :: BlockWithRevocationId+ -> Authorizer+ -> StateT ComputeState (Either PureExecError) ()+runAuthority (block, _rid) Authorizer{..} = do+ let world = collectWorld block <> collectWorld vBlock+ computeAllFacts world+ -- store the facts generated by the authority block (and the authorizer)+ -- in a dedicated `sAuthorityFacts` so that they can be queried independently+ -- later: we trust the authority facts, not the block facts+ modify $ \c@ComputeState{sFacts} -> c { sAuthorityFacts = sFacts }+ state@ComputeState{sFacts, sLimits} <- get+ let checkResults = checkChecks sLimits (bChecks block <> bChecks vBlock) sFacts+ let policyResult = checkPolicies sLimits vPolicies sFacts+ put state { sPolicyResult = policyResult+ , sFailedChecks = validation NE.toList mempty checkResults+ }++runBlock :: BlockWithRevocationId+ -> StateT ComputeState (Either PureExecError) ()+runBlock (block@Block{bChecks}, _rid) = do+ let world = collectWorld block+ computeAllFacts world+ state@ComputeState{sFacts, sLimits, sFailedChecks} <- get+ let checkResults = checkChecks sLimits bChecks sFacts+ put state { sFailedChecks = validation NE.toList mempty checkResults <> sFailedChecks+ }++checkChecks :: Limits -> [Check] -> Set Fact -> Validation (NonEmpty Check) ()+checkChecks limits checks facts = traverse_ (checkCheck limits facts) checks++checkPolicies :: Limits -> [Policy] -> Set Fact -> Either (Maybe MatchedQuery) MatchedQuery+checkPolicies limits policies facts =+ let results = mapMaybe (checkPolicy limits facts) policies+ in case results of+ p : _ -> first Just p+ [] -> Left Nothing++computeAllFacts :: World+ -> StateT ComputeState (Either PureExecError) ()+computeAllFacts world = do+ state@ComputeState{..} <- get+ let Limits{..} = sLimits+ let newFacts = extend sLimits (world `withFacts` sFacts)+ allFacts = sFacts <> facts world <> newFacts+ when (Set.size allFacts >= maxFacts) $ lift $ Left Facts+ when (sIterations >= maxIterations) $ lift $ Left Iterations+ put $ state { sIterations = sIterations + 1+ , sFacts = allFacts+ }+ if null newFacts+ then pure ()+ else computeAllFacts world++extend :: Limits -> World -> Set Fact+extend l World{..} =+ let buildFacts = foldMap (getFactsForRule l facts)+ allNewFacts = buildFacts rules+ in Set.difference allNewFacts facts++collectWorld :: Block -> World+collectWorld Block{..} = World+ { facts = Set.fromList bFacts+ , rules = Set.fromList bRules+ }++-- | Query the facts generated by the authority and authorizer blocks+-- during authorization. This can be used in conjuction with 'getVariableValues'+-- and 'getSingleVariableValue' to retrieve actual values.+--+-- ⚠ Only the facts generated by the authority and authorizer blocks are queried.+-- Block facts are not queried (since they can't be trusted).+--+-- 💁 If the facts you want to query are part of an allow query in the authorizer,+-- you can directly get values from 'AuthorizationSuccess'.+queryAuthorizerFacts :: AuthorizationSuccess -> Query -> Set Bindings+queryAuthorizerFacts AuthorizationSuccess{authorityFacts, limits} q =+ let getBindingsForQueryItem QueryItem{qBody,qExpressions} =+ getBindingsForRuleBody limits authorityFacts qBody qExpressions+ in foldMap getBindingsForQueryItem q++-- | Extract a set of values from a matched variable for a specific type.+-- Returning @Set Value@ allows to get all values, whatever their type.+getVariableValues :: (Ord t, FromValue t)+ => Set Bindings+ -> Text+ -> Set t+getVariableValues bindings variableName =+ let mapMaybeS f = foldMap (foldMap Set.singleton . f)+ getVar vars = fromValue =<< vars !? variableName+ in mapMaybeS getVar bindings++-- | Extract exactly one value from a matched variable. If the variable has 0+-- matches or more than one match, 'Nothing' will be returned+getSingleVariableValue :: (Ord t, FromValue t)+ => Set Bindings+ -> Text+ -> Maybe t+getSingleVariableValue bindings variableName =+ let values = getVariableValues bindings variableName+ in case Set.toList values of+ [v] -> Just v+ _ -> Nothing
src/Auth/Biscuit/Example.hs view
@@ -4,34 +4,34 @@ import Data.ByteString (ByteString) import Data.Functor (($>))+import Data.Maybe (fromMaybe) import Data.Time (getCurrentTime) import Auth.Biscuit -privateKey' :: PrivateKey-privateKey' = maybe (error "Error parsing private key") id $ parsePrivateKeyHex "todo"+privateKey' :: SecretKey+privateKey' = fromMaybe (error "Error parsing private key") $ parseSecretKeyHex "todo" publicKey' :: PublicKey-publicKey' = maybe (error "Error parsing public key") id $ parsePublicKeyHex "todo"+publicKey' = fromMaybe (error "Error parsing public key") $ parsePublicKeyHex "todo" creation :: IO ByteString creation = do let authority = [block| // toto- resource(#authority,"file1");+ resource("file1"); |]- keypair <- fromPrivateKey privateKey'- biscuit <- mkBiscuit keypair authority- let block1 = [block|check if current_time(#ambient, $time), $time < 2021-05-08T00:00:00Z;|]+ biscuit <- mkBiscuit privateKey' authority+ let block1 = [block|check if current_time($time), $time < 2021-05-08T00:00:00Z;|] newBiscuit <- addBlock block1 biscuit pure $ serializeB64 newBiscuit verification :: ByteString -> IO Bool verification serialized = do now <- getCurrentTime- biscuit <- either (fail . show) pure $ parseB64 serialized- let verifier' = [verifier|current_time(#ambient, ${now});|]- result <- verifyBiscuit biscuit verifier' publicKey'+ biscuit <- either (fail . show) pure $ parseB64 publicKey' serialized+ let authorizer' = [authorizer|current_time(${now});|]+ result <- authorizeBiscuit biscuit authorizer' case result of Left e -> print e $> False Right _ -> pure True
src/Auth/Biscuit/Proto.hs view
@@ -13,20 +13,25 @@ module Auth.Biscuit.Proto ( Biscuit (..)- , Signature (..)+ , SignedBlock (..)+ , PublicKey (..)+ , Algorithm (..)+ , Proof (..) , Block (..)- , FactV1 (..)- , RuleV1 (..)- , CheckV1 (..)- , PredicateV1 (..)- , IDV1 (..)- , ExpressionV1 (..)- , IDSet (..)+ , FactV2 (..)+ , RuleV2 (..)+ , CheckV2 (..)+ , PredicateV2 (..)+ , TermV2 (..)+ , ExpressionV2 (..)+ , TermSet (..) , Op (..) , OpUnary (..) , UnaryKind (..) , OpBinary (..) , BinaryKind (..)+ , OpTernary (..)+ , TernaryKind (..) , getField , putField , decodeBlockList@@ -43,107 +48,105 @@ import GHC.Generics (Generic) data Biscuit = Biscuit- { authority :: Required 1 (Value ByteString)- , blocks :: Repeated 2 (Value ByteString)- , keys :: Repeated 3 (Value ByteString)- , signature :: Required 4 (Message Signature)+ { rootKeyId :: Optional 1 (Value Int32)+ , authority :: Required 2 (Message SignedBlock)+ , blocks :: Repeated 3 (Message SignedBlock)+ , proof :: Required 4 (Message Proof) } deriving (Generic, Show) deriving anyclass (Decode, Encode) -data CBiscuit = CBiscuit- { cAuthority :: Required 1 (Message Block)- , cBlocks :: Repeated 2 (Message Block)- , cKeys :: Repeated 3 (Value ByteString)- , cSignature :: Required 4 (Message Signature)- } deriving (Generic, Show)- deriving anyclass (Decode, Encode)+data Proof =+ ProofSecret (Required 1 (Value ByteString))+ | ProofSignature (Required 2 (Value ByteString))+ deriving (Generic, Show)+ deriving anyclass (Decode, Encode) -data SealedBiscuit = SealedBiscuit- { sAuthority :: Required 1 (Value ByteString)- , sBlocks :: Repeated 2 (Value ByteString)- , sSignature :: Required 3 (Value ByteString)- } deriving (Generic, Show)- deriving anyclass (Decode, Encode)+data SignedBlock = SignedBlock+ { block :: Required 1 (Value ByteString)+ , nextKey :: Required 2 (Message PublicKey)+ , signature :: Required 3 (Value ByteString)+ }+ deriving (Generic, Show)+ deriving anyclass (Decode, Encode) -data Signature = Signature- { parameters :: Repeated 1 (Value ByteString)- , z :: Required 2 (Value ByteString)- } deriving (Generic, Show)- deriving anyclass (Decode, Encode)+data Algorithm = Ed25519+ deriving stock (Show, Enum, Bounded) +data PublicKey = PublicKey+ { algorithm :: Required 1 (Enumeration Algorithm)+ , key :: Required 2 (Value ByteString)+ }+ deriving (Generic, Show)+ deriving anyclass (Decode, Encode)+ data Block = Block {- index :: Required 1 (Value Int32)- , symbols :: Repeated 2 (Value Text)- -- , facts_v0 :: Repeated 3 (Message FactV0)- -- , rules_v0 :: Repeated 4 (Message RuleV0)- -- , caveats_v0 :: Repeated 5 (Message CaveatV0)- , context :: Optional 6 (Value Text)- , version :: Optional 7 (Value Int32)- , facts_v1 :: Repeated 8 (Message FactV1)- , rules_v1 :: Repeated 9 (Message RuleV1)- , checks_v1 :: Repeated 10 (Message CheckV1)+ 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) } deriving (Generic, Show) deriving anyclass (Decode, Encode) -newtype FactV1 = FactV1- { predicate :: Required 1 (Message PredicateV1)+newtype FactV2 = FactV2+ { predicate :: Required 1 (Message PredicateV2) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data RuleV1 = RuleV1- { head :: Required 1 (Message PredicateV1)- , body :: Repeated 2 (Message PredicateV1)- , expressions :: Repeated 3 (Message ExpressionV1)+data RuleV2 = RuleV2+ { head :: Required 1 (Message PredicateV2)+ , body :: Repeated 2 (Message PredicateV2)+ , expressions :: Repeated 3 (Message ExpressionV2) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -newtype CheckV1 = CheckV1- { queries :: Repeated 1 (Message RuleV1)+newtype CheckV2 = CheckV2+ { queries :: Repeated 1 (Message RuleV2) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data PredicateV1 = PredicateV1- { name :: Required 1 (Value Int64)- , ids :: Repeated 2 (Message IDV1)+data PredicateV2 = PredicateV2+ { name :: Required 1 (Value Int64)+ , terms :: Repeated 2 (Message TermV2) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data IDV1 =- IDSymbol (Required 1 (Value Int64))- | IDVariable (Required 2 (Value Int32))- | IDInteger (Required 3 (Value Int64))- | IDString (Required 4 (Value Text))- | IDDate (Required 5 (Value Int64))- | IDBytes (Required 6 (Value ByteString))- | IDBool (Required 7 (Value Bool))- | IDIDSet (Required 8 (Message IDSet))+data TermV2 =+ TermVariable (Required 1 (Value Int32))+ | 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)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -newtype IDSet = IDSet- { set :: Repeated 1 (Message IDV1)+newtype TermSet = TermSet+ { set :: Repeated 1 (Message TermV2) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -type CV1Id = Required 1 (Value Int32)-data ConstraintV1 =- CV1Int CV1Id (Required 2 (Message IntConstraintV1))- | CV1String CV1Id (Required 3 (Message StringConstraintV1))- | CV1Date CV1Id (Required 4 (Message DateConstraintV1))- | CV1Symbol CV1Id (Required 5 (Message SymbolConstraintV1))- | CV1Bytes CV1Id (Required 6 (Message BytesConstraintV1))+type CV2Id = Required 1 (Value Int32)+data ConstraintV2 =+ CV2Int CV2Id (Required 2 (Message IntConstraintV2))+ | CV2String CV2Id (Required 3 (Message StringConstraintV2))+ | CV2Date CV2Id (Required 4 (Message DateConstraintV2))+ | CV2Symbol CV2Id (Required 5 (Message SymbolConstraintV2))+ | CV2Bytes CV2Id (Required 6 (Message BytesConstraintV2)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data IntConstraintV1 =- ICV1LessThan (Required 1 (Value Int64))- | ICV1GreaterThan (Required 2 (Value Int64))- | ICV1LessOrEqual (Required 3 (Value Int64))- | ICV1GreaterOrEqual (Required 4 (Value Int64))- | ICV1Equal (Required 5 (Value Int64))- | ICV1InSet (Required 6 (Message IntSet))- | ICV1NotInSet (Required 7 (Message IntSet))+data IntConstraintV2 =+ ICV2LessThan (Required 1 (Value Int64))+ | ICV2GreaterThan (Required 2 (Value Int64))+ | ICV2LessOrEqual (Required 3 (Value Int64))+ | ICV2GreaterOrEqual (Required 4 (Value Int64))+ | ICV2Equal (Required 5 (Value Int64))+ | ICV2InSet (Required 6 (Message IntSet))+ | ICV2NotInSet (Required 7 (Message IntSet)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -152,13 +155,13 @@ } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data StringConstraintV1 =- SCV1Prefix (Required 1 (Value Text))- | SCV1Suffix (Required 2 (Value Text))- | SCV1Equal (Required 3 (Value Text))- | SCV1InSet (Required 4 (Message StringSet))- | SCV1NotInSet (Required 5 (Message StringSet))- | SCV1Regex (Required 6 (Value Text))+data StringConstraintV2 =+ SCV2Prefix (Required 1 (Value Text))+ | SCV2Suffix (Required 2 (Value Text))+ | SCV2Equal (Required 3 (Value Text))+ | SCV2InSet (Required 4 (Message StringSet))+ | SCV2NotInSet (Required 5 (Message StringSet))+ | SCV2Regex (Required 6 (Value Text)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -167,15 +170,15 @@ } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data DateConstraintV1 =- DCV1Before (Required 1 (Value Int64))- | DCV1After (Required 2 (Value Int64))+data DateConstraintV2 =+ DCV2Before (Required 1 (Value Int64))+ | DCV2After (Required 2 (Value Int64)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data SymbolConstraintV1 =- SyCV1InSet (Required 1 (Message SymbolSet))- | SyCV1NotInSet (Required 2 (Message SymbolSet))+data SymbolConstraintV2 =+ SyCV2InSet (Required 1 (Message SymbolSet))+ | SyCV2NotInSet (Required 2 (Message SymbolSet)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -185,10 +188,10 @@ deriving anyclass (Decode, Encode) -data BytesConstraintV1 =- BCV1Equal (Required 1 (Value ByteString))- | BCV1InSet (Required 2 (Message BytesSet))- | BCV1NotInSet (Required 3 (Message BytesSet))+data BytesConstraintV2 =+ BCV2Equal (Required 1 (Value ByteString))+ | BCV2InSet (Required 2 (Message BytesSet))+ | BCV2NotInSet (Required 3 (Message BytesSet)) deriving stock (Generic, Show) deriving anyclass (Decode, Encode) @@ -197,13 +200,13 @@ } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -newtype ExpressionV1 = ExpressionV1+newtype ExpressionV2 = ExpressionV2 { ops :: Repeated 1 (Message Op) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) data Op =- OpVValue (Required 1 (Message IDV1))+ OpVValue (Required 1 (Message TermV2)) | OpVUnary (Required 2 (Message OpUnary)) | OpVBinary (Required 3 (Message OpBinary)) deriving stock (Generic, Show)@@ -242,22 +245,12 @@ } deriving stock (Generic, Show) deriving anyclass (Decode, Encode) -data PolicyKind = Allow | Deny+data TernaryKind =+ VerifyEd25519Signature deriving stock (Show, Enum, Bounded) -data Policy = Policy- { queries :: Repeated 1 (Message RuleV1)- , kind :: Required 2 (Enumeration PolicyKind)- } deriving stock (Generic, Show)- deriving anyclass (Decode, Encode)--data VerifierPolicies = VerifierPolicies- { symbols :: Repeated 1 (Value Text)- , version :: Optional 2 (Value Int32)- , facts :: Repeated 3 (Message FactV1)- , rules :: Repeated 4 (Message RuleV1)- , checks :: Repeated 5 (Message CheckV1)- , policies :: Repeated 6 (Message Policy)+newtype OpTernary = OpTernary+ { kind :: Required 1 (Enumeration TernaryKind) } deriving stock (Generic, Show) deriving anyclass (Decode, Encode)
src/Auth/Biscuit/ProtoBufAdapter.hs view
@@ -17,9 +17,14 @@ , buildSymbolTable , pbToBlock , blockToPb+ , pbToSignedBlock+ , signedBlockToPb+ , pbToProof ) where import Control.Monad (when)+import Crypto.PubKey.Ed25519 (PublicKey)+import Data.Bifunctor (first) import Data.Int (Int32, Int64) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map@@ -30,6 +35,7 @@ utcTimeToPOSIXSeconds) import Data.Void (absurd) +import qualified Auth.Biscuit.Crypto as Crypto import Auth.Biscuit.Datalog.AST import qualified Auth.Biscuit.Proto as PB import Auth.Biscuit.Utils (maybeToRight)@@ -77,51 +83,84 @@ getSymbolCode :: Integral i => ReverseSymbols -> Text -> i getSymbolCode = (fromIntegral .) . (Map.!) +pbToPublicKey :: PB.PublicKey -> Either String PublicKey+pbToPublicKey PB.PublicKey{..} =+ let keyBytes = PB.getField key+ parseKey = Crypto.eitherCryptoError . Crypto.publicKey+ in case PB.getField algorithm of+ PB.Ed25519 -> first (const "Invalid ed25519 public key") $ parseKey keyBytes++-- | Parse a protobuf signed block into a signed biscuit block+pbToSignedBlock :: PB.SignedBlock -> Either String Crypto.SignedBlock+pbToSignedBlock PB.SignedBlock{..} = do+ sig <- first (const "Invalid signature") $ Crypto.eitherCryptoError $ Crypto.signature $ PB.getField signature+ pk <- pbToPublicKey $ PB.getField nextKey+ pure ( PB.getField block+ , sig+ , pk+ )++publicKeyToPb :: PublicKey -> PB.PublicKey+publicKeyToPb pk = PB.PublicKey+ { algorithm = PB.putField PB.Ed25519+ , key = PB.putField $ Crypto.convert pk+ }++signedBlockToPb :: Crypto.SignedBlock -> PB.SignedBlock+signedBlockToPb (block, sig, pk) = PB.SignedBlock+ { block = PB.putField block+ , signature = PB.putField $ Crypto.convert sig+ , nextKey = PB.putField $ publicKeyToPb pk+ }++pbToProof :: PB.Proof -> Either String (Either Crypto.Signature Crypto.SecretKey)+pbToProof (PB.ProofSignature rawSig) = Left <$> first (const "Invalid signature proof") (Crypto.eitherCryptoError $ Crypto.signature $ PB.getField rawSig)+pbToProof (PB.ProofSecret rawPk) = Right <$> first (const "Invalid public key proof") (Crypto.eitherCryptoError $ Crypto.secretKey $ PB.getField rawPk)+ -- | Parse a protobuf block into a proper biscuit block pbToBlock :: Symbols -> PB.Block -> Either String Block pbToBlock s PB.Block{..} = do let bContext = PB.getField context bVersion = PB.getField version- bFacts <- traverse (pbToFact s) $ PB.getField facts_v1- bRules <- traverse (pbToRule s) $ PB.getField rules_v1- bChecks <- traverse (pbToCheck s) $ PB.getField checks_v1- when (bVersion /= Just 1) $ Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only version 1 is supported"+ bFacts <- traverse (pbToFact s) $ PB.getField facts_v2+ bRules <- traverse (pbToRule s) $ PB.getField rules_v2+ bChecks <- traverse (pbToCheck s) $ PB.getField checks_v2+ when (bVersion /= Just 2) $ Left $ "Unsupported biscuit version: " <> maybe "0" show bVersion <> ". Only version 2 is supported" pure Block{ .. } -- | Turn a biscuit block into a protobuf block, for serialization, -- along with the newly defined symbols-blockToPb :: Symbols -> Int -> Block -> (Symbols, PB.Block)-blockToPb existingSymbols bIndex b@Block{..} =+blockToPb :: Symbols -> Block -> (Symbols, PB.Block)+blockToPb existingSymbols b@Block{..} = let bSymbols = buildSymbolTable existingSymbols b s = reverseSymbols $ existingSymbols <> bSymbols- index = PB.putField $ fromIntegral bIndex symbols = PB.putField $ Map.elems bSymbols context = PB.putField bContext- version = PB.putField $ Just 1- facts_v1 = PB.putField $ factToPb s <$> bFacts- rules_v1 = PB.putField $ ruleToPb s <$> bRules- checks_v1 = PB.putField $ checkToPb s <$> bChecks+ version = PB.putField $ Just 2+ facts_v2 = PB.putField $ factToPb s <$> bFacts+ rules_v2 = PB.putField $ ruleToPb s <$> bRules+ checks_v2 = PB.putField $ checkToPb s <$> bChecks in (bSymbols, PB.Block {..}) -pbToFact :: Symbols -> PB.FactV1 -> Either String Fact-pbToFact s PB.FactV1{predicate} = do- let pbName = PB.getField $ PB.name $ PB.getField predicate- pbIds = PB.getField $ PB.ids $ PB.getField predicate+pbToFact :: Symbols -> PB.FactV2 -> Either String Fact+pbToFact s PB.FactV2{predicate} = do+ let pbName = PB.getField $ PB.name $ PB.getField predicate+ pbTerms = PB.getField $ PB.terms $ PB.getField predicate name <- getSymbol s pbName- terms <- traverse (pbToValue s) pbIds+ terms <- traverse (pbToValue s) pbTerms pure Predicate{..} -factToPb :: ReverseSymbols -> Fact -> PB.FactV1+factToPb :: ReverseSymbols -> Fact -> PB.FactV2 factToPb s Predicate{..} = let- predicate = PB.PredicateV1- { name = PB.putField $ getSymbolCode s name- , ids = PB.putField $ valueToPb s <$> terms+ predicate = PB.PredicateV2+ { name = PB.putField $ getSymbolCode s name+ , terms = PB.putField $ valueToPb s <$> terms }- in PB.FactV1{predicate = PB.putField predicate}+ in PB.FactV2{predicate = PB.putField predicate} -pbToRule :: Symbols -> PB.RuleV1 -> Either String Rule+pbToRule :: Symbols -> PB.RuleV2 -> Either String Rule pbToRule s pbRule = do let pbHead = PB.getField $ PB.head pbRule pbBody = PB.getField $ PB.body pbRule@@ -131,134 +170,128 @@ expressions <- traverse (pbToExpression s) pbExpressions pure Rule {..} -ruleToPb :: ReverseSymbols -> Rule -> PB.RuleV1+ruleToPb :: ReverseSymbols -> Rule -> PB.RuleV2 ruleToPb s Rule{..} =- PB.RuleV1+ PB.RuleV2 { head = PB.putField $ predicateToPb s rhead , body = PB.putField $ predicateToPb s <$> body , expressions = PB.putField $ expressionToPb s <$> expressions } -pbToCheck :: Symbols -> PB.CheckV1 -> Either String Check-pbToCheck s PB.CheckV1{queries} = do+pbToCheck :: Symbols -> PB.CheckV2 -> Either String Check+pbToCheck s PB.CheckV2{queries} = do let toCheck Rule{body,expressions} = QueryItem{qBody = body, qExpressions = expressions } rules <- traverse (pbToRule s) $ PB.getField queries pure $ toCheck <$> rules -checkToPb :: ReverseSymbols -> Check -> PB.CheckV1+checkToPb :: ReverseSymbols -> Check -> PB.CheckV2 checkToPb s items = let dummyHead = Predicate "query" [] toQuery QueryItem{..} = ruleToPb s $ Rule dummyHead qBody qExpressions- in PB.CheckV1 { queries = PB.putField $ toQuery <$> items }+ in PB.CheckV2 { queries = PB.putField $ toQuery <$> items } getSymbol :: (Show i, Integral i) => Symbols -> i -> Either String Text getSymbol s i = maybeToRight ("Missing symbol at id " <> show i) $ Map.lookup (fromIntegral i) s -pbToPredicate :: Symbols -> PB.PredicateV1 -> Either String (Predicate' 'InPredicate 'RegularString)+pbToPredicate :: Symbols -> PB.PredicateV2 -> Either String (Predicate' 'InPredicate 'RegularString) pbToPredicate s pbPredicate = do- let pbName = PB.getField $ PB.name pbPredicate- pbIds = PB.getField $ PB.ids pbPredicate+ let pbName = PB.getField $ PB.name pbPredicate+ pbTerms = PB.getField $ PB.terms pbPredicate name <- getSymbol s pbName- terms <- traverse (pbToTerm s) pbIds+ terms <- traverse (pbToTerm s) pbTerms pure Predicate{..} -predicateToPb :: ReverseSymbols -> Predicate -> PB.PredicateV1+predicateToPb :: ReverseSymbols -> Predicate -> PB.PredicateV2 predicateToPb s Predicate{..} =- PB.PredicateV1- { name = PB.putField $ getSymbolCode s name- , ids = PB.putField $ termToPb s <$> terms+ PB.PredicateV2+ { name = PB.putField $ getSymbolCode s name+ , terms = PB.putField $ termToPb s <$> terms } pbTimeToUtcTime :: Int64 -> UTCTime pbTimeToUtcTime = posixSecondsToUTCTime . fromIntegral -pbToTerm :: Symbols -> PB.IDV1 -> Either String ID+pbToTerm :: Symbols -> PB.TermV2 -> Either String Term pbToTerm s = \case- PB.IDSymbol f -> Symbol <$> getSymbol s (PB.getField f)- PB.IDInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f- PB.IDString f -> pure $ LString $ PB.getField f- PB.IDDate f -> pure $ LDate $ pbTimeToUtcTime $ PB.getField f- PB.IDBytes f -> pure $ LBytes $ PB.getField f- PB.IDBool f -> pure $ LBool $ PB.getField f- PB.IDVariable f -> Variable <$> getSymbol s (PB.getField f)- PB.IDIDSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f)+ PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f+ PB.TermString f -> LString <$> getSymbol s (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.TermVariable f -> Variable <$> getSymbol s (PB.getField f)+ PB.TermTermSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f) -termToPb :: ReverseSymbols -> ID -> PB.IDV1+termToPb :: ReverseSymbols -> Term -> PB.TermV2 termToPb s = \case- Variable n -> PB.IDVariable $ PB.putField $ getSymbolCode s n- Symbol n -> PB.IDSymbol $ PB.putField $ getSymbolCode s n- LInteger v -> PB.IDInteger $ PB.putField $ fromIntegral v- LString v -> PB.IDString $ PB.putField v- LDate v -> PB.IDDate $ PB.putField $ round $ utcTimeToPOSIXSeconds v- LBytes v -> PB.IDBytes $ PB.putField v- LBool v -> PB.IDBool $ PB.putField v- TermSet vs -> PB.IDIDSet $ PB.putField $ PB.IDSet $ PB.putField $ setValueToPb s <$> Set.toList vs+ Variable n -> PB.TermVariable $ PB.putField $ getSymbolCode s n+ LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v+ LString v -> PB.TermString $ PB.putField $ 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+ TermSet vs -> PB.TermTermSet $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs Antiquote v -> absurd v -pbToValue :: Symbols -> PB.IDV1 -> Either String Value+pbToValue :: Symbols -> PB.TermV2 -> Either String Value pbToValue s = \case- PB.IDSymbol f -> Symbol <$> getSymbol s (PB.getField f)- PB.IDInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f- PB.IDString f -> pure $ LString $ PB.getField f- PB.IDDate f -> pure $ LDate $ pbTimeToUtcTime $ PB.getField f- PB.IDBytes f -> pure $ LBytes $ PB.getField f- PB.IDBool f -> pure $ LBool $ PB.getField f- PB.IDVariable _ -> Left "Variables can't appear in facts"- PB.IDIDSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f)+ PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f+ PB.TermString f -> LString <$> getSymbol s (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.TermVariable _ -> Left "Variables can't appear in facts"+ PB.TermTermSet f -> TermSet . Set.fromList <$> traverse (pbToSetValue s) (PB.getField . PB.set $ PB.getField f) -valueToPb :: ReverseSymbols -> Value -> PB.IDV1+valueToPb :: ReverseSymbols -> Value -> PB.TermV2 valueToPb s = \case- Symbol n -> PB.IDSymbol $ PB.putField $ getSymbolCode s n- LInteger v -> PB.IDInteger $ PB.putField $ fromIntegral v- LString v -> PB.IDString $ PB.putField v- LDate v -> PB.IDDate $ PB.putField $ round $ utcTimeToPOSIXSeconds v- LBytes v -> PB.IDBytes $ PB.putField v- LBool v -> PB.IDBool $ PB.putField v- TermSet vs -> PB.IDIDSet $ PB.putField $ PB.IDSet $ PB.putField $ setValueToPb s <$> Set.toList vs+ LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v+ LString v -> PB.TermString $ PB.putField $ 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+ TermSet vs -> PB.TermTermSet $ PB.putField $ PB.TermSet $ PB.putField $ setValueToPb s <$> Set.toList vs Variable v -> absurd v Antiquote v -> absurd v -pbToSetValue :: Symbols -> PB.IDV1 -> Either String (ID' 'WithinSet 'InFact 'RegularString)+pbToSetValue :: Symbols -> PB.TermV2 -> Either String (Term' 'WithinSet 'InFact 'RegularString) pbToSetValue s = \case- PB.IDSymbol f -> Symbol <$> getSymbol s (PB.getField f)- PB.IDInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f- PB.IDString f -> pure $ LString $ PB.getField f- PB.IDDate f -> pure $ LDate $ pbTimeToUtcTime $ PB.getField f- PB.IDBytes f -> pure $ LBytes $ PB.getField f- PB.IDBool f -> pure $ LBool $ PB.getField f- PB.IDVariable _ -> Left "Variables can't appear in facts or sets"- PB.IDIDSet _ -> Left "Sets can't be nested"+ PB.TermInteger f -> pure $ LInteger $ fromIntegral $ PB.getField f+ PB.TermString f -> LString <$> getSymbol s (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.TermVariable _ -> Left "Variables can't appear in facts or sets"+ PB.TermTermSet _ -> Left "Sets can't be nested" -setValueToPb :: ReverseSymbols -> ID' 'WithinSet 'InFact 'RegularString -> PB.IDV1+setValueToPb :: ReverseSymbols -> Term' 'WithinSet 'InFact 'RegularString -> PB.TermV2 setValueToPb s = \case- Symbol n -> PB.IDSymbol $ PB.putField $ getSymbolCode s n- LInteger v -> PB.IDInteger $ PB.putField $ fromIntegral v- LString v -> PB.IDString $ PB.putField v- LDate v -> PB.IDDate $ PB.putField $ round $ utcTimeToPOSIXSeconds v- LBytes v -> PB.IDBytes $ PB.putField v- LBool v -> PB.IDBool $ PB.putField v+ LInteger v -> PB.TermInteger $ PB.putField $ fromIntegral v+ LString v -> PB.TermString $ PB.putField $ 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 TermSet v -> absurd v Variable v -> absurd v Antiquote v -> absurd v -pbToExpression :: Symbols -> PB.ExpressionV1 -> Either String Expression-pbToExpression s PB.ExpressionV1{ops} = do+pbToExpression :: Symbols -> PB.ExpressionV2 -> Either String Expression+pbToExpression s PB.ExpressionV2{ops} = do parsedOps <- traverse (pbToOp s) $ PB.getField ops fromStack parsedOps -expressionToPb :: ReverseSymbols -> Expression -> PB.ExpressionV1+expressionToPb :: ReverseSymbols -> Expression -> PB.ExpressionV2 expressionToPb s e = let ops = opToPb s <$> toStack e- in PB.ExpressionV1 { ops = PB.putField ops }+ in PB.ExpressionV2 { 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.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 opToPb :: ReverseSymbols -> Op -> PB.Op
− src/Auth/Biscuit/Sel.hs
@@ -1,334 +0,0 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE RecordWildCards #-}-{- HLINT ignore "Reduce duplication" -}-{-|- Module : Auth.Biscuit.Sel- Copyright : © Clément Delafargue, 2021- License : MIT- Maintainer : clement@delafargue.name- Cryptographic primitives necessary to sign and verify biscuit tokens--}-module Auth.Biscuit.Sel- ( Keypair (..)- , PrivateKey- , PublicKey- , Signature (..)- , parsePrivateKey- , parsePublicKey- , serializePrivateKey- , serializePublicKey- , newKeypair- , fromPrivateKey- , signBlock- , aggregate- , verifySignature- , hashBytes- ) where--import Control.Monad.Cont (ContT (..), runContT)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.ByteString (ByteString, packCStringLen,- useAsCStringLen)-import qualified Data.ByteString as BS-import Data.ByteString.Base16 as Hex-import Data.Foldable (for_)-import Data.Functor (void)-import Data.List.NonEmpty (NonEmpty)-import Data.Primitive.Ptr (copyPtr)-import Foreign.C.Types-import Foreign.Marshal.Alloc-import Foreign.Ptr-import Libsodium---- | A private key used to generate a biscuit-newtype PrivateKey = PrivateKey ByteString- deriving newtype (Eq, Ord)--instance Show PrivateKey where- show (PrivateKey bs) = show $ Hex.encode bs---- | Parse a private key from raw bytes.--- This returns `Nothing` if the raw bytes don't have the expected length-parsePrivateKey :: ByteString -> Maybe PrivateKey-parsePrivateKey bs = if BS.length bs == cs2int crypto_core_ristretto255_scalarbytes- then Just (PrivateKey bs)- else Nothing---- | Serialize a private key to raw bytes-serializePrivateKey :: PrivateKey -> ByteString-serializePrivateKey (PrivateKey bs) = bs---- | A public key used to generate a biscuit-newtype PublicKey = PublicKey ByteString- deriving newtype (Eq, Ord)---- | Parse a public key from raw bytes.--- This returns `Nothing` if the raw bytes don't have the expected length-parsePublicKey :: ByteString -> Maybe PublicKey-parsePublicKey bs = if BS.length bs == cs2int crypto_core_ristretto255_bytes- then Just (PublicKey bs)- else Nothing---- | Serialize a public key to raw bytes-serializePublicKey :: PublicKey -> ByteString-serializePublicKey (PublicKey bs) = bs--instance Show PublicKey where- show (PublicKey bs) = show $ Hex.encode bs---- | A keypair containing both a private key and a public key-data Keypair- = Keypair- { privateKey :: PrivateKey- -- ^ the private key- , publicKey :: PublicKey- -- ^ the public key- } deriving (Eq, Ord)--instance Show Keypair where- show Keypair{privateKey, publicKey} =- show privateKey <> "/" <> show publicKey--keypairFromScalar :: Scalar -> CIO a Keypair-keypairFromScalar scalarBuf = do- pointBuf <- scalarToPoint scalarBuf- privateKey <- PrivateKey <$> scalarToByteString scalarBuf- publicKey <- PublicKey <$> pointToByteString pointBuf- pure Keypair{..}---- | Generate a random keypair-newKeypair :: IO Keypair-newKeypair = runCIO $ do- scalar <- randomScalar- keypairFromScalar scalar---- | Construct a keypair from a private key-fromPrivateKey :: PrivateKey -> IO Keypair-fromPrivateKey (PrivateKey privBs) = runCIO $ do- (privBuf, _) <- withBSLen privBs- keypairFromScalar privBuf---- | The signature of a series of blocks (raw bytestrings)-data Signature- = Signature- { parameters :: [ByteString]- -- ^ the list of parameters used to sign each block- , z :: ByteString- -- ^ the aggregated signature- } deriving (Eq, Show)--type Scalar = Ptr CUChar-type Point = Ptr CUChar---- | Pointer allocations are written in a continuation passing style,--- this type alias allows to use monadic notation instead of nesting--- callbacks-type CIO a = ContT a IO---- | Run a continuation to get back an IO value.-runCIO :: ContT a IO a -> IO a-runCIO = (`runContT` pure)--voidIO :: IO a -> CIO b ()-voidIO = void . liftIO--scalarToByteString :: MonadIO m => Ptr CUChar -> m ByteString-scalarToByteString ptr =- let scalarIntSize = cs2int crypto_core_ristretto255_scalarbytes- in liftIO $ packCStringLen (castPtr ptr, scalarIntSize)--pointToByteString :: MonadIO m => Ptr CUChar -> m ByteString-pointToByteString ptr =- let pointIntSize = cs2int crypto_core_ristretto255_bytes- in liftIO $ packCStringLen (castPtr ptr, pointIntSize)--randomScalar :: CIO a Scalar-randomScalar = do- scalarBuf <- withScalar- liftIO $ crypto_core_ristretto255_scalar_random scalarBuf- pure scalarBuf--withScalar :: CIO a Scalar-withScalar =- let intScalarSize = cs2int crypto_core_ristretto255_scalarbytes- in ContT $ allocaBytes intScalarSize--withPoint :: CIO a Point-withPoint =- let intPointSize = cs2int crypto_core_ristretto255_bytes- in ContT $ allocaBytes intPointSize--scalarToPoint :: Scalar- -> CIO a Point-scalarToPoint scalar = do- pointBuf <- withPoint- voidIO $ crypto_scalarmult_ristretto255_base pointBuf scalar- pure pointBuf--withBSLen :: ByteString- -> ContT a IO (Ptr CUChar, CULLong)-withBSLen bs = do- (buf, int) <- ContT $ useAsCStringLen bs- pure (castPtr buf, toEnum int)--scalarAdd :: Scalar -> Scalar -> CIO a Scalar-scalarAdd x y = do- z <- withScalar- liftIO $ crypto_core_ristretto255_scalar_add z x y- pure z--scalarAddBs :: ByteString -> ByteString -> CIO a ByteString-scalarAddBs xBs yBs = do- (x, _) <- withBSLen xBs- (y, _) <- withBSLen yBs- z <- scalarAdd x y- scalarToByteString z--scalarMul :: Scalar -> Scalar -> CIO a Scalar-scalarMul x y = do- z <- withScalar- z <$ liftIO (crypto_core_ristretto255_scalar_mul z x y)--scalarSub :: Scalar -> Scalar -> CIO a Scalar-scalarSub x y = do- z <- withScalar- z <$ liftIO (crypto_core_ristretto255_scalar_sub z x y)--scalarReduce :: Ptr CUChar -> CIO a Scalar-scalarReduce bytes = do- z <- withScalar- z <$ liftIO (crypto_core_ristretto255_scalar_reduce z bytes)--scalarMulPoint :: Scalar -> Point -> CIO a Point-scalarMulPoint p q = do- n <- withScalar- n <$ liftIO (crypto_scalarmult_ristretto255 n p q)--pointAdd :: Point -> Point -> CIO a Point-pointAdd p q = do- r <- withPoint- r <$ liftIO (crypto_core_ristretto255_add r p q)--pointSub :: Point -> Point -> CIO a Point-pointSub p q = do- r <- withPoint- r <$ liftIO (crypto_core_ristretto255_sub r p q)--zeroPoint :: CIO a Point-zeroPoint = do- p <- withPoint- p <$ zeroizePoint p--zeroizePoint :: MonadIO m => Point -> m ()-zeroizePoint p = liftIO $ sodium_memzero p crypto_core_ristretto255_bytes--isZeroPoint :: MonadIO m => Point -> m Bool-isZeroPoint p = liftIO $- (== 1) <$> sodium_is_zero p crypto_core_ristretto255_scalarbytes---- | Hash a bytestring with SHA256-hashBytes :: ByteString- -> IO ByteString-hashBytes message = runCIO $ do- out <- ContT $ allocaBytes $ cs2int crypto_hash_sha256_bytes- (buf, len) <- withBSLen message- voidIO $ crypto_hash_sha256 out buf len- liftIO $ packCStringLen (castPtr out, cs2int crypto_hash_sha256_bytes)--hashPoint :: Point- -> ContT a IO Scalar-hashPoint point = do- hash <- ContT $ allocaBytes (cs2int crypto_hash_sha512_bytes)- voidIO $ crypto_hash_sha512 hash point (fromInteger $ toInteger crypto_core_ristretto255_bytes)- scalarReduce hash--copyPointFrom :: Point -> Point -> IO ()-copyPointFrom to from = copyPtr to from (cs2int crypto_core_ristretto255_bytes)--hashMessage :: ByteString- -> ByteString- -> CIO a Scalar-hashMessage publicKey message = do- (kpBuf, kpLen) <- withBSLen publicKey- (msgBuf, msgLen) <- withBSLen message- state <- liftIO crypto_hash_sha512_state'malloc- statePtr <- ContT $ crypto_hash_sha512_state'ptr state- voidIO $ crypto_hash_sha512_init statePtr- voidIO $ crypto_hash_sha512_update statePtr kpBuf kpLen- voidIO $ crypto_hash_sha512_update statePtr msgBuf msgLen- hash <- ContT $ allocaBytes (cs2int crypto_hash_sha512_bytes)- voidIO $ crypto_hash_sha512_final statePtr hash- scalar <- withScalar- voidIO $ crypto_core_ristretto255_scalar_reduce scalar hash- pure scalar---- | Sign a single block with the given keypair-signBlock :: Keypair -> ByteString -> IO Signature-signBlock Keypair{publicKey,privateKey} message = do- let PublicKey pubBs = publicKey- PrivateKey prvBs = privateKey- (`runContT` pure) $ do- (pk, _) <- withBSLen prvBs-- r <- randomScalar- aa <- scalarToPoint r- d <- hashPoint aa- e <- hashMessage pubBs message- rd <- scalarMul r d- epk <- scalarMul e pk- z <- scalarSub rd epk- aaBs <- pointToByteString aa- zBs <- scalarToByteString z- pure Signature { parameters = [aaBs]- , z = zBs- }---- | Aggregate two signatures into a single one-aggregate :: Signature -> Signature -> IO Signature-aggregate first second = runCIO $ do- z <- scalarAddBs (z first) (z second)- pure Signature- { parameters = parameters first <> parameters second- , z- }---- | Verify a signature, given a list of messages and associated--- public keys-verifySignature :: NonEmpty (PublicKey, ByteString)- -> Signature- -> IO Bool-verifySignature messagesAndPks Signature{parameters,z} = runCIO $ do- zP <- scalarToPoint . fst =<< withBSLen z- eiXiRes <- computeHashMSums messagesAndPks- diAiRes <- computeHashPSums parameters- resTmp <- pointAdd zP eiXiRes- res <- pointSub resTmp diAiRes- isZeroPoint res--computeHashMSums :: NonEmpty (PublicKey, ByteString)- -> ContT a IO Point-computeHashMSums messagesAndPks = do- eiXiRes <- zeroPoint- for_ messagesAndPks $ \(PublicKey publicKey, message) -> do- ei <- hashMessage publicKey message- eiXi <- scalarMulPoint ei . fst =<< withBSLen publicKey- eiXiResTmp <- pointAdd eiXiRes eiXi- liftIO $ copyPointFrom eiXiRes eiXiResTmp- pure eiXiRes--computeHashPSums :: [ByteString] -- parameters- -> ContT a IO Point-computeHashPSums parameters = do- diAiRes <- zeroPoint- for_ parameters $ \aa -> do- (aaBuf, _) <- withBSLen aa- di <- hashPoint aaBuf- diAi <- scalarMulPoint di aaBuf- diAiResTmp <- pointAdd diAiRes diAi- liftIO $ copyPointFrom diAiRes diAiResTmp- pure diAiRes--cs2int :: CSize -> Int-cs2int = fromInteger . toInteger
src/Auth/Biscuit/Token.hs view
@@ -1,6 +1,10 @@-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RecordWildCards #-}+{- HLINT ignore "Reduce duplication" -} {-| Module : Auth.Biscuit.Token Copyright : © Clément Delafargue, 2021@@ -9,210 +13,446 @@ Module defining the main biscuit-related operations -} module Auth.Biscuit.Token- ( Biscuit (..)+ ( Biscuit+ , rootKeyId+ , symbols+ , authority+ , blocks+ , proof+ , proofCheck , ParseError (..)- , VerificationError (..) , ExistingBlock+ , ParsedSignedBlock+ -- $openOrSealed+ , OpenOrSealed+ , Open+ , Sealed+ , BiscuitProof (..)+ , Verified+ , Unverified , mkBiscuit , addBlock- , checkBiscuitSignature- , parseBiscuit+ , BiscuitEncoding (..)+ , ParserConfig (..)+ , parseBiscuitWith+ , parseBiscuitUnverified+ , checkBiscuitSignatures , serializeBiscuit- , verifyBiscuit- , verifyBiscuitWithLimits+ , authorizeBiscuit+ , authorizeBiscuitWithLimits+ , fromOpen+ , fromSealed+ , asOpen+ , asSealed+ , seal - , BlockWithRevocationIds (..) , getRevocationIds+ , getVerifiedBiscuitPublicKey+ ) where -import Control.Monad (when)-import Control.Monad.Except (runExceptT, throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Bifunctor (first)-import Data.ByteString (ByteString)-import Data.List.NonEmpty (NonEmpty ((:|)))-import qualified Data.List.NonEmpty as NE+import Control.Monad (join, when)+import Data.Bifunctor (first)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base64.URL as B64+import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE+import Data.Set (Set)+import qualified Data.Set as Set -import Auth.Biscuit.Datalog.AST (Block, Query, Verifier)-import Auth.Biscuit.Datalog.Executor (BlockWithRevocationIds (..),- ExecutionError, Limits,- defaultLimits,- runVerifierWithLimits)-import qualified Auth.Biscuit.Proto as PB-import Auth.Biscuit.ProtoBufAdapter (Symbols, blockToPb,- commonSymbols, extractSymbols,- pbToBlock)-import Auth.Biscuit.Sel (Keypair (publicKey), PublicKey,- Signature (..), aggregate,- hashBytes, newKeypair,- parsePublicKey,- serializePublicKey, signBlock,- verifySignature)-import Auth.Biscuit.Utils (maybeToRight)+import Auth.Biscuit.Crypto (PublicKey, SecretKey,+ Signature, SignedBlock,+ convert,+ getSignatureProof,+ signBlock, toPublic,+ verifyBlocks,+ verifySecretProof,+ verifySignatureProof)+import Auth.Biscuit.Datalog.AST (Authorizer, Block)+import Auth.Biscuit.Datalog.Executor (ExecutionError, Limits,+ defaultLimits)+import Auth.Biscuit.Datalog.ScopedExecutor (AuthorizationSuccess,+ runAuthorizerWithLimits)+import qualified Auth.Biscuit.Proto as PB+import Auth.Biscuit.ProtoBufAdapter (Symbols, blockToPb,+ commonSymbols,+ extractSymbols, pbToBlock,+ pbToProof,+ pbToSignedBlock,+ signedBlockToPb) -- | Protobuf serialization does not have a guaranteed deterministic behaviour, -- 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) --- | A parsed biscuit-data Biscuit+-- $openOrSealed+--+-- Biscuit tokens can be /open/ (capable of being attenuated further) or+-- /sealed/ (not capable of being attenuated further). Some operations+-- like verification work on both kinds, while others (like attenuation)+-- only work on a single kind. The 'OpenOrSealed', 'Open' and 'Sealed' trio+-- represents the different possibilities. 'OpenOrSealed' is usually obtained+-- through parsing, while 'Open' is obtained by creating a new biscuit (or+-- attenuating an existing one), and 'Sealed' is obtained by sealing an open+-- biscuit++-- | This datatype represents the final proof of a biscuit, which can be either+-- /open/ or /sealed/. This is the typical state of a biscuit that's been parsed.+data OpenOrSealed+ = SealedProof Signature+ | OpenProof SecretKey+ deriving (Eq, Show)++-- | This datatype represents the final proof of a biscuit statically known to be+-- /open/ (capable of being attenuated further). In that case the proof is a secret+-- key that can be used to sign a new block.+newtype Open = Open SecretKey++-- | This datatype represents the final proof of a biscuit statically known to be+-- /sealed/ (not capable of being attenuated further). In that case the proof is a+-- signature proving that the party who sealed the token did know the last secret+-- key.+newtype Sealed = Sealed Signature++-- | This class allows functions working on both open and sealed biscuits to accept+-- indifferently 'OpenOrSealed', 'Open' or 'Sealed' biscuits. It has no laws, it only+-- projects 'Open' and 'Sealed' to the general 'OpenOrSealed' case.+class BiscuitProof a where+ toPossibleProofs :: a -> OpenOrSealed++instance BiscuitProof OpenOrSealed where+ toPossibleProofs = id+instance BiscuitProof Sealed where+ toPossibleProofs (Sealed sig) = SealedProof sig+instance BiscuitProof Open where+ toPossibleProofs (Open sk) = OpenProof sk++-- $verifiedOrUnverified+--+-- The default parsing mechanism for biscuits checks the signature before parsing the blocks+-- contents (this reduces the attack surface, as only biscuits with a valid signature are parsed).+-- In some cases, we still want to operate on biscuits without knowing the public key necessary+-- to check signatures (eg for inspection, or for generically adding attenuation blocks). In that+-- case, we can have parsed tokens which signatures have /not/ been verified. In order to+-- accidentally forgetting to check signatures, parsed biscuits keep track of whether the+-- signatures have been verified with a dedicated type parameter, which can be instantiated with+-- two types: 'Verified' and 'Unverified'. 'Verified' additionally keeps track of the 'PublicKey'+-- that has been used to verify the signatures.++-- | Proof that a biscuit had its signatures verified with the carried root 'PublicKey'+newtype Verified = Verified PublicKey+ deriving stock (Eq, Show)++-- | Marker that a biscuit was parsed without having its signatures verified. Such a biscuit+-- cannot be trusted yet.+data Unverified = Unverified+ deriving stock (Eq, Show)++-- | A parsed biscuit. The @proof@ type param can be one of 'Open', 'Sealed' or 'OpenOrSealed'.+-- It describes whether a biscuit is open to further attenuation, or sealed and not modifyable+-- further.+--+-- The @check@ type param can be either 'Verified' or 'Unverified' and keeps track of whether+-- the blocks signatures (and final proof) have been verified with a given root 'PublicKey'.+--+-- The constructor is not exposed in order to ensure that 'Biscuit' values can only be created+-- by trusted code paths.+data Biscuit proof check = Biscuit- { symbols :: Symbols+ { rootKeyId :: Maybe Int+ -- ^ an optional identifier for the expected public key+ , symbols :: Symbols -- ^ The symbols already defined in the contained blocks- , authority :: (PublicKey, ExistingBlock)+ , authority :: ParsedSignedBlock -- ^ The authority block, along with the associated public key. The public key -- is kept around since it's embedded in the serialized biscuit, but should not -- be used for verification. An externally provided public key should be used instead.- , blocks :: [(PublicKey, ExistingBlock)]+ , blocks :: [ParsedSignedBlock] -- ^ The extra blocks, along with the public keys needed- , signature :: Signature+ , proof :: proof+ -- ^ The final proof allowing to check the validity of a biscuit+ , proofCheck :: check+ -- ^ A value that keeps track of whether the biscuit signatures have been verified or not. } deriving (Eq, Show) --- | Create a new biscuit with the provided authority block-mkBiscuit :: Keypair -> Block -> IO Biscuit-mkBiscuit keypair authority = do- let authorityPub = publicKey keypair- (s, authoritySerialized) = PB.encodeBlock <$> blockToPb commonSymbols 0 authority- signature <- signBlock keypair authoritySerialized- pure $ Biscuit { authority = (authorityPub, (authoritySerialized, authority))- , blocks = []- , symbols = commonSymbols <> s- , signature- }+-- | Turn a 'Biscuit' statically known to be 'Open' into a more generic 'OpenOrSealed' 'Biscuit'+-- (essentially /forgetting/ about the fact it's 'Open')+fromOpen :: Biscuit Open check -> Biscuit OpenOrSealed check+fromOpen b@Biscuit{proof = Open p } = b { proof = OpenProof p } --- | Add a block to an existing biscuit. The block will be signed--- with a randomly-generated keypair-addBlock :: Block -> Biscuit -> IO Biscuit-addBlock newBlock b@Biscuit{..} = do- let (s, newBlockSerialized) = PB.encodeBlock <$> blockToPb symbols (length blocks) newBlock- keypair <- newKeypair- newSig <- signBlock keypair newBlockSerialized- endSig <- aggregate signature newSig- pure $ b { blocks = blocks <> [(publicKey keypair, (newBlockSerialized, newBlock))]- , symbols = symbols <> s- , signature = endSig+-- | Turn a 'Biscuit' statically known to be 'Sealed' into a more generic 'OpenOrSealed' 'Biscuit'+-- (essentially /forgetting/ about the fact it's 'Sealed')+fromSealed :: Biscuit Sealed check -> Biscuit OpenOrSealed check+fromSealed b@Biscuit{proof = Sealed p } = b { proof = SealedProof p }++-- | Try to turn a 'Biscuit' that may be open or sealed into a biscuit that's statically known+-- to be 'Sealed'.+asSealed :: Biscuit OpenOrSealed check -> Maybe (Biscuit Sealed check)+asSealed b@Biscuit{proof} = case proof of+ SealedProof p -> Just $ b { proof = Sealed p }+ _ -> Nothing++-- | Try to turn a 'Biscuit' that may be open or sealed into a biscuit that's statically known+-- to be 'Open'.+asOpen :: Biscuit OpenOrSealed check -> Maybe (Biscuit Open check)+asOpen b@Biscuit{proof} = case proof of+ OpenProof p -> Just $ b { proof = Open p }+ _ -> Nothing++toParsedSignedBlock :: Block -> SignedBlock -> ParsedSignedBlock+toParsedSignedBlock block (serializedBlock, sig, pk) = ((serializedBlock, block), sig, pk)++-- | Create a new biscuit with the provided authority block. Such a biscuit is 'Open' to+-- further attenuation.+mkBiscuit :: SecretKey -> Block -> IO (Biscuit Open Verified)+mkBiscuit sk authority = do+ let (authoritySymbols, authoritySerialized) = PB.encodeBlock <$> blockToPb commonSymbols authority+ (signedBlock, nextSk) <- signBlock sk authoritySerialized+ pure Biscuit { rootKeyId = Nothing+ , authority = toParsedSignedBlock authority signedBlock+ , blocks = []+ , symbols = commonSymbols <> authoritySymbols+ , proof = Open nextSk+ , proofCheck = Verified $ toPublic sk+ }++-- | Add a block to an existing biscuit. Only 'Open' biscuits can be attenuated; the+-- newly created biscuit is 'Open' as well.+addBlock :: Block+ -> Biscuit Open check+ -> IO (Biscuit Open check)+addBlock block b@Biscuit{..} = do+ let (blockSymbols, blockSerialized) = PB.encodeBlock <$> blockToPb symbols block+ Open p = proof+ (signedBlock, nextSk) <- signBlock p blockSerialized+ pure $ b { blocks = blocks <> [toParsedSignedBlock block signedBlock]+ , symbols = symbols <> blockSymbols+ , proof = Open nextSk } --- | Only check a biscuit signature. This can be used to perform an early check, before--- bothering with constructing a verifier.-checkBiscuitSignature :: Biscuit -> PublicKey -> IO Bool-checkBiscuitSignature Biscuit{..} publicKey =- let publicKeysAndMessages = (publicKey, fst $ snd authority) :| (fmap fst <$> blocks)- in verifySignature publicKeysAndMessages signature+-- | Turn an 'Open' biscuit into a 'Sealed' one, preventing it from being attenuated+-- further. A 'Sealed' biscuit cannot be turned into an 'Open' one.+seal :: Biscuit Open check -> Biscuit Sealed check+seal b@Biscuit{..} =+ let Open sk = proof+ ((lastPayload, _), lastSig, lastPk) = NE.last $ authority :| blocks+ newProof = Sealed $ getSignatureProof (lastPayload, lastSig, lastPk) sk+ in b { proof = newProof } --- | Errors that can happen when parsing a biscuit+-- | Serialize a biscuit to a raw bytestring+serializeBiscuit :: BiscuitProof p => Biscuit p Verified -> ByteString+serializeBiscuit Biscuit{..} =+ let proofField = case toPossibleProofs proof of+ SealedProof sig -> PB.ProofSignature $ PB.putField (convert sig)+ OpenProof sk -> PB.ProofSecret $ PB.putField (convert sk)+ in PB.encodeBlockList PB.Biscuit+ { rootKeyId = PB.putField Nothing -- TODO+ , authority = PB.putField $ toPBSignedBlock authority+ , blocks = PB.putField $ toPBSignedBlock <$> blocks+ , proof = PB.putField proofField+ }++toPBSignedBlock :: ParsedSignedBlock -> PB.SignedBlock+toPBSignedBlock ((block, _), sig, pk) = signedBlockToPb (block, sig, pk)++-- | 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 data ParseError = InvalidHexEncoding -- ^ The provided ByteString is not hex-encoded | InvalidB64Encoding -- ^ The provided ByteString is not base64-encoded- | InvalidProtobufSer String+ | InvalidProtobufSer Bool String -- ^ The provided ByteString does not contain properly serialized protobuf values- | InvalidProtobuf String+ | InvalidProtobuf Bool String -- ^ The bytestring was correctly deserialized from protobuf, but the values can't be turned into a proper biscuit+ | InvalidSignatures+ -- ^ The signatures were invalid+ | InvalidProof+ -- ^ The biscuit final proof was invalid+ | RevokedBiscuit+ -- ^ The biscuit has been revoked deriving (Eq, Show) --- | Parse a biscuit from a raw bytestring.-parseBiscuit :: ByteString -> Either ParseError Biscuit-parseBiscuit bs = do- blockList <- first InvalidProtobufSer $ PB.decodeBlockList bs- let pbBlocks = PB.getField $ PB.blocks blockList- pbKeys = PB.getField $ PB.keys blockList- pbAuthority = PB.getField $ PB.authority blockList- pbSignature = PB.getField $ PB.signature blockList- when (length pbBlocks + 1 /= length pbKeys) $ Left (InvalidProtobufSer $ "Length mismatch " <> show (length pbBlocks, length pbKeys))- rawAuthority <- first InvalidProtobufSer $ PB.decodeBlock pbAuthority- rawBlocks <- traverse (first InvalidProtobufSer . PB.decodeBlock) pbBlocks- let s = extractSymbols commonSymbols $ rawAuthority : rawBlocks+data BiscuitWrapper+ = BiscuitWrapper+ { wAuthority :: SignedBlock+ , wBlocks :: [SignedBlock]+ , wProof :: OpenOrSealed+ , wRootKeyId :: Maybe Int+ } +parseBiscuitWrapper :: ByteString -> Either ParseError BiscuitWrapper+parseBiscuitWrapper bs = do+ blockList <- first (InvalidProtobufSer True) $ PB.decodeBlockList bs+ let rootKeyId = fromEnum <$> PB.getField (PB.rootKeyId blockList)+ signedAuthority <- first (InvalidProtobuf True) $ pbToSignedBlock $ PB.getField $ PB.authority blockList+ signedBlocks <- first (InvalidProtobuf True) $ traverse pbToSignedBlock $ PB.getField $ PB.blocks blockList+ proof <- first (InvalidProtobuf True) $ pbToProof $ PB.getField $ PB.proof blockList - parsedAuthority <- (pbAuthority,) <$> blockFromPB s rawAuthority- parsedBlocks <- zip pbBlocks <$> traverse (blockFromPB s) rawBlocks- parsedKeys <- maybeToRight (InvalidProtobufSer "Invalid pubkeys") $ traverse parsePublicKey pbKeys- let blocks = zip (drop 1 parsedKeys) parsedBlocks- authority = (head parsedKeys, parsedAuthority)- symbols = s- signature = Signature { parameters = PB.getField $ PB.parameters pbSignature- , z = PB.getField $ PB.z pbSignature- }- pure Biscuit{..}+ pure $ BiscuitWrapper+ { wAuthority = signedAuthority+ , wBlocks = signedBlocks+ , wProof = either SealedProof+ OpenProof+ proof+ , wRootKeyId = rootKeyId+ , ..+ } --- | Serialize a biscuit to a raw bytestring-serializeBiscuit :: Biscuit -> ByteString-serializeBiscuit Biscuit{..} =- let authorityBs = fst $ snd authority- blocksBs = fst . snd <$> blocks- keys = serializePublicKey . fst <$> authority : blocks- Signature{..} = signature- sigPb = PB.Signature- { parameters = PB.putField parameters- , z = PB.putField z- }- in PB.encodeBlockList PB.Biscuit- { authority = PB.putField authorityBs- , blocks = PB.putField blocksBs- , keys = PB.putField keys- , signature = PB.putField sigPb- }+checkRevocation :: Applicative m+ => (Set ByteString -> m Bool)+ -> BiscuitWrapper+ -> m (Either ParseError BiscuitWrapper)+checkRevocation isRevoked bw@BiscuitWrapper{wAuthority,wBlocks} =+ let getRevocationId (_, sig, _) = convert sig+ revocationIds = getRevocationId <$> wAuthority :| wBlocks+ keepIfNotRevoked True = Left RevokedBiscuit+ keepIfNotRevoked False = Right bw+ in keepIfNotRevoked <$> isRevoked (Set.fromList $ NE.toList revocationIds) --- | Parse a single block from a protobuf value-blockFromPB :: Symbols -> PB.Block -> Either ParseError Block-blockFromPB s pbBlock = first InvalidProtobuf $ pbToBlock s pbBlock+parseBlocks :: BiscuitWrapper -> Either ParseError (Symbols, NonEmpty ParsedSignedBlock)+parseBlocks BiscuitWrapper{..} = do+ let toRawSignedBlock (payload, sig, pk') = do+ pbBlock <- first (InvalidProtobufSer False) $ PB.decodeBlock payload+ pure ((payload, pbBlock), sig, pk') --- | An error that can happen when verifying a biscuit-data VerificationError- = SignatureError- -- ^ The signature is invalid- | DatalogError ExecutionError- -- ^ The checks and policies could not be verified- deriving (Eq, Show)+ rawAuthority <- toRawSignedBlock wAuthority+ rawBlocks <- traverse toRawSignedBlock wBlocks --- | Given a provided verifier (a set of facts, rules, checks and policies),--- and a public key, verify a biscuit:------ - make sure the biscuit has been signed with the private key associated to the public key--- - make sure the biscuit is valid for the provided verifier-verifyBiscuitWithLimits :: Limits -> Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)-verifyBiscuitWithLimits l b verifier pub = runExceptT $ do- sigCheck <- liftIO $ checkBiscuitSignature b pub- when (not sigCheck) $ throwError SignatureError- authorityBlock :| attBlocks <- liftIO $ getRevocationIds b- verifResult <- liftIO $ runVerifierWithLimits l authorityBlock attBlocks verifier- case verifResult of- Left e -> throwError $ DatalogError e- Right p -> pure p+ let symbols = extractSymbols commonSymbols $ (\((_, p), _, _) -> p) <$> rawAuthority : rawBlocks --- | Same as `verifyBiscuitWithLimits`, but with default limits (1ms timeout, max 1000 facts, max 100 iterations)-verifyBiscuit :: Biscuit -> Verifier -> PublicKey -> IO (Either VerificationError Query)-verifyBiscuit = verifyBiscuitWithLimits defaultLimits+ authority <- rawSignedBlockToParsedSignedBlock symbols rawAuthority+ blocks <- traverse (rawSignedBlockToParsedSignedBlock symbols) rawBlocks+ pure (symbols, authority :| blocks) --- | Get the components needed to compute revocation ids-getRidComponents :: (PublicKey, ExistingBlock) -> ByteString- -> ((ByteString, ByteString), Block)-getRidComponents (pub, (blockBs, block)) param =- ( ( blockBs <> serializePublicKey pub- , blockBs <> serializePublicKey pub <> param- )- , block- )+-- | Parse a biscuit without performing any signatures check. This function is intended to+-- provide tooling (eg adding a block, or inspecting a biscuit) without having to verify+-- its signatures. Running an 'Authorizer' is not possible without checking signatures.+-- 'checkBiscuitSignatures' allows a delayed signature check. For normal auth workflows,+-- please use 'parseWith' (or 'parse', or 'parseB64') instead, as they check signatures+-- before completely parsing the biscuit.+parseBiscuitUnverified :: ByteString -> Either ParseError (Biscuit OpenOrSealed Unverified)+parseBiscuitUnverified bs = do+ w@BiscuitWrapper{..} <- parseBiscuitWrapper bs+ (symbols, authority :| blocks) <- parseBlocks w+ pure $ Biscuit { rootKeyId = wRootKeyId+ , proof = wProof+ , proofCheck = Unverified+ , .. } --- | Given revocation ids components and a block, compute the revocation ids--- and attach them to the block-mkBRID :: ((ByteString, ByteString), Block) -> IO BlockWithRevocationIds-mkBRID ((g,u), bBlock) = do- genericRevocationId <- hashBytes g- uniqueRevocationId <- hashBytes u- pure BlockWithRevocationIds{..}+parseBiscuit' :: PublicKey -> BiscuitWrapper -> Either ParseError (Biscuit OpenOrSealed Verified)+parseBiscuit' pk w@BiscuitWrapper{..} = do+ let allBlocks = wAuthority :| wBlocks+ let blocksResult = verifyBlocks allBlocks pk+ let proofResult = case wProof of+ SealedProof sig -> verifySignatureProof sig (NE.last allBlocks)+ OpenProof sk -> verifySecretProof sk (NE.last allBlocks)+ when (not blocksResult || not proofResult) $ Left InvalidSignatures --- | Compute the revocation ids for a given biscuit-getRevocationIds :: Biscuit -> IO (NonEmpty BlockWithRevocationIds)-getRevocationIds Biscuit{..} = do- params <- maybe (fail "") pure . NE.nonEmpty $ parameters signature- let allBlocks = authority :| blocks- blocksAndParams = NE.zipWith getRidComponents allBlocks params- conc ((g1, u1), _) ((g2, u2), b) = ((g1 <> g2, u1 <> u2), b)- withPreviousBlocks :: NonEmpty ((ByteString, ByteString), Block)- withPreviousBlocks = NE.scanl1 conc blocksAndParams- traverse mkBRID withPreviousBlocks+ (symbols, authority :| blocks) <- parseBlocks w+ pure $ Biscuit { rootKeyId = wRootKeyId+ , proof = wProof+ , proofCheck = Verified pk+ , .. }++-- | Check the signatures (and final proof) of an already parsed biscuit. These checks normally+-- happen during the parsing phase, but can be delayed (or even ignored) in some cases. This+-- fuction allows to turn a 'Unverified' 'Biscuit' into a 'Verified' one after it has been parsed+-- with 'parseBiscuitUnverified'.+checkBiscuitSignatures :: BiscuitProof proof+ => (Maybe Int -> PublicKey)+ -> Biscuit proof Unverified+ -> Either ParseError (Biscuit proof Verified)+checkBiscuitSignatures getPublicKey b@Biscuit{..} = do+ let pk = getPublicKey rootKeyId+ toSignedBlock ((payload, _), sig, nextPk) = (payload, sig, nextPk)+ allBlocks = toSignedBlock <$> (authority :| blocks)+ blocksResult = verifyBlocks allBlocks pk+ proofResult = case toPossibleProofs proof of+ SealedProof sig -> verifySignatureProof sig (NE.last allBlocks)+ OpenProof sk -> verifySecretProof sk (NE.last allBlocks)+ when (not blocksResult || not proofResult) $ Left InvalidSignatures+ pure $ b { proofCheck = Verified pk }++-- | Biscuits can be transmitted as raw bytes, or as base64-encoded text. This datatype+-- lets the parser know about the expected encoding.+data BiscuitEncoding+ = RawBytes+ | UrlBase64++-- | Parsing a biscuit involves various steps. This data type allows configuring those steps.+data ParserConfig m+ = ParserConfig+ { encoding :: BiscuitEncoding+ -- ^ Is the biscuit base64-encoded, or is it raw binary?+ , isRevoked :: Set ByteString -> m Bool+ -- ^ Has one of the token blocks been revoked?+ -- 'fromRevocationList' lets you build this function from a static revocation list+ , getPublicKey :: Maybe Int -> PublicKey+ -- ^ How to select the public key based on the token 'rootKeyId'+ }++parseBiscuitWith :: Applicative m+ => ParserConfig m+ -> ByteString+ -> m (Either ParseError (Biscuit OpenOrSealed Verified))+parseBiscuitWith ParserConfig{..} bs =+ let input = case encoding of+ RawBytes -> Right bs+ UrlBase64 -> first (const InvalidB64Encoding) . B64.decodeBase64 $ bs+ parsedWrapper = parseBiscuitWrapper =<< input+ wrapperToBiscuit w@BiscuitWrapper{wRootKeyId} =+ let pk = getPublicKey wRootKeyId+ in (parseBiscuit' pk =<<) <$> checkRevocation isRevoked w+ in join <$> traverse wrapperToBiscuit parsedWrapper++rawSignedBlockToParsedSignedBlock :: Symbols+ -> ((ByteString, PB.Block), Signature, PublicKey)+ -> Either ParseError ParsedSignedBlock+rawSignedBlockToParsedSignedBlock s ((payload, pbBlock), sig, pk) = do+ block <- first (InvalidProtobuf False) $ pbToBlock s pbBlock+ pure ((payload, block), sig, pk)++-- | Extract the list of revocation ids from a biscuit.+-- To reject revoked biscuits, please use 'parseWith' instead. This function+-- should only be used for debugging purposes.+getRevocationIds :: Biscuit proof check -> NonEmpty ByteString+getRevocationIds Biscuit{authority, blocks} =+ let allBlocks = authority :| blocks+ getRevocationId (_, sig, _) = convert sig+ in getRevocationId <$> allBlocks++-- | Generic version of 'authorizeBiscuitWithLimits' which takes custom 'Limits'.+authorizeBiscuitWithLimits :: Limits -> Biscuit a Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)+authorizeBiscuitWithLimits l Biscuit{..} authorizer =+ let toBlockWithRevocationId ((_, block), sig, _) = (block, convert sig)+ in runAuthorizerWithLimits l+ (toBlockWithRevocationId authority)+ (toBlockWithRevocationId <$> blocks)+ authorizer++-- | Given a biscuit with a verified signature and an authorizer (a set of facts, rules, checks+-- and policies), verify a biscuit:+--+-- - all the checks declared in the biscuit and authorizer must pass+-- - an allow policy provided by the authorizer has to match (policies are tried in order)+-- - the datalog computation must happen in an alloted time, with a capped number of generated+-- facts and a capped number of iterations+--+-- checks and policies declared in the authorizer only operate on the authority block. Facts+-- declared by extra blocks cannot interfere with previous blocks.+--+-- Specific runtime limits can be specified by using 'authorizeBiscuitWithLimits'. 'authorizeBiscuit'+-- uses a set of defaults defined in 'defaultLimits'.+authorizeBiscuit :: Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError AuthorizationSuccess)+authorizeBiscuit = authorizeBiscuitWithLimits defaultLimits++-- | Retrieve the `PublicKey` which was used to verify the `Biscuit` signatures+getVerifiedBiscuitPublicKey :: Biscuit a Verified -> PublicKey+getVerifiedBiscuitPublicKey Biscuit{proofCheck} =+ let Verified pk = proofCheck+ in pk
test/Spec.hs view
@@ -2,23 +2,28 @@ import Test.Tasty -import qualified Spec.Crypto as Crypto-import qualified Spec.Executor as Executor-import qualified Spec.Parser as Parser-import qualified Spec.Quasiquoter as Quasiquoter-import qualified Spec.RevocationIds as RevocationIds-import qualified Spec.Roundtrip as Roundtrip-import qualified Spec.Samples as Samples-import qualified Spec.Verification as Verification+import qualified Spec.Executor as Executor+import qualified Spec.NewCrypto as NewCrypto+import qualified Spec.Parser as Parser+import qualified Spec.Quasiquoter as Quasiquoter+import qualified Spec.RevocationIds as RevocationIds+import qualified Spec.Roundtrip as Roundtrip+import qualified Spec.SampleReader as SampleReader+import qualified Spec.ScopedExecutor as ScopedExecutor+import qualified Spec.Verification as Verification main :: IO ()-main = defaultMain $ testGroup "biscuit-haskell"- [ Crypto.specs- , Executor.specs- , Parser.specs- , Quasiquoter.specs- , RevocationIds.specs- , Roundtrip.specs- , Samples.specs- , Verification.specs- ]+main = do+ sampleReader <- SampleReader.getSpecs+ defaultMain $ testGroup "biscuit-haskell"+ [+ NewCrypto.specs+ , Executor.specs+ , Parser.specs+ , Quasiquoter.specs+ , RevocationIds.specs+ , Roundtrip.specs+ , Verification.specs+ , ScopedExecutor.specs+ , sampleReader+ ]
− test/Spec/Crypto.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{- HLINT ignore "Reduce duplication" -}-module Spec.Crypto (specs) where--import Data.ByteString (ByteString)-import Data.List.NonEmpty (NonEmpty ((:|)))-import Test.Tasty-import Test.Tasty.HUnit--import Auth.Biscuit-import qualified Auth.Biscuit.Sel as Sel-import Auth.Biscuit.Token (Biscuit (..))--specs :: TestTree-specs = testGroup "biscuit crypto"- [ testGroup "signature algorithm"- [ singleBlockRoundtrip- , multiBlockRoundtrip- , tamperedAuthority- , tamperedBlock- ]- , testGroup "high-level functions"- [ singleBlockRoundtrip'- , multiBlockRoundtrip'- , tamperedAuthority'- , tamperedBlock'- ]- ]--singleBlockRoundtrip :: TestTree-singleBlockRoundtrip = testCase "Single block roundtrip" $ do- rootKp <- newKeypair- let pub = publicKey rootKp- content = "content"- token = (pub, content) :| []- sig <- Sel.signBlock rootKp content- result <- Sel.verifySignature token sig- result @?= True--multiBlockRoundtrip :: TestTree-multiBlockRoundtrip = testCase "Multi block roundtrip" $ do- kp' <- newKeypair- kp <- newKeypair- let pub = publicKey kp- pub' = publicKey kp'- content = "content"- content' = "block"- token = (pub, content) :| [(pub', content')]- sig <- Sel.signBlock kp content- sig' <- Sel.aggregate sig =<< Sel.signBlock kp' content'- result <- Sel.verifySignature token sig'- result @?= True--tamperedAuthority :: TestTree-tamperedAuthority = testCase "Tampered authority" $ do- kp' <- newKeypair- kp <- newKeypair- let pub = publicKey kp- pub' = publicKey kp'- content = "content"- content' = "block"- token = (pub, "modified") :| []- token' = (pub, "modified") :| [(pub', content')]- sig <- Sel.signBlock kp content- sig' <- Sel.aggregate sig =<< Sel.signBlock kp' content'- result <- Sel.verifySignature token sig'- result @?= False- result' <- Sel.verifySignature token' sig'- result' @?= False--tamperedBlock :: TestTree-tamperedBlock = testCase "Tampered block" $ do- kp' <- newKeypair- kp <- newKeypair- let pub = publicKey kp- pub' = publicKey kp'- content = "content"- content' = "block"- token = (pub, content) :| [(pub', "modified")]- sig <- Sel.signBlock kp content- sig' <- Sel.aggregate sig =<< Sel.signBlock kp' content'- result <- Sel.verifySignature token sig'- result @?= False--singleBlockRoundtrip' :: TestTree-singleBlockRoundtrip' = testCase "Single block roundtrip" $ do- rootKp <- newKeypair- let pub = publicKey rootKp- b <- mkBiscuit rootKp [block|right(#authority,#read);|]- result <- checkBiscuitSignature b pub- result @?= True--multiBlockRoundtrip' :: TestTree-multiBlockRoundtrip' = testCase "Multi block roundtrip" $ do- kp <- newKeypair- let pub = publicKey kp- b <- mkBiscuit kp [block|right(#authority,#read);|]- b' <- addBlock [block|check if true;|] b- result <- checkBiscuitSignature b' pub- result @?= True--tamper :: (PublicKey, (ByteString, Block))- -> (PublicKey, (ByteString, Block))-tamper (pk, (_, b)) = (pk, ("tampered", b))--tamperedAuthority' :: TestTree-tamperedAuthority' = testCase "Tampered authority" $ do- kp <- newKeypair- let pub = publicKey kp- b <- mkBiscuit kp [block|right(#authority,#read);|]- b' <- addBlock [block|check if true;|] b- let modified = b'- { authority = tamper $ authority b- }- result <- checkBiscuitSignature modified pub- result @?= False--tamperedBlock' :: TestTree-tamperedBlock' = testCase "Tampered block" $ do- kp <- newKeypair- let pub = publicKey kp- b <- mkBiscuit kp [block|right(#authority,#read);|]- b' <- addBlock [block|check if true;|] b- let modified = b'- { blocks = tamper <$> blocks b- }- result <- checkBiscuitSignature modified pub- result @?= False
test/Spec/Executor.hs view
@@ -2,16 +2,21 @@ {-# LANGUAGE QuasiQuotes #-} module Spec.Executor (specs) where -import Data.Attoparsec.Text (parseOnly)-import Data.Map.Strict as Map-import Data.Set as Set-import Data.Text (Text, unpack)+import Data.Attoparsec.Text (parseOnly)+import Data.Map.Strict as Map+import Data.Set as Set+import Data.Text (Text, unpack) import Test.Tasty import Test.Tasty.HUnit import Auth.Biscuit.Datalog.AST-import Auth.Biscuit.Datalog.Executor-import Auth.Biscuit.Datalog.Parser (expressionParser, fact, rule)+import Auth.Biscuit.Datalog.Executor (ExecutionError (..),+ Limits (..),+ defaultLimits,+ evaluateExpression)+import Auth.Biscuit.Datalog.Parser (expressionParser, fact,+ rule)+import Auth.Biscuit.Datalog.ScopedExecutor hiding (limits) specs :: TestTree specs = testGroup "Datalog evaluation"@@ -29,14 +34,13 @@ { rules = Set.fromList [ [rule|grandparent($a,$b) <- parent($a,$c), parent($c,$b)|] ]- , blockRules = mempty , facts = Set.fromList [ [fact|parent("alice", "bob")|] , [fact|parent("bob", "jean-pierre")|] , [fact|parent("alice", "toto")|] ] }- in computeAllFacts defaultLimits world @?= Right (Set.fromList+ in runFactGeneration defaultLimits world @?= Right (Set.fromList [ [fact|parent("alice", "bob")|] , [fact|parent("bob", "jean-pierre")|] , [fact|parent("alice", "toto")|]@@ -66,9 +70,7 @@ , ("\"test\".length()", LInteger 4) , ("hex:ababab.length()", LInteger 3) , ("[].length()", LInteger 0)- , ("[#test, #test].length()", LInteger 1)- , ("#toto == #toto", LBool True)- , ("#toto == #truc", LBool False)+ , ("[\"test\", \"test\"].length()", LInteger 1) , ("1 == 1", LBool True) , ("2 == 1", LBool False) , ("\"toto\" == \"toto\"", LBool True)@@ -119,14 +121,14 @@ , ("true || false", LBool True) , ("false || true", LBool True) , ("false || false", LBool False)- , ("[#test].contains([#test])", LBool True)- , ("[#test].contains(#test)", LBool True)- , ("[].contains(#test)", LBool False)- , ("[\"test\"].contains(#test)", LBool False)- , ("[#test].intersection([#test])", TermSet (Set.fromList [Symbol "test"]))- , ("[#test].intersection([\"test\"])", TermSet (Set.fromList []))- , ("[#test].union([#test])", TermSet (Set.fromList [Symbol "test"]))- , ("[#test].union([\"test\"])", TermSet (Set.fromList [Symbol "test", 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"])) ] exprEvalError :: TestTree@@ -147,20 +149,19 @@ rulesWithConstraints = testCase "Rule with constraints" $ let world = World { rules = Set.fromList- [ [rule|valid_date("file1") <- time(#ambient, $0), resource(#ambient, "file1"), $0 <= 2019-12-04T09:46:41+00:00|]- , [rule|valid_date("file2") <- time(#ambient, $0), resource(#ambient, "file2"), $0 <= 2010-12-04T09:46:41+00:00|]+ [ [rule|valid_date("file1") <- time($0), resource("file1"), $0 <= 2019-12-04T09:46:41+00:00|]+ , [rule|valid_date("file2") <- time($0), resource("file2"), $0 <= 2010-12-04T09:46:41+00:00|] ]- , blockRules = mempty , facts = Set.fromList- [ [fact|time(#ambient, 2019-12-04T01:00:00Z)|]- , [fact|resource(#ambient, "file1")|]- , [fact|resource(#ambient, "file2")|]+ [ [fact|time(2019-12-04T01:00:00Z)|]+ , [fact|resource("file1")|]+ , [fact|resource("file2")|] ] }- in computeAllFacts defaultLimits world @?= Right (Set.fromList- [ [fact|time(#ambient, 2019-12-04T01:00:00Z)|]- , [fact|resource(#ambient, "file1")|]- , [fact|resource(#ambient, "file2")|]+ in runFactGeneration defaultLimits world @?= Right (Set.fromList+ [ [fact|time(2019-12-04T01:00:00Z)|]+ , [fact|resource("file1")|]+ , [fact|resource("file2")|] , [fact|valid_date("file1")|] ]) @@ -168,15 +169,14 @@ ruleHeadWithNoVars = testCase "Rule head with no variables" $ let world = World { rules = Set.fromList- [ [rule|operation(#authority,#read) <- test($yolo, #nothing)|]+ [ [rule|operation("authority", "read") <- test($yolo, "nothing")|] ]- , blockRules = mempty , facts = Set.fromList- [ [fact|test(#whatever, #notNothing)|]+ [ [fact|test("whatever", "notNothing")|] ] }- in computeAllFacts defaultLimits world @?= Right (Set.fromList- [ [fact|test(#whatever, #notNothing)|]+ in runFactGeneration defaultLimits world @?= Right (Set.fromList+ [ [fact|test("whatever", "notNothing")|] ]) limits :: TestTree@@ -186,7 +186,6 @@ [ [rule|ancestor($a,$b) <- parent($a,$c), ancestor($c,$b)|] , [rule|ancestor($a,$b) <- parent($a,$b)|] ]- , blockRules = mempty , facts = Set.fromList [ [fact|parent("alice", "bob")|] , [fact|parent("bob", "jean-pierre")|]@@ -198,7 +197,7 @@ iterLimits = defaultLimits { maxIterations = 2 } in testGroup "Facts generation limits" [ testCase "max facts" $- computeAllFacts factLimits world @?= Left TooManyFacts+ runFactGeneration factLimits world @?= Left Facts , testCase "max iterations" $- computeAllFacts iterLimits world @?= Left TooManyIterations+ runFactGeneration iterLimits world @?= Left Iterations ]
+ test/Spec/NewCrypto.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{- HLINT ignore "Reduce duplication" -}+module Spec.NewCrypto (specs) where++import Data.ByteString (ByteString)+import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE+import Data.Maybe (isJust)+import Test.Tasty+import Test.Tasty.HUnit++import Auth.Biscuit.Crypto+import Crypto.PubKey.Ed25519++-- This test module is only there to test the crypto layer of biscuits,+-- so we define a custom token type that only cares about the envelope,+-- not the actual payload+data Token = Token+ { payload :: Blocks+ , privKey :: SecretKey+ }++data SealedToken = SealedToken+ { payload :: Blocks+ , sig :: Signature+ }++signToken :: ByteString -> SecretKey -> IO Token+signToken p sk = do+ (signedBlock, privKey) <- signBlock sk p+ pure Token+ { payload = pure signedBlock+ , privKey+ }++snocNE :: NonEmpty a -> a -> NonEmpty a+snocNE (h :| t) e = h :| (t <> [e])++append :: Token -> ByteString -> IO Token+append t@Token{payload} p = do+ (signedBlock, privKey) <- signBlock (privKey t) p+ pure Token+ { payload = snocNE payload signedBlock+ , privKey+ }++seal :: Token -> SealedToken+seal Token{payload,privKey} =+ let lastBlock = NE.last payload+ in SealedToken+ { sig = getSignatureProof lastBlock privKey+ , payload+ }++verifyToken :: Token+ -> PublicKey+ -> Bool+verifyToken Token{payload, privKey} rootPk =+ let blocks = payload+ sigChecks = verifyBlocks blocks rootPk+ lastCheck = verifySecretProof privKey (NE.last payload)+ in sigChecks && lastCheck++verifySealedToken :: SealedToken+ -> PublicKey+ -> Bool+verifySealedToken SealedToken{payload, sig} rootPk =+ let blocks = payload+ sigChecks = verifyBlocks blocks rootPk+ lastCheck = verifySignatureProof sig (NE.last payload)+ in sigChecks && lastCheck++specs :: TestTree+specs = testGroup "new biscuit crypto"+ [ testGroup "signature algorithm - normal"+ [ singleBlockRoundtrip+ , multiBlockRoundtrip+ , tamperedAuthority+ , tamperedBlock+ , removedBlock+ ]+ , testGroup "signature algorithm - sealed"+ [ singleBlockRoundtripSealed+ , multiBlockRoundtripSealed+ , tamperedAuthoritySealed+ , tamperedBlockSealed+ , removedBlockSealed+ ]+ ]++singleBlockRoundtrip :: TestTree+singleBlockRoundtrip = testCase "Single block roundtrip" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ let res = verifyToken token pk+ res @?= True++multiBlockRoundtrip :: TestTree+multiBlockRoundtrip = testCase "Multi block roundtrip" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- append token "block1"+ let res = verifyToken attenuated pk+ res @?= True++alterPayload :: (Blocks -> Blocks)+ -> Token+ -> Token+alterPayload f Token{..} = Token { payload = f payload, ..}++tamperedAuthority :: TestTree+tamperedAuthority = testCase "Tampered authority" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- append token "block1"+ let tamper ((_, s, pk) :| o) = ("tampered", s, pk) :| o+ tampered = alterPayload tamper attenuated+ let res = verifyToken tampered pk+ res @?= False++tamperedBlock :: TestTree+tamperedBlock = testCase "Tampered block" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- append token "block1"+ let tamper (h :| ((_, s, pk): t)) = h :| (("tampered", s, pk) : t)+ tampered = alterPayload tamper attenuated+ let res = verifyToken tampered pk+ res @?= False++removedBlock :: TestTree+removedBlock = testCase "Removed block" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- append token "block1"+ let tamper (h :| _) = h :| []+ tampered = alterPayload tamper attenuated+ let res = verifyToken tampered pk+ res @?= False++singleBlockRoundtripSealed :: TestTree+singleBlockRoundtripSealed = testCase "Single block roundtrip" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- seal <$> signToken content sk+ let res = verifySealedToken token pk+ res @?= True++multiBlockRoundtripSealed :: TestTree+multiBlockRoundtripSealed = testCase "Multi block roundtrip" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- seal <$> append token "block1"+ let res = verifySealedToken attenuated pk+ res @?= True++alterPayloadSealed :: (Blocks -> Blocks)+ -> SealedToken+ -> SealedToken+alterPayloadSealed f SealedToken{..} = SealedToken { payload = f payload, ..}++tamperedAuthoritySealed :: TestTree+tamperedAuthoritySealed = testCase "Tampered authority" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- seal <$> append token "block1"+ let tamper ((_, s, pk) :| o) = ("tampered", s, pk) :| o+ tampered = alterPayloadSealed tamper attenuated+ let res = verifySealedToken tampered pk+ res @?= False++tamperedBlockSealed :: TestTree+tamperedBlockSealed = testCase "Tampered block" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- seal <$> append token "block1"+ let tamper (h :| ((_, s, pk): t)) = h :| (("tampered", s, pk) : t)+ tampered = alterPayloadSealed tamper attenuated+ let res = verifySealedToken tampered pk+ res @?= False++removedBlockSealed :: TestTree+removedBlockSealed = testCase "Removed block" $ do+ sk <- generateSecretKey+ let pk = toPublic sk+ content = "content"+ token <- signToken content sk+ attenuated <- seal <$> append token "block1"+ let tamper (h :| _) = h :| []+ tampered = alterPayloadSealed tamper attenuated+ let res = verifySealedToken tampered pk+ res @?= False
test/Spec/Parser.hs view
@@ -13,12 +13,12 @@ import Auth.Biscuit.Datalog.Parser (checkParser, expressionParser, policyParser, predicateParser, ruleParser, termParser,- verifierParser)+ authorizerParser) -parseTerm :: Text -> Either String ID+parseTerm :: Text -> Either String Term parseTerm = parseOnly termParser -parseTermQQ :: Text -> Either String QQID+parseTermQQ :: Text -> Either String QQTerm parseTermQQ = parseOnly termParser parsePredicate :: Text -> Either String Predicate@@ -33,8 +33,8 @@ parseCheck :: Text -> Either String Check parseCheck = parseOnly checkParser -parseVerifier :: Text -> Either String Verifier-parseVerifier = parseOnly verifierParser+parseAuthorizer :: Text -> Either String Authorizer+parseAuthorizer = parseOnly authorizerParser parsePolicy :: Text -> Either String Policy parsePolicy = parseOnly policyParser@@ -43,6 +43,7 @@ specs = testGroup "datalog parser" [ factWithDate , simpleFact+ , oneLetterFact , simpleRule , multilineRule , termsGroup@@ -52,13 +53,12 @@ , constrainedRuleOrdering , checkParsing , policyParsing- , verifierParsing+ , authorizerParsing ] termsGroup :: TestTree termsGroup = testGroup "Parse terms"- [ testCase "Symbol" $ parseTerm "#ambient" @?= Right (Symbol "ambient")- , testCase "String" $ parseTerm "\"file1 a hello - 123_\"" @?= Right (LString "file1 a hello - 123_")+ [ testCase "String" $ parseTerm "\"file1 a hello - 123_\"" @?= Right (LString "file1 a hello - 123_") , testCase "Positive integer" $ parseTerm "123" @?= Right (LInteger 123) , testCase "Negative integer" $ parseTerm "-42" @?= Right (LInteger (-42)) , testCase "Date" $ parseTerm "2019-12-02T13:49:53Z" @?=@@ -69,8 +69,7 @@ termsGroupQQ :: TestTree termsGroupQQ = testGroup "Parse terms (in a QQ setting)"- [ testCase "Symbol" $ parseTermQQ "#ambient" @?= Right (Symbol "ambient")- , testCase "String" $ parseTermQQ "\"file1 a hello - 123_\"" @?= Right (LString "file1 a hello - 123_")+ [ testCase "String" $ parseTermQQ "\"file1 a hello - 123_\"" @?= Right (LString "file1 a hello - 123_") , testCase "Positive integer" $ parseTermQQ "123" @?= Right (LInteger 123) , testCase "Negative integer" $ parseTermQQ "-42" @?= Right (LInteger (-42)) , testCase "Date" $ parseTermQQ "2019-12-02T13:49:53Z" @?=@@ -81,36 +80,41 @@ simpleFact :: TestTree simpleFact = testCase "Parse simple fact" $- parsePredicate "right(#authority, \"file1\", #read)" @?=- Right (Predicate "right" [Symbol "authority", LString "file1", Symbol "read"])+ parsePredicate "right(\"file1\", \"read\")" @?=+ Right (Predicate "right" [LString "file1", LString "read"]) +oneLetterFact :: TestTree+oneLetterFact = testCase "Parse one-letter fact" $+ parsePredicate "a(12)" @?=+ Right (Predicate "a" [LInteger 12])+ factWithDate :: TestTree factWithDate = testCase "Parse fact containing a date" $- parsePredicate "date(#ambient,2019-12-02T13:49:53Z)" @?=- Right (Predicate "date" [Symbol "ambient", LDate $ read "2019-12-02 13:49:53 UTC"])+ parsePredicate "date(2019-12-02T13:49:53Z)" @?=+ Right (Predicate "date" [LDate $ read "2019-12-02 13:49:53 UTC"]) simpleRule :: TestTree simpleRule = testCase "Parse simple rule" $- parseRule "right(#authority, $0, #read) <- resource( #ambient, $0), operation(#ambient, #read)" @?=- Right (Rule (Predicate "right" [Symbol "authority", Variable "0", Symbol "read"])- [ Predicate "resource" [Symbol "ambient", Variable "0"]- , Predicate "operation" [Symbol "ambient", Symbol "read"]+ parseRule "right($0, \"read\") <- resource( $0), operation(\"read\")" @?=+ Right (Rule (Predicate "right" [Variable "0", LString "read"])+ [ Predicate "resource" [Variable "0"]+ , Predicate "operation" [LString "read"] ] []) multilineRule :: TestTree multilineRule = testCase "Parse multiline rule" $- parseRule "right(#authority, $0, #read) <-\n resource( #ambient, $0),\n operation(#ambient, #read)" @?=- Right (Rule (Predicate "right" [Symbol "authority", Variable "0", Symbol "read"])- [ Predicate "resource" [Symbol "ambient", Variable "0"]- , Predicate "operation" [Symbol "ambient", Symbol "read"]+ parseRule "right($0, \"read\") <-\n resource( $0),\n operation(\"read\")" @?=+ Right (Rule (Predicate "right" [Variable "0", LString "read"])+ [ Predicate "resource" [Variable "0"]+ , Predicate "operation" [LString "read"] ] []) constrainedRule :: TestTree constrainedRule = testCase "Parse constained rule" $- parseRule "valid_date(\"file1\") <- time(#ambient, $0), resource(#ambient, \"file1\"), $0 <= 2019-12-04T09:46:41+00:00" @?=+ parseRule "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2019-12-04T09:46:41+00:00" @?= Right (Rule (Predicate "valid_date" [LString "file1"])- [ Predicate "time" [Symbol "ambient", Variable "0"]- , Predicate "resource" [Symbol "ambient", LString "file1"]+ [ Predicate "time" [Variable "0"]+ , Predicate "resource" [LString "file1"] ] [ EBinary LessOrEqual (EValue $ Variable "0")@@ -119,10 +123,10 @@ constrainedRuleOrdering :: TestTree constrainedRuleOrdering = testCase "Parse constained rule (interleaved)" $- parseRule "valid_date(\"file1\") <- time(#ambient, $0), $0 <= 2019-12-04T09:46:41+00:00, resource(#ambient, \"file1\")" @?=+ parseRule "valid_date(\"file1\") <- time($0), $0 <= 2019-12-04T09:46:41+00:00, resource(\"file1\")" @?= Right (Rule (Predicate "valid_date" [LString "file1"])- [ Predicate "time" [Symbol "ambient", Variable "0"]- , Predicate "resource" [Symbol "ambient", LString "file1"]+ [ Predicate "time" [Variable "0"]+ , Predicate "resource" [LString "file1"] ] [ EBinary LessOrEqual (EValue $ Variable "0")@@ -223,19 +227,6 @@ (EValue (TermSet $ Set.fromList [LString "abc", LString "def"])) (EValue (Variable "0")) ))- , testCase "symbol set operation" $- parseExpression "[#abc, #def].contains($0)" @?=- Right (EBinary Contains- (EValue (TermSet $ Set.fromList [Symbol "abc", Symbol "def"]))- (EValue (Variable "0"))- )- , testCase "negated symbol set operation" $- parseExpression "![#abc, #def].contains($0)" @?=- Right (EUnary Negate- (EBinary Contains- (EValue (TermSet $ Set.fromList [Symbol "abc", Symbol "def"]))- (EValue (Variable "0"))- )) , operatorPrecedences ] @@ -294,12 +285,12 @@ Right [QueryItem [] [EValue $ LBool True]] , testCase "Multiple groups" $ parseCheck- "check if fact(#ambient, $var), $var == true or \- \other(#authority, $var), $var == 2" @?=+ "check if fact($var), $var == true or \+ \other($var), $var == 2" @?= Right- [ QueryItem [Predicate "fact" [Symbol "ambient", Variable "var"]]+ [ QueryItem [Predicate "fact" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]- , QueryItem [Predicate "other" [Symbol "authority", Variable "var"]]+ , QueryItem [Predicate "other" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] ] ]@@ -314,124 +305,122 @@ Right (Deny, [QueryItem [] [EValue $ LBool True]]) , testCase "Allow with multiple groups" $ parsePolicy- "allow if fact(#ambient, $var), $var == true or \- \other(#authority, $var), $var == 2" @?=+ "allow if fact($var), $var == true or \+ \other($var), $var == 2" @?= Right ( Allow- , [ QueryItem [Predicate "fact" [Symbol "ambient", Variable "var"]]+ , [ QueryItem [Predicate "fact" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]- , QueryItem [Predicate "other" [Symbol "authority", Variable "var"]]+ , QueryItem [Predicate "other" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] ] ) , testCase "Deny with multiple groups" $ parsePolicy- "deny if fact(#ambient, $var), $var == true or \- \other(#authority, $var), $var == 2" @?=+ "deny if fact($var), $var == true or \+ \other($var), $var == 2" @?= Right ( Deny- , [ QueryItem [Predicate "fact" [Symbol "ambient", Variable "var"]]+ , [ QueryItem [Predicate "fact" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]- , QueryItem [Predicate "other" [Symbol "authority", Variable "var"]]+ , QueryItem [Predicate "other" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] ] ) , testCase "Deny with multiple groups, multiline" $ parsePolicy "deny if\n\- \fact(#ambient, $var), $var == true or\n\- \other(#authority, $var), $var == 2" @?=+ \fact($var), $var == true or\n\+ \other($var), $var == 2" @?= Right ( Deny- , [ QueryItem [Predicate "fact" [Symbol "ambient", Variable "var"]]+ , [ QueryItem [Predicate "fact" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LBool True))]- , QueryItem [Predicate "other" [Symbol "authority", Variable "var"]]+ , QueryItem [Predicate "other" [Variable "var"]] [EBinary Equal (EValue (Variable "var")) (EValue (LInteger 2))] ] ) ] -verifierParsing :: TestTree-verifierParsing = testGroup "Simple verifiers"+authorizerParsing :: TestTree+authorizerParsing = testGroup "Simple authorizers" [ testCase "Just a deny" $- parseVerifier "deny if true;" @?=- Right (Verifier [(Deny, [QueryItem [] [EValue (LBool True)]])] mempty+ parseAuthorizer "deny if true;" @?=+ Right (Authorizer [(Deny, [QueryItem [] [EValue (LBool True)]])] mempty ) , testCase "Allow and deny" $- parseVerifier "allow if operation(#ambient, #read);\n deny if true;" @?=- Right (Verifier- [ (Allow, [QueryItem [Predicate "operation" [Symbol "ambient", Symbol "read"]] []])+ parseAuthorizer "allow if operation(\"read\");\n deny if true;" @?=+ Right (Authorizer+ [ (Allow, [QueryItem [Predicate "operation" [LString "read"]] []]) , (Deny, [QueryItem [] [EValue (LBool True)]]) ] mempty )- , testCase "Complete verifier" $ do+ , testCase "Complete authorizer" $ do let spec :: Text spec = "// the owner has all rights\n\- \right(#authority, $blog_id, $article_id, $operation) <-\n\- \ article(#ambient, $blog_id, $article_id),\n\- \ operation(#ambient, $operation),\n\- \ user(#authority, $user_id),\n\- \ owner(#authority, $user_id, $blog_id);\n\+ \right($blog_id, $article_id, $operation) <-\n\+ \ article($blog_id, $article_id),\n\+ \ operation($operation),\n\+ \ user($user_id),\n\+ \ owner($user_id, $blog_id);\n\ \// premium users can access some restricted articles\n\- \right(#authority, $blog_id, $article_id, #read) <-\n\- \ article(#ambient, $blog_id, $article_id),\n\- \ premium_readable(#authority, $blog_id, $article_id),\n\- \ user(#authority, $user_id),\n\- \ premium_user(#authority, $user_id, $blog_id);\n\+ \right($blog_id, $article_id, \"read\") <-\n\+ \ article($blog_id, $article_id),\n\+ \ premium_readable($blog_id, $article_id),\n\+ \ user($user_id),\n\+ \ premium_user($user_id, $blog_id);\n\ \// define teams and roles\n\- \right(#authority, $blog_id, $article_id, $operation) <-\n\- \ article(#ambient, $blog_id, $article_id),\n\- \ operation(#ambient, $operation),\n\- \ user(#authority, $user_id),\n\- \ member(#authority, $user_id, $team_id),\n\- \ team_role(#authority, $team_id, $blog_id, #contributor),\n\- \ [#read, #write].contains($operation);\n\+ \right($blog_id, $article_id, $operation) <-\n\+ \ article($blog_id, $article_id),\n\+ \ operation($operation),\n\+ \ user($user_id),\n\+ \ member($user_id, $team_id),\n\+ \ team_role($team_id, $blog_id, \"contributor\"),\n\+ \ [\"read\", \"write\"].contains($operation);\n\ \// unauthenticated users have read access on published articles\n\ \allow if\n\- \ operation(#ambient, #read),\n\- \ article(#ambient, $blog_id, $article_id),\n\- \ readable(#authority, $blog_id, $article_id);\n\+ \ operation(\"read\"),\n\+ \ article($blog_id, $article_id),\n\+ \ readable($blog_id, $article_id);\n\ \// authorize if got the rights on this blog and article\n\ \allow if\n\- \ blog(#ambient, $blog_id),\n\- \ article(#ambient, $blog_id, $article_id),\n\- \ operation(#ambient, $operation),\n\- \ right(#authority, $blog_id, $article_id, $operation);\n\+ \ blog($blog_id),\n\+ \ article($blog_id, $article_id),\n\+ \ operation($operation),\n\+ \ right($blog_id, $article_id, $operation);\n\ \// catch all rule in case the allow did not match\n\ \deny if true;\ \ " p = Predicate- sAuth = Symbol "authority"- sAmb = Symbol "ambient"- sRead = Symbol "read"- sWrite = Symbol "write"- sContributor = Symbol "contributor"+ sRead = LString "read"+ sWrite = LString "write"+ sContributor = LString "contributor" vBlogId = Variable "blog_id" vArticleId = Variable "article_id" vUserId = Variable "user_id" vTeamId = Variable "team_id" vOp = Variable "operation" bRules =- [ Rule (p "right" [sAuth, vBlogId, vArticleId, vOp])- [ p "article" [sAmb, vBlogId, vArticleId]- , p "operation" [sAmb, vOp]- , p "user" [sAuth, vUserId]- , p "owner" [sAuth, vUserId, vBlogId]+ [ Rule (p "right" [vBlogId, vArticleId, vOp])+ [ p "article" [vBlogId, vArticleId]+ , p "operation" [vOp]+ , p "user" [vUserId]+ , p "owner" [vUserId, vBlogId] ] []- , Rule (p "right" [sAuth, vBlogId, vArticleId, sRead])- [ p "article" [sAmb, vBlogId, vArticleId]- , p "premium_readable" [sAuth, vBlogId, vArticleId]- , p "user" [sAuth, vUserId]- , p "premium_user" [sAuth, vUserId, vBlogId]+ , Rule (p "right" [vBlogId, vArticleId, sRead])+ [ p "article" [vBlogId, vArticleId]+ , p "premium_readable" [vBlogId, vArticleId]+ , p "user" [vUserId]+ , p "premium_user" [vUserId, vBlogId] ] []- , Rule (p "right" [sAuth, vBlogId, vArticleId, vOp])- [ p "article" [sAmb, vBlogId, vArticleId]- , p "operation" [sAmb, vOp]- , p "user" [sAuth, vUserId]- , p "member" [sAuth, vUserId, vTeamId]- , p "team_role" [sAuth, vTeamId, vBlogId, sContributor]+ , Rule (p "right" [vBlogId, vArticleId, vOp])+ [ p "article" [vBlogId, vArticleId]+ , p "operation" [vOp]+ , p "user" [vUserId]+ , p "member" [vUserId, vTeamId]+ , p "team_role" [vTeamId, vBlogId, sContributor] ] [EBinary Contains (EValue (TermSet $ Set.fromList [sRead, sWrite])) (EValue vOp)] ]@@ -439,16 +428,16 @@ bChecks = [] bContext = Nothing vPolicies =- [ (Allow, [QueryItem [ p "operation" [sAmb, sRead]- , p "article" [sAmb, vBlogId, vArticleId]- , p "readable" [sAuth, vBlogId, vArticleId]+ [ (Allow, [QueryItem [ p "operation" [sRead]+ , p "article" [vBlogId, vArticleId]+ , p "readable" [vBlogId, vArticleId] ] []])- , (Allow, [QueryItem [ p "blog" [sAmb, vBlogId]- , p "article" [sAmb, vBlogId, vArticleId]- , p "operation" [sAmb, vOp]- , p "right" [sAuth, vBlogId, vArticleId, vOp]+ , (Allow, [QueryItem [ p "blog" [vBlogId]+ , p "article" [vBlogId, vArticleId]+ , p "operation" [vOp]+ , p "right" [vBlogId, vArticleId, vOp] ] []]) , (Deny, [QueryItem [] [EValue (LBool True)]]) ]- parseVerifier spec @?= Right Verifier{vBlock = Block{..}, ..}+ parseAuthorizer spec @?= Right Authorizer{vBlock = Block{..}, ..} ]
test/Spec/Quasiquoter.hs view
@@ -20,21 +20,20 @@ basicFact :: TestTree basicFact = testCase "Basic fact" $ let actual :: Fact- actual = [fact|right(#authority, "file1", #read)|]+ actual = [fact|right("file1", "read")|] in actual @?=- Predicate "right" [ Symbol "authority"- , LString "file1"- , Symbol "read"+ Predicate "right" [ LString "file1"+ , LString "read" ] basicRule :: TestTree basicRule = testCase "Basic rule" $ let actual :: Rule- actual = [rule|right(#authority, $0, #read) <- resource( #ambient, $0), operation(#ambient, #read)|]+ actual = [rule|right($0, "read") <- resource( $0), operation("read")|] in actual @?=- Rule (Predicate "right" [Symbol "authority", Variable "0", Symbol "read"])- [ Predicate "resource" [Symbol "ambient", Variable "0"]- , Predicate "operation" [Symbol "ambient", Symbol "read"]+ Rule (Predicate "right" [Variable "0", LString "read"])+ [ Predicate "resource" [Variable "0"]+ , Predicate "operation" [LString "read"] ] [] antiquotedFact :: TestTree@@ -42,11 +41,10 @@ let toto :: Text toto = "test" actual :: Fact- actual = [fact|right(#authority, ${toto}, #read)|]+ actual = [fact|right(${toto}, "read")|] in actual @?=- Predicate "right" [ Symbol "authority"- , LString "test"- , Symbol "read"+ Predicate "right" [ LString "test"+ , LString "read" ] antiquotedRule :: TestTree@@ -54,9 +52,9 @@ let toto :: Text toto = "test" actual :: Rule- actual = [rule|right(#authority, $0, #read) <- resource( #ambient, $0), operation(#ambient, #read, ${toto})|]+ actual = [rule|right($0, "read") <- resource( $0), operation("read", ${toto})|] in actual @?=- Rule (Predicate "right" [Symbol "authority", Variable "0", Symbol "read"])- [ Predicate "resource" [Symbol "ambient", Variable "0"]- , Predicate "operation" [Symbol "ambient", Symbol "read", LString "test"]+ Rule (Predicate "right" [Variable "0", LString "read"])+ [ Predicate "resource" [Variable "0"]+ , Predicate "operation" [LString "read", LString "test"] ] []
test/Spec/RevocationIds.hs view
@@ -1,5 +1,5 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} module Spec.RevocationIds ( specs ) where@@ -7,48 +7,71 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString import qualified Data.ByteString.Base16 as Hex+import Data.Functor (void)+import Data.List (intersect) import qualified Data.List.NonEmpty as NE+import Data.Maybe (mapMaybe) import Test.Tasty import Test.Tasty.HUnit import Auth.Biscuit-import Auth.Biscuit.Token (BlockWithRevocationIds (..),- getRevocationIds)+import Auth.Biscuit.Token (getRevocationIds) -readFromFile :: FilePath -> IO Biscuit+pk :: PublicKey+pk = maybe undefined id $ parsePublicKeyHex "acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189"++readFromFile :: FilePath -> IO (Biscuit OpenOrSealed Verified) readFromFile path = do- result <- parse <$> ByteString.readFile ("test/samples/" <> path)+ result <- parse pk <$> ByteString.readFile ("test/samples/v2/" <> path) case result of Left x -> fail $ show x Right b -> pure b -getHex :: Biscuit -> IO [ByteString]-getHex b = do- let gi BlockWithRevocationIds{..} =- Hex.encode genericRevocationId- rids <- getRevocationIds b- pure . NE.toList $ gi <$> rids+readFromFileCheckRevocation :: [ByteString]+ -> FilePath+ -> IO (Either ParseError (Biscuit OpenOrSealed Verified))+readFromFileCheckRevocation revokedIds path =+ let parser = parseWith ParserConfig { encoding = RawBytes+ , getPublicKey = const pk+ , isRevoked = fromRevocationList revokedIds+ }+ in parser =<< ByteString.readFile ("test/samples/v2/" <> path) +getHex :: Biscuit OpenOrSealed Verified -> [ByteString]+getHex = NE.toList . fmap Hex.encode . getRevocationIds+ specs :: TestTree specs = testGroup "Revocation ids"- [ token1- , token16+ [ testGroup "Computation"+ [ token1+ , token16+ ]+ , parseTimeCheck ] token1 :: TestTree token1 = testCase "Token 1" $ do b <- readFromFile "test1_basic.bc"- rids <- getHex b+ let rids = getHex b rids @?=- [ "596a24631a8eeec5cbc0d84fc6c22fec1a524c7367bc8926827201ddd218f4bb"- , "dec4e0a7f817fe6c5964a18e9f0eae5564c12531b05dc4525f553570519baa87"+ [ "9d3e984bd0447eea9f31a56df51ba606160c66102063dd29410a2c85601a2139ce0cd212daf755ed0b8fe1f0e9388a89074b009b7169499e51df83c308e8d20b"+ , "5cade9fd3690b72bf90c29c529cb5b1bb50832554ba525b15c5d3f7c994814af522c5a68d61a950bc5f98d9ff4e3e20ffecef65ddaa2858251768ec999ed8b06" ] token16 :: TestTree token16 = testCase "Token 16" $ do b <- readFromFile "test16_caveat_head_name.bc"- rids <- getHex b+ let rids = getHex b rids @?=- [ "8f03890eeaa997cd03da71115168e41425b2be82731026225b0c5b87163e4d8e"- , "94fff36a9fa4d4149ab1488bf4aa84ed0bab0075cc7d051270367fb9c9688795"+ [ "aa8f26e32b6a55fe99decfb0f2c229776cc30360e5b68a5b06e730f1e9a13697f87929592f37b7b58dd00dececd6fa40540a3879f74bd232505f1c419907000c"+ , "02766fa2dbb0bd5a2d4d3fc4e0dd9252ec4dc118fe5bc0eafb67fbce0ddf6a86f4db7ecc0b1da14c210b8dcae53fcfc44565edb32ba18bfc9ca9f97258c4db0d" ]++parseTimeCheck :: TestTree+parseTimeCheck = testCase "Parse time revocation check" $ do+ let revokedIds :: [ByteString]+ revokedIds = mapMaybe fromHex [ "02766fa2dbb0bd5a2d4d3fc4e0dd9252ec4dc118fe5bc0eafb67fbce0ddf6a86f4db7ecc0b1da14c210b8dcae53fcfc44565edb32ba18bfc9ca9f97258c4db0d" ]+ res1 <- readFromFileCheckRevocation revokedIds "test16_caveat_head_name.bc"+ res1 @?= Left RevokedBiscuit+ res2 <- void <$> readFromFileCheckRevocation revokedIds "test1_basic.bc"+ res2 @?= Right ()
test/Spec/Roundtrip.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-}@@ -5,13 +6,15 @@ ( specs ) where -import Data.ByteString (ByteString)-import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.ByteString (ByteString)+import Data.List.NonEmpty (NonEmpty ((:|))) import Test.Tasty import Test.Tasty.HUnit -import Auth.Biscuit-import Auth.Biscuit.Token (Biscuit (..))+import Auth.Biscuit hiding (Biscuit, ParseError, PublicKey,+ addBlock, mkBiscuit, publicKey)+import Auth.Biscuit.Crypto+import Auth.Biscuit.Token specs :: TestTree specs = testGroup "Serde roundtrips"@@ -23,17 +26,15 @@ [ singleBlock (serializeB64, parseB64) , multipleBlocks (serializeB64, parseB64) ]- , testGroup "Hex serde"- [ singleBlock (serializeHex, parseHex)- , multipleBlocks (serializeHex, parseHex)- ] , testGroup "Keys serde"- [ private+ [ secret , public ] ] -type Roundtrip = (Biscuit -> ByteString, ByteString -> Either ParseError Biscuit)+type Roundtrip = ( Biscuit Open Verified -> ByteString+ , PublicKey -> ByteString -> Either ParseError (Biscuit OpenOrSealed Verified)+ ) roundtrip :: Roundtrip -> NonEmpty Block@@ -42,33 +43,35 @@ let addBlocks bs biscuit = case bs of (b:rest) -> addBlocks rest =<< addBlock b biscuit [] -> pure biscuit- keypair <- newKeypair- init' <- mkBiscuit keypair authority'+ sk <- generateSecretKey+ let pk = toPublic sk+ init' <- mkBiscuit sk authority' final <- addBlocks blocks' init' let serialized = s final- parsed = p serialized- getBlocks Biscuit{..} = snd (snd authority) :| (snd . snd <$> blocks)+ parsed = p pk serialized+ getBlock ((_, b), _, _) = b+ getBlocks b = getBlock <$> authority b :| blocks b getBlocks <$> parsed @?= Right i singleBlock :: Roundtrip -> TestTree singleBlock r = testCase "Single block" $ roundtrip r $ pure [block|- right(#authority, "file1", #read);- right(#authority, "file2", #read);- right(#authority, "file1", #write);+ right("file1", "read");+ right("file2", "read");+ right("file1", "write"); |] multipleBlocks :: Roundtrip -> TestTree multipleBlocks r = testCase "Multiple block" $ roundtrip r $ [block|- right(#authority, "file1", #read);- right(#authority, "file2", #read);- right(#authority, "file1", #write);+ right("file1", "read");+ right("file2", "read");+ right("file1", "write"); |] :| [ [block|- valid_date("file1") <- time(#ambient, $0), resource(#ambient, "file1"), $0 <= 2030-12-31T12:59:59+00:00;- valid_date($1) <- time(#ambient, $0), resource(#ambient, $1), $0 <= 1999-12-31T12:59:59+00:00, !["file1"].contains($1);- check if valid_date($0), resource(#ambient, $0);+ 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);+ check if valid_date($0), resource($0); |] , [block| check if true;@@ -93,43 +96,42 @@ 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 #abc == #abc; 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 ["hello", "world"].contains("hello"); |] , [block| check if- resource(#ambient, $0),- operation(#ambient, #read),- right(#authority, $0, #read);+ resource($0),+ operation("read"),+ right($0, "read"); |] , [block|- check if resource(#ambient, "file1");- check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00;+ check if resource("file1");+ check if time($date), $date <= 2018-12-20T00:00:00+00:00; |] ] -private :: TestTree-private = testGroup "Private key serde"+secret :: TestTree+secret = testGroup "Secret key serde" [ testCase "Raw bytes" $ do- pk <- privateKey <$> newKeypair- parsePrivateKey (serializePrivateKey pk) @?= Just pk+ sk <- newSecret+ parseSecretKey (serializeSecretKey sk) @?= Just sk , testCase "Hex encoding" $ do- pk <- privateKey <$> newKeypair- parsePrivateKeyHex (serializePrivateKeyHex pk) @?= Just pk+ sk <- newSecret+ parseSecretKeyHex (serializeSecretKeyHex sk) @?= Just sk ] public :: TestTree public = testGroup "Public key serde" [ testCase "Raw bytes" $ do- pk <- publicKey <$> newKeypair+ pk <- toPublic <$> newSecret parsePublicKey (serializePublicKey pk) @?= Just pk , testCase "Hex encoding" $ do- pk <- publicKey <$> newKeypair+ pk <- toPublic <$> newSecret parsePublicKeyHex (serializePublicKeyHex pk) @?= Just pk ]
+ test/Spec/SampleReader.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+module Spec.SampleReader where++import Debug.Trace++import Control.Arrow ((&&&))+import Control.Monad (join, void, when)+import Data.Aeson+import Data.Aeson.Types (typeMismatch, unexpected)+import Data.Attoparsec.Text (parseOnly)+import Data.Bifunctor (Bifunctor (..))+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Foldable (traverse_)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Text (Text, pack)+import Data.Text.Encoding (encodeUtf8)+import GHC.Generics (Generic)++import Test.Tasty hiding (Timeout)+import Test.Tasty.HUnit++import Auth.Biscuit+import Auth.Biscuit.Datalog.Executor (ExecutionError (..),+ ResultError (..))+import Auth.Biscuit.Datalog.Parser (authorizerParser, blockParser)+import Auth.Biscuit.Token++getB :: ParsedSignedBlock -> Block+getB ((_, b), _, _) = b++getAuthority :: Biscuit OpenOrSealed Verified -> Block+getAuthority = getB . authority++getBlocks :: Biscuit OpenOrSealed Verified -> [Block]+getBlocks = fmap getB . blocks++instance FromJSON SecretKey where+ parseJSON = withText "Ed25519 secret key" $ \t -> do+ let bs = encodeUtf8 t+ res = parseSecretKeyHex bs+ notSk = typeMismatch "Ed25519 secret key" (String t)+ maybe notSk pure res++instance FromJSON PublicKey where+ parseJSON = withText "Ed25519 public key" $ \t -> do+ let bs = encodeUtf8 t+ res = parsePublicKeyHex bs+ notPk = typeMismatch "Ed25519 public key" (String t)+ maybe notPk pure res++instance FromJSON Authorizer where+ parseJSON = withText "authorizer" $ \t -> do+ let res = parseAuthorizer t+ notAuthorizer e = typeMismatch e (String t)+ either notAuthorizer pure res++parseAuthorizer :: Text -> Either String Authorizer+parseAuthorizer = parseOnly authorizerParser++parseBlock :: Text -> Either String Block+parseBlock = parseOnly blockParser++data SampleFile a+ = SampleFile+ { root_private_key :: SecretKey+ , root_public_key :: PublicKey+ , testcases :: [TestCase a]+ }+ deriving stock (Eq, Show, Generic, Functor, Foldable, Traversable)+ deriving anyclass FromJSON++data RustResult e a+ = Err e+ | Ok a+ deriving stock (Generic, Eq, Show)++instance Bifunctor RustResult where+ bimap f g = \case+ Err e -> Err $ f e+ Ok a -> Ok $ g a++instance (FromJSON e, FromJSON a) => FromJSON (RustResult e a) where+ parseJSON = genericParseJSON $+ defaultOptions { sumEncoding = ObjectWithSingleField }++data ValidationR+ = ValidationR+ { world :: Maybe WorldDesc+ , result :: RustResult [Text] Int+ , authorizer_code :: Authorizer+ } deriving stock (Eq, Show, Generic)+ deriving anyclass FromJSON+++checkResult :: Show a+ => RustResult [Text] Int+ -> Either a b+ -> Assertion+checkResult r e = case (r, e) of+ (Err es, Right _) -> assertFailure $ "Got success, but expected failure: " <> show es+ (Ok _, Left e) -> assertFailure $ "Expected success, but got failure: " <> show e+ _ -> pure ()+++data TestCase a+ = TestCase+ { title :: String+ , filename :: a+ , token :: NonEmpty BlockDesc+ , validations :: Map String ValidationR+ }+ deriving stock (Eq, Show, Generic, Functor, Foldable, Traversable)+ deriving anyclass FromJSON++data BlockDesc+ = BlockDesc+ { symbols :: [Text]+ , code :: Text+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass FromJSON++data WorldDesc+ = WorldDesc+ { facts :: [Text]+ , rules :: [Text]+ , checks :: [Text]+ , policies :: [Text]+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass FromJSON++readBiscuits :: SampleFile FilePath -> IO (SampleFile (FilePath, ByteString))+readBiscuits =+ traverse $ traverse (BS.readFile . ("test/samples/v2/" <>)) . join (&&&) id++readSamplesFile :: IO (SampleFile (FilePath, ByteString))+readSamplesFile = do+ Just f <- decodeFileStrict' "test/samples/v2/samples.json"+ readBiscuits f++checkTokenBlocks :: (String -> IO ())+ -> Biscuit OpenOrSealed Verified+ -> NonEmpty BlockDesc+ -> Assertion+checkTokenBlocks step b blockDescs = do+ step "Checking blocks"+ let bs = getAuthority b :| getBlocks b+ expected = traverse (parseBlock . code) blockDescs+ expected @?= Right bs++processTestCase :: (String -> IO ())+ -> PublicKey -> TestCase (FilePath, ByteString)+ -> Assertion+processTestCase step rootPk TestCase{..} = do+ step "Parsing "+ let vList = Map.toList validations+ case parse rootPk (snd filename) of+ Left parseError -> traverse_ (processFailedValidation step parseError) vList+ Right biscuit -> do+ checkTokenBlocks step biscuit token+ traverse_ (processValidation step biscuit) vList++parseErrorToRust :: ParseError -> RustResult [Text] a+parseErrorToRust = Err . pure . \case+ InvalidHexEncoding -> "todo"+ InvalidB64Encoding -> "todo"+ InvalidProtobufSer w e -> pack $ "todo " <> show w <> e+ InvalidProtobuf True "Invalid signature" -> "Format(InvalidSignatureSize(16))"+ InvalidProtobuf w e -> "todo"+ InvalidProof -> "todo"+ InvalidSignatures -> "Format(Signature(InvalidSignature(\"signature error\")))"++processFailedValidation :: (String -> IO ())+ -> ParseError+ -> (String, ValidationR)+ -> Assertion+processFailedValidation step e (name, ValidationR{result}) = do+ step $ "Checking validation " <> name+ checkResult result (Left e)++execErrorToRust :: Either ExecutionError a -> RustResult [Text] Int+execErrorToRust (Right _) = Ok 0+execErrorToRust (Left e) = Err $ case e of+ Timeout -> ["todo"]+ TooManyFacts -> ["todo"]+ TooManyIterations -> ["todo"]+ FactsInBlocks -> ["todo"]+ ResultError (NoPoliciesMatched cs) -> ["todo"]+ ResultError (FailedChecks cs) -> ["Block(FailedBlockCheck { block_id: 1, check_id: 0, rule: \"check if resource($0), operation(#read), right($0, #read)\" })"]+ ResultError (DenyRuleMatched cs q) -> ["todo"]++processValidation :: (String -> IO ())+ -> Biscuit OpenOrSealed Verified+ -> (String, ValidationR)+ -> Assertion+processValidation step b (name, ValidationR{..}) = do+ when (name /= "") $ step ("Checking " <> name)+ w <- maybe (assertFailure "missing authorizer contents") pure world+ pols <- either (assertFailure . show) pure $ parseAuthorizer $ foldMap (<> ";") (policies w)+ res <- authorizeBiscuit b (authorizer_code <> pols)+ checkResult result res++runTests :: (String -> IO ())+ -> Assertion+runTests step = do+ step "Parsing sample file"+ SampleFile{..} <- readSamplesFile+ traverse_ (processTestCase step root_public_key) testcases++specs :: TestTree+specs = testCaseSteps "Biscuit samples - compliance checks" runTests++mkTestCase :: PublicKey -> TestCase (FilePath, ByteString) -> TestTree+mkTestCase root_public_key tc@TestCase{filename} =+ testCaseSteps (fst filename) (\step -> processTestCase step root_public_key tc)++getSpecs :: IO TestTree+getSpecs = do+ SampleFile{..} <- readSamplesFile+ pure $ testGroup "Biscuit samples - compliance checks"+ $ mkTestCase root_public_key <$> testcases
− test/Spec/Samples.hs
@@ -1,103 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-module Spec.Samples- ( specs- ) where--import qualified Data.ByteString as ByteString-import Test.Tasty-import Test.Tasty.HUnit--import Auth.Biscuit (ParseError (..), parse)-import Auth.Biscuit.Datalog.AST (Block)-import Auth.Biscuit.Datalog.Parser (block)-import Auth.Biscuit.Token (Biscuit (..))--readFromFile :: FilePath -> IO (Either ParseError Biscuit)-readFromFile path =- parse <$> ByteString.readFile ("test/samples/" <> path)--getAuthority :: Biscuit -> Block-getAuthority = snd . snd . authority--getBlocks :: Biscuit -> [Block]-getBlocks = fmap (snd . snd) . blocks--specs :: TestTree-specs = testGroup "Biscuit samples"- [ testCase "test1_basic" $ do- result <- readFromFile "test1_basic.bc"- getAuthority <$> result @?= Right- [block|- right(#authority, "file1", #read);- right(#authority, "file2", #read);- right(#authority, "file1", #write);- |]- getBlocks <$> result @?= Right- [ [block|- check if- resource(#ambient, $0),- operation(#ambient, #read),- right(#authority, $0, #read);- |]- ]- , testCase "test9_expired_token" $ do- result <- readFromFile "test9_expired_token.bc"- getAuthority <$> result @?= Right mempty- getBlocks <$> result @?= Right- [ [block|- check if resource(#ambient, "file1");- check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00;- |]- ]- , testCase "test13_block_rules" $ do- result <- readFromFile "test13_block_rules.bc"- getAuthority <$> result @?= Right- [block|- right(#authority, "file1", #read);- right(#authority, "file2", #read);- |]- getBlocks <$> result @?= Right- [ [block|- valid_date("file1") <- time(#ambient, $0), resource(#ambient, "file1"), $0 <= 2030-12-31T12:59:59+00:00;- valid_date($1) <- time(#ambient, $0), resource(#ambient, $1), $0 <= 1999-12-31T12:59:59+00:00, !["file1"].contains($1);- check if valid_date($0), resource(#ambient, $0);- |]- ]- , testCase "test17_expressions" $ do- result <- readFromFile "test17_expressions.bc"- getAuthority <$> result @?= Right- [block|- check if true;- check if !false;- check if !false;- check if false or true;- check if 1 < 2;- check if 2 > 1;- check if 1 <= 2;- check if 1 <= 1;- check if 2 >= 1;- check if 2 >= 2;- check if 3 == 3;- check if 1 + 2 * 3 - 4 / 2 == 5;- check if "hello world".starts_with("hello") && "hello world".ends_with("world");- check if "aaabde".matches("a*c?.e");- 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 #abc == #abc;- 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);- |]- getBlocks <$> result @?= Right []- ]
+ test/Spec/ScopedExecutor.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{- HLINT ignore "Reduce duplication" -}+module Spec.ScopedExecutor (specs) where++import Data.Attoparsec.Text (parseOnly)+import Data.Map.Strict as Map+import Data.Set as Set+import Data.Text (Text, unpack)+import Test.Tasty+import Test.Tasty.HUnit++import Auth.Biscuit.Datalog.AST+import Auth.Biscuit.Datalog.Executor (ExecutionError (..),+ Limits (..),+ ResultError (..),+ defaultLimits)+import Auth.Biscuit.Datalog.Parser (authorizer, block, check,+ query)+import Auth.Biscuit.Datalog.ScopedExecutor++specs :: TestTree+specs = testGroup "Block-scoped Datalog Evaluation"+ [ authorizerOnlySeesAuthority+ , authorityOnlySeesItselfAndAuthorizer+ , block1OnlySeesAuthorityAndAuthorizer+ , block1SeesAuthorityAndAuthorizer+ , iterationCountWorks+ , maxFactsCountWorks+ , allChecksAreCollected+ , revocationIdsAreInjected+ , factsAreQueried+ ]++authorizerOnlySeesAuthority :: TestTree+authorizerOnlySeesAuthority = testCase "Authorizer only accesses facts from authority" $ do+ let authority =+ [block|+ user(1234);+ |]+ block1 =+ [block|+ is_allowed(1234, "file1", "write");+ |]+ verif =+ [authorizer|+ allow if is_allowed(1234, "file1", "write");+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "") [(block1, "")] verif @?= Left (ResultError (NoPoliciesMatched []))++authorityOnlySeesItselfAndAuthorizer :: TestTree+authorityOnlySeesItselfAndAuthorizer = testCase "Authority rules only see authority and authorizer facts" $ do+ let authority =+ [block|+ user(1234);+ is_allowed($user, $resource) <- right($user, $resource, "read");+ |]+ block1 =+ [block|+ right(1234, "file1", "read");+ |]+ verif =+ [authorizer|+ allow if is_allowed(1234, "file1");+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "") [(block1, "")] verif @?= Left (ResultError (NoPoliciesMatched []))++block1OnlySeesAuthorityAndAuthorizer :: TestTree+block1OnlySeesAuthorityAndAuthorizer = testCase "Arbitrary blocks only see previous blocks" $ do+ let authority =+ [block|+ user(1234);+ |]+ block1 =+ [block|+ is_allowed($user, $resource) <- right($user, $resource, "read");+ check if is_allowed(1234, "file1");+ |]+ block2 =+ [block|+ right(1234, "file1", "read");+ |]+ verif =+ [authorizer|+ allow if true;+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "") [(block1, ""), (block2, "")] verif @?= Left (ResultError (FailedChecks $ pure [check|check if is_allowed(1234, "file1") |]))++block1SeesAuthorityAndAuthorizer :: TestTree+block1SeesAuthorityAndAuthorizer = testCase "Arbitrary blocks see previous blocks" $ do+ let authority =+ [block|+ user(1234);+ |]+ block1 =+ [block|+ is_allowed($user, $resource) <- user($user), right($user, $resource, "read");+ right(1234, "file1", "read");+ check if is_allowed(1234, "file1");+ |]+ verif =+ [authorizer| allow if false;+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "") [(block1, "")] verif @?= Left (ResultError $ NoPoliciesMatched [])+++iterationCountWorks :: TestTree+iterationCountWorks = testCase "ScopedExecutions stops when hitting the iterations threshold" $ do+ let limits = defaultLimits { maxIterations = 8 }+ authority =+ [block|+ a("yolo");+ b($a) <- a($a);+ c($b) <- b($b);+ d($c) <- c($c);+ e($d) <- d($d);+ f($e) <- e($e);+ g($f) <- f($f);+ |]+ block1 =+ [block|+ h($g) <- g($g);+ i($h) <- h($h);+ j($i) <- i($i);+ k($j) <- j($j);+ l($k) <- k($k);+ m($l) <- l($l);+ |]+ verif =+ [authorizer|+ allow if true;+ |]+ runAuthorizerNoTimeout limits (authority, "") [(block1, "")] verif @?= Left TooManyIterations++maxFactsCountWorks :: TestTree+maxFactsCountWorks = testCase "ScopedExecutions stops when hitting the facts threshold" $ do+ let limits = defaultLimits { maxFacts = 8 }+ authority =+ [block|+ a("yolo");+ b($a) <- a($a);+ c($b) <- b($b);+ d($c) <- c($c);+ e($d) <- d($d);+ f($e) <- e($e);+ g($f) <- f($f);+ |]+ block1 =+ [block|+ h($g) <- g($g);+ i($h) <- h($h);+ j($i) <- i($i);+ k($j) <- j($j);+ l($k) <- k($k);+ m($l) <- l($l);+ |]+ verif =+ [authorizer|+ allow if true;+ |]+ runAuthorizerNoTimeout limits (authority, "") [(block1, "")] verif @?= Left TooManyFacts++allChecksAreCollected :: TestTree+allChecksAreCollected = testCase "ScopedExecutions collects all facts results even after a failure" $ do+ let authority =+ [block|+ user(1234);+ |]+ block1 =+ [block|+ check if false;+ |]+ block2 =+ [block|+ check if false;+ |]+ verif =+ [authorizer|+ allow if user(4567);+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "") [(block1, ""), (block2, "")] verif @?= Left (ResultError $ NoPoliciesMatched [[check|check if false|], [check|check if false|]])++revocationIdsAreInjected :: TestTree+revocationIdsAreInjected = testCase "ScopedExecutions injects revocation ids" $ do+ let authority =+ [block|+ user(1234);+ |]+ block1 =+ [block|yolo("block1");|]+ block2 =+ [block|yolo("block2");|]+ verif =+ [authorizer|+ check if revocation_id(0, hex:61),+ revocation_id(1, hex:62),+ revocation_id(2, hex:63);+ |]+ runAuthorizerNoTimeout defaultLimits (authority, "a") [(block1, "b"), (block2, "c")] verif @?= Left (ResultError $ NoPoliciesMatched [])++factsAreQueried :: TestTree+factsAreQueried = testCase "AuthorizationSuccess can be queried" $ do+ let authority = [block|user(1234);|]+ block1 = [block|user("tampered value");|]+ verif = [authorizer|allow if true;|]+ result = runAuthorizerNoTimeout defaultLimits (authority, "a") [(block1, "b")] verif+ getUser s = queryAuthorizerFacts s [query|user($user)|]+ expected = Set.singleton $ Map.fromList+ [ ("user", LInteger 1234)+ ]+ getUser <$> result @?= Right expected
test/Spec/Verification.hs view
@@ -6,15 +6,17 @@ ) where import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.Set as Set import Test.Tasty import Test.Tasty.HUnit import Auth.Biscuit-import Auth.Biscuit.Datalog.AST (Expression' (..), ID' (..),- Query, QueryItem' (..))-import Auth.Biscuit.Datalog.Executor (ResultError (..))+import Auth.Biscuit.Datalog.AST (Expression' (..), Query,+ QueryItem' (..), Term' (..))+import Auth.Biscuit.Datalog.Executor (MatchedQuery (..),+ ResultError (..)) import qualified Auth.Biscuit.Datalog.Executor as Executor-import Auth.Biscuit.Datalog.Parser (check)+import Auth.Biscuit.Datalog.Parser (check, fact, query) specs :: TestTree specs = testGroup "Datalog checks"@@ -25,64 +27,69 @@ , factsRestrictions ] -ifTrue :: Query-ifTrue = [QueryItem [] [EValue $ LBool True]]+ifTrue :: MatchedQuery+ifTrue = MatchedQuery+ { matchedQuery = [query|true|]+ , bindings = Set.singleton mempty+ } -ifFalse :: Query-ifFalse = [QueryItem [] [EValue $ LBool False]]+ifFalse :: MatchedQuery+ifFalse = MatchedQuery+ { matchedQuery = [query|false|]+ , bindings = Set.singleton mempty+ } +ifFalse' :: Query+ifFalse' = matchedQuery ifFalse+ singleBlock :: TestTree singleBlock = testCase "Single block" $ do- keypair <- newKeypair- biscuit <- mkBiscuit keypair [block|right(#authority, "file1", #read);|]- res <- verifyBiscuit biscuit [verifier|check if right(#authority, "file1", #read);allow if true;|] (publicKey keypair)- res @?= Right ifTrue--resultError :: Executor.ResultError- -> Either VerificationError a-resultError = Left . DatalogError . Executor.ResultError+ secret <- newSecret+ biscuit <- mkBiscuit secret [block|right("file1", "read");|]+ res <- authorizeBiscuit biscuit [authorizer|check if right("file1", "read");allow if true;|]+ matchedAllowQuery <$> res @?= Right ifTrue errorAccumulation :: TestTree errorAccumulation = testGroup "Error accumulation" [ testCase "Only checks" $ do- keypair <- newKeypair- biscuit <- mkBiscuit keypair [block|check if false; check if false;|]- res <- verifyBiscuit biscuit [verifier|allow if true;|] (publicKey keypair)- res @?= resultError (FailedChecks $ ifFalse :| [ifFalse])+ secret <- newSecret+ biscuit <- mkBiscuit secret[block|check if false; check if false;|]+ res <- authorizeBiscuit biscuit [authorizer|allow if true;|]+ res @?= Left (ResultError $ FailedChecks $ ifFalse' :| [ifFalse']) , testCase "Checks and deny policies" $ do- keypair <- newKeypair- biscuit <- mkBiscuit keypair [block|check if false; check if false;|]- res <- verifyBiscuit biscuit [verifier|deny if true;|] (publicKey keypair)- res @?= resultError (DenyRuleMatched [ifFalse, ifFalse] ifTrue)+ secret <- newSecret+ biscuit <- mkBiscuit secret [block|check if false; check if false;|]+ res <- authorizeBiscuit biscuit [authorizer|deny if true;|]+ res @?= Left(ResultError $ DenyRuleMatched [ifFalse', ifFalse'] ifTrue) , testCase "Checks and no policies matched" $ do- keypair <- newKeypair- biscuit <- mkBiscuit keypair [block|check if false; check if false;|]- res <- verifyBiscuit biscuit [verifier|allow if false;|] (publicKey keypair)- res @?= resultError (NoPoliciesMatched [ifFalse, ifFalse])+ secret <- newSecret+ biscuit <- mkBiscuit secret [block|check if false; check if false;|]+ res <- authorizeBiscuit biscuit [authorizer|allow if false;|]+ res @?= Left (ResultError $ NoPoliciesMatched [ifFalse', ifFalse']) ] unboundVarRule :: TestTree unboundVarRule = testCase "Rule with unbound variable" $ do- keypair <- newKeypair- b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]- b2 <- addBlock [block|operation($unbound, #read) <- operation($any1, $any2);|] b1- res <- verifyBiscuit b2 [verifier|operation(#ambient,#write);allow if true;|] (publicKey keypair)- res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])+ secret <- newSecret+ b1 <- mkBiscuit secret [block|check if operation("read");|]+ b2 <- addBlock [block|operation($unbound, "read") <- operation($any1, $any2);|] b1+ res <- authorizeBiscuit b2 [authorizer|operation("write");allow if true;|]+ res @?= Left (Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation("read")|]) symbolRestrictions :: TestTree symbolRestrictions = testGroup "Restricted symbols in blocks" [ testCase "In facts" $ do- keypair <- newKeypair- b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]- b2 <- addBlock [block|operation(#ambient, #read);|] b1- res <- verifyBiscuit b2 [verifier|allow if true;|] (publicKey keypair)- res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])+ secret <- newSecret+ b1 <- mkBiscuit secret [block|check if operation("read");|]+ b2 <- addBlock [block|operation("read");|] b1+ res <- authorizeBiscuit b2 [authorizer|allow if true;|]+ res @?= Left (Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation("read")|]) , testCase "In rules" $ do- keypair <- newKeypair- b1 <- mkBiscuit keypair [block|check if operation(#ambient, #read);|]- b2 <- addBlock [block|operation($ambient, #read) <- operation($ambient, $any);|] b1- res <- verifyBiscuit b2 [verifier|operation(#ambient,#write);allow if true;|] (publicKey keypair)- res @?= Left (DatalogError $ Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation(#ambient, #read)|])+ secret <- newSecret+ b1 <- mkBiscuit secret [block|check if operation("read");|]+ b2 <- addBlock [block|operation($ambient, "read") <- operation($ambient, $any);|] b1+ res <- authorizeBiscuit b2 [authorizer|operation("write");allow if true;|]+ res @?= Left (Executor.ResultError $ Executor.FailedChecks $ pure [check|check if operation("read")|]) ] factsRestrictions :: TestTree@@ -90,15 +97,15 @@ let limits = defaultLimits { allowBlockFacts = False } in testGroup "No facts or rules in blocks" [ testCase "No facts" $ do- keypair <- newKeypair- b1 <- mkBiscuit keypair [block|right(#read);|]- b2 <- addBlock [block|right(#write);|] b1- res <- verifyBiscuitWithLimits limits b2 [verifier|allow if right(#write);|] (publicKey keypair)- res @?= Left (DatalogError $ Executor.ResultError $ Executor.NoPoliciesMatched [])+ secret <- newSecret+ b1 <- mkBiscuit secret [block|right("read");|]+ b2 <- addBlock [block|right("write");|] b1+ res <- authorizeBiscuitWithLimits limits b2 [authorizer|allow if right("write");|]+ res @?= Left (Executor.ResultError $ Executor.NoPoliciesMatched []) , testCase "No rules" $ do- keypair <- newKeypair- b1 <- mkBiscuit keypair [block|right(#read);|]- b2 <- addBlock [block|right(#write) <- right(#read);|] b1- res <- verifyBiscuitWithLimits limits b2 [verifier|allow if right(#write);|] (publicKey keypair)- res @?= Left (DatalogError $ Executor.ResultError $ Executor.NoPoliciesMatched [])+ secret <- newSecret+ b1 <- mkBiscuit secret [block|right("read");|]+ b2 <- addBlock [block|right("write") <- right("read");|] b1+ res <- authorizeBiscuitWithLimits limits b2 [authorizer|allow if right("write");|]+ res @?= Left (Executor.ResultError $ Executor.NoPoliciesMatched []) ]