summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/caveats/eval.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/caveats/eval.go')
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/caveats/eval.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/caveats/eval.go b/vendor/github.com/authzed/spicedb/pkg/caveats/eval.go
new file mode 100644
index 0000000..cef9a4b
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/caveats/eval.go
@@ -0,0 +1,156 @@
+package caveats
+
+import (
+ "fmt"
+
+ "google.golang.org/protobuf/types/known/structpb"
+
+ "github.com/authzed/cel-go/cel"
+ "github.com/authzed/cel-go/common/types"
+ "github.com/authzed/cel-go/common/types/ref"
+)
+
+// EvaluationConfig is configuration given to an EvaluateCaveatWithConfig call.
+type EvaluationConfig struct {
+ // MaxCost is the max cost of the caveat to be executed.
+ MaxCost uint64
+}
+
+// CaveatResult holds the result of evaluating a caveat.
+type CaveatResult struct {
+ val ref.Val
+ details *cel.EvalDetails
+ parentCaveat *CompiledCaveat
+ contextValues map[string]any
+ missingVarNames []string
+ isPartial bool
+}
+
+// Value returns the computed value for the result.
+func (cr CaveatResult) Value() bool {
+ if cr.isPartial {
+ return false
+ }
+
+ return cr.val.Value().(bool)
+}
+
+// IsPartial returns true if the caveat was only partially evaluated.
+func (cr CaveatResult) IsPartial() bool {
+ return cr.isPartial
+}
+
+// PartialValue returns the partially evaluated caveat. Only applies if IsPartial is true.
+func (cr CaveatResult) PartialValue() (*CompiledCaveat, error) {
+ if !cr.isPartial {
+ return nil, fmt.Errorf("result is fully evaluated")
+ }
+
+ ast, err := cr.parentCaveat.celEnv.ResidualAst(cr.parentCaveat.ast, cr.details)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CompiledCaveat{cr.parentCaveat.celEnv, ast, cr.parentCaveat.name}, nil
+}
+
+// ContextValues returns the context values used when computing this result.
+func (cr CaveatResult) ContextValues() map[string]any {
+ return cr.contextValues
+}
+
+// ContextStruct returns the context values used when computing this result as
+// a structpb.
+func (cr CaveatResult) ContextStruct() (*structpb.Struct, error) {
+ return ConvertContextToStruct(cr.contextValues)
+}
+
+// ExpressionString returns the human-readable expression string for the evaluated expression.
+func (cr CaveatResult) ExpressionString() (string, error) {
+ return cr.parentCaveat.ExprString()
+}
+
+// ParentCaveat returns the caveat that was evaluated to produce this result.
+func (cr CaveatResult) ParentCaveat() *CompiledCaveat {
+ return cr.parentCaveat
+}
+
+// MissingVarNames returns the name(s) of the missing variables.
+func (cr CaveatResult) MissingVarNames() ([]string, error) {
+ if !cr.isPartial {
+ return nil, fmt.Errorf("result is fully evaluated")
+ }
+
+ return cr.missingVarNames, nil
+}
+
+// EvaluateCaveat evaluates the compiled caveat with the specified values, and returns
+// the result or an error.
+func EvaluateCaveat(caveat *CompiledCaveat, contextValues map[string]any) (*CaveatResult, error) {
+ return EvaluateCaveatWithConfig(caveat, contextValues, nil)
+}
+
+// EvaluateCaveatWithConfig evaluates the compiled caveat with the specified values, and returns
+// the result or an error.
+func EvaluateCaveatWithConfig(caveat *CompiledCaveat, contextValues map[string]any, config *EvaluationConfig) (*CaveatResult, error) {
+ env := caveat.celEnv
+ celopts := make([]cel.ProgramOption, 0, 3)
+
+ // Option: enables partial evaluation and state tracking for partial evaluation.
+ celopts = append(celopts, cel.EvalOptions(cel.OptTrackState))
+ celopts = append(celopts, cel.EvalOptions(cel.OptPartialEval))
+
+ // Option: Cost limit on the evaluation.
+ if config != nil && config.MaxCost > 0 {
+ celopts = append(celopts, cel.CostLimit(config.MaxCost))
+ }
+
+ prg, err := env.Program(caveat.ast, celopts...)
+ if err != nil {
+ return nil, err
+ }
+
+ // Mark any unspecified variables as unknown, to ensure that partial application
+ // will result in producing a type of Unknown.
+ activation, err := env.PartialVars(contextValues)
+ if err != nil {
+ return nil, err
+ }
+
+ val, details, err := prg.Eval(activation)
+ if err != nil {
+ return nil, EvaluationError{err}
+ }
+
+ // If the value produced has Unknown type, then it means required context was missing.
+ if types.IsUnknown(val) {
+ unknownVal := val.(*types.Unknown)
+ missingVarNames := make([]string, 0, len(unknownVal.IDs()))
+ for _, id := range unknownVal.IDs() {
+ trails, ok := unknownVal.GetAttributeTrails(id)
+ if ok {
+ for _, attributeTrail := range trails {
+ missingVarNames = append(missingVarNames, attributeTrail.String())
+ }
+ }
+ }
+
+ return &CaveatResult{
+ val: val,
+ details: details,
+ parentCaveat: caveat,
+ contextValues: contextValues,
+ missingVarNames: missingVarNames,
+ isPartial: true,
+ }, nil
+ }
+
+ return &CaveatResult{
+ val: val,
+ details: details,
+ parentCaveat: caveat,
+ contextValues: contextValues,
+ missingVarNames: nil,
+ isPartial: false,
+ }, nil
+}