diff options
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/caveats/eval.go')
| -rw-r--r-- | vendor/github.com/authzed/spicedb/pkg/caveats/eval.go | 156 |
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 +} |
