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  }