github.com/openfga/openfga@v1.5.4-rc1/internal/condition/eval/eval_test.go (about) 1 package eval 2 3 import ( 4 "context" 5 "testing" 6 7 openfgav1 "github.com/openfga/api/proto/openfga/v1" 8 parser "github.com/openfga/language/pkg/go/transformer" 9 "github.com/stretchr/testify/require" 10 "google.golang.org/protobuf/types/known/structpb" 11 12 "github.com/openfga/openfga/internal/condition" 13 "github.com/openfga/openfga/pkg/testutils" 14 "github.com/openfga/openfga/pkg/tuple" 15 "github.com/openfga/openfga/pkg/typesystem" 16 ) 17 18 func TestEvaluateTupleCondition(t *testing.T) { 19 tests := []struct { 20 name string 21 tupleKey *openfgav1.TupleKey 22 model *openfgav1.AuthorizationModel 23 context map[string]interface{} 24 conditionMet bool 25 expectedErr string 26 }{ 27 { 28 name: "condition_in_tuple_key_not_found_in_model", 29 tupleKey: tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "unknown", nil), 30 model: parser.MustTransformDSLToProto(`model 31 schema 1.1 32 type user 33 34 type document 35 relations 36 define can_view: [user with correct_ip] 37 38 condition correct_ip(ip: string) { 39 ip == "192.168.0.1" 40 }`), 41 context: map[string]interface{}{"ip": "192.168.0.1"}, 42 conditionMet: false, 43 expectedErr: "'unknown' - condition was not found", 44 }, 45 { 46 name: "condition_not_met", 47 tupleKey: tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "correct_ip", nil), 48 model: parser.MustTransformDSLToProto(`model 49 schema 1.1 50 type user 51 52 type document 53 relations 54 define can_view: [user with correct_ip] 55 56 condition correct_ip(ip: string) { 57 ip == "192.168.0.1" 58 }`), 59 context: map[string]interface{}{"ip": "not_met"}, 60 conditionMet: false, 61 expectedErr: "", 62 }, 63 { 64 name: "condition_met", 65 tupleKey: tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "correct_ip", nil), 66 model: parser.MustTransformDSLToProto(`model 67 schema 1.1 68 type user 69 70 type document 71 relations 72 define can_view: [user with correct_ip] 73 74 condition correct_ip(ip: string) { 75 ip == "192.168.0.1" 76 }`), 77 context: map[string]interface{}{"ip": "192.168.0.1"}, 78 conditionMet: true, 79 expectedErr: "", 80 }, 81 } 82 for _, test := range tests { 83 t.Run(test.name, func(t *testing.T) { 84 ctx := context.Background() 85 86 ts, err := typesystem.NewAndValidate(context.Background(), test.model) 87 require.NoError(t, err) 88 89 contextStruct, err := structpb.NewStruct(test.context) 90 require.NoError(t, err) 91 92 condEvalResult, err := EvaluateTupleCondition(ctx, test.tupleKey, ts, contextStruct) 93 if err != nil { 94 var evalError *condition.EvaluationError 95 require.ErrorAs(t, err, &evalError) 96 require.EqualError(t, evalError, test.expectedErr) 97 } else { 98 require.Empty(t, test.expectedErr) 99 require.Equal(t, test.conditionMet, condEvalResult.ConditionMet) 100 } 101 }) 102 } 103 } 104 105 // TestDefaultCELEvaluationCost is used to ensure we don't decreasee the default evaluation cost 106 // of CEL expressions, which would break API compatibility. 107 // 108 // Critical paths involving ABAC Condition evaluations use the EvaluateTupleCondition function, 109 // and so we test that directly to give us higher confidence we're not introducing a compatibility 110 // issue. 111 func TestDefaultCELEvaluationCost(t *testing.T) { 112 tests := []struct { 113 name string 114 model *openfgav1.AuthorizationModel 115 tupleKey *openfgav1.TupleKey 116 context map[string]any 117 result *condition.EvaluationResult 118 }{ 119 { 120 name: "list_comprehension", 121 model: parser.MustTransformDSLToProto(`model 122 schema 1.1 123 type user 124 125 type document 126 relations 127 define can_view: [user with str_cond] 128 129 condition str_cond(s: list<string>) { 130 "98" in s 131 }`), 132 tupleKey: tuple.NewTupleKeyWithCondition("document:1", "can_view", "user:jon", "str_cond", nil), 133 context: map[string]any{ 134 "s": testutils.MakeSliceWithGenerator[any](99, testutils.NumericalStringGenerator), 135 }, 136 result: &condition.EvaluationResult{ 137 Cost: 100, 138 ConditionMet: true, 139 }, 140 }, 141 { 142 name: "string_contains", 143 model: parser.MustTransformDSLToProto(`model 144 schema 1.1 145 type user 146 147 type document 148 relations 149 define can_view: [user with str_cond] 150 151 condition str_cond(s: string) { 152 s.contains("b") 153 }`), 154 tupleKey: tuple.NewTupleKeyWithCondition("document:1", "can_view", "user:jon", "str_cond", nil), 155 context: map[string]any{ 156 // see https://github.com/google/cel-go/blob/cfbf821f1b458533051306305a39b743db7c4bdb/checker/cost.go#L604-L609 157 "s": testutils.MakeStringWithRuneset(990, []rune{'a'}), // string has cost factor of 0.1, so 990*0.1 < 100 158 }, 159 result: &condition.EvaluationResult{ 160 Cost: 100, 161 ConditionMet: false, 162 }, 163 }, 164 } 165 166 for _, test := range tests { 167 t.Run(test.name, func(t *testing.T) { 168 ctx := context.Background() 169 ts, err := typesystem.NewAndValidate(context.Background(), test.model) 170 require.NoError(t, err) 171 172 contextStruct, err := structpb.NewStruct(test.context) 173 require.NoError(t, err) 174 175 condEvalResult, err := EvaluateTupleCondition(ctx, test.tupleKey, ts, contextStruct) 176 require.NoError(t, err) 177 178 require.Equal(t, test.result, condEvalResult) 179 }) 180 } 181 }