diff --git a/.gitignore b/.gitignore index 7f170847..964e0286 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build .gobincache /go.work /go.work.sum +.buf-plugins/ diff --git a/Makefile b/Makefile index 4319529a..a976dc3f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ IMAGE ?= biscuit-go-test -.PHONY: all build test test-local verify lint apidiff clean +.PHONY: all build test test-local verify lint apidiff generate clean all: verify lint test @@ -27,5 +27,9 @@ lint: apidiff: ./script/apidiff.sh -r main +# Regenerates pb/ from the biscuit spec protobuf schema with buf. +generate: + ./script/generate.sh + clean: rm -rf build diff --git a/README.md b/README.md index 7c60a5a5..606bc50c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,23 @@ biscuit-go is an implementation of [Biscuit](https://github.com/eclipse-biscuit/ - [biscuit specification](https://github.com/eclipse-biscuit/biscuit) - [biscuit-rust](https://github.com/eclipse-biscuit/biscuit-rust) for some more technical details. +## Specification compatibility + +This library currently accepts datalog `v3.0` to `v3.3` blocks (block versions `3` to `6`, see +`MaxSchemaVersion`). Of datalog `v3.3`, `reject if`, the heterogeneous `==` and `!=`, `null`, +the lazy `&&` and `||`, the `.all()` / `.any()` closures, arrays, maps, `.type()`, +`extern::` function calls (registered with `WithExternFuncs`) and `.try_or()` are supported. + +The [specification sample suite](./samples) runs in CI: samples using block versions above +`MaxSchemaVersion` are verified to be rejected and otherwise skipped, so they become active as +support for each version lands. + +Tokens may be signed with Ed25519 or ECDSA on P-256 (`secp256r1`). `Biscuit.AuthorizerFrom` +takes a `RootKeyProvider` (`RootKey`, `RootKeysByID`, or your own function) for either key type; +`Biscuit.Authorizer` and `Biscuit.AuthorizerFor` still work with Ed25519 keys and are marked +deprecated. The `datalog.World` methods take the origin of each fact and the origins each rule +trusts, as in biscuit-rust. + ## Usage #### Create a biscuit @@ -56,7 +73,7 @@ if err != nil { // Attenuate the biscuit by appending a new block to it blockBuilder := b.CreateBlock() block, err := parser.FromStringBlockWithParams(` - check if resource($file), operation($permission), [{read}].contains($permission);`, + check if resource($file), operation($permission), {{read}}.contains($permission);`, map[string]biscuit.Term{"read": biscuit.String("read")}) if err != nil { panic(fmt.Errorf("failed to parse block: %v", err)) @@ -75,6 +92,40 @@ if err != nil { // attenuatedToken is a []byte, representing an attenuated token ``` +#### Append a third-party block + +A third party signs a block for a token without seeing the token. Facts of that block are only +visible to rules that trust the third party's key. + +```go +// Token holder: build the request for the third party. +request, err := b.ThirdPartyRequest() +if err != nil { + panic(err) +} +requestBytes, _ := request.Serialize() + +// Third party: sign a block for that request with its own key. +request, _ = biscuit.UnmarshalThirdPartyBlockRequest(requestBytes) +blockBuilder := biscuit.NewThirdPartyBlockBuilder() +blockBuilder.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{Name: "group", IDs: []biscuit.Term{biscuit.String("admin")}}}) +contents, err := request.CreateBlock(thirdPartyPrivateKey, blockBuilder) +if err != nil { + panic(err) +} +contentsBytes, _ := contents.Serialize() + +// Token holder: append the signed block. +contents, _ = biscuit.UnmarshalThirdPartyBlockContents(contentsBytes) +b, err = b.AppendThirdPartyBlock(nil, thirdPartyPublicKey, contents) +if err != nil { + panic(err) +} + +// A check trusting the third party's key sees group("admin"): +// check if group("admin") trusting ed25519/ +``` + #### Verify a biscuit ```go @@ -83,7 +134,7 @@ if err != nil { panic(fmt.Errorf("failed to deserialize token: %v", err)) } -authorizer, err := b.Authorizer(publicRoot) +authorizer, err := b.AuthorizerFrom(biscuit.RootKey(publicRoot)) if err != nil { panic(fmt.Errorf("failed to verify token and create authorizer: %v", err)) } diff --git a/authorizer.go b/authorizer.go index 74c753bd..92beae24 100644 --- a/authorizer.go +++ b/authorizer.go @@ -17,6 +17,10 @@ var ( ErrMissingSymbols = errors.New("biscuit: missing symbols") ErrPolicyDenied = errors.New("biscuit: denied by policy") ErrNoMatchingPolicy = errors.New("biscuit: denied by no matching policies") + // ErrExecution wraps an error raised while evaluating an expression + // (integer overflow, division by zero, type mismatch). Authorization + // stops at the first one, as the spec requires. + ErrExecution = errors.New("biscuit: execution error") ) type Authorizer interface { @@ -26,6 +30,9 @@ type Authorizer interface { AddRule(rule Rule) AddCheck(check Check) AddPolicy(policy Policy) + // AddScope sets the default scope of the authorizer's rules, checks + // and policies; see Scope. + AddScope(scope Scope) Authorize() error Query(rule Rule) (FactSet, error) Biscuit() *Biscuit @@ -36,15 +43,22 @@ type Authorizer interface { } type authorizer struct { - biscuit *Biscuit - baseWorld *datalog.World - world *datalog.World - baseSymbols *datalog.SymbolTable - symbols *datalog.SymbolTable - block_worlds []*datalog.World + biscuit *Biscuit + baseWorld *datalog.World + world *datalog.World + baseSymbols *datalog.SymbolTable + symbols *datalog.SymbolTable checks []Check policies []Policy + scopes []Scope + + // blocksByKey maps the public key of a third-party block, as printed + // by datalog.PublicKey.String, to the blocks it signed; `trusting ` + // resolves to those blocks. + blocksByKey map[string][]datalog.BlockID + + externs datalog.ExternFuncs dirty bool } @@ -59,19 +73,66 @@ func WithWorldOptions(opts ...datalog.WorldOption) AuthorizerOption { } } +// ExternFunc is a function that datalog v3.3 expressions call with +// `$left.extern::name()` (right is nil) or `$left.extern::name($right)`. +// It returns the value of the call. +type ExternFunc func(left Term, right Term) (Term, error) + +// WithExternFuncs registers the functions available to extern:: calls, by +// name. A call to a name that is not registered fails the check or rule that +// makes it with ErrExecution. +func WithExternFuncs(funcs map[string]ExternFunc) AuthorizerOption { + return func(a *authorizer) { + for name, f := range funcs { + a.externs[name] = wrapExternFunc(f) + } + } +} + +func wrapExternFunc(f ExternFunc) datalog.ExternFunc { + return func(symbols *datalog.SymbolTable, left datalog.Term, right datalog.Term) (datalog.Term, error) { + l, err := fromDatalogID(symbols, left) + if err != nil { + return nil, err + } + var r Term + if right != nil { + if r, err = fromDatalogID(symbols, right); err != nil { + return nil, err + } + } + res, err := f(l, r) + if err != nil { + return nil, err + } + if res == nil { + return nil, errors.New("biscuit: extern function returned no value") + } + return res.convert(symbols), nil + } +} + func NewVerifier(b *Biscuit, opts ...AuthorizerOption) (Authorizer, error) { a := &authorizer{ - biscuit: b, - baseWorld: datalog.NewWorld(), - baseSymbols: defaultSymbolTable.Clone(), - checks: []Check{}, - policies: []Policy{}, - block_worlds: []*datalog.World{}, + biscuit: b, + baseWorld: datalog.NewWorld(), + baseSymbols: defaultSymbolTable.Clone(), + checks: []Check{}, + policies: []Policy{}, + blocksByKey: map[string][]datalog.BlockID{}, + externs: datalog.ExternFuncs{}, } for _, opt := range opts { opt(a) } + datalog.WithExternFuncs(a.externs)(a.baseWorld) + for i, block := range b.blocks { + if block.externalKey != nil { + key := block.externalKey.String() + a.blocksByKey[key] = append(a.blocksByKey[key], datalog.BlockID(i+1)) + } + } a.world = a.baseWorld.Clone() a.symbols = a.baseSymbols.Clone() @@ -96,14 +157,32 @@ func (v *authorizer) AddBlock(block ParsedBlock) { for _, c := range block.Checks { v.AddCheck(c) } + for _, s := range block.Scopes { + v.AddScope(s) + } } func (v *authorizer) AddFact(fact Fact) { - v.world.AddFact(fact.convert(v.symbols)) + v.world.AddFact(authorizerOrigin, fact.convert(v.symbols)) } func (v *authorizer) AddRule(rule Rule) { - v.world.AddRule(rule.convert(v.symbols)) + r := rule.convert(v.symbols) + v.world.AddRule(datalog.AuthorizerBlockID, v.trustedOrigins(r.Scopes, v.authorizerTrustedOrigins(), datalog.AuthorizerBlockID), r) +} + +func (v *authorizer) AddScope(scope Scope) { + v.scopes = append(v.scopes, scope) +} + +// authorizerTrustedOrigins is the default scope of the authorizer's rules, +// checks and policies: its own scopes, or the authority block and itself. +func (v *authorizer) authorizerTrustedOrigins() datalog.TrustedOrigins { + return v.trustedOrigins(v.scopes, datalog.DefaultTrustedOrigins(), datalog.AuthorizerBlockID) +} + +func (v *authorizer) trustedOrigins(scopes []datalog.Scope, defaults datalog.TrustedOrigins, current datalog.BlockID) datalog.TrustedOrigins { + return datalog.TrustedOriginsFromScopes(scopes, defaults, current, v.blocksByKey) } func (v *authorizer) AddCheck(check Check) { @@ -114,26 +193,37 @@ func (v *authorizer) AddPolicy(policy Policy) { v.policies = append(v.policies, policy) } +// authorizerOrigin is the origin of the facts the authorizer adds itself. +var authorizerOrigin = datalog.NewOrigin(datalog.AuthorizerBlockID) + func (v *authorizer) Authorize() error { // if we load facts from the verifier before // the token's fact and rules, we might get inconsistent symbols // token ements should first be converted to builder elements // with the token's symbol table, then converted back // with the verifier's symbol table - for _, fact := range *v.biscuit.authority.facts { - f, err := fromDatalogFact(v.biscuit.symbols, fact) - if err != nil { - return fmt.Errorf("biscuit: verification failed: %s", err) + blocks := append([]*Block{v.biscuit.authority}, v.biscuit.blocks...) + blockTrusted := make([]datalog.TrustedOrigins, len(blocks)) + for i, block := range blocks { + blockID := datalog.BlockID(i) + blockTrusted[i] = v.trustedOrigins(block.scopes, datalog.DefaultTrustedOrigins(), blockID) + symbols := block.symbolTable(v.biscuit.symbols) + for _, fact := range *block.facts { + f, err := fromDatalogFact(symbols, fact) + if err != nil { + return fmt.Errorf("biscuit: verification failed: %s", err) + } + v.world.AddFact(datalog.NewOrigin(blockID), f.convert(v.symbols)) } - v.world.AddFact(f.convert(v.symbols)) - } - for _, rule := range v.biscuit.authority.rules { - r, err := fromDatalogRule(v.biscuit.symbols, rule) - if err != nil { - return fmt.Errorf("biscuit: verification failed: %s", err) + for _, rule := range block.rules { + r, err := fromDatalogRule(symbols, rule) + if err != nil { + return fmt.Errorf("biscuit: verification failed: %s", err) + } + dlRule := r.convert(v.symbols) + v.world.AddRule(blockID, v.trustedOrigins(dlRule.Scopes, blockTrusted[i], blockID), dlRule) } - v.world.AddRule(r.convert(v.symbols)) } if err := v.world.Run(v.symbols); err != nil { @@ -142,44 +232,32 @@ func (v *authorizer) Authorize() error { v.dirty = true var errs []error + debug := datalog.SymbolDebugger{SymbolTable: v.symbols} + authorizerTrusted := v.authorizerTrustedOrigins() for i, check := range v.checks { c := check.convert(v.symbols) - successful := false - for _, query := range c.Queries { - res := v.world.QueryRule(query, v.symbols) - if len(*res) != 0 { - successful = true - break - } + successful, err := v.checkPasses(c, datalog.AuthorizerBlockID, authorizerTrusted) + if err != nil { + return fmt.Errorf("%w: %w", ErrExecution, err) } if !successful { - debug := datalog.SymbolDebugger{ - SymbolTable: v.symbols, - } errs = append(errs, fmt.Errorf("failed to verify check #%d: %s", i, debug.Check(c))) } } for i, check := range v.biscuit.authority.checks { - ch, err := fromDatalogCheck(v.biscuit.symbols, check) + ch, err := fromDatalogCheck(v.biscuit.authority.symbolTable(v.biscuit.symbols), check) if err != nil { return fmt.Errorf("biscuit: verification failed: %s", err) } c := ch.convert(v.symbols) - successful := false - for _, query := range c.Queries { - res := v.world.QueryRule(query, v.symbols) - if len(*res) != 0 { - successful = true - break - } + successful, err := v.checkPasses(c, 0, blockTrusted[0]) + if err != nil { + return fmt.Errorf("%w: %w", ErrExecution, err) } if !successful { - debug := datalog.SymbolDebugger{ - SymbolTable: v.symbols, - } errs = append(errs, fmt.Errorf("failed to verify block 0 check #%d: %s", i, debug.Check(c))) } } @@ -191,7 +269,11 @@ func (v *authorizer) Authorize() error { break } for _, query := range policy.Queries { - res := v.world.QueryRule(query.convert(v.symbols), v.symbols) + q := query.convert(v.symbols) + res, err := v.world.QueryRule(q, datalog.AuthorizerBlockID, v.trustedOrigins(q.Scopes, authorizerTrusted, datalog.AuthorizerBlockID), v.symbols) + if err != nil { + return fmt.Errorf("%w: %w", ErrExecution, err) + } if len(*res) != 0 { switch policy.Kind { case PolicyKindAllow: @@ -206,59 +288,23 @@ func (v *authorizer) Authorize() error { } } - // remove the rules from the vrifier and authority blocks - // so they are not affected by facts created by later blocks - v.world.ResetRules() - for i, block := range v.biscuit.blocks { - block_world := v.world.Clone() - - for _, fact := range *block.facts { - f, err := fromDatalogFact(v.biscuit.symbols, fact) - if err != nil { - return fmt.Errorf("biscuit: verification failed: %s", err) - } - block_world.AddFact(f.convert(v.symbols)) - } - - for _, rule := range block.rules { - r, err := fromDatalogRule(v.biscuit.symbols, rule) - if err != nil { - return fmt.Errorf("biscuit: verification failed: %s", err) - } - block_world.AddRule(r.convert(v.symbols)) - } - - if err := block_world.Run(v.symbols); err != nil { - return err - } - + blockID := datalog.BlockID(i + 1) for j, check := range block.checks { - ch, err := fromDatalogCheck(v.biscuit.symbols, check) + ch, err := fromDatalogCheck(block.symbolTable(v.biscuit.symbols), check) if err != nil { return fmt.Errorf("biscuit: verification failed: %s", err) } c := ch.convert(v.symbols) - successful := false - for _, query := range c.Queries { - res := block_world.QueryRule(query, v.symbols) - - if len(*res) != 0 { - successful = true - break - } + successful, err := v.checkPasses(c, blockID, blockTrusted[blockID]) + if err != nil { + return fmt.Errorf("%w: %w", ErrExecution, err) } if !successful { - debug := datalog.SymbolDebugger{ - SymbolTable: v.symbols, - } - errs = append(errs, fmt.Errorf("failed to verify block #%d check #%d: %s", i+1, j, debug.Check(c))) + errs = append(errs, fmt.Errorf("failed to verify block #%d check #%d: %s", blockID, j, debug.Check(c))) } } - - block_world.ResetRules() - v.block_worlds = append(v.block_worlds, block_world) } if len(errs) > 0 { @@ -280,13 +326,55 @@ func (v *authorizer) Authorize() error { } } +// checkPasses evaluates the queries of a check from block blockID: a check +// passes when one of its queries does, under the semantics of its kind. Each +// query reads the facts of its own scopes, or of defaults. +func (v *authorizer) checkPasses(c datalog.Check, blockID datalog.BlockID, defaults datalog.TrustedOrigins) (bool, error) { + for _, query := range c.Queries { + trusted := v.trustedOrigins(query.Scopes, defaults, blockID) + switch c.Kind { + case datalog.CheckKindAll: + ok, err := v.world.QueryMatchAll(query, trusted, v.symbols) + if err != nil { + return false, err + } + if ok { + return true, nil + } + case datalog.CheckKindReject: + res, err := v.world.QueryRule(query, blockID, trusted, v.symbols) + if err != nil { + return false, err + } + if len(*res) == 0 { + return true, nil + } + default: + res, err := v.world.QueryRule(query, blockID, trusted, v.symbols) + if err != nil { + return false, err + } + if len(*res) != 0 { + return true, nil + } + } + } + return false, nil +} + func (v *authorizer) Query(rule Rule) (FactSet, error) { if err := v.world.Run(v.symbols); err != nil { return nil, err } v.dirty = true - facts := v.world.QueryRule(rule.convert(v.symbols), v.symbols) + // Without scopes a query reads the authority block and the authorizer, + // like the reference implementation; facts of later blocks are not visible. + q := rule.convert(v.symbols) + facts, err := v.world.QueryRule(q, datalog.AuthorizerBlockID, v.trustedOrigins(q.Scopes, datalog.DefaultTrustedOrigins(), datalog.AuthorizerBlockID), v.symbols) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrExecution, err) + } result := make([]Fact, 0, len(*facts)) for _, fact := range *facts { @@ -321,6 +409,7 @@ func (v *authorizer) Reset() { v.symbols = v.baseSymbols.Clone() v.checks = []Check{} v.policies = []Policy{} + v.scopes = nil v.dirty = false } @@ -331,7 +420,7 @@ func (v *authorizer) LoadPolicies(authorizerPolicies []byte) error { } switch pbPolicies.GetVersion() { - case 3: + case blockVersion3_0, blockVersion3_1, blockVersion3_2, blockVersion3_3: return v.loadPoliciesV2(pbPolicies) default: return fmt.Errorf("verifier: unsupported policies version %d", pbPolicies.GetVersion()) @@ -348,20 +437,20 @@ func (v *authorizer) loadPoliciesV2(pbPolicies *pb.AuthorizerPolicies) error { if err != nil { return fmt.Errorf("verifier: load policies v1: failed to convert datalog fact: %w", err) } - v.world.AddFact(*fact) + v.world.AddFact(authorizerOrigin, *fact) } for _, pbRule := range pbPolicies.Rules { - rule, err := protoRuleToTokenRuleV2(pbRule) + rule, err := protoRuleToTokenRuleV2(pbRule, nil) if err != nil { return fmt.Errorf("verifier: load policies v1: failed to convert datalog rule: %w", err) } - v.world.AddRule(*rule) + v.world.AddRule(datalog.AuthorizerBlockID, v.trustedOrigins(rule.Scopes, v.authorizerTrustedOrigins(), datalog.AuthorizerBlockID), *rule) } v.checks = make([]Check, len(pbPolicies.Checks)) for i, pbCheck := range pbPolicies.Checks { - dlCheck, err := protoCheckToTokenCheckV2(pbCheck) + dlCheck, err := protoCheckToTokenCheckV2(pbCheck, nil) if err != nil { return fmt.Errorf("verifier: load policies v1: failed to convert datalog check: %w", err) } @@ -386,7 +475,7 @@ func (v *authorizer) loadPoliciesV2(pbPolicies *pb.AuthorizerPolicies) error { policy.Queries = make([]Rule, len(pbPolicy.Queries)) for j, pbRule := range pbPolicy.Queries { - dlRule, err := protoRuleToTokenRuleV2(pbRule) + dlRule, err := protoRuleToTokenRuleV2(pbRule, nil) if err != nil { return fmt.Errorf("verifier: load policies v1: failed to convert datalog policy rule: %w", err) } @@ -408,27 +497,37 @@ func (v *authorizer) SerializePolicies() ([]byte, error) { return nil, errors.New("verifier: can't serialize after world has been run") } - protoFacts := make([]*pb.FactV2, len(*v.world.Facts())) - for i, fact := range *v.world.Facts() { - protoFact, err := tokenFactToProtoFactV2(fact) - if err != nil { - return nil, fmt.Errorf("verifier: failed to convert fact: %w", err) + // Only what the authorizer added itself is saved; the token is not part + // of the policies. + var protoFacts []*pb.Fact + for _, group := range v.world.Facts() { + if !group.Origin.Equal(authorizerOrigin) { + continue + } + for _, fact := range group.Facts { + protoFact, err := tokenFactToProtoFactV2(fact) + if err != nil { + return nil, fmt.Errorf("verifier: failed to convert fact: %w", err) + } + protoFacts = append(protoFacts, protoFact) } - protoFacts[i] = protoFact } - protoRules := make([]*pb.RuleV2, len(v.world.Rules())) - for i, rule := range v.world.Rules() { - protoRule, err := tokenRuleToProtoRuleV2(rule) + var protoRules []*pb.Rule + for _, br := range v.world.Rules() { + if br.BlockID != datalog.AuthorizerBlockID { + continue + } + protoRule, err := tokenRuleToProtoRuleV2(br.Rule, nil) if err != nil { return nil, fmt.Errorf("verifier: failed to convert rule: %w", err) } - protoRules[i] = protoRule + protoRules = append(protoRules, protoRule) } - protoChecks := make([]*pb.CheckV2, len(v.checks)) + protoChecks := make([]*pb.Check, len(v.checks)) for i, check := range v.checks { - protoCheck, err := tokenCheckToProtoCheckV2(check.convert(v.symbols)) + protoCheck, err := tokenCheckToProtoCheckV2(check.convert(v.symbols), nil) if err != nil { return nil, fmt.Errorf("verifier: failed to convert check: %w", err) } @@ -449,9 +548,9 @@ func (v *authorizer) SerializePolicies() ([]byte, error) { return nil, fmt.Errorf("verifier: unsupported policy kind %v", policy.Kind) } - protoPolicy.Queries = make([]*pb.RuleV2, len(policy.Queries)) + protoPolicy.Queries = make([]*pb.Rule, len(policy.Queries)) for j, rule := range policy.Queries { - protoRule, err := tokenRuleToProtoRuleV2(rule.convert(v.symbols)) + protoRule, err := tokenRuleToProtoRuleV2(rule.convert(v.symbols), nil) if err != nil { return nil, fmt.Errorf("verifier: failed to convert policy rule: %w", err) } diff --git a/biscuit.go b/biscuit.go index 8c3cf316..69bcf4bc 100644 --- a/biscuit.go +++ b/biscuit.go @@ -5,19 +5,17 @@ package biscuit import ( "bytes" + stdcrypto "crypto" + "crypto/ed25519" "crypto/rand" "encoding/binary" - - //"crypto/sha256" - "crypto/ed25519" "errors" "fmt" "io" "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/eclipse-biscuit/biscuit-go/v2/internal/crypto" "github.com/eclipse-biscuit/biscuit-go/v2/pb" - - //"github.com/eclipse-biscuit/biscuit-go/sig" "google.golang.org/protobuf/proto" ) @@ -28,7 +26,10 @@ type Biscuit struct { authority *Block blocks []*Block symbols *datalog.SymbolTable - container *pb.Biscuit + // publicKeys is the key table of the token: the keys the scopes of its + // blocks refer to, in order of introduction. + publicKeys publicKeyTable + container *pb.Biscuit } var ( @@ -50,30 +51,35 @@ var ( // ErrUnknownPublicKey is returned when verifying a biscuit with the wrong public key ErrUnknownPublicKey = errors.New("biscuit: unknown public key") - ErrInvalidSignature = errors.New("biscuit: invalid signature") + ErrInvalidSignature = crypto.ErrInvalidSignature ErrInvalidSignatureSize = errors.New("biscuit: invalid signature size") - ErrInvalidKeySize = errors.New("biscuit: invalid key size") + ErrInvalidKeySize = crypto.ErrInvalidKeySize - ErrUnsupportedAlgorithm = errors.New("biscuit: unsupported signature algorithm") + ErrUnsupportedAlgorithm = crypto.ErrUnsupportedAlgorithm // Deprecated: use ErrUnsupportedAlgorithm. UnsupportedAlgorithm = ErrUnsupportedAlgorithm ) type biscuitOptions struct { - rng io.Reader - rootKeyID *uint32 + rng io.Reader + rootKeyID *uint32 + nextKeyAlgorithm pb.PublicKey_Algorithm } type biscuitOption interface { applyToBiscuit(*biscuitOptions) error } -func newBiscuit(root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, authority *Block, opts ...biscuitOption) (*Biscuit, error) { +func newBiscuit(root crypto.Signer, baseSymbols *datalog.SymbolTable, authority *Block, opts ...biscuitOption) (*Biscuit, error) { + if root == nil { + return nil, ErrNoPublicKeyAvailable + } options := biscuitOptions{ - rng: rand.Reader, + rng: rand.Reader, + nextKeyAlgorithm: root.Algorithm(), } for _, opt := range opts { if err := opt.applyToBiscuit(&options); err != nil { @@ -88,10 +94,9 @@ func newBiscuit(root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, autho } symbols.Extend(authority.symbols) + publicKeys := publicKeyTable(nil).with(authority.publicKeys...) - nextPublicKey, nextPrivateKey, _ := ed25519.GenerateKey(options.rng) - - protoAuthority, err := tokenBlockToProtoBlock(authority) + protoAuthority, err := tokenBlockToProtoBlock(authority, nil) if err != nil { return nil, err } @@ -100,28 +105,9 @@ func newBiscuit(root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, autho return nil, err } - algorithm := pb.PublicKey_Ed25519 - toSignAlgorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(toSignAlgorithm[0:], uint32(pb.PublicKey_Ed25519)) - toSign := append(marshalledAuthority[:], toSignAlgorithm...) - toSign = append(toSign, nextPublicKey[:]...) - - signature := ed25519.Sign(root, toSign) - nextKey := &pb.PublicKey{ - Algorithm: &algorithm, - Key: nextPublicKey, - } - - signedBlock := &pb.SignedBlock{ - Block: marshalledAuthority, - NextKey: nextKey, - Signature: signature, - } - - proof := &pb.Proof{ - Content: &pb.Proof_NextSecret{ - NextSecret: nextPrivateKey.Seed(), - }, + signedBlock, proof, err := signBlock(root, options.nextKeyAlgorithm, marshalledAuthority, authority.version, nil, nil, options.rng) + if err != nil { + return nil, err } container := &pb.Biscuit{ @@ -131,9 +117,10 @@ func newBiscuit(root ed25519.PrivateKey, baseSymbols *datalog.SymbolTable, autho } return &Biscuit{ - authority: authority, - symbols: symbols, - container: container, + authority: authority, + symbols: symbols, + publicKeys: publicKeys, + container: container, }, nil } @@ -142,11 +129,176 @@ func New(rng io.Reader, root ed25519.PrivateKey, baseSymbols *datalog.SymbolTabl if rng != nil { opts = []biscuitOption{WithRNG(rng)} } - return newBiscuit(root, baseSymbols, authority, opts...) + signer, err := crypto.NewSigner(root) + if err != nil { + return nil, err + } + return newBiscuit(signer, baseSymbols, authority, opts...) +} + +// Signature payload formats, selected per block by pb.SignedBlock.Version. +const ( + signatureVersionV0 uint32 = 0 + // signatureVersionV1 binds each signature to the previous one and tags + // every field; required for third-party blocks, datalog v3.3 blocks and + // any key that is not Ed25519. + signatureVersionV1 uint32 = 1 +) + +// signatureVersion picks the payload format for a new block: v1 when the spec +// requires it, otherwise whatever the previous blocks use, so that tokens +// which only need v0 keep the same bytes as before. +func signatureVersion(signer, next crypto.Signer, blockVersion uint32, externalSignature *pb.ExternalSignature, previous ...*pb.SignedBlock) uint32 { + if externalSignature != nil || blockVersion >= 6 { + return signatureVersionV1 + } + if signer.Algorithm() != pb.PublicKey_Ed25519 || next.Algorithm() != pb.PublicKey_Ed25519 { + return signatureVersionV1 + } + version := signatureVersionV0 + for _, block := range previous { + version = max(version, block.GetVersion()) + } + return version +} + +// signBlock signs a serialized block with the key of the preceding block (or +// the root key for the authority block) and returns the block with the proof +// for the next block. The next key is generated with the given algorithm; each +// block in a token may use a different one. previous are the signed blocks this +// one is appended to, nil for the authority block. externalSignature is set +// for a third-party block and becomes part of the signed payload. +func signBlock(signer crypto.Signer, nextAlgorithm pb.PublicKey_Algorithm, marshalledBlock []byte, blockVersion uint32, externalSignature *pb.ExternalSignature, previous []*pb.SignedBlock, rng io.Reader) (*pb.SignedBlock, *pb.Proof, error) { + next, err := crypto.GenerateSigner(nextAlgorithm, rng) + if err != nil { + return nil, nil, err + } + signedBlock := &pb.SignedBlock{ + Block: marshalledBlock, + NextKey: &pb.PublicKey{ + Algorithm: next.Algorithm().Enum(), + Key: next.PublicKey(), + }, + ExternalSignature: externalSignature, + } + if version := signatureVersion(signer, next, blockVersion, externalSignature, previous...); version != signatureVersionV0 { + signedBlock.Version = &version + } + + var previousSignature []byte + if n := len(previous); n > 0 { + previousSignature = previous[n-1].GetSignature() + } + signature, err := signer.Sign(blockSignaturePayload(signedBlock, previousSignature)) + if err != nil { + return nil, nil, err + } + signedBlock.Signature = signature + + proof := &pb.Proof{ + Content: &pb.Proof_NextSecret{ + NextSecret: next.Secret(), + }, + } + return signedBlock, proof, nil +} + +// blockSignaturePayload is the data signed for a block. previousSignature is +// nil for the authority block. +// +// v0: block [externalSig] alg(le u32) nextKey +// v1: "\0BLOCK\0\0VERSION\0" version(le u32) "\0PAYLOAD\0" block +// +// "\0ALGORITHM\0" alg(le u32) "\0NEXTKEY\0" nextKey +// ["\0PREVSIG\0" previousSignature] ["\0EXTERNALSIG\0" externalSig] +func blockSignaturePayload(block *pb.SignedBlock, previousSignature []byte) []byte { + algorithm := make([]byte, 4) + binary.LittleEndian.PutUint32(algorithm, uint32(block.GetNextKey().GetAlgorithm())) + externalSignature := block.GetExternalSignature().GetSignature() + + if block.GetVersion() == signatureVersionV0 { + payload := make([]byte, 0, len(block.GetBlock())+len(externalSignature)+4+len(block.GetNextKey().GetKey())) + payload = append(payload, block.GetBlock()...) + payload = append(payload, externalSignature...) + payload = append(payload, algorithm...) + return append(payload, block.GetNextKey().GetKey()...) + } + + version := make([]byte, 4) + binary.LittleEndian.PutUint32(version, block.GetVersion()) + payload := []byte("\x00BLOCK\x00\x00VERSION\x00") + payload = append(payload, version...) + payload = append(payload, "\x00PAYLOAD\x00"...) + payload = append(payload, block.GetBlock()...) + payload = append(payload, "\x00ALGORITHM\x00"...) + payload = append(payload, algorithm...) + payload = append(payload, "\x00NEXTKEY\x00"...) + payload = append(payload, block.GetNextKey().GetKey()...) + if previousSignature != nil { + payload = append(payload, "\x00PREVSIG\x00"...) + payload = append(payload, previousSignature...) + } + if externalSignature != nil { + payload = append(payload, "\x00EXTERNALSIG\x00"...) + payload = append(payload, externalSignature...) + } + return payload +} + +// externalSignaturePayload is the data a third party signs for its block: +// "\0EXTERNAL\0\0VERSION\0" version(le u32) "\0PAYLOAD\0" block "\0PREVSIG\0" previousSignature +// where previousSignature is the signature of the block it is appended to. +func externalSignaturePayload(marshalledBlock []byte, previousSignature []byte, version uint32) []byte { + v := make([]byte, 4) + binary.LittleEndian.PutUint32(v, version) + payload := []byte("\x00EXTERNAL\x00\x00VERSION\x00") + payload = append(payload, v...) + payload = append(payload, "\x00PAYLOAD\x00"...) + payload = append(payload, marshalledBlock...) + payload = append(payload, "\x00PREVSIG\x00"...) + return append(payload, previousSignature...) +} + +// verifyExternalSignature checks the third-party signature of a block against +// the signature of the block before it. +func verifyExternalSignature(block *pb.SignedBlock, previousSignature []byte) error { + ext := block.GetExternalSignature() + verifier, err := crypto.ParseVerifier(ext.GetPublicKey().GetAlgorithm(), ext.GetPublicKey().GetKey()) + if err != nil { + return err + } + if err := verifier.Verify(externalSignaturePayload(block.GetBlock(), previousSignature, block.GetVersion()), ext.GetSignature()); err != nil { + return fmt.Errorf("biscuit: invalid external signature: %w", err) + } + return nil +} + +// sealSignaturePayload is the data signed for the final proof. The spec +// defines only a v0 format: the v0 block payload of the last block, then +// its signature. +func sealSignaturePayload(lastBlock *pb.SignedBlock) []byte { + v0 := &pb.SignedBlock{Block: lastBlock.GetBlock(), NextKey: lastBlock.GetNextKey()} + return append(blockSignaturePayload(v0, nil), lastBlock.GetSignature()...) +} + +// nextSigner decodes the private key of the proof with the algorithm of the last block. +func (b *Biscuit) nextSigner() (crypto.Signer, error) { + secret := b.container.GetProof().GetNextSecret() + if secret == nil { + return nil, errors.New("biscuit: token is sealed") + } + return crypto.ParseSigner(b.lastSignedBlock().GetNextKey().GetAlgorithm(), secret) +} + +func (b *Biscuit) lastSignedBlock() *pb.SignedBlock { + if n := len(b.container.GetBlocks()); n > 0 { + return b.container.Blocks[n-1] + } + return b.container.GetAuthority() } func (b *Biscuit) CreateBlock() BlockBuilder { - return NewBlockBuilder(b.symbols.Clone()) + return newBlockBuilder(b.symbols.Clone(), b.publicKeys) } func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error) { @@ -154,17 +306,11 @@ func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error) { return nil, errors.New("biscuit: append failed, token is sealed") } - privateKey := b.container.Proof.GetNextSecret() - if privateKey == nil { - return nil, errors.New("biscuit: append failed, token is sealed") - } - - if len(privateKey) != 32 { - return nil, ErrInvalidKeySize + signer, err := b.nextSigner() + if err != nil { + return nil, fmt.Errorf("biscuit: append failed: %w", err) } - privateKey = ed25519.NewKeyFromSeed(privateKey) - if !b.symbols.IsDisjoint(block.symbols) { return nil, ErrSymbolTableOverlap } @@ -182,11 +328,10 @@ func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error) { symbols := b.symbols.Clone() symbols.Extend(block.symbols) - - nextPublicKey, nextPrivateKey, _ := ed25519.GenerateKey(rng) + publicKeys := b.publicKeys.with(block.publicKeys...) // serialize and sign the new block - protoBlock, err := tokenBlockToProtoBlock(block) + protoBlock, err := tokenBlockToProtoBlock(block, b.publicKeys) if err != nil { return nil, err } @@ -195,32 +340,18 @@ func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error) { return nil, err } - algorithm := pb.PublicKey_Ed25519 - toSignAlgorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(toSignAlgorithm[0:], uint32(pb.PublicKey_Ed25519)) - toSign := append(marshalledBlock[:], toSignAlgorithm...) - toSign = append(toSign, nextPublicKey[:]...) - - signature := ed25519.Sign(privateKey, toSign) - nextKey := &pb.PublicKey{ - Algorithm: &algorithm, - Key: nextPublicKey, + if rng == nil { + rng = rand.Reader } - - signedBlock := &pb.SignedBlock{ - Block: marshalledBlock, - NextKey: nextKey, - Signature: signature, - } - - proof := &pb.Proof{ - Content: &pb.Proof_NextSecret{ - NextSecret: nextPrivateKey.Seed(), - }, + previous := append([]*pb.SignedBlock{b.container.Authority}, b.container.Blocks...) + signedBlock, proof, err := signBlock(signer, signer.Algorithm(), marshalledBlock, block.version, nil, previous, rng) + if err != nil { + return nil, err } // clone container and append new marshalled block and public key container := &pb.Biscuit{ + RootKeyId: b.container.RootKeyId, Authority: b.container.Authority, Blocks: append([]*pb.SignedBlock{}, b.container.Blocks...), Proof: proof, @@ -229,10 +360,11 @@ func (b *Biscuit) Append(rng io.Reader, block *Block) (*Biscuit, error) { container.Blocks = append(container.Blocks, signedBlock) return &Biscuit{ - authority: authority, - blocks: blocks, - symbols: symbols, - container: container, + authority: authority, + blocks: blocks, + symbols: symbols, + publicKeys: publicKeys, + container: container, }, nil } @@ -241,17 +373,11 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) { return nil, errors.New("biscuit: token is already sealed") } - privateKey := b.container.Proof.GetNextSecret() - if privateKey == nil { - return nil, errors.New("biscuit: token is already sealed") - } - - if len(privateKey) != 32 { - return nil, ErrInvalidKeySize + signer, err := b.nextSigner() + if err != nil { + return nil, fmt.Errorf("biscuit: seal failed: %w", err) } - privateKey = ed25519.NewKeyFromSeed(privateKey) - // clone biscuit fields and append new block authority := new(Block) *authority = *b.authority @@ -262,21 +388,11 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) { *blocks[i] = *oldBlock } - var lastBlock *pb.SignedBlock - if len(b.blocks) == 0 { - lastBlock = b.container.Authority - } else { - lastBlock = b.container.Blocks[len(b.blocks)-1] + signature, err := signer.Sign(sealSignaturePayload(b.lastSignedBlock())) + if err != nil { + return nil, err } - toSignAlgorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(toSignAlgorithm[0:], uint32(lastBlock.NextKey.Algorithm.Number())) - toSign := append(lastBlock.Block[:], toSignAlgorithm...) - toSign = append(toSign, lastBlock.NextKey.Key[:]...) - toSign = append(toSign, lastBlock.Signature[:]...) - - signature := ed25519.Sign(privateKey, toSign) - proof := &pb.Proof{ Content: &pb.Proof_FinalSignature{ FinalSignature: signature, @@ -285,6 +401,7 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) { // clone container and append new marshalled block and public key container := &pb.Biscuit{ + RootKeyId: b.container.RootKeyId, Authority: b.container.Authority, Blocks: append([]*pb.SignedBlock{}, b.container.Blocks...), Proof: proof, @@ -293,23 +410,65 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) { symbols := b.symbols.Clone() return &Biscuit{ - authority: authority, - blocks: blocks, - symbols: symbols, - container: container, + authority: authority, + blocks: blocks, + symbols: symbols, + publicKeys: b.publicKeys, + container: container, }, nil } type ( + // A RootKeyProvider selects the root public key to verify the signatures of a biscuit with, + // from the root key ID embedded in the biscuit when it was created, if any. The key is an + // ed25519.PublicKey or an *ecdsa.PublicKey on the P-256 curve. If the provider doesn't + // recognize the ID or can't find the public key, or no ID is supplied and there is no default + // public key available, it should return an error satisfying + // errors.Is(err, ErrNoPublicKeyAvailable). + RootKeyProvider func(keyID *uint32) (stdcrypto.PublicKey, error) + // A PublickKeyByIDProjection inspects an optional ID for a public key and returns the // corresponding public key, if any. If it doesn't recognize the ID or can't find the public // key, or no ID is supplied and there is no default public key available, it should return an // error satisfying errors.Is(err, ErrNoPublicKeyAvailable). + // + // Deprecated: use [RootKeyProvider], which supports every key algorithm of the specification. PublickKeyByIDProjection func(*uint32) (ed25519.PublicKey, error) ) +// RootKey supplies one public key to use as the root key with which to verify the signatures on a +// biscuit's blocks, whatever root key ID the biscuit carries. The key is an ed25519.PublicKey or +// an *ecdsa.PublicKey on the P-256 curve. +func RootKey(key stdcrypto.PublicKey) RootKeyProvider { + return func(*uint32) (stdcrypto.PublicKey, error) { + return key, nil + } +} + +// RootKeysByID supplies a mapping to public keys from their corresponding IDs, used to select +// which public key to use to verify the signatures on a biscuit's blocks based on the key ID +// embedded within the biscuit when it was created. If the biscuit has no key ID available, this +// function selects defaultKey instead, when it is not nil. If no public key is available—whether +// for the biscuit's embedded key ID or a default key when no such ID is present—it returns +// [ErrNoPublicKeyAvailable]. The keys are ed25519.PublicKey or *ecdsa.PublicKey on the P-256 +// curve. +func RootKeysByID(keysByID map[uint32]stdcrypto.PublicKey, defaultKey stdcrypto.PublicKey) RootKeyProvider { + return func(id *uint32) (stdcrypto.PublicKey, error) { + if id == nil { + if defaultKey != nil { + return defaultKey, nil + } + } else if key, ok := keysByID[*id]; ok { + return key, nil + } + return nil, ErrNoPublicKeyAvailable + } +} + // WithSingularRootPublicKey supplies one public key to use as the root key with which to verify the // signatures on a biscuit's blocks. +// +// Deprecated: use [RootKey], which also accepts *ecdsa.PublicKey. func WithSingularRootPublicKey(key ed25519.PublicKey) PublickKeyByIDProjection { return func(*uint32) (ed25519.PublicKey, error) { return key, nil @@ -322,6 +481,8 @@ func WithSingularRootPublicKey(key ed25519.PublicKey) PublickKeyByIDProjection { // function selects the optional default key instead. If no public key is available—whether for the // biscuit's embedded key ID or a default key when no such ID is present—it returns // [ErrNoPublicKeyAvailable]. +// +// Deprecated: use [RootKeysByID], which also accepts *ecdsa.PublicKey. func WithRootPublicKeys(keysByID map[uint32]ed25519.PublicKey, defaultKey *ed25519.PublicKey) PublickKeyByIDProjection { return func(id *uint32) (ed25519.PublicKey, error) { if id == nil { @@ -335,113 +496,115 @@ func WithRootPublicKeys(keysByID map[uint32]ed25519.PublicKey, defaultKey *ed255 } } -func (b *Biscuit) authorizerFor(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { - currentKey := root - - // for now we only support Ed25519 - if *b.container.Authority.NextKey.Algorithm != pb.PublicKey_Ed25519 { - return nil, ErrUnsupportedAlgorithm +func (b *Biscuit) authorizerFor(root stdcrypto.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { + verifier, err := crypto.NewVerifier(root) + if err != nil { + return nil, err } - - algorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(algorithm[0:], uint32(b.container.Authority.NextKey.Algorithm.Number())) - - toVerify := append(b.container.Authority.Block[:], algorithm...) - toVerify = append(toVerify, b.container.Authority.NextKey.Key[:]...) - - if ok := ed25519.Verify(currentKey, toVerify, b.container.Authority.Signature); !ok { - return nil, ErrInvalidSignature + if err := b.verifySignatures(verifier); err != nil { + return nil, err } + return NewVerifier(b, opts...) +} - currentKey = b.container.Authority.NextKey.Key - if len(currentKey) != 32 { - return nil, ErrInvalidKeySize +// verifySignatures checks the chain of block signatures starting from the +// root key, then the proof: the next secret must match the last next key, or +// the final signature must verify with it. +func (b *Biscuit) verifySignatures(root crypto.Verifier) error { + authority := b.container.GetAuthority() + if err := root.Verify(blockSignaturePayload(authority, nil), authority.GetSignature()); err != nil { + return err } + current, err := crypto.ParseVerifier(authority.GetNextKey().GetAlgorithm(), authority.GetNextKey().GetKey()) + if err != nil { + return err + } + previousSignature := authority.GetSignature() - for _, block := range b.container.Blocks { - if *block.NextKey.Algorithm != pb.PublicKey_Ed25519 { - return nil, ErrUnsupportedAlgorithm + for _, block := range b.container.GetBlocks() { + if err := current.Verify(blockSignaturePayload(block, previousSignature), block.GetSignature()); err != nil { + return err } - - algorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(algorithm[0:], uint32(block.NextKey.Algorithm.Number())) - toVerify := append(block.Block[:], algorithm...) - toVerify = append(toVerify, block.NextKey.Key[:]...) - - if ok := ed25519.Verify(currentKey, toVerify, block.Signature); !ok { - return nil, ErrInvalidSignature + if block.GetExternalSignature() != nil { + if err := verifyExternalSignature(block, previousSignature); err != nil { + return err + } } - - currentKey = block.NextKey.Key - if len(currentKey) != 32 { - return nil, ErrInvalidKeySize + if current, err = crypto.ParseVerifier(block.GetNextKey().GetAlgorithm(), block.GetNextKey().GetKey()); err != nil { + return err } + previousSignature = block.GetSignature() } - switch { - case b.container.Proof.GetNextSecret() != nil: - { - privateKey := b.container.Proof.GetNextSecret() - if privateKey == nil { - return nil, errors.New("biscuit: sealed token verification not implemented") - } - - publicKey := ed25519.NewKeyFromSeed(privateKey).Public() - if !bytes.Equal(currentKey, publicKey.(ed25519.PublicKey)) { - return nil, errors.New("biscuit: invalid last signature") - } + switch proof := b.container.GetProof().GetContent().(type) { + case *pb.Proof_NextSecret: + next, err := crypto.ParseSigner(current.Algorithm(), proof.NextSecret) + if err != nil { + return err } - case b.container.Proof.GetFinalSignature() != nil: - { - signature := b.container.Proof.GetFinalSignature() - var lastBlock *pb.SignedBlock - if len(b.blocks) == 0 { - lastBlock = b.container.Authority - } else { - lastBlock = b.container.Blocks[len(b.blocks)-1] - } - - algorithm := make([]byte, 4) - binary.LittleEndian.PutUint32(algorithm[0:], uint32(lastBlock.NextKey.Algorithm.Number())) - toVerify := append(lastBlock.Block[:], algorithm...) - toVerify = append(toVerify, lastBlock.NextKey.Key[:]...) - toVerify = append(toVerify, lastBlock.Signature[:]...) - - if ok := ed25519.Verify(currentKey, toVerify, signature); !ok { - return nil, errors.New("biscuit: invalid last signature") - } + if !bytes.Equal(current.PublicKey(), next.PublicKey()) { + return errors.New("biscuit: invalid last signature") + } + case *pb.Proof_FinalSignature: + if err := current.Verify(sealSignaturePayload(b.lastSignedBlock()), proof.FinalSignature); err != nil { + return errors.New("biscuit: invalid last signature") } default: - return nil, errors.New("biscuit: cannot find proof") + return errors.New("biscuit: cannot find proof") } + return nil +} - return NewVerifier(b, opts...) +// AuthorizerFrom selects from the supplied provider a root public key to use to verify the +// signatures on the biscuit's blocks, returning an error satisfying +// errors.Is(err, ErrNoPublicKeyAvailable) if no such public key is available. If the signatures +// are valid, it creates an [Authorizer], which can then test the authorization policies and accept +// or refuse the request. +func (b *Biscuit) AuthorizerFrom(provider RootKeyProvider, opts ...AuthorizerOption) (Authorizer, error) { + if provider == nil { + return nil, errors.New("root public key provider must not be nil") + } + rootPublicKey, err := provider(b.RootKeyID()) + if err != nil { + return nil, fmt.Errorf("choosing root public key: %w", err) + } + if rootPublicKey == nil { + return nil, ErrNoPublicKeyAvailable + } + if key, ok := rootPublicKey.(ed25519.PublicKey); ok && len(key) == 0 { + return nil, ErrNoPublicKeyAvailable + } + return b.authorizerFor(rootPublicKey, opts...) } // AuthorizerFor selects from the supplied source a root public key to use to verify the signatures // on the biscuit's blocks, returning an error satisfying errors.Is(err, ErrNoPublicKeyAvailable) if // no such public key is available. If the signatures are valid, it creates an [Authorizer], which // can then test the authorization policies and accept or refuse the request. +// +// Deprecated: use [Biscuit.AuthorizerFrom], which supports every key algorithm of the +// specification. func (b *Biscuit) AuthorizerFor(keySource PublickKeyByIDProjection, opts ...AuthorizerOption) (Authorizer, error) { if keySource == nil { return nil, errors.New("root public key source must not be nil") } - rootPublicKey, err := keySource(b.RootKeyID()) - if err != nil { - return nil, fmt.Errorf("choosing root public key: %w", err) - } - if len(rootPublicKey) == 0 { - return nil, ErrNoPublicKeyAvailable - } - return b.authorizerFor(rootPublicKey, opts...) + return b.AuthorizerFrom(func(id *uint32) (stdcrypto.PublicKey, error) { + key, err := keySource(id) + if err != nil { + return nil, err + } + if len(key) == 0 { + return nil, ErrNoPublicKeyAvailable + } + return key, nil + }, opts...) } -// TODO: Add "Deprecated" note to the "(*Biscuit).Authorizer" method, recommending use of -// "(*Biscuit).AuthorizerFor" instead. Wait until after we release the module with the latter -// available, per https://go.dev/wiki/Deprecated. - // Authorizer checks the signature and creates an [Authorizer]. The Authorizer can then test the // authorizaion policies and accept or refuse the request. +// +// Deprecated: use [Biscuit.AuthorizerFrom] with [RootKey], which also accepts *ecdsa.PublicKey +// and honors the root key ID through [RootKeysByID]. func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { return b.authorizerFor(root, opts...) } @@ -539,7 +702,7 @@ func (b *Biscuit) RootKeyID() *uint32 { func (b *Biscuit) String() string { blocks := make([]string, len(b.blocks)) for i, block := range b.blocks { - blocks[i] = block.String(b.symbols) + blocks[i] = block.String(block.symbolTable(b.symbols)) } return fmt.Sprintf(` @@ -557,51 +720,11 @@ Biscuit { func (b *Biscuit) Code() []string { blocks := make([]string, len(b.blocks)) for i, block := range b.blocks { - blocks[i] = block.Code(b.symbols) + blocks[i] = block.Code(block.symbolTable(b.symbols)) } return blocks } -/* -func (b *Biscuit) checkRootKey(root ed25519.PublicKey) error { - if len(b.container.Keys) == 0 { - return ErrEmptyKeys - } - if !bytes.Equal(b.container.Keys[0], root.Bytes()) { - return ErrUnknownPublicKey - } - - return nil -}*/ - -func (b *Biscuit) generateWorld(symbols *datalog.SymbolTable) (*datalog.World, error) { - world := datalog.NewWorld() - - for _, fact := range *b.authority.facts { - world.AddFact(fact) - } - - for _, rule := range b.authority.rules { - world.AddRule(rule) - } - - for _, block := range b.blocks { - for _, fact := range *block.facts { - world.AddFact(fact) - } - - for _, rule := range block.rules { - world.AddRule(rule) - } - } - - if err := world.Run(symbols); err != nil { - return nil, err - } - - return world, nil -} - func (b *Biscuit) RevocationIds() [][]byte { result := make([][]byte, 0, len(b.blocks)+1) result = append(result, b.container.Authority.Signature) diff --git a/biscuit_test.go b/biscuit_test.go index aab7117c..d5d125ae 100644 --- a/biscuit_test.go +++ b/biscuit_test.go @@ -4,13 +4,16 @@ package biscuit import ( + stdcrypto "crypto" "crypto/ed25519" "crypto/rand" "fmt" "testing" "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/eclipse-biscuit/biscuit-go/v2/pb" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) func TestBiscuit(t *testing.T) { @@ -103,7 +106,7 @@ func TestBiscuit(t *testing.T) { b3deser, err := Unmarshal(b3ser) require.NoError(t, err) - v3, err := b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + v3, err := b3deser.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}}) @@ -111,14 +114,14 @@ func TestBiscuit(t *testing.T) { v3.AddPolicy(DefaultAllowPolicy) require.NoError(t, v3.Authorize()) - v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + v3, err = b3deser.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file2")}}}) v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("read")}}}) v3.AddPolicy(DefaultAllowPolicy) require.Error(t, v3.Authorize()) - v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + v3, err = b3deser.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}}) v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("write")}}}) @@ -179,7 +182,7 @@ func TestSealedBiscuit(t *testing.T) { b2deser, err := Unmarshal(b2ser) require.NoError(t, err) - _, err = b2deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + _, err = b2deser.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) } @@ -262,6 +265,200 @@ func TestBiscuitRules(t *testing.T) { } // A check that compares two sets of byte arrays used to crash the authorizer. +// The root key id identifies the verification key for the whole token; it +// must survive attenuation and sealing. +func TestRootKeyIDSurvivesAppendAndSeal(t *testing.T) { + _, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + b1, err := NewBuilder(privateRoot, WithRootKeyID(42)).Build() + require.NoError(t, err) + + b2, err := b1.Append(rand.Reader, b1.CreateBlock().Build()) + require.NoError(t, err) + require.NotNil(t, b2.RootKeyID()) + require.EqualValues(t, 42, *b2.RootKeyID()) + + sealed, err := b2.Seal(rand.Reader) + require.NoError(t, err) + require.NotNil(t, sealed.RootKeyID()) + require.EqualValues(t, 42, *sealed.RootKeyID()) + + deser, err := Unmarshal(mustSerialize(t, sealed)) + require.NoError(t, err) + require.EqualValues(t, 42, *deser.RootKeyID()) +} + +// check all passes only when every matching operation is allowed. +func TestBiscuitCheckAll(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{ + Predicate: Predicate{Name: "allowed_operations", IDs: []Term{Set{String("A"), String("B")}}}, + })) + require.NoError(t, builder.AddAuthorityCheck(Check{ + Kind: CheckKindAll, + Queries: []Rule{{ + Head: Predicate{Name: "allowed", IDs: []Term{Variable("op")}}, + Body: []Predicate{ + {Name: "operation", IDs: []Term{Variable("op")}}, + {Name: "allowed_operations", IDs: []Term{Variable("allowed")}}, + }, + Expressions: []Expression{{Value{Variable("allowed")}, Value{Variable("op")}, BinaryContains}}, + }}, + })) + b, err := builder.Build() + require.NoError(t, err) + // check all needs a datalog v3.1 block. + require.EqualValues(t, 4, b.authority.version) + + deser, err := Unmarshal(mustSerialize(t, b)) + require.NoError(t, err) + require.Contains(t, deser.String(), "check all operation($op), allowed_operations($allowed), $allowed.contains($op)") + + authorize := func(ops ...string) error { + ab, err := deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + for _, op := range ops { + ab.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String(op)}}}) + } + ab.AddPolicy(DefaultAllowPolicy) + return ab.Authorize() + } + require.NoError(t, authorize("A", "B")) + require.ErrorContains(t, authorize("A", "invalid"), "check all") + require.ErrorContains(t, authorize(), "check all") +} + +// An expression that cannot be evaluated stops authorization with an +// execution error instead of counting as a failed check. +func TestAuthorizeExecutionError(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + overflow := Check{Queries: []Rule{{ + Head: Predicate{Name: "overflow"}, + Expressions: []Expression{{Value{Integer(9223372036854775807)}, Value{Integer(1)}, BinaryAdd, Value{Integer(0)}, BinaryEqual}}, + }}} + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityCheck(overflow)) + b, err := builder.Build() + require.NoError(t, err) + + ab, err := b.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + err = ab.Authorize() + require.ErrorIs(t, err, ErrExecution) + require.ErrorIs(t, err, datalog.ErrInt64Overflow) + + // The same from an authorizer check and from a policy, on a token + // without checks of its own. + plain, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + + ab, err = plain.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddCheck(overflow) + ab.AddPolicy(DefaultAllowPolicy) + require.ErrorIs(t, ab.Authorize(), ErrExecution) + + ab, err = plain.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(Policy{Kind: PolicyKindAllow, Queries: overflow.Queries}) + require.ErrorIs(t, ab.Authorize(), ErrExecution) +} + +// reject if passes when its query has no match; a block with one is v6. +func TestBiscuitRejectIf(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityCheck(Check{ + Kind: CheckKindReject, + Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "test", IDs: []Term{Variable("t")}}}, + Expressions: []Expression{{Value{Variable("t")}}}, + }}, + })) + b, err := builder.Build() + require.NoError(t, err) + require.EqualValues(t, 6, b.authority.version) + + deser, err := Unmarshal(mustSerialize(t, b)) + require.NoError(t, err) + require.Contains(t, deser.String(), "reject if test($t), $t") + + authorize := func(value bool) error { + ab, err := deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddFact(Fact{Predicate: Predicate{Name: "test", IDs: []Term{Bool(value)}}}) + ab.AddPolicy(DefaultAllowPolicy) + return ab.Authorize() + } + require.NoError(t, authorize(false)) + require.ErrorContains(t, authorize(true), "reject if") +} + +// null round-trips through a token, equals only itself under ==, and makes +// the block v6. +func TestBiscuitNull(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{Predicate{Name: "fact", IDs: []Term{Null{}, Integer(1)}}})) + require.NoError(t, builder.AddAuthorityCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "fact", IDs: []Term{Null{}, Variable("v")}}}, + Expressions: []Expression{{Value{Variable("v")}, Value{Null{}}, BinaryHeterogeneousNotEqual}}, + }}})) + b, err := builder.Build() + require.NoError(t, err) + require.EqualValues(t, 6, b.authority.version) + + deser, err := Unmarshal(mustSerialize(t, b)) + require.NoError(t, err) + require.Contains(t, deser.String(), "fact(null, 1)") + require.Contains(t, deser.String(), "check if fact(null, $v), $v != null") + + ab, err := deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + require.NoError(t, ab.Authorize()) + + nullOnly, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + require.EqualValues(t, 3, nullOnly.authority.version) +} + +// Blocks that do not use v3.1 features keep version 3, and a v3 block +// declaring a check kind is rejected. +func TestBlockVersionFromContent(t *testing.T) { + _, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + b, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + require.EqualValues(t, 3, b.authority.version) + + kind := pb.Check_All + _, err = protoBlockToTokenBlock(&pb.Block{ + Version: proto.Uint32(3), + Checks: []*pb.Check{{Kind: &kind}}, + }, nil) + require.ErrorContains(t, err, "block version 3 uses features of version 4") + + _, err = protoBlockToTokenBlock(&pb.Block{ + Version: proto.Uint32(3), + Scope: []*pb.Scope{{Content: &pb.Scope_ScopeType_{ScopeType: pb.Scope_Previous}}}, + }, nil) + require.ErrorContains(t, err, "block version 3 uses features of version 4") + + _, err = protoBlockToTokenBlock(&pb.Block{ + Version: proto.Uint32(4), + Scope: []*pb.Scope{{Content: &pb.Scope_PublicKey{PublicKey: 0}}}, + }, nil) + require.ErrorContains(t, err, "public key index 0 out of the key table") +} + func TestBiscuitBytesSetEquality(t *testing.T) { rng := rand.Reader publicRoot, privateRoot, _ := ed25519.GenerateKey(rng) @@ -292,7 +489,7 @@ func TestBiscuitBytesSetEquality(t *testing.T) { deser, err := Unmarshal(ser) require.NoError(t, err) - v, err := deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + v, err := deser.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) v.AddPolicy(DefaultAllowPolicy) require.NoError(t, v.Authorize()) @@ -300,7 +497,7 @@ func TestBiscuitBytesSetEquality(t *testing.T) { func verifyOwner(t *testing.T, b Biscuit, publicRoot ed25519.PublicKey, owners map[string]bool) { for user, valid := range owners { - v, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + v, err := b.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) t.Run(fmt.Sprintf("verify owner %s", user), func(t *testing.T) { @@ -336,99 +533,56 @@ func TestCheckRootKey(t *testing.T) { b, err := builder.Build() require.NoError(t, err) - _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + _, err = b.AuthorizerFrom(RootKeysByID(map[uint32]stdcrypto.PublicKey{ rootKeyID: publicRoot, }, nil)) require.NoError(t, err) - _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + _, err = b.AuthorizerFrom(RootKeysByID(map[uint32]stdcrypto.PublicKey{ rootKeyID + 1: publicRoot, }, nil)) require.ErrorIs(t, err, ErrNoPublicKeyAvailable) - _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ - rootKeyID: nil, + _, err = b.AuthorizerFrom(RootKeysByID(map[uint32]stdcrypto.PublicKey{ + rootKeyID: ed25519.PublicKey(nil), }, nil)) require.ErrorIs(t, err, ErrNoPublicKeyAvailable) + _, err = b.AuthorizerFrom(nil) + require.Error(t, err) + publicNotRoot, _, _ := ed25519.GenerateKey(rng) - _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicNotRoot)) + _, err = b.AuthorizerFrom(RootKey(publicNotRoot)) require.Equal(t, ErrInvalidSignature, err) -} - -func TestGenerateWorld(t *testing.T) { - rng := rand.Reader - _, privateRoot, _ := ed25519.GenerateKey(rng) - - build := NewBuilder(privateRoot) - - authorityFact1 := Fact{Predicate: Predicate{Name: "fact1", IDs: []Term{String("file1")}}} - authorityFact2 := Fact{Predicate: Predicate{Name: "fact2", IDs: []Term{String("file2")}}} - - authorityRule1 := Rule{ - Head: Predicate{Name: "right", IDs: []Term{Variable("1"), String("read")}}, - Body: []Predicate{ - {Name: "resource", IDs: []Term{Variable("1")}}, - {Name: "owner", IDs: []Term{Variable("0"), Variable("1")}}, - }, - } - authorityRule2 := Rule{ - Head: Predicate{Name: "right", IDs: []Term{Variable("1"), String("write")}}, - Body: []Predicate{ - {Name: "resource", IDs: []Term{Variable("1")}}, - {Name: "owner", IDs: []Term{Variable("0"), Variable("1")}}, - }, - } - - require.NoError(t, build.AddAuthorityFact(authorityFact1)) - require.NoError(t, build.AddAuthorityFact(authorityFact2)) - require.NoError(t, build.AddAuthorityRule(authorityRule1)) - require.NoError(t, build.AddAuthorityRule(authorityRule2)) - - b, err := build.Build() - require.NoError(t, err) - StringTable := (build.(*builderOptions)).symbols - world, err := b.generateWorld(defaultSymbolTable.Clone()) - require.NoError(t, err) + t.Run("deprecated", func(t *testing.T) { + _, err := b.Authorizer(publicRoot) + require.NoError(t, err) - expectedWorld := datalog.NewWorld() - expectedWorld.AddFact(authorityFact1.convert(StringTable)) - expectedWorld.AddFact(authorityFact2.convert(StringTable)) - expectedWorld.AddRule(authorityRule1.convert(StringTable)) - expectedWorld.AddRule(authorityRule2.convert(StringTable)) - require.Equal(t, expectedWorld, world) + _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + require.NoError(t, err) - blockBuild := b.CreateBlock() - blockRule := Rule{ - Head: Predicate{Name: "blockRule", IDs: []Term{Variable("1")}}, - Body: []Predicate{ - {Name: "resource", IDs: []Term{Variable("1")}}, - {Name: "owner", IDs: []Term{String("alice"), Variable("1")}}, - }, - } - require.NoError(t, blockBuild.AddRule(blockRule)) + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID: publicRoot, + }, nil)) + require.NoError(t, err) - blockFact := Fact{Predicate{Name: "resource", IDs: []Term{String("file1")}}} - require.NoError(t, blockBuild.AddFact(blockFact)) + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID + 1: publicRoot, + }, nil)) + require.ErrorIs(t, err, ErrNoPublicKeyAvailable) - b2, err := b.Append(rng, blockBuild.Build()) - require.NoError(t, err) + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID: nil, + }, nil)) + require.ErrorIs(t, err, ErrNoPublicKeyAvailable) - allStrings := append(*StringTable, *(blockBuild.(*blockBuilder)).symbols...) - world, err = b2.generateWorld(&allStrings) - require.NoError(t, err) + _, err = b.AuthorizerFor(nil) + require.Error(t, err) - expectedWorld = datalog.NewWorld() - expectedWorld.AddFact(authorityFact1.convert(&allStrings)) - expectedWorld.AddFact(authorityFact2.convert(&allStrings)) - expectedWorld.AddFact(blockFact.convert(&allStrings)) - expectedWorld.AddRule(authorityRule1.convert(&allStrings)) - expectedWorld.AddRule(authorityRule2.convert(&allStrings)) - expectedWorld.AddRule( - blockRule.convert(&allStrings), - ) - require.Equal(t, expectedWorld, world) + _, err = b.Authorizer(publicNotRoot) + require.Equal(t, ErrInvalidSignature, err) + }) } func TestAppendErrors(t *testing.T) { @@ -487,11 +641,11 @@ func TestBiscuitVerifyErrors(t *testing.T) { b, err := builder.Build() require.NoError(t, err) - _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + _, err = b.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) publicTest, _, _ := ed25519.GenerateKey(rng) - _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicTest)) + _, err = b.AuthorizerFrom(RootKey(publicTest)) require.Error(t, err) } @@ -644,7 +798,7 @@ func TestInvalidRuleGeneration(t *testing.T) { require.NoError(t, err) t.Log(b.String()) - verifier, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) + verifier, err := b.AuthorizerFrom(RootKey(publicRoot)) require.NoError(t, err) verifier.AddFact(Fact{Predicate: Predicate{ @@ -656,3 +810,51 @@ func TestInvalidRuleGeneration(t *testing.T) { t.Log(verifier.PrintWorld()) require.Error(t, err) } + +func TestBiscuitExternFuncs(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{Predicate{Name: "user", IDs: []Term{String("alice")}}})) + require.NoError(t, builder.AddAuthorityCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "user", IDs: []Term{Variable("u")}}}, + Expressions: []Expression{ + {Value{Variable("u")}, ExternUnary{Name: "known"}}, + {Value{Variable("u")}, Value{String("alice")}, ExternBinary{Name: "same"}, Value{String("yes")}, BinaryHeterogeneousEqual}, + }, + }}})) + b, err := builder.Build() + require.NoError(t, err) + require.EqualValues(t, 6, b.authority.version) + + deser, err := Unmarshal(mustSerialize(t, b)) + require.NoError(t, err) + require.Contains(t, deser.String(), `check if user($u), $u.extern::known(), $u.extern::same("alice") == "yes"`) + + funcs := map[string]ExternFunc{ + "known": func(left Term, right Term) (Term, error) { + require.Nil(t, right) + return Bool(left == String("alice")), nil + }, + "same": func(left Term, right Term) (Term, error) { + if left == right { + return String("yes"), nil + } + return String("no"), nil + }, + } + + ab, err := deser.AuthorizerFrom(RootKey(publicRoot), WithExternFuncs(funcs)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + require.NoError(t, ab.Authorize()) + + // Without the functions the check fails with an execution error. + ab, err = deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + err = ab.Authorize() + require.ErrorIs(t, err, ErrExecution) + require.ErrorIs(t, err, datalog.ErrUndefinedExtern) +} diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 00000000..a418097f --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,16 @@ +version: v2 +managed: + enabled: true + override: + - file_option: go_package_prefix + value: github.com/eclipse-biscuit/biscuit-go/v2/pb + +# All of these plugins are executed locally, so `protoc-gen-$name` executable +# has to exist in your `$PATH` for these to work. +# +# As long as you're using our generation scripts/make targets, these +# dependencies should be installed for you. +plugins: + - local: protoc-gen-go + out: pb + opt: paths=source_relative diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 00000000..f973f029 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: proto/v0 +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/builder.go b/builder.go index 9e8b40a9..444fb970 100644 --- a/builder.go +++ b/builder.go @@ -4,11 +4,15 @@ package biscuit import ( + stdcrypto "crypto" "crypto/ed25519" "errors" + "fmt" "io" + "slices" "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/eclipse-biscuit/biscuit-go/v2/internal/crypto" "github.com/eclipse-biscuit/biscuit-go/v2/pb" //"github.com/eclipse-biscuit/biscuit-go/sig" @@ -25,13 +29,16 @@ type Builder interface { AddAuthorityFact(fact Fact) error AddAuthorityRule(rule Rule) error AddAuthorityCheck(check Check) error + // AddAuthorityScope sets the default scope of the authority block's + // rules and checks; see Scope. + AddAuthorityScope(scope Scope) SetContext(string) Build() (*Biscuit, error) } type builderOptions struct { rng io.Reader - rootKey ed25519.PrivateKey + rootKey crypto.Signer rootKeyID *uint32 symbolsStart int @@ -39,6 +46,7 @@ type builderOptions struct { facts *datalog.FactSet rules []datalog.Rule checks []datalog.Check + scopes []datalog.Scope context string } @@ -60,7 +68,24 @@ func WithSymbols(symbols *datalog.SymbolTable) builderOption { return symbolsOption{symbols} } +// NewBuilder creates a Builder signing the authority block with an Ed25519 +// root key. Use NewBuilderWithSigner for other algorithms. func NewBuilder(root ed25519.PrivateKey, opts ...builderOption) Builder { + return NewBuilderWithSigner(root, opts...) +} + +// NewBuilderWithSigner creates a Builder signing the authority block with the +// given root key: an ed25519.PrivateKey or an *ecdsa.PrivateKey on the P-256 +// curve. Any other key makes Build return ErrUnsupportedAlgorithm. +func NewBuilderWithSigner(root stdcrypto.Signer, opts ...builderOption) Builder { + signer, err := crypto.NewSigner(root) + if err != nil { + signer = nil + } + return newBuilder(signer, opts...) +} + +func newBuilder(root crypto.Signer, opts ...builderOption) Builder { b := &builderOptions{ rootKey: root, symbols: defaultSymbolTable.Clone(), @@ -117,11 +142,18 @@ func (b *builderOptions) AddAuthorityCheck(check Check) error { return nil } +func (b *builderOptions) AddAuthorityScope(scope Scope) { + b.scopes = append(b.scopes, scope) +} + func (b *builderOptions) SetContext(context string) { b.context = context } func (b *builderOptions) Build() (*Biscuit, error) { + if b.rootKey == nil { + return nil, ErrUnsupportedAlgorithm + } opts := make([]biscuitOption, 0, 2) if v := b.rng; v != nil { opts = append(opts, WithRNG(b.rng)) @@ -133,12 +165,14 @@ func (b *builderOptions) Build() (*Biscuit, error) { b.rootKey, b.symbols, &Block{ - symbols: b.symbols.SplitOff(b.symbolsStart), - facts: b.facts, - rules: b.rules, - checks: b.checks, - context: b.context, - version: MaxSchemaVersion, + symbols: b.symbols.SplitOff(b.symbolsStart), + facts: b.facts, + rules: b.rules, + checks: b.checks, + scopes: b.scopes, + publicKeys: scopeKeys(b.scopes, b.rules, b.checks), + context: b.context, + version: schemaVersion(*b.facts, b.scopes, b.rules, b.checks), }, opts...) } @@ -163,11 +197,8 @@ func (u *Unmarshaler) Unmarshal(serialized []byte) (*Biscuit, error) { return nil, err } - if len(container.Authority.NextKey.Key) != 32 { - return nil, ErrInvalidKeySize - } - if len(container.Authority.Signature) != 64 { - return nil, ErrInvalidSignatureSize + if err := checkSignedBlockFormat(container.Authority); err != nil { + return nil, err } pbAuthority := new(pb.Block) @@ -175,20 +206,19 @@ func (u *Unmarshaler) Unmarshal(serialized []byte) (*Biscuit, error) { return nil, err } - authority, err := protoBlockToTokenBlock(pbAuthority) + var publicKeys publicKeyTable + authority, err := protoBlockToTokenBlock(pbAuthority, publicKeys) if err != nil { return nil, err } symbols.Extend(authority.symbols) + publicKeys = publicKeys.with(authority.publicKeys...) blocks := make([]*Block, len(container.Blocks)) for i, sb := range container.Blocks { - if len(sb.NextKey.Key) != 32 { - return nil, ErrInvalidKeySize - } - if len(sb.Signature) != 64 { - return nil, ErrInvalidSignatureSize + if err := checkSignedBlockFormat(sb); err != nil { + return nil, err } pbBlock := new(pb.Block) @@ -196,19 +226,31 @@ func (u *Unmarshaler) Unmarshal(serialized []byte) (*Biscuit, error) { return nil, err } - block, err := protoBlockToTokenBlock(pbBlock) + if sb.ExternalSignature == nil { + block, err := protoBlockToTokenBlock(pbBlock, publicKeys) + if err != nil { + return nil, err + } + blocks[i] = block + publicKeys = publicKeys.with(block.publicKeys...) + symbols.Extend(block.symbols) + continue + } + + // A third-party block has its own tables and leaves the token's alone. + block, err := thirdPartyBlock(pbBlock, sb.ExternalSignature) if err != nil { return nil, err } blocks[i] = block - symbols.Extend(blocks[i].symbols) } return &Biscuit{ - authority: authority, - symbols: symbols, - blocks: blocks, - container: container, + authority: authority, + symbols: symbols, + publicKeys: publicKeys, + blocks: blocks, + container: container, }, nil } @@ -217,6 +259,9 @@ type BlockBuilder interface { AddFact(fact Fact) error AddRule(rule Rule) error AddCheck(check Check) error + // AddScope sets the default scope of the block's rules and checks; see + // Scope. + AddScope(scope Scope) SetContext(string) Build() *Block } @@ -224,18 +269,26 @@ type BlockBuilder interface { type blockBuilder struct { symbolsStart int symbols *datalog.SymbolTable - facts *datalog.FactSet - rules []datalog.Rule - checks []datalog.Check - context string + // publicKeys is the key table of the token the block is appended to. + publicKeys publicKeyTable + facts *datalog.FactSet + rules []datalog.Rule + checks []datalog.Check + scopes []datalog.Scope + context string } var _ BlockBuilder = (*blockBuilder)(nil) func NewBlockBuilder(baseSymbols *datalog.SymbolTable) BlockBuilder { + return newBlockBuilder(baseSymbols, nil) +} + +func newBlockBuilder(baseSymbols *datalog.SymbolTable, publicKeys publicKeyTable) BlockBuilder { return &blockBuilder{ symbolsStart: baseSymbols.Len(), symbols: baseSymbols, + publicKeys: publicKeys, facts: new(datalog.FactSet), } } @@ -259,6 +312,9 @@ func (b *blockBuilder) AddBlock(block ParsedBlock) error { return err } } + for _, s := range block.Scopes { + b.AddScope(s) + } return nil } @@ -286,6 +342,10 @@ func (b *blockBuilder) AddCheck(check Check) error { return nil } +func (b *blockBuilder) AddScope(scope Scope) { + b.scopes = append(b.scopes, scope) +} + func (b *blockBuilder) SetContext(context string) { b.context = context } @@ -302,12 +362,53 @@ func (b *blockBuilder) Build() *Block { checks := make([]datalog.Check, len(b.checks)) copy(checks, b.checks) + // Only the keys the token does not have yet are carried by the block. + var publicKeys []datalog.PublicKey + for _, k := range scopeKeys(b.scopes, rules, checks) { + if _, known := b.publicKeys.index(k); !known { + publicKeys = append(publicKeys, k) + } + } + return &Block{ - symbols: b.symbols.Clone(), - facts: &facts, - rules: rules, - checks: checks, - context: b.context, - version: MaxSchemaVersion, + symbols: b.symbols.Clone(), + facts: &facts, + rules: rules, + checks: checks, + scopes: slices.Clone(b.scopes), + publicKeys: publicKeys, + context: b.context, + version: schemaVersion(facts, b.scopes, rules, checks), + } +} + +// checkSignedBlockFormat rejects a block whose next key or signature cannot be +// the encoding of its algorithm, before any verification. Ed25519 has fixed +// sizes; ECDSA signatures are DER and only the key is checked. +func checkSignedBlockFormat(sb *pb.SignedBlock) error { + if _, err := crypto.ParseVerifier(sb.GetNextKey().GetAlgorithm(), sb.GetNextKey().GetKey()); err != nil { + return err + } + if sb.GetNextKey().GetAlgorithm() == pb.PublicKey_Ed25519 && len(sb.GetSignature()) != ed25519.SignatureSize { + return ErrInvalidSignatureSize + } + return nil +} + +// thirdPartyBlock converts a block signed by a third party: its scopes refer +// to its own key table, and its version must be at least datalog v3.2. +func thirdPartyBlock(pbBlock *pb.Block, ext *pb.ExternalSignature) (*Block, error) { + if pbBlock.GetVersion() < blockVersion3_2 { + return nil, fmt.Errorf("biscuit: failed to convert proto block to token block: third-party blocks require block version %d, got %d", blockVersion3_2, pbBlock.GetVersion()) + } + block, err := protoBlockToTokenBlock(pbBlock, nil) + if err != nil { + return nil, err + } + externalKey, err := protoPublicKeyToTokenPublicKey(ext.GetPublicKey()) + if err != nil { + return nil, err } + block.externalKey = &externalKey + return block, nil } diff --git a/converters.go b/converters.go index 2d470c83..c01b081c 100644 --- a/converters.go +++ b/converters.go @@ -11,19 +11,29 @@ import ( "google.golang.org/protobuf/proto" ) -func tokenBlockToProtoBlock(input *Block) (*pb.Block, error) { +// tokenBlockToProtoBlock serializes a block. keys is the key table of the +// token before this block; the keys the block introduces come after. +func tokenBlockToProtoBlock(input *Block, keys publicKeyTable) (*pb.Block, error) { + keys = keys.with(input.publicKeys...) out := &pb.Block{ Symbols: *input.symbols, Context: proto.String(input.context), Version: proto.Uint32(input.version), } + for _, k := range input.publicKeys { + out.PublicKeys = append(out.PublicKeys, tokenPublicKeyToProtoPublicKey(k)) + } + var err error + if out.Scope, err = tokenScopesToProtoScopes(input.scopes, keys); err != nil { + return nil, err + } facts := input.facts if facts != nil { - out.FactsV2 = make([]*pb.FactV2, len(*facts)) + out.Facts = make([]*pb.Fact, len(*facts)) var err error for i, fact := range *facts { - out.FactsV2[i], err = tokenFactToProtoFactV2(fact) + out.Facts[i], err = tokenFactToProtoFactV2(fact) if err != nil { return nil, err } @@ -32,38 +42,55 @@ func tokenBlockToProtoBlock(input *Block) (*pb.Block, error) { rules := input.rules if rules != nil { - out.RulesV2 = make([]*pb.RuleV2, len(rules)) + out.Rules = make([]*pb.Rule, len(rules)) for i, rule := range rules { - r, err := tokenRuleToProtoRuleV2(rule) + r, err := tokenRuleToProtoRuleV2(rule, keys) if err != nil { return nil, err } - out.RulesV2[i] = r + out.Rules[i] = r } } checks := input.checks if checks != nil { - out.ChecksV2 = make([]*pb.CheckV2, len(checks)) + out.Checks = make([]*pb.Check, len(checks)) for i, check := range checks { - c, err := tokenCheckToProtoCheckV2(check) + c, err := tokenCheckToProtoCheckV2(check, keys) if err != nil { return nil, err } - out.ChecksV2[i] = c + out.Checks[i] = c } } return out, nil } -func protoBlockToTokenBlock(input *pb.Block) (*Block, error) { +// protoBlockToTokenBlock deserializes a block. keys is the key table of the +// token before this block; the keys the block introduces come after. +func protoBlockToTokenBlock(input *pb.Block, keys publicKeyTable) (*Block, error) { symbols := datalog.SymbolTable(input.Symbols) var facts datalog.FactSet var rules []datalog.Rule var checks []datalog.Check + var publicKeys []datalog.PublicKey + for _, pbKey := range input.PublicKeys { + key, err := protoPublicKeyToTokenPublicKey(pbKey) + if err != nil { + return nil, fmt.Errorf("biscuit: failed to convert proto block to token block: %w", err) + } + publicKeys = append(publicKeys, key) + } + keys = keys.with(publicKeys...) + + scopes, err := protoScopesToTokenScopes(input.Scope, keys) + if err != nil { + return nil, fmt.Errorf("biscuit: failed to convert proto block to token block: %w", err) + } + if input.GetVersion() < MinSchemaVersion { return nil, fmt.Errorf( "biscuit: failed to convert proto block to token block: block version: %d < library version %d", @@ -80,12 +107,12 @@ func protoBlockToTokenBlock(input *pb.Block) (*Block, error) { } switch input.GetVersion() { - case 3: - facts = make(datalog.FactSet, len(input.FactsV2)) - rules = make([]datalog.Rule, len(input.RulesV2)) - checks = make([]datalog.Check, len(input.ChecksV2)) + case blockVersion3_0, blockVersion3_1, blockVersion3_2, blockVersion3_3: + facts = make(datalog.FactSet, len(input.Facts)) + rules = make([]datalog.Rule, len(input.Rules)) + checks = make([]datalog.Check, len(input.Checks)) - for i, pbFact := range input.FactsV2 { + for i, pbFact := range input.Facts { f, err := protoFactToTokenFactV2(pbFact) if err != nil { return nil, err @@ -93,16 +120,16 @@ func protoBlockToTokenBlock(input *pb.Block) (*Block, error) { facts[i] = *f } - for i, pbRule := range input.RulesV2 { - r, err := protoRuleToTokenRuleV2(pbRule) + for i, pbRule := range input.Rules { + r, err := protoRuleToTokenRuleV2(pbRule, keys) if err != nil { return nil, err } rules[i] = *r } - for i, pbCheck := range input.ChecksV2 { - c, err := protoCheckToTokenCheckV2(pbCheck) + for i, pbCheck := range input.Checks { + c, err := protoCheckToTokenCheckV2(pbCheck, keys) if err != nil { return nil, err } @@ -112,13 +139,23 @@ func protoBlockToTokenBlock(input *pb.Block) (*Block, error) { return nil, fmt.Errorf("biscuit: failed to convert proto block to token block: unsupported version: %d", input.GetVersion()) } + if required := schemaVersion(facts, scopes, rules, checks); input.GetVersion() < required { + return nil, fmt.Errorf( + "biscuit: failed to convert proto block to token block: block version %d uses features of version %d", + input.GetVersion(), + required, + ) + } + return &Block{ - symbols: &symbols, - facts: &facts, - rules: rules, - checks: checks, - context: input.GetContext(), - version: input.GetVersion(), + symbols: &symbols, + facts: &facts, + rules: rules, + checks: checks, + scopes: scopes, + publicKeys: publicKeys, + context: input.GetContext(), + version: input.GetVersion(), }, nil } diff --git a/converters_v2.go b/converters_v2.go index 20c7085a..27cd2196 100644 --- a/converters_v2.go +++ b/converters_v2.go @@ -12,18 +12,18 @@ import ( "github.com/eclipse-biscuit/biscuit-go/v2/pb" ) -func tokenFactToProtoFactV2(input datalog.Fact) (*pb.FactV2, error) { +func tokenFactToProtoFactV2(input datalog.Fact) (*pb.Fact, error) { pred, err := tokenPredicateToProtoPredicateV2(input.Predicate) if err != nil { return nil, err } - return &pb.FactV2{ + return &pb.Fact{ Predicate: pred, }, nil } -func protoFactToTokenFactV2(input *pb.FactV2) (*datalog.Fact, error) { +func protoFactToTokenFactV2(input *pb.Fact) (*datalog.Fact, error) { pred, err := protoPredicateToTokenPredicateV2(input.Predicate) if err != nil { return nil, err @@ -33,8 +33,8 @@ func protoFactToTokenFactV2(input *pb.FactV2) (*datalog.Fact, error) { }, nil } -func tokenPredicateToProtoPredicateV2(input datalog.Predicate) (*pb.PredicateV2, error) { - pbTerms := make([]*pb.TermV2, len(input.Terms)) +func tokenPredicateToProtoPredicateV2(input datalog.Predicate) (*pb.Predicate, error) { + pbTerms := make([]*pb.Term, len(input.Terms)) var err error for i, id := range input.Terms { pbTerms[i], err = tokenIDToProtoIDV2(id) @@ -44,13 +44,13 @@ func tokenPredicateToProtoPredicateV2(input datalog.Predicate) (*pb.PredicateV2, } nameSymbol := uint64(input.Name) - return &pb.PredicateV2{ + return &pb.Predicate{ Name: &nameSymbol, Terms: pbTerms, }, nil } -func protoPredicateToTokenPredicateV2(input *pb.PredicateV2) (*datalog.Predicate, error) { +func protoPredicateToTokenPredicateV2(input *pb.Predicate) (*datalog.Predicate, error) { Terms := make([]datalog.Term, len(input.Terms)) for i, id := range input.Terms { dlid, err := protoIDToTokenIDV2(id) @@ -68,50 +68,89 @@ func protoPredicateToTokenPredicateV2(input *pb.PredicateV2) (*datalog.Predicate }, nil } -func tokenIDToProtoIDV2(input datalog.Term) (*pb.TermV2, error) { - var pbId *pb.TermV2 +func tokenIDToProtoIDV2(input datalog.Term) (*pb.Term, error) { + var pbId *pb.Term switch input.Type() { case datalog.TermTypeString: - pbId = &pb.TermV2{ - Content: &pb.TermV2_String_{String_: uint64(input.(datalog.String))}, + pbId = &pb.Term{ + Content: &pb.Term_String_{String_: uint64(input.(datalog.String))}, } case datalog.TermTypeDate: - pbId = &pb.TermV2{ - Content: &pb.TermV2_Date{Date: uint64(input.(datalog.Date))}, + pbId = &pb.Term{ + Content: &pb.Term_Date{Date: uint64(input.(datalog.Date))}, } case datalog.TermTypeInteger: - pbId = &pb.TermV2{ - Content: &pb.TermV2_Integer{Integer: int64(input.(datalog.Integer))}, + pbId = &pb.Term{ + Content: &pb.Term_Integer{Integer: int64(input.(datalog.Integer))}, } case datalog.TermTypeVariable: - pbId = &pb.TermV2{ - Content: &pb.TermV2_Variable{Variable: uint32(input.(datalog.Variable))}, + pbId = &pb.Term{ + Content: &pb.Term_Variable{Variable: uint32(input.(datalog.Variable))}, } case datalog.TermTypeBytes: - pbId = &pb.TermV2{ - Content: &pb.TermV2_Bytes{Bytes: input.(datalog.Bytes)}, + pbId = &pb.Term{ + Content: &pb.Term_Bytes{Bytes: input.(datalog.Bytes)}, } case datalog.TermTypeBool: - pbId = &pb.TermV2{ - Content: &pb.TermV2_Bool{Bool: bool(input.(datalog.Bool))}, + pbId = &pb.Term{ + Content: &pb.Term_Bool{Bool: bool(input.(datalog.Bool))}, } - case datalog.TermTypeSet: - datalogSet := input.(datalog.Set) - if len(datalogSet) == 0 { - return nil, errors.New("biscuit: failed to convert token ID to proto ID: set cannot be empty") + case datalog.TermTypeNull: + pbId = &pb.Term{ + Content: &pb.Term_Null{Null: &pb.Empty{}}, } - - expectedEltType := datalogSet[0].Type() - switch expectedEltType { - case datalog.TermTypeVariable: - return nil, errors.New("biscuit: failed to convert token ID to proto ID: set cannot contains variable") - case datalog.TermTypeSet: - return nil, errors.New("biscuit: failed to convert token ID to proto ID: set cannot contains other sets") + case datalog.TermTypeArray: + array := input.(datalog.Array) + protoArray := make([]*pb.Term, len(array)) + for i, elt := range array { + if elt.Type() == datalog.TermTypeVariable { + return nil, errors.New("biscuit: failed to convert token ID to proto ID: array cannot contain variables") + } + protoElt, err := tokenIDToProtoIDV2(elt) + if err != nil { + return nil, err + } + protoArray[i] = protoElt } - - protoSet := make([]*pb.TermV2, 0, len(datalogSet)) - for _, datalogElt := range datalogSet { - if datalogElt.Type() != expectedEltType { + pbId = &pb.Term{ + Content: &pb.Term_Array{Array: &pb.Array{Array: protoArray}}, + } + case datalog.TermTypeMap: + m := input.(datalog.Map) + entries := make([]*pb.MapEntry, len(m)) + for i, e := range m { + key := &pb.MapKey{} + switch k := e.Key.(type) { + case datalog.Integer: + key.Content = &pb.MapKey_Integer{Integer: int64(k)} + case datalog.String: + key.Content = &pb.MapKey_String_{String_: uint64(k)} + default: + return nil, fmt.Errorf("biscuit: failed to convert token ID to proto ID: map key must be an integer or a string, got %T", e.Key) + } + if e.Value.Type() == datalog.TermTypeVariable { + return nil, errors.New("biscuit: failed to convert token ID to proto ID: map cannot contain variables") + } + value, err := tokenIDToProtoIDV2(e.Value) + if err != nil { + return nil, err + } + entries[i] = &pb.MapEntry{Key: key, Value: value} + } + pbId = &pb.Term{ + Content: &pb.Term_Map{Map: &pb.Map{Entries: entries}}, + } + case datalog.TermTypeSet: + datalogSet := input.(datalog.Set) + protoSet := make([]*pb.Term, 0, len(datalogSet)) + for i, datalogElt := range datalogSet { + switch datalogElt.Type() { + case datalog.TermTypeVariable: + return nil, errors.New("biscuit: failed to convert token ID to proto ID: set cannot contains variable") + case datalog.TermTypeSet: + return nil, errors.New("biscuit: failed to convert token ID to proto ID: set cannot contains other sets") + } + if expectedEltType := datalogSet[0].Type(); i > 0 && datalogElt.Type() != expectedEltType { return nil, fmt.Errorf( "biscuit: failed to convert token ID to proto ID: set elements must have the same type (got %x, want %x)", datalogElt.Type(), @@ -126,8 +165,8 @@ func tokenIDToProtoIDV2(input datalog.Term) (*pb.TermV2, error) { protoSet = append(protoSet, protoElt) } - pbId = &pb.TermV2{ - Content: &pb.TermV2_Set{ + pbId = &pb.Term{ + Content: &pb.Term_Set{ Set: &pb.TermSet{ Set: protoSet, }, @@ -139,38 +178,34 @@ func tokenIDToProtoIDV2(input datalog.Term) (*pb.TermV2, error) { return pbId, nil } -func protoIDToTokenIDV2(input *pb.TermV2) (*datalog.Term, error) { +func protoIDToTokenIDV2(input *pb.Term) (*datalog.Term, error) { var id datalog.Term switch input.Content.(type) { - case *pb.TermV2_String_: + case *pb.Term_String_: id = datalog.String(input.GetString_()) - case *pb.TermV2_Date: + case *pb.Term_Date: id = datalog.Date(input.GetDate()) - case *pb.TermV2_Integer: + case *pb.Term_Integer: id = datalog.Integer(input.GetInteger()) - case *pb.TermV2_Variable: + case *pb.Term_Variable: id = datalog.Variable(input.GetVariable()) - case *pb.TermV2_Bytes: + case *pb.Term_Bytes: id = datalog.Bytes(input.GetBytes()) - case *pb.TermV2_Bool: + case *pb.Term_Bool: id = datalog.Bool(input.GetBool()) - case *pb.TermV2_Set: + case *pb.Term_Null: + id = datalog.Null{} + case *pb.Term_Set: elts := input.GetSet().Set - if len(elts) == 0 { - return nil, errors.New("biscuit: failed to convert proto ID to token ID: set cannot be empty") - } - - expectedEltType := reflect.TypeOf(elts[0].GetContent()) - switch expectedEltType { - case reflect.TypeFor[*pb.TermV2_Variable](): - return nil, errors.New("biscuit: failed to convert proto ID to token ID: set cannot contains variable") - case reflect.TypeFor[*pb.TermV2_Set](): - return nil, errors.New("biscuit: failed to convert proto ID to token ID: set cannot contains other sets") - } - datalogSet := make(datalog.Set, 0, len(elts)) - for _, protoElt := range elts { - if eltType := reflect.TypeOf(protoElt.GetContent()); eltType != expectedEltType { + for i, protoElt := range elts { + switch protoElt.GetContent().(type) { + case *pb.Term_Variable: + return nil, errors.New("biscuit: failed to convert proto ID to token ID: set cannot contains variable") + case *pb.Term_Set: + return nil, errors.New("biscuit: failed to convert proto ID to token ID: set cannot contains other sets") + } + if eltType, expectedEltType := reflect.TypeOf(protoElt.GetContent()), reflect.TypeOf(elts[0].GetContent()); i > 0 && eltType != expectedEltType { return nil, fmt.Errorf( "biscuit: failed to convert proto ID to token ID: set elements must have the same type (got %x, want %x)", eltType, @@ -185,6 +220,43 @@ func protoIDToTokenIDV2(input *pb.TermV2) (*datalog.Term, error) { datalogSet = append(datalogSet, *datalogElt) } id = datalogSet + case *pb.Term_Array: + elts := input.GetArray().GetArray() + array := make(datalog.Array, len(elts)) + for i, protoElt := range elts { + if _, isVar := protoElt.GetContent().(*pb.Term_Variable); isVar { + return nil, errors.New("biscuit: failed to convert proto ID to token ID: array cannot contain variables") + } + elt, err := protoIDToTokenIDV2(protoElt) + if err != nil { + return nil, err + } + array[i] = *elt + } + id = array + case *pb.Term_Map: + pbEntries := input.GetMap().GetEntries() + entries := make([]datalog.MapEntry, len(pbEntries)) + for i, e := range pbEntries { + var key datalog.Term + switch k := e.GetKey().GetContent().(type) { + case *pb.MapKey_Integer: + key = datalog.Integer(k.Integer) + case *pb.MapKey_String_: + key = datalog.String(k.String_) + default: + return nil, fmt.Errorf("biscuit: failed to convert proto ID to token ID: unsupported map key: %T", e.GetKey().GetContent()) + } + if _, isVar := e.GetValue().GetContent().(*pb.Term_Variable); isVar { + return nil, errors.New("biscuit: failed to convert proto ID to token ID: map cannot contain variables") + } + value, err := protoIDToTokenIDV2(e.GetValue()) + if err != nil { + return nil, err + } + entries[i] = datalog.MapEntry{Key: key, Value: *value} + } + id = datalog.NewMap(entries...) default: return nil, fmt.Errorf("biscuit: failed to convert proto ID to token ID: unsupported id type: %T", input.Content) } @@ -192,8 +264,8 @@ func protoIDToTokenIDV2(input *pb.TermV2) (*datalog.Term, error) { return &id, nil } -func tokenRuleToProtoRuleV2(input datalog.Rule) (*pb.RuleV2, error) { - pbBody := make([]*pb.PredicateV2, len(input.Body)) +func tokenRuleToProtoRuleV2(input datalog.Rule, keys publicKeyTable) (*pb.Rule, error) { + pbBody := make([]*pb.Predicate, len(input.Body)) for i, p := range input.Body { pred, err := tokenPredicateToProtoPredicateV2(p) if err != nil { @@ -202,7 +274,7 @@ func tokenRuleToProtoRuleV2(input datalog.Rule) (*pb.RuleV2, error) { pbBody[i] = pred } - pbExpressions := make([]*pb.ExpressionV2, len(input.Expressions)) + pbExpressions := make([]*pb.Expression, len(input.Expressions)) for i, e := range input.Expressions { expr, err := tokenExpressionToProtoExpressionV2(e) if err != nil { @@ -216,14 +288,20 @@ func tokenRuleToProtoRuleV2(input datalog.Rule) (*pb.RuleV2, error) { return nil, err } - return &pb.RuleV2{ + pbScopes, err := tokenScopesToProtoScopes(input.Scopes, keys) + if err != nil { + return nil, err + } + + return &pb.Rule{ Head: pbHead, Body: pbBody, Expressions: pbExpressions, + Scope: pbScopes, }, nil } -func protoRuleToTokenRuleV2(input *pb.RuleV2) (*datalog.Rule, error) { +func protoRuleToTokenRuleV2(input *pb.Rule, keys publicKeyTable) (*datalog.Rule, error) { body := make([]datalog.Predicate, len(input.Body)) for i, predicateV2 := range input.Body { b, err := protoPredicateToTokenPredicateV2(predicateV2) @@ -246,15 +324,22 @@ func protoRuleToTokenRuleV2(input *pb.RuleV2) (*datalog.Rule, error) { if err != nil { return nil, err } + + scopes, err := protoScopesToTokenScopes(input.Scope, keys) + if err != nil { + return nil, err + } + return &datalog.Rule{ Head: *head, Body: body, Expressions: expressions, + Scopes: scopes, }, nil } -func tokenExpressionToProtoExpressionV2(input datalog.Expression) (*pb.ExpressionV2, error) { - pbExpr := &pb.ExpressionV2{ +func tokenExpressionToProtoExpressionV2(input datalog.Expression) (*pb.Expression, error) { + pbExpr := &pb.Expression{ Ops: make([]*pb.Op, len(input)), } @@ -278,6 +363,17 @@ func tokenExpressionToProtoExpressionV2(input datalog.Expression) (*pb.Expressio return nil, err } pbExpr.Ops[i] = &pb.Op{Content: &pb.Op_Binary{Binary: pbBinary}} + case datalog.OpTypeClosure: + closure := op.(datalog.Closure) + body, err := tokenExpressionToProtoExpressionV2(closure.Body) + if err != nil { + return nil, err + } + params := make([]uint32, len(closure.Params)) + for j, p := range closure.Params { + params[j] = uint32(p) + } + pbExpr.Ops[i] = &pb.Op{Content: &pb.Op_Closure{Closure: &pb.OpClosure{Params: params, Ops: body.Ops}}} default: return nil, fmt.Errorf("biscuit: unsupported expression type: %v", op.Type()) } @@ -285,7 +381,7 @@ func tokenExpressionToProtoExpressionV2(input datalog.Expression) (*pb.Expressio return pbExpr, nil } -func protoExpressionToTokenExpressionV2(input *pb.ExpressionV2) (datalog.Expression, error) { +func protoExpressionToTokenExpressionV2(input *pb.Expression) (datalog.Expression, error) { expr := make(datalog.Expression, len(input.Ops)) for i, op := range input.Ops { switch op.Content.(type) { @@ -307,6 +403,17 @@ func protoExpressionToTokenExpressionV2(input *pb.ExpressionV2) (datalog.Express return nil, err } expr[i] = datalog.BinaryOp{BinaryOpFunc: op} + case *pb.Op_Closure: + closure := op.GetClosure() + body, err := protoExpressionToTokenExpressionV2(&pb.Expression{Ops: closure.GetOps()}) + if err != nil { + return nil, err + } + params := make([]datalog.Variable, len(closure.GetParams())) + for j, p := range closure.GetParams() { + params[j] = datalog.Variable(p) + } + expr[i] = datalog.Closure{Params: params, Body: body} default: return nil, fmt.Errorf("biscuit: unsupported proto expression type: %T", op.Content) } @@ -323,6 +430,12 @@ func tokenExprUnaryToProtoExprUnary(op datalog.UnaryOp) (*pb.OpUnary, error) { pbUnaryKind = pb.OpUnary_Parens case datalog.UnaryLength: pbUnaryKind = pb.OpUnary_Length + case datalog.UnaryTypeOf: + pbUnaryKind = pb.OpUnary_TypeOf + case datalog.UnaryFfi: + pbUnaryKind = pb.OpUnary_Ffi + name := uint64(op.UnaryOpFunc.(datalog.Ffi).Name) + return &pb.OpUnary{Kind: &pbUnaryKind, FfiName: &name}, nil default: return nil, fmt.Errorf("biscuit: unsupported UnaryOpFunc type: %v", op.UnaryOpFunc.Type()) } @@ -338,6 +451,13 @@ func protoExprUnaryToTokenExprUnary(op *pb.OpUnary) (datalog.UnaryOpFunc, error) unaryOp = datalog.Parens{} case pb.OpUnary_Length: unaryOp = datalog.Length{} + case pb.OpUnary_TypeOf: + unaryOp = datalog.TypeOf{} + case pb.OpUnary_Ffi: + if op.FfiName == nil { + return nil, errors.New("biscuit: proto OpUnary Ffi without a name") + } + unaryOp = datalog.Ffi{Name: datalog.String(op.GetFfiName())} default: return nil, fmt.Errorf("biscuit: unsupported proto OpUnary type: %v", op.Kind) } @@ -381,6 +501,34 @@ func tokenExprBinaryToProtoExprBinary(op datalog.BinaryOp) (*pb.OpBinary, error) pbBinaryKind = pb.OpBinary_Intersection case datalog.BinaryUnion: pbBinaryKind = pb.OpBinary_Union + case datalog.BinaryBitwiseAnd: + pbBinaryKind = pb.OpBinary_BitwiseAnd + case datalog.BinaryBitwiseOr: + pbBinaryKind = pb.OpBinary_BitwiseOr + case datalog.BinaryBitwiseXor: + pbBinaryKind = pb.OpBinary_BitwiseXor + case datalog.BinaryNotEqual: + pbBinaryKind = pb.OpBinary_NotEqual + case datalog.BinaryHeterogeneousEqual: + pbBinaryKind = pb.OpBinary_HeterogeneousEqual + case datalog.BinaryHeterogeneousNotEqual: + pbBinaryKind = pb.OpBinary_HeterogeneousNotEqual + case datalog.BinaryLazyAnd: + pbBinaryKind = pb.OpBinary_LazyAnd + case datalog.BinaryLazyOr: + pbBinaryKind = pb.OpBinary_LazyOr + case datalog.BinaryAll: + pbBinaryKind = pb.OpBinary_All + case datalog.BinaryAny: + pbBinaryKind = pb.OpBinary_Any + case datalog.BinaryGet: + pbBinaryKind = pb.OpBinary_Get + case datalog.BinaryTryOr: + pbBinaryKind = pb.OpBinary_TryOr + case datalog.BinaryFfi: + pbBinaryKind = pb.OpBinary_Ffi + name := uint64(op.BinaryOpFunc.(datalog.FfiBinary).Name) + return &pb.OpBinary{Kind: &pbBinaryKind, FfiName: &name}, nil default: return nil, fmt.Errorf("biscuit: unsupported BinaryOpFunc type: %v", op.BinaryOpFunc.Type()) } @@ -424,38 +572,88 @@ func protoExprBinaryToTokenExprBinary(op *pb.OpBinary) (datalog.BinaryOpFunc, er binaryOp = datalog.Intersection{} case pb.OpBinary_Union: binaryOp = datalog.Union{} + case pb.OpBinary_BitwiseAnd: + binaryOp = datalog.BitwiseAnd{} + case pb.OpBinary_BitwiseOr: + binaryOp = datalog.BitwiseOr{} + case pb.OpBinary_BitwiseXor: + binaryOp = datalog.BitwiseXor{} + case pb.OpBinary_NotEqual: + binaryOp = datalog.NotEqual{} + case pb.OpBinary_HeterogeneousEqual: + binaryOp = datalog.HeterogeneousEqual{} + case pb.OpBinary_HeterogeneousNotEqual: + binaryOp = datalog.HeterogeneousNotEqual{} + case pb.OpBinary_LazyAnd: + binaryOp = datalog.LazyAnd{} + case pb.OpBinary_LazyOr: + binaryOp = datalog.LazyOr{} + case pb.OpBinary_All: + binaryOp = datalog.All{} + case pb.OpBinary_Any: + binaryOp = datalog.Any{} + case pb.OpBinary_Get: + binaryOp = datalog.Get{} + case pb.OpBinary_TryOr: + binaryOp = datalog.TryOr{} + case pb.OpBinary_Ffi: + if op.FfiName == nil { + return nil, errors.New("biscuit: proto OpBinary Ffi without a name") + } + binaryOp = datalog.FfiBinary{Name: datalog.String(op.GetFfiName())} default: return nil, fmt.Errorf("biscuit: unsupported proto OpBinary type: %v", op.Kind) } return binaryOp, nil } -func tokenCheckToProtoCheckV2(input datalog.Check) (*pb.CheckV2, error) { - pbQueries := make([]*pb.RuleV2, len(input.Queries)) +func tokenCheckToProtoCheckV2(input datalog.Check, keys publicKeyTable) (*pb.Check, error) { + pbQueries := make([]*pb.Rule, len(input.Queries)) for i, query := range input.Queries { - q, err := tokenRuleToProtoRuleV2(query) + q, err := tokenRuleToProtoRuleV2(query, keys) if err != nil { return nil, err } pbQueries[i] = q } - return &pb.CheckV2{ + pbCheck := &pb.Check{ Queries: pbQueries, - }, nil + } + // `check if` is the default and stays unset, as other implementations do. + switch input.Kind { + case datalog.CheckKindOne: + case datalog.CheckKindAll: + pbCheck.Kind = pb.Check_All.Enum() + case datalog.CheckKindReject: + pbCheck.Kind = pb.Check_Reject.Enum() + default: + return nil, fmt.Errorf("biscuit: unsupported check kind: %d", input.Kind) + } + return pbCheck, nil } -func protoCheckToTokenCheckV2(input *pb.CheckV2) (*datalog.Check, error) { +func protoCheckToTokenCheckV2(input *pb.Check, keys publicKeyTable) (*datalog.Check, error) { queries := make([]datalog.Rule, len(input.Queries)) for i, query := range input.Queries { - q, err := protoRuleToTokenRuleV2(query) + q, err := protoRuleToTokenRuleV2(query, keys) if err != nil { return nil, err } queries[i] = *q } - return &datalog.Check{ + check := &datalog.Check{ Queries: queries, - }, nil + } + switch input.GetKind() { + case pb.Check_One: + case pb.Check_All: + check.Kind = datalog.CheckKindAll + case pb.Check_Reject: + check.Kind = datalog.CheckKindReject + default: + return nil, fmt.Errorf("biscuit: unsupported check kind: %s", input.GetKind()) + } + return check, nil } diff --git a/converters_v2_test.go b/converters_v2_test.go index af3e7b56..399d730b 100644 --- a/converters_v2_test.go +++ b/converters_v2_test.go @@ -21,7 +21,7 @@ func TestExpressionConvertV2(t *testing.T) { testCases := []struct { Desc string Input datalog.Expression - Expected *pb.ExpressionV2 + Expected *pb.Expression }{ { Desc: "date comparison after", @@ -30,10 +30,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Date(now.Unix())}, datalog.BinaryOp{BinaryOpFunc: datalog.GreaterOrEqual{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 1}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Date{Date: uint64(now.Unix())}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 1}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Date{Date: uint64(now.Unix())}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_GreaterOrEqual.Enum()}}}, }, }, @@ -45,10 +45,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Date(123456789)}, datalog.BinaryOp{BinaryOpFunc: datalog.LessOrEqual{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 2}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Date{Date: uint64(123456789)}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 2}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Date{Date: uint64(123456789)}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_LessOrEqual.Enum()}}}, }, }, @@ -60,10 +60,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(42)}, datalog.BinaryOp{BinaryOpFunc: datalog.Equal{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 3}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 3}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Equal.Enum()}}}, }, }, @@ -75,10 +75,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(-42)}, datalog.BinaryOp{BinaryOpFunc: datalog.GreaterThan{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 4}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: -42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 4}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: -42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_GreaterThan.Enum()}}}, }, }, @@ -90,10 +90,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(43)}, datalog.BinaryOp{BinaryOpFunc: datalog.GreaterOrEqual{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 5}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 43}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 5}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 43}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_GreaterOrEqual.Enum()}}}, }, }, @@ -105,10 +105,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(0)}, datalog.BinaryOp{BinaryOpFunc: datalog.LessThan{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 6}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 0}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 6}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 0}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_LessThan.Enum()}}}, }, }, @@ -120,10 +120,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(math.MaxInt64)}, datalog.BinaryOp{BinaryOpFunc: datalog.LessOrEqual{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 7}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: math.MaxInt64}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 7}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: math.MaxInt64}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_LessOrEqual.Enum()}}}, }, }, @@ -135,14 +135,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Variable(8)}, datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_Integer{Integer: 1}}, - {Content: &pb.TermV2_Integer{Integer: 2}}, - {Content: &pb.TermV2_Integer{Integer: 3}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_Integer{Integer: 1}}, + {Content: &pb.Term_Integer{Integer: 2}}, + {Content: &pb.Term_Integer{Integer: 3}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 8}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 8}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, }, }, @@ -155,14 +155,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, datalog.UnaryOp{UnaryOpFunc: datalog.Negate{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_Integer{Integer: 1}}, - {Content: &pb.TermV2_Integer{Integer: 2}}, - {Content: &pb.TermV2_Integer{Integer: 3}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_Integer{Integer: 1}}, + {Content: &pb.Term_Integer{Integer: 2}}, + {Content: &pb.Term_Integer{Integer: 3}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 9}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 9}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Negate.Enum()}}}, }, @@ -176,10 +176,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: syms.Insert("abcd")}, datalog.BinaryOp{BinaryOpFunc: datalog.Equal{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 10}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 10}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Equal.Enum()}}}, }, }, @@ -191,10 +191,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: syms.Insert("abcd")}, datalog.BinaryOp{BinaryOpFunc: datalog.Prefix{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 11}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 11}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Prefix.Enum()}}}, }, }, @@ -206,10 +206,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: syms.Insert("abcd")}, datalog.BinaryOp{BinaryOpFunc: datalog.Suffix{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 12}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 12}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Suffix.Enum()}}}, }, }, @@ -221,14 +221,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Variable(13)}, datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: syms.Index("a")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("b")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("c")}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: syms.Index("a")}}, + {Content: &pb.Term_String_{String_: syms.Index("b")}}, + {Content: &pb.Term_String_{String_: syms.Index("c")}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 13}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 13}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, }, }, @@ -241,14 +241,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, datalog.UnaryOp{UnaryOpFunc: datalog.Negate{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: syms.Index("a")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("b")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("c")}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: syms.Index("a")}}, + {Content: &pb.Term_String_{String_: syms.Index("b")}}, + {Content: &pb.Term_String_{String_: syms.Index("c")}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 14}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 14}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Negate.Enum()}}}, }, @@ -261,10 +261,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: syms.Insert("abcd")}, datalog.BinaryOp{BinaryOpFunc: datalog.Regex{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 15}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 15}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Regex.Enum()}}}, }, }, @@ -276,10 +276,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Bytes("abcd")}, datalog.BinaryOp{BinaryOpFunc: datalog.Equal{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 16}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Bytes{Bytes: []byte("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 16}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Bytes{Bytes: []byte("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Equal.Enum()}}}, }, }, @@ -291,14 +291,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Variable(17)}, datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_Bytes{Bytes: []byte("a")}}, - {Content: &pb.TermV2_Bytes{Bytes: []byte("b")}}, - {Content: &pb.TermV2_Bytes{Bytes: []byte("c")}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_Bytes{Bytes: []byte("a")}}, + {Content: &pb.Term_Bytes{Bytes: []byte("b")}}, + {Content: &pb.Term_Bytes{Bytes: []byte("c")}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 17}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 17}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, }, }, @@ -311,14 +311,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, datalog.UnaryOp{UnaryOpFunc: datalog.Negate{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_Bytes{Bytes: []byte("a")}}, - {Content: &pb.TermV2_Bytes{Bytes: []byte("b")}}, - {Content: &pb.TermV2_Bytes{Bytes: []byte("c")}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_Bytes{Bytes: []byte("a")}}, + {Content: &pb.Term_Bytes{Bytes: []byte("b")}}, + {Content: &pb.Term_Bytes{Bytes: []byte("c")}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 18}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 18}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Negate.Enum()}}}, }, @@ -331,14 +331,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Variable(19)}, datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: 1}}, - {Content: &pb.TermV2_String_{String_: 2}}, - {Content: &pb.TermV2_String_{String_: 3}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: 1}}, + {Content: &pb.Term_String_{String_: 2}}, + {Content: &pb.Term_String_{String_: 3}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 19}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 19}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, }, }, @@ -351,14 +351,14 @@ func TestExpressionConvertV2(t *testing.T) { datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}}, datalog.UnaryOp{UnaryOpFunc: datalog.Negate{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: 1}}, - {Content: &pb.TermV2_String_{String_: 2}}, - {Content: &pb.TermV2_String_{String_: 3}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: 1}}, + {Content: &pb.Term_String_{String_: 2}}, + {Content: &pb.Term_String_{String_: 3}}, }}}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 20}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 20}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Contains.Enum()}}}, {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Negate.Enum()}}}, }, @@ -371,10 +371,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(42)}, datalog.BinaryOp{BinaryOpFunc: datalog.Add{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 21}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 21}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Add.Enum()}}}, }, }, @@ -386,10 +386,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(42)}, datalog.BinaryOp{BinaryOpFunc: datalog.Sub{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 22}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 22}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Sub.Enum()}}}, }, }, @@ -401,10 +401,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(42)}, datalog.BinaryOp{BinaryOpFunc: datalog.Mul{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 23}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 23}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Mul.Enum()}}}, }, }, @@ -416,10 +416,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Integer(42)}, datalog.BinaryOp{BinaryOpFunc: datalog.Div{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 24}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 24}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Div.Enum()}}}, }, }, @@ -431,10 +431,10 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Bool(true)}, datalog.BinaryOp{BinaryOpFunc: datalog.And{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 25}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Bool{Bool: true}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 25}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Bool{Bool: true}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_And.Enum()}}}, }, }, @@ -446,14 +446,49 @@ func TestExpressionConvertV2(t *testing.T) { datalog.Value{ID: datalog.Bool(true)}, datalog.BinaryOp{BinaryOpFunc: datalog.Or{}}, }, - Expected: &pb.ExpressionV2{ + Expected: &pb.Expression{ Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 26}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Bool{Bool: true}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 26}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Bool{Bool: true}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Or.Enum()}}}, }, }, }, + { + Desc: "extern functions", + Input: datalog.Expression{ + datalog.Value{ID: datalog.Variable(1)}, + datalog.UnaryOp{UnaryOpFunc: datalog.Ffi{Name: datalog.String(1024)}}, + datalog.Value{ID: datalog.Integer(1)}, + datalog.BinaryOp{BinaryOpFunc: datalog.FfiBinary{Name: datalog.String(1025)}}, + }, + Expected: &pb.Expression{ + Ops: []*pb.Op{ + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 1}}}}, + {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Ffi.Enum(), FfiName: proto.Uint64(1024)}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 1}}}}, + {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Ffi.Enum(), FfiName: proto.Uint64(1025)}}}, + }, + }, + }, + { + Desc: "try_or", + Input: datalog.Expression{ + datalog.Closure{Params: []datalog.Variable{}, Body: datalog.Expression{datalog.Value{ID: datalog.Variable(1)}, datalog.UnaryOp{UnaryOpFunc: datalog.Length{}}}}, + datalog.Value{ID: datalog.Integer(0)}, + datalog.BinaryOp{BinaryOpFunc: datalog.TryOr{}}, + }, + Expected: &pb.Expression{ + Ops: []*pb.Op{ + {Content: &pb.Op_Closure{Closure: &pb.OpClosure{Params: []uint32{}, Ops: []*pb.Op{ + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 1}}}}, + {Content: &pb.Op_Unary{Unary: &pb.OpUnary{Kind: pb.OpUnary_Length.Enum()}}}, + }}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 0}}}}, + {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_TryOr.Enum()}}}, + }, + }, + }, } for _, testCase := range testCases { @@ -505,44 +540,44 @@ func TestRuleConvertV2(t *testing.T) { name1 := uint64(42) name2 := uint64(43) name3 := uint64(44) - expectedPbRule := &pb.RuleV2{ - Head: &pb.PredicateV2{Name: &name1, Terms: []*pb.TermV2{ - {Content: &pb.TermV2_Integer{Integer: 1}}, - {Content: &pb.TermV2_String_{String_: syms.Index("id_1")}}, + expectedPbRule := &pb.Rule{ + Head: &pb.Predicate{Name: &name1, Terms: []*pb.Term{ + {Content: &pb.Term_Integer{Integer: 1}}, + {Content: &pb.Term_String_{String_: syms.Index("id_1")}}, }}, - Body: []*pb.PredicateV2{ + Body: []*pb.Predicate{ { Name: &name2, - Terms: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: 2}}, - {Content: &pb.TermV2_Date{Date: uint64(now.Unix())}}, + Terms: []*pb.Term{ + {Content: &pb.Term_String_{String_: 2}}, + {Content: &pb.Term_Date{Date: uint64(now.Unix())}}, }, }, { Name: &name3, - Terms: []*pb.TermV2{ - {Content: &pb.TermV2_Bytes{Bytes: []byte("abcd")}}, + Terms: []*pb.Term{ + {Content: &pb.Term_Bytes{Bytes: []byte("abcd")}}, }, }, }, - Expressions: []*pb.ExpressionV2{ + Expressions: []*pb.Expression{ {Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 9}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 9}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 42}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Equal.Enum()}}}, }}, {Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 99}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: 99}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Prefix.Enum()}}}, }}, }, } - pbRule, err := tokenRuleToProtoRuleV2(*in) + pbRule, err := tokenRuleToProtoRuleV2(*in, nil) require.NoError(t, err) require.Equal(t, expectedPbRule, pbRule) - out, err := protoRuleToTokenRuleV2(pbRule) + out, err := protoRuleToTokenRuleV2(pbRule, nil) require.NoError(t, err) require.Equal(t, in, out) } @@ -569,19 +604,19 @@ func TestFactConvertV2(t *testing.T) { }} name1 := uint64(42) - expectedPbFact := &pb.FactV2{Predicate: &pb.PredicateV2{ + expectedPbFact := &pb.Fact{Predicate: &pb.Predicate{ Name: &name1, - Terms: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: 1}}, - {Content: &pb.TermV2_Integer{Integer: 2}}, - {Content: &pb.TermV2_Variable{Variable: 3}}, - {Content: &pb.TermV2_Bytes{Bytes: []byte("bytes")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}, - {Content: &pb.TermV2_Date{Date: uint64(now.Unix())}}, - {Content: &pb.TermV2_Bool{Bool: true}}, - {Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: syms.Index("abc")}}, - {Content: &pb.TermV2_String_{String_: syms.Index("def")}}, + Terms: []*pb.Term{ + {Content: &pb.Term_String_{String_: 1}}, + {Content: &pb.Term_Integer{Integer: 2}}, + {Content: &pb.Term_Variable{Variable: 3}}, + {Content: &pb.Term_Bytes{Bytes: []byte("bytes")}}, + {Content: &pb.Term_String_{String_: syms.Index("abcd")}}, + {Content: &pb.Term_Date{Date: uint64(now.Unix())}}, + {Content: &pb.Term_Bool{Bool: true}}, + {Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: syms.Index("abc")}}, + {Content: &pb.Term_String_{String_: syms.Index("def")}}, }}}}, }, }} @@ -602,10 +637,6 @@ func TestConvertInvalTermsets(t *testing.T) { desc string in datalog.Set }{ - { - desc: "empty set", - in: datalog.Set{}, - }, { desc: "mixed element types", in: datalog.Set{ @@ -633,37 +664,31 @@ func TestConvertInvalTermsets(t *testing.T) { protoTestCases := []struct { desc string - in *pb.TermV2 + in *pb.Term }{ - { - desc: "empty set", - in: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{ - Set: []*pb.TermV2{}, - }}}, - }, { desc: "mixed element types", - in: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{ - Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: syms.Index("abc")}}, - {Content: &pb.TermV2_Integer{Integer: 0}}, + in: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{ + Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: syms.Index("abc")}}, + {Content: &pb.Term_Integer{Integer: 0}}, }, }}}, }, { desc: "set with variables", - in: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{ - Set: []*pb.TermV2{ - {Content: &pb.TermV2_Variable{Variable: 1}}, + in: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{ + Set: []*pb.Term{ + {Content: &pb.Term_Variable{Variable: 1}}, }, }}}, }, { desc: "set with sub sets", - in: &pb.TermV2{Content: &pb.TermV2_Set{Set: &pb.TermSet{ - Set: []*pb.TermV2{ - {Content: &pb.TermV2_Set{Set: &pb.TermSet{Set: []*pb.TermV2{ - {Content: &pb.TermV2_String_{String_: syms.Index("abc")}}, + in: &pb.Term{Content: &pb.Term_Set{Set: &pb.TermSet{ + Set: []*pb.Term{ + {Content: &pb.Term_Set{Set: &pb.TermSet{Set: []*pb.Term{ + {Content: &pb.Term_String_{String_: syms.Index("abc")}}, }}}}, }, }}}, @@ -685,6 +710,17 @@ func TestConvertInvalTermsets(t *testing.T) { } } +// The spec allows the empty set, written {,}. +func TestConvertEmptySetV2(t *testing.T) { + protoSet, err := tokenIDToProtoIDV2(datalog.Set{}) + require.NoError(t, err) + require.Empty(t, protoSet.GetSet().GetSet()) + + tokenSet, err := protoIDToTokenIDV2(protoSet) + require.NoError(t, err) + require.Equal(t, datalog.Set{}, *tokenSet) +} + func TestBlockConvertV2(t *testing.T) { syms := &datalog.SymbolTable{} @@ -694,9 +730,9 @@ func TestBlockConvertV2(t *testing.T) { } name1 := uint64(12) - pbPredicate := &pb.PredicateV2{ + pbPredicate := &pb.Predicate{ Name: &name1, - Terms: []*pb.TermV2{{Content: &pb.TermV2_String_{String_: syms.Index("abcd")}}}, + Terms: []*pb.Term{{Content: &pb.Term_String_{String_: syms.Index("abcd")}}}, } rule := &datalog.Rule{ @@ -711,13 +747,13 @@ func TestBlockConvertV2(t *testing.T) { }, } - pbRule := &pb.RuleV2{ + pbRule := &pb.Rule{ Head: pbPredicate, - Body: []*pb.PredicateV2{pbPredicate}, - Expressions: []*pb.ExpressionV2{ + Body: []*pb.Predicate{pbPredicate}, + Expressions: []*pb.Expression{ {Ops: []*pb.Op{ - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: uint32(datalog.OFFSET + 13)}}}}, - {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 1234}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Variable{Variable: uint32(datalog.OFFSET + 13)}}}}, + {Content: &pb.Op_Value{Value: &pb.Term{Content: &pb.Term_Integer{Integer: 1234}}}}, {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_Equal.Enum()}}}, }}, }, @@ -736,25 +772,25 @@ func TestBlockConvertV2(t *testing.T) { version := uint32(3) expectedPbBlock := &pb.Block{ Symbols: []string{"a", "b", "c", "d"}, - FactsV2: []*pb.FactV2{ + Facts: []*pb.Fact{ {Predicate: pbPredicate}, }, - RulesV2: []*pb.RuleV2{pbRule}, - ChecksV2: []*pb.CheckV2{{Queries: []*pb.RuleV2{pbRule}}}, - Context: &ctx, - Version: proto.Uint32(version), + Rules: []*pb.Rule{pbRule}, + Checks: []*pb.Check{{Queries: []*pb.Rule{pbRule}}}, + Context: &ctx, + Version: proto.Uint32(version), } - pbBlock, err := tokenBlockToProtoBlock(in) + pbBlock, err := tokenBlockToProtoBlock(in, nil) require.NoError(t, err) require.Equal(t, expectedPbBlock, pbBlock) - out, err := protoBlockToTokenBlock(pbBlock) + out, err := protoBlockToTokenBlock(pbBlock, nil) require.NoError(t, err) require.Equal(t, in, out) version = uint32(MaxSchemaVersion + 1) pbBlock.Version = proto.Uint32(version) - _, err = protoBlockToTokenBlock(pbBlock) + _, err = protoBlockToTokenBlock(pbBlock, nil) require.Error(t, err) } diff --git a/crypto_test.go b/crypto_test.go new file mode 100644 index 00000000..ffeb4b04 --- /dev/null +++ b/crypto_test.go @@ -0,0 +1,114 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package biscuit + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/eclipse-biscuit/biscuit-go/v2/internal/crypto" + "github.com/eclipse-biscuit/biscuit-go/v2/pb" + "github.com/stretchr/testify/require" +) + +// A token built from an ECDSA root key, appended, sealed and verified. +func TestBiscuitSecp256r1(t *testing.T) { + privateRoot, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + builder := NewBuilderWithSigner(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{ + Predicate: Predicate{Name: "right", IDs: []Term{String("/a/file1"), String("read")}}, + })) + b1, err := builder.Build() + require.NoError(t, err) + + b1deser, err := Unmarshal(mustSerialize(t, b1)) + require.NoError(t, err) + + // Non-Ed25519 keys require the v1 signature payload. + require.EqualValues(t, 1, b1deser.container.Authority.GetVersion()) + require.Equal(t, pb.PublicKey_SECP256R1, b1deser.container.Authority.GetNextKey().GetAlgorithm()) + + block := b1deser.CreateBlock() + require.NoError(t, block.AddFact(Fact{Predicate: Predicate{Name: "appended", IDs: []Term{Integer(1)}}})) + b2, err := b1deser.Append(rand.Reader, block.Build()) + require.NoError(t, err) + + b2deser, err := Unmarshal(mustSerialize(t, b2)) + require.NoError(t, err) + _, err = b2deser.AuthorizerFrom(RootKey(&privateRoot.PublicKey)) + require.NoError(t, err) + + sealed, err := b2deser.Seal(rand.Reader) + require.NoError(t, err) + sealedDeser, err := Unmarshal(mustSerialize(t, sealed)) + require.NoError(t, err) + _, err = sealedDeser.AuthorizerFrom(RootKey(&privateRoot.PublicKey)) + require.NoError(t, err) + + otherRoot, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + _, err = b2deser.AuthorizerFrom(RootKey(&otherRoot.PublicKey)) + require.ErrorIs(t, err, ErrInvalidSignature) + + edPub, _, _ := ed25519.GenerateKey(rand.Reader) + _, err = b2deser.AuthorizerFrom(RootKey(edPub)) + require.ErrorIs(t, err, ErrInvalidSignature) +} + +// Ed25519-only tokens keep the v0 payload, so they are byte compatible with +// tokens produced before signature payload v1 existed. +func TestBiscuitSignatureVersionEd25519(t *testing.T) { + _, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + b1, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + require.EqualValues(t, 0, b1.container.Authority.GetVersion()) + + b2, err := b1.Append(rand.Reader, b1.CreateBlock().Build()) + require.NoError(t, err) + require.EqualValues(t, 0, b2.container.Blocks[0].GetVersion()) +} + +func TestBuilderUnsupportedRootKey(t *testing.T) { + _, err := NewBuilderWithSigner(nil).Build() + require.ErrorIs(t, err, ErrUnsupportedAlgorithm) +} + +// Each block verifies with the previous block's key; a tampered previous +// signature breaks a v1 block even when its own signature is intact. +func TestBiscuitV1PreviousSignatureBinding(t *testing.T) { + privateRoot, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + b1, err := NewBuilderWithSigner(privateRoot).Build() + require.NoError(t, err) + b2, err := b1.Append(rand.Reader, b1.CreateBlock().Build()) + require.NoError(t, err) + + _, err = b2.AuthorizerFrom(RootKey(&privateRoot.PublicKey)) + require.NoError(t, err) + + // Re-sign the authority with the root key over the same content: the + // signature stays valid on its own, but block 1 bound the old one. + authority := b2.container.Authority + rootSigner, err := crypto.NewSigner(privateRoot) + require.NoError(t, err) + resigned, err := rootSigner.Sign(blockSignaturePayload(authority, nil)) + require.NoError(t, err) + require.NotEqual(t, authority.Signature, resigned) + authority.Signature = resigned + + _, err = b2.AuthorizerFrom(RootKey(&privateRoot.PublicKey)) + require.ErrorIs(t, err, ErrInvalidSignature) +} + +func mustSerialize(t *testing.T, b *Biscuit) []byte { + t.Helper() + ser, err := b.Serialize() + require.NoError(t, err) + return ser +} diff --git a/datalog/benchmark_test.go b/datalog/benchmark_test.go index 33f8ac0d..91f9cf08 100644 --- a/datalog/benchmark_test.go +++ b/datalog/benchmark_test.go @@ -46,10 +46,10 @@ func BenchmarkWorldRunTransitiveClosure(b *testing.B) { for i := 0; i < b.N; i++ { w := benchWorld() for _, f := range facts { - w.AddFact(f) + w.AddFact(NewOrigin(0), f) } for _, r := range rules { - w.AddRule(r) + w.AddRule(0, DefaultTrustedOrigins(), r) } if err := w.Run(syms); err != nil { b.Fatal(err) @@ -76,7 +76,7 @@ func BenchmarkWorldAddFact(b *testing.B) { for i := 0; i < b.N; i++ { w := benchWorld() for _, f := range facts { - w.AddFact(f) + w.AddFact(NewOrigin(0), f) } } }) @@ -96,10 +96,10 @@ func BenchmarkWorldQueryRule(b *testing.B) { w := benchWorld() for i := range n { - w.AddFact(Fact{Predicate{right, []Term{syms.Insert(fmt.Sprintf("/file%d", i)), read}}}) + w.AddFact(NewOrigin(0), Fact{Predicate{right, []Term{syms.Insert(fmt.Sprintf("/file%d", i)), read}}}) } - w.AddFact(Fact{Predicate{resource, []Term{syms.Insert(fmt.Sprintf("/file%d", n-1))}}}) - w.AddFact(Fact{Predicate{operation, []Term{read}}}) + w.AddFact(NewOrigin(0), Fact{Predicate{resource, []Term{syms.Insert(fmt.Sprintf("/file%d", n-1))}}}) + w.AddFact(NewOrigin(0), Fact{Predicate{operation, []Term{read}}}) query := Rule{ Head: Predicate{check, []Term{Variable(0)}}, @@ -113,8 +113,9 @@ func BenchmarkWorldQueryRule(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - if res := w.QueryRule(query, syms); len(*res) != 1 { - b.Fatalf("got %d results, want 1", len(*res)) + res, err := w.QueryRule(query, 0, DefaultTrustedOrigins(), syms) + if err != nil || len(*res) != 1 { + b.Fatalf("got %d results (%v), want 1", len(*res), err) } } }) diff --git a/datalog/collections.go b/datalog/collections.go new file mode 100644 index 00000000..b46c1dbe --- /dev/null +++ b/datalog/collections.go @@ -0,0 +1,155 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "cmp" + "fmt" + "slices" + "strings" +) + +// Array is an ordered sequence of terms of any type (datalog v3.3). +type Array []Term + +func (Array) Type() TermType { return TermTypeArray } + +func (a Array) Equal(t Term) bool { + other, ok := t.(Array) + if !ok || len(a) != len(other) { + return false + } + for i := range a { + if !a[i].Equal(other[i]) { + return false + } + } + return true +} + +// HasPrefix reports whether a starts with the elements of prefix. +func (a Array) HasPrefix(prefix Array) bool { + if len(prefix) > len(a) { + return false + } + return Array(a[:len(prefix)]).Equal(prefix) +} + +// HasSuffix reports whether a ends with the elements of suffix. +func (a Array) HasSuffix(suffix Array) bool { + if len(suffix) > len(a) { + return false + } + return Array(a[len(a)-len(suffix):]).Equal(suffix) +} + +func (a Array) contains(t Term) bool { + for _, e := range a { + if e.Equal(t) { + return true + } + } + return false +} + +func (a Array) String() string { + strs := make([]string, len(a)) + for i, e := range a { + strs[i] = e.String() + } + return fmt.Sprintf("[%s]", strings.Join(strs, ", ")) +} + +// MapEntry is one key/value pair of a Map. The key is an Integer or a +// String. +type MapEntry struct { + Key Term + Value Term +} + +// Map associates Integer or String keys with terms of any type (datalog +// v3.3). Entries are kept sorted by key, integers first, so that two maps +// with the same entries are equal and print the same. +type Map []MapEntry + +// NewMap builds a map; a key given twice keeps its last value. +func NewMap(entries ...MapEntry) Map { + m := make(Map, 0, len(entries)) + for _, e := range entries { + m = m.with(e) + } + return m +} + +func (m Map) with(e MapEntry) Map { + i, found := slices.BinarySearchFunc(m, e.Key, func(entry MapEntry, key Term) int { + return compareMapKeys(entry.Key, key) + }) + if found { + res := slices.Clone(m) + res[i].Value = e.Value + return res + } + return slices.Insert(slices.Clone(m), i, e) +} + +// compareMapKeys orders integers before strings, then by value. +func compareMapKeys(a, b Term) int { + switch ka := a.(type) { + case Integer: + if kb, ok := b.(Integer); ok { + return cmp.Compare(ka, kb) + } + return -1 + case String: + if kb, ok := b.(String); ok { + return cmp.Compare(ka, kb) + } + return 1 + default: + return 0 + } +} + +// IsMapKey reports whether a term may be a map key. +func IsMapKey(t Term) bool { + switch t.(type) { + case Integer, String: + return true + } + return false +} + +func (Map) Type() TermType { return TermTypeMap } + +func (m Map) Equal(t Term) bool { + other, ok := t.(Map) + if !ok || len(m) != len(other) { + return false + } + for i := range m { + if !m[i].Key.Equal(other[i].Key) || !m[i].Value.Equal(other[i].Value) { + return false + } + } + return true +} + +// Get returns the value for the key, or nil when absent. +func (m Map) Get(key Term) Term { + for _, e := range m { + if e.Key.Equal(key) { + return e.Value + } + } + return nil +} + +func (m Map) String() string { + strs := make([]string, len(m)) + for i, e := range m { + strs[i] = fmt.Sprintf("%s: %s", e.Key, e.Value) + } + return fmt.Sprintf("{%s}", strings.Join(strs, ", ")) +} diff --git a/datalog/collections_test.go b/datalog/collections_test.go new file mode 100644 index 00000000..c70f1b66 --- /dev/null +++ b/datalog/collections_test.go @@ -0,0 +1,96 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestArrayAndMapTerms(t *testing.T) { + syms := &SymbolTable{} + dbg := SymbolDebugger{syms} + a, b := syms.Insert("a"), syms.Insert("b") + + arr := Array{Integer(1), Integer(2), a} + require.True(t, arr.Equal(Array{Integer(1), Integer(2), a})) + require.False(t, arr.Equal(Array{Integer(2), Integer(1), a}), "arrays are ordered") + require.False(t, arr.Equal(Set{Integer(1), Integer(2), a})) + require.True(t, arr.HasPrefix(Array{Integer(1), Integer(2)})) + require.False(t, arr.HasPrefix(Array{Integer(2)})) + require.True(t, arr.HasSuffix(Array{a})) + require.Equal(t, `[1, 2, "a"]`, dbg.Term(arr)) + + // Entries are ordered by key, integers first, whatever the insertion order. + m := NewMap(MapEntry{b, Integer(2)}, MapEntry{Integer(1), a}, MapEntry{a, Integer(1)}) + require.Equal(t, Map{{Integer(1), a}, {a, Integer(1)}, {b, Integer(2)}}, m) + require.True(t, m.Equal(NewMap(MapEntry{a, Integer(1)}, MapEntry{b, Integer(2)}, MapEntry{Integer(1), a}))) + require.False(t, m.Equal(NewMap(MapEntry{a, Integer(1)}))) + require.Equal(t, Integer(2), m.Get(b)) + require.Nil(t, m.Get(Integer(2))) + require.Equal(t, `{1: "a", "a": 1, "b": 2}`, dbg.Term(m)) + require.Equal(t, Integer(3), NewMap(MapEntry{a, Integer(1)}, MapEntry{a, Integer(3)}).Get(a), "a repeated key keeps the last value") + require.Equal(t, "{}", dbg.Term(Map{})) +} + +func TestArrayAndMapOperators(t *testing.T) { + syms := &SymbolTable{} + a, b, c := syms.Insert("a"), syms.Insert("b"), syms.Insert("c") + kv := Variable(syms.Insert("kv")) + arr := Array{a, b} + m := NewMap(MapEntry{Integer(1), syms.Insert("A")}, MapEntry{a, Integer(1)}, MapEntry{b, Integer(2)}) + + for _, tc := range []struct { + expr Expression + want Term + printed string + }{ + {Expression{Value{Array{Integer(1), Integer(2), Integer(1)}}, UnaryOp{Length{}}}, Integer(3), "[1, 2, 1].length()"}, + {Expression{Value{m}, UnaryOp{Length{}}}, Integer(3), `{1: "A", "a": 1, "b": 2}.length()`}, + {Expression{Value{arr}, Value{Bool(true)}, BinaryOp{HeterogeneousNotEqual{}}}, Bool(true), `["a", "b"] != true`}, + {Expression{Value{arr}, Value{Array{a, b}}, BinaryOp{Equal{}}}, Bool(true), `["a", "b"] === ["a", "b"]`}, + {Expression{Value{arr}, Value{Array{a, c}}, BinaryOp{NotEqual{}}}, Bool(true), `["a", "b"] !== ["a", "c"]`}, + {Expression{Value{Array{a, b, c}}, Value{c}, BinaryOp{Contains{}}}, Bool(true), `["a", "b", "c"].contains("c")`}, + {Expression{Value{Array{Integer(1), Integer(2), Integer(3)}}, Value{Array{Integer(1), Integer(2)}}, BinaryOp{Prefix{}}}, Bool(true), "[1, 2, 3].starts_with([1, 2])"}, + {Expression{Value{Array{Integer(4), Integer(5), Integer(6)}}, Value{Array{Integer(6)}}, BinaryOp{Suffix{}}}, Bool(true), "[4, 5, 6].ends_with([6])"}, + {Expression{Value{Array{Integer(1), Integer(2), a}}, Value{Integer(2)}, BinaryOp{Get{}}}, a, `[1, 2, "a"].get(2)`}, + {Expression{Value{Array{Integer(1), Integer(2)}}, Value{Integer(3)}, BinaryOp{Get{}}}, Null{}, "[1, 2].get(3)"}, + {Expression{Value{m}, Value{b}, BinaryOp{Contains{}}}, Bool(true), `{1: "A", "a": 1, "b": 2}.contains("b")`}, + {Expression{Value{m}, Value{Integer(2)}, BinaryOp{Contains{}}}, Bool(false), `{1: "A", "a": 1, "b": 2}.contains(2)`}, + {Expression{Value{m}, Value{a}, BinaryOp{Get{}}}, Integer(1), `{1: "A", "a": 1, "b": 2}.get("a")`}, + {Expression{Value{m}, Value{Integer(1)}, BinaryOp{Get{}}}, syms.Insert("A"), `{1: "A", "a": 1, "b": 2}.get(1)`}, + {Expression{Value{m}, Value{c}, BinaryOp{Get{}}}, Null{}, `{1: "A", "a": 1, "b": 2}.get("c")`}, + {Expression{Value{m}, Value{m}, BinaryOp{HeterogeneousEqual{}}}, Bool(true), `{1: "A", "a": 1, "b": 2} == {1: "A", "a": 1, "b": 2}`}, + {Expression{Value{m}, Value{NewMap(MapEntry{a, Integer(1)})}, BinaryOp{NotEqual{}}}, Bool(true), `{1: "A", "a": 1, "b": 2} !== {"a": 1}`}, + // .all() and .any() over an array, and over a map as [key, value] pairs. + {Expression{Value{Array{Integer(1), Integer(2), Integer(3)}}, Closure{[]Variable{kv}, Expression{Value{kv}, Value{Integer(0)}, BinaryOp{GreaterThan{}}}}, BinaryOp{All{}}}, Bool(true), "[1, 2, 3].all($kv -> $kv > 0)"}, + {Expression{Value{m}, Closure{[]Variable{kv}, Expression{ + Value{kv}, Value{Integer(0)}, BinaryOp{Get{}}, Value{Integer(1)}, BinaryOp{HeterogeneousEqual{}}, + Closure{nil, Expression{Value{kv}, Value{Integer(1)}, BinaryOp{Get{}}, Value{syms.Insert("A")}, BinaryOp{HeterogeneousEqual{}}}}, + BinaryOp{LazyAnd{}}, + }}, BinaryOp{Any{}}}, Bool(true), `{1: "A", "a": 1, "b": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == "A")`}, + // Nested maps and arrays. + {Expression{ + Value{NewMap(MapEntry{syms.Insert("user"), NewMap(MapEntry{syms.Insert("id"), Integer(1)}, MapEntry{syms.Insert("roles"), Array{syms.Insert("admin")}})})}, + Value{syms.Insert("user")}, BinaryOp{Get{}}, + Value{syms.Insert("roles")}, BinaryOp{Get{}}, + Value{syms.Insert("admin")}, BinaryOp{Contains{}}, + }, Bool(true), `{"user": {"id": 1, "roles": ["admin"]}}.get("user").get("roles").contains("admin")`}, + } { + t.Run(tc.printed, func(t *testing.T) { + res, err := tc.expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, tc.want, res) + require.Equal(t, tc.printed, tc.expr.Print(syms)) + }) + } + + bad := Expression{Value{arr}, Value{a}, BinaryOp{Get{}}} + _, err := bad.Evaluate(nil, syms) + require.Error(t, err, "an array index must be an integer") + bad = Expression{Value{m}, Value{Bool(true)}, BinaryOp{Get{}}} + _, err = bad.Evaluate(nil, syms) + require.Error(t, err, "a map key must be an integer or a string") +} diff --git a/datalog/datalog.go b/datalog/datalog.go index d262a971..2f33a2c1 100644 --- a/datalog/datalog.go +++ b/datalog/datalog.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "maps" + "slices" "sort" "strings" "time" @@ -24,6 +25,10 @@ const ( TermTypeBytes TermTypeBool TermTypeSet + // TermTypeNull, TermTypeArray and TermTypeMap are datalog v3.3. + TermTypeNull + TermTypeArray + TermTypeMap ) // Term is a value in a predicate. Implementations are value types and @@ -45,6 +50,9 @@ var ( _ Term = Bytes(nil) _ Term = Bool(false) _ Term = Set(nil) + _ Term = Null{} + _ Term = Array(nil) + _ Term = Map(nil) ) type Set []Term @@ -85,8 +93,11 @@ func (s Set) String() string { for _, e := range s { eltStr = append(eltStr, e.String()) } + if len(eltStr) == 0 { + return "{,}" + } sort.Strings(eltStr) - return fmt.Sprintf("[%s]", strings.Join(eltStr, ", ")) + return fmt.Sprintf("{%s}", strings.Join(eltStr, ", ")) } func (s Set) Intersect(t Set) Set { result := Set{} @@ -159,6 +170,14 @@ func (b Bool) String() string { return fmt.Sprintf("%t", b) } +// Null is the absence of a value (datalog v3.3). It equals only itself; +// `==` with another type is false, `===` is a type error. +type Null struct{} + +func (Null) Type() TermType { return TermTypeNull } +func (Null) Equal(t Term) bool { _, ok := t.(Null); return ok } +func (Null) String() string { return "null" } + type Predicate struct { Name String Terms []Term @@ -208,6 +227,8 @@ type Rule struct { Head Predicate Body []Predicate Expressions []Expression + // Scopes is the `trusting` clause; empty means the scopes of the block. + Scopes []Scope } type InvalidRuleError struct { @@ -219,46 +240,64 @@ func (e InvalidRuleError) Error() string { return fmt.Sprintf("datalog: variable %d in head is missing from body and/or constraints", e.MissingVariable) } -func (r Rule) Apply(facts *FactSet, newFacts *FactSet, syms *SymbolTable) error { - // extract all variables from the rule body +// variables lists the variables of the body, unbound. +func (r Rule) variables() MatchedVariables { variables := make(MatchedVariables) for _, predicate := range r.Body { for _, term := range predicate.Terms { - v, ok := term.(Variable) - if !ok { - continue + if v, ok := term.(Variable); ok { + variables[v] = nil } - variables[v] = nil } } + return variables +} - combinations, err := combine(variables, r.Body, r.Expressions, facts, syms) +// apply derives the facts of the rule from the visible facts and calls emit +// for each, with its origin: the origins of the matched facts plus blockID, +// the block the rule belongs to. +func (r Rule) apply(blockID BlockID, facts []factWithOrigin, syms *SymbolTable, externs ExternFuncs, emit func(Origin, Fact)) error { + matches, err := combine(r.variables(), r.Body, r.Expressions, facts, syms, externs) if err != nil { return err } - for _, res := range combinations { + for _, res := range matches { predicate := r.Head.Clone() for i, term := range predicate.Terms { k, ok := term.(Variable) if !ok { continue } - v, ok := res[k] + v, ok := res.vars[k] if !ok { return InvalidRuleError{r, k} } predicate.Terms[i] = *v } - newFacts.Insert(Fact{predicate}) + emit(res.origin.With(blockID), Fact{predicate}) } return nil } +// CheckKind selects how the queries of a Check are evaluated. +type CheckKind byte + +const ( + // CheckKindOne passes when one query has at least one match. + CheckKindOne CheckKind = iota + // CheckKindAll passes when one query has at least one match and every + // match of that query satisfies its expressions. + CheckKindAll + // CheckKindReject (`reject if`) passes when no query has a match. + CheckKindReject +) + type Check struct { Queries []Rule + Kind CheckKind } type FactSet []Fact @@ -318,6 +357,13 @@ var ( type WorldOption func(w *World) +// WithExternFuncs registers the functions that extern::name calls resolve to. +func WithExternFuncs(externs ExternFuncs) WorldOption { + return func(w *World) { + w.externs = externs + } +} + func WithMaxFacts(maxFacts int) WorldOption { return func(w *World) { w.runLimits.maxFacts = maxFacts @@ -336,16 +382,36 @@ func WithMaxDuration(maxDuration time.Duration) WorldOption { } } +type factWithOrigin struct { + origin Origin + fact Fact +} + +// originFacts is the set of facts sharing one origin. +type originFacts struct { + origin Origin + facts FactSet +} + +type scopedRule struct { + blockID BlockID + trusted TrustedOrigins + rule Rule +} + +// World holds the facts and rules of an authorization, each fact with the +// origin it derives from and each rule with the origins it trusts. type World struct { - facts *FactSet - rules []Rule + // facts is kept sorted by origin so that iteration is deterministic. + facts []originFacts + rules []scopedRule runLimits runLimits + externs ExternFuncs } func NewWorld(opts ...WorldOption) *World { w := &World{ - facts: &FactSet{}, runLimits: defaultRunLimits, } @@ -356,26 +422,86 @@ func NewWorld(opts ...WorldOption) *World { return w } -func (w *World) AddFact(f Fact) { - w.facts.Insert(f) +// AddFact adds a fact stated by the blocks of origin. Returns false when the +// world already had it under that origin. +func (w *World) AddFact(origin Origin, f Fact) bool { + i, found := slices.BinarySearchFunc(w.facts, origin, func(of originFacts, o Origin) int { + return of.origin.Compare(o) + }) + if !found { + w.facts = slices.Insert(w.facts, i, originFacts{origin: origin.With()}) + } + return w.facts[i].facts.Insert(f) } -func (w *World) Facts() *FactSet { - return w.facts +// Facts returns the facts of the world grouped by origin, in origin order. +func (w *World) Facts() []OriginFacts { + res := make([]OriginFacts, 0, len(w.facts)) + for _, of := range w.facts { + if len(of.facts) == 0 { + continue + } + facts := make(FactSet, len(of.facts)) + copy(facts, of.facts) + res = append(res, OriginFacts{Origin: of.origin.With(), Facts: facts}) + } + return res } -func (w *World) AddRule(r Rule) { - w.rules = append(w.rules, r) +// OriginFacts is a group of facts sharing an origin. +type OriginFacts struct { + Origin Origin + Facts FactSet +} + +func (w *World) factCount() int { + n := 0 + for _, of := range w.facts { + n += len(of.facts) + } + return n +} + +// visibleFacts flattens the facts whose origin is trusted. +func (w *World) visibleFacts(trusted TrustedOrigins) []factWithOrigin { + var res []factWithOrigin + for _, of := range w.facts { + if !trusted.Contains(of.origin) { + continue + } + for _, f := range of.facts { + res = append(res, factWithOrigin{origin: of.origin, fact: f}) + } + } + return res +} + +// AddRule adds a rule from block blockID, reading the facts of the trusted +// origins. +func (w *World) AddRule(blockID BlockID, trusted TrustedOrigins, r Rule) { + w.rules = append(w.rules, scopedRule{blockID: blockID, trusted: trusted.With(), rule: r}) } func (w *World) ResetRules() { - w.rules = make([]Rule, 0) + w.rules = nil } -func (w *World) Rules() []Rule { - return w.rules +// BlockRule is a rule with the block it belongs to. +type BlockRule struct { + BlockID BlockID + Rule Rule } +// Rules returns the rules of the world with their block, in insertion order. +func (w *World) Rules() []BlockRule { + res := make([]BlockRule, len(w.rules)) + for i, sr := range w.rules { + res[i] = BlockRule{BlockID: sr.blockID, Rule: sr.rule} + } + return res +} + +// Run applies the rules until no new fact appears or a limit is reached. func (w *World) Run(syms *SymbolTable) error { deadline := time.Now().Add(w.runLimits.maxDuration) for i := 0; i < w.runLimits.maxIterations; i++ { @@ -383,20 +509,26 @@ func (w *World) Run(syms *SymbolTable) error { if time.Now().After(deadline) { return ErrWorldRunLimitTimeout } - var newFacts FactSet - for _, r := range w.rules { + var newFacts []factWithOrigin + for _, sr := range w.rules { if time.Now().After(deadline) { return ErrWorldRunLimitTimeout } - if err := r.Apply(w.facts, &newFacts, syms); err != nil { + visible := w.visibleFacts(sr.trusted) + err := sr.rule.apply(sr.blockID, visible, syms, w.externs, func(origin Origin, f Fact) { + newFacts = append(newFacts, factWithOrigin{origin, f}) + }) + if err != nil { return err } } - prevCount := len(*w.facts) - w.facts.InsertAll([]Fact(newFacts)) + prevCount := w.factCount() + for _, nf := range newFacts { + w.AddFact(nf.origin, nf.fact) + } - newCount := len(*w.facts) + newCount := w.factCount() if newCount >= w.runLimits.maxFacts { return ErrWorldRunLimitMaxFacts } @@ -409,25 +541,19 @@ func (w *World) Run(syms *SymbolTable) error { return ErrWorldRunLimitMaxIterations } -func (w *World) Query(pred Predicate) *FactSet { +// Query returns the facts of the trusted origins matching the predicate, +// where a variable matches any term. +func (w *World) Query(trusted TrustedOrigins, pred Predicate) *FactSet { res := &FactSet{} - for _, f := range *w.facts { - if f.Name != pred.Name { - continue - } - - // if the predicate has a different number of IDs - // the fact must not match - if len(f.Terms) != len(pred.Terms) { + for _, fo := range w.visibleFacts(trusted) { + f := fo.fact + if f.Name != pred.Name || len(f.Terms) != len(pred.Terms) { continue } matches := true for i := 0; i < len(pred.Terms); i++ { - fID := f.Terms[i] - pID := pred.Terms[i] - - if pID.Type() != TermTypeVariable && !fID.Equal(pID) { + if pID := pred.Terms[i]; pID.Type() != TermTypeVariable && !f.Terms[i].Equal(pID) { matches = false break } @@ -440,20 +566,55 @@ func (w *World) Query(pred Predicate) *FactSet { return res } -func (w *World) QueryRule(rule Rule, syms *SymbolTable) *FactSet { +// QueryRule returns the facts the rule, from block blockID, derives from the +// facts of the trusted origins. An error from an expression (overflow, +// division by zero, type mismatch) is returned rather than treated as a +// non-match. +func (w *World) QueryRule(rule Rule, blockID BlockID, trusted TrustedOrigins, syms *SymbolTable) (*FactSet, error) { newFacts := &FactSet{} - // swallowing the error explicitly since propagating it would break the current API - _ = rule.Apply(w.facts, newFacts, syms) - return newFacts + err := rule.apply(blockID, w.visibleFacts(trusted), syms, w.externs, func(_ Origin, f Fact) { + newFacts.Insert(f) + }) + if err != nil { + return nil, err + } + return newFacts, nil +} + +// QueryMatchAll reports whether the body of the rule matches at least once +// in the facts of the trusted origins and its expressions hold for every +// match (the semantics of `check all`). +func (w *World) QueryMatchAll(rule Rule, trusted TrustedOrigins, syms *SymbolTable) (bool, error) { + // The expressions are evaluated here rather than passed to combine, which + // would filter the matches instead of reporting those that fail. + matches, err := combine(rule.variables(), rule.Body, nil, w.visibleFacts(trusted), syms, w.externs) + if err != nil { + return false, err + } + for _, res := range matches { + for _, e := range rule.Expressions { + v, err := e.EvaluateWith(res.vars, syms, w.externs) + if err != nil { + return false, err + } + if !v.Equal(Bool(true)) { + return false, nil + } + } + } + return len(matches) > 0, nil } func (w *World) Clone() *World { - newFacts := new(FactSet) - *newFacts = *w.facts + facts := make([]originFacts, len(w.facts)) + for i, of := range w.facts { + facts[i] = originFacts{origin: of.origin.With(), facts: slices.Clone(of.facts)} + } return &World{ - facts: newFacts, - rules: append([]Rule{}, w.rules...), + facts: facts, + rules: slices.Clone(w.rules), runLimits: w.runLimits, + externs: w.externs, } } @@ -483,33 +644,44 @@ func (m MatchedVariables) Clone() MatchedVariables { return res } +// match is one binding of the variables of a rule body, with the origin of +// the facts it was built from. +type match struct { + origin Origin + vars MatchedVariables +} + +// combine enumerates the bindings of variables for which every predicate +// matches a fact and every expression holds. The first expression error +// stops the enumeration and is returned. func combine( variables MatchedVariables, predicates []Predicate, expressions []Expression, - facts *FactSet, + facts []factWithOrigin, syms *SymbolTable, -) ([]MatchedVariables, error) { - var res []MatchedVariables + externs ExternFuncs, +) ([]match, error) { + var res []match current := 0 indexes := make([]int, len(predicates)) // cannot apply a rule on an empty list of facts - if len(predicates) > 0 && len(*facts) == 0 { + if len(predicates) > 0 && len(facts) == 0 { return nil, nil } // main loop for { - if len(predicates) > 0 && len(*facts) > 0 { + if len(predicates) > 0 && len(facts) > 0 { // look for the next matching set of facts // current indicates which predicate we are looking at, and indexes contains // a list of indexes in the facts list, for each predicate // when we are done looking at a set of facts, the last index is incremented // and if that one reached the max number of facts, the previous one, etc for { - if (*facts)[indexes[current]].Match(predicates[current]) { + if facts[indexes[current]].fact.Match(predicates[current]) { if current == len(predicates)-1 { // extract and check variables, check expressions, send variables break @@ -519,7 +691,7 @@ func combine( } else { // did not match, we either increase the current index or the previous one // then we check again for a match - if !advanceIndexes(¤t, &indexes, facts) { + if !advanceIndexes(¤t, &indexes, len(facts)) { return res, nil } } @@ -528,11 +700,13 @@ func combine( // extract and check variables, check expressions, send variables var vars = variables.Clone() + var origin Origin var matching = true match: for i, pred := range predicates { - fact := (*facts)[indexes[i]] + fo := facts[indexes[i]] + origin = origin.Union(fo.origin) for j := 0; j < len(pred.Terms); j++ { term := pred.Terms[j] @@ -540,7 +714,7 @@ func combine( if !ok { continue } - v := fact.Terms[j] + v := fo.fact.Terms[j] if !vars.Insert(k, v) { matching = false break match @@ -553,7 +727,7 @@ func combine( if complete_vars := vars.Complete(); complete_vars != nil { valid := true for _, e := range expressions { - r, err := e.Evaluate(complete_vars, syms) + r, err := e.EvaluateWith(complete_vars, syms, externs) if err != nil { return nil, err } @@ -564,7 +738,7 @@ func combine( } if valid { - res = append(res, complete_vars) + res = append(res, match{origin, complete_vars}) } } else { // if all predicates match but variables are not complete, it means @@ -581,15 +755,15 @@ func combine( } // next index - if !advanceIndexes(¤t, &indexes, facts) { + if !advanceIndexes(¤t, &indexes, len(facts)) { return res, nil } } } -func advanceIndexes(current *int, indexes *[]int, facts *FactSet) bool { +func advanceIndexes(current *int, indexes *[]int, factCount int) bool { for i := *current; i >= 0; i-- { - if (*indexes)[i] < len(*facts)-1 { + if (*indexes)[i] < factCount-1 { (*indexes)[i] += 1 break } else { diff --git a/datalog/datalog_test.go b/datalog/datalog_test.go index 31e2c7db..ed4d9398 100644 --- a/datalog/datalog_test.go +++ b/datalog/datalog_test.go @@ -15,6 +15,20 @@ import ( "go.uber.org/goleak" ) +// Most tests use one block: facts stated by the authority block, rules of the +// authority block trusting the defaults. +var ( + testOrigin = NewOrigin(0) + testTrusted = DefaultTrustedOrigins() +) + +func mustQueryRule(t *testing.T, w *World, rule Rule, syms *SymbolTable) *FactSet { + t.Helper() + res, err := w.QueryRule(rule, 0, testTrusted, syms) + require.NoError(t, err) + return res +} + func hashVar(s string) Variable { h := sha256.Sum256([]byte(s)) id := uint32(h[0]) + @@ -24,6 +38,24 @@ func hashVar(s string) Variable { return Variable(id) } +// Strings and variables inside a set go through the symbol table like any +// other term, in facts and in expressions. +func TestSymbolDebuggerSets(t *testing.T) { + syms := &SymbolTable{} + dbg := SymbolDebugger{syms} + allowed := syms.Insert("allowed") + a, b := syms.Insert("A"), syms.Insert("B") + op := Variable(syms.Insert("op")) + + require.Equal(t, `allowed({"A", "B"})`, dbg.Predicate(Predicate{allowed, []Term{Set{b, a}}})) + require.Equal(t, `allowed({,})`, dbg.Predicate(Predicate{allowed, []Term{Set{}}})) + require.Equal(t, `{"A", "B"}.contains($op)`, dbg.Expression(Expression{ + Value{Set{a, b}}, + Value{op}, + BinaryOp{Contains{}}, + })) +} + func TestFamily(t *testing.T) { w := NewWorld() syms := &SymbolTable{} @@ -36,9 +68,9 @@ func TestFamily(t *testing.T) { parent := syms.Insert("parent") grandparent := syms.Insert("grandparent") - w.AddFact(Fact{Predicate{parent, []Term{a, b}}}) - w.AddFact(Fact{Predicate{parent, []Term{b, c}}}) - w.AddFact(Fact{Predicate{parent, []Term{c, d}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{a, b}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{b, c}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{c, d}}}) r1 := Rule{ Head: Predicate{grandparent, []Term{hashVar("grandparent"), hashVar("grandchild")}}, @@ -49,9 +81,9 @@ func TestFamily(t *testing.T) { } t.Logf("querying r1: %s", dbg.Rule(r1)) - queryRuleResult := w.QueryRule(r1, syms) + queryRuleResult := mustQueryRule(t, w, r1, syms) t.Logf("r1 query: %s", dbg.FactSet(queryRuleResult)) - t.Logf("current facts: %s", dbg.FactSet(w.facts)) + t.Logf("current world: %s", dbg.World(w)) r2 := Rule{ Head: Predicate{grandparent, []Term{hashVar("grandparent"), hashVar("grandchild")}}, @@ -62,17 +94,17 @@ func TestFamily(t *testing.T) { } t.Logf("adding r2: %s", dbg.Rule(r2)) - w.AddRule(r2) + w.AddRule(0, testTrusted, r2) if err := w.Run(syms); err != nil { t.Error(err) } - w.AddFact(Fact{Predicate{parent, []Term{c, e}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{c, e}}}) if err := w.Run(syms); err != nil { t.Error(err) } - res := w.Query(Predicate{grandparent, []Term{hashVar("grandparent"), hashVar("grandchild")}}) + res := w.Query(testTrusted, Predicate{grandparent, []Term{hashVar("grandparent"), hashVar("grandchild")}}) t.Logf("grandparents after inserting parent(C, E): %s", dbg.FactSet(res)) expected := &FactSet{ Fact{Predicate{grandparent, []Term{a, c}}}, @@ -101,17 +133,17 @@ func TestNumbers(t *testing.T) { t2 := syms.Insert("t2") join := syms.Insert("join") - w.AddFact(Fact{Predicate{t1, []Term{Integer(0), abc}}}) - w.AddFact(Fact{Predicate{t1, []Term{Integer(1), def}}}) - w.AddFact(Fact{Predicate{t1, []Term{Integer(2), ghi}}}) - w.AddFact(Fact{Predicate{t1, []Term{Integer(3), jkl}}}) - w.AddFact(Fact{Predicate{t1, []Term{Integer(4), mno}}}) + w.AddFact(testOrigin, Fact{Predicate{t1, []Term{Integer(0), abc}}}) + w.AddFact(testOrigin, Fact{Predicate{t1, []Term{Integer(1), def}}}) + w.AddFact(testOrigin, Fact{Predicate{t1, []Term{Integer(2), ghi}}}) + w.AddFact(testOrigin, Fact{Predicate{t1, []Term{Integer(3), jkl}}}) + w.AddFact(testOrigin, Fact{Predicate{t1, []Term{Integer(4), mno}}}) - w.AddFact(Fact{Predicate{t2, []Term{Integer(0), aaa, Integer(0)}}}) - w.AddFact(Fact{Predicate{t2, []Term{Integer(1), bbb, Integer(0)}}}) - w.AddFact(Fact{Predicate{t2, []Term{Integer(2), ccc, Integer(1)}}}) + w.AddFact(testOrigin, Fact{Predicate{t2, []Term{Integer(0), aaa, Integer(0)}}}) + w.AddFact(testOrigin, Fact{Predicate{t2, []Term{Integer(1), bbb, Integer(0)}}}) + w.AddFact(testOrigin, Fact{Predicate{t2, []Term{Integer(2), ccc, Integer(1)}}}) - res := w.QueryRule(Rule{ + res := mustQueryRule(t, w, Rule{ Head: Predicate{join, []Term{hashVar("left"), hashVar("right")}}, Body: []Predicate{ {t1, []Term{hashVar("id"), hashVar("left")}}, @@ -127,7 +159,7 @@ func TestNumbers(t *testing.T) { t.Errorf("query failed:\n have: %s\n want: %s", dbg.FactSet(res), dbg.FactSet(expected)) } - res = w.QueryRule(Rule{ + res = mustQueryRule(t, w, Rule{ Head: Predicate{join, []Term{hashVar("left"), hashVar("right")}}, Body: []Predicate{ {t1, []Term{Variable(1234), hashVar("left")}}, @@ -159,14 +191,14 @@ func TestString(t *testing.T) { route := syms.Insert("route") suff := syms.Insert("route suffix") - w.AddFact(Fact{Predicate{route, []Term{Integer(0), app0, syms.Insert("example.com")}}}) - w.AddFact(Fact{Predicate{route, []Term{Integer(1), app1, syms.Insert("test.com")}}}) - w.AddFact(Fact{Predicate{route, []Term{Integer(2), app2, syms.Insert("test.fr")}}}) - w.AddFact(Fact{Predicate{route, []Term{Integer(3), app0, syms.Insert("www.example.com")}}}) - w.AddFact(Fact{Predicate{route, []Term{Integer(4), app1, syms.Insert("mx.example.com")}}}) + w.AddFact(testOrigin, Fact{Predicate{route, []Term{Integer(0), app0, syms.Insert("example.com")}}}) + w.AddFact(testOrigin, Fact{Predicate{route, []Term{Integer(1), app1, syms.Insert("test.com")}}}) + w.AddFact(testOrigin, Fact{Predicate{route, []Term{Integer(2), app2, syms.Insert("test.fr")}}}) + w.AddFact(testOrigin, Fact{Predicate{route, []Term{Integer(3), app0, syms.Insert("www.example.com")}}}) + w.AddFact(testOrigin, Fact{Predicate{route, []Term{Integer(4), app1, syms.Insert("mx.example.com")}}}) testSuffix := func(suffix string, syms *SymbolTable) *FactSet { - return w.QueryRule(Rule{ + return mustQueryRule(t, w, Rule{ Head: Predicate{suff, []Term{hashVar("app_id"), Variable(1234)}}, Body: []Predicate{{route, []Term{Variable(0), hashVar("app_id"), Variable(1234)}}}, Expressions: []Expression{{ @@ -209,10 +241,10 @@ func TestDate(t *testing.T) { before := syms.Insert("before") after := syms.Insert("after") - w.AddFact(Fact{Predicate{x, []Term{Date(t1.Unix()), abc}}}) - w.AddFact(Fact{Predicate{x, []Term{Date(t3.Unix()), def}}}) + w.AddFact(testOrigin, Fact{Predicate{x, []Term{Date(t1.Unix()), abc}}}) + w.AddFact(testOrigin, Fact{Predicate{x, []Term{Date(t3.Unix()), def}}}) - res := w.QueryRule(Rule{ + res := mustQueryRule(t, w, Rule{ Head: Predicate{before, []Term{Variable(1234), hashVar("val")}}, Body: []Predicate{{x, []Term{Variable(1234), hashVar("val")}}}, Expressions: []Expression{{ @@ -230,7 +262,7 @@ func TestDate(t *testing.T) { t.Errorf("before query failed:\n have: %s\n want: %s", dbg.FactSet(res), dbg.FactSet(expected)) } - res = w.QueryRule(Rule{ + res = mustQueryRule(t, w, Rule{ Head: Predicate{after, []Term{Variable(1234), hashVar("val")}}, Body: []Predicate{{x, []Term{Variable(1234), hashVar("val")}}}, Expressions: []Expression{{ @@ -279,11 +311,11 @@ func TestBytes(t *testing.T) { key := syms.Insert("pkey") keyMatch := syms.Insert("pkey match") - w.AddFact(Fact{Predicate{key, []Term{usr1, Bytes(k1)}}}) - w.AddFact(Fact{Predicate{key, []Term{usr2, Bytes(k2)}}}) - w.AddFact(Fact{Predicate{key, []Term{usr3, Bytes(k3)}}}) + w.AddFact(testOrigin, Fact{Predicate{key, []Term{usr1, Bytes(k1)}}}) + w.AddFact(testOrigin, Fact{Predicate{key, []Term{usr2, Bytes(k2)}}}) + w.AddFact(testOrigin, Fact{Predicate{key, []Term{usr3, Bytes(k3)}}}) - res := w.QueryRule(Rule{ + res := mustQueryRule(t, w, Rule{ Head: Predicate{keyMatch, []Term{hashVar("usr"), Variable(1)}}, Body: []Predicate{{key, []Term{hashVar("usr"), Variable(1)}}}, Expressions: []Expression{{ @@ -299,7 +331,7 @@ func TestBytes(t *testing.T) { t.Errorf("key equal query failed:\n have: %s\n want: %s", dbg.FactSet(res), dbg.FactSet(expected)) } - res = w.QueryRule(Rule{ + res = mustQueryRule(t, w, Rule{ Head: Predicate{keyMatch, []Term{hashVar("usr"), Variable(1)}}, Body: []Predicate{{key, []Term{hashVar("usr"), Variable(1)}}}, Expressions: []Expression{{ @@ -316,7 +348,7 @@ func TestBytes(t *testing.T) { t.Errorf("key in query failed:\n have: %s\n want: %s", dbg.FactSet(res), dbg.FactSet(expected)) } - res = w.QueryRule(Rule{ + res = mustQueryRule(t, w, Rule{ Head: Predicate{keyMatch, []Term{hashVar("usr"), Variable(1)}}, Body: []Predicate{{key, []Term{hashVar("usr"), Variable(1)}}}, Expressions: []Expression{{ @@ -350,14 +382,14 @@ func TestResource(t *testing.T) { read := syms.Insert("read") write := syms.Insert("write") - w.AddFact(Fact{Predicate{resource, []Term{ambient, file2}}}) - w.AddFact(Fact{Predicate{operation, []Term{ambient, write}}}) - w.AddFact(Fact{Predicate{right, []Term{authority, file1, read}}}) - w.AddFact(Fact{Predicate{right, []Term{authority, file2, read}}}) - w.AddFact(Fact{Predicate{right, []Term{authority, file1, write}}}) + w.AddFact(testOrigin, Fact{Predicate{resource, []Term{ambient, file2}}}) + w.AddFact(testOrigin, Fact{Predicate{operation, []Term{ambient, write}}}) + w.AddFact(testOrigin, Fact{Predicate{right, []Term{authority, file1, read}}}) + w.AddFact(testOrigin, Fact{Predicate{right, []Term{authority, file2, read}}}) + w.AddFact(testOrigin, Fact{Predicate{right, []Term{authority, file1, write}}}) check1 := syms.Insert("check1") - res := w.QueryRule(Rule{ + res := mustQueryRule(t, w, Rule{ Head: Predicate{check1, []Term{file1}}, Body: []Predicate{{resource, []Term{ambient, file1}}}, }, syms) @@ -376,7 +408,7 @@ func TestResource(t *testing.T) { }, } t.Logf("r2 = %s", dbg.Rule(r2)) - res = w.QueryRule(r2, syms) + res = mustQueryRule(t, w, r2, syms) if len(*res) > 0 { t.Errorf("unexpected facts: %s", dbg.FactSet(res)) } @@ -547,12 +579,12 @@ func TestWorldFactsWithBytesSets(t *testing.T) { keys := syms.Insert("keys") first := syms.Insert("first") - w.AddFact(Fact{Predicate{keys, []Term{Set{Bytes{0x01}, Bytes{0x02}}}}}) - w.AddFact(Fact{Predicate{keys, []Term{Set{Bytes{0x02}, Bytes{0x01}}}}}) - w.AddFact(Fact{Predicate{keys, []Term{Set{Bytes{0x03}}}}}) - require.Len(t, *w.Facts(), 2) + w.AddFact(testOrigin, Fact{Predicate{keys, []Term{Set{Bytes{0x01}, Bytes{0x02}}}}}) + w.AddFact(testOrigin, Fact{Predicate{keys, []Term{Set{Bytes{0x02}, Bytes{0x01}}}}}) + w.AddFact(testOrigin, Fact{Predicate{keys, []Term{Set{Bytes{0x03}}}}}) + require.Len(t, w.Facts()[0].Facts, 2) - w.AddRule(Rule{ + w.AddRule(0, testTrusted, Rule{ Head: Predicate{first, []Term{Variable(0)}}, Body: []Predicate{{keys, []Term{Variable(0)}}}, Expressions: []Expression{{ @@ -568,30 +600,84 @@ func TestWorldFactsWithBytesSets(t *testing.T) { {Predicate{keys, []Term{Set{Bytes{0x03}}}}}, {Predicate{first, []Term{Set{Bytes{0x01}, Bytes{0x02}}}}}, } - require.True(t, expected.Equal(w.Facts()), "have: %v", SymbolDebugger{syms}.FactSet(w.Facts())) + have := w.Query(testTrusted, Predicate{keys, []Term{Variable(0)}}) + *have = append(*have, *w.Query(testTrusted, Predicate{first, []Term{Variable(0)}})...) + require.True(t, expected.Equal(have), "have: %v", SymbolDebugger{syms}.FactSet(have)) } // Query used to compare terms with ==, which panics on slice-backed terms. +// check all: every match must satisfy the expressions, and there must be one. +func TestWorldQueryMatchAll(t *testing.T) { + syms := &SymbolTable{} + operation := syms.Insert("operation") + allowed := syms.Insert("allowed_operations") + a, b, invalid := syms.Insert("A"), syms.Insert("B"), syms.Insert("invalid") + + rule := Rule{ + Head: Predicate{syms.Insert("ok"), nil}, + Body: []Predicate{ + {operation, []Term{Variable(0)}}, + {allowed, []Term{Variable(1)}}, + }, + Expressions: []Expression{{ + Value{Variable(1)}, + Value{Variable(0)}, + BinaryOp{Contains{}}, + }}, + } + + world := func(ops ...Term) *World { + w := NewWorld() + w.AddFact(testOrigin, Fact{Predicate{allowed, []Term{Set{a, b}}}}) + for _, op := range ops { + w.AddFact(testOrigin, Fact{Predicate{operation, []Term{op}}}) + } + return w + } + + for _, tc := range []struct { + desc string + ops []Term + want bool + }{ + {"all allowed", []Term{a, b}, true}, + {"one not allowed", []Term{a, invalid}, false}, + {"no match", nil, false}, + } { + t.Run(tc.desc, func(t *testing.T) { + got, err := world(tc.ops...).QueryMatchAll(rule, testTrusted, syms) + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } + + // Without body predicates the expressions are evaluated once. + constant := Rule{Head: Predicate{syms.Insert("ok"), nil}, Expressions: []Expression{{Value{Bool(false)}}}} + got, err := NewWorld().QueryMatchAll(constant, testTrusted, syms) + require.NoError(t, err) + require.False(t, got) +} + func TestWorldQueryBytesAndSetTerms(t *testing.T) { w := NewWorld() syms := &SymbolTable{} key := syms.Insert("key") keys := syms.Insert("keys") - w.AddFact(Fact{Predicate{key, []Term{Bytes{0x01}}}}) - w.AddFact(Fact{Predicate{key, []Term{Bytes{0x02}}}}) - w.AddFact(Fact{Predicate{keys, []Term{Set{Bytes{0x01}, Bytes{0x02}}}}}) + w.AddFact(testOrigin, Fact{Predicate{key, []Term{Bytes{0x01}}}}) + w.AddFact(testOrigin, Fact{Predicate{key, []Term{Bytes{0x02}}}}) + w.AddFact(testOrigin, Fact{Predicate{keys, []Term{Set{Bytes{0x01}, Bytes{0x02}}}}}) - res := w.Query(Predicate{key, []Term{Bytes{0x01}}}) + res := w.Query(testTrusted, Predicate{key, []Term{Bytes{0x01}}}) require.True(t, (&FactSet{{Predicate{key, []Term{Bytes{0x01}}}}}).Equal(res)) - res = w.Query(Predicate{key, []Term{Bytes{0x03}}}) + res = w.Query(testTrusted, Predicate{key, []Term{Bytes{0x03}}}) require.Empty(t, *res) - res = w.Query(Predicate{keys, []Term{Set{Bytes{0x02}, Bytes{0x01}}}}) + res = w.Query(testTrusted, Predicate{keys, []Term{Set{Bytes{0x02}, Bytes{0x01}}}}) require.Len(t, *res, 1) - res = w.Query(Predicate{key, []Term{Variable(0)}}) + res = w.Query(testTrusted, Predicate{key, []Term{Variable(0)}}) require.Len(t, *res, 2) } @@ -653,9 +739,9 @@ func TestWorldRunLimits(t *testing.T) { for _, tc := range testCases { w := NewWorld(tc.opts...) - w.AddFact(Fact{Predicate{parent, []Term{a, b}}}) - w.AddFact(Fact{Predicate{parent, []Term{b, c}}}) - w.AddFact(Fact{Predicate{parent, []Term{c, d}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{a, b}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{b, c}}}) + w.AddFact(testOrigin, Fact{Predicate{parent, []Term{c, d}}}) r1 := Rule{ Head: Predicate{grandparent, []Term{hashVar("grandparent"), hashVar("grandchild")}}, @@ -665,7 +751,7 @@ func TestWorldRunLimits(t *testing.T) { }, } - w.AddRule(r1) + w.AddRule(0, testTrusted, r1) require.Equal(t, tc.expectedErr, w.Run(syms)) } } @@ -675,7 +761,7 @@ func TestWorldRunTimeoutWithoutRules(t *testing.T) { p := syms.Insert("p") w := NewWorld(WithMaxDuration(0)) - w.AddFact(Fact{Predicate{p, []Term{Integer(1)}}}) + w.AddFact(testOrigin, Fact{Predicate{p, []Term{Integer(1)}}}) if err := w.Run(syms); !errors.Is(err, ErrWorldRunLimitTimeout) { t.Fatalf("Run() = %v, want %v", err, ErrWorldRunLimitTimeout) @@ -693,13 +779,13 @@ func TestApplyInvalidRuleNoGoroutineLeak(t *testing.T) { Head: Predicate{p, []Term{hashVar("x"), hashVar("missing")}}, Body: []Predicate{{q, []Term{hashVar("x")}}}, } - facts := &FactSet{ - {Predicate{q, []Term{Integer(1)}}}, - {Predicate{q, []Term{Integer(2)}}}, + facts := []factWithOrigin{ + {testOrigin, Fact{Predicate{q, []Term{Integer(1)}}}}, + {testOrigin, Fact{Predicate{q, []Term{Integer(2)}}}}, } for range 100 { - err := r.Apply(facts, &FactSet{}, syms) + err := r.apply(0, facts, syms, nil, func(Origin, Fact) {}) require.IsType(t, InvalidRuleError{}, err) } } @@ -715,13 +801,13 @@ func TestWorldRunTimeoutMidRun(t *testing.T) { WithMaxIterations(1<<20), ) for i := range 20 { - w.AddFact(Fact{Predicate{edge, []Term{Integer(i), Integer(i + 1)}}}) + w.AddFact(testOrigin, Fact{Predicate{edge, []Term{Integer(i), Integer(i + 1)}}}) } - w.AddRule(Rule{ + w.AddRule(0, testTrusted, Rule{ Head: Predicate{path, []Term{Variable(0), Variable(1)}}, Body: []Predicate{{edge, []Term{Variable(0), Variable(1)}}}, }) - w.AddRule(Rule{ + w.AddRule(0, testTrusted, Rule{ Head: Predicate{path, []Term{Variable(0), Variable(2)}}, Body: []Predicate{ {path, []Term{Variable(0), Variable(1)}}, @@ -731,5 +817,5 @@ func TestWorldRunTimeoutMidRun(t *testing.T) { require.ErrorIs(t, w.Run(syms), ErrWorldRunLimitTimeout) // Exposes a race condition under -race if Run left a goroutine still writing facts. - _ = len(*w.Facts()) + _ = len(w.Facts()) } diff --git a/datalog/expressions.go b/datalog/expressions.go index a44aebd6..8d42c8f7 100644 --- a/datalog/expressions.go +++ b/datalog/expressions.go @@ -19,11 +19,22 @@ const maxStackSize = 1000 var ( ErrExprDivByZero = errors.New("datalog: Div by zero") ErrInt64Overflow = errors.New("datalog: expression overflowed int64") + // ErrShadowedVariable is returned when a closure parameter has the name + // of a variable already bound in the expression. + ErrShadowedVariable = errors.New("datalog: closure parameter shadows a variable") ) type Expression []Op +// Evaluate evaluates the expression without extern functions; an +// extern:: call fails with ErrUndefinedExtern. func (e *Expression) Evaluate(values map[Variable]*Term, symbols *SymbolTable) (Term, error) { + return e.EvaluateWith(values, symbols, nil) +} + +// EvaluateWith evaluates the expression with the variables bound to values; +// extern::name calls resolve against externs. +func (e *Expression) EvaluateWith(values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { s := &stack{} for _, op := range *e { @@ -39,21 +50,31 @@ func (e *Expression) Evaluate(values map[Variable]*Term, symbols *SymbolTable) ( id = *idptr default: // do nothing } - err := s.Push(id) + err := s.Push(stackElem{term: id}) if err != nil { return nil, fmt.Errorf("datalog: expressions: stack overflow") } + case OpTypeClosure: + closure := op.(Closure) + if err := s.Push(stackElem{closure: &closure}); err != nil { + return nil, fmt.Errorf("datalog: expressions: stack overflow") + } case OpTypeUnary: - v, err := s.Pop() + v, err := s.PopTerm() if err != nil { return nil, fmt.Errorf("datalog: expressions: failed to pop unary value: %w", err) } - res, err := op.(UnaryOp).Eval(v, symbols) + var res Term + if ffi, ok := op.(UnaryOp).UnaryOpFunc.(Ffi); ok { + res, err = externs.call(symbols, ffi.Name, v, nil) + } else { + res, err = op.(UnaryOp).Eval(v, symbols) + } if err != nil { return nil, fmt.Errorf("datalog: expressions: unary eval failed: %w", err) } - err = s.Push(res) + err = s.Push(stackElem{term: res}) if err != nil { return nil, fmt.Errorf("datalog: expressions: stack overflow") } @@ -67,11 +88,25 @@ func (e *Expression) Evaluate(values map[Variable]*Term, symbols *SymbolTable) ( return nil, fmt.Errorf("datalog: expressions: failed to pop binary left value: %w", err) } - res, err := op.(BinaryOp).Eval(left, right, symbols) + var res Term + switch { + case right.closure != nil && left.closure != nil: + return nil, errors.New("datalog: expressions: binary operator with two closures") + case right.closure != nil: + res, err = evalWithClosure(op.(BinaryOp), left.term, *right.closure, values, symbols, externs) + case left.closure != nil: + res, err = evalWithClosure(op.(BinaryOp), right.term, *left.closure, values, symbols, externs) + default: + if ffi, ok := op.(BinaryOp).BinaryOpFunc.(FfiBinary); ok { + res, err = externs.call(symbols, ffi.Name, left.term, right.term) + } else { + res, err = op.(BinaryOp).Eval(left.term, right.term, symbols) + } + } if err != nil { return nil, fmt.Errorf("datalog: expressions: binary eval failed: %w", err) } - err = s.Push(res) + err = s.Push(stackElem{term: res}) if err != nil { return nil, fmt.Errorf("datalog: expressions: stack overflow") } @@ -85,7 +120,28 @@ func (e *Expression) Evaluate(values map[Variable]*Term, symbols *SymbolTable) ( return nil, fmt.Errorf("datalog: expressions: invalid resulting stack: %#v", *s) } - return s.Pop() + return s.PopTerm() +} + +// evalWithClosure applies a binary operator with one operand a closure and +// the other the term. The closure runs with its parameters bound on top of +// the current variables; a parameter may not have the name of a bound +// variable. +func evalWithClosure(op BinaryOp, left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + f, ok := op.BinaryOpFunc.(ClosureOpFunc) + if !ok { + return nil, fmt.Errorf("datalog: %s does not take a closure", op.Print("", "")) + } + for _, p := range closure.Params { + if _, bound := values[p]; bound { + return nil, ErrShadowedVariable + } + } + scope := make(map[Variable]*Term, len(values)+len(closure.Params)) + for k, v := range values { + scope[k] = v + } + return f.EvalClosure(left, closure, scope, symbols, externs) } func (e *Expression) Print(symbols *SymbolTable) string { @@ -94,30 +150,24 @@ func (e *Expression) Print(symbols *SymbolTable) string { for _, op := range *e { switch op.Type() { case OpTypeValue: - id := op.(Value).ID - switch id.Type() { - case TermTypeString: - err := s.Push(fmt.Sprintf("\"%s\"", symbols.Str(id.(String)))) - if err != nil { - return "" - } - case TermTypeVariable: - err := s.Push(fmt.Sprintf("$%s", symbols.Var(id.(Variable)))) - if err != nil { - return "" - } - default: - err := s.Push(id.String()) - if err != nil { - return "" - } + if err := s.Push(SymbolDebugger{SymbolTable: symbols}.Term(op.(Value).ID)); err != nil { + return "" + } + case OpTypeClosure: + if err := s.Push(op.(Closure).Print(symbols)); err != nil { + return "" } case OpTypeUnary: v, err := s.Pop() if err != nil { return "" } - res := op.(UnaryOp).Print(v) + var res string + if ffi, ok := op.(UnaryOp).UnaryOpFunc.(Ffi); ok { + res = fmt.Sprintf("%s.extern::%s()", v, symbols.Str(ffi.Name)) + } else { + res = op.(UnaryOp).Print(v) + } err = s.Push(res) if err != nil { return "" @@ -131,7 +181,12 @@ func (e *Expression) Print(symbols *SymbolTable) string { if err != nil { return "" } - res := op.(BinaryOp).Print(left, right) + var res string + if ffi, ok := op.(BinaryOp).BinaryOpFunc.(FfiBinary); ok { + res = fmt.Sprintf("%s.extern::%s(%s)", left, symbols.Str(ffi.Name), right) + } else { + res = op.(BinaryOp).Print(left, right) + } err = s.Push(res) if err != nil { return "" @@ -158,12 +213,50 @@ const ( OpTypeValue OpType = iota OpTypeUnary OpTypeBinary + // OpTypeClosure is datalog v3.3. + OpTypeClosure ) type Op interface { Type() OpType } +// Closure is an expression evaluated by the operator that consumes it, with +// Params bound by that operator: the right side of a lazy && or ||, or the +// predicate of .all() and .any(). Datalog v3.3. +type Closure struct { + Params []Variable + Body Expression +} + +func (Closure) Type() OpType { + return OpTypeClosure +} + +// Print renders the closure as `$p -> body`, or just the body without +// parameters. +func (c Closure) Print(symbols *SymbolTable) string { + body := c.Body.Print(symbols) + if len(c.Params) == 0 { + return body + } + params := make([]string, len(c.Params)) + for i, p := range c.Params { + params[i] = "$" + symbols.Var(p) + } + return fmt.Sprintf("%s -> %s", strings.Join(params, ", "), body) +} + +// ClosureOpFunc is a binary operator with a closure operand: the right one +// for && || .all() .any(), the receiver for .try_or(). +type ClosureOpFunc interface { + BinaryOpFunc + // EvalClosure receives the term operand and the closure, and the + // variables of the expression, which it may extend with the closure + // parameters. + EvalClosure(left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) +} + type Value struct { ID Term } @@ -188,6 +281,10 @@ func (op UnaryOp) Print(value string) string { out = fmt.Sprintf("(%s)", value) case UnaryLength: out = fmt.Sprintf("%s.length()", value) + case UnaryTypeOf: + out = fmt.Sprintf("%s.type()", value) + case UnaryFfi: + out = fmt.Sprintf("%s.extern::()", value) default: out = fmt.Sprintf("unknown(%s)", value) } @@ -205,8 +302,58 @@ const ( UnaryNegate UnaryOpType = iota UnaryParens UnaryLength + // UnaryTypeOf and UnaryFfi are datalog v3.3. + UnaryTypeOf + UnaryFfi ) +// Ffi is the unary extern::name() call; Name is the symbol of the function +// name. It is evaluated by Expression.EvaluateWith, which holds the +// registered functions. +type Ffi struct { + Name String +} + +func (Ffi) Type() UnaryOpType { + return UnaryFfi +} +func (Ffi) Eval(Term, *SymbolTable) (Term, error) { + return nil, ErrUndefinedExtern +} + +// TypeOf is .type(): the name of the type of a value, as a String. +type TypeOf struct{} + +func (TypeOf) Type() UnaryOpType { + return UnaryTypeOf +} +func (TypeOf) Eval(value Term, symbols *SymbolTable) (Term, error) { + var name string + switch value.Type() { + case TermTypeInteger: + name = "integer" + case TermTypeString: + name = "string" + case TermTypeDate: + name = "date" + case TermTypeBytes: + name = "bytes" + case TermTypeBool: + name = "bool" + case TermTypeSet: + name = "set" + case TermTypeNull: + name = "null" + case TermTypeArray: + name = "array" + case TermTypeMap: + name = "map" + default: + return nil, fmt.Errorf("datalog: unexpected TypeOf value type: %d", value.Type()) + } + return symbols.Insert(name), nil +} + // Negate returns the negation of a value. // It only accepts a Bool value. type Negate struct{} @@ -255,6 +402,10 @@ func (Length) Eval(value Term, symbols *SymbolTable) (Term, error) { out = Integer(len(value.(Bytes))) case TermTypeSet: out = Integer(len(value.(Set))) + case TermTypeArray: + out = Integer(len(value.(Array))) + case TermTypeMap: + out = Integer(len(value.(Map))) default: return nil, fmt.Errorf("datalog: unexpected Length value type: %d", value.Type()) } @@ -280,7 +431,7 @@ func (op BinaryOp) Print(left, right string) string { case BinaryGreaterOrEqual: out = fmt.Sprintf("%s >= %s", left, right) case BinaryEqual: - out = fmt.Sprintf("%s == %s", left, right) + out = fmt.Sprintf("%s === %s", left, right) case BinaryContains: out = fmt.Sprintf("%s.contains(%s)", left, right) case BinaryPrefix: @@ -305,6 +456,32 @@ func (op BinaryOp) Print(left, right string) string { out = fmt.Sprintf("%s.intersection(%s)", left, right) case BinaryUnion: out = fmt.Sprintf("%s.union(%s)", left, right) + case BinaryBitwiseAnd: + out = fmt.Sprintf("%s & %s", left, right) + case BinaryBitwiseOr: + out = fmt.Sprintf("%s | %s", left, right) + case BinaryBitwiseXor: + out = fmt.Sprintf("%s ^ %s", left, right) + case BinaryNotEqual: + out = fmt.Sprintf("%s !== %s", left, right) + case BinaryHeterogeneousEqual: + out = fmt.Sprintf("%s == %s", left, right) + case BinaryHeterogeneousNotEqual: + out = fmt.Sprintf("%s != %s", left, right) + case BinaryLazyAnd: + out = fmt.Sprintf("%s && %s", left, right) + case BinaryLazyOr: + out = fmt.Sprintf("%s || %s", left, right) + case BinaryAll: + out = fmt.Sprintf("%s.all(%s)", left, right) + case BinaryAny: + out = fmt.Sprintf("%s.any(%s)", left, right) + case BinaryGet: + out = fmt.Sprintf("%s.get(%s)", left, right) + case BinaryFfi: + out = fmt.Sprintf("%s.extern::(%s)", left, right) + case BinaryTryOr: + out = fmt.Sprintf("%s.try_or(%s)", left, right) default: out = fmt.Sprintf("unknown(%s, %s)", left, right) } @@ -336,8 +513,35 @@ const ( BinaryOr BinaryIntersection BinaryUnion + // Datalog v3.1 operators. + BinaryBitwiseAnd + BinaryBitwiseOr + BinaryBitwiseXor + BinaryNotEqual + // Datalog v3.3 operators. + BinaryHeterogeneousEqual + BinaryHeterogeneousNotEqual + BinaryLazyAnd + BinaryLazyOr + BinaryAll + BinaryAny + BinaryGet + BinaryFfi + BinaryTryOr ) +// FfiBinary is the binary extern::name(right) call; see Ffi. +type FfiBinary struct { + Name String +} + +func (FfiBinary) Type() BinaryOpType { + return BinaryFfi +} +func (FfiBinary) Eval(Term, Term, *SymbolTable) (Term, error) { + return nil, ErrUndefinedExtern +} + // LessThan returns true when left is less than right. // It requires left and right to have the same concrete type // and only accepts Integer. @@ -462,6 +666,9 @@ func (Equal) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { case TermTypeDate: case TermTypeBool: case TermTypeSet: + case TermTypeNull: + case TermTypeArray: + case TermTypeMap: default: return nil, fmt.Errorf("datalog: unexpected Equal value type: %d", left.Type()) @@ -470,6 +677,49 @@ func (Equal) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { return Bool(left.Equal(right)), nil } +// NotEqual is the negation of Equal, with the same type requirements. +type NotEqual struct{} + +func (NotEqual) Type() BinaryOpType { + return BinaryNotEqual +} +func (NotEqual) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) { + equal, err := Equal{}.Eval(left, right, symbols) + if err != nil { + return nil, err + } + return Bool(!equal.(Bool)), nil +} + +// HeterogeneousEqual is == since datalog v3.3: values of different types +// are not equal instead of being a type error. Equal (===) keeps the strict +// behaviour. +type HeterogeneousEqual struct{} + +func (HeterogeneousEqual) Type() BinaryOpType { + return BinaryHeterogeneousEqual +} +func (HeterogeneousEqual) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) { + if left.Type() != right.Type() { + return Bool(false), nil + } + return Equal{}.Eval(left, right, symbols) +} + +// HeterogeneousNotEqual is != since datalog v3.3, the negation of +// HeterogeneousEqual. +type HeterogeneousNotEqual struct{} + +func (HeterogeneousNotEqual) Type() BinaryOpType { + return BinaryHeterogeneousNotEqual +} +func (HeterogeneousNotEqual) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) { + if left.Type() != right.Type() { + return Bool(true), nil + } + return NotEqual{}.Eval(left, right, symbols) +} + // Contains returns true when the right value exists in the left Set. // The right value must be an Integer, Bytes, String or Symbol. // The left value must be a Set, containing elements of right type. @@ -489,6 +739,14 @@ func (Contains) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) return Bool(strings.Contains(symbols.Str(sleft), symbols.Str(sright))), nil } + // An array contains an element, a map contains a key. + switch left := left.(type) { + case Array: + return Bool(left.contains(right)), nil + case Map: + return Bool(IsMapKey(right) && left.Get(right) != nil), nil + } + switch right.Type() { case TermTypeInteger: case TermTypeBytes: @@ -578,6 +836,13 @@ func (Prefix) Type() BinaryOpType { return BinaryPrefix } func (Prefix) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) { + if aleft, ok := left.(Array); ok { + aright, ok := right.(Array) + if !ok { + return nil, fmt.Errorf("datalog: Prefix requires right value to be an Array, got %T", right) + } + return Bool(aleft.HasPrefix(aright)), nil + } sleft, ok := left.(String) if !ok { return nil, fmt.Errorf("datalog: Prefix requires left value to be a String, got %T", left) @@ -598,6 +863,13 @@ func (Suffix) Type() BinaryOpType { return BinarySuffix } func (Suffix) Eval(left Term, right Term, symbols *SymbolTable) (Term, error) { + if aleft, ok := left.(Array); ok { + aright, ok := right.(Array) + if !ok { + return nil, fmt.Errorf("datalog: Suffix requires right value to be an Array, got %T", right) + } + return Bool(aleft.HasSuffix(aright)), nil + } sleft, ok := left.(String) if !ok { return nil, fmt.Errorf("datalog: Suffix requires left value to be a String, got %T", left) @@ -754,6 +1026,194 @@ func (Div) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { return Integer(ileft / iright), nil } +// LazyAnd is && since datalog v3.3: the right side is a closure, evaluated +// only when the left side is true. +type LazyAnd struct{} + +func (LazyAnd) Type() BinaryOpType { + return BinaryLazyAnd +} +func (LazyAnd) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + return nil, errors.New("datalog: && requires a closure as right value") +} +func (LazyAnd) EvalClosure(left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + b, ok := left.(Bool) + if !ok { + return nil, fmt.Errorf("datalog: && requires left value to be a Bool, got %T", left) + } + if len(closure.Params) != 0 { + return nil, errors.New("datalog: && takes a closure without parameters") + } + if !b { + return Bool(false), nil + } + return closure.Body.EvaluateWith(values, symbols, externs) +} + +// LazyOr is || since datalog v3.3: the right side is a closure, evaluated +// only when the left side is false. +type LazyOr struct{} + +func (LazyOr) Type() BinaryOpType { + return BinaryLazyOr +} +func (LazyOr) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + return nil, errors.New("datalog: || requires a closure as right value") +} +func (LazyOr) EvalClosure(left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + b, ok := left.(Bool) + if !ok { + return nil, fmt.Errorf("datalog: || requires left value to be a Bool, got %T", left) + } + if len(closure.Params) != 0 { + return nil, errors.New("datalog: || takes a closure without parameters") + } + if b { + return Bool(true), nil + } + return closure.Body.EvaluateWith(values, symbols, externs) +} + +// TryOr is receiver.try_or(fallback): the receiver is a closure without +// parameters; its value, or fallback when evaluating it fails. The fallback +// is an ordinary operand, so an error in it is not caught. +type TryOr struct{} + +func (TryOr) Type() BinaryOpType { + return BinaryTryOr +} +func (TryOr) Eval(Term, Term, *SymbolTable) (Term, error) { + return nil, errors.New("datalog: .try_or() receiver must be a closure") +} +func (TryOr) EvalClosure(fallback Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + if len(closure.Params) != 0 { + return nil, errors.New("datalog: .try_or() receiver takes no parameters") + } + res, err := closure.Body.EvaluateWith(values, symbols, externs) + if err != nil { + return fallback, nil + } + return res, nil +} + +// All is .all($x -> ...): true when the closure holds for every element of +// the left set. +type All struct{} + +func (All) Type() BinaryOpType { + return BinaryAll +} +func (All) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + return nil, errors.New("datalog: .all() requires a closure") +} +func (All) EvalClosure(left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + return forEachElement("all", left, closure, values, symbols, externs, func(res Bool) (Term, bool) { + if !res { + return Bool(false), true + } + return nil, false + }, Bool(true)) +} + +// Any is .any($x -> ...): true when the closure holds for one element of +// the left set. +type Any struct{} + +func (Any) Type() BinaryOpType { + return BinaryAny +} +func (Any) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + return nil, errors.New("datalog: .any() requires a closure") +} +func (Any) EvalClosure(left Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs) (Term, error) { + return forEachElement("any", left, closure, values, symbols, externs, func(res Bool) (Term, bool) { + if res { + return Bool(true), true + } + return nil, false + }, Bool(false)) +} + +// forEachElement evaluates the closure on each element of the collection, +// stopping when decide returns a result; otherwise the result is exhausted. +func forEachElement(name string, collection Term, closure Closure, values map[Variable]*Term, symbols *SymbolTable, externs ExternFuncs, decide func(Bool) (Term, bool), exhausted Term) (Term, error) { + if len(closure.Params) != 1 { + return nil, fmt.Errorf("datalog: .%s() takes a closure with one parameter", name) + } + elements, err := collectionElements(collection) + if err != nil { + return nil, fmt.Errorf("datalog: .%s(): %w", name, err) + } + param := closure.Params[0] + for _, element := range elements { + element := element + values[param] = &element + res, err := closure.Body.EvaluateWith(values, symbols, externs) + delete(values, param) + if err != nil { + return nil, err + } + b, ok := res.(Bool) + if !ok { + return nil, fmt.Errorf("datalog: .%s() closure must return a Bool, got %T", name, res) + } + if out, done := decide(b); done { + return out, nil + } + } + return exhausted, nil +} + +// collectionElements lists the elements a closure iterates over: the +// elements of a set or an array, or [key, value] arrays for a map. +func collectionElements(t Term) ([]Term, error) { + switch t := t.(type) { + case Set: + return t, nil + case Array: + return t, nil + case Map: + elements := make([]Term, len(t)) + for i, e := range t { + elements[i] = Array{e.Key, e.Value} + } + return elements, nil + default: + return nil, fmt.Errorf("requires a Set, an Array or a Map, got %T", t) + } +} + +// Get is .get(): the element of an array at an index, or the value of a map +// for a key; null when there is none. +type Get struct{} + +func (Get) Type() BinaryOpType { + return BinaryGet +} +func (Get) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + switch left := left.(type) { + case Array: + index, ok := right.(Integer) + if !ok { + return nil, fmt.Errorf("datalog: Get on an Array requires an Integer index, got %T", right) + } + if index < 0 || int64(index) >= int64(len(left)) { + return Null{}, nil + } + return left[index], nil + case Map: + if !IsMapKey(right) { + return nil, fmt.Errorf("datalog: Get on a Map requires an Integer or String key, got %T", right) + } + if v := left.Get(right); v != nil { + return v, nil + } + return Null{}, nil + default: + return nil, fmt.Errorf("datalog: Get requires left value to be an Array or a Map, got %T", left) + } +} + // And performs a logical AND between left and right and returns a Bool. // It requires left and right to be Bool. type And struct{} @@ -774,6 +1234,58 @@ func (And) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { return Bool(bleft && bright), nil } +// BitwiseAnd, BitwiseOr and BitwiseXor operate on two Integers. +type BitwiseAnd struct{} + +func (BitwiseAnd) Type() BinaryOpType { + return BinaryBitwiseAnd +} +func (BitwiseAnd) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + l, r, err := integerOperands("BitwiseAnd", left, right) + if err != nil { + return nil, err + } + return l & r, nil +} + +type BitwiseOr struct{} + +func (BitwiseOr) Type() BinaryOpType { + return BinaryBitwiseOr +} +func (BitwiseOr) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + l, r, err := integerOperands("BitwiseOr", left, right) + if err != nil { + return nil, err + } + return l | r, nil +} + +type BitwiseXor struct{} + +func (BitwiseXor) Type() BinaryOpType { + return BinaryBitwiseXor +} +func (BitwiseXor) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + l, r, err := integerOperands("BitwiseXor", left, right) + if err != nil { + return nil, err + } + return l ^ r, nil +} + +func integerOperands(op string, left, right Term) (Integer, Integer, error) { + l, ok := left.(Integer) + if !ok { + return 0, 0, fmt.Errorf("datalog: %s requires left value to be an Integer, got %T", op, left) + } + r, ok := right.(Integer) + if !ok { + return 0, 0, fmt.Errorf("datalog: %s requires right value to be an Integer, got %T", op, right) + } + return l, r, nil +} + // Or performs a logical OR between left and right and returns a Bool. // It requires left and right to be Bool. type Or struct{} @@ -794,9 +1306,15 @@ func (Or) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { return Bool(bleft || bright), nil } -type stack []Term +// stackElem is a term, or a closure waiting for the operator that consumes it. +type stackElem struct { + term Term + closure *Closure +} + +type stack []stackElem -func (s *stack) Push(v Term) error { +func (s *stack) Push(v stackElem) error { if len(*s) >= maxStackSize { return errors.New("stack overflow") } @@ -806,9 +1324,9 @@ func (s *stack) Push(v Term) error { return nil } -func (s *stack) Pop() (Term, error) { +func (s *stack) Pop() (stackElem, error) { if len(*s) == 0 { - return nil, errors.New("cannot pop from empty stack") + return stackElem{}, errors.New("cannot pop from empty stack") } e := (*s)[len(*s)-1] @@ -817,6 +1335,18 @@ func (s *stack) Pop() (Term, error) { return e, nil } +// PopTerm pops an element that must be a term. +func (s *stack) PopTerm() (Term, error) { + e, err := s.Pop() + if err != nil { + return nil, err + } + if e.closure != nil { + return nil, errors.New("closure where a value was expected") + } + return e.term, nil +} + type stringstack []string func (s *stringstack) Push(v string) error { diff --git a/datalog/expressions_test.go b/datalog/expressions_test.go index 500c6b4a..cfa0a172 100644 --- a/datalog/expressions_test.go +++ b/datalog/expressions_test.go @@ -428,6 +428,106 @@ func TestBinaryGreaterOrEqual(t *testing.T) { } } +func TestBinaryNotEqual(t *testing.T) { + require.Equal(t, BinaryNotEqual, NotEqual{}.Type()) + syms := &SymbolTable{} + + for _, tc := range []struct { + desc string + left, right Term + res Bool + expectedErr bool + }{ + {desc: "different integers", left: Integer(1), right: Integer(3), res: true}, + {desc: "same integers", left: Integer(3), right: Integer(3), res: false}, + {desc: "different strings", left: syms.Insert("abcD12x"), right: syms.Insert("abcD12"), res: true}, + {desc: "different bytes", left: Bytes{0x12, 0xab, 0xcd}, right: Bytes{0x12, 0xab}, res: true}, + {desc: "different dates", left: Date(2), right: Date(1), res: true}, + {desc: "different bools", left: Bool(true), right: Bool(false), res: true}, + {desc: "different sets", left: Set{Integer(1), Integer(4)}, right: Set{Integer(1), Integer(2)}, res: true}, + {desc: "same sets", left: Set{Integer(1), Integer(2)}, right: Set{Integer(2), Integer(1)}, res: false}, + {desc: "type mismatch errors", left: Integer(1), right: syms.Insert("1"), expectedErr: true}, + } { + t.Run(tc.desc, func(t *testing.T) { + expr := Expression{Value{tc.left}, Value{tc.right}, BinaryOp{NotEqual{}}} + res, err := expr.Evaluate(nil, syms) + if tc.expectedErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.res, res) + }) + } +} + +// == and != compare across types since datalog v3.3: different types are +// not equal, where === and !== are a type error. +func TestBinaryHeterogeneousEqual(t *testing.T) { + syms := &SymbolTable{} + require.Equal(t, BinaryHeterogeneousEqual, HeterogeneousEqual{}.Type()) + require.Equal(t, BinaryHeterogeneousNotEqual, HeterogeneousNotEqual{}.Type()) + + for _, tc := range []struct { + left, right Term + equal bool + }{ + {Integer(1), Integer(1), true}, + {Integer(1), Integer(3), false}, + {Integer(1), Bool(true), false}, + {syms.Insert("abcD12x"), Bool(true), false}, + {Bytes{0x12}, Bytes{0x12}, true}, + {Set{Integer(1), Integer(4)}, Set{Integer(1), Integer(2)}, false}, + {Set{Integer(1), Integer(4)}, Bool(true), false}, + } { + eq := Expression{Value{tc.left}, Value{tc.right}, BinaryOp{HeterogeneousEqual{}}} + res, err := eq.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, Bool(tc.equal), res, "%v == %v", tc.left, tc.right) + + ne := Expression{Value{tc.left}, Value{tc.right}, BinaryOp{HeterogeneousNotEqual{}}} + res, err = ne.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, Bool(!tc.equal), res, "%v != %v", tc.left, tc.right) + } + + strict := Expression{Value{Integer(1)}, Value{Bool(true)}, BinaryOp{Equal{}}} + _, err := strict.Evaluate(nil, syms) + require.Error(t, err) + + printed := Expression{Value{Integer(1)}, Value{Bool(true)}, BinaryOp{HeterogeneousNotEqual{}}} + require.Equal(t, "1 != true", printed.Print(syms)) +} + +func TestBinaryBitwise(t *testing.T) { + syms := &SymbolTable{} + for _, tc := range []struct { + op BinaryOpFunc + opType BinaryOpType + left, right Term + res Integer + printed string + }{ + {BitwiseAnd{}, BinaryBitwiseAnd, Integer(6), Integer(3), 2, "6 & 3"}, + {BitwiseOr{}, BinaryBitwiseOr, Integer(1), Integer(2), 3, "1 | 2"}, + {BitwiseXor{}, BinaryBitwiseXor, Integer(3), Integer(3), 0, "3 ^ 3"}, + {BitwiseAnd{}, BinaryBitwiseAnd, Integer(-1), Integer(255), 255, "-1 & 255"}, + } { + t.Run(tc.printed, func(t *testing.T) { + require.Equal(t, tc.opType, tc.op.Type()) + expr := Expression{Value{tc.left}, Value{tc.right}, BinaryOp{tc.op}} + res, err := expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, tc.res, res) + require.Equal(t, tc.printed, expr.Print(syms)) + + mismatch := Expression{Value{tc.left}, Value{Bool(true)}, BinaryOp{tc.op}} + _, err = mismatch.Evaluate(nil, syms) + require.Error(t, err) + }) + } +} + func TestBinaryEqual(t *testing.T) { require.Equal(t, BinaryEqual, Equal{}.Type()) syms := &SymbolTable{} @@ -1240,3 +1340,203 @@ func TestPrint(t *testing.T) { }) } } + +// Closures: the right side of && and || is evaluated only when needed, and +// .all() / .any() bind their parameter to each element of a set. +func TestClosures(t *testing.T) { + syms := &SymbolTable{} + p := Variable(syms.Insert("p")) + q := Variable(syms.Insert("q")) + // A closure body that errors, to observe laziness. + errorBody := Expression{Value{syms.Insert("x")}, Value{syms.Insert("x")}, BinaryOp{Intersection{}}} + closure := func(params []Variable, body Expression) Closure { return Closure{Params: params, Body: body} } + + for _, tc := range []struct { + desc string + expr Expression + want Term + printed string + }{ + {"and short-circuits", Expression{Value{Bool(false)}, closure(nil, errorBody), BinaryOp{LazyAnd{}}}, Bool(false), `false && "x".intersection("x")`}, + {"and evaluates", Expression{Value{Bool(true)}, closure(nil, Expression{Value{Bool(true)}}), BinaryOp{LazyAnd{}}}, Bool(true), "true && true"}, + {"or short-circuits", Expression{Value{Bool(true)}, closure(nil, errorBody), BinaryOp{LazyOr{}}}, Bool(true), `true || "x".intersection("x")`}, + {"or evaluates", Expression{Value{Bool(false)}, closure(nil, Expression{Value{Bool(true)}}), BinaryOp{LazyOr{}}}, Bool(true), "false || true"}, + {"all true", Expression{Value{Set{Integer(1), Integer(2), Integer(3)}}, closure([]Variable{p}, Expression{Value{p}, Value{Integer(0)}, BinaryOp{GreaterThan{}}}), BinaryOp{All{}}}, Bool(true), "{1, 2, 3}.all($p -> $p > 0)"}, + {"all false", Expression{Value{Set{Integer(1), Integer(2), Integer(3)}}, closure([]Variable{p}, Expression{Value{p}, Value{Integer(2)}, BinaryOp{HeterogeneousEqual{}}}), BinaryOp{All{}}}, Bool(false), "{1, 2, 3}.all($p -> $p == 2)"}, + {"any true", Expression{Value{Set{Integer(1), Integer(2), Integer(3)}}, closure([]Variable{p}, Expression{Value{p}, Value{Integer(2)}, BinaryOp{GreaterThan{}}}), BinaryOp{Any{}}}, Bool(true), "{1, 2, 3}.any($p -> $p > 2)"}, + {"any false", Expression{Value{Set{Integer(1), Integer(2), Integer(3)}}, closure([]Variable{p}, Expression{Value{p}, Value{Integer(3)}, BinaryOp{GreaterThan{}}}), BinaryOp{Any{}}}, Bool(false), "{1, 2, 3}.any($p -> $p > 3)"}, + {"nested closures", Expression{ + Value{Set{Integer(1), Integer(2), Integer(3)}}, + closure([]Variable{p}, Expression{ + Value{p}, Value{Integer(1)}, BinaryOp{GreaterThan{}}, + closure(nil, Expression{ + Value{Set{Integer(3), Integer(4), Integer(5)}}, + closure([]Variable{q}, Expression{Value{p}, Value{q}, BinaryOp{HeterogeneousEqual{}}}), + BinaryOp{Any{}}, + }), + BinaryOp{LazyAnd{}}, + }), + BinaryOp{Any{}}, + }, Bool(true), "{1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))"}, + } { + t.Run(tc.desc, func(t *testing.T) { + res, err := tc.expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, tc.want, res) + require.Equal(t, tc.printed, tc.expr.Print(syms)) + }) + } + + // A closure parameter may not shadow a bound variable. + shadow := Expression{Value{Set{Integer(1)}}, closure([]Variable{p}, Expression{Value{p}}), BinaryOp{Any{}}} + var one Term = Integer(1) + _, err := shadow.Evaluate(map[Variable]*Term{p: &one}, syms) + require.ErrorIs(t, err, ErrShadowedVariable) + + // Errors inside an evaluated closure propagate. + failing := Expression{Value{Bool(true)}, closure(nil, errorBody), BinaryOp{LazyAnd{}}} + _, err = failing.Evaluate(nil, syms) + require.Error(t, err) + + // A closure where a value is expected, and a value where a closure is. + _, err = (&Expression{closure(nil, Expression{Value{Bool(true)}}), UnaryOp{Negate{}}}).Evaluate(nil, syms) + require.Error(t, err) + _, err = (&Expression{Value{Bool(true)}, Value{Bool(true)}, BinaryOp{LazyAnd{}}}).Evaluate(nil, syms) + require.Error(t, err) + _, err = (&Expression{Value{Bool(true)}, closure(nil, Expression{Value{Bool(true)}}), BinaryOp{Add{}}}).Evaluate(nil, syms) + require.Error(t, err) +} + +func TestUnaryTypeOf(t *testing.T) { + syms := &SymbolTable{} + require.Equal(t, UnaryTypeOf, TypeOf{}.Type()) + for _, tc := range []struct { + value Term + name string + }{ + {Integer(1), "integer"}, + {syms.Insert("test"), "string"}, + {Date(1), "date"}, + {Bytes{0xaa}, "bytes"}, + {Bool(true), "bool"}, + {Set{Bool(false), Bool(true)}, "set"}, + {Null{}, "null"}, + {Array{Integer(1)}, "array"}, + {NewMap(MapEntry{syms.Insert("a"), Bool(true)}), "map"}, + } { + expr := Expression{Value{tc.value}, UnaryOp{TypeOf{}}} + res, err := expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, syms.Insert(tc.name), res, tc.name) + require.Equal(t, dbgTerm(syms, tc.value)+".type()", expr.Print(syms)) + } + + // A variable: evaluated on its value. + var one Term = Integer(1) + v := Variable(syms.Insert("t")) + expr := Expression{Value{v}, UnaryOp{TypeOf{}}, Value{syms.Insert("integer")}, BinaryOp{HeterogeneousEqual{}}} + res, err := expr.Evaluate(map[Variable]*Term{v: &one}, syms) + require.NoError(t, err) + require.Equal(t, Bool(true), res) +} + +func dbgTerm(syms *SymbolTable, t Term) string { + return SymbolDebugger{SymbolTable: syms}.Term(t) +} + +func TestExternFuncs(t *testing.T) { + syms := &SymbolTable{} + name := syms.Insert("test") + externs := ExternFuncs{ + "test": func(symbols *SymbolTable, left Term, right Term) (Term, error) { + if right == nil { + return left, nil + } + if left == right { + return symbols.Insert("equal"), nil + } + return nil, errors.New("unsupported operands") + }, + } + + unary := Expression{Value{Bool(true)}, UnaryOp{Ffi{Name: name}}} + require.Equal(t, "true.extern::test()", unary.Print(syms)) + res, err := unary.EvaluateWith(nil, syms, externs) + require.NoError(t, err) + require.Equal(t, Bool(true), res) + + binary := Expression{Value{syms.Insert("a")}, Value{syms.Insert("a")}, BinaryOp{FfiBinary{Name: name}}} + require.Equal(t, `"a".extern::test("a")`, binary.Print(syms)) + res, err = binary.EvaluateWith(nil, syms, externs) + require.NoError(t, err) + require.Equal(t, syms.Insert("equal"), res) + + // The function error is reported. + failing := Expression{Value{Integer(1)}, Value{Integer(2)}, BinaryOp{FfiBinary{Name: name}}} + _, err = failing.EvaluateWith(nil, syms, externs) + require.ErrorContains(t, err, "unsupported operands") + + // Unknown name, and no registry at all. + unknown := Expression{Value{Bool(true)}, UnaryOp{Ffi{Name: syms.Insert("other")}}} + _, err = unknown.EvaluateWith(nil, syms, externs) + require.ErrorIs(t, err, ErrUndefinedExtern) + _, err = unary.Evaluate(nil, syms) + require.ErrorIs(t, err, ErrUndefinedExtern) + + // Extern functions are visible inside closures. + inClosure := Expression{ + Value{Set{Bool(true)}}, + Closure{Params: []Variable{Variable(syms.Insert("p"))}, Body: Expression{Value{Variable(syms.Insert("p"))}, UnaryOp{Ffi{Name: name}}}}, + BinaryOp{All{}}, + } + res, err = inClosure.EvaluateWith(map[Variable]*Term{}, syms, externs) + require.NoError(t, err) + require.Equal(t, Bool(true), res) +} + +func TestTryOr(t *testing.T) { + syms := &SymbolTable{} + failing := Expression{Value{Bool(true)}, Value{Integer(12)}, BinaryOp{Equal{}}, UnaryOp{Parens{}}} + + // The receiver fails: the fallback is returned. + expr := Expression{Closure{Body: failing}, Value{Bool(true)}, BinaryOp{TryOr{}}} + require.Equal(t, "(true === 12).try_or(true)", expr.Print(syms)) + res, err := expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, Bool(true), res) + + // The receiver succeeds: its value is returned. + expr = Expression{Closure{Body: Expression{Value{Integer(1)}, Value{Integer(2)}, BinaryOp{Add{}}}}, Value{Integer(0)}, BinaryOp{TryOr{}}} + require.Equal(t, "1 + 2.try_or(0)", expr.Print(syms)) + res, err = expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, Integer(3), res) + + // The fallback is an ordinary operand: its error is not caught. + expr = Expression{Closure{Body: Expression{Value{Bool(true)}}}, Value{Bool(true)}, Value{Integer(12)}, BinaryOp{Equal{}}, BinaryOp{TryOr{}}} + require.Equal(t, "true.try_or(true === 12)", expr.Print(syms)) + _, err = expr.Evaluate(nil, syms) + require.Error(t, err) + + // Nested: an error in the inner fallback is caught by the outer try_or. + inner := Expression{Closure{Body: failing}, Value{Bool(true)}, Value{Integer(12)}, BinaryOp{Equal{}}, BinaryOp{TryOr{}}, UnaryOp{Parens{}}} + expr = Expression{Closure{Body: inner}, Value{Bool(true)}, BinaryOp{TryOr{}}} + require.Equal(t, "((true === 12).try_or(true === 12)).try_or(true)", expr.Print(syms)) + res, err = expr.Evaluate(nil, syms) + require.NoError(t, err) + require.Equal(t, Bool(true), res) + + // Variables of the expression are visible in the receiver. + var one Term = Integer(1) + v := Variable(syms.Insert("x")) + expr = Expression{Closure{Body: Expression{Value{v}, Value{Integer(1)}, BinaryOp{Add{}}}}, Value{Integer(0)}, BinaryOp{TryOr{}}} + res, err = expr.Evaluate(map[Variable]*Term{v: &one}, syms) + require.NoError(t, err) + require.Equal(t, Integer(2), res) + + // Without a closure receiver, or with a receiver taking parameters. + _, err = (&Expression{Value{Bool(true)}, Value{Bool(false)}, BinaryOp{TryOr{}}}).Evaluate(nil, syms) + require.ErrorContains(t, err, "must be a closure") + _, err = (&Expression{Closure{Params: []Variable{v}, Body: Expression{Value{v}}}, Value{Bool(false)}, BinaryOp{TryOr{}}}).Evaluate(map[Variable]*Term{}, syms) + require.ErrorContains(t, err, "takes no parameters") +} diff --git a/datalog/extern.go b/datalog/extern.go new file mode 100644 index 00000000..6ec72985 --- /dev/null +++ b/datalog/extern.go @@ -0,0 +1,35 @@ +package datalog + +import ( + "errors" + "fmt" +) + +// ErrUndefinedExtern is returned when an expression calls an extern function +// that is not registered. +var ErrUndefinedExtern = errors.New("datalog: undefined extern function") + +// ExternFunc is a function called by the extern::name operators of datalog +// v3.3. For the unary form `left.extern::name()` right is nil; for the +// binary form `left.extern::name(right)` it is set. Strings are symbols of +// the table, which the function may extend for its result. +type ExternFunc func(symbols *SymbolTable, left Term, right Term) (Term, error) + +// ExternFuncs maps function names to their implementation. +type ExternFuncs map[string]ExternFunc + +func (e ExternFuncs) call(symbols *SymbolTable, name String, left Term, right Term) (Term, error) { + fname := symbols.Str(name) + f, ok := e[fname] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrUndefinedExtern, fname) + } + res, err := f(symbols, left, right) + if err != nil { + return nil, fmt.Errorf("datalog: extern function %s: %w", fname, err) + } + if res == nil { + return nil, fmt.Errorf("datalog: extern function %s returned no value", fname) + } + return res, nil +} diff --git a/datalog/origin.go b/datalog/origin.go new file mode 100644 index 00000000..50d0136c --- /dev/null +++ b/datalog/origin.go @@ -0,0 +1,105 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "fmt" + "math" + "slices" + "strconv" + "strings" +) + +// BlockID identifies the source of a fact or rule: the authority block is 0, +// the following blocks count up, and the authorizer is AuthorizerBlockID. +type BlockID uint64 + +const AuthorizerBlockID BlockID = math.MaxUint64 + +func (b BlockID) String() string { + if b == AuthorizerBlockID { + return "authorizer" + } + return strconv.FormatUint(uint64(b), 10) +} + +// Origin is the set of blocks a fact derives from: the block that stated it, +// or for a derived fact the blocks of every fact the rule matched plus the +// block of the rule. It is sorted and has no duplicates. +type Origin []BlockID + +func NewOrigin(ids ...BlockID) Origin { + return Origin(nil).With(ids...) +} + +// With returns a copy of the origin extended with the given blocks. +func (o Origin) With(ids ...BlockID) Origin { + res := make(Origin, 0, len(o)+len(ids)) + res = append(res, o...) + res = append(res, ids...) + slices.Sort(res) + return slices.Compact(res) +} + +func (o Origin) Union(other Origin) Origin { + return o.With(other...) +} + +func (o Origin) Equal(other Origin) bool { + return slices.Equal(o, other) +} + +// IsSubsetOf reports whether every block of o is in other. +func (o Origin) IsSubsetOf(other Origin) bool { + for _, id := range o { + if _, found := slices.BinarySearch(other, id); !found { + return false + } + } + return true +} + +// Compare orders origins by their block ids; the authorizer sorts last. +func (o Origin) Compare(other Origin) int { + return slices.Compare(o, other) +} + +func (o Origin) String() string { + ids := make([]string, len(o)) + for i, id := range o { + ids[i] = id.String() + } + return fmt.Sprintf("[%s]", strings.Join(ids, ", ")) +} + +// TrustedOrigins is the set of blocks a rule, check or policy may read facts +// from. A fact is visible when its whole origin is trusted. +type TrustedOrigins Origin + +// DefaultTrustedOrigins is what a rule trusts without an explicit scope: the +// authority block and the authorizer. The rule's own block is added by the +// caller. +func DefaultTrustedOrigins() TrustedOrigins { + return TrustedOrigins(NewOrigin(0, AuthorizerBlockID)) +} + +func NewTrustedOrigins(ids ...BlockID) TrustedOrigins { + return TrustedOrigins(NewOrigin(ids...)) +} + +func (t TrustedOrigins) With(ids ...BlockID) TrustedOrigins { + return TrustedOrigins(Origin(t).With(ids...)) +} + +func (t TrustedOrigins) Contains(o Origin) bool { + return o.IsSubsetOf(Origin(t)) +} + +func (t TrustedOrigins) Equal(other TrustedOrigins) bool { + return Origin(t).Equal(Origin(other)) +} + +func (t TrustedOrigins) String() string { + return Origin(t).String() +} diff --git a/datalog/origin_test.go b/datalog/origin_test.go new file mode 100644 index 00000000..a21a1d78 --- /dev/null +++ b/datalog/origin_test.go @@ -0,0 +1,127 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOrigin(t *testing.T) { + o := NewOrigin(2, 0, 2) + require.Equal(t, Origin{0, 2}, o) + require.Equal(t, Origin{0, 1, 2}, o.With(1)) + require.Equal(t, Origin{0, 2}, o, "With must not modify the receiver") + require.Equal(t, Origin{0, 2, AuthorizerBlockID}, o.Union(NewOrigin(AuthorizerBlockID))) + require.Equal(t, "[0, 2]", o.String()) + require.Equal(t, "[authorizer]", NewOrigin(AuthorizerBlockID).String()) + + require.True(t, NewOrigin(0).IsSubsetOf(o)) + require.True(t, Origin(nil).IsSubsetOf(o)) + require.False(t, NewOrigin(1).IsSubsetOf(o)) + + trusted := DefaultTrustedOrigins().With(1) + require.True(t, trusted.Contains(NewOrigin(0, 1))) + require.False(t, trusted.Contains(NewOrigin(1, 2))) +} + +// A rule only reads the facts of the origins it trusts, and the facts it +// derives carry the origins of what they were built from plus the rule's +// block. +func TestWorldOrigins(t *testing.T) { + syms := &SymbolTable{} + dbg := SymbolDebugger{syms} + user := syms.Insert("user") + group := syms.Insert("group") + member := syms.Insert("member") + alice := syms.Insert("alice") + admin := syms.Insert("admin") + u, g := Variable(syms.Insert("u")), Variable(syms.Insert("g")) + + w := NewWorld() + w.AddFact(NewOrigin(0), Fact{Predicate{user, []Term{alice}}}) + w.AddFact(NewOrigin(1), Fact{Predicate{group, []Term{alice, admin}}}) + + // member($u, $g) <- user($u), group($u, $g) + rule := Rule{ + Head: Predicate{member, []Term{u, g}}, + Body: []Predicate{ + {user, []Term{u}}, + {group, []Term{u, g}}, + }, + } + + // From block 2 with the default scope, block 1 is not visible. + w.AddRule(2, DefaultTrustedOrigins().With(2), rule) + require.NoError(t, w.Run(syms)) + require.Equal(t, []OriginFacts{ + {NewOrigin(0), FactSet{{Predicate{user, []Term{alice}}}}}, + {NewOrigin(1), FactSet{{Predicate{group, []Term{alice, admin}}}}}, + }, w.Facts()) + + // Trusting block 1 as well, the derived fact comes from blocks 0, 1 and 2. + w.ResetRules() + w.AddRule(2, DefaultTrustedOrigins().With(1, 2), rule) + require.NoError(t, w.Run(syms)) + require.Equal(t, []OriginFacts{ + {NewOrigin(0), FactSet{{Predicate{user, []Term{alice}}}}}, + {NewOrigin(0, 1, 2), FactSet{{Predicate{member, []Term{alice, admin}}}}}, + {NewOrigin(1), FactSet{{Predicate{group, []Term{alice, admin}}}}}, + }, w.Facts()) + + // Queries follow the same visibility. + res, err := w.QueryRule(rule, AuthorizerBlockID, DefaultTrustedOrigins(), syms) + require.NoError(t, err) + require.Empty(t, *res) + res, err = w.QueryRule(rule, AuthorizerBlockID, DefaultTrustedOrigins().With(1), syms) + require.NoError(t, err) + require.Len(t, *res, 1) + + require.Len(t, *w.Query(DefaultTrustedOrigins().With(1, 2), Predicate{member, []Term{u, g}}), 1) + require.Empty(t, *w.Query(DefaultTrustedOrigins().With(1), Predicate{member, []Term{u, g}})) + + require.Equal(t, `// Facts: +// origin: [0] +user("alice"); +// origin: [0, 1, 2] +member("alice", "admin"); +// origin: [1] +group("alice", "admin"); + +// Rules: +// origin: 2 +member($u, $g) <- user($u), group($u, $g); +`, dbg.World(w)) +} + +// The same fact stated by two blocks is kept once per origin. +func TestWorldAddFactPerOrigin(t *testing.T) { + syms := &SymbolTable{} + f := Fact{Predicate{syms.Insert("f"), nil}} + w := NewWorld() + require.True(t, w.AddFact(NewOrigin(0), f)) + require.False(t, w.AddFact(NewOrigin(0), f)) + require.True(t, w.AddFact(NewOrigin(1), f)) + require.Equal(t, 2, w.factCount()) +} + +// Clone must not share fact storage with the original. +func TestWorldCloneIsIndependent(t *testing.T) { + syms := &SymbolTable{} + a := Fact{Predicate{syms.Insert("a"), nil}} + b := Fact{Predicate{syms.Insert("b"), nil}} + c := Fact{Predicate{syms.Insert("c"), nil}} + + w := NewWorld() + w.AddFact(NewOrigin(0), a) + w1 := w.Clone() + w2 := w.Clone() + w1.AddFact(NewOrigin(0), b) + w2.AddFact(NewOrigin(0), c) + + require.Equal(t, FactSet{a}, w.Facts()[0].Facts) + require.Equal(t, FactSet{a, b}, w1.Facts()[0].Facts) + require.Equal(t, FactSet{a, c}, w2.Facts()[0].Facts) +} diff --git a/datalog/scope.go b/datalog/scope.go new file mode 100644 index 00000000..edf84819 --- /dev/null +++ b/datalog/scope.go @@ -0,0 +1,128 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "bytes" + "encoding/hex" + "fmt" + "strings" +) + +// Algorithm of a public key, numbered as in the schema. +type Algorithm int32 + +const ( + AlgorithmEd25519 Algorithm = 0 + AlgorithmSecp256r1 Algorithm = 1 +) + +func (a Algorithm) String() string { + switch a { + case AlgorithmEd25519: + return "ed25519" + case AlgorithmSecp256r1: + return "secp256r1" + default: + return fmt.Sprintf("algorithm(%d)", int32(a)) + } +} + +// PublicKey is a public key as it appears in a scope: the algorithm and the +// key bytes in the encoding of the spec. +type PublicKey struct { + Algorithm Algorithm + Key []byte +} + +func (k PublicKey) Equal(other PublicKey) bool { + return k.Algorithm == other.Algorithm && bytes.Equal(k.Key, other.Key) +} + +// String prints the key as the datalog syntax does: algorithm/hex. +func (k PublicKey) String() string { + return k.Algorithm.String() + "/" + hex.EncodeToString(k.Key) +} + +// ScopeKind is the kind of a scope entry in a `trusting` clause. +type ScopeKind byte + +const ( + // ScopeAuthority trusts the authority block. + ScopeAuthority ScopeKind = iota + // ScopePrevious trusts every block up to the current one. + ScopePrevious + // ScopePublicKey trusts the third-party blocks signed by PublicKey. + ScopePublicKey +) + +// Scope is one entry of a `trusting` clause. A rule with scopes reads the +// facts of the authorizer, of its own block and of the blocks its scopes +// designate; without scopes it inherits the scopes of its block, or the +// defaults. +type Scope struct { + Kind ScopeKind + PublicKey PublicKey +} + +func (s Scope) Equal(other Scope) bool { + return s.Kind == other.Kind && s.PublicKey.Equal(other.PublicKey) +} + +func (s Scope) String() string { + switch s.Kind { + case ScopeAuthority: + return "authority" + case ScopePrevious: + return "previous" + case ScopePublicKey: + return s.PublicKey.String() + default: + return fmt.Sprintf("scope(%d)", s.Kind) + } +} + +// ScopesString prints a `trusting` clause, empty when there are no scopes. +func ScopesString(scopes []Scope) string { + if len(scopes) == 0 { + return "" + } + strs := make([]string, len(scopes)) + for i, s := range scopes { + strs[i] = s.String() + } + return " trusting " + strings.Join(strs, ", ") +} + +// TrustedOriginsFromScopes resolves the scopes of a rule, check or policy +// from block current into the blocks it may read facts from. Without scopes +// the defaults apply, plus the current block and the authorizer. With +// scopes, only the authorizer, the current block and what the scopes +// designate are trusted: the authority for ScopeAuthority, blocks 0 to +// current for ScopePrevious, and for ScopePublicKey the blocks of +// blocksByKey, keyed by PublicKey.String(), signed by that key. +func TrustedOriginsFromScopes(scopes []Scope, defaults TrustedOrigins, current BlockID, blocksByKey map[string][]BlockID) TrustedOrigins { + if len(scopes) == 0 { + return defaults.With(current, AuthorizerBlockID) + } + + origins := NewOrigin(AuthorizerBlockID, current) + for _, scope := range scopes { + switch scope.Kind { + case ScopeAuthority: + origins = origins.With(0) + case ScopePrevious: + if current != AuthorizerBlockID { + previous := make([]BlockID, 0, current+1) + for i := BlockID(0); i <= current; i++ { + previous = append(previous, i) + } + origins = origins.With(previous...) + } + case ScopePublicKey: + origins = origins.With(blocksByKey[scope.PublicKey.String()]...) + } + } + return TrustedOrigins(origins) +} diff --git a/datalog/scope_test.go b/datalog/scope_test.go new file mode 100644 index 00000000..3117a014 --- /dev/null +++ b/datalog/scope_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package datalog + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestScopeString(t *testing.T) { + key := PublicKey{Algorithm: AlgorithmEd25519, Key: []byte{0xac, 0xdd}} + require.Equal(t, "authority", Scope{Kind: ScopeAuthority}.String()) + require.Equal(t, "previous", Scope{Kind: ScopePrevious}.String()) + require.Equal(t, "ed25519/acdd", Scope{Kind: ScopePublicKey, PublicKey: key}.String()) + require.Equal(t, "secp256r1/acdd", PublicKey{Algorithm: AlgorithmSecp256r1, Key: []byte{0xac, 0xdd}}.String()) + require.Equal(t, "", ScopesString(nil)) + require.Equal(t, " trusting previous, ed25519/acdd", ScopesString([]Scope{{Kind: ScopePrevious}, {Kind: ScopePublicKey, PublicKey: key}})) +} + +func TestTrustedOriginsFromScopes(t *testing.T) { + key := PublicKey{Algorithm: AlgorithmEd25519, Key: []byte{0x01}} + other := PublicKey{Algorithm: AlgorithmEd25519, Key: []byte{0x02}} + blocksByKey := map[string][]BlockID{key.String(): {1, 3}} + + for _, tc := range []struct { + desc string + scopes []Scope + defaults TrustedOrigins + current BlockID + want TrustedOrigins + }{ + {"no scopes: defaults plus the block and the authorizer", nil, DefaultTrustedOrigins(), 2, NewTrustedOrigins(0, 2, AuthorizerBlockID)}, + {"no scopes inherits block scopes", nil, NewTrustedOrigins(1, 2, AuthorizerBlockID), 2, NewTrustedOrigins(1, 2, AuthorizerBlockID)}, + {"authority", []Scope{{Kind: ScopeAuthority}}, NewTrustedOrigins(5), 2, NewTrustedOrigins(0, 2, AuthorizerBlockID)}, + {"previous", []Scope{{Kind: ScopePrevious}}, DefaultTrustedOrigins(), 2, NewTrustedOrigins(0, 1, 2, AuthorizerBlockID)}, + {"previous from the authorizer", []Scope{{Kind: ScopePrevious}}, DefaultTrustedOrigins(), AuthorizerBlockID, NewTrustedOrigins(AuthorizerBlockID)}, + {"public key with blocks", []Scope{{Kind: ScopePublicKey, PublicKey: key}}, DefaultTrustedOrigins(), 2, NewTrustedOrigins(1, 2, 3, AuthorizerBlockID)}, + {"public key without blocks", []Scope{{Kind: ScopePublicKey, PublicKey: other}}, DefaultTrustedOrigins(), 2, NewTrustedOrigins(2, AuthorizerBlockID)}, + {"combined", []Scope{{Kind: ScopeAuthority}, {Kind: ScopePublicKey, PublicKey: key}}, DefaultTrustedOrigins(), 4, NewTrustedOrigins(0, 1, 3, 4, AuthorizerBlockID)}, + } { + t.Run(tc.desc, func(t *testing.T) { + require.Equal(t, tc.want, TrustedOriginsFromScopes(tc.scopes, tc.defaults, tc.current, blocksByKey)) + }) + } +} diff --git a/datalog/symbol.go b/datalog/symbol.go index bf5b0ad0..60ac85f6 100644 --- a/datalog/symbol.go +++ b/datalog/symbol.go @@ -5,6 +5,7 @@ package datalog import ( "fmt" + "slices" "sort" "strings" ) @@ -173,18 +174,45 @@ type SymbolDebugger struct { *SymbolTable } +// Term prints a term, resolving strings and variables through the symbol +// table, including inside sets. +func (d SymbolDebugger) Term(t Term) string { + switch t := t.(type) { + case String: + return "\"" + d.Str(t) + "\"" + case Variable: + return "$" + d.Var(t) + case Set: + if len(t) == 0 { + return "{,}" + } + elts := make([]string, len(t)) + for i, e := range t { + elts[i] = d.Term(e) + } + sort.Strings(elts) + return fmt.Sprintf("{%s}", strings.Join(elts, ", ")) + case Array: + elts := make([]string, len(t)) + for i, e := range t { + elts[i] = d.Term(e) + } + return fmt.Sprintf("[%s]", strings.Join(elts, ", ")) + case Map: + entries := make([]string, len(t)) + for i, e := range t { + entries[i] = fmt.Sprintf("%s: %s", d.Term(e.Key), d.Term(e.Value)) + } + return fmt.Sprintf("{%s}", strings.Join(entries, ", ")) + default: + return t.String() + } +} + func (d SymbolDebugger) Predicate(p Predicate) string { strs := make([]string, len(p.Terms)) for i, id := range p.Terms { - var s string - if sym, ok := id.(String); ok { - s = "\"" + d.Str(sym) + "\"" - } else if variable, ok := id.(Variable); ok { - s = "$" + d.Var(variable) - } else { - s = fmt.Sprintf("%v", id) - } - strs[i] = s + strs[i] = d.Term(id) } return fmt.Sprintf("%s(%s)", d.Str(p.Name), strings.Join(strs, ", ")) } @@ -205,7 +233,7 @@ func (d SymbolDebugger) Rule(r Rule) string { expressionsStart = ", " } - return fmt.Sprintf("%s <- %s%s%s", head, strings.Join(preds, ", "), expressionsStart, strings.Join(expressions, ", ")) + return fmt.Sprintf("%s <- %s%s%s%s", head, strings.Join(preds, ", "), expressionsStart, strings.Join(expressions, ", "), ScopesString(r.Scopes)) } func (d SymbolDebugger) CheckQuery(r Rule) string { @@ -223,7 +251,7 @@ func (d SymbolDebugger) CheckQuery(r Rule) string { expressionsStart = ", " } - return fmt.Sprintf("%s%s%s", strings.Join(preds, ", "), expressionsStart, strings.Join(expressions, ", ")) + return fmt.Sprintf("%s%s%s%s", strings.Join(preds, ", "), expressionsStart, strings.Join(expressions, ", "), ScopesString(r.Scopes)) } func (d SymbolDebugger) Expression(e Expression) string { @@ -235,22 +263,67 @@ func (d SymbolDebugger) Check(c Check) string { for i, q := range c.Queries { queries[i] = d.CheckQuery(q) } - return fmt.Sprintf("check if %s", strings.Join(queries, " or ")) + kind := "check if" + switch c.Kind { + case CheckKindAll: + kind = "check all" + case CheckKindReject: + kind = "reject if" + } + return fmt.Sprintf("%s %s", kind, strings.Join(queries, " or ")) } +// World prints the facts grouped by origin and the rules by block, each +// group sorted, in the layout of the reference implementation: +// +// // origin: [0] +// fact(1); +// // origin: authorizer +// rule($a) <- fact($a); func (d SymbolDebugger) World(w *World) string { - facts := make([]string, len(*w.facts)) - for i, f := range *w.facts { - facts[i] = d.Predicate(f.Predicate) + var b strings.Builder + if len(w.facts) > 0 { + b.WriteString("// Facts:\n") } - rules := make([]string, len(w.rules)) - for i, r := range w.rules { - rules[i] = d.Rule(r) + for _, of := range w.facts { + if len(of.facts) == 0 { + continue + } + facts := make([]string, len(of.facts)) + for i, f := range of.facts { + facts[i] = d.Predicate(f.Predicate) + } + sort.Strings(facts) + fmt.Fprintf(&b, "// origin: %s\n", of.origin) + for _, f := range facts { + fmt.Fprintf(&b, "%s;\n", f) + } } - sort.Strings(facts) - sort.Strings(rules) - return fmt.Sprintf("World {{\n\tfacts: %v\n\trules: %v\n}}", facts, rules) + rulesByBlock := map[BlockID][]string{} + for _, sr := range w.rules { + rulesByBlock[sr.blockID] = append(rulesByBlock[sr.blockID], d.Rule(sr.rule)) + } + if len(rulesByBlock) > 0 { + if b.Len() > 0 { + b.WriteString("\n") + } + b.WriteString("// Rules:\n") + } + blocks := make([]BlockID, 0, len(rulesByBlock)) + for id := range rulesByBlock { + blocks = append(blocks, id) + } + slices.Sort(blocks) + for _, id := range blocks { + rules := rulesByBlock[id] + sort.Strings(rules) + fmt.Fprintf(&b, "// origin: %s\n", id) + for _, r := range rules { + fmt.Fprintf(&b, "%s;\n", r) + } + } + return b.String() } func (d SymbolDebugger) FactSet(s *FactSet) string { diff --git a/example_test.go b/example_test.go index 26919dd8..a33584fe 100644 --- a/example_test.go +++ b/example_test.go @@ -52,7 +52,7 @@ func ExampleBiscuit() { blockBuilder := deser.CreateBlock() block, err := parser.FromStringBlockWithParams(` - check if resource($file), operation($permission), [{read}].contains($permission);`, + check if resource($file), operation($permission), {{read}}.contains($permission);`, map[string]biscuit.Term{"read": biscuit.String("read")}) if err != nil { @@ -81,7 +81,7 @@ func ExampleBiscuit() { panic(fmt.Errorf("failed to deserialize token: %v", err)) } - v1, err := b2.Authorizer(publicRoot) + v1, err := b2.AuthorizerFrom(biscuit.RootKey(publicRoot)) if err != nil { panic(fmt.Errorf("failed to create verifier: %v", err)) } @@ -107,7 +107,7 @@ func ExampleBiscuit() { fmt.Println("allowed to read /a/file1.txt") } - v1, _ = b2.Authorizer(publicRoot) + v1, _ = b2.AuthorizerFrom(biscuit.RootKey(publicRoot)) authorizer, err = parser.FromStringAuthorizerWithParams(` resource({res}); diff --git a/go.mod b/go.mod index 3c8074e6..4ca58ee0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/eclipse-biscuit/biscuit-go/v2 -go 1.23 +go 1.25.0 require ( github.com/alecthomas/participle/v2 v2.1.4 diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go new file mode 100644 index 00000000..769f9ea3 --- /dev/null +++ b/internal/crypto/crypto.go @@ -0,0 +1,194 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +// Package crypto wraps the signature algorithms a token can use (Ed25519 and +// ECDSA over secp256r1) behind one Signer / Verifier pair, with the key and +// signature encodings defined by the Biscuit specification. +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "errors" + "fmt" + "io" + + "github.com/eclipse-biscuit/biscuit-go/v2/pb" +) + +var ( + ErrInvalidSignature = errors.New("biscuit: invalid signature") + ErrInvalidKeySize = errors.New("biscuit: invalid key size") + ErrUnsupportedAlgorithm = errors.New("biscuit: unsupported signature algorithm") +) + +// Signer signs block payloads with a private key of one algorithm. +type Signer interface { + Sign(payload []byte) ([]byte, error) + Algorithm() pb.PublicKey_Algorithm + // PublicKey is the public key encoded as the spec requires in pb.PublicKey.Key. + PublicKey() []byte + // Secret is the private key encoded as the spec requires in pb.Proof.NextSecret. + Secret() []byte +} + +// Verifier checks block signatures with a public key of one algorithm. +type Verifier interface { + Verify(payload, signature []byte) error + Algorithm() pb.PublicKey_Algorithm + PublicKey() []byte +} + +// NewSigner accepts an ed25519.PrivateKey or an *ecdsa.PrivateKey on the P-256 curve. +func NewSigner(key crypto.Signer) (Signer, error) { + switch k := key.(type) { + case ed25519.PrivateKey: + if len(k) != ed25519.PrivateKeySize { + return nil, ErrInvalidKeySize + } + return ed25519Signer{k}, nil + case *ecdsa.PrivateKey: + if k == nil || k.Curve != elliptic.P256() { + return nil, ErrUnsupportedAlgorithm + } + return secp256r1Signer{k}, nil + default: + return nil, fmt.Errorf("%w: %T", ErrUnsupportedAlgorithm, key) + } +} + +// NewVerifier accepts an ed25519.PublicKey or an *ecdsa.PublicKey on the P-256 +// curve. A plain []byte is taken as an Ed25519 key, which is what callers +// passed before other algorithms were supported. +func NewVerifier(key crypto.PublicKey) (Verifier, error) { + switch k := key.(type) { + case []byte: + return NewVerifier(ed25519.PublicKey(k)) + case ed25519.PublicKey: + if len(k) != ed25519.PublicKeySize { + return nil, ErrInvalidKeySize + } + return ed25519Verifier{k}, nil + case *ecdsa.PublicKey: + if k == nil || k.Curve != elliptic.P256() { + return nil, ErrUnsupportedAlgorithm + } + return secp256r1Verifier{k}, nil + default: + return nil, fmt.Errorf("%w: %T", ErrUnsupportedAlgorithm, key) + } +} + +// ParseVerifier decodes a public key as stored in pb.PublicKey. +func ParseVerifier(alg pb.PublicKey_Algorithm, raw []byte) (Verifier, error) { + switch alg { + case pb.PublicKey_Ed25519: + if len(raw) != ed25519.PublicKeySize { + return nil, ErrInvalidKeySize + } + return ed25519Verifier{ed25519.PublicKey(raw)}, nil + case pb.PublicKey_SECP256R1: + x, y := elliptic.UnmarshalCompressed(elliptic.P256(), raw) + if x == nil { + return nil, ErrInvalidKeySize + } + return secp256r1Verifier{&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}}, nil + default: + return nil, fmt.Errorf("%w: %s", ErrUnsupportedAlgorithm, alg) + } +} + +// ParseSigner decodes a private key as stored in pb.Proof.NextSecret. +func ParseSigner(alg pb.PublicKey_Algorithm, raw []byte) (Signer, error) { + switch alg { + case pb.PublicKey_Ed25519: + if len(raw) != ed25519.SeedSize { + return nil, ErrInvalidKeySize + } + return ed25519Signer{ed25519.NewKeyFromSeed(raw)}, nil + case pb.PublicKey_SECP256R1: + key, err := ecdsa.ParseRawPrivateKey(elliptic.P256(), raw) + if err != nil { + return nil, ErrInvalidKeySize + } + return secp256r1Signer{key}, nil + default: + return nil, fmt.Errorf("%w: %s", ErrUnsupportedAlgorithm, alg) + } +} + +// GenerateSigner creates a fresh key of the given algorithm. +func GenerateSigner(alg pb.PublicKey_Algorithm, rng io.Reader) (Signer, error) { + switch alg { + case pb.PublicKey_Ed25519: + _, key, err := ed25519.GenerateKey(rng) + if err != nil { + return nil, err + } + return ed25519Signer{key}, nil + case pb.PublicKey_SECP256R1: + key, err := ecdsa.GenerateKey(elliptic.P256(), rng) + if err != nil { + return nil, err + } + return secp256r1Signer{key}, nil + default: + return nil, fmt.Errorf("%w: %s", ErrUnsupportedAlgorithm, alg) + } +} + +type ed25519Signer struct{ key ed25519.PrivateKey } + +func (s ed25519Signer) Sign(payload []byte) ([]byte, error) { + return ed25519.Sign(s.key, payload), nil +} +func (s ed25519Signer) Algorithm() pb.PublicKey_Algorithm { return pb.PublicKey_Ed25519 } +func (s ed25519Signer) PublicKey() []byte { return s.key.Public().(ed25519.PublicKey) } +func (s ed25519Signer) Secret() []byte { return s.key.Seed() } + +type ed25519Verifier struct{ key ed25519.PublicKey } + +func (v ed25519Verifier) Verify(payload, signature []byte) error { + if !ed25519.Verify(v.key, payload, signature) { + return ErrInvalidSignature + } + return nil +} +func (v ed25519Verifier) Algorithm() pb.PublicKey_Algorithm { return pb.PublicKey_Ed25519 } +func (v ed25519Verifier) PublicKey() []byte { return v.key } + +// secp256r1 keys use the SEC1 compressed point for the public key, the big +// endian scalar for the secret, and ASN.1 DER (r, s) for signatures over SHA-256. +type secp256r1Signer struct{ key *ecdsa.PrivateKey } + +func (s secp256r1Signer) Sign(payload []byte) ([]byte, error) { + digest := sha256.Sum256(payload) + return ecdsa.SignASN1(rand.Reader, s.key, digest[:]) +} +func (s secp256r1Signer) Algorithm() pb.PublicKey_Algorithm { return pb.PublicKey_SECP256R1 } +func (s secp256r1Signer) PublicKey() []byte { + return elliptic.MarshalCompressed(elliptic.P256(), s.key.X, s.key.Y) +} +func (s secp256r1Signer) Secret() []byte { + secret := make([]byte, 32) + s.key.D.FillBytes(secret) + return secret +} + +type secp256r1Verifier struct{ key *ecdsa.PublicKey } + +func (v secp256r1Verifier) Verify(payload, signature []byte) error { + digest := sha256.Sum256(payload) + if !ecdsa.VerifyASN1(v.key, digest[:], signature) { + return ErrInvalidSignature + } + return nil +} +func (v secp256r1Verifier) Algorithm() pb.PublicKey_Algorithm { return pb.PublicKey_SECP256R1 } +func (v secp256r1Verifier) PublicKey() []byte { + return elliptic.MarshalCompressed(elliptic.P256(), v.key.X, v.key.Y) +} diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go new file mode 100644 index 00000000..3c057a2a --- /dev/null +++ b/internal/crypto/crypto_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/eclipse-biscuit/biscuit-go/v2/pb" + "github.com/stretchr/testify/require" +) + +func TestRoundTrip(t *testing.T) { + for _, alg := range []pb.PublicKey_Algorithm{pb.PublicKey_Ed25519, pb.PublicKey_SECP256R1} { + t.Run(alg.String(), func(t *testing.T) { + signer, err := GenerateSigner(alg, rand.Reader) + require.NoError(t, err) + require.Equal(t, alg, signer.Algorithm()) + + payload := []byte("payload") + sig, err := signer.Sign(payload) + require.NoError(t, err) + + // The public key travels in pb.PublicKey, the secret in pb.Proof. + verifier, err := ParseVerifier(alg, signer.PublicKey()) + require.NoError(t, err) + require.NoError(t, verifier.Verify(payload, sig)) + require.ErrorIs(t, verifier.Verify([]byte("other"), sig), ErrInvalidSignature) + + parsed, err := ParseSigner(alg, signer.Secret()) + require.NoError(t, err) + require.Equal(t, signer.PublicKey(), parsed.PublicKey()) + require.Len(t, signer.Secret(), 32) + }) + } +} + +func TestNewSignerVerifier(t *testing.T) { + edPub, edPriv, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + ecPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + for _, tc := range []struct { + name string + priv crypto.Signer + pub crypto.PublicKey + alg pb.PublicKey_Algorithm + }{ + {"ed25519", edPriv, edPub, pb.PublicKey_Ed25519}, + {"secp256r1", ecPriv, &ecPriv.PublicKey, pb.PublicKey_SECP256R1}, + } { + t.Run(tc.name, func(t *testing.T) { + signer, err := NewSigner(tc.priv) + require.NoError(t, err) + verifier, err := NewVerifier(tc.pub) + require.NoError(t, err) + require.Equal(t, tc.alg, signer.Algorithm()) + require.Equal(t, tc.alg, verifier.Algorithm()) + require.Equal(t, signer.PublicKey(), verifier.PublicKey()) + + sig, err := signer.Sign([]byte("payload")) + require.NoError(t, err) + require.NoError(t, verifier.Verify([]byte("payload"), sig)) + }) + } + + _, err = NewSigner(nil) + require.ErrorIs(t, err, ErrUnsupportedAlgorithm) + _, err = NewVerifier([]byte("not a key")) + require.ErrorIs(t, err, ErrInvalidKeySize) + fromBytes, err := NewVerifier([]byte(edPub)) + require.NoError(t, err) + require.Equal(t, pb.PublicKey_Ed25519, fromBytes.Algorithm()) + _, err = ParseVerifier(pb.PublicKey_SECP256R1, edPub) + require.ErrorIs(t, err, ErrInvalidKeySize) +} diff --git a/parser/GRAMMAR.md b/parser/GRAMMAR.md index 40978195..4215b42e 100644 --- a/parser/GRAMMAR.md +++ b/parser/GRAMMAR.md @@ -4,7 +4,7 @@ This document describes the currently supported Datalog grammar. ## Term -Represents a Datalog type, can be one of: parameter, variable, integer, string, date, bytes, boolean, or set. +Represents a Datalog type, can be one of: parameter, variable, integer, string, date, bytes, boolean, null, set, array, or map. - parameter is delimited by curly brackets: `{param}`. Those are replaced by actual values before evaluation. - variable is prefixed with a `$` sign followed by a string or an unsigned 32bit base-10 integer, e.g. `$0` or `$variable1` @@ -13,7 +13,10 @@ Represents a Datalog type, can be one of: parameter, variable, integer, string, - date is RFC3339 encoded, e.g. `2006-01-02T15:04:05Z` - bytes is an hexadecimal encoded string, prefixed with a `hex:` sequence - boolean is either `true` or `false` -- set is a sequence of any of the above types, except variable, between brackets, e.g. `["file1", "file2"]` (sets cannot be nested) +- null is the absence of a value, written `null` (datalog v3.3); `$x == null` tests for it +- set is a sequence of any of the above types, except variable, between braces, e.g. `{"file1", "file2"}`; the empty set is `{,}` (sets cannot be nested) +- array is an ordered sequence of terms of any type, except variable, between brackets, e.g. `[1, "a", [2]]` (datalog v3.3); the empty array is `[]` +- map associates integer or string keys with terms of any type, except variable, e.g. `{"a": 1, 2: "b"}` (datalog v3.3); the empty map is `{}` ## Predicate @@ -27,11 +30,15 @@ Constraints allows performing checks on a variable, below is the list of availab - Equal: `$b == true` - Negation: `!$b` -- And / Or: `$b || $c && $d` +- And / Or: `$b || $c && $d`. The right side is evaluated only when needed (datalog v3.3). ### Integer -- Equal: `$i == 1` +Integer literals are signed 64-bit; an operation that overflows is an execution error. + +- Equal: `$i === 1` +- Not equal: `$i !== 1` +- Bitwise and / or / xor: `$i & 1`, `$i | 1`, `$i ^ 1` - Greater than: `$i > 1` - Greater than or equal: `$i >= 1` - Less than: `$i < 1` @@ -62,12 +69,41 @@ Constraints allows performing checks on a variable, below is the list of availab ### Set -- Equal: `$set == ["a", "b"]` +- Equal: `$set === {"a", "b"}` - Contains (element membership): `$set.contains("a")` -- Contains (set inclusion): `$set.contains([a])` -- Union: `$set.union(["a"])` -- Intersection: `$set.intersection(["a"])` +- Contains (set inclusion): `$set.contains({"a"})` +- Union: `$set.union({"a"})` +- Intersection: `$set.intersection({"a"})` - Length: `$set.length()` +- All / Any (datalog v3.3): `$set.all($p -> $p > 0)`, `$set.any($p -> $p > 2)`. The closure parameter + may not have the name of a variable already bound in the rule. + +### Array (datalog v3.3) + +- Equal: `$array == [1, 2]` +- Contains: `$array.contains(1)` +- Prefix / Suffix: `$array.starts_with([1])`, `$array.ends_with([2])` +- Get: `$array.get(0)`, `null` when the index is out of range +- Length, All, Any: as for sets + +### Map (datalog v3.3) + +- Equal: `$map == {"a": 1}` +- Contains (key membership): `$map.contains("a")` +- Get: `$map.get("a")`, `null` when the key is absent +- Length: `$map.length()` +- All / Any: the closure receives each entry as a `[key, value]` array, e.g. `$map.all($kv -> $kv.get(1) > 0)` + +### Any value (datalog v3.3) + +- Type name: `$x.type()` returns one of `"integer"`, `"string"`, `"date"`, `"bytes"`, `"bool"`, + `"set"`, `"null"`, `"array"` or `"map"`, e.g. `$x.type() == "integer"` +- Extern function call: `$x.extern::name()` or `$x.extern::name($y)`, where `name` is a function + registered on the authorizer with `biscuit.WithExternFuncs`. A call to a name that is not + registered fails the evaluation. +- Error fallback: `$x.try_or(fallback)` is the value of `$x` (the receiver, including the method + calls before `.try_or()`), or `fallback` when evaluating the receiver fails, e.g. + `$s.length().try_or(0)`. An error in `fallback` itself is not caught. ### Operators precedence @@ -79,7 +115,13 @@ The operators have the following precedence (highest to lowest): | `!` (prefix) | not associative | | `*`, `/` | left-associative | | `+`, `-` | left-associative | -| `>`, `>=`, `<`, `<=`, `==` | not associative | +| `&` | left-associative | +| `|` | left-associative | +| `^` | left-associative | +| `>`, `>=`, `<`, `<=`, `===`, `==`, `!==`, `!=` | not associative | + +`===` and `!==` are the strict comparisons: comparing values of different types is an error. +`==` and `!=` compare across types (datalog v3.3): values of different types are not equal. | `&&` | left-associative | | `||` | left-associative | @@ -101,8 +143,25 @@ e.g. `right($file, "read") <- resource($file), owner($user, $file), $user == "us # Check -A check starts with `check if`, followed by one or more rule bodies, separated with ` or `. +A check starts with `check if`, `check all` or `reject if`, followed by one or more rule bodies, +separated with ` or `. + +`check if` passes when one of the bodies has at least one match. `check all` passes when one of the +bodies has at least one match and every match of that body satisfies its expressions, e.g. +`check all operation($op), allowed_operations($allowed), $allowed.contains($op)`. `reject if` +passes when none of the bodies has a match. # Policy A policy starts with either `allow if` or `deny if`, followed by one or more rule bodies, separated with ` or `. + +# Scope + +A rule body, a check body or a policy body may end with a `trusting` clause that selects which +blocks of the token it reads facts from: `trusting authority`, `trusting previous` (every block up +to the current one) or `trusting ` (the third-party blocks signed by that key, written +`ed25519/` or `secp256r1/`). Entries are separated with commas, e.g. +`check if group("admin") trusting previous, ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189`. + +Without a clause, a body reads the authority block, the authorizer and its own block. A block or an +authorizer may set the default for all its bodies with a `trusting ...;` statement. diff --git a/parser/benchmark_test.go b/parser/benchmark_test.go new file mode 100644 index 00000000..d1d210ce --- /dev/null +++ b/parser/benchmark_test.go @@ -0,0 +1,72 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "testing" +) + +const ( + benchFact = `right("/a/file1.txt", "read", {"read", "/a/file2.txt"})` + benchRule = `grandparent($a, $c) <- parent($a, $b), parent($b, $c), $a.starts_with("x") || $c == "y"` + benchCheck = `check if resource($0), operation("read"), right($0, "read"), $0.length() < 100 or admin(true)` + benchBlock = `// a block +right("/a/file1", "read"); +right("/a/file1", "write"); +right("/a/file2", "read"); +grandparent($a, $c) <- parent($a, $b), parent($b, $c); +check if resource($0), operation("read"), right($0, "read"); +check if time($t), $t < 2030-12-31T12:59:59+00:00; +` + benchAuthorizer = `resource("/a/file1"); +operation("read"); +time(2025-01-01T00:00:00Z); +check if right($r, "read"), resource($r); +allow if user("alice") or user("bob"); +deny if true; +` +) + +func BenchmarkNew(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + New() + } +} + +func BenchmarkParser(b *testing.B) { + p := New() + + cases := []struct { + name string + fn func() error + }{ + {"fact", func() error { _, err := p.Fact(benchFact, nil); return err }}, + {"rule", func() error { _, err := p.Rule(benchRule, nil); return err }}, + {"check", func() error { _, err := p.Check(benchCheck, nil); return err }}, + {"block", func() error { _, err := p.Block(benchBlock, nil); return err }}, + {"authorizer", func() error { _, err := p.Authorizer(benchAuthorizer, nil); return err }}, + } + + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err := c.fn(); err != nil { + b.Fatal(err) + } + } + }) + } +} + +// The FromString helpers are the API the README recommends; compare with BenchmarkParser/fact. +func BenchmarkFromStringFact(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := FromStringFact(benchFact); err != nil { + b.Fatal(err) + } + } +} diff --git a/parser/corpus_test.go b/parser/corpus_test.go new file mode 100644 index 00000000..0d49ef17 --- /dev/null +++ b/parser/corpus_test.go @@ -0,0 +1,84 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "sort" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +var update = flag.Bool("update", false, "rewrite "+corpusGolden) + +const ( + corpusSamples = "../samples/data/current/samples.json" + corpusGolden = "testdata/samples_corpus.golden" +) + +// Parses every datalog snippet in the spec samples and compares the outcome +// (accepted, with this AST, or rejected) with the checked-in golden file, so +// a grammar or lexer change that alters any outcome shows up as a diff. +// +// Regenerate with: go test ./parser/ -run TestSamplesCorpus -update +func TestSamplesCorpus(t *testing.T) { + raw, err := os.ReadFile(corpusSamples) + require.NoError(t, err) + + var samples struct { + TestCases []struct { + Filename string `json:"filename"` + Token []struct { + Code string `json:"code"` + } `json:"token"` + Validations map[string]struct { + AuthorizerCode string `json:"authorizer_code"` + } `json:"validations"` + } `json:"testcases"` + } + require.NoError(t, json.Unmarshal(raw, &samples)) + + p := New() + var lines []string + for _, tc := range samples.TestCases { + for i, b := range tc.Token { + parsed, err := p.Block(b.Code, nil) + lines = append(lines, corpusLine(fmt.Sprintf("%s block %d", tc.Filename, i), parsed, err)) + } + + names := make([]string, 0, len(tc.Validations)) + for n := range tc.Validations { + names = append(names, n) + } + sort.Strings(names) + for i, n := range names { + parsed, err := p.Authorizer(tc.Validations[n].AuthorizerCode, nil) + lines = append(lines, corpusLine(fmt.Sprintf("%s authorizer %d", tc.Filename, i), parsed, err)) + } + } + got := strings.Join(lines, "\n") + "\n" + + if *update { + require.NoError(t, os.MkdirAll("testdata", 0o755)) + require.NoError(t, os.WriteFile(corpusGolden, []byte(got), 0o644)) + return + } + + want, err := os.ReadFile(corpusGolden) + require.NoError(t, err, "missing golden file; run with -update to create it") + require.Equal(t, string(want), got, "parser outcome changed; review the diff and run with -update if intended") +} + +func corpusLine(key string, parsed interface{}, err error) string { + if err != nil { + return key + "\terror" + } + return key + "\tok\t" + strconv.Quote(fmt.Sprintf("%+v", parsed)) +} diff --git a/parser/grammar.go b/parser/grammar.go index d478d69e..294e57ad 100644 --- a/parser/grammar.go +++ b/parser/grammar.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + "slices" "strconv" "strings" "time" @@ -44,15 +45,17 @@ func (v *Variable) Capture(values []string) error { type Parameter string -func (p *Parameter) Capture(values []string) error { - if len(values) != 1 { - return errors.New("parser: invalid parameter values") - } - if !strings.HasPrefix(values[0], "{") || - !strings.HasSuffix(values[0], "}") { - return errors.New("parser: invalid parameter capture") +// Integer is an int64 literal with an optional leading minus sign. The sign +// is a separate token, so "1 -2" is still a subtraction, and the full text +// is parsed so that the smallest int64 is accepted. +type Integer int64 + +func (i *Integer) Capture(values []string) error { + v, err := strconv.ParseInt(strings.Join(values, ""), 10, 64) + if err != nil { + return err } - *p = Parameter(strings.Trim(values[0], "{}")) + *i = Integer(v) return nil } @@ -76,9 +79,50 @@ type Block struct { } type BlockElement struct { - Check *Check `@@` - Predicate *Predicate `|@@` - RuleBody []*RuleElement `("<-" @@ ("," @@)*)?` + Check *Check `@@` + Scopes []*ScopeElem `| "trusting" @@ ("," @@)*` + Predicate *Predicate `| @@` + RuleBody []*RuleElement `("<-" @@ ("," @@)*)?` + RuleScopes []*ScopeElem `("trusting" @@ ("," @@)*)?` +} + +// ScopeElem is one entry of a `trusting` clause. +type ScopeElem struct { + Authority bool `@"authority"` + Previous bool `| @"previous"` + PublicKey *string `| @PublicKey` +} + +func (s *ScopeElem) ToBiscuit() (biscuit.Scope, error) { + switch { + case s.Authority: + return biscuit.Scope{Kind: biscuit.ScopeAuthority}, nil + case s.Previous: + return biscuit.Scope{Kind: biscuit.ScopePrevious}, nil + case s.PublicKey != nil: + key, err := biscuit.ParsePublicKey(*s.PublicKey) + if err != nil { + return biscuit.Scope{}, fmt.Errorf("parser: %w", err) + } + return biscuit.Scope{Kind: biscuit.ScopePublicKey, PublicKey: key}, nil + default: + return biscuit.Scope{}, errors.New("parser: empty scope") + } +} + +func scopesToBiscuit(elems []*ScopeElem) ([]biscuit.Scope, error) { + if len(elems) == 0 { + return nil, nil + } + scopes := make([]biscuit.Scope, len(elems)) + for i, e := range elems { + s, err := e.ToBiscuit() + if err != nil { + return nil, err + } + scopes[i] = s + } + return scopes, nil } type ParametersMap map[string]biscuit.Term @@ -87,6 +131,7 @@ func (b *Block) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedBlock, error facts := []biscuit.Fact{} rules := []biscuit.Rule{} checks := []biscuit.Check{} + var scopes []biscuit.Scope for _, e := range b.Body { if e.Check != nil { c, err := e.Check.ToBiscuit(parameters) @@ -94,10 +139,17 @@ func (b *Block) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedBlock, error return nil, err } checks = append(checks, *c) + } else if e.Scopes != nil { + s, err := scopesToBiscuit(e.Scopes) + if err != nil { + return nil, err + } + scopes = append(scopes, s...) } else if e.Predicate != nil && e.RuleBody != nil { rule := Rule{ - Head: e.Predicate, - Body: e.RuleBody, + Head: e.Predicate, + Body: e.RuleBody, + Scopes: e.RuleScopes, } r, err := rule.ToBiscuit(parameters) if err != nil { @@ -112,7 +164,7 @@ func (b *Block) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedBlock, error facts = append(facts, biscuit.Fact{Predicate: *p}) } } - return &biscuit.ParsedBlock{Facts: facts, Rules: rules, Checks: checks}, nil + return &biscuit.ParsedBlock{Facts: facts, Rules: rules, Checks: checks, Scopes: scopes}, nil } type Authorizer struct { @@ -130,6 +182,7 @@ func (b *Authorizer) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedAuthori rules := []biscuit.Rule{} checks := []biscuit.Check{} policies := []biscuit.Policy{} + var scopes []biscuit.Scope for _, e := range b.Body { if e.BlockElement != nil { @@ -140,10 +193,17 @@ func (b *Authorizer) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedAuthori return nil, err } checks = append(checks, *c) + } else if be.Scopes != nil { + s, err := scopesToBiscuit(be.Scopes) + if err != nil { + return nil, err + } + scopes = append(scopes, s...) } else if be.Predicate != nil && be.RuleBody != nil { rule := Rule{ - Head: be.Predicate, - Body: be.RuleBody, + Head: be.Predicate, + Body: be.RuleBody, + Scopes: be.RuleScopes, } r, err := rule.ToBiscuit(parameters) if err != nil { @@ -168,7 +228,7 @@ func (b *Authorizer) ToBiscuit(parameters ParametersMap) (*biscuit.ParsedAuthori } return &biscuit.ParsedAuthorizer{ Policies: policies, - Block: biscuit.ParsedBlock{Facts: facts, Rules: rules, Checks: checks}, + Block: biscuit.ParsedBlock{Facts: facts, Rules: rules, Checks: checks, Scopes: scopes}, }, nil } @@ -176,6 +236,7 @@ type Rule struct { Comments []*Comment `@Comment*` Head *Predicate `@@` Body []*RuleElement `"<-" @@ ("," @@)*` + Scopes []*ScopeElem `("trusting" @@ ("," @@)*)?` } type RuleElement struct { @@ -185,15 +246,33 @@ type RuleElement struct { type Predicate struct { Name *string `@Ident` - IDs []*Term `"(" (@@ ("," @@)*)* ")"` + IDs []*Term `"(" (@@ ("," @@)*)? ")"` +} + +type CheckKind biscuit.CheckKind + +func (k *CheckKind) Capture(values []string) error { + switch strings.Join(values, " ") { + case "check if": + *k = CheckKind(biscuit.CheckKindOne) + case "check all": + *k = CheckKind(biscuit.CheckKindAll) + case "reject if": + *k = CheckKind(biscuit.CheckKindReject) + default: + return fmt.Errorf("parser: invalid check kind %q", values) + } + return nil } type Check struct { - Queries []*CheckQuery `"check if" @@ ( "or" @@ )*` + Kind CheckKind `@("check if" | "check all" | "reject if")` + Queries []*CheckQuery `@@ ( "or" @@ )*` } type CheckQuery struct { - Body []*RuleElement `@@ ("," @@)*` + Body []*RuleElement `@@ ("," @@)*` + Scopes []*ScopeElem `("trusting" @@ ("," @@)*)?` } type Policy struct { @@ -209,22 +288,86 @@ type Deny struct { Queries []*CheckQuery `"deny if" @@ ( "or" @@ )*` } +// Braces hold a parameter {name}, the empty set {,}, the empty map {}, a set +// {a, b} or a map {k: v}; brackets hold an array [a, b]. type Term struct { - Parameter *Parameter `@Parameter` - Variable *Variable `| @Variable` - Bytes *HexString `| @@` - String *string `| @String` - Date *string `| @DateTime` - Integer *int64 `| @Int` - Bool *Bool `| @Bool` - Set []*Term `| "[" @@ ("," @@)* "]"` + Parameter *Parameter `"{" @Ident "}"` + Variable *Variable `| @Variable` + Bytes *HexString `| @@` + String *string `| @String` + Date *string `| @DateTime` + Integer *Integer `| @("-"? Int)` + Bool *Bool `| @Bool` + Null bool `| @"null"` + Braces *Braces `| @@` + EmptyArray bool `| @("[" "]")` + Array []*Term `| "[" @@ ("," @@)* "]"` } -type Value struct { - Number *float64 ` @(Float|Int)` - Variable *string `| @Ident` - Parameter *Parameter `| @Parameter` - Subexpression *Expression `| "(" @@ ")"` +// Braces is a set or a map: entries with a value make a map, entries +// without make a set. {,} is the empty set and {} the empty map. +type Braces struct { + EmptySet bool `"{" ( @("," "}")` + EmptyMap bool `| @"}"` + Entries []*BraceEntry `| @@ ("," @@)* "}" )` +} + +type BraceEntry struct { + Key *Term `@@` + Value *Term `(":" @@)?` +} + +func (b *Braces) ToBiscuit(parameters ParametersMap) (biscuit.Term, error) { + switch { + case b.EmptySet: + return biscuit.Set{}, nil + case b.EmptyMap: + return biscuit.Map{}, nil + } + + isMap := b.Entries[0].Value != nil + if isMap { + m := make(biscuit.Map, 0, len(b.Entries)) + for _, e := range b.Entries { + if e.Value == nil { + return nil, errors.New("parser: map entry without a value") + } + key, err := e.Key.ToBiscuit(parameters) + if err != nil { + return nil, err + } + switch key.Type() { + case biscuit.TermTypeInteger, biscuit.TermTypeString: + default: + return nil, fmt.Errorf("parser: map key must be an integer or a string, got %s", key) + } + value, err := e.Value.ToBiscuit(parameters) + if err != nil { + return nil, err + } + if value.Type() == biscuit.TermTypeVariable { + return nil, errors.New("parser: a map cannot contain variables") + } + m = append(m, biscuit.MapEntry{Key: key, Value: value}) + } + return m, nil + } + + set := make(biscuit.Set, 0, len(b.Entries)) + for _, e := range b.Entries { + if e.Value != nil { + return nil, errors.New("parser: set element with a value") + } + elt, err := e.Key.ToBiscuit(parameters) + if err != nil { + return nil, err + } + if elt.Type() == biscuit.TermTypeVariable { + return nil, ErrVariableInSet + } + set = append(set, elt) + } + return set, nil } type Operator int @@ -249,12 +392,25 @@ const ( OpUnion OpLength OpNegate + OpNotEqual + OpBitwiseAnd + OpBitwiseOr + OpBitwiseXor + OpHeterogeneousEqual + OpHeterogeneousNotEqual + OpLazyAnd + OpLazyOr + OpAll + OpAny + OpGet + OpTypeOf + OpTryOr ) var operatorMap = map[string]Operator{ "+": OpAdd, - "-": OpSub, "*": OpMul, "/": OpDiv, "&&": OpAnd, "||": OpOr, "<=": OpLessOrEqual, ">=": OpGreaterOrEqual, "<": OpLessThan, ">": OpGreaterThan, - "==": OpEqual, "!": OpNegate, "contains": OpContains, "starts_with": OpPrefix, "ends_with": OpSuffix, "matches": OpMatches, "intersection": OpIntersection, "union": OpUnion, "length": OpLength} + "-": OpSub, "*": OpMul, "/": OpDiv, "&&": OpLazyAnd, "||": OpLazyOr, "all": OpAll, "any": OpAny, "get": OpGet, "try_or": OpTryOr, "<=": OpLessOrEqual, ">=": OpGreaterOrEqual, "<": OpLessThan, ">": OpGreaterThan, + "==": OpHeterogeneousEqual, "===": OpEqual, "!=": OpHeterogeneousNotEqual, "!==": OpNotEqual, "&": OpBitwiseAnd, "|": OpBitwiseOr, "^": OpBitwiseXor, "!": OpNegate, "contains": OpContains, "starts_with": OpPrefix, "ends_with": OpSuffix, "matches": OpMatches, "intersection": OpIntersection, "union": OpUnion, "length": OpLength, "type": OpTypeOf} func (o *Operator) Capture(s []string) error { *o = operatorMap[s[0]] @@ -282,13 +438,45 @@ type OpExpr2 struct { } type Expr2 struct { - Left *Expr3 `@@` + Left *ExprXor `@@` Right *OpExpr3 `@@?` } type OpExpr3 struct { - Operator Operator `@("<=" | ">=" | "<" | ">" | "==")` - Expr3 *Expr3 `@@` + Operator Operator `@("<=" | ">=" | "<" | ">" | "===" | "==" | "!==" | "!=")` + Expr3 *ExprXor `@@` +} + +// Bitwise operators sit between comparisons and arithmetic, in the order of +// the spec: ^ binds loosest, then |, then &. +type ExprXor struct { + Left *ExprBitOr `@@` + Right []*OpExprXor `@@*` +} + +type OpExprXor struct { + Operator Operator `@("^")` + Expr *ExprBitOr `@@` +} + +type ExprBitOr struct { + Left *ExprBitAnd `@@` + Right []*OpExprBitOr `@@*` +} + +type OpExprBitOr struct { + Operator Operator `@("|")` + Expr *ExprBitAnd `@@` +} + +type ExprBitAnd struct { + Left *Expr3 `@@` + Right []*OpExprBitAnd `@@*` +} + +type OpExprBitAnd struct { + Operator Operator `@("&")` + Expr *Expr3 `@@` } type Expr3 struct { @@ -322,8 +510,18 @@ type Expr6 struct { } type OpExpr7 struct { - Operator Operator `Dot @("matches" | "starts_with" | "ends_with" | "contains" | "union" | "intersection" | "length")` - Expression *Expression `"(" @@? ")"` + Operator Operator `Dot (@("matches" | "starts_with" | "ends_with" | "contains" | "union" | "intersection" | "length" | "all" | "any" | "get" | "type" | "try_or")` + Extern string `| @Ident)` + Closure *ClosureArg `"(" (@@` + Expression *Expression `| @@)? ")"` +} + +const externPrefix = "extern::" + +// ClosureArg is the `$p -> body` argument of .all() and .any(). +type ClosureArg struct { + Param *Variable `@Variable ClosureArrow` + Body *Expression `@@` } type ExprTerm struct { @@ -331,108 +529,237 @@ type ExprTerm struct { Expression *Expression `| "(" @@? ")"` } -func (e *Expression) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *Expression) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } for _, op := range e.Right { - op.ToExpr(expr, parameters) + if err := op.ToExpr(expr, parameters); err != nil { + return err + } } + return nil } -func (e *Expr1) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *Expr1) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } for _, op := range e.Right { - op.ToExpr(expr, parameters) + if err := op.ToExpr(expr, parameters); err != nil { + return err + } } + return nil } -func (e *Expr2) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *Expr2) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } if e.Right != nil { + return e.Right.ToExpr(expr, parameters) + } + return nil +} - e.Right.ToExpr(expr, parameters) +func (e *ExprXor) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } + for _, op := range e.Right { + if err := op.Expr.ToExpr(expr, parameters); err != nil { + return err + } + if err := op.Operator.ToExpr(expr); err != nil { + return err + } } + return nil } -func (e *Expr3) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *ExprBitOr) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } + for _, op := range e.Right { + if err := op.Expr.ToExpr(expr, parameters); err != nil { + return err + } + if err := op.Operator.ToExpr(expr); err != nil { + return err + } + } + return nil +} +func (e *ExprBitAnd) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } for _, op := range e.Right { - op.ToExpr(expr, parameters) + if err := op.Expr.ToExpr(expr, parameters); err != nil { + return err + } + if err := op.Operator.ToExpr(expr); err != nil { + return err + } } + return nil } -func (e *Expr4) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *Expr3) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } for _, op := range e.Right { - op.ToExpr(expr, parameters) + if err := op.ToExpr(expr, parameters); err != nil { + return err + } } + return nil } -func (e *Expr5) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr6.ToExpr(expr, parameters) +func (e *Expr4) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } + + for _, op := range e.Right { + if err := op.ToExpr(expr, parameters); err != nil { + return err + } + } + return nil +} + +func (e *Expr5) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Expr6.ToExpr(expr, parameters); err != nil { + return err + } if e.Operator != nil { *expr = append(*expr, biscuit.UnaryNegate) } + return nil } -func (e *Expr6) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Left.ToExpr(expr, parameters) +func (e *Expr6) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + start := len(*expr) + if err := e.Left.ToExpr(expr, parameters); err != nil { + return err + } for _, op := range e.Right { - op.ToExpr(expr, parameters) + if op.Extern == "" && op.Operator == OpTryOr { + // The receiver of .try_or() is evaluated lazily, as a closure. + receiver := slices.Clone((*expr)[start:]) + *expr = append((*expr)[:start], biscuit.Closure{Body: receiver}) + } + if err := op.ToExpr(expr, parameters); err != nil { + return err + } } + return nil } -func (e *ExprTerm) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - +func (e *ExprTerm) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { switch { case e.Term != nil: - //FIXME: error management - term, _ := e.Term.ToBiscuit(parameters) + term, err := e.Term.ToBiscuit(parameters) + if err != nil { + return err + } *expr = append(*expr, biscuit.Value{Term: term}) case e.Expression != nil: - e.Expression.ToExpr(expr, parameters) + if err := e.Expression.ToExpr(expr, parameters); err != nil { + return err + } *expr = append(*expr, biscuit.UnaryParens) } - + return nil } -func (e *OpExpr1) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr1.ToExpr(expr, parameters) - e.Operator.ToExpr(expr) +// && and || evaluate their right side only when needed: it is emitted as a +// closure without parameters (datalog v3.3). +func (e *OpExpr1) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + var right biscuit.Expression + if err := e.Expr1.ToExpr(&right, parameters); err != nil { + return err + } + *expr = append(*expr, biscuit.Closure{Body: right}) + return e.Operator.ToExpr(expr) } -func (e *OpExpr2) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr2.ToExpr(expr, parameters) - e.Operator.ToExpr(expr) +func (e *OpExpr2) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + var right biscuit.Expression + if err := e.Expr2.ToExpr(&right, parameters); err != nil { + return err + } + *expr = append(*expr, biscuit.Closure{Body: right}) + return e.Operator.ToExpr(expr) } -func (e *OpExpr3) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr3.ToExpr(expr, parameters) - e.Operator.ToExpr(expr) +func (e *OpExpr3) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Expr3.ToExpr(expr, parameters); err != nil { + return err + } + return e.Operator.ToExpr(expr) } -func (e *OpExpr4) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr4.ToExpr(expr, parameters) - e.Operator.ToExpr(expr) +func (e *OpExpr4) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Expr4.ToExpr(expr, parameters); err != nil { + return err + } + return e.Operator.ToExpr(expr) } -func (e *OpExpr5) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - e.Expr5.ToExpr(expr, parameters) - e.Operator.ToExpr(expr) +func (e *OpExpr5) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if err := e.Expr5.ToExpr(expr, parameters); err != nil { + return err + } + return e.Operator.ToExpr(expr) } -func (e *OpExpr7) ToExpr(expr *biscuit.Expression, parameters ParametersMap) { - if e.Expression != nil { - e.Expression.ToExpr(expr, parameters) +func (e *OpExpr7) ToExpr(expr *biscuit.Expression, parameters ParametersMap) error { + if e.Extern != "" { + name := strings.TrimPrefix(e.Extern, externPrefix) + if name == "" || name == e.Extern { + return fmt.Errorf("parser: unknown method .%s()", e.Extern) + } + if e.Closure != nil { + return fmt.Errorf("parser: .%s() does not take a closure", e.Extern) + } + if e.Expression == nil { + *expr = append(*expr, biscuit.ExternUnary{Name: name}) + return nil + } + if err := e.Expression.ToExpr(expr, parameters); err != nil { + return err + } + *expr = append(*expr, biscuit.ExternBinary{Name: name}) + return nil } - e.Operator.ToExpr(expr) + if e.Operator == OpTryOr && (e.Expression == nil || e.Closure != nil) { + return errors.New("parser: .try_or() takes a fallback value") + } + switch { + case e.Closure != nil: + var body biscuit.Expression + if err := e.Closure.Body.ToExpr(&body, parameters); err != nil { + return err + } + *expr = append(*expr, biscuit.Closure{Params: []biscuit.Variable{biscuit.Variable(*e.Closure.Param)}, Body: body}) + case e.Expression != nil: + if err := e.Expression.ToExpr(expr, parameters); err != nil { + return err + } + } + return e.Operator.ToExpr(expr) } -func (op *Operator) ToExpr(expr *biscuit.Expression) { - +func (op *Operator) ToExpr(expr *biscuit.Expression) error { var biscuit_op biscuit.Op switch *op { case OpAnd: @@ -457,6 +784,30 @@ func (op *Operator) ToExpr(expr *biscuit.Expression) { biscuit_op = biscuit.BinaryGreaterThan case OpEqual: biscuit_op = biscuit.BinaryEqual + case OpNotEqual: + biscuit_op = biscuit.BinaryNotEqual + case OpHeterogeneousEqual: + biscuit_op = biscuit.BinaryHeterogeneousEqual + case OpHeterogeneousNotEqual: + biscuit_op = biscuit.BinaryHeterogeneousNotEqual + case OpLazyAnd: + biscuit_op = biscuit.BinaryLazyAnd + case OpLazyOr: + biscuit_op = biscuit.BinaryLazyOr + case OpAll: + biscuit_op = biscuit.BinaryAll + case OpAny: + biscuit_op = biscuit.BinaryAny + case OpGet: + biscuit_op = biscuit.BinaryGet + case OpTryOr: + biscuit_op = biscuit.BinaryTryOr + case OpBitwiseAnd: + biscuit_op = biscuit.BinaryBitwiseAnd + case OpBitwiseOr: + biscuit_op = biscuit.BinaryBitwiseOr + case OpBitwiseXor: + biscuit_op = biscuit.BinaryBitwiseXor case OpContains: biscuit_op = biscuit.BinaryContains case OpPrefix: @@ -467,20 +818,18 @@ func (op *Operator) ToExpr(expr *biscuit.Expression) { biscuit_op = biscuit.BinaryRegex case OpLength: biscuit_op = biscuit.UnaryLength + case OpTypeOf: + biscuit_op = biscuit.UnaryTypeOf case OpIntersection: biscuit_op = biscuit.BinaryIntersection case OpUnion: biscuit_op = biscuit.BinaryUnion + default: + return fmt.Errorf("parser: unsupported operator %d", *op) } *expr = append(*expr, biscuit_op) -} - -type Set struct { - Not bool `@"not"? "in"` - Bytes []HexString `("[" ( @@ ("," @@)*)+ "]"` - String []string `| "[" (@String ("," @String)*)+ "]"` - Int []int64 `| "[" (@Int ("," @Int)*)+ "]")` + return nil } type HexString string @@ -543,19 +892,29 @@ func (a *Term) ToBiscuit(parameters ParametersMap) (biscuit.Term, error) { biscuitTerm = biscuit.Bytes(b) case a.Bool != nil: biscuitTerm = biscuit.Bool(*a.Bool) - case a.Set != nil: - biscuitSet := make(biscuit.Set, 0, len(a.Set)) - for _, term := range a.Set { - setTerm, err := term.ToBiscuit(parameters) + case a.Null: + biscuitTerm = biscuit.Null{} + case a.Braces != nil: + t, err := a.Braces.ToBiscuit(parameters) + if err != nil { + return nil, err + } + biscuitTerm = t + case a.EmptyArray: + biscuitTerm = biscuit.Array{} + case a.Array != nil: + array := make(biscuit.Array, 0, len(a.Array)) + for _, term := range a.Array { + elt, err := term.ToBiscuit(parameters) if err != nil { return nil, err } - if setTerm.Type() == biscuit.TermTypeVariable { - return nil, ErrVariableInSet + if elt.Type() == biscuit.TermTypeVariable { + return nil, errors.New("parser: an array cannot contain variables") } - biscuitSet = append(biscuitSet, setTerm) + array = append(array, elt) } - biscuitTerm = biscuitSet + biscuitTerm = array case a.Parameter != nil: paramName := string(*(a.Parameter)) paramValue := parameters[paramName] @@ -588,7 +947,9 @@ func (r *Rule) ToBiscuit(parameters ParametersMap) (*biscuit.Rule, error) { case p.Expression != nil: { var expr biscuit.Expression - (*p.Expression).ToExpr(&expr, parameters) + if err := (*p.Expression).ToExpr(&expr, parameters); err != nil { + return nil, err + } expressions = append(expressions, expr) } @@ -600,10 +961,16 @@ func (r *Rule) ToBiscuit(parameters ParametersMap) (*biscuit.Rule, error) { return nil, err } + scopes, err := scopesToBiscuit(r.Scopes) + if err != nil { + return nil, err + } + return &biscuit.Rule{ Head: *head, Body: body, Expressions: expressions, + Scopes: scopes, }, nil } @@ -620,6 +987,7 @@ func (c *Check) ToBiscuit(parameters ParametersMap) (*biscuit.Check, error) { return &biscuit.Check{ Queries: queries, + Kind: biscuit.CheckKind(c.Kind), }, nil } @@ -640,7 +1008,9 @@ func (r *CheckQuery) ToBiscuit(parameters ParametersMap) (*biscuit.Rule, error) case p.Expression != nil: { var expr biscuit.Expression - (*p.Expression).ToExpr(&expr, parameters) + if err := (*p.Expression).ToExpr(&expr, parameters); err != nil { + return nil, err + } expressions = append(expressions, expr) } @@ -652,10 +1022,16 @@ func (r *CheckQuery) ToBiscuit(parameters ParametersMap) (*biscuit.Rule, error) IDs: []biscuit.Term{}, } + scopes, err := scopesToBiscuit(r.Scopes) + if err != nil { + return nil, err + } + return &biscuit.Rule{ Head: *head, Body: body, Expressions: expressions, + Scopes: scopes, }, nil } diff --git a/parser/grammar_test.go b/parser/grammar_test.go index e22c13cf..288e8556 100644 --- a/parser/grammar_test.go +++ b/parser/grammar_test.go @@ -88,13 +88,33 @@ func TestGrammarPredicate(t *testing.T) { }, }, }, + { + Input: `right($1, {hex:41414141, "sym"})`, + Expected: &Predicate{ + Name: sptr("right"), + IDs: []*Term{ + {Variable: varptr("1")}, + {Braces: &Braces{Entries: []*BraceEntry{{Key: &Term{Bytes: hexsptr("41414141")}}, {Key: &Term{String: sptr("sym")}}}}}, + }, + }, + }, { Input: `right($1, [hex:41414141, "sym"])`, Expected: &Predicate{ Name: sptr("right"), IDs: []*Term{ {Variable: varptr("1")}, - {Set: []*Term{{Bytes: hexsptr("41414141")}, {String: sptr("sym")}}}, + {Array: []*Term{{Bytes: hexsptr("41414141")}, {String: sptr("sym")}}}, + }, + }, + }, + { + Input: `right({,}, {abc})`, + Expected: &Predicate{ + Name: sptr("right"), + IDs: []*Term{ + {Braces: &Braces{EmptySet: true}}, + {Parameter: paramptr("abc")}, }, }, }, @@ -141,16 +161,17 @@ func TestGrammarExpression(t *testing.T) { t2, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00") testCases := []struct { - Input string - Params ParametersMap - Expected *biscuit.Expression + Input string + Params ParametersMap + Expected *biscuit.Expression + ExpectErr bool }{ { Input: `$0 == 1`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Variable("0")}, biscuit.Value{Term: biscuit.Integer(1)}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, }, { @@ -193,7 +214,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `[1, 2, 3].contains($0)`, + Input: `{1, 2, 3}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.Integer(1), biscuit.Integer(2), biscuit.Integer(3)}}, biscuit.Value{Term: biscuit.Variable("0")}, @@ -201,7 +222,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `![4,5,6].contains($0)`, + Input: `!{4,5,6}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.Integer(4), biscuit.Integer(5), biscuit.Integer(6)}}, biscuit.Value{Term: biscuit.Variable("0")}, @@ -214,7 +235,7 @@ func TestGrammarExpression(t *testing.T) { Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Variable("0")}, biscuit.Value{Term: biscuit.String("abc")}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, }, { @@ -242,7 +263,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `["abc", "def"].contains($0)`, + Input: `{"abc", "def"}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.String("abc"), biscuit.String("def")}}, biscuit.Value{Term: biscuit.Variable("0")}, @@ -250,7 +271,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `!["abc", "def"].contains($0)`, + Input: `!{"abc", "def"}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.String("abc"), biscuit.String("def")}}, biscuit.Value{Term: biscuit.Variable("0")}, @@ -275,7 +296,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `[hex:41, hex:42, hex:43].contains($0)`, + Input: `{hex:41, hex:42, hex:43}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("A")), biscuit.Bytes([]byte("B")), biscuit.Bytes([]byte("C"))}}, @@ -284,7 +305,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `![hex:41, hex:42, hex:43].contains($0)`, + Input: `!{hex:41, hex:42, hex:43}.contains($0)`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("A")), biscuit.Bytes([]byte("B")), biscuit.Bytes([]byte("C"))}}, @@ -294,7 +315,7 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `[hex:41].union([hex:42]).intersection([hex:41]).length() == $0`, + Input: `{hex:41}.union({hex:42}).intersection({hex:41}).length() == $0`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("A"))}}, biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("B"))}}, @@ -303,7 +324,7 @@ func TestGrammarExpression(t *testing.T) { biscuit.BinaryIntersection, biscuit.UnaryLength, biscuit.Value{Term: biscuit.Variable("0")}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, }, { @@ -311,7 +332,7 @@ func TestGrammarExpression(t *testing.T) { Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Bytes([]byte{0x12, 0xab})}, biscuit.Value{Term: biscuit.Bytes([]byte{0xab})}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, }, { @@ -330,11 +351,23 @@ func TestGrammarExpression(t *testing.T) { biscuit.BinaryMul, biscuit.BinaryAdd, biscuit.Value{Term: biscuit.Integer(7)}, - biscuit.BinaryEqual, - biscuit.Value{Term: biscuit.Bool(false)}, - biscuit.BinaryOr, + biscuit.BinaryHeterogeneousEqual, + biscuit.Closure{Body: biscuit.Expression{biscuit.Value{Term: biscuit.Bool(false)}}}, + biscuit.BinaryLazyOr, }, }, + { + Input: `$0 == {missing}`, + ExpectErr: true, + }, + { + Input: `({missing} + 1) * 2 > $0`, + ExpectErr: true, + }, + { + Input: `$0.contains({missing})`, + ExpectErr: true, + }, } for _, testCase := range testCases { @@ -343,7 +376,12 @@ func TestGrammarExpression(t *testing.T) { require.NoError(t, err, testCase.Input) var expr biscuit.Expression - (*parsed).ToExpr(&expr, testCase.Params) + err = (*parsed).ToExpr(&expr, testCase.Params) + if testCase.ExpectErr { + require.Error(t, err, testCase.Input) + return + } + require.NoError(t, err, testCase.Input) require.Equal(t, testCase.Expected, &expr, testCase.Input) }) } @@ -468,7 +506,7 @@ func TestGrammarCheck(t *testing.T) { Expression: &Expression{ Left: &Expr1{ Left: &Expr2{ - Left: &Expr3{ + Left: &ExprXor{Left: &ExprBitOr{Left: &ExprBitAnd{Left: &Expr3{ Left: &Expr4{ Left: &Expr5{ Expr6: &Expr6{ @@ -480,10 +518,10 @@ func TestGrammarCheck(t *testing.T) { }, }, }, - }, + }}}}, Right: &OpExpr3{ Operator: OpGreaterThan, - Expr3: &Expr3{ + Expr3: &ExprXor{Left: &ExprBitOr{Left: &ExprBitAnd{Left: &Expr3{ Left: &Expr4{ Left: &Expr5{ Expr6: &Expr6{ @@ -495,7 +533,7 @@ func TestGrammarCheck(t *testing.T) { }, }, }, - }, + }}}}, }, }, }, @@ -505,7 +543,7 @@ func TestGrammarCheck(t *testing.T) { Expression: &Expression{ Left: &Expr1{ Left: &Expr2{ - Left: &Expr3{ + Left: &ExprXor{Left: &ExprBitOr{Left: &ExprBitAnd{Left: &Expr3{ Left: &Expr4{ Left: &Expr5{ Expr6: &Expr6{ @@ -520,7 +558,7 @@ func TestGrammarCheck(t *testing.T) { Expression: &Expression{ Left: &Expr1{ Left: &Expr2{ - Left: &Expr3{ + Left: &ExprXor{Left: &ExprBitOr{Left: &ExprBitAnd{Left: &Expr3{ Left: &Expr4{ Left: &Expr5{ Expr6: &Expr6{ @@ -532,7 +570,7 @@ func TestGrammarCheck(t *testing.T) { }, }, }, - }, + }}}}, }, }, }, @@ -541,7 +579,7 @@ func TestGrammarCheck(t *testing.T) { }, }, }, - }, + }}}}, }, }, }, @@ -750,8 +788,9 @@ func sptr(s string) *string { return &s } -func i64ptr(i int64) *int64 { - return &i +func i64ptr(i int64) *Integer { + v := Integer(i) + return &v } func hexsptr(s string) *HexString { diff --git a/parser/parser.go b/parser/parser.go index 3df3596b..036ec63a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -5,6 +5,7 @@ package parser import ( "errors" + "sync" "github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2/lexer" @@ -16,22 +17,26 @@ var ( ErrVariableInSet = errors.New("parser: a set cannot contain any variables") ) +// Rules are tried in order and the first match wins. Word-like rules need \b +// so that they do not split an identifier such as `truename` or `checkpoint`. +// Method names (length, contains, ...) lex as Ident; the grammar matches them +// by value after a Dot. var BiscuitLexerRules = []lexer.SimpleRule{ - {Name: "Keyword", Pattern: `check if|allow if|deny if`}, - {Name: "Function", Pattern: `prefix|suffix|matches|length|contains`}, + {Name: "Keyword", Pattern: `\b(check if|check all|reject if|allow if|deny if)\b`}, {Name: "Hex", Pattern: `hex:([0-9a-fA-F]{2})*`}, + {Name: "PublicKey", Pattern: `\b(ed25519|secp256r1)/[0-9a-fA-F]+`}, {Name: "Dot", Pattern: `\.`}, {Name: "Arrow", Pattern: `<-`}, + {Name: "ClosureArrow", Pattern: `->`}, {Name: "Or", Pattern: `\|\|`}, {Name: "And", Pattern: `&&`}, - {Name: "Operator", Pattern: `==|>=|<=|>|<|\+|-|\*`}, + {Name: "Operator", Pattern: `===|==|!==|!=|>=|<=|>|<|\+|-|\*`}, {Name: "Comment", Pattern: `//[^\n]*`}, {Name: "String", Pattern: `\"[^\"]*\"`}, {Name: "Variable", Pattern: `\$[a-zA-Z0-9_:]+`}, - {Name: "Parameter", Pattern: `\{[a-zA-Z0-9_:]+\}`}, {Name: "DateTime", Pattern: `\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|([-+]\d\d:\d\d))?`}, {Name: "Int", Pattern: `[0-9]+`}, - {Name: "Bool", Pattern: `true|false`}, + {Name: "Bool", Pattern: `\b(true|false)\b`}, {Name: "Ident", Pattern: `[a-z][a-zA-Z0-9_:]*`}, {Name: "Whitespace", Pattern: `[ \t]+`}, {Name: "EOL", Pattern: `[\n\r]+`}, @@ -40,7 +45,9 @@ var BiscuitLexerRules = []lexer.SimpleRule{ var DefaultParserOptions = []participle.Option{ participle.Lexer(lexer.MustSimple(BiscuitLexerRules)), - participle.UseLookahead(1), + // A method argument may be `$p -> body` or an expression starting with a + // variable; the second token decides. + participle.UseLookahead(2), participle.Elide("Whitespace", "EOL"), participle.Unquote("String"), } @@ -217,6 +224,17 @@ func (p *parser) Must() MustParser { return &mustParser{parser: p} } +var ( + sharedParser Parser + sharedParserOnce sync.Once +) + +// Building a parser costs milliseconds; a built parser is stateless and safe for concurrent use. +func shared() Parser { + sharedParserOnce.Do(func() { sharedParser = New() }) + return sharedParser +} + func (m *mustParser) Fact(fact string, parameters ParametersMap) biscuit.Fact { f, err := m.parser.Fact(fact, parameters) if err != nil { @@ -296,37 +314,25 @@ func FromStringAuthorizer(input string) (biscuit.ParsedAuthorizer, error) { } func FromStringFactWithParams(input string, parameters ParametersMap) (biscuit.Fact, error) { - p := New() - - return p.Fact(input, parameters) + return shared().Fact(input, parameters) } func FromStringRuleWithParams(input string, parameters ParametersMap) (biscuit.Rule, error) { - p := New() - - return p.Rule(input, parameters) + return shared().Rule(input, parameters) } func FromStringCheckWithParams(input string, parameters ParametersMap) (biscuit.Check, error) { - p := New() - - return p.Check(input, parameters) + return shared().Check(input, parameters) } func FromStringPolicyWithParams(input string, parameters ParametersMap) (biscuit.Policy, error) { - p := New() - - return p.Policy(input, parameters) + return shared().Policy(input, parameters) } func FromStringBlockWithParams(input string, parameters ParametersMap) (biscuit.ParsedBlock, error) { - p := New() - - return p.Block(input, parameters) + return shared().Block(input, parameters) } func FromStringAuthorizerWithParams(input string, parameters ParametersMap) (biscuit.ParsedAuthorizer, error) { - p := New() - - return p.Authorizer(input, parameters) + return shared().Authorizer(input, parameters) } diff --git a/parser/parser_test.go b/parser/parser_test.go index a2bd7e7a..3c2b8baa 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -5,6 +5,7 @@ package parser import ( "fmt" + "sync" "testing" "time" @@ -22,7 +23,7 @@ type testCase struct { func getFactTestCases() []testCase { return []testCase{ { - Input: `right("/a/file1.txt", "read", ["read", "/a/file2.txt"])`, + Input: `right("/a/file1.txt", "read", {"read", "/a/file2.txt"})`, Expected: biscuit.Fact{ Predicate: biscuit.Predicate{ Name: "right", @@ -54,6 +55,43 @@ func getFactTestCases() []testCase { Input: `right("/a/file1.txt", [$0])`, ExpectFailure: true, }, + { + Input: `right("/a/file1.txt" "read")`, + ExpectFailure: true, + }, + { + Input: `right(1, 2 3, 4)`, + ExpectFailure: true, + }, + { + Input: `empty()`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "empty", IDs: []biscuit.Term{}}}, + }, + // Names starting with a method or literal keyword are still names. + { + Input: `prefixed("a")`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "prefixed", IDs: []biscuit.Term{biscuit.String("a")}}}, + }, + { + Input: `lengthy(1)`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "lengthy", IDs: []biscuit.Term{biscuit.Integer(1)}}}, + }, + { + Input: `contains_x(1)`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "contains_x", IDs: []biscuit.Term{biscuit.Integer(1)}}}, + }, + { + Input: `matcheslist(1)`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "matcheslist", IDs: []biscuit.Term{biscuit.Integer(1)}}}, + }, + { + Input: `truename("x")`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "truename", IDs: []biscuit.Term{biscuit.String("x")}}}, + }, + { + Input: `falsehood(true)`, + Expected: biscuit.Fact{Predicate: biscuit.Predicate{Name: "falsehood", IDs: []biscuit.Term{biscuit.Bool(true)}}}, + }, } } @@ -162,7 +200,7 @@ func getRuleTestCases() []testCase { ExpectFailure: true, }, { - Input: `rule1("a") <- body1("b"), $0 > 0, $1 < 1, $2 >= 2, $3 <= 3, $4 == 4, [1, 2, 3].contains($5), ![4,5,6].contains($6)`, + Input: `rule1("a") <- body1("b"), $0 > 0, $1 < 1, $2 >= 2, $3 <= 3, $4 == 4, {1, 2, 3}.contains($5), !{4,5,6}.contains($6)`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -196,7 +234,7 @@ func getRuleTestCases() []testCase { { biscuit.Value{Term: biscuit.Variable("4")}, biscuit.Value{Term: biscuit.Integer(4)}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, { biscuit.Value{Term: biscuit.Set{biscuit.Integer(1), biscuit.Integer(2), biscuit.Integer(3)}}, @@ -213,7 +251,7 @@ func getRuleTestCases() []testCase { }, }, { - Input: `rule1("a") <- body1("b"), $0 == "abc", $1.starts_with("def"), $2.ends_with("ghi"), $3.matches("file[0-9]+.txt"), ["a","b"].contains($4), !["c", "d"].contains($5)`, + Input: `rule1("a") <- body1("b"), $0 == "abc", $1.starts_with("def"), $2.ends_with("ghi"), $3.matches("file[0-9]+.txt"), {"a","b"}.contains($4), !{"c", "d"}.contains($5)`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -227,7 +265,7 @@ func getRuleTestCases() []testCase { { biscuit.Value{Term: biscuit.Variable("0")}, biscuit.Value{Term: biscuit.String("abc")}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, { biscuit.Value{Term: biscuit.Variable("1")}, @@ -259,7 +297,7 @@ func getRuleTestCases() []testCase { }, }, { - Input: `rule1("a") <- body1("b"), ["a", "b"].contains($0), !["c", "d"].contains($1)`, + Input: `rule1("a") <- body1("b"), {"a", "b"}.contains($0), !{"c", "d"}.contains($1)`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -313,13 +351,13 @@ func getRuleTestCases() []testCase { { biscuit.Value{Term: biscuit.Variable("0")}, biscuit.Value{Term: biscuit.Bytes([]byte{0x41, 0x41, 0x41, 0x41})}, - biscuit.BinaryEqual, + biscuit.BinaryHeterogeneousEqual, }, }, }, }, { - Input: `rule1("a") <- body1($0, $1), ["abc", "def"].contains($0), ! [41, 42].contains($1)`, + Input: `rule1("a") <- body1($0, $1), {"abc", "def"}.contains($0), ! {41, 42}.contains($1)`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -384,10 +422,12 @@ func getRuleTestCases() []testCase { Expressions: []biscuit.Expression{ { biscuit.Value{Term: biscuit.Bool(true)}, - biscuit.Value{Term: biscuit.Bool(false)}, - biscuit.Value{Term: biscuit.Bool(true)}, - biscuit.BinaryAnd, - biscuit.BinaryOr, + biscuit.Closure{Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Bool(false)}, + biscuit.Closure{Body: biscuit.Expression{biscuit.Value{Term: biscuit.Bool(true)}}}, + biscuit.BinaryLazyAnd, + }}, + biscuit.BinaryLazyOr, }, }, }, @@ -445,7 +485,7 @@ func getRuleTestCases() []testCase { func getCheckTestCases() []testCase { return []testCase{ { - Input: `check if parent("a", "b"), parent("b", "c"), [1,2,3].contains($0) or right("read", "/a/file1.txt")`, + Input: `check if parent("a", "b"), parent("b", "c"), {1,2,3}.contains($0) or right("read", "/a/file1.txt")`, Expected: biscuit.Check{ Queries: []biscuit.Rule{ { @@ -615,3 +655,238 @@ func TestIssue84(t *testing.T) { _ = rule require.NoError(t, err) } + +// The FromString helpers share one parser; parsing from many goroutines must be safe. +func TestFromStringConcurrent(t *testing.T) { + want, err := FromStringFact(`right("/a/file1", "read")`) + require.NoError(t, err) + + var wg sync.WaitGroup + for i := 0; i < 32; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 50; j++ { + got, err := FromStringFact(`right("/a/file1", "read")`) + require.NoError(t, err) + require.Equal(t, want, got) + _, err = FromStringRule(`head($a) <- body($a), $a == "x"`) + require.NoError(t, err) + } + }() + } + wg.Wait() +} + +// An unbound parameter inside an expression used to produce a nil term +// instead of an error, and crash later when the element was converted. +func TestUnboundParameterInExpression(t *testing.T) { + p := New() + params := ParametersMap{"bound": biscuit.Integer(1)} + + _, err := p.Check(`check if resource($r), $r == {missing}`, params) + require.ErrorContains(t, err, "unbound parameter: missing") + + _, err = p.Rule(`head($r) <- body($r), $r == {missing}`, params) + require.ErrorContains(t, err, "unbound parameter: missing") + + _, err = p.Policy(`allow if resource($r), $r == {missing}`, params) + require.ErrorContains(t, err, "unbound parameter: missing") + + _, err = p.Block(`f(1); check if resource($r), $r == {missing};`, params) + require.ErrorContains(t, err, "unbound parameter: missing") + + _, err = p.Authorizer(`allow if resource($r), $r == {missing};`, params) + require.ErrorContains(t, err, "unbound parameter: missing") + + // Bound parameters in expressions still work. + c, err := p.Check(`check if resource($r), $r == {bound}`, params) + require.NoError(t, err) + require.Equal(t, biscuit.Value{Term: biscuit.Integer(1)}, c.Queries[0].Expressions[0][1]) +} + +// `trusting` clauses on rules, check queries, policies and blocks. +func TestParseScopes(t *testing.T) { + p := New() + const keyText = "ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189" + key, err := biscuit.ParsePublicKey(keyText) + require.NoError(t, err) + trustKey := biscuit.Scope{Kind: biscuit.ScopePublicKey, PublicKey: key} + + rule, err := p.Rule(`member($u) <- user($u), group($u, "admin") trusting previous, `+keyText, nil) + require.NoError(t, err) + require.Equal(t, []biscuit.Scope{{Kind: biscuit.ScopePrevious}, trustKey}, rule.Scopes) + + rule, err = p.Rule(`member($u) <- user($u)`, nil) + require.NoError(t, err) + require.Empty(t, rule.Scopes) + + check, err := p.Check(`check if group("admin") trusting `+keyText+` or user("root") trusting authority`, nil) + require.NoError(t, err) + require.Len(t, check.Queries, 2) + require.Equal(t, []biscuit.Scope{trustKey}, check.Queries[0].Scopes) + require.Equal(t, []biscuit.Scope{{Kind: biscuit.ScopeAuthority}}, check.Queries[1].Scopes) + + policy, err := p.Policy(`allow if query(1, 2) trusting `+keyText, nil) + require.NoError(t, err) + require.Equal(t, []biscuit.Scope{trustKey}, policy.Queries[0].Scopes) + + block, err := p.Block(`trusting authority, previous; fact(1); rule($a) <- fact($a) trusting `+keyText+`;`, nil) + require.NoError(t, err) + require.Equal(t, []biscuit.Scope{{Kind: biscuit.ScopeAuthority}, {Kind: biscuit.ScopePrevious}}, block.Scopes) + require.Len(t, block.Facts, 1) + require.Equal(t, []biscuit.Scope{trustKey}, block.Rules[0].Scopes) + + authorizer, err := p.Authorizer(`trusting `+keyText+`; allow if true;`, nil) + require.NoError(t, err) + require.Equal(t, []biscuit.Scope{trustKey}, authorizer.Block.Scopes) + + // A predicate named after a scope keyword is still a predicate. + fact, err := p.Fact(`previous("x")`, nil) + require.NoError(t, err) + require.Equal(t, "previous", fact.Name) + + _, err = p.Rule(`r($a) <- f($a) trusting ed25519/abcd`, nil) + require.ErrorContains(t, err, "invalid public key") + _, err = p.Rule(`r($a) <- f($a) trusting rsa/abcd`, nil) + require.Error(t, err) +} + +// .all() and .any() take a `$param -> body` closure; && and || wrap their +// right side in a closure. +func TestParseClosures(t *testing.T) { + p := New() + + check, err := p.Check(`check if {1, 2, 3}.all($p -> $p > 0)`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Set{biscuit.Integer(1), biscuit.Integer(2), biscuit.Integer(3)}}, + biscuit.Closure{Params: []biscuit.Variable{"p"}, Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("p")}, + biscuit.Value{Term: biscuit.Integer(0)}, + biscuit.BinaryGreaterThan, + }}, + biscuit.BinaryAll, + }, check.Queries[0].Expressions[0]) + + check, err = p.Check(`check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Set{biscuit.Integer(1), biscuit.Integer(2), biscuit.Integer(3)}}, + biscuit.Closure{Params: []biscuit.Variable{"p"}, Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("p")}, + biscuit.Value{Term: biscuit.Integer(1)}, + biscuit.BinaryGreaterThan, + biscuit.Closure{Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Set{biscuit.Integer(3), biscuit.Integer(4), biscuit.Integer(5)}}, + biscuit.Closure{Params: []biscuit.Variable{"q"}, Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("p")}, + biscuit.Value{Term: biscuit.Variable("q")}, + biscuit.BinaryHeterogeneousEqual, + }}, + biscuit.BinaryAny, + }}, + biscuit.BinaryLazyAnd, + }}, + biscuit.BinaryAny, + }, check.Queries[0].Expressions[0]) + + // Extern functions, unary and binary. + check, err = p.Check(`check if $x.extern::check(), $x.extern::compare("a") == "equal strings"`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("x")}, + biscuit.ExternUnary{Name: "check"}, + }, check.Queries[0].Expressions[0]) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("x")}, + biscuit.Value{Term: biscuit.String("a")}, + biscuit.ExternBinary{Name: "compare"}, + biscuit.Value{Term: biscuit.String("equal strings")}, + biscuit.BinaryHeterogeneousEqual, + }, check.Queries[0].Expressions[1]) + _, err = p.Check(`check if $x.extern::()`, nil) + require.Error(t, err) + _, err = p.Check(`check if $x.unknown()`, nil) + require.Error(t, err) + _, err = p.Check(`check if $x.extern::f($p -> $p)`, nil) + require.ErrorContains(t, err, "does not take a closure") + + // .try_or(): the receiver, with the method calls before it, becomes a + // closure; the fallback is an ordinary operand. + check, err = p.Check(`check if ($x === 12).try_or(true), $s.length().try_or(0) > 1`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Closure{Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("x")}, + biscuit.Value{Term: biscuit.Integer(12)}, + biscuit.BinaryEqual, + biscuit.UnaryParens, + }}, + biscuit.Value{Term: biscuit.Bool(true)}, + biscuit.BinaryTryOr, + }, check.Queries[0].Expressions[0]) + require.Equal(t, biscuit.Expression{ + biscuit.Closure{Body: biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("s")}, + biscuit.UnaryLength, + }}, + biscuit.Value{Term: biscuit.Integer(0)}, + biscuit.BinaryTryOr, + biscuit.Value{Term: biscuit.Integer(1)}, + biscuit.BinaryGreaterThan, + }, check.Queries[0].Expressions[1]) + _, err = p.Check(`check if $x.try_or()`, nil) + require.ErrorContains(t, err, "fallback") + _, err = p.Check(`check if $x.try_or($p -> $p)`, nil) + require.ErrorContains(t, err, "fallback") + + // A method argument that is a plain expression starting with a variable. + check, err = p.Check(`check if {1, 2}.contains($x)`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Set{biscuit.Integer(1), biscuit.Integer(2)}}, + biscuit.Value{Term: biscuit.Variable("x")}, + biscuit.BinaryContains, + }, check.Queries[0].Expressions[0]) +} + +// Brackets are arrays, braces are sets or maps. +func TestParseArraysAndMaps(t *testing.T) { + p := New() + + fact, err := p.Fact(`f([1, "a", [2]], {}, [], {"a": 1, 2: "b"}, {1, 2}, {,})`, nil) + require.NoError(t, err) + require.Equal(t, []biscuit.Term{ + biscuit.Array{biscuit.Integer(1), biscuit.String("a"), biscuit.Array{biscuit.Integer(2)}}, + biscuit.Map{}, + biscuit.Array{}, + biscuit.Map{{Key: biscuit.String("a"), Value: biscuit.Integer(1)}, {Key: biscuit.Integer(2), Value: biscuit.String("b")}}, + biscuit.Set{biscuit.Integer(1), biscuit.Integer(2)}, + biscuit.Set{}, + }, fact.IDs) + + check, err := p.Check(`check if {"user": {"roles": ["admin"]}}.get("user").get("roles").contains("admin")`, nil) + require.NoError(t, err) + require.Equal(t, biscuit.Expression{ + biscuit.Value{Term: biscuit.Map{{Key: biscuit.String("user"), Value: biscuit.Map{{Key: biscuit.String("roles"), Value: biscuit.Array{biscuit.String("admin")}}}}}}, + biscuit.Value{Term: biscuit.String("user")}, + biscuit.BinaryGet, + biscuit.Value{Term: biscuit.String("roles")}, + biscuit.BinaryGet, + biscuit.Value{Term: biscuit.String("admin")}, + biscuit.BinaryContains, + }, check.Queries[0].Expressions[0]) + + // A set of one parameter, as the README writes it. + fact, err = p.Fact(`f({{read}})`, ParametersMap{"read": biscuit.String("read")}) + require.NoError(t, err) + require.Equal(t, []biscuit.Term{biscuit.Set{biscuit.String("read")}}, fact.IDs) + + _, err = p.Fact(`f({1: "a", 2})`, nil) + require.ErrorContains(t, err, "map entry without a value") + _, err = p.Fact(`f({true: 1})`, nil) + require.ErrorContains(t, err, "map key must be an integer or a string") + _, err = p.Rule(`r($x) <- s($x), [$x].contains(1)`, nil) + require.ErrorContains(t, err, "array cannot contain variables") +} diff --git a/parser/testdata/samples_corpus.golden b/parser/testdata/samples_corpus.golden new file mode 100644 index 00000000..17d21b4b --- /dev/null +++ b/parser/testdata/samples_corpus.golden @@ -0,0 +1,115 @@ +test001_basic.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test001_basic.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test001_basic.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\")\n] Rules:[] Checks:[] Scopes:[]}}" +test002_different_root_key.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test002_different_root_key.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test002_different_root_key.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test003_invalid_signature_format.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test003_invalid_signature_format.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test003_invalid_signature_format.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test004_random_block.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test004_random_block.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test004_random_block.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test005_invalid_signature.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test005_invalid_signature.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test005_invalid_signature.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test006_reordered_blocks.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test006_reordered_blocks.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test006_reordered_blocks.bc block 2 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource(\"file1\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test006_reordered_blocks.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test007_scoped_rules.bc block 0 ok "{Facts:[\n\tuser_id(\"alice\"),\n\towner(\"alice\", \"file1\")\n] Rules:[] Checks:[] Scopes:[]}" +test007_scoped_rules.bc block 1 ok "{Facts:[] Rules:[{Head:right($0, \"read\") Body:[resource($0) user_id($1) owner($1, $0)] Expressions:[] Scopes:[]}] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test007_scoped_rules.bc block 2 ok "{Facts:[\n\towner(\"alice\", \"file2\")\n] Rules:[] Checks:[] Scopes:[]}" +test007_scoped_rules.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test008_scoped_checks.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test008_scoped_checks.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test008_scoped_checks.bc block 2 ok "{Facts:[\n\tright(\"file2\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test008_scoped_checks.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test009_expired_token.bc block 0 ok "{Facts:[] Rules:[] Checks:[] Scopes:[]}" +test009_expired_token.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource(\"file1\")] Expressions:[] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[time($time)] Expressions:[[{Term:$time} {Term:2018-12-20T00:00:00Z} 2]] Scopes:[]}] Kind:0}] Scopes:[]}" +test009_expired_token.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\toperation(\"read\"),\n\ttime(2020-12-21T09:23:12Z)\n] Rules:[] Checks:[] Scopes:[]}}" +test010_authorizer_scope.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test010_authorizer_scope.bc block 1 ok "{Facts:[\n\tright(\"file2\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test010_authorizer_scope.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\toperation(\"read\")\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[right($0, $1) resource($0) operation($1)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}}" +test011_authorizer_authority_caveats.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test011_authorizer_authority_caveats.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\toperation(\"read\")\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[right($0, $1) resource($0) operation($1)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}}" +test012_authority_caveats.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource(\"file1\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test012_authority_caveats.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test012_authority_caveats.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test013_block_rules.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\")\n] Rules:[] Checks:[] Scopes:[]}" +test013_block_rules.bc block 1 ok "{Facts:[] Rules:[{Head:valid_date(\"file1\") Body:[time($0) resource(\"file1\")] Expressions:[[{Term:$0} {Term:2030-12-31T12:59:59Z} 2]] Scopes:[]} {Head:valid_date($1) Body:[time($0) resource($1)] Expressions:[[{Term:$0} {Term:1999-12-31T12:59:59Z} 2] [{Term:{\"file1\"}} {Term:$1} 6 1]] Scopes:[]}] Checks:[{Queries:[{Head:query() Body:[valid_date($0) resource($0)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test013_block_rules.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\ttime(2020-12-21T09:23:12Z)\n] Rules:[] Checks:[] Scopes:[]}}" +test013_block_rules.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file2\"),\n\ttime(2020-12-21T09:23:12Z)\n] Rules:[] Checks:[] Scopes:[]}}" +test014_regex_constraint.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0)] Expressions:[[{Term:$0} {Term:\"file[0-9]+.txt\"} 9]] Scopes:[]}] Kind:0}] Scopes:[]}" +test014_regex_constraint.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\")\n] Rules:[] Checks:[] Scopes:[]}}" +test014_regex_constraint.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file123.txt\")\n] Rules:[] Checks:[] Scopes:[]}}" +test015_multi_queries_caveats.bc block 0 ok "{Facts:[\n\tmust_be_present(\"hello\")\n] Rules:[] Checks:[] Scopes:[]}" +test015_multi_queries_caveats.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[must_be_present($0)] Expressions:[] Scopes:[]} {Head:query() Body:[must_be_present($0)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}}" +test016_caveat_head_name.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource(\"hello\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test016_caveat_head_name.bc block 1 ok "{Facts:[\n\tquery(\"test\")\n] Rules:[] Checks:[] Scopes:[]}" +test016_caveat_head_name.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test017_expressions.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Term:true} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Term:false} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:2} 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2} {Term:1} 3]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:2} 2]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:1} 2]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2} {Term:1} 4]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2} {Term:2} 4]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:3} {Term:3} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:2} {Term:3} 12 10 {Term:4} {Term:2} 13 11 {Term:5} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"hello world\"} {Term:\"hello\"} 7] [{Term:\"hello world\"} {Term:\"world\"} 8]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"aaabde\"} {Term:\"a*c?.e\"} 9]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"aaabde\"} {Term:\"abd\"} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"aaabde\"} {Term:\"aaa\"} {Term:\"b\"} 10 {Term:\"de\"} 10 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12\"} {Term:\"abcD12\"} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12\"} 3 {Term:6} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"é\"} 3 {Term:2} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2019-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2020-12-04T09:46:41Z} {Term:2019-12-04T09:46:41Z} 3]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2019-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 2]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2020-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 4]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2020-12-04T09:46:41Z} {Term:2019-12-04T09:46:41Z} 4]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2020-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 4]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2020-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:12ab} {Term:hex:12ab} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:2} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}} {Term:2020-12-04T09:46:41Z} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{false, true}} {Term:true} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"abc\", \"def\"}} {Term:\"abc\"} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{hex:12ab, hex:34de}} {Term:hex:34de} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:{2}} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:{1, 2}} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:{2, 3}} 16 {Term:{2}} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:{2, 3}} 17 {Term:{1, 2, 3}} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Term:{1, 2}} 16 {Term:1} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Term:{1, 2}} 16 3 {Term:2} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{,}} 3 {Term:0} 5]] Scopes:[]}] Kind:0}] Scopes:[]}" +test017_expressions.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test018_unbound_variables_in_rule.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[operation(\"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test018_unbound_variables_in_rule.bc block 1 ok "{Facts:[] Rules:[{Head:operation($unbound, \"read\") Body:[operation($any1, $any2)] Expressions:[] Scopes:[]}] Checks:[] Scopes:[]}" +test018_unbound_variables_in_rule.bc authorizer 0 ok "{Policies:[] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test019_generating_ambient_from_variables.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[operation(\"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test019_generating_ambient_from_variables.bc block 1 ok "{Facts:[] Rules:[{Head:operation(\"read\") Body:[operation($any)] Expressions:[] Scopes:[]}] Checks:[] Scopes:[]}" +test019_generating_ambient_from_variables.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\toperation(\"write\")\n] Rules:[] Checks:[] Scopes:[]}}" +test020_sealed.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test020_sealed.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test020_sealed.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test021_parsing.bc block 0 ok "{Facts:[\n\tns::fact_123(\"hello é\\t😁\")\n] Rules:[] Checks:[] Scopes:[]}" +test021_parsing.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[ns::fact_123(\"hello é\\t😁\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}}" +test022_default_symbols.bc block 0 ok "{Facts:[\n\tread(0),\n\twrite(1),\n\tresource(2),\n\toperation(3),\n\tright(4),\n\ttime(5),\n\trole(6),\n\towner(7),\n\ttenant(8),\n\tnamespace(9),\n\tuser(10),\n\tteam(11),\n\tservice(12),\n\tadmin(13),\n\temail(14),\n\tgroup(15),\n\tmember(16),\n\tip_address(17),\n\tclient(18),\n\tclient_ip(19),\n\tdomain(20),\n\tpath(21),\n\tversion(22),\n\tcluster(23),\n\tnode(24),\n\thostname(25),\n\tnonce(26),\n\tquery(27)\n] Rules:[] Checks:[] Scopes:[]}" +test022_default_symbols.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[read(0) write(1) resource(2) operation(3) right(4) time(5) role(6) owner(7) tenant(8) namespace(9) user(10) team(11) service(12) admin(13) email(14) group(15) member(16) ip_address(17) client(18) client_ip(19) domain(20) path(21) version(22) cluster(23) node(24) hostname(25) nonce(26) query(27)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}}" +test023_execution_scope.bc block 0 ok "{Facts:[\n\tauthority_fact(1)\n] Rules:[] Checks:[] Scopes:[]}" +test023_execution_scope.bc block 1 ok "{Facts:[\n\tblock1_fact(1)\n] Rules:[] Checks:[] Scopes:[]}" +test023_execution_scope.bc block 2 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[authority_fact($var)] Expressions:[] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[block1_fact($var)] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test023_execution_scope.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test024_third_party.bc block 0 ok "{Facts:[\n\tright(\"read\")\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[group(\"admin\")] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:0}] Scopes:[]}" +test024_third_party.bc block 1 ok "{Facts:[\n\tgroup(\"admin\")\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[right(\"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test024_third_party.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test025_check_all.bc block 0 ok "{Facts:[\n\tallowed_operations({\"A\", \"B\"})\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[operation($op) allowed_operations($allowed)] Expressions:[[{Term:$allowed} {Term:$op} 6]] Scopes:[]}] Kind:1}] Scopes:[]}" +test025_check_all.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\toperation(\"A\"),\n\toperation(\"B\")\n] Rules:[] Checks:[] Scopes:[]}}" +test025_check_all.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\toperation(\"A\"),\n\toperation(\"invalid\")\n] Rules:[] Checks:[] Scopes:[]}}" +test025_check_all.bc authorizer 2 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test026_public_keys_interning.bc block 0 ok "{Facts:[\n\tquery(0)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[previous ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:0}] Scopes:[]}" +test026_public_keys_interning.bc block 1 ok "{Facts:[\n\tquery(1)\n] Rules:[{Head:query(1, 2) Body:[query(1) query(2)] Expressions:[] Scopes:[ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Checks:[{Queries:[{Head:query() Body:[query(2) query(3)] Expressions:[] Scopes:[ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Kind:0} {Queries:[{Head:query() Body:[query(1)] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:0}] Scopes:[]}" +test026_public_keys_interning.bc block 2 ok "{Facts:[\n\tquery(2)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[query(2) query(3)] Expressions:[] Scopes:[ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Kind:0} {Queries:[{Head:query() Body:[query(1)] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:0}] Scopes:[]}" +test026_public_keys_interning.bc block 3 ok "{Facts:[\n\tquery(3)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[query(2) query(3)] Expressions:[] Scopes:[ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Kind:0} {Queries:[{Head:query() Body:[query(1)] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:0}] Scopes:[]}" +test026_public_keys_interning.bc block 4 ok "{Facts:[\n\tquery(4)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[query(2)] Expressions:[] Scopes:[ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Kind:0} {Queries:[{Head:query() Body:[query(4)] Expressions:[] Scopes:[ed25519/f98da8c1cf907856431bfc3dc87531e0eaadba90f919edc232405b85877ef136]}] Kind:0}] Scopes:[]}" +test026_public_keys_interning.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[query(3)] Expressions:[] Scopes:[]}] Kind:1} {Queries:[{Head:query() Body:[query(1, 2)] Expressions:[] Scopes:[]}] Kind:1} {Queries:[{Head:query() Body:[query(0)] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189]}] Kind:1} {Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[query(1, 2)] Expressions:[] Scopes:[ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189 ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463]}] Kind:0}] Scopes:[]}}" +test027_integer_wraparound.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:10000000000} {Term:10000000000} 12 {Term:0} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:9223372036854775807} {Term:1} 10 {Term:0} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:-9223372036854775808} {Term:1} 11 {Term:0} 21]] Scopes:[]}] Kind:0}] Scopes:[]}" +test027_integer_wraparound.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test028_expressions_v4.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Term:false} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:3} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:2} 19 {Term:3} 20 {Term:0} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12x\"} {Term:\"abcD12\"} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2022-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:12abcd} {Term:hex:12ab} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 4}} {Term:{1, 2}} 21]] Scopes:[]}] Kind:0}] Scopes:[]}" +test028_expressions_v4.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test029_reject_if.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[test($test)] Expressions:[[{Term:$test}]] Scopes:[]}] Kind:2}] Scopes:[]}" +test029_reject_if.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\ttest(false)\n] Rules:[] Checks:[] Scopes:[]}}" +test029_reject_if.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\ttest(true)\n] Rules:[] Checks:[] Scopes:[]}}" +test030_null.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[fact(null, $value)] Expressions:[[{Term:$value} {Term:null} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[fact(null, $value)] Expressions:[[{Term:$value} {Term:null} 23]] Scopes:[]}] Kind:2}] Scopes:[]}" +test030_null.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(null, null)\n] Rules:[] Checks:[] Scopes:[]}}" +test030_null.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(null, 1)\n] Rules:[] Checks:[] Scopes:[]}}" +test030_null.bc authorizer 2 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(null, true)\n] Rules:[] Checks:[] Scopes:[]}}" +test030_null.bc authorizer 3 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(null, \"abcd\")\n] Rules:[] Checks:[] Scopes:[]}}" +test031_heterogeneous_equal.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Term:true} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Term:false} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:1} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:3} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:1} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12\"} {Term:\"abcD12\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12x\"} {Term:\"abcD12\"} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"abcD12x\"} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2022-12-04T09:46:41Z} {Term:2022-12-04T09:46:41Z} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2022-12-04T09:46:41Z} {Term:2020-12-04T09:46:41Z} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2022-12-04T09:46:41Z} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:12abcd} {Term:hex:12abcd} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:12abcd} {Term:hex:12ab} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:12abcd} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2}} {Term:{1, 2}} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 4}} {Term:{1, 2}} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 4}} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[fact(1, $value)] Expressions:[[{Term:1} {Term:$value} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[fact2(1, $value)] Expressions:[[{Term:1} {Term:$value} 23]] Scopes:[]}] Kind:0}] Scopes:[]}" +test031_heterogeneous_equal.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(1, 1),\n\tfact2(1, 2)\n] Rules:[] Checks:[] Scopes:[]}}" +test031_heterogeneous_equal.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tfact(1, 2),\n\tfact2(1, 1)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Term:false} 23]] Scopes:[]}] Kind:0}] Scopes:[]}}" +test032_laziness_closures.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:false} 1 {Params:[] Body:[{Term:true}]} 24]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Params:[] Body:[{Term:true}]} 25]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Params:[] Body:[{Term:false}]} 25 2 {Params:[] Body:[{Term:true}]} 24]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:false} {Params:[] Body:[{Term:\"x\"} {Term:\"x\"} 16]} 24 2 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Params:[] Body:[{Term:\"x\"} {Term:\"x\"} 16]} 25]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Params:[$p] Body:[{Term:$p} {Term:0} 3]} 26]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Params:[$p] Body:[{Term:$p} {Term:2} 22]} 26 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Params:[$p] Body:[{Term:$p} {Term:2} 3]} 27]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Params:[$p] Body:[{Term:$p} {Term:3} 3]} 27 1]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1, 2, 3}} {Params:[$p] Body:[{Term:$p} {Term:1} 3 {Params:[] Body:[{Term:{3, 4, 5}} {Params:[$q] Body:[{Term:$p} {Term:$q} 22]} 27]} 24]} 27]] Scopes:[]}] Kind:0}] Scopes:[]}" +test032_laziness_closures.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test032_laziness_closures.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"true\"}} {Params:[$p] Body:[{Term:{\"true\"}} {Params:[$p] Body:[{Term:$p}]} 26]} 27]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test033_typeof.bc block 0 ok "{Facts:[\n\tinteger(1),\n\tstring(\"test\"),\n\tdate(2023-12-28T00:00:00Z),\n\tbytes(hex:aa),\n\tbool(true),\n\tset({false, true}),\n\tnull(null),\n\tarray([1, 2, 3]),\n\tmap({\"a\": true})\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:1} 4 {Term:\"integer\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[integer($t)] Expressions:[[{Term:$t} 4 {Term:\"integer\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:\"test\"} 4 {Term:\"string\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[string($t)] Expressions:[[{Term:$t} 4 {Term:\"string\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:2023-12-28T00:00:00Z} 2 4 {Term:\"date\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[date($t)] Expressions:[[{Term:$t} 4 {Term:\"date\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:hex:aa} 4 {Term:\"bytes\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[bytes($t)] Expressions:[[{Term:$t} 4 {Term:\"bytes\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:true} 4 {Term:\"bool\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[bool($t)] Expressions:[[{Term:$t} 4 {Term:\"bool\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{false, true}} 4 {Term:\"set\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[set($t)] Expressions:[[{Term:$t} 4 {Term:\"set\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:null} 4 {Term:\"null\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[null($t)] Expressions:[[{Term:$t} 4 {Term:\"null\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[array($t)] Expressions:[[{Term:$t} 4 {Term:\"array\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[map($t)] Expressions:[[{Term:$t} 4 {Term:\"map\"} 22]] Scopes:[]}] Kind:0}] Scopes:[]}" +test033_typeof.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test034_array_map.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2, 1]} 3 {Term:3} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\"]} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\"]} {Term:[1, 2, 3]} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\"]} {Term:[\"a\", \"b\"]} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\"]} {Term:[\"a\", \"b\"]} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\"]} {Term:[\"a\", \"c\"]} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[\"a\", \"b\", \"c\"]} {Term:\"c\"} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2, 3]} {Term:[1, 2]} 7]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[4, 5, 6]} {Term:[6]} 8]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2, \"a\"]} {Term:2} 28 {Term:\"a\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2]} {Term:3} 28 {Term:null} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2, 3]} {Params:[$p] Body:[{Term:$p} {Term:0} 3]} 26]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:[1, 2, 3]} {Params:[$p] Body:[{Term:$p} {Term:2} 3]} 27]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}} 3 {Term:4} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"a\", 2: \"b\"}} {Term:true} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"a\", 2: \"b\"}} {Term:{\"a\": 1, \"b\": 2}} 23]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"a\", 2: \"b\"}} {Term:{1: \"a\", 2: \"b\"}} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"a\", 2: \"b\"}} {Term:{\"a\": 1, \"b\": 2}} 21]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"a\", 2: \"b\"}} {Term:{1: \"a\", 2: \"b\"}} 5]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}} {Term:\"d\"} 6]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"A\", \"a\": 1, \"b\": 2}} {Term:\"a\"} 28 {Term:1} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"A\", \"a\": 1, \"b\": 2}} {Term:1} 28 {Term:\"A\"} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"A\", \"a\": 1, \"b\": 2}} {Term:\"c\"} 28 {Term:null} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"A\", \"a\": 1, \"b\": 2}} {Term:2} 28 {Term:null} 22]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"a\": 1, \"b\": 2}} {Params:[$kv] Body:[{Term:$kv} {Term:0} 28 {Term:\"c\"} 23 {Params:[] Body:[{Term:$kv} {Term:1} 28 {Term:3} 1]} 24]} 26]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{1: \"A\", \"a\": 1, \"b\": 2}} {Params:[$kv] Body:[{Term:$kv} {Term:0} 28 {Term:1} 22 {Params:[] Body:[{Term:$kv} {Term:1} 28 {Term:\"A\"} 22]} 24]} 27]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Term:{\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}} {Term:\"user\"} 28 {Term:\"roles\"} 28 {Term:\"admin\"} 6]] Scopes:[]}] Kind:0}] Scopes:[]}" +test034_array_map.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test035_ffi.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true} {Name:test}] [{Term:\"a\"} {Term:\"a\"} {Name:test} {Term:\"equal strings\"} 22]] Scopes:[]}] Kind:0}] Scopes:[]}" +test035_ffi.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test036_secp256r1.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[] Scopes:[]}" +test036_secp256r1.bc block 1 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test036_secp256r1.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test037_secp256r1_third_party.bc block 0 ok "{Facts:[\n\tright(\"file1\", \"read\"),\n\tright(\"file2\", \"read\"),\n\tright(\"file1\", \"write\")\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[from_third(true)] Expressions:[] Scopes:[secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf]}] Kind:0}] Scopes:[]}" +test037_secp256r1_third_party.bc block 1 ok "{Facts:[\n\tfrom_third(true)\n] Rules:[] Checks:[{Queries:[{Head:query() Body:[resource($0) operation(\"read\") right($0, \"read\")] Expressions:[] Scopes:[]}] Kind:0}] Scopes:[]}" +test037_secp256r1_third_party.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[\n\tresource(\"file1\"),\n\toperation(\"read\")\n] Rules:[] Checks:[] Scopes:[]}}" +test038_try_op.bc block 0 ok "{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Params:[] Body:[{Term:true} {Term:12} 5 2]} {Term:true} 29]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Params:[] Body:[{Params:[] Body:[{Term:true} {Term:12} 5 2]} {Term:true} {Term:12} 5 29 2]} {Term:true} 29]] Scopes:[]}] Kind:0} {Queries:[{Head:query() Body:[] Expressions:[[{Params:[] Body:[{Term:true} {Term:12} 22 2]} {Term:true} 29]] Scopes:[]}] Kind:2}] Scopes:[]}" +test038_try_op.bc authorizer 0 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[] Scopes:[]}}" +test038_try_op.bc authorizer 1 ok "{Policies:[{Queries:[{Head:query() Body:[] Expressions:[[{Term:true}]] Scopes:[]}] Kind:0}] Block:{Facts:[] Rules:[] Checks:[{Queries:[{Head:query() Body:[] Expressions:[[{Params:[] Body:[{Term:true}]} {Term:true} {Term:12} 5 29]] Scopes:[]}] Kind:0}] Scopes:[]}}" diff --git a/pb/biscuit.pb.go b/pb/biscuit.pb.go deleted file mode 100644 index ccc6fa4e..00000000 --- a/pb/biscuit.pb.go +++ /dev/null @@ -1,1889 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 -// source: biscuit.proto - -package pb - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type PublicKey_Algorithm int32 - -const ( - PublicKey_Ed25519 PublicKey_Algorithm = 0 -) - -// Enum value maps for PublicKey_Algorithm. -var ( - PublicKey_Algorithm_name = map[int32]string{ - 0: "Ed25519", - } - PublicKey_Algorithm_value = map[string]int32{ - "Ed25519": 0, - } -) - -func (x PublicKey_Algorithm) Enum() *PublicKey_Algorithm { - p := new(PublicKey_Algorithm) - *p = x - return p -} - -func (x PublicKey_Algorithm) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (PublicKey_Algorithm) Descriptor() protoreflect.EnumDescriptor { - return file_biscuit_proto_enumTypes[0].Descriptor() -} - -func (PublicKey_Algorithm) Type() protoreflect.EnumType { - return &file_biscuit_proto_enumTypes[0] -} - -func (x PublicKey_Algorithm) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Do not use. -func (x *PublicKey_Algorithm) UnmarshalJSON(b []byte) error { - num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) - if err != nil { - return err - } - *x = PublicKey_Algorithm(num) - return nil -} - -// Deprecated: Use PublicKey_Algorithm.Descriptor instead. -func (PublicKey_Algorithm) EnumDescriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{2, 0} -} - -type OpUnary_Kind int32 - -const ( - OpUnary_Negate OpUnary_Kind = 0 - OpUnary_Parens OpUnary_Kind = 1 - OpUnary_Length OpUnary_Kind = 2 -) - -// Enum value maps for OpUnary_Kind. -var ( - OpUnary_Kind_name = map[int32]string{ - 0: "Negate", - 1: "Parens", - 2: "Length", - } - OpUnary_Kind_value = map[string]int32{ - "Negate": 0, - "Parens": 1, - "Length": 2, - } -) - -func (x OpUnary_Kind) Enum() *OpUnary_Kind { - p := new(OpUnary_Kind) - *p = x - return p -} - -func (x OpUnary_Kind) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (OpUnary_Kind) Descriptor() protoreflect.EnumDescriptor { - return file_biscuit_proto_enumTypes[1].Descriptor() -} - -func (OpUnary_Kind) Type() protoreflect.EnumType { - return &file_biscuit_proto_enumTypes[1] -} - -func (x OpUnary_Kind) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Do not use. -func (x *OpUnary_Kind) UnmarshalJSON(b []byte) error { - num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) - if err != nil { - return err - } - *x = OpUnary_Kind(num) - return nil -} - -// Deprecated: Use OpUnary_Kind.Descriptor instead. -func (OpUnary_Kind) EnumDescriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{13, 0} -} - -type OpBinary_Kind int32 - -const ( - OpBinary_LessThan OpBinary_Kind = 0 - OpBinary_GreaterThan OpBinary_Kind = 1 - OpBinary_LessOrEqual OpBinary_Kind = 2 - OpBinary_GreaterOrEqual OpBinary_Kind = 3 - OpBinary_Equal OpBinary_Kind = 4 - OpBinary_Contains OpBinary_Kind = 5 - OpBinary_Prefix OpBinary_Kind = 6 - OpBinary_Suffix OpBinary_Kind = 7 - OpBinary_Regex OpBinary_Kind = 8 - OpBinary_Add OpBinary_Kind = 9 - OpBinary_Sub OpBinary_Kind = 10 - OpBinary_Mul OpBinary_Kind = 11 - OpBinary_Div OpBinary_Kind = 12 - OpBinary_And OpBinary_Kind = 13 - OpBinary_Or OpBinary_Kind = 14 - OpBinary_Intersection OpBinary_Kind = 15 - OpBinary_Union OpBinary_Kind = 16 -) - -// Enum value maps for OpBinary_Kind. -var ( - OpBinary_Kind_name = map[int32]string{ - 0: "LessThan", - 1: "GreaterThan", - 2: "LessOrEqual", - 3: "GreaterOrEqual", - 4: "Equal", - 5: "Contains", - 6: "Prefix", - 7: "Suffix", - 8: "Regex", - 9: "Add", - 10: "Sub", - 11: "Mul", - 12: "Div", - 13: "And", - 14: "Or", - 15: "Intersection", - 16: "Union", - } - OpBinary_Kind_value = map[string]int32{ - "LessThan": 0, - "GreaterThan": 1, - "LessOrEqual": 2, - "GreaterOrEqual": 3, - "Equal": 4, - "Contains": 5, - "Prefix": 6, - "Suffix": 7, - "Regex": 8, - "Add": 9, - "Sub": 10, - "Mul": 11, - "Div": 12, - "And": 13, - "Or": 14, - "Intersection": 15, - "Union": 16, - } -) - -func (x OpBinary_Kind) Enum() *OpBinary_Kind { - p := new(OpBinary_Kind) - *p = x - return p -} - -func (x OpBinary_Kind) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (OpBinary_Kind) Descriptor() protoreflect.EnumDescriptor { - return file_biscuit_proto_enumTypes[2].Descriptor() -} - -func (OpBinary_Kind) Type() protoreflect.EnumType { - return &file_biscuit_proto_enumTypes[2] -} - -func (x OpBinary_Kind) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Do not use. -func (x *OpBinary_Kind) UnmarshalJSON(b []byte) error { - num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) - if err != nil { - return err - } - *x = OpBinary_Kind(num) - return nil -} - -// Deprecated: Use OpBinary_Kind.Descriptor instead. -func (OpBinary_Kind) EnumDescriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{14, 0} -} - -type Policy_Kind int32 - -const ( - Policy_Allow Policy_Kind = 0 - Policy_Deny Policy_Kind = 1 -) - -// Enum value maps for Policy_Kind. -var ( - Policy_Kind_name = map[int32]string{ - 0: "Allow", - 1: "Deny", - } - Policy_Kind_value = map[string]int32{ - "Allow": 0, - "Deny": 1, - } -) - -func (x Policy_Kind) Enum() *Policy_Kind { - p := new(Policy_Kind) - *p = x - return p -} - -func (x Policy_Kind) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Policy_Kind) Descriptor() protoreflect.EnumDescriptor { - return file_biscuit_proto_enumTypes[3].Descriptor() -} - -func (Policy_Kind) Type() protoreflect.EnumType { - return &file_biscuit_proto_enumTypes[3] -} - -func (x Policy_Kind) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Do not use. -func (x *Policy_Kind) UnmarshalJSON(b []byte) error { - num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) - if err != nil { - return err - } - *x = Policy_Kind(num) - return nil -} - -// Deprecated: Use Policy_Kind.Descriptor instead. -func (Policy_Kind) EnumDescriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{15, 0} -} - -type Biscuit struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - RootKeyId *uint32 `protobuf:"varint,1,opt,name=rootKeyId" json:"rootKeyId,omitempty"` - Authority *SignedBlock `protobuf:"bytes,2,req,name=authority" json:"authority,omitempty"` - Blocks []*SignedBlock `protobuf:"bytes,3,rep,name=blocks" json:"blocks,omitempty"` - Proof *Proof `protobuf:"bytes,4,req,name=proof" json:"proof,omitempty"` -} - -func (x *Biscuit) Reset() { - *x = Biscuit{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Biscuit) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Biscuit) ProtoMessage() {} - -func (x *Biscuit) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Biscuit.ProtoReflect.Descriptor instead. -func (*Biscuit) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{0} -} - -func (x *Biscuit) GetRootKeyId() uint32 { - if x != nil && x.RootKeyId != nil { - return *x.RootKeyId - } - return 0 -} - -func (x *Biscuit) GetAuthority() *SignedBlock { - if x != nil { - return x.Authority - } - return nil -} - -func (x *Biscuit) GetBlocks() []*SignedBlock { - if x != nil { - return x.Blocks - } - return nil -} - -func (x *Biscuit) GetProof() *Proof { - if x != nil { - return x.Proof - } - return nil -} - -type SignedBlock struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Block []byte `protobuf:"bytes,1,req,name=block" json:"block,omitempty"` - NextKey *PublicKey `protobuf:"bytes,2,req,name=nextKey" json:"nextKey,omitempty"` - Signature []byte `protobuf:"bytes,3,req,name=signature" json:"signature,omitempty"` -} - -func (x *SignedBlock) Reset() { - *x = SignedBlock{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SignedBlock) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SignedBlock) ProtoMessage() {} - -func (x *SignedBlock) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SignedBlock.ProtoReflect.Descriptor instead. -func (*SignedBlock) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{1} -} - -func (x *SignedBlock) GetBlock() []byte { - if x != nil { - return x.Block - } - return nil -} - -func (x *SignedBlock) GetNextKey() *PublicKey { - if x != nil { - return x.NextKey - } - return nil -} - -func (x *SignedBlock) GetSignature() []byte { - if x != nil { - return x.Signature - } - return nil -} - -type PublicKey struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Algorithm *PublicKey_Algorithm `protobuf:"varint,1,req,name=algorithm,enum=PublicKey_Algorithm" json:"algorithm,omitempty"` - Key []byte `protobuf:"bytes,2,req,name=key" json:"key,omitempty"` -} - -func (x *PublicKey) Reset() { - *x = PublicKey{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PublicKey) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PublicKey) ProtoMessage() {} - -func (x *PublicKey) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PublicKey.ProtoReflect.Descriptor instead. -func (*PublicKey) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{2} -} - -func (x *PublicKey) GetAlgorithm() PublicKey_Algorithm { - if x != nil && x.Algorithm != nil { - return *x.Algorithm - } - return PublicKey_Ed25519 -} - -func (x *PublicKey) GetKey() []byte { - if x != nil { - return x.Key - } - return nil -} - -type Proof struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to Content: - // *Proof_NextSecret - // *Proof_FinalSignature - Content isProof_Content `protobuf_oneof:"Content"` -} - -func (x *Proof) Reset() { - *x = Proof{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Proof) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Proof) ProtoMessage() {} - -func (x *Proof) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Proof.ProtoReflect.Descriptor instead. -func (*Proof) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{3} -} - -func (m *Proof) GetContent() isProof_Content { - if m != nil { - return m.Content - } - return nil -} - -func (x *Proof) GetNextSecret() []byte { - if x, ok := x.GetContent().(*Proof_NextSecret); ok { - return x.NextSecret - } - return nil -} - -func (x *Proof) GetFinalSignature() []byte { - if x, ok := x.GetContent().(*Proof_FinalSignature); ok { - return x.FinalSignature - } - return nil -} - -type isProof_Content interface { - isProof_Content() -} - -type Proof_NextSecret struct { - NextSecret []byte `protobuf:"bytes,1,opt,name=nextSecret,oneof"` -} - -type Proof_FinalSignature struct { - FinalSignature []byte `protobuf:"bytes,2,opt,name=finalSignature,oneof"` -} - -func (*Proof_NextSecret) isProof_Content() {} - -func (*Proof_FinalSignature) isProof_Content() {} - -type Block struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Symbols []string `protobuf:"bytes,1,rep,name=symbols" json:"symbols,omitempty"` - Context *string `protobuf:"bytes,2,opt,name=context" json:"context,omitempty"` - Version *uint32 `protobuf:"varint,3,opt,name=version" json:"version,omitempty"` - FactsV2 []*FactV2 `protobuf:"bytes,4,rep,name=facts_v2,json=factsV2" json:"facts_v2,omitempty"` - RulesV2 []*RuleV2 `protobuf:"bytes,5,rep,name=rules_v2,json=rulesV2" json:"rules_v2,omitempty"` - ChecksV2 []*CheckV2 `protobuf:"bytes,6,rep,name=checks_v2,json=checksV2" json:"checks_v2,omitempty"` -} - -func (x *Block) Reset() { - *x = Block{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Block) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Block) ProtoMessage() {} - -func (x *Block) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Block.ProtoReflect.Descriptor instead. -func (*Block) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{4} -} - -func (x *Block) GetSymbols() []string { - if x != nil { - return x.Symbols - } - return nil -} - -func (x *Block) GetContext() string { - if x != nil && x.Context != nil { - return *x.Context - } - return "" -} - -func (x *Block) GetVersion() uint32 { - if x != nil && x.Version != nil { - return *x.Version - } - return 0 -} - -func (x *Block) GetFactsV2() []*FactV2 { - if x != nil { - return x.FactsV2 - } - return nil -} - -func (x *Block) GetRulesV2() []*RuleV2 { - if x != nil { - return x.RulesV2 - } - return nil -} - -func (x *Block) GetChecksV2() []*CheckV2 { - if x != nil { - return x.ChecksV2 - } - return nil -} - -type FactV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Predicate *PredicateV2 `protobuf:"bytes,1,req,name=predicate" json:"predicate,omitempty"` -} - -func (x *FactV2) Reset() { - *x = FactV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *FactV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FactV2) ProtoMessage() {} - -func (x *FactV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FactV2.ProtoReflect.Descriptor instead. -func (*FactV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{5} -} - -func (x *FactV2) GetPredicate() *PredicateV2 { - if x != nil { - return x.Predicate - } - return nil -} - -type RuleV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Head *PredicateV2 `protobuf:"bytes,1,req,name=head" json:"head,omitempty"` - Body []*PredicateV2 `protobuf:"bytes,2,rep,name=body" json:"body,omitempty"` - Expressions []*ExpressionV2 `protobuf:"bytes,3,rep,name=expressions" json:"expressions,omitempty"` -} - -func (x *RuleV2) Reset() { - *x = RuleV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RuleV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RuleV2) ProtoMessage() {} - -func (x *RuleV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RuleV2.ProtoReflect.Descriptor instead. -func (*RuleV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{6} -} - -func (x *RuleV2) GetHead() *PredicateV2 { - if x != nil { - return x.Head - } - return nil -} - -func (x *RuleV2) GetBody() []*PredicateV2 { - if x != nil { - return x.Body - } - return nil -} - -func (x *RuleV2) GetExpressions() []*ExpressionV2 { - if x != nil { - return x.Expressions - } - return nil -} - -type CheckV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Queries []*RuleV2 `protobuf:"bytes,1,rep,name=queries" json:"queries,omitempty"` -} - -func (x *CheckV2) Reset() { - *x = CheckV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CheckV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CheckV2) ProtoMessage() {} - -func (x *CheckV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CheckV2.ProtoReflect.Descriptor instead. -func (*CheckV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{7} -} - -func (x *CheckV2) GetQueries() []*RuleV2 { - if x != nil { - return x.Queries - } - return nil -} - -type PredicateV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name *uint64 `protobuf:"varint,1,req,name=name" json:"name,omitempty"` - Terms []*TermV2 `protobuf:"bytes,2,rep,name=terms" json:"terms,omitempty"` -} - -func (x *PredicateV2) Reset() { - *x = PredicateV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PredicateV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PredicateV2) ProtoMessage() {} - -func (x *PredicateV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PredicateV2.ProtoReflect.Descriptor instead. -func (*PredicateV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{8} -} - -func (x *PredicateV2) GetName() uint64 { - if x != nil && x.Name != nil { - return *x.Name - } - return 0 -} - -func (x *PredicateV2) GetTerms() []*TermV2 { - if x != nil { - return x.Terms - } - return nil -} - -type TermV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to Content: - // *TermV2_Variable - // *TermV2_Integer - // *TermV2_String_ - // *TermV2_Date - // *TermV2_Bytes - // *TermV2_Bool - // *TermV2_Set - Content isTermV2_Content `protobuf_oneof:"Content"` -} - -func (x *TermV2) Reset() { - *x = TermV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TermV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TermV2) ProtoMessage() {} - -func (x *TermV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TermV2.ProtoReflect.Descriptor instead. -func (*TermV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{9} -} - -func (m *TermV2) GetContent() isTermV2_Content { - if m != nil { - return m.Content - } - return nil -} - -func (x *TermV2) GetVariable() uint32 { - if x, ok := x.GetContent().(*TermV2_Variable); ok { - return x.Variable - } - return 0 -} - -func (x *TermV2) GetInteger() int64 { - if x, ok := x.GetContent().(*TermV2_Integer); ok { - return x.Integer - } - return 0 -} - -func (x *TermV2) GetString_() uint64 { - if x, ok := x.GetContent().(*TermV2_String_); ok { - return x.String_ - } - return 0 -} - -func (x *TermV2) GetDate() uint64 { - if x, ok := x.GetContent().(*TermV2_Date); ok { - return x.Date - } - return 0 -} - -func (x *TermV2) GetBytes() []byte { - if x, ok := x.GetContent().(*TermV2_Bytes); ok { - return x.Bytes - } - return nil -} - -func (x *TermV2) GetBool() bool { - if x, ok := x.GetContent().(*TermV2_Bool); ok { - return x.Bool - } - return false -} - -func (x *TermV2) GetSet() *TermSet { - if x, ok := x.GetContent().(*TermV2_Set); ok { - return x.Set - } - return nil -} - -type isTermV2_Content interface { - isTermV2_Content() -} - -type TermV2_Variable struct { - Variable uint32 `protobuf:"varint,1,opt,name=variable,oneof"` -} - -type TermV2_Integer struct { - Integer int64 `protobuf:"varint,2,opt,name=integer,oneof"` -} - -type TermV2_String_ struct { - String_ uint64 `protobuf:"varint,3,opt,name=string,oneof"` -} - -type TermV2_Date struct { - Date uint64 `protobuf:"varint,4,opt,name=date,oneof"` -} - -type TermV2_Bytes struct { - Bytes []byte `protobuf:"bytes,5,opt,name=bytes,oneof"` -} - -type TermV2_Bool struct { - Bool bool `protobuf:"varint,6,opt,name=bool,oneof"` -} - -type TermV2_Set struct { - Set *TermSet `protobuf:"bytes,7,opt,name=set,oneof"` -} - -func (*TermV2_Variable) isTermV2_Content() {} - -func (*TermV2_Integer) isTermV2_Content() {} - -func (*TermV2_String_) isTermV2_Content() {} - -func (*TermV2_Date) isTermV2_Content() {} - -func (*TermV2_Bytes) isTermV2_Content() {} - -func (*TermV2_Bool) isTermV2_Content() {} - -func (*TermV2_Set) isTermV2_Content() {} - -type TermSet struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Set []*TermV2 `protobuf:"bytes,1,rep,name=set" json:"set,omitempty"` -} - -func (x *TermSet) Reset() { - *x = TermSet{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TermSet) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TermSet) ProtoMessage() {} - -func (x *TermSet) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TermSet.ProtoReflect.Descriptor instead. -func (*TermSet) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{10} -} - -func (x *TermSet) GetSet() []*TermV2 { - if x != nil { - return x.Set - } - return nil -} - -type ExpressionV2 struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ops []*Op `protobuf:"bytes,1,rep,name=ops" json:"ops,omitempty"` -} - -func (x *ExpressionV2) Reset() { - *x = ExpressionV2{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ExpressionV2) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExpressionV2) ProtoMessage() {} - -func (x *ExpressionV2) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExpressionV2.ProtoReflect.Descriptor instead. -func (*ExpressionV2) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{11} -} - -func (x *ExpressionV2) GetOps() []*Op { - if x != nil { - return x.Ops - } - return nil -} - -type Op struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to Content: - // *Op_Value - // *Op_Unary - // *Op_Binary - Content isOp_Content `protobuf_oneof:"Content"` -} - -func (x *Op) Reset() { - *x = Op{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Op) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Op) ProtoMessage() {} - -func (x *Op) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Op.ProtoReflect.Descriptor instead. -func (*Op) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{12} -} - -func (m *Op) GetContent() isOp_Content { - if m != nil { - return m.Content - } - return nil -} - -func (x *Op) GetValue() *TermV2 { - if x, ok := x.GetContent().(*Op_Value); ok { - return x.Value - } - return nil -} - -func (x *Op) GetUnary() *OpUnary { - if x, ok := x.GetContent().(*Op_Unary); ok { - return x.Unary - } - return nil -} - -func (x *Op) GetBinary() *OpBinary { - if x, ok := x.GetContent().(*Op_Binary); ok { - return x.Binary - } - return nil -} - -type isOp_Content interface { - isOp_Content() -} - -type Op_Value struct { - Value *TermV2 `protobuf:"bytes,1,opt,name=value,oneof"` -} - -type Op_Unary struct { - Unary *OpUnary `protobuf:"bytes,2,opt,name=unary,oneof"` -} - -type Op_Binary struct { - Binary *OpBinary `protobuf:"bytes,3,opt,name=Binary,oneof"` -} - -func (*Op_Value) isOp_Content() {} - -func (*Op_Unary) isOp_Content() {} - -func (*Op_Binary) isOp_Content() {} - -type OpUnary struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Kind *OpUnary_Kind `protobuf:"varint,1,req,name=kind,enum=OpUnary_Kind" json:"kind,omitempty"` -} - -func (x *OpUnary) Reset() { - *x = OpUnary{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OpUnary) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OpUnary) ProtoMessage() {} - -func (x *OpUnary) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OpUnary.ProtoReflect.Descriptor instead. -func (*OpUnary) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{13} -} - -func (x *OpUnary) GetKind() OpUnary_Kind { - if x != nil && x.Kind != nil { - return *x.Kind - } - return OpUnary_Negate -} - -type OpBinary struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Kind *OpBinary_Kind `protobuf:"varint,1,req,name=kind,enum=OpBinary_Kind" json:"kind,omitempty"` -} - -func (x *OpBinary) Reset() { - *x = OpBinary{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OpBinary) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OpBinary) ProtoMessage() {} - -func (x *OpBinary) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OpBinary.ProtoReflect.Descriptor instead. -func (*OpBinary) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{14} -} - -func (x *OpBinary) GetKind() OpBinary_Kind { - if x != nil && x.Kind != nil { - return *x.Kind - } - return OpBinary_LessThan -} - -type Policy struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Queries []*RuleV2 `protobuf:"bytes,1,rep,name=queries" json:"queries,omitempty"` - Kind *Policy_Kind `protobuf:"varint,2,req,name=kind,enum=Policy_Kind" json:"kind,omitempty"` -} - -func (x *Policy) Reset() { - *x = Policy{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Policy) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Policy) ProtoMessage() {} - -func (x *Policy) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Policy.ProtoReflect.Descriptor instead. -func (*Policy) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{15} -} - -func (x *Policy) GetQueries() []*RuleV2 { - if x != nil { - return x.Queries - } - return nil -} - -func (x *Policy) GetKind() Policy_Kind { - if x != nil && x.Kind != nil { - return *x.Kind - } - return Policy_Allow -} - -type AuthorizerPolicies struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Symbols []string `protobuf:"bytes,1,rep,name=symbols" json:"symbols,omitempty"` - Version *uint32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` - Facts []*FactV2 `protobuf:"bytes,3,rep,name=facts" json:"facts,omitempty"` - Rules []*RuleV2 `protobuf:"bytes,4,rep,name=rules" json:"rules,omitempty"` - Checks []*CheckV2 `protobuf:"bytes,5,rep,name=checks" json:"checks,omitempty"` - Policies []*Policy `protobuf:"bytes,6,rep,name=policies" json:"policies,omitempty"` -} - -func (x *AuthorizerPolicies) Reset() { - *x = AuthorizerPolicies{} - if protoimpl.UnsafeEnabled { - mi := &file_biscuit_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuthorizerPolicies) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuthorizerPolicies) ProtoMessage() {} - -func (x *AuthorizerPolicies) ProtoReflect() protoreflect.Message { - mi := &file_biscuit_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AuthorizerPolicies.ProtoReflect.Descriptor instead. -func (*AuthorizerPolicies) Descriptor() ([]byte, []int) { - return file_biscuit_proto_rawDescGZIP(), []int{16} -} - -func (x *AuthorizerPolicies) GetSymbols() []string { - if x != nil { - return x.Symbols - } - return nil -} - -func (x *AuthorizerPolicies) GetVersion() uint32 { - if x != nil && x.Version != nil { - return *x.Version - } - return 0 -} - -func (x *AuthorizerPolicies) GetFacts() []*FactV2 { - if x != nil { - return x.Facts - } - return nil -} - -func (x *AuthorizerPolicies) GetRules() []*RuleV2 { - if x != nil { - return x.Rules - } - return nil -} - -func (x *AuthorizerPolicies) GetChecks() []*CheckV2 { - if x != nil { - return x.Checks - } - return nil -} - -func (x *AuthorizerPolicies) GetPolicies() []*Policy { - if x != nil { - return x.Policies - } - return nil -} - -var File_biscuit_proto protoreflect.FileDescriptor - -var file_biscuit_proto_rawDesc = []byte{ - 0x0a, 0x0d, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x97, 0x01, 0x0a, 0x07, 0x42, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, - 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x09, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x53, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x24, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1c, 0x0a, 0x05, 0x70, - 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x50, 0x72, 0x6f, - 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x67, 0x0a, 0x0b, 0x53, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x24, - 0x0a, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, 0x32, - 0x0a, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6e, 0x65, 0x78, - 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x03, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x22, 0x6b, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, - 0x32, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x02, - 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x2e, 0x41, - 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, - 0x74, 0x68, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0c, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x18, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, - 0x68, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x10, 0x00, 0x22, - 0x5e, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x20, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, - 0x6e, 0x65, 0x78, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x0e, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, - 0xc4, 0x01, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, - 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, - 0x6f, 0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x08, 0x66, 0x61, 0x63, 0x74, 0x73, - 0x5f, 0x76, 0x32, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x46, 0x61, 0x63, 0x74, - 0x56, 0x32, 0x52, 0x07, 0x66, 0x61, 0x63, 0x74, 0x73, 0x56, 0x32, 0x12, 0x22, 0x0a, 0x08, 0x72, - 0x75, 0x6c, 0x65, 0x73, 0x5f, 0x76, 0x32, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, - 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x56, 0x32, 0x12, - 0x25, 0x0a, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x5f, 0x76, 0x32, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x32, 0x52, 0x08, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x73, 0x56, 0x32, 0x22, 0x34, 0x0a, 0x06, 0x46, 0x61, 0x63, 0x74, 0x56, 0x32, - 0x12, 0x2a, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, - 0x02, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x56, - 0x32, 0x52, 0x09, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x7d, 0x0a, 0x06, - 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, 0x12, 0x20, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x18, 0x01, - 0x20, 0x02, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x56, 0x32, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x20, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x56, 0x32, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x2f, 0x0a, 0x0b, 0x65, 0x78, - 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0d, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x52, 0x0b, - 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x2c, 0x0a, 0x07, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x56, 0x32, 0x12, 0x21, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, - 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0x40, 0x0a, 0x0b, 0x50, 0x72, 0x65, - 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x56, 0x32, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x02, 0x28, 0x04, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x05, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x54, 0x65, - 0x72, 0x6d, 0x56, 0x32, 0x52, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x06, - 0x54, 0x65, 0x72, 0x6d, 0x56, 0x32, 0x12, 0x1c, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, - 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x04, 0x64, 0x61, - 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x16, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x48, - 0x00, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x04, 0x62, 0x6f, 0x6f, 0x6c, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x12, 0x1c, - 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x54, 0x65, - 0x72, 0x6d, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x73, 0x65, 0x74, 0x42, 0x09, 0x0a, 0x07, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x24, 0x0a, 0x07, 0x54, 0x65, 0x72, 0x6d, 0x53, - 0x65, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x07, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x56, 0x32, 0x52, 0x03, 0x73, 0x65, 0x74, 0x22, 0x25, 0x0a, - 0x0c, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x12, 0x15, 0x0a, - 0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x03, 0x2e, 0x4f, 0x70, 0x52, - 0x03, 0x6f, 0x70, 0x73, 0x22, 0x77, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x1f, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x54, 0x65, 0x72, 0x6d, - 0x56, 0x32, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x20, 0x0a, 0x05, 0x75, - 0x6e, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x4f, 0x70, 0x55, - 0x6e, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x05, 0x75, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x23, 0x0a, - 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, - 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x06, 0x42, 0x69, 0x6e, 0x61, - 0x72, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x58, 0x0a, - 0x07, 0x4f, 0x70, 0x55, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, - 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x4f, 0x70, 0x55, 0x6e, 0x61, 0x72, 0x79, - 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x2a, 0x0a, 0x04, 0x4b, - 0x69, 0x6e, 0x64, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x65, 0x67, 0x61, 0x74, 0x65, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x73, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x10, 0x02, 0x22, 0x89, 0x02, 0x0a, 0x08, 0x4f, 0x70, 0x42, 0x69, - 0x6e, 0x61, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x02, - 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x2e, 0x4b, 0x69, - 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xd8, 0x01, 0x0a, 0x04, 0x4b, 0x69, 0x6e, - 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x65, 0x73, 0x73, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, - 0x0f, 0x0a, 0x0b, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x01, - 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x65, 0x73, 0x73, 0x4f, 0x72, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, - 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x4f, 0x72, 0x45, 0x71, - 0x75, 0x61, 0x6c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x04, - 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x10, 0x05, 0x12, 0x0a, - 0x0a, 0x06, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x10, 0x06, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x75, - 0x66, 0x66, 0x69, 0x78, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, - 0x08, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x10, 0x09, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x75, - 0x62, 0x10, 0x0a, 0x12, 0x07, 0x0a, 0x03, 0x4d, 0x75, 0x6c, 0x10, 0x0b, 0x12, 0x07, 0x0a, 0x03, - 0x44, 0x69, 0x76, 0x10, 0x0c, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6e, 0x64, 0x10, 0x0d, 0x12, 0x06, - 0x0a, 0x02, 0x4f, 0x72, 0x10, 0x0e, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x73, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x0f, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x6f, - 0x6e, 0x10, 0x10, 0x22, 0x6a, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x21, 0x0a, - 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, - 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x12, 0x20, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0c, - 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x22, 0x1b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, - 0x6c, 0x6f, 0x77, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x01, 0x22, - 0xcd, 0x01, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x66, 0x61, - 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x46, 0x61, 0x63, 0x74, - 0x56, 0x32, 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, - 0x32, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x56, 0x32, 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x23, 0x0a, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x42, - 0x06, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, -} - -var ( - file_biscuit_proto_rawDescOnce sync.Once - file_biscuit_proto_rawDescData = file_biscuit_proto_rawDesc -) - -func file_biscuit_proto_rawDescGZIP() []byte { - file_biscuit_proto_rawDescOnce.Do(func() { - file_biscuit_proto_rawDescData = protoimpl.X.CompressGZIP(file_biscuit_proto_rawDescData) - }) - return file_biscuit_proto_rawDescData -} - -var file_biscuit_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_biscuit_proto_msgTypes = make([]protoimpl.MessageInfo, 17) -var file_biscuit_proto_goTypes = []interface{}{ - (PublicKey_Algorithm)(0), // 0: PublicKey.Algorithm - (OpUnary_Kind)(0), // 1: OpUnary.Kind - (OpBinary_Kind)(0), // 2: OpBinary.Kind - (Policy_Kind)(0), // 3: Policy.Kind - (*Biscuit)(nil), // 4: Biscuit - (*SignedBlock)(nil), // 5: SignedBlock - (*PublicKey)(nil), // 6: PublicKey - (*Proof)(nil), // 7: Proof - (*Block)(nil), // 8: Block - (*FactV2)(nil), // 9: FactV2 - (*RuleV2)(nil), // 10: RuleV2 - (*CheckV2)(nil), // 11: CheckV2 - (*PredicateV2)(nil), // 12: PredicateV2 - (*TermV2)(nil), // 13: TermV2 - (*TermSet)(nil), // 14: TermSet - (*ExpressionV2)(nil), // 15: ExpressionV2 - (*Op)(nil), // 16: Op - (*OpUnary)(nil), // 17: OpUnary - (*OpBinary)(nil), // 18: OpBinary - (*Policy)(nil), // 19: Policy - (*AuthorizerPolicies)(nil), // 20: AuthorizerPolicies -} -var file_biscuit_proto_depIdxs = []int32{ - 5, // 0: Biscuit.authority:type_name -> SignedBlock - 5, // 1: Biscuit.blocks:type_name -> SignedBlock - 7, // 2: Biscuit.proof:type_name -> Proof - 6, // 3: SignedBlock.nextKey:type_name -> PublicKey - 0, // 4: PublicKey.algorithm:type_name -> PublicKey.Algorithm - 9, // 5: Block.facts_v2:type_name -> FactV2 - 10, // 6: Block.rules_v2:type_name -> RuleV2 - 11, // 7: Block.checks_v2:type_name -> CheckV2 - 12, // 8: FactV2.predicate:type_name -> PredicateV2 - 12, // 9: RuleV2.head:type_name -> PredicateV2 - 12, // 10: RuleV2.body:type_name -> PredicateV2 - 15, // 11: RuleV2.expressions:type_name -> ExpressionV2 - 10, // 12: CheckV2.queries:type_name -> RuleV2 - 13, // 13: PredicateV2.terms:type_name -> TermV2 - 14, // 14: TermV2.set:type_name -> TermSet - 13, // 15: TermSet.set:type_name -> TermV2 - 16, // 16: ExpressionV2.ops:type_name -> Op - 13, // 17: Op.value:type_name -> TermV2 - 17, // 18: Op.unary:type_name -> OpUnary - 18, // 19: Op.Binary:type_name -> OpBinary - 1, // 20: OpUnary.kind:type_name -> OpUnary.Kind - 2, // 21: OpBinary.kind:type_name -> OpBinary.Kind - 10, // 22: Policy.queries:type_name -> RuleV2 - 3, // 23: Policy.kind:type_name -> Policy.Kind - 9, // 24: AuthorizerPolicies.facts:type_name -> FactV2 - 10, // 25: AuthorizerPolicies.rules:type_name -> RuleV2 - 11, // 26: AuthorizerPolicies.checks:type_name -> CheckV2 - 19, // 27: AuthorizerPolicies.policies:type_name -> Policy - 28, // [28:28] is the sub-list for method output_type - 28, // [28:28] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name -} - -func init() { file_biscuit_proto_init() } -func file_biscuit_proto_init() { - if File_biscuit_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_biscuit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Biscuit); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignedBlock); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PublicKey); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Proof); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Block); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FactV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RuleV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PredicateV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TermV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TermSet); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExpressionV2); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Op); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpUnary); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpBinary); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Policy); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_biscuit_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthorizerPolicies); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_biscuit_proto_msgTypes[3].OneofWrappers = []interface{}{ - (*Proof_NextSecret)(nil), - (*Proof_FinalSignature)(nil), - } - file_biscuit_proto_msgTypes[9].OneofWrappers = []interface{}{ - (*TermV2_Variable)(nil), - (*TermV2_Integer)(nil), - (*TermV2_String_)(nil), - (*TermV2_Date)(nil), - (*TermV2_Bytes)(nil), - (*TermV2_Bool)(nil), - (*TermV2_Set)(nil), - } - file_biscuit_proto_msgTypes[12].OneofWrappers = []interface{}{ - (*Op_Value)(nil), - (*Op_Unary)(nil), - (*Op_Binary)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_biscuit_proto_rawDesc, - NumEnums: 4, - NumMessages: 17, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_biscuit_proto_goTypes, - DependencyIndexes: file_biscuit_proto_depIdxs, - EnumInfos: file_biscuit_proto_enumTypes, - MessageInfos: file_biscuit_proto_msgTypes, - }.Build() - File_biscuit_proto = out.File - file_biscuit_proto_rawDesc = nil - file_biscuit_proto_goTypes = nil - file_biscuit_proto_depIdxs = nil -} diff --git a/pb/biscuit.proto b/pb/biscuit.proto deleted file mode 100644 index b443c32d..00000000 --- a/pb/biscuit.proto +++ /dev/null @@ -1,143 +0,0 @@ -syntax = "proto2"; - -option go_package = ".;pb"; - -message Biscuit { - optional uint32 rootKeyId = 1; - required SignedBlock authority = 2; - repeated SignedBlock blocks = 3; - required Proof proof = 4; -} - -message SignedBlock { - required bytes block = 1; - required PublicKey nextKey = 2; - required bytes signature = 3; -} - -message PublicKey { - required Algorithm algorithm = 1; - - enum Algorithm { - Ed25519 = 0; - } - - required bytes key = 2; -} - - -message Proof { - oneof Content { - bytes nextSecret = 1; - bytes finalSignature = 2; - } -} - -message Block { - repeated string symbols = 1; - optional string context = 2; - optional uint32 version = 3; - repeated FactV2 facts_v2 = 4; - repeated RuleV2 rules_v2 = 5; - repeated CheckV2 checks_v2 = 6; -} - -message FactV2 { - required PredicateV2 predicate = 1; -} - -message RuleV2 { - required PredicateV2 head = 1; - repeated PredicateV2 body = 2; - repeated ExpressionV2 expressions = 3; -} - -message CheckV2 { - repeated RuleV2 queries = 1; -} - -message PredicateV2 { - required uint64 name = 1; - repeated TermV2 terms = 2; -} - -message TermV2 { - oneof Content { - uint32 variable = 1; - int64 integer = 2; - uint64 string = 3; - uint64 date = 4; - bytes bytes = 5; - bool bool = 6; - TermSet set = 7; - } -} - -message TermSet { - repeated TermV2 set = 1; -} - -message ExpressionV2 { - repeated Op ops = 1; -} - -message Op { - oneof Content { - TermV2 value = 1; - OpUnary unary = 2; - OpBinary Binary = 3; - } -} - -message OpUnary { - enum Kind { - Negate = 0; - Parens = 1; - Length = 2; - } - - required Kind kind = 1; -} - -message OpBinary { - enum Kind { - LessThan = 0; - GreaterThan = 1; - LessOrEqual = 2; - GreaterOrEqual = 3; - Equal = 4; - Contains = 5; - Prefix = 6; - Suffix = 7; - Regex = 8; - Add = 9; - Sub = 10; - Mul = 11; - Div = 12; - And = 13; - Or = 14; - Intersection = 15; - Union = 16; - } - - required Kind kind = 1; -} - -message Policy { - enum Kind { - Allow = 0; - Deny = 1; - } - - repeated RuleV2 queries = 1; - required Kind kind = 2; -} - -message AuthorizerPolicies { - repeated string symbols = 1; - optional uint32 version = 2; - repeated FactV2 facts = 3; - repeated RuleV2 rules = 4; - repeated CheckV2 checks = 5; - repeated Policy policies = 6; -} diff --git a/pb/gen.go b/pb/gen.go deleted file mode 100644 index 7a7371b6..00000000 --- a/pb/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package pb - -//go:generate ../build/protoc/bin/protoc --plugin=../build/bin/protoc-gen-go --go_out=. --proto_path ../build/protoc/include --proto_path . biscuit.proto diff --git a/pb/schema.pb.go b/pb/schema.pb.go new file mode 100644 index 00000000..1ed7f3b2 --- /dev/null +++ b/pb/schema.pb.go @@ -0,0 +1,3273 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc (unknown) +// source: schema.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PublicKey_Algorithm int32 + +const ( + PublicKey_Ed25519 PublicKey_Algorithm = 0 + PublicKey_SECP256R1 PublicKey_Algorithm = 1 +) + +// Enum value maps for PublicKey_Algorithm. +var ( + PublicKey_Algorithm_name = map[int32]string{ + 0: "Ed25519", + 1: "SECP256R1", + } + PublicKey_Algorithm_value = map[string]int32{ + "Ed25519": 0, + "SECP256R1": 1, + } +) + +func (x PublicKey_Algorithm) Enum() *PublicKey_Algorithm { + p := new(PublicKey_Algorithm) + *p = x + return p +} + +func (x PublicKey_Algorithm) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PublicKey_Algorithm) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[0].Descriptor() +} + +func (PublicKey_Algorithm) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[0] +} + +func (x PublicKey_Algorithm) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *PublicKey_Algorithm) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = PublicKey_Algorithm(num) + return nil +} + +// Deprecated: Use PublicKey_Algorithm.Descriptor instead. +func (PublicKey_Algorithm) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{3, 0} +} + +type Scope_ScopeType int32 + +const ( + Scope_Authority Scope_ScopeType = 0 + Scope_Previous Scope_ScopeType = 1 +) + +// Enum value maps for Scope_ScopeType. +var ( + Scope_ScopeType_name = map[int32]string{ + 0: "Authority", + 1: "Previous", + } + Scope_ScopeType_value = map[string]int32{ + "Authority": 0, + "Previous": 1, + } +) + +func (x Scope_ScopeType) Enum() *Scope_ScopeType { + p := new(Scope_ScopeType) + *p = x + return p +} + +func (x Scope_ScopeType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Scope_ScopeType) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[1].Descriptor() +} + +func (Scope_ScopeType) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[1] +} + +func (x Scope_ScopeType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Scope_ScopeType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Scope_ScopeType(num) + return nil +} + +// Deprecated: Use Scope_ScopeType.Descriptor instead. +func (Scope_ScopeType) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{6, 0} +} + +type Check_Kind int32 + +const ( + Check_One Check_Kind = 0 + Check_All Check_Kind = 1 + Check_Reject Check_Kind = 2 +) + +// Enum value maps for Check_Kind. +var ( + Check_Kind_name = map[int32]string{ + 0: "One", + 1: "All", + 2: "Reject", + } + Check_Kind_value = map[string]int32{ + "One": 0, + "All": 1, + "Reject": 2, + } +) + +func (x Check_Kind) Enum() *Check_Kind { + p := new(Check_Kind) + *p = x + return p +} + +func (x Check_Kind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Check_Kind) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[2].Descriptor() +} + +func (Check_Kind) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[2] +} + +func (x Check_Kind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Check_Kind) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Check_Kind(num) + return nil +} + +// Deprecated: Use Check_Kind.Descriptor instead. +func (Check_Kind) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{9, 0} +} + +type OpUnary_Kind int32 + +const ( + OpUnary_Negate OpUnary_Kind = 0 + OpUnary_Parens OpUnary_Kind = 1 + OpUnary_Length OpUnary_Kind = 2 + OpUnary_TypeOf OpUnary_Kind = 3 + OpUnary_Ffi OpUnary_Kind = 4 +) + +// Enum value maps for OpUnary_Kind. +var ( + OpUnary_Kind_name = map[int32]string{ + 0: "Negate", + 1: "Parens", + 2: "Length", + 3: "TypeOf", + 4: "Ffi", + } + OpUnary_Kind_value = map[string]int32{ + "Negate": 0, + "Parens": 1, + "Length": 2, + "TypeOf": 3, + "Ffi": 4, + } +) + +func (x OpUnary_Kind) Enum() *OpUnary_Kind { + p := new(OpUnary_Kind) + *p = x + return p +} + +func (x OpUnary_Kind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OpUnary_Kind) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[3].Descriptor() +} + +func (OpUnary_Kind) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[3] +} + +func (x OpUnary_Kind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *OpUnary_Kind) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = OpUnary_Kind(num) + return nil +} + +// Deprecated: Use OpUnary_Kind.Descriptor instead. +func (OpUnary_Kind) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{19, 0} +} + +type OpBinary_Kind int32 + +const ( + OpBinary_LessThan OpBinary_Kind = 0 + OpBinary_GreaterThan OpBinary_Kind = 1 + OpBinary_LessOrEqual OpBinary_Kind = 2 + OpBinary_GreaterOrEqual OpBinary_Kind = 3 + OpBinary_Equal OpBinary_Kind = 4 + OpBinary_Contains OpBinary_Kind = 5 + OpBinary_Prefix OpBinary_Kind = 6 + OpBinary_Suffix OpBinary_Kind = 7 + OpBinary_Regex OpBinary_Kind = 8 + OpBinary_Add OpBinary_Kind = 9 + OpBinary_Sub OpBinary_Kind = 10 + OpBinary_Mul OpBinary_Kind = 11 + OpBinary_Div OpBinary_Kind = 12 + OpBinary_And OpBinary_Kind = 13 + OpBinary_Or OpBinary_Kind = 14 + OpBinary_Intersection OpBinary_Kind = 15 + OpBinary_Union OpBinary_Kind = 16 + OpBinary_BitwiseAnd OpBinary_Kind = 17 + OpBinary_BitwiseOr OpBinary_Kind = 18 + OpBinary_BitwiseXor OpBinary_Kind = 19 + OpBinary_NotEqual OpBinary_Kind = 20 + OpBinary_HeterogeneousEqual OpBinary_Kind = 21 + OpBinary_HeterogeneousNotEqual OpBinary_Kind = 22 + OpBinary_LazyAnd OpBinary_Kind = 23 + OpBinary_LazyOr OpBinary_Kind = 24 + OpBinary_All OpBinary_Kind = 25 + OpBinary_Any OpBinary_Kind = 26 + OpBinary_Get OpBinary_Kind = 27 + OpBinary_Ffi OpBinary_Kind = 28 + OpBinary_TryOr OpBinary_Kind = 29 +) + +// Enum value maps for OpBinary_Kind. +var ( + OpBinary_Kind_name = map[int32]string{ + 0: "LessThan", + 1: "GreaterThan", + 2: "LessOrEqual", + 3: "GreaterOrEqual", + 4: "Equal", + 5: "Contains", + 6: "Prefix", + 7: "Suffix", + 8: "Regex", + 9: "Add", + 10: "Sub", + 11: "Mul", + 12: "Div", + 13: "And", + 14: "Or", + 15: "Intersection", + 16: "Union", + 17: "BitwiseAnd", + 18: "BitwiseOr", + 19: "BitwiseXor", + 20: "NotEqual", + 21: "HeterogeneousEqual", + 22: "HeterogeneousNotEqual", + 23: "LazyAnd", + 24: "LazyOr", + 25: "All", + 26: "Any", + 27: "Get", + 28: "Ffi", + 29: "TryOr", + } + OpBinary_Kind_value = map[string]int32{ + "LessThan": 0, + "GreaterThan": 1, + "LessOrEqual": 2, + "GreaterOrEqual": 3, + "Equal": 4, + "Contains": 5, + "Prefix": 6, + "Suffix": 7, + "Regex": 8, + "Add": 9, + "Sub": 10, + "Mul": 11, + "Div": 12, + "And": 13, + "Or": 14, + "Intersection": 15, + "Union": 16, + "BitwiseAnd": 17, + "BitwiseOr": 18, + "BitwiseXor": 19, + "NotEqual": 20, + "HeterogeneousEqual": 21, + "HeterogeneousNotEqual": 22, + "LazyAnd": 23, + "LazyOr": 24, + "All": 25, + "Any": 26, + "Get": 27, + "Ffi": 28, + "TryOr": 29, + } +) + +func (x OpBinary_Kind) Enum() *OpBinary_Kind { + p := new(OpBinary_Kind) + *p = x + return p +} + +func (x OpBinary_Kind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OpBinary_Kind) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[4].Descriptor() +} + +func (OpBinary_Kind) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[4] +} + +func (x OpBinary_Kind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *OpBinary_Kind) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = OpBinary_Kind(num) + return nil +} + +// Deprecated: Use OpBinary_Kind.Descriptor instead. +func (OpBinary_Kind) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{20, 0} +} + +type Policy_Kind int32 + +const ( + Policy_Allow Policy_Kind = 0 + Policy_Deny Policy_Kind = 1 +) + +// Enum value maps for Policy_Kind. +var ( + Policy_Kind_name = map[int32]string{ + 0: "Allow", + 1: "Deny", + } + Policy_Kind_value = map[string]int32{ + "Allow": 0, + "Deny": 1, + } +) + +func (x Policy_Kind) Enum() *Policy_Kind { + p := new(Policy_Kind) + *p = x + return p +} + +func (x Policy_Kind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Policy_Kind) Descriptor() protoreflect.EnumDescriptor { + return file_schema_proto_enumTypes[5].Descriptor() +} + +func (Policy_Kind) Type() protoreflect.EnumType { + return &file_schema_proto_enumTypes[5] +} + +func (x Policy_Kind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Policy_Kind) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Policy_Kind(num) + return nil +} + +// Deprecated: Use Policy_Kind.Descriptor instead. +func (Policy_Kind) EnumDescriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{22, 0} +} + +type Biscuit struct { + state protoimpl.MessageState `protogen:"open.v1"` + RootKeyId *uint32 `protobuf:"varint,1,opt,name=rootKeyId" json:"rootKeyId,omitempty"` + Authority *SignedBlock `protobuf:"bytes,2,req,name=authority" json:"authority,omitempty"` + Blocks []*SignedBlock `protobuf:"bytes,3,rep,name=blocks" json:"blocks,omitempty"` + Proof *Proof `protobuf:"bytes,4,req,name=proof" json:"proof,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Biscuit) Reset() { + *x = Biscuit{} + mi := &file_schema_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Biscuit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Biscuit) ProtoMessage() {} + +func (x *Biscuit) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Biscuit.ProtoReflect.Descriptor instead. +func (*Biscuit) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{0} +} + +func (x *Biscuit) GetRootKeyId() uint32 { + if x != nil && x.RootKeyId != nil { + return *x.RootKeyId + } + return 0 +} + +func (x *Biscuit) GetAuthority() *SignedBlock { + if x != nil { + return x.Authority + } + return nil +} + +func (x *Biscuit) GetBlocks() []*SignedBlock { + if x != nil { + return x.Blocks + } + return nil +} + +func (x *Biscuit) GetProof() *Proof { + if x != nil { + return x.Proof + } + return nil +} + +type SignedBlock struct { + state protoimpl.MessageState `protogen:"open.v1"` + Block []byte `protobuf:"bytes,1,req,name=block" json:"block,omitempty"` + NextKey *PublicKey `protobuf:"bytes,2,req,name=nextKey" json:"nextKey,omitempty"` + Signature []byte `protobuf:"bytes,3,req,name=signature" json:"signature,omitempty"` + ExternalSignature *ExternalSignature `protobuf:"bytes,4,opt,name=externalSignature" json:"externalSignature,omitempty"` + Version *uint32 `protobuf:"varint,5,opt,name=version" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignedBlock) Reset() { + *x = SignedBlock{} + mi := &file_schema_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignedBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedBlock) ProtoMessage() {} + +func (x *SignedBlock) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedBlock.ProtoReflect.Descriptor instead. +func (*SignedBlock) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{1} +} + +func (x *SignedBlock) GetBlock() []byte { + if x != nil { + return x.Block + } + return nil +} + +func (x *SignedBlock) GetNextKey() *PublicKey { + if x != nil { + return x.NextKey + } + return nil +} + +func (x *SignedBlock) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *SignedBlock) GetExternalSignature() *ExternalSignature { + if x != nil { + return x.ExternalSignature + } + return nil +} + +func (x *SignedBlock) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +type ExternalSignature struct { + state protoimpl.MessageState `protogen:"open.v1"` + Signature []byte `protobuf:"bytes,1,req,name=signature" json:"signature,omitempty"` + PublicKey *PublicKey `protobuf:"bytes,2,req,name=publicKey" json:"publicKey,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExternalSignature) Reset() { + *x = ExternalSignature{} + mi := &file_schema_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExternalSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExternalSignature) ProtoMessage() {} + +func (x *ExternalSignature) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExternalSignature.ProtoReflect.Descriptor instead. +func (*ExternalSignature) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{2} +} + +func (x *ExternalSignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *ExternalSignature) GetPublicKey() *PublicKey { + if x != nil { + return x.PublicKey + } + return nil +} + +type PublicKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + Algorithm *PublicKey_Algorithm `protobuf:"varint,1,req,name=algorithm,enum=biscuit.format.schema.PublicKey_Algorithm" json:"algorithm,omitempty"` + Key []byte `protobuf:"bytes,2,req,name=key" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublicKey) Reset() { + *x = PublicKey{} + mi := &file_schema_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublicKey) ProtoMessage() {} + +func (x *PublicKey) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublicKey.ProtoReflect.Descriptor instead. +func (*PublicKey) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{3} +} + +func (x *PublicKey) GetAlgorithm() PublicKey_Algorithm { + if x != nil && x.Algorithm != nil { + return *x.Algorithm + } + return PublicKey_Ed25519 +} + +func (x *PublicKey) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +type Proof struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *Proof_NextSecret + // *Proof_FinalSignature + Content isProof_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Proof) Reset() { + *x = Proof{} + mi := &file_schema_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Proof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Proof) ProtoMessage() {} + +func (x *Proof) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Proof.ProtoReflect.Descriptor instead. +func (*Proof) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{4} +} + +func (x *Proof) GetContent() isProof_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Proof) GetNextSecret() []byte { + if x != nil { + if x, ok := x.Content.(*Proof_NextSecret); ok { + return x.NextSecret + } + } + return nil +} + +func (x *Proof) GetFinalSignature() []byte { + if x != nil { + if x, ok := x.Content.(*Proof_FinalSignature); ok { + return x.FinalSignature + } + } + return nil +} + +type isProof_Content interface { + isProof_Content() +} + +type Proof_NextSecret struct { + NextSecret []byte `protobuf:"bytes,1,opt,name=nextSecret,oneof"` +} + +type Proof_FinalSignature struct { + FinalSignature []byte `protobuf:"bytes,2,opt,name=finalSignature,oneof"` +} + +func (*Proof_NextSecret) isProof_Content() {} + +func (*Proof_FinalSignature) isProof_Content() {} + +type Block struct { + state protoimpl.MessageState `protogen:"open.v1"` + Symbols []string `protobuf:"bytes,1,rep,name=symbols" json:"symbols,omitempty"` + Context *string `protobuf:"bytes,2,opt,name=context" json:"context,omitempty"` + Version *uint32 `protobuf:"varint,3,opt,name=version" json:"version,omitempty"` + Facts []*Fact `protobuf:"bytes,4,rep,name=facts" json:"facts,omitempty"` + Rules []*Rule `protobuf:"bytes,5,rep,name=rules" json:"rules,omitempty"` + Checks []*Check `protobuf:"bytes,6,rep,name=checks" json:"checks,omitempty"` + Scope []*Scope `protobuf:"bytes,7,rep,name=scope" json:"scope,omitempty"` + PublicKeys []*PublicKey `protobuf:"bytes,8,rep,name=publicKeys" json:"publicKeys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Block) Reset() { + *x = Block{} + mi := &file_schema_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Block) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Block) ProtoMessage() {} + +func (x *Block) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Block.ProtoReflect.Descriptor instead. +func (*Block) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{5} +} + +func (x *Block) GetSymbols() []string { + if x != nil { + return x.Symbols + } + return nil +} + +func (x *Block) GetContext() string { + if x != nil && x.Context != nil { + return *x.Context + } + return "" +} + +func (x *Block) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +func (x *Block) GetFacts() []*Fact { + if x != nil { + return x.Facts + } + return nil +} + +func (x *Block) GetRules() []*Rule { + if x != nil { + return x.Rules + } + return nil +} + +func (x *Block) GetChecks() []*Check { + if x != nil { + return x.Checks + } + return nil +} + +func (x *Block) GetScope() []*Scope { + if x != nil { + return x.Scope + } + return nil +} + +func (x *Block) GetPublicKeys() []*PublicKey { + if x != nil { + return x.PublicKeys + } + return nil +} + +type Scope struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *Scope_ScopeType_ + // *Scope_PublicKey + Content isScope_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Scope) Reset() { + *x = Scope{} + mi := &file_schema_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Scope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Scope) ProtoMessage() {} + +func (x *Scope) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Scope.ProtoReflect.Descriptor instead. +func (*Scope) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{6} +} + +func (x *Scope) GetContent() isScope_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Scope) GetScopeType() Scope_ScopeType { + if x != nil { + if x, ok := x.Content.(*Scope_ScopeType_); ok { + return x.ScopeType + } + } + return Scope_Authority +} + +func (x *Scope) GetPublicKey() int64 { + if x != nil { + if x, ok := x.Content.(*Scope_PublicKey); ok { + return x.PublicKey + } + } + return 0 +} + +type isScope_Content interface { + isScope_Content() +} + +type Scope_ScopeType_ struct { + ScopeType Scope_ScopeType `protobuf:"varint,1,opt,name=scopeType,enum=biscuit.format.schema.Scope_ScopeType,oneof"` +} + +type Scope_PublicKey struct { + PublicKey int64 `protobuf:"varint,2,opt,name=publicKey,oneof"` +} + +func (*Scope_ScopeType_) isScope_Content() {} + +func (*Scope_PublicKey) isScope_Content() {} + +type Fact struct { + state protoimpl.MessageState `protogen:"open.v1"` + Predicate *Predicate `protobuf:"bytes,1,req,name=predicate" json:"predicate,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Fact) Reset() { + *x = Fact{} + mi := &file_schema_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Fact) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Fact) ProtoMessage() {} + +func (x *Fact) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Fact.ProtoReflect.Descriptor instead. +func (*Fact) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{7} +} + +func (x *Fact) GetPredicate() *Predicate { + if x != nil { + return x.Predicate + } + return nil +} + +type Rule struct { + state protoimpl.MessageState `protogen:"open.v1"` + Head *Predicate `protobuf:"bytes,1,req,name=head" json:"head,omitempty"` + Body []*Predicate `protobuf:"bytes,2,rep,name=body" json:"body,omitempty"` + Expressions []*Expression `protobuf:"bytes,3,rep,name=expressions" json:"expressions,omitempty"` + Scope []*Scope `protobuf:"bytes,4,rep,name=scope" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Rule) Reset() { + *x = Rule{} + mi := &file_schema_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Rule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Rule) ProtoMessage() {} + +func (x *Rule) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Rule.ProtoReflect.Descriptor instead. +func (*Rule) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{8} +} + +func (x *Rule) GetHead() *Predicate { + if x != nil { + return x.Head + } + return nil +} + +func (x *Rule) GetBody() []*Predicate { + if x != nil { + return x.Body + } + return nil +} + +func (x *Rule) GetExpressions() []*Expression { + if x != nil { + return x.Expressions + } + return nil +} + +func (x *Rule) GetScope() []*Scope { + if x != nil { + return x.Scope + } + return nil +} + +type Check struct { + state protoimpl.MessageState `protogen:"open.v1"` + Queries []*Rule `protobuf:"bytes,1,rep,name=queries" json:"queries,omitempty"` + Kind *Check_Kind `protobuf:"varint,2,opt,name=kind,enum=biscuit.format.schema.Check_Kind" json:"kind,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Check) Reset() { + *x = Check{} + mi := &file_schema_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Check) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Check) ProtoMessage() {} + +func (x *Check) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Check.ProtoReflect.Descriptor instead. +func (*Check) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{9} +} + +func (x *Check) GetQueries() []*Rule { + if x != nil { + return x.Queries + } + return nil +} + +func (x *Check) GetKind() Check_Kind { + if x != nil && x.Kind != nil { + return *x.Kind + } + return Check_One +} + +type Predicate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name *uint64 `protobuf:"varint,1,req,name=name" json:"name,omitempty"` + Terms []*Term `protobuf:"bytes,2,rep,name=terms" json:"terms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Predicate) Reset() { + *x = Predicate{} + mi := &file_schema_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Predicate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Predicate) ProtoMessage() {} + +func (x *Predicate) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Predicate.ProtoReflect.Descriptor instead. +func (*Predicate) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{10} +} + +func (x *Predicate) GetName() uint64 { + if x != nil && x.Name != nil { + return *x.Name + } + return 0 +} + +func (x *Predicate) GetTerms() []*Term { + if x != nil { + return x.Terms + } + return nil +} + +type Term struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *Term_Variable + // *Term_Integer + // *Term_String_ + // *Term_Date + // *Term_Bytes + // *Term_Bool + // *Term_Set + // *Term_Null + // *Term_Array + // *Term_Map + Content isTerm_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Term) Reset() { + *x = Term{} + mi := &file_schema_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Term) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Term) ProtoMessage() {} + +func (x *Term) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Term.ProtoReflect.Descriptor instead. +func (*Term) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{11} +} + +func (x *Term) GetContent() isTerm_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Term) GetVariable() uint32 { + if x != nil { + if x, ok := x.Content.(*Term_Variable); ok { + return x.Variable + } + } + return 0 +} + +func (x *Term) GetInteger() int64 { + if x != nil { + if x, ok := x.Content.(*Term_Integer); ok { + return x.Integer + } + } + return 0 +} + +func (x *Term) GetString_() uint64 { + if x != nil { + if x, ok := x.Content.(*Term_String_); ok { + return x.String_ + } + } + return 0 +} + +func (x *Term) GetDate() uint64 { + if x != nil { + if x, ok := x.Content.(*Term_Date); ok { + return x.Date + } + } + return 0 +} + +func (x *Term) GetBytes() []byte { + if x != nil { + if x, ok := x.Content.(*Term_Bytes); ok { + return x.Bytes + } + } + return nil +} + +func (x *Term) GetBool() bool { + if x != nil { + if x, ok := x.Content.(*Term_Bool); ok { + return x.Bool + } + } + return false +} + +func (x *Term) GetSet() *TermSet { + if x != nil { + if x, ok := x.Content.(*Term_Set); ok { + return x.Set + } + } + return nil +} + +func (x *Term) GetNull() *Empty { + if x != nil { + if x, ok := x.Content.(*Term_Null); ok { + return x.Null + } + } + return nil +} + +func (x *Term) GetArray() *Array { + if x != nil { + if x, ok := x.Content.(*Term_Array); ok { + return x.Array + } + } + return nil +} + +func (x *Term) GetMap() *Map { + if x != nil { + if x, ok := x.Content.(*Term_Map); ok { + return x.Map + } + } + return nil +} + +type isTerm_Content interface { + isTerm_Content() +} + +type Term_Variable struct { + Variable uint32 `protobuf:"varint,1,opt,name=variable,oneof"` +} + +type Term_Integer struct { + Integer int64 `protobuf:"varint,2,opt,name=integer,oneof"` +} + +type Term_String_ struct { + String_ uint64 `protobuf:"varint,3,opt,name=string,oneof"` +} + +type Term_Date struct { + Date uint64 `protobuf:"varint,4,opt,name=date,oneof"` +} + +type Term_Bytes struct { + Bytes []byte `protobuf:"bytes,5,opt,name=bytes,oneof"` +} + +type Term_Bool struct { + Bool bool `protobuf:"varint,6,opt,name=bool,oneof"` +} + +type Term_Set struct { + Set *TermSet `protobuf:"bytes,7,opt,name=set,oneof"` +} + +type Term_Null struct { + Null *Empty `protobuf:"bytes,8,opt,name=null,oneof"` +} + +type Term_Array struct { + Array *Array `protobuf:"bytes,9,opt,name=array,oneof"` +} + +type Term_Map struct { + Map *Map `protobuf:"bytes,10,opt,name=map,oneof"` +} + +func (*Term_Variable) isTerm_Content() {} + +func (*Term_Integer) isTerm_Content() {} + +func (*Term_String_) isTerm_Content() {} + +func (*Term_Date) isTerm_Content() {} + +func (*Term_Bytes) isTerm_Content() {} + +func (*Term_Bool) isTerm_Content() {} + +func (*Term_Set) isTerm_Content() {} + +func (*Term_Null) isTerm_Content() {} + +func (*Term_Array) isTerm_Content() {} + +func (*Term_Map) isTerm_Content() {} + +type TermSet struct { + state protoimpl.MessageState `protogen:"open.v1"` + Set []*Term `protobuf:"bytes,1,rep,name=set" json:"set,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TermSet) Reset() { + *x = TermSet{} + mi := &file_schema_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TermSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TermSet) ProtoMessage() {} + +func (x *TermSet) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TermSet.ProtoReflect.Descriptor instead. +func (*TermSet) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{12} +} + +func (x *TermSet) GetSet() []*Term { + if x != nil { + return x.Set + } + return nil +} + +type Array struct { + state protoimpl.MessageState `protogen:"open.v1"` + Array []*Term `protobuf:"bytes,1,rep,name=array" json:"array,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Array) Reset() { + *x = Array{} + mi := &file_schema_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Array) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Array) ProtoMessage() {} + +func (x *Array) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Array.ProtoReflect.Descriptor instead. +func (*Array) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{13} +} + +func (x *Array) GetArray() []*Term { + if x != nil { + return x.Array + } + return nil +} + +type Map struct { + state protoimpl.MessageState `protogen:"open.v1"` + Entries []*MapEntry `protobuf:"bytes,1,rep,name=entries" json:"entries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Map) Reset() { + *x = Map{} + mi := &file_schema_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Map) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Map) ProtoMessage() {} + +func (x *Map) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Map.ProtoReflect.Descriptor instead. +func (*Map) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{14} +} + +func (x *Map) GetEntries() []*MapEntry { + if x != nil { + return x.Entries + } + return nil +} + +type MapEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + Key *MapKey `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` + Value *Term `protobuf:"bytes,2,req,name=value" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapEntry) Reset() { + *x = MapEntry{} + mi := &file_schema_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapEntry) ProtoMessage() {} + +func (x *MapEntry) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapEntry.ProtoReflect.Descriptor instead. +func (*MapEntry) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{15} +} + +func (x *MapEntry) GetKey() *MapKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *MapEntry) GetValue() *Term { + if x != nil { + return x.Value + } + return nil +} + +type MapKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *MapKey_Integer + // *MapKey_String_ + Content isMapKey_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapKey) Reset() { + *x = MapKey{} + mi := &file_schema_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapKey) ProtoMessage() {} + +func (x *MapKey) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapKey.ProtoReflect.Descriptor instead. +func (*MapKey) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{16} +} + +func (x *MapKey) GetContent() isMapKey_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *MapKey) GetInteger() int64 { + if x != nil { + if x, ok := x.Content.(*MapKey_Integer); ok { + return x.Integer + } + } + return 0 +} + +func (x *MapKey) GetString_() uint64 { + if x != nil { + if x, ok := x.Content.(*MapKey_String_); ok { + return x.String_ + } + } + return 0 +} + +type isMapKey_Content interface { + isMapKey_Content() +} + +type MapKey_Integer struct { + Integer int64 `protobuf:"varint,1,opt,name=integer,oneof"` +} + +type MapKey_String_ struct { + String_ uint64 `protobuf:"varint,2,opt,name=string,oneof"` +} + +func (*MapKey_Integer) isMapKey_Content() {} + +func (*MapKey_String_) isMapKey_Content() {} + +type Expression struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ops []*Op `protobuf:"bytes,1,rep,name=ops" json:"ops,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Expression) Reset() { + *x = Expression{} + mi := &file_schema_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Expression) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Expression) ProtoMessage() {} + +func (x *Expression) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Expression.ProtoReflect.Descriptor instead. +func (*Expression) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{17} +} + +func (x *Expression) GetOps() []*Op { + if x != nil { + return x.Ops + } + return nil +} + +type Op struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *Op_Value + // *Op_Unary + // *Op_Binary + // *Op_Closure + Content isOp_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Op) Reset() { + *x = Op{} + mi := &file_schema_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Op) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Op) ProtoMessage() {} + +func (x *Op) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Op.ProtoReflect.Descriptor instead. +func (*Op) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{18} +} + +func (x *Op) GetContent() isOp_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Op) GetValue() *Term { + if x != nil { + if x, ok := x.Content.(*Op_Value); ok { + return x.Value + } + } + return nil +} + +func (x *Op) GetUnary() *OpUnary { + if x != nil { + if x, ok := x.Content.(*Op_Unary); ok { + return x.Unary + } + } + return nil +} + +func (x *Op) GetBinary() *OpBinary { + if x != nil { + if x, ok := x.Content.(*Op_Binary); ok { + return x.Binary + } + } + return nil +} + +func (x *Op) GetClosure() *OpClosure { + if x != nil { + if x, ok := x.Content.(*Op_Closure); ok { + return x.Closure + } + } + return nil +} + +type isOp_Content interface { + isOp_Content() +} + +type Op_Value struct { + Value *Term `protobuf:"bytes,1,opt,name=value,oneof"` +} + +type Op_Unary struct { + Unary *OpUnary `protobuf:"bytes,2,opt,name=unary,oneof"` +} + +type Op_Binary struct { + Binary *OpBinary `protobuf:"bytes,3,opt,name=Binary,oneof"` +} + +type Op_Closure struct { + Closure *OpClosure `protobuf:"bytes,4,opt,name=closure,oneof"` +} + +func (*Op_Value) isOp_Content() {} + +func (*Op_Unary) isOp_Content() {} + +func (*Op_Binary) isOp_Content() {} + +func (*Op_Closure) isOp_Content() {} + +type OpUnary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind *OpUnary_Kind `protobuf:"varint,1,req,name=kind,enum=biscuit.format.schema.OpUnary_Kind" json:"kind,omitempty"` + FfiName *uint64 `protobuf:"varint,2,opt,name=ffiName" json:"ffiName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpUnary) Reset() { + *x = OpUnary{} + mi := &file_schema_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpUnary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpUnary) ProtoMessage() {} + +func (x *OpUnary) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpUnary.ProtoReflect.Descriptor instead. +func (*OpUnary) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{19} +} + +func (x *OpUnary) GetKind() OpUnary_Kind { + if x != nil && x.Kind != nil { + return *x.Kind + } + return OpUnary_Negate +} + +func (x *OpUnary) GetFfiName() uint64 { + if x != nil && x.FfiName != nil { + return *x.FfiName + } + return 0 +} + +type OpBinary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind *OpBinary_Kind `protobuf:"varint,1,req,name=kind,enum=biscuit.format.schema.OpBinary_Kind" json:"kind,omitempty"` + FfiName *uint64 `protobuf:"varint,2,opt,name=ffiName" json:"ffiName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpBinary) Reset() { + *x = OpBinary{} + mi := &file_schema_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpBinary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpBinary) ProtoMessage() {} + +func (x *OpBinary) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpBinary.ProtoReflect.Descriptor instead. +func (*OpBinary) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{20} +} + +func (x *OpBinary) GetKind() OpBinary_Kind { + if x != nil && x.Kind != nil { + return *x.Kind + } + return OpBinary_LessThan +} + +func (x *OpBinary) GetFfiName() uint64 { + if x != nil && x.FfiName != nil { + return *x.FfiName + } + return 0 +} + +type OpClosure struct { + state protoimpl.MessageState `protogen:"open.v1"` + Params []uint32 `protobuf:"varint,1,rep,name=params" json:"params,omitempty"` + Ops []*Op `protobuf:"bytes,2,rep,name=ops" json:"ops,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpClosure) Reset() { + *x = OpClosure{} + mi := &file_schema_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpClosure) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpClosure) ProtoMessage() {} + +func (x *OpClosure) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpClosure.ProtoReflect.Descriptor instead. +func (*OpClosure) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{21} +} + +func (x *OpClosure) GetParams() []uint32 { + if x != nil { + return x.Params + } + return nil +} + +func (x *OpClosure) GetOps() []*Op { + if x != nil { + return x.Ops + } + return nil +} + +type Policy struct { + state protoimpl.MessageState `protogen:"open.v1"` + Queries []*Rule `protobuf:"bytes,1,rep,name=queries" json:"queries,omitempty"` + Kind *Policy_Kind `protobuf:"varint,2,req,name=kind,enum=biscuit.format.schema.Policy_Kind" json:"kind,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Policy) Reset() { + *x = Policy{} + mi := &file_schema_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Policy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Policy) ProtoMessage() {} + +func (x *Policy) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Policy.ProtoReflect.Descriptor instead. +func (*Policy) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{22} +} + +func (x *Policy) GetQueries() []*Rule { + if x != nil { + return x.Queries + } + return nil +} + +func (x *Policy) GetKind() Policy_Kind { + if x != nil && x.Kind != nil { + return *x.Kind + } + return Policy_Allow +} + +type AuthorizerPolicies struct { + state protoimpl.MessageState `protogen:"open.v1"` + Symbols []string `protobuf:"bytes,1,rep,name=symbols" json:"symbols,omitempty"` + Version *uint32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` + Facts []*Fact `protobuf:"bytes,3,rep,name=facts" json:"facts,omitempty"` + Rules []*Rule `protobuf:"bytes,4,rep,name=rules" json:"rules,omitempty"` + Checks []*Check `protobuf:"bytes,5,rep,name=checks" json:"checks,omitempty"` + Policies []*Policy `protobuf:"bytes,6,rep,name=policies" json:"policies,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizerPolicies) Reset() { + *x = AuthorizerPolicies{} + mi := &file_schema_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizerPolicies) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizerPolicies) ProtoMessage() {} + +func (x *AuthorizerPolicies) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizerPolicies.ProtoReflect.Descriptor instead. +func (*AuthorizerPolicies) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{23} +} + +func (x *AuthorizerPolicies) GetSymbols() []string { + if x != nil { + return x.Symbols + } + return nil +} + +func (x *AuthorizerPolicies) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +func (x *AuthorizerPolicies) GetFacts() []*Fact { + if x != nil { + return x.Facts + } + return nil +} + +func (x *AuthorizerPolicies) GetRules() []*Rule { + if x != nil { + return x.Rules + } + return nil +} + +func (x *AuthorizerPolicies) GetChecks() []*Check { + if x != nil { + return x.Checks + } + return nil +} + +func (x *AuthorizerPolicies) GetPolicies() []*Policy { + if x != nil { + return x.Policies + } + return nil +} + +type ThirdPartyBlockRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + LegacyPreviousKey *PublicKey `protobuf:"bytes,1,opt,name=legacyPreviousKey" json:"legacyPreviousKey,omitempty"` + LegacyPublicKeys []*PublicKey `protobuf:"bytes,2,rep,name=legacyPublicKeys" json:"legacyPublicKeys,omitempty"` + PreviousSignature []byte `protobuf:"bytes,3,req,name=previousSignature" json:"previousSignature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThirdPartyBlockRequest) Reset() { + *x = ThirdPartyBlockRequest{} + mi := &file_schema_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThirdPartyBlockRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThirdPartyBlockRequest) ProtoMessage() {} + +func (x *ThirdPartyBlockRequest) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThirdPartyBlockRequest.ProtoReflect.Descriptor instead. +func (*ThirdPartyBlockRequest) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{24} +} + +func (x *ThirdPartyBlockRequest) GetLegacyPreviousKey() *PublicKey { + if x != nil { + return x.LegacyPreviousKey + } + return nil +} + +func (x *ThirdPartyBlockRequest) GetLegacyPublicKeys() []*PublicKey { + if x != nil { + return x.LegacyPublicKeys + } + return nil +} + +func (x *ThirdPartyBlockRequest) GetPreviousSignature() []byte { + if x != nil { + return x.PreviousSignature + } + return nil +} + +type ThirdPartyBlockContents struct { + state protoimpl.MessageState `protogen:"open.v1"` + Payload []byte `protobuf:"bytes,1,req,name=payload" json:"payload,omitempty"` + ExternalSignature *ExternalSignature `protobuf:"bytes,2,req,name=externalSignature" json:"externalSignature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThirdPartyBlockContents) Reset() { + *x = ThirdPartyBlockContents{} + mi := &file_schema_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThirdPartyBlockContents) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThirdPartyBlockContents) ProtoMessage() {} + +func (x *ThirdPartyBlockContents) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThirdPartyBlockContents.ProtoReflect.Descriptor instead. +func (*ThirdPartyBlockContents) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{25} +} + +func (x *ThirdPartyBlockContents) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ThirdPartyBlockContents) GetExternalSignature() *ExternalSignature { + if x != nil { + return x.ExternalSignature + } + return nil +} + +type AuthorizerSnapshot struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limits *RunLimits `protobuf:"bytes,1,req,name=limits" json:"limits,omitempty"` + ExecutionTime *uint64 `protobuf:"varint,2,req,name=executionTime" json:"executionTime,omitempty"` + World *AuthorizerWorld `protobuf:"bytes,3,req,name=world" json:"world,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizerSnapshot) Reset() { + *x = AuthorizerSnapshot{} + mi := &file_schema_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizerSnapshot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizerSnapshot) ProtoMessage() {} + +func (x *AuthorizerSnapshot) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizerSnapshot.ProtoReflect.Descriptor instead. +func (*AuthorizerSnapshot) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{26} +} + +func (x *AuthorizerSnapshot) GetLimits() *RunLimits { + if x != nil { + return x.Limits + } + return nil +} + +func (x *AuthorizerSnapshot) GetExecutionTime() uint64 { + if x != nil && x.ExecutionTime != nil { + return *x.ExecutionTime + } + return 0 +} + +func (x *AuthorizerSnapshot) GetWorld() *AuthorizerWorld { + if x != nil { + return x.World + } + return nil +} + +type RunLimits struct { + state protoimpl.MessageState `protogen:"open.v1"` + MaxFacts *uint64 `protobuf:"varint,1,req,name=maxFacts" json:"maxFacts,omitempty"` + MaxIterations *uint64 `protobuf:"varint,2,req,name=maxIterations" json:"maxIterations,omitempty"` + MaxTime *uint64 `protobuf:"varint,3,req,name=maxTime" json:"maxTime,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RunLimits) Reset() { + *x = RunLimits{} + mi := &file_schema_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunLimits) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunLimits) ProtoMessage() {} + +func (x *RunLimits) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunLimits.ProtoReflect.Descriptor instead. +func (*RunLimits) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{27} +} + +func (x *RunLimits) GetMaxFacts() uint64 { + if x != nil && x.MaxFacts != nil { + return *x.MaxFacts + } + return 0 +} + +func (x *RunLimits) GetMaxIterations() uint64 { + if x != nil && x.MaxIterations != nil { + return *x.MaxIterations + } + return 0 +} + +func (x *RunLimits) GetMaxTime() uint64 { + if x != nil && x.MaxTime != nil { + return *x.MaxTime + } + return 0 +} + +type AuthorizerWorld struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version *uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"` + Symbols []string `protobuf:"bytes,2,rep,name=symbols" json:"symbols,omitempty"` + PublicKeys []*PublicKey `protobuf:"bytes,3,rep,name=publicKeys" json:"publicKeys,omitempty"` + Blocks []*SnapshotBlock `protobuf:"bytes,4,rep,name=blocks" json:"blocks,omitempty"` + AuthorizerBlock *SnapshotBlock `protobuf:"bytes,5,req,name=authorizerBlock" json:"authorizerBlock,omitempty"` + AuthorizerPolicies []*Policy `protobuf:"bytes,6,rep,name=authorizerPolicies" json:"authorizerPolicies,omitempty"` + GeneratedFacts []*GeneratedFacts `protobuf:"bytes,7,rep,name=generatedFacts" json:"generatedFacts,omitempty"` + Iterations *uint64 `protobuf:"varint,8,req,name=iterations" json:"iterations,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizerWorld) Reset() { + *x = AuthorizerWorld{} + mi := &file_schema_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizerWorld) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizerWorld) ProtoMessage() {} + +func (x *AuthorizerWorld) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizerWorld.ProtoReflect.Descriptor instead. +func (*AuthorizerWorld) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{28} +} + +func (x *AuthorizerWorld) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +func (x *AuthorizerWorld) GetSymbols() []string { + if x != nil { + return x.Symbols + } + return nil +} + +func (x *AuthorizerWorld) GetPublicKeys() []*PublicKey { + if x != nil { + return x.PublicKeys + } + return nil +} + +func (x *AuthorizerWorld) GetBlocks() []*SnapshotBlock { + if x != nil { + return x.Blocks + } + return nil +} + +func (x *AuthorizerWorld) GetAuthorizerBlock() *SnapshotBlock { + if x != nil { + return x.AuthorizerBlock + } + return nil +} + +func (x *AuthorizerWorld) GetAuthorizerPolicies() []*Policy { + if x != nil { + return x.AuthorizerPolicies + } + return nil +} + +func (x *AuthorizerWorld) GetGeneratedFacts() []*GeneratedFacts { + if x != nil { + return x.GeneratedFacts + } + return nil +} + +func (x *AuthorizerWorld) GetIterations() uint64 { + if x != nil && x.Iterations != nil { + return *x.Iterations + } + return 0 +} + +type Origin struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Content: + // + // *Origin_Authorizer + // *Origin_Origin + Content isOrigin_Content `protobuf_oneof:"Content"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Origin) Reset() { + *x = Origin{} + mi := &file_schema_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Origin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Origin) ProtoMessage() {} + +func (x *Origin) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Origin.ProtoReflect.Descriptor instead. +func (*Origin) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{29} +} + +func (x *Origin) GetContent() isOrigin_Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Origin) GetAuthorizer() *Empty { + if x != nil { + if x, ok := x.Content.(*Origin_Authorizer); ok { + return x.Authorizer + } + } + return nil +} + +func (x *Origin) GetOrigin() uint32 { + if x != nil { + if x, ok := x.Content.(*Origin_Origin); ok { + return x.Origin + } + } + return 0 +} + +type isOrigin_Content interface { + isOrigin_Content() +} + +type Origin_Authorizer struct { + Authorizer *Empty `protobuf:"bytes,1,opt,name=authorizer,oneof"` +} + +type Origin_Origin struct { + Origin uint32 `protobuf:"varint,2,opt,name=origin,oneof"` +} + +func (*Origin_Authorizer) isOrigin_Content() {} + +func (*Origin_Origin) isOrigin_Content() {} + +type Empty struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Empty) Reset() { + *x = Empty{} + mi := &file_schema_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{30} +} + +type GeneratedFacts struct { + state protoimpl.MessageState `protogen:"open.v1"` + Origins []*Origin `protobuf:"bytes,1,rep,name=origins" json:"origins,omitempty"` + Facts []*Fact `protobuf:"bytes,2,rep,name=facts" json:"facts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GeneratedFacts) Reset() { + *x = GeneratedFacts{} + mi := &file_schema_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GeneratedFacts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratedFacts) ProtoMessage() {} + +func (x *GeneratedFacts) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratedFacts.ProtoReflect.Descriptor instead. +func (*GeneratedFacts) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{31} +} + +func (x *GeneratedFacts) GetOrigins() []*Origin { + if x != nil { + return x.Origins + } + return nil +} + +func (x *GeneratedFacts) GetFacts() []*Fact { + if x != nil { + return x.Facts + } + return nil +} + +type SnapshotBlock struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context *string `protobuf:"bytes,1,opt,name=context" json:"context,omitempty"` + Version *uint32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` + Facts []*Fact `protobuf:"bytes,3,rep,name=facts" json:"facts,omitempty"` + Rules []*Rule `protobuf:"bytes,4,rep,name=rules" json:"rules,omitempty"` + Checks []*Check `protobuf:"bytes,5,rep,name=checks" json:"checks,omitempty"` + Scope []*Scope `protobuf:"bytes,6,rep,name=scope" json:"scope,omitempty"` + ExternalKey *PublicKey `protobuf:"bytes,7,opt,name=externalKey" json:"externalKey,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SnapshotBlock) Reset() { + *x = SnapshotBlock{} + mi := &file_schema_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SnapshotBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SnapshotBlock) ProtoMessage() {} + +func (x *SnapshotBlock) ProtoReflect() protoreflect.Message { + mi := &file_schema_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SnapshotBlock.ProtoReflect.Descriptor instead. +func (*SnapshotBlock) Descriptor() ([]byte, []int) { + return file_schema_proto_rawDescGZIP(), []int{32} +} + +func (x *SnapshotBlock) GetContext() string { + if x != nil && x.Context != nil { + return *x.Context + } + return "" +} + +func (x *SnapshotBlock) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +func (x *SnapshotBlock) GetFacts() []*Fact { + if x != nil { + return x.Facts + } + return nil +} + +func (x *SnapshotBlock) GetRules() []*Rule { + if x != nil { + return x.Rules + } + return nil +} + +func (x *SnapshotBlock) GetChecks() []*Check { + if x != nil { + return x.Checks + } + return nil +} + +func (x *SnapshotBlock) GetScope() []*Scope { + if x != nil { + return x.Scope + } + return nil +} + +func (x *SnapshotBlock) GetExternalKey() *PublicKey { + if x != nil { + return x.ExternalKey + } + return nil +} + +var File_schema_proto protoreflect.FileDescriptor + +var file_schema_proto_rawDesc = string([]byte{ + 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, + 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0xd9, 0x01, 0x0a, 0x07, 0x42, 0x69, 0x73, 0x63, 0x75, 0x69, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, + 0x40, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x02, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x3a, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x32, 0x0a, + 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, + 0x66, 0x22, 0xef, 0x01, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0c, + 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x3a, 0x0a, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x03, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x56, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x22, 0x71, 0x0a, 0x11, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, + 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x90, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x48, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, + 0x6d, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, + 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, + 0x74, 0x68, 0x6d, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x22, 0x27, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x0b, 0x0a, + 0x07, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x45, + 0x43, 0x50, 0x32, 0x35, 0x36, 0x52, 0x31, 0x10, 0x01, 0x22, 0x5e, 0x0a, 0x05, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x12, 0x20, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0e, + 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x09, + 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x05, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x46, 0x61, 0x63, 0x74, 0x52, 0x05, 0x66, + 0x61, 0x63, 0x74, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6c, 0x65, + 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, + 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x32, 0x0a, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x12, 0x40, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x73, 0x22, 0xa4, 0x01, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, + 0x09, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x26, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x28, 0x0a, 0x09, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x10, + 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x10, 0x01, 0x42, + 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x04, 0x46, 0x61, + 0x63, 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, + 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x09, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x22, 0xeb, 0x01, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x68, + 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, + 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x04, 0x68, 0x65, 0x61, + 0x64, 0x12, 0x34, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x43, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x0b, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x05, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, + 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x22, 0x9b, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x35, 0x0a, 0x07, 0x71, 0x75, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, + 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x12, 0x35, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x21, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4b, 0x69, + 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x24, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, + 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x10, 0x02, 0x22, 0x52, + 0x0a, 0x09, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x04, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x31, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x52, 0x05, 0x74, 0x65, 0x72, + 0x6d, 0x73, 0x22, 0xf7, 0x02, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x12, 0x1c, 0x0a, 0x08, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, + 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x07, 0x69, 0x6e, 0x74, + 0x65, 0x67, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, + 0x14, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, + 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x62, + 0x6f, 0x6f, 0x6c, 0x12, 0x32, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x53, 0x65, 0x74, + 0x48, 0x00, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x6e, 0x75, 0x6c, 0x6c, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x75, 0x6c, 0x6c, 0x12, 0x34, 0x0a, 0x05, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x41, 0x72, 0x72, 0x61, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x72, 0x72, 0x61, + 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x70, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x61, + 0x70, 0x42, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x07, + 0x54, 0x65, 0x72, 0x6d, 0x53, 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, + 0x6d, 0x52, 0x03, 0x73, 0x65, 0x74, 0x22, 0x3a, 0x0a, 0x05, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, + 0x31, 0x0a, 0x05, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x52, 0x05, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x22, 0x40, 0x0a, 0x03, 0x4d, 0x61, 0x70, 0x12, 0x39, 0x0a, 0x07, 0x65, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x22, 0x6e, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x2f, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x61, 0x70, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x49, 0x0a, 0x06, 0x4d, 0x61, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x1a, + 0x0a, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, + 0x00, 0x52, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x42, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, + 0x39, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, + 0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x02, 0x4f, + 0x70, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x48, 0x00, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x75, 0x6e, 0x61, 0x72, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, + 0x55, 0x6e, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x05, 0x75, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x39, + 0x0a, 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x48, + 0x00, 0x52, 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x3c, 0x0a, 0x07, 0x63, 0x6c, 0x6f, + 0x73, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x48, 0x00, 0x52, 0x07, + 0x63, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x07, 0x4f, 0x70, 0x55, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x37, + 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x55, 0x6e, 0x61, 0x72, 0x79, 0x2e, 0x4b, 0x69, 0x6e, + 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x66, 0x69, 0x4e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x66, 0x69, 0x4e, 0x61, 0x6d, + 0x65, 0x22, 0x3f, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x73, 0x10, + 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x10, 0x02, 0x12, 0x0a, 0x0a, + 0x06, 0x54, 0x79, 0x70, 0x65, 0x4f, 0x66, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x66, 0x69, + 0x10, 0x04, 0x22, 0xf1, 0x03, 0x0a, 0x08, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, + 0x38, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x24, 0x2e, + 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x2e, 0x4b, + 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x66, 0x69, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x66, 0x69, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x90, 0x03, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0c, 0x0a, 0x08, + 0x4c, 0x65, 0x73, 0x73, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, + 0x65, 0x73, 0x73, 0x4f, 0x72, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, + 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x4f, 0x72, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x03, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x10, 0x06, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x10, + 0x07, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x08, 0x12, 0x07, 0x0a, 0x03, + 0x41, 0x64, 0x64, 0x10, 0x09, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x10, 0x0a, 0x12, 0x07, + 0x0a, 0x03, 0x4d, 0x75, 0x6c, 0x10, 0x0b, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x69, 0x76, 0x10, 0x0c, + 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6e, 0x64, 0x10, 0x0d, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x72, 0x10, + 0x0e, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x10, 0x0f, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x6f, 0x6e, 0x10, 0x10, 0x12, 0x0e, + 0x0a, 0x0a, 0x42, 0x69, 0x74, 0x77, 0x69, 0x73, 0x65, 0x41, 0x6e, 0x64, 0x10, 0x11, 0x12, 0x0d, + 0x0a, 0x09, 0x42, 0x69, 0x74, 0x77, 0x69, 0x73, 0x65, 0x4f, 0x72, 0x10, 0x12, 0x12, 0x0e, 0x0a, + 0x0a, 0x42, 0x69, 0x74, 0x77, 0x69, 0x73, 0x65, 0x58, 0x6f, 0x72, 0x10, 0x13, 0x12, 0x0c, 0x0a, + 0x08, 0x4e, 0x6f, 0x74, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x14, 0x12, 0x16, 0x0a, 0x12, 0x48, + 0x65, 0x74, 0x65, 0x72, 0x6f, 0x67, 0x65, 0x6e, 0x65, 0x6f, 0x75, 0x73, 0x45, 0x71, 0x75, 0x61, + 0x6c, 0x10, 0x15, 0x12, 0x19, 0x0a, 0x15, 0x48, 0x65, 0x74, 0x65, 0x72, 0x6f, 0x67, 0x65, 0x6e, + 0x65, 0x6f, 0x75, 0x73, 0x4e, 0x6f, 0x74, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, 0x16, 0x12, 0x0b, + 0x0a, 0x07, 0x4c, 0x61, 0x7a, 0x79, 0x41, 0x6e, 0x64, 0x10, 0x17, 0x12, 0x0a, 0x0a, 0x06, 0x4c, + 0x61, 0x7a, 0x79, 0x4f, 0x72, 0x10, 0x18, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, 0x10, 0x19, + 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6e, 0x79, 0x10, 0x1a, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x65, 0x74, + 0x10, 0x1b, 0x12, 0x07, 0x0a, 0x03, 0x46, 0x66, 0x69, 0x10, 0x1c, 0x12, 0x09, 0x0a, 0x05, 0x54, + 0x72, 0x79, 0x4f, 0x72, 0x10, 0x1d, 0x22, 0x50, 0x0a, 0x09, 0x4f, 0x70, 0x43, 0x6c, 0x6f, 0x73, + 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x2b, 0x0a, 0x03, 0x6f, + 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x22, 0x94, 0x01, 0x0a, 0x06, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x12, 0x35, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x22, 0x1b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, + 0x6c, 0x6f, 0x77, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x01, 0x22, + 0x9f, 0x02, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x66, 0x61, + 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, + 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x46, 0x61, 0x63, 0x74, 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x31, 0x0a, + 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, + 0x12, 0x34, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x06, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, + 0x73, 0x22, 0xe4, 0x01, 0x0a, 0x16, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4e, 0x0a, 0x11, + 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, + 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x11, 0x6c, 0x65, 0x67, 0x61, 0x63, + 0x79, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x4c, 0x0a, 0x10, + 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, + 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x10, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x03, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x17, 0x54, 0x68, 0x69, + 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, + 0x01, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x56, + 0x0a, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x69, 0x73, 0x63, + 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x38, 0x0a, + 0x06, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x75, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, + 0x06, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x02, 0x28, 0x04, 0x52, 0x0d, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3c, 0x0a, + 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x67, 0x0a, 0x09, 0x52, + 0x75, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x46, + 0x61, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x02, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x46, + 0x61, 0x63, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x49, 0x74, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x02, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78, + 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, + 0x78, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x02, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x61, 0x78, + 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd3, 0x03, 0x0a, 0x0f, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x40, 0x0a, 0x0a, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x3c, + 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x4e, 0x0a, 0x0f, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, + 0x05, 0x20, 0x02, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x0f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x4d, 0x0a, 0x12, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x61, 0x63, 0x74, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x61, 0x63, 0x74, 0x73, 0x52, 0x0e, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x02, 0x28, 0x04, 0x52, 0x0a, + 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x6d, 0x0a, 0x06, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x12, 0x3e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x42, 0x09, + 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x7c, 0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, + 0x61, 0x63, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x07, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x31, 0x0a, + 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x46, 0x61, 0x63, 0x74, 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, + 0x22, 0xd7, 0x02, 0x0a, 0x0d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x46, 0x61, + 0x63, 0x74, 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x75, 0x6c, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, + 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x06, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, + 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x73, 0x12, 0x32, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x69, + 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x42, 0xcb, 0x01, 0x0a, 0x19, 0x63, + 0x6f, 0x6d, 0x2e, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x42, 0x0b, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x2d, 0x62, 0x69, 0x73, 0x63, + 0x75, 0x69, 0x74, 0x2f, 0x62, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x2d, 0x67, 0x6f, 0x2f, 0x76, + 0x32, 0x2f, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x42, 0x46, 0x53, 0xaa, 0x02, 0x15, 0x42, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0xca, 0x02, 0x15, 0x42, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x5c, 0x46, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x5c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0xe2, 0x02, 0x21, 0x42, 0x69, 0x73, + 0x63, 0x75, 0x69, 0x74, 0x5c, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x5c, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x17, 0x42, 0x69, 0x73, 0x63, 0x75, 0x69, 0x74, 0x3a, 0x3a, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x3a, 0x3a, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, +}) + +var ( + file_schema_proto_rawDescOnce sync.Once + file_schema_proto_rawDescData []byte +) + +func file_schema_proto_rawDescGZIP() []byte { + file_schema_proto_rawDescOnce.Do(func() { + file_schema_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc))) + }) + return file_schema_proto_rawDescData +} + +var file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 33) +var file_schema_proto_goTypes = []any{ + (PublicKey_Algorithm)(0), // 0: biscuit.format.schema.PublicKey.Algorithm + (Scope_ScopeType)(0), // 1: biscuit.format.schema.Scope.ScopeType + (Check_Kind)(0), // 2: biscuit.format.schema.Check.Kind + (OpUnary_Kind)(0), // 3: biscuit.format.schema.OpUnary.Kind + (OpBinary_Kind)(0), // 4: biscuit.format.schema.OpBinary.Kind + (Policy_Kind)(0), // 5: biscuit.format.schema.Policy.Kind + (*Biscuit)(nil), // 6: biscuit.format.schema.Biscuit + (*SignedBlock)(nil), // 7: biscuit.format.schema.SignedBlock + (*ExternalSignature)(nil), // 8: biscuit.format.schema.ExternalSignature + (*PublicKey)(nil), // 9: biscuit.format.schema.PublicKey + (*Proof)(nil), // 10: biscuit.format.schema.Proof + (*Block)(nil), // 11: biscuit.format.schema.Block + (*Scope)(nil), // 12: biscuit.format.schema.Scope + (*Fact)(nil), // 13: biscuit.format.schema.Fact + (*Rule)(nil), // 14: biscuit.format.schema.Rule + (*Check)(nil), // 15: biscuit.format.schema.Check + (*Predicate)(nil), // 16: biscuit.format.schema.Predicate + (*Term)(nil), // 17: biscuit.format.schema.Term + (*TermSet)(nil), // 18: biscuit.format.schema.TermSet + (*Array)(nil), // 19: biscuit.format.schema.Array + (*Map)(nil), // 20: biscuit.format.schema.Map + (*MapEntry)(nil), // 21: biscuit.format.schema.MapEntry + (*MapKey)(nil), // 22: biscuit.format.schema.MapKey + (*Expression)(nil), // 23: biscuit.format.schema.Expression + (*Op)(nil), // 24: biscuit.format.schema.Op + (*OpUnary)(nil), // 25: biscuit.format.schema.OpUnary + (*OpBinary)(nil), // 26: biscuit.format.schema.OpBinary + (*OpClosure)(nil), // 27: biscuit.format.schema.OpClosure + (*Policy)(nil), // 28: biscuit.format.schema.Policy + (*AuthorizerPolicies)(nil), // 29: biscuit.format.schema.AuthorizerPolicies + (*ThirdPartyBlockRequest)(nil), // 30: biscuit.format.schema.ThirdPartyBlockRequest + (*ThirdPartyBlockContents)(nil), // 31: biscuit.format.schema.ThirdPartyBlockContents + (*AuthorizerSnapshot)(nil), // 32: biscuit.format.schema.AuthorizerSnapshot + (*RunLimits)(nil), // 33: biscuit.format.schema.RunLimits + (*AuthorizerWorld)(nil), // 34: biscuit.format.schema.AuthorizerWorld + (*Origin)(nil), // 35: biscuit.format.schema.Origin + (*Empty)(nil), // 36: biscuit.format.schema.Empty + (*GeneratedFacts)(nil), // 37: biscuit.format.schema.GeneratedFacts + (*SnapshotBlock)(nil), // 38: biscuit.format.schema.SnapshotBlock +} +var file_schema_proto_depIdxs = []int32{ + 7, // 0: biscuit.format.schema.Biscuit.authority:type_name -> biscuit.format.schema.SignedBlock + 7, // 1: biscuit.format.schema.Biscuit.blocks:type_name -> biscuit.format.schema.SignedBlock + 10, // 2: biscuit.format.schema.Biscuit.proof:type_name -> biscuit.format.schema.Proof + 9, // 3: biscuit.format.schema.SignedBlock.nextKey:type_name -> biscuit.format.schema.PublicKey + 8, // 4: biscuit.format.schema.SignedBlock.externalSignature:type_name -> biscuit.format.schema.ExternalSignature + 9, // 5: biscuit.format.schema.ExternalSignature.publicKey:type_name -> biscuit.format.schema.PublicKey + 0, // 6: biscuit.format.schema.PublicKey.algorithm:type_name -> biscuit.format.schema.PublicKey.Algorithm + 13, // 7: biscuit.format.schema.Block.facts:type_name -> biscuit.format.schema.Fact + 14, // 8: biscuit.format.schema.Block.rules:type_name -> biscuit.format.schema.Rule + 15, // 9: biscuit.format.schema.Block.checks:type_name -> biscuit.format.schema.Check + 12, // 10: biscuit.format.schema.Block.scope:type_name -> biscuit.format.schema.Scope + 9, // 11: biscuit.format.schema.Block.publicKeys:type_name -> biscuit.format.schema.PublicKey + 1, // 12: biscuit.format.schema.Scope.scopeType:type_name -> biscuit.format.schema.Scope.ScopeType + 16, // 13: biscuit.format.schema.Fact.predicate:type_name -> biscuit.format.schema.Predicate + 16, // 14: biscuit.format.schema.Rule.head:type_name -> biscuit.format.schema.Predicate + 16, // 15: biscuit.format.schema.Rule.body:type_name -> biscuit.format.schema.Predicate + 23, // 16: biscuit.format.schema.Rule.expressions:type_name -> biscuit.format.schema.Expression + 12, // 17: biscuit.format.schema.Rule.scope:type_name -> biscuit.format.schema.Scope + 14, // 18: biscuit.format.schema.Check.queries:type_name -> biscuit.format.schema.Rule + 2, // 19: biscuit.format.schema.Check.kind:type_name -> biscuit.format.schema.Check.Kind + 17, // 20: biscuit.format.schema.Predicate.terms:type_name -> biscuit.format.schema.Term + 18, // 21: biscuit.format.schema.Term.set:type_name -> biscuit.format.schema.TermSet + 36, // 22: biscuit.format.schema.Term.null:type_name -> biscuit.format.schema.Empty + 19, // 23: biscuit.format.schema.Term.array:type_name -> biscuit.format.schema.Array + 20, // 24: biscuit.format.schema.Term.map:type_name -> biscuit.format.schema.Map + 17, // 25: biscuit.format.schema.TermSet.set:type_name -> biscuit.format.schema.Term + 17, // 26: biscuit.format.schema.Array.array:type_name -> biscuit.format.schema.Term + 21, // 27: biscuit.format.schema.Map.entries:type_name -> biscuit.format.schema.MapEntry + 22, // 28: biscuit.format.schema.MapEntry.key:type_name -> biscuit.format.schema.MapKey + 17, // 29: biscuit.format.schema.MapEntry.value:type_name -> biscuit.format.schema.Term + 24, // 30: biscuit.format.schema.Expression.ops:type_name -> biscuit.format.schema.Op + 17, // 31: biscuit.format.schema.Op.value:type_name -> biscuit.format.schema.Term + 25, // 32: biscuit.format.schema.Op.unary:type_name -> biscuit.format.schema.OpUnary + 26, // 33: biscuit.format.schema.Op.Binary:type_name -> biscuit.format.schema.OpBinary + 27, // 34: biscuit.format.schema.Op.closure:type_name -> biscuit.format.schema.OpClosure + 3, // 35: biscuit.format.schema.OpUnary.kind:type_name -> biscuit.format.schema.OpUnary.Kind + 4, // 36: biscuit.format.schema.OpBinary.kind:type_name -> biscuit.format.schema.OpBinary.Kind + 24, // 37: biscuit.format.schema.OpClosure.ops:type_name -> biscuit.format.schema.Op + 14, // 38: biscuit.format.schema.Policy.queries:type_name -> biscuit.format.schema.Rule + 5, // 39: biscuit.format.schema.Policy.kind:type_name -> biscuit.format.schema.Policy.Kind + 13, // 40: biscuit.format.schema.AuthorizerPolicies.facts:type_name -> biscuit.format.schema.Fact + 14, // 41: biscuit.format.schema.AuthorizerPolicies.rules:type_name -> biscuit.format.schema.Rule + 15, // 42: biscuit.format.schema.AuthorizerPolicies.checks:type_name -> biscuit.format.schema.Check + 28, // 43: biscuit.format.schema.AuthorizerPolicies.policies:type_name -> biscuit.format.schema.Policy + 9, // 44: biscuit.format.schema.ThirdPartyBlockRequest.legacyPreviousKey:type_name -> biscuit.format.schema.PublicKey + 9, // 45: biscuit.format.schema.ThirdPartyBlockRequest.legacyPublicKeys:type_name -> biscuit.format.schema.PublicKey + 8, // 46: biscuit.format.schema.ThirdPartyBlockContents.externalSignature:type_name -> biscuit.format.schema.ExternalSignature + 33, // 47: biscuit.format.schema.AuthorizerSnapshot.limits:type_name -> biscuit.format.schema.RunLimits + 34, // 48: biscuit.format.schema.AuthorizerSnapshot.world:type_name -> biscuit.format.schema.AuthorizerWorld + 9, // 49: biscuit.format.schema.AuthorizerWorld.publicKeys:type_name -> biscuit.format.schema.PublicKey + 38, // 50: biscuit.format.schema.AuthorizerWorld.blocks:type_name -> biscuit.format.schema.SnapshotBlock + 38, // 51: biscuit.format.schema.AuthorizerWorld.authorizerBlock:type_name -> biscuit.format.schema.SnapshotBlock + 28, // 52: biscuit.format.schema.AuthorizerWorld.authorizerPolicies:type_name -> biscuit.format.schema.Policy + 37, // 53: biscuit.format.schema.AuthorizerWorld.generatedFacts:type_name -> biscuit.format.schema.GeneratedFacts + 36, // 54: biscuit.format.schema.Origin.authorizer:type_name -> biscuit.format.schema.Empty + 35, // 55: biscuit.format.schema.GeneratedFacts.origins:type_name -> biscuit.format.schema.Origin + 13, // 56: biscuit.format.schema.GeneratedFacts.facts:type_name -> biscuit.format.schema.Fact + 13, // 57: biscuit.format.schema.SnapshotBlock.facts:type_name -> biscuit.format.schema.Fact + 14, // 58: biscuit.format.schema.SnapshotBlock.rules:type_name -> biscuit.format.schema.Rule + 15, // 59: biscuit.format.schema.SnapshotBlock.checks:type_name -> biscuit.format.schema.Check + 12, // 60: biscuit.format.schema.SnapshotBlock.scope:type_name -> biscuit.format.schema.Scope + 9, // 61: biscuit.format.schema.SnapshotBlock.externalKey:type_name -> biscuit.format.schema.PublicKey + 62, // [62:62] is the sub-list for method output_type + 62, // [62:62] is the sub-list for method input_type + 62, // [62:62] is the sub-list for extension type_name + 62, // [62:62] is the sub-list for extension extendee + 0, // [0:62] is the sub-list for field type_name +} + +func init() { file_schema_proto_init() } +func file_schema_proto_init() { + if File_schema_proto != nil { + return + } + file_schema_proto_msgTypes[4].OneofWrappers = []any{ + (*Proof_NextSecret)(nil), + (*Proof_FinalSignature)(nil), + } + file_schema_proto_msgTypes[6].OneofWrappers = []any{ + (*Scope_ScopeType_)(nil), + (*Scope_PublicKey)(nil), + } + file_schema_proto_msgTypes[11].OneofWrappers = []any{ + (*Term_Variable)(nil), + (*Term_Integer)(nil), + (*Term_String_)(nil), + (*Term_Date)(nil), + (*Term_Bytes)(nil), + (*Term_Bool)(nil), + (*Term_Set)(nil), + (*Term_Null)(nil), + (*Term_Array)(nil), + (*Term_Map)(nil), + } + file_schema_proto_msgTypes[16].OneofWrappers = []any{ + (*MapKey_Integer)(nil), + (*MapKey_String_)(nil), + } + file_schema_proto_msgTypes[18].OneofWrappers = []any{ + (*Op_Value)(nil), + (*Op_Unary)(nil), + (*Op_Binary)(nil), + (*Op_Closure)(nil), + } + file_schema_proto_msgTypes[29].OneofWrappers = []any{ + (*Origin_Authorizer)(nil), + (*Origin_Origin)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc)), + NumEnums: 6, + NumMessages: 33, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_schema_proto_goTypes, + DependencyIndexes: file_schema_proto_depIdxs, + EnumInfos: file_schema_proto_enumTypes, + MessageInfos: file_schema_proto_msgTypes, + }.Build() + File_schema_proto = out.File + file_schema_proto_goTypes = nil + file_schema_proto_depIdxs = nil +} diff --git a/samples/samples_test.go b/samples/samples_test.go index c40fa38d..66f8a33b 100644 --- a/samples/samples_test.go +++ b/samples/samples_test.go @@ -4,16 +4,23 @@ package biscuittest import ( + "cmp" "crypto/ed25519" "crypto/rand" "encoding/hex" "encoding/json" + "errors" "fmt" + "math" "os" + "slices" "sort" + "strconv" + "strings" "testing" "github.com/eclipse-biscuit/biscuit-go/v2" + "github.com/eclipse-biscuit/biscuit-go/v2/datalog" "github.com/eclipse-biscuit/biscuit-go/v2/parser" "github.com/stretchr/testify/require" ) @@ -51,11 +58,15 @@ type BiscuitError struct { Allow int `json:"Allow"` } `json:"policy"` Checks []struct { - Block struct { + Block *struct { BlockID int `json:"block_id"` CheckID int `json:"check_id"` Rule string `json:"rule"` } `json:"Block"` + Authorizer *struct { + CheckID int `json:"check_id"` + Rule string `json:"rule"` + } `json:"Authorizer"` } `json:"checks"` } `json:"Unauthorized"` InvalidBlockRule []any `json:"InvalidBlockRule"` @@ -64,76 +75,158 @@ type BiscuitError struct { Signature *struct { InvalidSignature string `json:"InvalidSignature"` } `json:"Signature"` + BlockSignatureDeserializationError *string `json:"BlockSignatureDeserializationError"` } `json:"Format"` + Execution *string `json:"Execution"` } +// authorizerOrigin is the origin the spec assigns to authorizer rules and checks (usize::MAX). +const authorizerOrigin = math.MaxUint64 + type World struct { - Facts []ScopedFact `json:"facts"` - Rules []ScopedRule `json:"rules"` - Checks []any `json:"checks"` + Facts []FactGroup `json:"facts"` + Rules []RuleGroup `json:"rules"` + Checks []CheckGroup `json:"checks"` Policies []string `json:"policies"` } -type ScopedFact struct { - Facts []string `json:"facts"` - Scope [](*int32) `json:"origin"` +// FactGroup holds the facts derived from the same set of block origins; +// a nil origin denotes the authorizer. +type FactGroup struct { + Origin []*uint64 `json:"origin"` + Facts []string `json:"facts"` +} + +type RuleGroup struct { + Origin *uint64 `json:"origin"` + Rules []string `json:"rules"` } -type ScopedRule struct { - Rules []string `json:"rules"` - Scope *int32 `json:"origin"` +type CheckGroup struct { + Origin *uint64 `json:"origin"` + Checks []string `json:"checks"` +} + +// originString prints an origin the way the authorizer does; a nil block id +// is the authorizer (usize::MAX in the reference implementation). The JSON +// lists the ids as a set, so they are sorted here, authorizer last. +func originString(ids []*uint64) string { + ids = sortedOrigin(ids) + parts := make([]string, len(ids)) + for i, id := range ids { + if id == nil || *id == authorizerOrigin { + parts[i] = "authorizer" + } else { + parts[i] = strconv.FormatUint(*id, 10) + } + } + return "[" + strings.Join(parts, ", ") + "]" } +// String renders the expected world in the format of Authorizer.PrintWorld: +// facts grouped by origin and rules by block, groups and entries sorted. +// Checks and policies are compared through the authorization result. func (w World) String() string { - facts := []string{} - for _, f := range w.Facts { - visible := true - for _, s := range f.Scope { + var b strings.Builder - if s != nil && *s != 0 { - visible = false - break - } + facts := slices.Clone(w.Facts) + for i := range facts { + facts[i].Origin = sortedOrigin(facts[i].Origin) + } + slices.SortFunc(facts, func(a, b FactGroup) int { return compareOrigins(a.Origin, b.Origin) }) + if len(facts) > 0 { + b.WriteString("// Facts:\n") + } + for _, group := range facts { + fmt.Fprintf(&b, "// origin: %s\n", originString(group.Origin)) + entries := slices.Clone(group.Facts) + sort.Strings(entries) + for _, f := range entries { + fmt.Fprintf(&b, "%s;\n", f) } + } - if visible { - facts = append(facts, f.Facts...) + rules := slices.Clone(w.Rules) + slices.SortFunc(rules, func(a, b RuleGroup) int { return compareOrigins([]*uint64{a.Origin}, []*uint64{b.Origin}) }) + if len(rules) > 0 { + if b.Len() > 0 { + b.WriteString("\n") } + b.WriteString("// Rules:\n") } - sort.Strings(facts) - rules := []string{} - for _, r := range w.Rules { - if r.Scope == nil || *r.Scope == 0 { - rules = append(rules, r.Rules...) + for _, group := range rules { + fmt.Fprintf(&b, "// origin: %s\n", strings.Trim(originString([]*uint64{group.Origin}), "[]")) + entries := slices.Clone(group.Rules) + sort.Strings(entries) + for _, r := range entries { + fmt.Fprintf(&b, "%s;\n", r) } } - sort.Strings(rules) + return b.String() +} - return fmt.Sprintf("World {{\n\tfacts: %v\n\trules: %v\n}}", facts, rules) +func sortedOrigin(ids []*uint64) []*uint64 { + ids = slices.Clone(ids) + slices.SortFunc(ids, func(a, b *uint64) int { return compareOrigins([]*uint64{a}, []*uint64{b}) }) + return ids +} + +// compareOrigins orders origins by block id, the authorizer last. +func compareOrigins(a, b []*uint64) int { + value := func(id *uint64) uint64 { + if id == nil { + return authorizerOrigin + } + return *id + } + for i := 0; i < len(a) && i < len(b); i++ { + if c := cmp.Compare(value(a[i]), value(b[i])); c != 0 { + return c + } + } + return cmp.Compare(len(a), len(b)) } type Validation struct { - World World `json:"world"` + World *World `json:"world"` Result Result `json:"result"` AuthorizerCode string `json:"authorizer_code"` RevocationIds []string `json:"revocation_ids"` } -func CheckSample(root_key ed25519.PublicKey, c TestCase, t *testing.T) { - // all these use features from v3.1+, which are not supported yet - if c.Filename == "test013_block_rules.bc" || - c.Filename == "test017_expressions.bc" || - c.Filename == "test036_secp256r1.bc" { - t.SkipNow() - } +// Support for newer datalog versions lands incrementally. Samples are gated +// on the block version they exercise: bumping biscuit.MaxSchemaVersion enables +// the matching samples here with no other change. +// +// unsupported lists the samples inside the supported version range that +// cannot pass yet; each entry is removed by the change that closes the gap. +var unsupported = map[string]string{} + +func maxBlockVersion(c TestCase) uint32 { + var v uint32 for _, b := range c.Token { - if b.Version > biscuit.MaxSchemaVersion { - t.SkipNow() + if b.Version > v { + v = b.Version } } + return v +} + +func CheckSample(root_key ed25519.PublicKey, c TestCase, t *testing.T) { fmt.Printf("Checking sample %s\n", c.Filename) b, err := os.ReadFile("./data/current/" + c.Filename) require.NoError(t, err) + + if v := maxBlockVersion(c); v > biscuit.MaxSchemaVersion { + // The spec requires refusing blocks newer than the supported range. + _, err := biscuit.Unmarshal(b) + require.Error(t, err) + t.Skipf("block version %d > MaxSchemaVersion %d", v, biscuit.MaxSchemaVersion) + } + if reason, ok := unsupported[c.Filename]; ok { + t.Skipf("unsupported: %s", reason) + } + token, err := biscuit.Unmarshal(b) if err == nil { @@ -183,11 +276,30 @@ func CompareBlocks(token biscuit.Biscuit, blocks []Block, t *testing.T) { require.Equal(t, sample, rebuilt.Code()) } +// sampleExternFuncs mirrors the extern functions registered by the sample +// generator (biscuit-rust examples/testcases.rs). +var sampleExternFuncs = map[string]biscuit.ExternFunc{ + "test": func(left biscuit.Term, right biscuit.Term) (biscuit.Term, error) { + if right == nil { + return left, nil + } + l, lok := left.(biscuit.String) + r, rok := right.(biscuit.String) + if !lok || !rok { + return nil, errors.New("unsupported operands") + } + if l == r { + return biscuit.String("equal strings"), nil + } + return biscuit.String("different strings"), nil + }, +} + func CompareResult(root_key ed25519.PublicKey, filename string, token biscuit.Biscuit, v Validation, t *testing.T) { p := parser.New() authorizer_code, err := p.Authorizer(v.AuthorizerCode, nil) require.NoError(t, err) - authorizer, err := token.Authorizer(root_key) + authorizer, err := token.AuthorizerFrom(biscuit.RootKey(root_key), biscuit.WithExternFuncs(sampleExternFuncs)) if err != nil { CompareError(err, v.Result.Err, t) @@ -199,7 +311,10 @@ func CompareResult(root_key ed25519.PublicKey, filename string, token biscuit.Bi } else { require.NotNil(t, v.Result.Ok) } - require.Equal(t, v.World.String(), authorizer.PrintWorld()) + // The world is null when the reference implementation rejected the token outright. + if v.World != nil { + require.Equal(t, v.World.String(), authorizer.PrintWorld()) + } } } @@ -217,6 +332,18 @@ func CompareError(authorization_error error, sample_error *BiscuitError, t *test } else { require.Fail(t, error_string) } + } else if sample_error.Execution != nil { + require.ErrorIs(t, authorization_error, biscuit.ErrExecution) + switch *sample_error.Execution { + case "Overflow": + require.ErrorIs(t, authorization_error, datalog.ErrInt64Overflow) + case "ShadowedVariable": + require.ErrorIs(t, authorization_error, datalog.ErrShadowedVariable) + case "InvalidType": + // Type errors have no sentinel in the datalog package. + default: + require.Fail(t, "unknown execution error kind", *sample_error.Execution) + } } else { fmt.Println(sample_error) require.Fail(t, error_string) diff --git a/scope.go b/scope.go new file mode 100644 index 00000000..8f5489f3 --- /dev/null +++ b/scope.go @@ -0,0 +1,232 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package biscuit + +import ( + stdcrypto "crypto" + "encoding/hex" + "errors" + "fmt" + "slices" + "strings" + + "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/eclipse-biscuit/biscuit-go/v2/internal/crypto" + "github.com/eclipse-biscuit/biscuit-go/v2/pb" +) + +// Scope is one entry of a `trusting` clause on a rule, check, policy or +// block. See datalog.Scope. +type Scope = datalog.Scope + +// PublicKey is a public key as written in a scope. Build one from a crypto +// key with PublicKeyFrom. +type PublicKey = datalog.PublicKey + +const ( + ScopeAuthority = datalog.ScopeAuthority + ScopePrevious = datalog.ScopePrevious + ScopePublicKey = datalog.ScopePublicKey +) + +// PublicKeyFrom encodes an ed25519.PublicKey or an *ecdsa.PublicKey on the +// P-256 curve as a scope public key. +func PublicKeyFrom(key stdcrypto.PublicKey) (PublicKey, error) { + verifier, err := crypto.NewVerifier(key) + if err != nil { + return PublicKey{}, err + } + return PublicKey{Algorithm: datalog.Algorithm(verifier.Algorithm()), Key: verifier.PublicKey()}, nil +} + +// ParsePublicKey reads a key in the datalog syntax, algorithm/hex, where +// algorithm is ed25519 or secp256r1. +func ParsePublicKey(s string) (PublicKey, error) { + algorithm, encoded, ok := strings.Cut(s, "/") + if !ok { + return PublicKey{}, fmt.Errorf("biscuit: invalid public key %q: expected algorithm/hex", s) + } + var alg pb.PublicKey_Algorithm + switch algorithm { + case "ed25519": + alg = pb.PublicKey_Ed25519 + case "secp256r1": + alg = pb.PublicKey_SECP256R1 + default: + return PublicKey{}, fmt.Errorf("biscuit: unsupported public key algorithm %q", algorithm) + } + key, err := hex.DecodeString(encoded) + if err != nil { + return PublicKey{}, fmt.Errorf("biscuit: invalid public key %q: %w", s, err) + } + if _, err := crypto.ParseVerifier(alg, key); err != nil { + return PublicKey{}, fmt.Errorf("biscuit: invalid public key %q: %w", s, err) + } + return PublicKey{Algorithm: datalog.Algorithm(alg), Key: key}, nil +} + +// TrustingPublicKey is the scope `trusting `. +func TrustingPublicKey(key stdcrypto.PublicKey) (Scope, error) { + pk, err := PublicKeyFrom(key) + if err != nil { + return Scope{}, err + } + return Scope{Kind: ScopePublicKey, PublicKey: pk}, nil +} + +// publicKeyTable interns the public keys of scopes, the way the symbol table +// interns strings: a block carries the keys it introduces, and a scope refers +// to a key by its index in the table of the token. +type publicKeyTable []datalog.PublicKey + +func (t publicKeyTable) index(key datalog.PublicKey) (int64, bool) { + for i, k := range t { + if k.Equal(key) { + return int64(i), true + } + } + return 0, false +} + +// insert adds the key when the table does not have it; returns its index. +func (t *publicKeyTable) insert(key datalog.PublicKey) int64 { + if i, ok := t.index(key); ok { + return i + } + *t = append(*t, key) + return int64(len(*t) - 1) +} + +func (t publicKeyTable) with(keys ...datalog.PublicKey) publicKeyTable { + res := slices.Clone(t) + for _, k := range keys { + res.insert(k) + } + return res +} + +// scopeKeys lists the public keys of a block's scopes, in order of +// appearance, without duplicates. +func scopeKeys(scopes []datalog.Scope, rules []datalog.Rule, checks []datalog.Check) []datalog.PublicKey { + var keys publicKeyTable + add := func(scopes []datalog.Scope) { + for _, s := range scopes { + if s.Kind == datalog.ScopePublicKey { + keys.insert(s.PublicKey) + } + } + } + add(scopes) + for _, r := range rules { + add(r.Scopes) + } + for _, c := range checks { + for _, q := range c.Queries { + add(q.Scopes) + } + } + return keys +} + +func hasScopes(scopes []datalog.Scope, rules []datalog.Rule, checks []datalog.Check) bool { + if len(scopes) > 0 { + return true + } + for _, r := range rules { + if len(r.Scopes) > 0 { + return true + } + } + for _, c := range checks { + for _, q := range c.Queries { + if len(q.Scopes) > 0 { + return true + } + } + } + return false +} + +func tokenScopeToProtoScope(scope datalog.Scope, keys publicKeyTable) (*pb.Scope, error) { + switch scope.Kind { + case datalog.ScopeAuthority: + return &pb.Scope{Content: &pb.Scope_ScopeType_{ScopeType: pb.Scope_Authority}}, nil + case datalog.ScopePrevious: + return &pb.Scope{Content: &pb.Scope_ScopeType_{ScopeType: pb.Scope_Previous}}, nil + case datalog.ScopePublicKey: + i, ok := keys.index(scope.PublicKey) + if !ok { + return nil, fmt.Errorf("biscuit: public key %s is not in the key table", scope.PublicKey) + } + return &pb.Scope{Content: &pb.Scope_PublicKey{PublicKey: i}}, nil + default: + return nil, fmt.Errorf("biscuit: unsupported scope kind: %d", scope.Kind) + } +} + +func protoScopeToTokenScope(scope *pb.Scope, keys publicKeyTable) (datalog.Scope, error) { + switch content := scope.GetContent().(type) { + case *pb.Scope_ScopeType_: + switch content.ScopeType { + case pb.Scope_Authority: + return datalog.Scope{Kind: datalog.ScopeAuthority}, nil + case pb.Scope_Previous: + return datalog.Scope{Kind: datalog.ScopePrevious}, nil + default: + return datalog.Scope{}, fmt.Errorf("biscuit: unsupported scope type: %s", content.ScopeType) + } + case *pb.Scope_PublicKey: + if content.PublicKey < 0 || content.PublicKey >= int64(len(keys)) { + return datalog.Scope{}, fmt.Errorf("biscuit: public key index %d out of the key table", content.PublicKey) + } + return datalog.Scope{Kind: datalog.ScopePublicKey, PublicKey: keys[content.PublicKey]}, nil + default: + return datalog.Scope{}, errors.New("biscuit: empty scope") + } +} + +func tokenScopesToProtoScopes(scopes []datalog.Scope, keys publicKeyTable) ([]*pb.Scope, error) { + if len(scopes) == 0 { + return nil, nil + } + res := make([]*pb.Scope, len(scopes)) + for i, s := range scopes { + pbScope, err := tokenScopeToProtoScope(s, keys) + if err != nil { + return nil, err + } + res[i] = pbScope + } + return res, nil +} + +func protoScopesToTokenScopes(scopes []*pb.Scope, keys publicKeyTable) ([]datalog.Scope, error) { + if len(scopes) == 0 { + return nil, nil + } + res := make([]datalog.Scope, len(scopes)) + for i, s := range scopes { + scope, err := protoScopeToTokenScope(s, keys) + if err != nil { + return nil, err + } + res[i] = scope + } + return res, nil +} + +func tokenPublicKeyToProtoPublicKey(key datalog.PublicKey) *pb.PublicKey { + return &pb.PublicKey{ + Algorithm: pb.PublicKey_Algorithm(key.Algorithm).Enum(), + Key: key.Key, + } +} + +func protoPublicKeyToTokenPublicKey(key *pb.PublicKey) (datalog.PublicKey, error) { + // ParseVerifier validates the encoding for the algorithm. + if _, err := crypto.ParseVerifier(key.GetAlgorithm(), key.GetKey()); err != nil { + return datalog.PublicKey{}, err + } + return datalog.PublicKey{Algorithm: datalog.Algorithm(key.GetAlgorithm()), Key: key.GetKey()}, nil +} diff --git a/scope_test.go b/scope_test.go new file mode 100644 index 00000000..b0aef791 --- /dev/null +++ b/scope_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package biscuit + +import ( + "crypto/ed25519" + "crypto/rand" + "testing" + + "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/stretchr/testify/require" +) + +func TestPublicKeyParse(t *testing.T) { + pub, _, _ := ed25519.GenerateKey(rand.Reader) + key, err := PublicKeyFrom(pub) + require.NoError(t, err) + parsed, err := ParsePublicKey(key.String()) + require.NoError(t, err) + require.Equal(t, key, parsed) + + _, err = ParsePublicKey("ed25519/abcd") + require.ErrorContains(t, err, "invalid public key") + _, err = ParsePublicKey("nokey") + require.ErrorContains(t, err, "expected algorithm/hex") +} + +// Scopes round-trip through the token: keys are interned once per token and +// blocks carry only the keys they introduce; blocks with scopes are v4. +func TestScopesRoundTrip(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + pub1, _, _ := ed25519.GenerateKey(rand.Reader) + pub2, _, _ := ed25519.GenerateKey(rand.Reader) + trust1, err := TrustingPublicKey(pub1) + require.NoError(t, err) + trust2, err := TrustingPublicKey(pub2) + require.NoError(t, err) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "group", IDs: []Term{String("admin")}}}, + Scopes: []Scope{{Kind: ScopePrevious}, trust1}, + }}})) + b1, err := builder.Build() + require.NoError(t, err) + require.EqualValues(t, 4, b1.authority.version) + require.Equal(t, []datalog.PublicKey{trust1.PublicKey}, b1.authority.publicKeys) + + block := b1.CreateBlock() + block.AddScope(Scope{Kind: ScopeAuthority}) + require.NoError(t, block.AddRule(Rule{ + Head: Predicate{Name: "allowed", IDs: []Term{Variable("u")}}, + Body: []Predicate{{Name: "user", IDs: []Term{Variable("u")}}}, + Scopes: []Scope{trust1, trust2}, + })) + b2, err := b1.Append(rand.Reader, block.Build()) + require.NoError(t, err) + // trust1 is already in the token's table, only trust2 is new. + require.Equal(t, []datalog.PublicKey{trust2.PublicKey}, b2.blocks[0].publicKeys) + + deser, err := Unmarshal(mustSerialize(t, b2)) + require.NoError(t, err) + require.Equal(t, publicKeyTable{trust1.PublicKey, trust2.PublicKey}, deser.publicKeys) + require.Equal(t, []Scope{{Kind: ScopeAuthority}}, deser.blocks[0].scopes) + require.Equal(t, []Scope{trust1, trust2}, deser.blocks[0].rules[0].Scopes) + require.Equal(t, []Scope{{Kind: ScopePrevious}, trust1}, deser.authority.checks[0].Queries[0].Scopes) + require.Contains(t, deser.String(), "trusting previous, "+trust1.PublicKey.String()) + require.Contains(t, deser.Code()[0], "trusting authority;") + + _, err = deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) +} + +// A block's rules and checks read the authority block, the authorizer and +// their own block unless a scope says otherwise. +func TestScopesAuthorization(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + + b1, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + + block1 := b1.CreateBlock() + require.NoError(t, block1.AddFact(Fact{Predicate{Name: "block1", IDs: []Term{Integer(1)}}})) + b2, err := b1.Append(rand.Reader, block1.Build()) + require.NoError(t, err) + + authorize := func(t *testing.T, check Check) error { + t.Helper() + block2 := b2.CreateBlock() + require.NoError(t, block2.AddCheck(check)) + b3, err := b2.Append(rand.Reader, block2.Build()) + require.NoError(t, err) + ab, err := b3.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + return ab.Authorize() + } + + query := Rule{Head: Predicate{Name: "query"}, Body: []Predicate{{Name: "block1", IDs: []Term{Variable("x")}}}} + + // Block 1 is not visible from block 2 by default. + require.ErrorContains(t, authorize(t, Check{Queries: []Rule{query}}), "failed to verify block #2 check #0") + + // `trusting previous` makes it visible. + previous := query + previous.Scopes = []Scope{{Kind: ScopePrevious}} + require.NoError(t, authorize(t, Check{Queries: []Rule{previous}})) + + // `trusting authority` alone does not. + authority := query + authority.Scopes = []Scope{{Kind: ScopeAuthority}} + require.ErrorContains(t, authorize(t, Check{Queries: []Rule{authority}}), "failed to verify block #2 check #0") + + // Neither does trusting a key no block was signed with. + pub, _, _ := ed25519.GenerateKey(rand.Reader) + trust, err := TrustingPublicKey(pub) + require.NoError(t, err) + keyed := query + keyed.Scopes = []Scope{trust} + require.ErrorContains(t, authorize(t, Check{Queries: []Rule{keyed}}), "failed to verify block #2 check #0") + + // On the authorizer side `previous` designates no block, as in the + // reference implementation: later blocks are reached through the key of + // a third-party block only. + for _, q := range []Rule{query, previous} { + ab, err := b2.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(Policy{Kind: PolicyKindAllow, Queries: []Rule{q}}) + require.ErrorIs(t, ab.Authorize(), ErrNoMatchingPolicy) + } +} diff --git a/script/generate.sh b/script/generate.sh new file mode 100755 index 00000000..36d7205a --- /dev/null +++ b/script/generate.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Buf tooling expects a fully qualified path to the plugins directory. +BUF="go run github.com/bufbuild/buf/cmd/buf@v1.50.0" + +# Installs protoc plugins required by our buf config. +BUF_PLUGINS_DIR="$(pwd)/.buf-plugins" +mkdir -p "$BUF_PLUGINS_DIR" +GOBIN="$BUF_PLUGINS_DIR" go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5 + +# Generate pb code from the biscuit spec. Pinned to the commit after v3.3 that +# adds OpBinary.TryOr, which the v3.3 samples use. +PATH="${BUF_PLUGINS_DIR}:$PATH" $BUF generate \ + --template buf.gen.yaml \ + --debug \ + https://github.com/eclipse-biscuit/biscuit.git#ref=b3d3fe2 diff --git a/script/install-protoc.sh b/script/install-protoc.sh deleted file mode 100755 index 42319da2..00000000 --- a/script/install-protoc.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -protoc_version="3.12.3" -protoc_shasum="90257aed22e983a6772fb5af259a14d8f78deac0814a7df76a741975ffeea1c0" - -protos_version="1_3_1" -protos_shasum="9584b7ac21de5b31832faf827f898671cdcb034bd557a36ea3e7fc07e6571dcb" - -gobin_version="0.0.14" -gobin_shasum="5bc800e8be7eaefcb86780786c38b75243082685a17ceea3c2235e06b8259151" - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -mkdir -p "${ROOT}/build/bin" - -tmpdir=$(mktemp --directory) -trap "rm -rf ${tmpdir}" EXIT - -# install protobuf compiler -curl -sL https://github.com/google/protobuf/releases/download/v${protoc_version}/protoc-${protoc_version}-linux-x86_64.zip > "${tmpdir}/protoc.zip" -echo "${protoc_shasum} ${tmpdir}/protoc.zip" | shasum -c - || (shasum -a 256 "${tmpdir}/protoc.zip" && exit 1) -unzip -d "${tmpdir}/protoc" "${tmpdir}/protoc.zip" -rm -rf "${ROOT}/build/protoc" -mv "${tmpdir}/protoc" "${ROOT}/build" - -# install googleapis common protos -curl -fSLo ${tmpdir}/common-protos.tar.gz "https://github.com/googleapis/googleapis/archive/common-protos-${protos_version}.tar.gz" -echo "${protos_shasum} ${tmpdir}/common-protos.tar.gz" | shasum -c - || (shasum -a 256 "${tmpdir}/common-protos.tar.gz" && exit 1) -tar xzf ${tmpdir}/common-protos.tar.gz -C "${ROOT}/build/protoc" --strip-components=1 - -gobin_dest="${ROOT}/build/bin/gobin" -curl -sLo "${gobin_dest}" https://github.com/myitcv/gobin/releases/download/v${gobin_version}/linux-amd64 -echo "${gobin_shasum} ${gobin_dest}" | shasum -c - || (shasum -a 256 "${gobin_dest}" && exit 1) -chmod +x "${gobin_dest}" - -cd "${ROOT}" -GOBIN="${ROOT}/build/bin" "${gobin_dest}" -mod=readonly google.golang.org/protobuf/cmd/protoc-gen-go diff --git a/third_party.go b/third_party.go new file mode 100644 index 00000000..00ce87b7 --- /dev/null +++ b/third_party.go @@ -0,0 +1,202 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package biscuit + +import ( + stdcrypto "crypto" + "crypto/rand" + "errors" + "fmt" + "io" + + "github.com/eclipse-biscuit/biscuit-go/v2/internal/crypto" + "github.com/eclipse-biscuit/biscuit-go/v2/pb" + "google.golang.org/protobuf/proto" +) + +// A third-party block is a block signed by a party that does not hold the +// token. The holder sends a ThirdPartyBlockRequest, the third party answers +// with a ThirdPartyBlockContents built from its own key, and the holder +// appends it with Biscuit.AppendThirdPartyBlock. Facts of such a block are +// visible to rules that trust the third party's key (`trusting `). + +// ThirdPartyBlockRequest is what a third party needs to sign a block for a +// token: the signature of the token's last block, which binds the new block +// to that token. +type ThirdPartyBlockRequest struct { + previousSignature []byte +} + +// ThirdPartyRequest creates the request for a third-party block to append to +// this token. +func (b *Biscuit) ThirdPartyRequest() (*ThirdPartyBlockRequest, error) { + if b.container.GetProof().GetNextSecret() == nil { + return nil, errors.New("biscuit: token is sealed") + } + return &ThirdPartyBlockRequest{previousSignature: b.lastSignedBlock().GetSignature()}, nil +} + +func (r *ThirdPartyBlockRequest) Serialize() ([]byte, error) { + return proto.Marshal(&pb.ThirdPartyBlockRequest{PreviousSignature: r.previousSignature}) +} + +func UnmarshalThirdPartyBlockRequest(serialized []byte) (*ThirdPartyBlockRequest, error) { + req := new(pb.ThirdPartyBlockRequest) + if err := proto.Unmarshal(serialized, req); err != nil { + return nil, err + } + // Set only by implementations of the deprecated v0 flow. + if req.LegacyPreviousKey != nil || len(req.LegacyPublicKeys) > 0 { + return nil, errors.New("biscuit: third-party block request uses the legacy format") + } + if len(req.PreviousSignature) == 0 { + return nil, errors.New("biscuit: third-party block request without previous signature") + } + return &ThirdPartyBlockRequest{previousSignature: req.PreviousSignature}, nil +} + +// NewThirdPartyBlockBuilder creates the builder for a third-party block, +// which has its own symbol table and does not know the token's. +func NewThirdPartyBlockBuilder() BlockBuilder { + return NewBlockBuilder(defaultSymbolTable.Clone()) +} + +// CreateBlock builds and signs the block with the third party's key: an +// ed25519.PrivateKey or an *ecdsa.PrivateKey on the P-256 curve. The builder +// must come from NewThirdPartyBlockBuilder. +func (r *ThirdPartyBlockRequest) CreateBlock(key stdcrypto.Signer, builder BlockBuilder) (*ThirdPartyBlockContents, error) { + signer, err := crypto.NewSigner(key) + if err != nil { + return nil, err + } + if bb, ok := builder.(*blockBuilder); !ok || bb.symbolsStart != defaultSymbolTable.Len() || len(bb.publicKeys) > 0 { + return nil, errors.New("biscuit: a third-party block must be built with NewThirdPartyBlockBuilder") + } + + block := builder.Build() + block.version = max(block.version, blockVersion3_2) + protoBlock, err := tokenBlockToProtoBlock(block, nil) + if err != nil { + return nil, err + } + payload, err := proto.Marshal(protoBlock) + if err != nil { + return nil, err + } + + signature, err := signer.Sign(externalSignaturePayload(payload, r.previousSignature, signatureVersionV1)) + if err != nil { + return nil, err + } + + return &ThirdPartyBlockContents{ + payload: payload, + externalSignature: &pb.ExternalSignature{ + Signature: signature, + PublicKey: &pb.PublicKey{ + Algorithm: signer.Algorithm().Enum(), + Key: signer.PublicKey(), + }, + }, + }, nil +} + +// ThirdPartyBlockContents is a block serialized and signed by a third party, +// to append to the token the request came from. +type ThirdPartyBlockContents struct { + payload []byte + externalSignature *pb.ExternalSignature +} + +func (c *ThirdPartyBlockContents) Serialize() ([]byte, error) { + return proto.Marshal(&pb.ThirdPartyBlockContents{Payload: c.payload, ExternalSignature: c.externalSignature}) +} + +func UnmarshalThirdPartyBlockContents(serialized []byte) (*ThirdPartyBlockContents, error) { + contents := new(pb.ThirdPartyBlockContents) + if err := proto.Unmarshal(serialized, contents); err != nil { + return nil, err + } + if contents.ExternalSignature == nil || len(contents.Payload) == 0 { + return nil, errors.New("biscuit: incomplete third-party block contents") + } + return &ThirdPartyBlockContents{payload: contents.Payload, externalSignature: contents.ExternalSignature}, nil +} + +// PublicKey is the key the third party signed with. +func (c *ThirdPartyBlockContents) PublicKey() (PublicKey, error) { + return protoPublicKeyToTokenPublicKey(c.externalSignature.GetPublicKey()) +} + +// AppendThirdPartyBlock appends a block signed by a third party. The block +// must have been made for this token (from its ThirdPartyRequest) and signed +// by expectedKey, an ed25519.PublicKey or an *ecdsa.PublicKey. A nil rng +// uses crypto/rand. +func (b *Biscuit) AppendThirdPartyBlock(rng io.Reader, expectedKey stdcrypto.PublicKey, contents *ThirdPartyBlockContents) (*Biscuit, error) { + if rng == nil { + rng = rand.Reader + } + signer, err := b.nextSigner() + if err != nil { + return nil, fmt.Errorf("biscuit: append failed: %w", err) + } + + expected, err := PublicKeyFrom(expectedKey) + if err != nil { + return nil, err + } + provided, err := contents.PublicKey() + if err != nil { + return nil, err + } + if !expected.Equal(provided) { + return nil, fmt.Errorf("biscuit: third-party block signed by %s, expected %s", provided, expected) + } + + previous := append([]*pb.SignedBlock{b.container.Authority}, b.container.Blocks...) + previousSignature := previous[len(previous)-1].GetSignature() + candidate := &pb.SignedBlock{Block: contents.payload, ExternalSignature: contents.externalSignature, Version: proto.Uint32(signatureVersionV1)} + if err := verifyExternalSignature(candidate, previousSignature); err != nil { + return nil, err + } + + pbBlock := new(pb.Block) + if err := proto.Unmarshal(contents.payload, pbBlock); err != nil { + return nil, err + } + block, err := thirdPartyBlock(pbBlock, contents.externalSignature) + if err != nil { + return nil, err + } + + signedBlock, proof, err := signBlock(signer, signer.Algorithm(), contents.payload, block.version, contents.externalSignature, previous, rng) + if err != nil { + return nil, err + } + + authority := new(Block) + *authority = *b.authority + blocks := make([]*Block, len(b.blocks)+1) + for i, oldBlock := range b.blocks { + blocks[i] = new(Block) + *blocks[i] = *oldBlock + } + blocks[len(b.blocks)] = block + + container := &pb.Biscuit{ + RootKeyId: b.container.RootKeyId, + Authority: b.container.Authority, + Blocks: append(append([]*pb.SignedBlock{}, b.container.Blocks...), signedBlock), + Proof: proof, + } + + // The token's symbol and key tables are not extended by a third-party block. + return &Biscuit{ + authority: authority, + blocks: blocks, + symbols: b.symbols.Clone(), + publicKeys: b.publicKeys, + container: container, + }, nil +} diff --git a/third_party_test.go b/third_party_test.go new file mode 100644 index 00000000..04d8e2e7 --- /dev/null +++ b/third_party_test.go @@ -0,0 +1,195 @@ +// Copyright (c) 2019 Titanous, daeMOn63 and Contributors to the Eclipse Foundation. +// SPDX-License-Identifier: Apache-2.0 + +package biscuit + +import ( + stdcrypto "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/eclipse-biscuit/biscuit-go/v2/datalog" + "github.com/stretchr/testify/require" +) + +// The flow of the spec: the holder issues a request, the third party signs a +// block for it, the holder appends it; rules see its facts only when they +// trust the third party's key. +func TestThirdPartyBlock(t *testing.T) { + ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + edPub, edPriv, _ := ed25519.GenerateKey(rand.Reader) + + for _, tc := range []struct { + name string + private stdcrypto.Signer + public stdcrypto.PublicKey + }{ + {"ed25519", edPriv, edPub}, + {"secp256r1", ecKey, &ecKey.PublicKey}, + } { + t.Run(tc.name, func(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + thirdPartyPublic := tc.public + trust, err := TrustingPublicKey(thirdPartyPublic) + require.NoError(t, err) + + // The authority block requires a fact from the third party. + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{Predicate{Name: "right", IDs: []Term{String("read")}}})) + require.NoError(t, builder.AddAuthorityCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "group", IDs: []Term{String("admin")}}}, + Scopes: []Scope{trust}, + }}})) + token, err := builder.Build() + require.NoError(t, err) + + // Holder side: the request travels serialized. + request, err := token.ThirdPartyRequest() + require.NoError(t, err) + requestBytes, err := request.Serialize() + require.NoError(t, err) + + // Third party side: a block with its own symbols, signed with its key. + request, err = UnmarshalThirdPartyBlockRequest(requestBytes) + require.NoError(t, err) + blockBuilder := NewThirdPartyBlockBuilder() + require.NoError(t, blockBuilder.AddFact(Fact{Predicate{Name: "group", IDs: []Term{String("admin")}}})) + require.NoError(t, blockBuilder.AddCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "right", IDs: []Term{String("read")}}}, + }}})) + contents, err := request.CreateBlock(tc.private, blockBuilder) + require.NoError(t, err) + contentsBytes, err := contents.Serialize() + require.NoError(t, err) + + // Holder side again. + contents, err = UnmarshalThirdPartyBlockContents(contentsBytes) + require.NoError(t, err) + appended, err := token.AppendThirdPartyBlock(nil, thirdPartyPublic, contents) + require.NoError(t, err) + require.Equal(t, token.symbols, appended.symbols, "a third-party block must not extend the token's symbol table") + require.EqualValues(t, 5, appended.blocks[0].version) + require.NotNil(t, appended.blocks[0].externalKey) + + deser, err := Unmarshal(mustSerialize(t, appended)) + require.NoError(t, err) + require.Contains(t, deser.Code()[0], `group("admin")`) + + ab, err := deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + require.NoError(t, ab.Authorize()) + require.Contains(t, ab.PrintWorld(), "// origin: [1]\ngroup(\"admin\");") + + // A policy trusting the key sees the third-party fact; without it, not. + query := Rule{Head: Predicate{Name: "query"}, Body: []Predicate{{Name: "group", IDs: []Term{String("admin")}}}} + ab, err = deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(Policy{Kind: PolicyKindAllow, Queries: []Rule{query}}) + require.ErrorIs(t, ab.Authorize(), ErrNoMatchingPolicy) + + trusted := query + trusted.Scopes = []Scope{trust} + ab, err = deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(Policy{Kind: PolicyKindAllow, Queries: []Rule{trusted}}) + require.NoError(t, ab.Authorize()) + + // The block is bound to the token it was requested for. + otherToken, err := NewBuilder(privateRoot).Build() + require.NoError(t, err) + _, err = otherToken.AppendThirdPartyBlock(nil, thirdPartyPublic, contents) + require.ErrorContains(t, err, "invalid external signature") + + // And to the key the holder expects. + otherPub, _, _ := ed25519.GenerateKey(rand.Reader) + _, err = token.AppendThirdPartyBlock(nil, otherPub, contents) + require.ErrorContains(t, err, "third-party block signed by") + + // A builder made from the token would use the token's symbol table. + _, err = request.CreateBlock(tc.private, token.CreateBlock()) + require.ErrorContains(t, err, "NewThirdPartyBlockBuilder") + + // A tampered payload fails verification. + tampered := *contents + tampered.payload = append([]byte{}, contents.payload...) + tampered.payload[len(tampered.payload)-1] ^= 0xff + _, err = token.AppendThirdPartyBlock(nil, thirdPartyPublic, &tampered) + require.Error(t, err) + }) + } +} + +// A third-party block's public keys and symbols are its own: the same key +// in the token table and in the block table resolves independently. +func TestThirdPartyBlockIsolatedTables(t *testing.T) { + publicRoot, privateRoot, _ := ed25519.GenerateKey(rand.Reader) + pub1, priv1, _ := ed25519.GenerateKey(rand.Reader) + pub2, priv2, _ := ed25519.GenerateKey(rand.Reader) + trust1, _ := TrustingPublicKey(pub1) + trust2, _ := TrustingPublicKey(pub2) + + builder := NewBuilder(privateRoot) + require.NoError(t, builder.AddAuthorityFact(Fact{Predicate{Name: "query", IDs: []Term{Integer(0)}}})) + token, err := builder.Build() + require.NoError(t, err) + + appendThirdParty := func(token *Biscuit, priv ed25519.PrivateKey, pub ed25519.PublicKey, block func(BlockBuilder)) *Biscuit { + request, err := token.ThirdPartyRequest() + require.NoError(t, err) + bb := NewThirdPartyBlockBuilder() + block(bb) + contents, err := request.CreateBlock(priv, bb) + require.NoError(t, err) + next, err := token.AppendThirdPartyBlock(nil, pub, contents) + require.NoError(t, err) + return next + } + + // Block 1 (key 1): query(1), and a rule trusting key 2 that joins with block 2. + token = appendThirdParty(token, priv1, pub1, func(bb BlockBuilder) { + require.NoError(t, bb.AddFact(Fact{Predicate{Name: "query", IDs: []Term{Integer(1)}}})) + require.NoError(t, bb.AddRule(Rule{ + Head: Predicate{Name: "query", IDs: []Term{Integer(1), Integer(2)}}, + Body: []Predicate{{Name: "query", IDs: []Term{Integer(1)}}, {Name: "query", IDs: []Term{Integer(2)}}}, + Scopes: []Scope{trust2}, + })) + }) + // Block 2 (key 2): query(2), with a check trusting key 1. + token = appendThirdParty(token, priv2, pub2, func(bb BlockBuilder) { + require.NoError(t, bb.AddFact(Fact{Predicate{Name: "query", IDs: []Term{Integer(2)}}})) + require.NoError(t, bb.AddCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "query", IDs: []Term{Integer(1)}}}, + Scopes: []Scope{trust1}, + }}})) + }) + // Block 3 (first party): a check trusting both keys. + block3 := token.CreateBlock() + require.NoError(t, block3.AddCheck(Check{Queries: []Rule{{ + Head: Predicate{Name: "query"}, + Body: []Predicate{{Name: "query", IDs: []Term{Integer(1), Integer(2)}}}, + Scopes: []Scope{trust1, trust2}, + }}})) + token, err = token.Append(rand.Reader, block3.Build()) + require.NoError(t, err) + require.Equal(t, publicKeyTable{trust1.PublicKey, trust2.PublicKey}, token.publicKeys) + + deser, err := Unmarshal(mustSerialize(t, token)) + require.NoError(t, err) + require.Equal(t, []datalog.PublicKey{trust2.PublicKey}, deser.blocks[0].publicKeys) + require.Equal(t, []datalog.PublicKey{trust1.PublicKey}, deser.blocks[1].publicKeys) + require.Equal(t, []datalog.PublicKey{trust1.PublicKey, trust2.PublicKey}, deser.blocks[2].publicKeys) + + ab, err := deser.AuthorizerFrom(RootKey(publicRoot)) + require.NoError(t, err) + ab.AddPolicy(DefaultAllowPolicy) + require.NoError(t, ab.Authorize()) + require.Contains(t, ab.PrintWorld(), "// origin: [1, 2]\nquery(1, 2);") +} diff --git a/types.go b/types.go index a9e4e57c..7ff84ffc 100644 --- a/types.go +++ b/types.go @@ -6,6 +6,7 @@ package biscuit import ( "encoding/hex" "fmt" + "slices" "sort" "strings" "time" @@ -13,8 +14,149 @@ import ( "github.com/eclipse-biscuit/biscuit-go/v2/datalog" ) +// Block versions this library accepts: 3 is datalog v3.0, 4 is datalog v3.1, +// 5 is datalog v3.2, 6 is datalog v3.3. const MinSchemaVersion uint32 = 3 -const MaxSchemaVersion uint32 = 3 +const MaxSchemaVersion uint32 = 6 + +// Block versions, by the datalog release that introduced them. +const ( + blockVersion3_0 uint32 = 3 + blockVersion3_1 uint32 = 4 + // blockVersion3_2 is the minimum for a third-party block. + blockVersion3_2 uint32 = 5 + blockVersion3_3 uint32 = 6 +) + +// schemaVersion is the lowest block version able to carry the given content. +func schemaVersion(facts datalog.FactSet, scopes []datalog.Scope, rules []datalog.Rule, checks []datalog.Check) uint32 { + for _, f := range facts { + if containsV33Term(f.Predicate) { + return blockVersion3_3 + } + } + for _, c := range checks { + if c.Kind == datalog.CheckKindReject { + return blockVersion3_3 + } + for _, q := range c.Queries { + if containsV33Rule(q) { + return blockVersion3_3 + } + } + } + for _, r := range rules { + if containsV33Rule(r) { + return blockVersion3_3 + } + } + if hasScopes(scopes, rules, checks) { + return blockVersion3_1 + } + for _, c := range checks { + if c.Kind != datalog.CheckKindOne { + return blockVersion3_1 + } + for _, q := range c.Queries { + if containsV31Op(q.Expressions) { + return blockVersion3_1 + } + } + } + for _, r := range rules { + if containsV31Op(r.Expressions) { + return blockVersion3_1 + } + } + return blockVersion3_0 +} + +// containsV33Rule reports whether a rule uses a datalog v3.3 feature in its +// predicates or expressions. +func containsV33Rule(r datalog.Rule) bool { + if containsV33Term(r.Head) { + return true + } + for _, p := range r.Body { + if containsV33Term(p) { + return true + } + } + return containsV33Op(r.Expressions) +} + +// containsV33Term reports whether a predicate holds a term type introduced +// in datalog v3.3: null. +func containsV33Term(p datalog.Predicate) bool { + for _, t := range p.Terms { + if isV33Term(t) { + return true + } + } + return false +} + +func isV33Term(t datalog.Term) bool { + switch t := t.(type) { + case datalog.Null, datalog.Array, datalog.Map: + return true + case datalog.Set: + for _, e := range t { + if isV33Term(e) { + return true + } + } + } + return false +} + +// containsV33Op reports whether an expression uses an operator or a term +// introduced in datalog v3.3: the heterogeneous == and !=, null, closures. +func containsV33Op(expressions []datalog.Expression) bool { + for _, e := range expressions { + for _, op := range e { + switch op := op.(type) { + case datalog.Value: + if isV33Term(op.ID) { + return true + } + case datalog.Closure: + return true + case datalog.UnaryOp: + switch op.UnaryOpFunc.Type() { + case datalog.UnaryTypeOf, datalog.UnaryFfi: + return true + } + case datalog.BinaryOp: + switch op.BinaryOpFunc.Type() { + case datalog.BinaryHeterogeneousEqual, datalog.BinaryHeterogeneousNotEqual, + datalog.BinaryLazyAnd, datalog.BinaryLazyOr, datalog.BinaryAll, datalog.BinaryAny, datalog.BinaryGet, + datalog.BinaryFfi, datalog.BinaryTryOr: + return true + } + } + } + } + return false +} + +// containsV31Op reports whether an expression uses an operator introduced +// in datalog v3.1: bitwise operators and !==. +func containsV31Op(expressions []datalog.Expression) bool { + for _, e := range expressions { + for _, op := range e { + b, ok := op.(datalog.BinaryOp) + if !ok { + continue + } + switch b.BinaryOpFunc.Type() { + case datalog.BinaryBitwiseAnd, datalog.BinaryBitwiseOr, datalog.BinaryBitwiseXor, datalog.BinaryNotEqual: + return true + } + } + } + return false +} // defaultSymbolTable predefines some symbols available in every implementation, to avoid // transmitting them with every token @@ -25,8 +167,29 @@ type Block struct { facts *datalog.FactSet rules []datalog.Rule checks []datalog.Check - context string - version uint32 + // scopes is the block-level `trusting` clause, the default scope of + // the rules and checks of the block. + scopes []datalog.Scope + // publicKeys are the keys the scopes of this block add to the key + // table of the token. + publicKeys []datalog.PublicKey + // externalKey is the key a third party signed the block with; nil for + // a first-party block. A third-party block has its own symbol and key + // tables, which the token tables do not include. + externalKey *datalog.PublicKey + context string + version uint32 +} + +// symbolTable is the table the content of the block refers to: the token's +// for a first-party block, the defaults plus its own for a third-party block. +func (b *Block) symbolTable(token *datalog.SymbolTable) *datalog.SymbolTable { + if b.externalKey == nil { + return token + } + symbols := defaultSymbolTable.Clone() + symbols.Extend(b.symbols) + return symbols } func (b *Block) Code(symbols *datalog.SymbolTable) string { @@ -47,11 +210,17 @@ func (b *Block) Code(symbols *datalog.SymbolTable) string { checks[i] = debug.Check(c) } + var scopes string + if len(b.scopes) > 0 { + scopes = strings.TrimPrefix(datalog.ScopesString(b.scopes), " ") + ";\n" + } + return fmt.Sprintf(`Block { - %v + %s%v %s %s }`, + scopes, strings.Join(facts, ";\n"), strings.Join(rules, ";\n"), strings.Join(checks, ";\n"), @@ -109,6 +278,7 @@ type ParsedBlock struct { Facts FactSet Rules []Rule Checks []Check + Scopes []Scope } type ParsedAuthorizer struct { @@ -171,6 +341,34 @@ func fromDatalogID(symbols *datalog.SymbolTable, id datalog.Term) (Term, error) a = Bytes(id.(datalog.Bytes)) case datalog.TermTypeBool: a = Bool(id.(datalog.Bool)) + case datalog.TermTypeNull: + a = Null{} + case datalog.TermTypeArray: + dlArray := id.(datalog.Array) + array := make(Array, len(dlArray)) + for i, e := range dlArray { + elt, err := fromDatalogID(symbols, e) + if err != nil { + return nil, err + } + array[i] = elt + } + a = array + case datalog.TermTypeMap: + dlMap := id.(datalog.Map) + m := make(Map, len(dlMap)) + for i, e := range dlMap { + key, err := fromDatalogID(symbols, e.Key) + if err != nil { + return nil, err + } + value, err := fromDatalogID(symbols, e.Value) + if err != nil { + return nil, err + } + m[i] = MapEntry{Key: key, Value: value} + } + a = m case datalog.TermTypeSet: setIDs := id.(datalog.Set) set := make(Set, 0, len(setIDs)) @@ -193,6 +391,8 @@ type Rule struct { Head Predicate Body []Predicate Expressions []Expression + // Scopes is the `trusting` clause; empty means the scopes of the block. + Scopes []Scope } func (r Rule) convert(symbols *datalog.SymbolTable) datalog.Rule { @@ -209,6 +409,7 @@ func (r Rule) convert(symbols *datalog.SymbolTable) datalog.Rule { Head: r.Head.convert(symbols), Body: dlBody, Expressions: dlExpressions, + Scopes: slices.Clone(r.Scopes), } } @@ -240,6 +441,7 @@ func fromDatalogRule(symbols *datalog.SymbolTable, dlRule datalog.Rule) (*Rule, Head: *head, Body: body, Expressions: expressions, + Scopes: slices.Clone(dlRule.Scopes), }, nil } @@ -275,6 +477,12 @@ func fromDatalogExpression(symbols *datalog.SymbolTable, dlExpr datalog.Expressi return nil, fmt.Errorf("failed to convert datalog binary expression: %w", err) } expr[i] = b + case datalog.OpTypeClosure: + c, err := fromDatalogClosure(symbols, dlOP.(datalog.Closure)) + if err != nil { + return nil, err + } + expr[i] = c default: return nil, fmt.Errorf("unsupported datalog expression type: %v", dlOP.Type()) } @@ -293,8 +501,41 @@ const ( OpTypeValue OpType = iota OpTypeUnary OpTypeBinary + // OpTypeClosure is datalog v3.3; using it makes the block version 6. + OpTypeClosure ) +// Closure is an expression evaluated by the operator that consumes it, with +// Params bound by that operator: the right side of && and || (BinaryLazyAnd, +// BinaryLazyOr), the predicate of .all() and .any() (BinaryAll, BinaryAny), +// or the receiver of .try_or() (BinaryTryOr). +type Closure struct { + Params []Variable + Body Expression +} + +func (Closure) Type() OpType { + return OpTypeClosure +} +func (c Closure) convert(symbols *datalog.SymbolTable) datalog.Op { + params := make([]datalog.Variable, len(c.Params)) + for i, p := range c.Params { + params[i] = p.convert(symbols).(datalog.Variable) + } + return datalog.Closure{Params: params, Body: c.Body.convert(symbols)} +} +func fromDatalogClosure(symbols *datalog.SymbolTable, dlClosure datalog.Closure) (Op, error) { + params := make([]Variable, len(dlClosure.Params)) + for i, p := range dlClosure.Params { + params[i] = Variable(symbols.Var(p)) + } + body, err := fromDatalogExpression(symbols, dlClosure.Body) + if err != nil { + return nil, fmt.Errorf("failed to convert datalog closure: %w", err) + } + return Closure{Params: params, Body: body}, nil +} + type Value struct { Term Term } @@ -322,6 +563,8 @@ const ( UnaryNegate UnaryParens UnaryLength + // UnaryTypeOf is datalog v3.3; using it makes the block version 6. + UnaryTypeOf ) func (UnaryOp) Type() OpType { @@ -335,6 +578,8 @@ func (op UnaryOp) convert(symbols *datalog.SymbolTable) datalog.Op { return datalog.UnaryOp{UnaryOpFunc: datalog.Parens{}} case UnaryLength: return datalog.UnaryOp{UnaryOpFunc: datalog.Length{}} + case UnaryTypeOf: + return datalog.UnaryOp{UnaryOpFunc: datalog.TypeOf{}} default: panic(fmt.Sprintf("biscuit: cannot convert invalid unary op type: %v", op)) } @@ -348,11 +593,43 @@ func fromDatalogUnaryOp(symbols *datalog.SymbolTable, dlUnary datalog.UnaryOp) ( return UnaryParens, nil case datalog.UnaryLength: return UnaryLength, nil + case datalog.UnaryTypeOf: + return UnaryTypeOf, nil + case datalog.UnaryFfi: + return ExternUnary{Name: symbols.Str(dlUnary.UnaryOpFunc.(datalog.Ffi).Name)}, nil default: return UnaryUndefined, fmt.Errorf("unsupported datalog unary op: %v", dlUnary.UnaryOpFunc.Type()) } } +// ExternUnary is the datalog v3.3 call `$value.extern::name()` of a function +// registered on the authorizer with WithExternFuncs; using it makes the block +// version 6. +type ExternUnary struct { + Name string +} + +func (ExternUnary) Type() OpType { + return OpTypeUnary +} +func (op ExternUnary) convert(symbols *datalog.SymbolTable) datalog.Op { + return datalog.UnaryOp{UnaryOpFunc: datalog.Ffi{Name: symbols.Insert(op.Name)}} +} + +// ExternBinary is the datalog v3.3 call `$left.extern::name($right)` of a +// function registered on the authorizer with WithExternFuncs; using it makes +// the block version 6. +type ExternBinary struct { + Name string +} + +func (ExternBinary) Type() OpType { + return OpTypeBinary +} +func (op ExternBinary) convert(symbols *datalog.SymbolTable) datalog.Op { + return datalog.BinaryOp{BinaryOpFunc: datalog.FfiBinary{Name: symbols.Insert(op.Name)}} +} + type binaryOpType byte type BinaryOp binaryOpType @@ -376,6 +653,22 @@ const ( BinaryOr BinaryIntersection BinaryUnion + // Datalog v3.1 operators; using one makes the block version 4. + BinaryBitwiseAnd + BinaryBitwiseOr + BinaryBitwiseXor + BinaryNotEqual + // Datalog v3.3 operators; using one makes the block version 6. + BinaryHeterogeneousEqual + BinaryHeterogeneousNotEqual + BinaryLazyAnd + BinaryLazyOr + BinaryAll + BinaryAny + BinaryGet + // BinaryTryOr is datalog v3.3; its receiver is a Closure without + // parameters and its right operand the fallback value. + BinaryTryOr ) func (BinaryOp) Type() OpType { @@ -417,6 +710,30 @@ func (op BinaryOp) convert(symbols *datalog.SymbolTable) datalog.Op { return datalog.BinaryOp{BinaryOpFunc: datalog.Intersection{}} case BinaryUnion: return datalog.BinaryOp{BinaryOpFunc: datalog.Union{}} + case BinaryBitwiseAnd: + return datalog.BinaryOp{BinaryOpFunc: datalog.BitwiseAnd{}} + case BinaryBitwiseOr: + return datalog.BinaryOp{BinaryOpFunc: datalog.BitwiseOr{}} + case BinaryBitwiseXor: + return datalog.BinaryOp{BinaryOpFunc: datalog.BitwiseXor{}} + case BinaryNotEqual: + return datalog.BinaryOp{BinaryOpFunc: datalog.NotEqual{}} + case BinaryHeterogeneousEqual: + return datalog.BinaryOp{BinaryOpFunc: datalog.HeterogeneousEqual{}} + case BinaryHeterogeneousNotEqual: + return datalog.BinaryOp{BinaryOpFunc: datalog.HeterogeneousNotEqual{}} + case BinaryLazyAnd: + return datalog.BinaryOp{BinaryOpFunc: datalog.LazyAnd{}} + case BinaryLazyOr: + return datalog.BinaryOp{BinaryOpFunc: datalog.LazyOr{}} + case BinaryAll: + return datalog.BinaryOp{BinaryOpFunc: datalog.All{}} + case BinaryAny: + return datalog.BinaryOp{BinaryOpFunc: datalog.Any{}} + case BinaryGet: + return datalog.BinaryOp{BinaryOpFunc: datalog.Get{}} + case BinaryTryOr: + return datalog.BinaryOp{BinaryOpFunc: datalog.TryOr{}} default: panic(fmt.Sprintf("biscuit: cannot convert invalid binary op type: %v", op)) } @@ -458,13 +775,77 @@ func fromDatalogBinaryOp(symbols *datalog.SymbolTable, dbBinary datalog.BinaryOp return BinaryIntersection, nil case datalog.BinaryUnion: return BinaryUnion, nil + case datalog.BinaryBitwiseAnd: + return BinaryBitwiseAnd, nil + case datalog.BinaryBitwiseOr: + return BinaryBitwiseOr, nil + case datalog.BinaryBitwiseXor: + return BinaryBitwiseXor, nil + case datalog.BinaryNotEqual: + return BinaryNotEqual, nil + case datalog.BinaryHeterogeneousEqual: + return BinaryHeterogeneousEqual, nil + case datalog.BinaryHeterogeneousNotEqual: + return BinaryHeterogeneousNotEqual, nil + case datalog.BinaryLazyAnd: + return BinaryLazyAnd, nil + case datalog.BinaryLazyOr: + return BinaryLazyOr, nil + case datalog.BinaryAll: + return BinaryAll, nil + case datalog.BinaryAny: + return BinaryAny, nil + case datalog.BinaryGet: + return BinaryGet, nil + case datalog.BinaryTryOr: + return BinaryTryOr, nil + case datalog.BinaryFfi: + return ExternBinary{Name: symbols.Str(dbBinary.BinaryOpFunc.(datalog.FfiBinary).Name)}, nil default: return BinaryUndefined, fmt.Errorf("unsupported datalog binary op: %v", dbBinary.BinaryOpFunc.Type()) } } +// CheckKind selects how the queries of a Check are evaluated. +type CheckKind byte + +const ( + // CheckKindOne (`check if`) passes when one query has at least one match. + CheckKindOne CheckKind = iota + // CheckKindAll (`check all`) passes when one query has at least one match + // and every match of that query satisfies its expressions. Requires a + // datalog v3.1 block (version 4). + CheckKindAll + // CheckKindReject (`reject if`) passes when no query has a match. + // Requires a datalog v3.3 block (version 6). + CheckKindReject +) + +func (k CheckKind) convert() datalog.CheckKind { + switch k { + case CheckKindAll: + return datalog.CheckKindAll + case CheckKindReject: + return datalog.CheckKindReject + default: + return datalog.CheckKindOne + } +} + +func fromDatalogCheckKind(k datalog.CheckKind) CheckKind { + switch k { + case datalog.CheckKindAll: + return CheckKindAll + case datalog.CheckKindReject: + return CheckKindReject + default: + return CheckKindOne + } +} + type Check struct { Queries []Rule + Kind CheckKind } func (c Check) convert(symbols *datalog.SymbolTable) datalog.Check { @@ -475,6 +856,7 @@ func (c Check) convert(symbols *datalog.SymbolTable) datalog.Check { return datalog.Check{ Queries: queries, + Kind: c.Kind.convert(), } } @@ -490,6 +872,7 @@ func fromDatalogCheck(symbols *datalog.SymbolTable, dlCheck datalog.Check) (*Che return &Check{ Queries: queries, + Kind: fromDatalogCheckKind(dlCheck.Kind), }, nil } @@ -528,6 +911,11 @@ const ( TermTypeBytes TermTypeBool TermTypeSet + // TermTypeNull, TermTypeArray and TermTypeMap are datalog v3.3; using + // them makes the block version 6. + TermTypeNull + TermTypeArray + TermTypeMap ) type Term interface { @@ -584,6 +972,15 @@ func (b Bool) convert(symbols *datalog.SymbolTable) datalog.Term { } func (b Bool) String() string { return fmt.Sprintf("%t", b) } +// Null is the absence of a value (datalog v3.3). +type Null struct{} + +func (Null) Type() TermType { return TermTypeNull } +func (Null) convert(symbols *datalog.SymbolTable) datalog.Term { + return datalog.Null{} +} +func (Null) String() string { return "null" } + type Set []Term func (a Set) Type() TermType { return TermTypeSet } @@ -599,10 +996,58 @@ func (a Set) String() string { for _, e := range a { elts = append(elts, e.String()) } + if len(elts) == 0 { + return "{,}" + } sort.Strings(elts) + return fmt.Sprintf("{%s}", strings.Join(elts, ", ")) +} + +// Array is an ordered sequence of terms of any type (datalog v3.3). +type Array []Term + +func (a Array) Type() TermType { return TermTypeArray } +func (a Array) convert(symbols *datalog.SymbolTable) datalog.Term { + array := make(datalog.Array, len(a)) + for i, e := range a { + array[i] = e.convert(symbols) + } + return array +} +func (a Array) String() string { + elts := make([]string, len(a)) + for i, e := range a { + elts[i] = e.String() + } return fmt.Sprintf("[%s]", strings.Join(elts, ", ")) } +// MapEntry is one key/value pair of a Map; the key is an Integer or a String. +type MapEntry struct { + Key Term + Value Term +} + +// Map associates Integer or String keys with terms of any type (datalog +// v3.3). Entry order does not matter; a key given twice keeps its last value. +type Map []MapEntry + +func (m Map) Type() TermType { return TermTypeMap } +func (m Map) convert(symbols *datalog.SymbolTable) datalog.Term { + entries := make([]datalog.MapEntry, len(m)) + for i, e := range m { + entries[i] = datalog.MapEntry{Key: e.Key.convert(symbols), Value: e.Value.convert(symbols)} + } + return datalog.NewMap(entries...) +} +func (m Map) String() string { + entries := make([]string, len(m)) + for i, e := range m { + entries[i] = fmt.Sprintf("%s: %s", e.Key, e.Value) + } + return fmt.Sprintf("{%s}", strings.Join(entries, ", ")) +} + type PolicyKind byte const ( diff --git a/website_test.go b/website_test.go index 069a97fa..9810b21a 100644 --- a/website_test.go +++ b/website_test.go @@ -52,7 +52,7 @@ func CreateToken(root *ed25519.PrivateKey) (*biscuit.Biscuit, error) { } func Authorize(token *biscuit.Biscuit, root *ed25519.PublicKey) error { - authorizer, err := token.Authorizer(*root) + authorizer, err := token.AuthorizerFrom(biscuit.RootKey(*root)) if err != nil { return fmt.Errorf("failed to create verifier: %v", err) } @@ -86,7 +86,7 @@ func Attenuate(serializedToken []byte, root *ed25519.PublicKey) ([]byte, error) blockBuilder := token.CreateBlock() - check, err := parser.FromStringCheck(`check if resource($file), operation($permission), ["read"].contains($permission)`) + check, err := parser.FromStringCheck(`check if resource($file), operation($permission), {"read"}.contains($permission)`) if err != nil { return nil, fmt.Errorf("failed to parse check: %v", err) }