github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/caveats/eval.go (about) 1 package caveats 2 3 import ( 4 "fmt" 5 6 "google.golang.org/protobuf/types/known/structpb" 7 8 "github.com/authzed/cel-go/cel" 9 "github.com/authzed/cel-go/common/types" 10 "github.com/authzed/cel-go/common/types/ref" 11 ) 12 13 // EvaluationConfig is configuration given to an EvaluateCaveatWithConfig call. 14 type EvaluationConfig struct { 15 // MaxCost is the max cost of the caveat to be executed. 16 MaxCost uint64 17 } 18 19 // CaveatResult holds the result of evaluating a caveat. 20 type CaveatResult struct { 21 val ref.Val 22 details *cel.EvalDetails 23 parentCaveat *CompiledCaveat 24 contextValues map[string]any 25 missingVarNames []string 26 isPartial bool 27 } 28 29 // Value returns the computed value for the result. 30 func (cr CaveatResult) Value() bool { 31 if cr.isPartial { 32 return false 33 } 34 35 return cr.val.Value().(bool) 36 } 37 38 // IsPartial returns true if the caveat was only partially evaluated. 39 func (cr CaveatResult) IsPartial() bool { 40 return cr.isPartial 41 } 42 43 // PartialValue returns the partially evaluated caveat. Only applies if IsPartial is true. 44 func (cr CaveatResult) PartialValue() (*CompiledCaveat, error) { 45 if !cr.isPartial { 46 return nil, fmt.Errorf("result is fully evaluated") 47 } 48 49 ast, err := cr.parentCaveat.celEnv.ResidualAst(cr.parentCaveat.ast, cr.details) 50 if err != nil { 51 return nil, err 52 } 53 54 return &CompiledCaveat{cr.parentCaveat.celEnv, ast, cr.parentCaveat.name}, nil 55 } 56 57 // ContextValues returns the context values used when computing this result. 58 func (cr CaveatResult) ContextValues() map[string]any { 59 return cr.contextValues 60 } 61 62 // ContextStruct returns the context values used when computing this result as 63 // a structpb. 64 func (cr CaveatResult) ContextStruct() (*structpb.Struct, error) { 65 return ConvertContextToStruct(cr.contextValues) 66 } 67 68 // ExpressionString returns the human-readable expression string for the evaluated expression. 69 func (cr CaveatResult) ExpressionString() (string, error) { 70 return cr.parentCaveat.ExprString() 71 } 72 73 // MissingVarNames returns the name(s) of the missing variables. 74 func (cr CaveatResult) MissingVarNames() ([]string, error) { 75 if !cr.isPartial { 76 return nil, fmt.Errorf("result is fully evaluated") 77 } 78 79 return cr.missingVarNames, nil 80 } 81 82 // EvaluateCaveat evaluates the compiled caveat with the specified values, and returns 83 // the result or an error. 84 func EvaluateCaveat(caveat *CompiledCaveat, contextValues map[string]any) (*CaveatResult, error) { 85 return EvaluateCaveatWithConfig(caveat, contextValues, nil) 86 } 87 88 // EvaluateCaveatWithConfig evaluates the compiled caveat with the specified values, and returns 89 // the result or an error. 90 func EvaluateCaveatWithConfig(caveat *CompiledCaveat, contextValues map[string]any, config *EvaluationConfig) (*CaveatResult, error) { 91 env := caveat.celEnv 92 celopts := make([]cel.ProgramOption, 0, 3) 93 94 // Option: enables partial evaluation and state tracking for partial evaluation. 95 celopts = append(celopts, cel.EvalOptions(cel.OptTrackState)) 96 celopts = append(celopts, cel.EvalOptions(cel.OptPartialEval)) 97 98 // Option: Cost limit on the evaluation. 99 if config != nil && config.MaxCost > 0 { 100 celopts = append(celopts, cel.CostLimit(config.MaxCost)) 101 } 102 103 prg, err := env.Program(caveat.ast, celopts...) 104 if err != nil { 105 return nil, err 106 } 107 108 // Mark any unspecified variables as unknown, to ensure that partial application 109 // will result in producing a type of Unknown. 110 activation, err := env.PartialVars(contextValues) 111 if err != nil { 112 return nil, err 113 } 114 115 val, details, err := prg.Eval(activation) 116 if err != nil { 117 return nil, EvaluationErr{err} 118 } 119 120 // If the value produced has Unknown type, then it means required context was missing. 121 if types.IsUnknown(val) { 122 unknownVal := val.(*types.Unknown) 123 missingVarNames := make([]string, 0, len(unknownVal.IDs())) 124 for _, id := range unknownVal.IDs() { 125 trails, ok := unknownVal.GetAttributeTrails(id) 126 if ok { 127 for _, attributeTrail := range trails { 128 missingVarNames = append(missingVarNames, attributeTrail.String()) 129 } 130 } 131 } 132 133 return &CaveatResult{ 134 val: val, 135 details: details, 136 parentCaveat: caveat, 137 contextValues: contextValues, 138 missingVarNames: missingVarNames, 139 isPartial: true, 140 }, nil 141 } 142 143 return &CaveatResult{ 144 val: val, 145 details: details, 146 parentCaveat: caveat, 147 contextValues: contextValues, 148 missingVarNames: nil, 149 isPartial: false, 150 }, nil 151 }