github.com/openfga/openfga@v1.5.4-rc1/tests/functional_test.go (about) 1 package tests 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/oklog/ulid/v2" 9 openfgav1 "github.com/openfga/api/proto/openfga/v1" 10 parser "github.com/openfga/language/pkg/go/transformer" 11 "github.com/stretchr/testify/require" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/metadata" 14 "google.golang.org/grpc/status" 15 "google.golang.org/protobuf/testing/protocmp" 16 "google.golang.org/protobuf/types/known/wrapperspb" 17 18 "github.com/openfga/openfga/internal/server/config" 19 checktest "github.com/openfga/openfga/internal/test/check" 20 "github.com/openfga/openfga/pkg/testutils" 21 "github.com/openfga/openfga/pkg/tuple" 22 "github.com/openfga/openfga/pkg/typesystem" 23 ) 24 25 // newOpenFGAServerAndClient starts an OpenFGA server, waits until its is healthy, and returns a grpc client to it. 26 func newOpenFGAServerAndClient(t *testing.T) openfgav1.OpenFGAServiceClient { 27 cfg := config.MustDefaultConfig() 28 cfg.Log.Level = "error" 29 cfg.Datastore.Engine = "memory" 30 31 StartServer(t, cfg) 32 conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr) 33 34 testutils.EnsureServiceHealthy(t, cfg.GRPC.Addr, cfg.HTTP.Addr, nil, true) 35 36 client := openfgav1.NewOpenFGAServiceClient(conn) 37 return client 38 } 39 40 func TestGRPCMaxMessageSize(t *testing.T) { 41 client := newOpenFGAServerAndClient(t) 42 43 createResp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 44 Name: "max_message_size", 45 }) 46 require.NoError(t, err) 47 require.NotPanics(t, func() { ulid.MustParse(createResp.GetId()) }) 48 49 storeID := createResp.GetId() 50 51 model := parser.MustTransformDSLToProto(`model 52 schema 1.1 53 54 type user 55 56 type document 57 relations 58 define viewer: [user with conds] 59 60 condition conds(s: string) { 61 "alpha" == s 62 }`) 63 64 writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 65 StoreId: storeID, 66 TypeDefinitions: model.GetTypeDefinitions(), 67 Conditions: model.GetConditions(), 68 SchemaVersion: typesystem.SchemaVersion1_1, 69 }) 70 require.NoError(t, err) 71 require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) }) 72 73 modelID := writeModelResp.GetAuthorizationModelId() 74 75 checkResp, err := client.Check(context.Background(), &openfgav1.CheckRequest{ 76 StoreId: storeID, 77 AuthorizationModelId: modelID, 78 TupleKey: &openfgav1.CheckRequestTupleKey{ 79 Object: "document:1", 80 Relation: "viewer", 81 User: "user:jon", 82 }, 83 Context: testutils.MustNewStruct(t, map[string]interface{}{ 84 "s": testutils.CreateRandomString(config.DefaultMaxRPCMessageSizeInBytes + 1), 85 }), 86 }) 87 s, ok := status.FromError(err) 88 require.True(t, ok) 89 require.Equal(t, codes.ResourceExhausted, s.Code()) 90 require.ErrorContains(t, err, "grpc: received message larger than max") 91 require.Nil(t, checkResp) 92 } 93 94 // TODO make a unit test from this. 95 func TestCheckWithQueryCacheEnabled(t *testing.T) { 96 cfg := config.MustDefaultConfig() 97 cfg.CheckQueryCache.Enabled = true 98 99 StartServer(t, cfg) 100 101 conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr) 102 103 client := openfgav1.NewOpenFGAServiceClient(conn) 104 105 tests := []struct { 106 name string 107 typeDefinitions []*openfgav1.TypeDefinition 108 tuples []*openfgav1.TupleKey 109 assertions []checktest.Assertion 110 }{ 111 { 112 name: "issue_1058", 113 typeDefinitions: parser.MustTransformDSLToProto(`model 114 schema 1.1 115 type fga_user 116 117 type timeslot 118 relations 119 define user: [fga_user] 120 121 type commerce_store 122 relations 123 define approved_hourly_access: user from approved_timeslot and hourly_employee 124 define approved_timeslot: [timeslot] 125 define hourly_employee: [fga_user] 126 `).GetTypeDefinitions(), 127 tuples: []*openfgav1.TupleKey{ 128 {Object: "commerce_store:0", Relation: "hourly_employee", User: "fga_user:anne"}, 129 {Object: "commerce_store:1", Relation: "hourly_employee", User: "fga_user:anne"}, 130 {Object: "commerce_store:0", Relation: "approved_timeslot", User: "timeslot:11_12"}, 131 {Object: "commerce_store:1", Relation: "approved_timeslot", User: "timeslot:12_13"}, 132 }, 133 assertions: []checktest.Assertion{ 134 { 135 Tuple: tuple.NewTupleKey("commerce_store:0", "approved_hourly_access", "fga_user:anne"), 136 ContextualTuples: []*openfgav1.TupleKey{ 137 tuple.NewTupleKey("timeslot:11_12", "user", "fga_user:anne"), 138 }, 139 Expectation: true, 140 }, 141 { 142 Tuple: tuple.NewTupleKey("commerce_store:1", "approved_hourly_access", "fga_user:anne"), 143 ContextualTuples: []*openfgav1.TupleKey{ 144 tuple.NewTupleKey("timeslot:11_12", "user", "fga_user:anne"), 145 }, 146 Expectation: false, 147 }, 148 { 149 Tuple: tuple.NewTupleKey("commerce_store:1", "approved_hourly_access", "fga_user:anne"), 150 ContextualTuples: []*openfgav1.TupleKey{ 151 tuple.NewTupleKey("timeslot:12_13", "user", "fga_user:anne"), 152 }, 153 Expectation: true, 154 }, 155 }, 156 }, 157 { 158 name: "cache_computed_userset_subproblem_with_contextual_tuple", 159 typeDefinitions: parser.MustTransformDSLToProto(`model 160 schema 1.1 161 type user 162 163 type document 164 relations 165 define restricted: [user] 166 define viewer: [user] but not restricted 167 `).GetTypeDefinitions(), 168 tuples: []*openfgav1.TupleKey{ 169 {Object: "document:1", Relation: "viewer", User: "user:jon"}, 170 }, 171 assertions: []checktest.Assertion{ 172 { 173 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 174 ContextualTuples: []*openfgav1.TupleKey{}, 175 Expectation: true, 176 }, 177 { 178 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 179 ContextualTuples: []*openfgav1.TupleKey{ 180 tuple.NewTupleKey("document:1", "restricted", "user:jon"), 181 }, 182 Expectation: false, 183 }, 184 }, 185 }, 186 { 187 name: "cached_direct_relationship_with_contextual_tuple", 188 typeDefinitions: parser.MustTransformDSLToProto(`model 189 schema 1.1 190 type user 191 192 type document 193 relations 194 define viewer: [user] 195 `).GetTypeDefinitions(), 196 assertions: []checktest.Assertion{ 197 { 198 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 199 ContextualTuples: []*openfgav1.TupleKey{}, 200 Expectation: false, 201 }, 202 { 203 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 204 ContextualTuples: []*openfgav1.TupleKey{ 205 tuple.NewTupleKey("document:1", "viewer", "user:jon"), 206 }, 207 Expectation: true, 208 }, 209 }, 210 }, 211 { 212 name: "cached_direct_userset_relationship_with_contextual_tuple", 213 typeDefinitions: parser.MustTransformDSLToProto(`model 214 schema 1.1 215 type user 216 217 type group 218 relations 219 define restricted: [user] 220 define member: [user] but not restricted 221 222 type document 223 relations 224 define viewer: [group#member] 225 `).GetTypeDefinitions(), 226 tuples: []*openfgav1.TupleKey{ 227 {Object: "document:1", Relation: "viewer", User: "group:eng#member"}, 228 {Object: "group:eng", Relation: "member", User: "user:jon"}, 229 }, 230 assertions: []checktest.Assertion{ 231 { 232 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 233 ContextualTuples: []*openfgav1.TupleKey{}, 234 Expectation: true, 235 }, 236 { 237 Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"), 238 ContextualTuples: []*openfgav1.TupleKey{ 239 tuple.NewTupleKey("group:eng", "restricted", "user:jon"), 240 }, 241 Expectation: false, 242 }, 243 }, 244 }, 245 } 246 247 for _, test := range tests { 248 test := test 249 250 t.Run(test.name, func(t *testing.T) { 251 createResp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 252 Name: test.name, 253 }) 254 require.NoError(t, err) 255 require.NotPanics(t, func() { ulid.MustParse(createResp.GetId()) }) 256 257 storeID := createResp.GetId() 258 259 writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 260 StoreId: storeID, 261 TypeDefinitions: test.typeDefinitions, 262 SchemaVersion: typesystem.SchemaVersion1_1, 263 }) 264 require.NoError(t, err) 265 require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) }) 266 267 modelID := writeModelResp.GetAuthorizationModelId() 268 269 if len(test.tuples) > 0 { 270 _, err = client.Write(context.Background(), &openfgav1.WriteRequest{ 271 StoreId: storeID, 272 AuthorizationModelId: modelID, 273 Writes: &openfgav1.WriteRequestWrites{ 274 TupleKeys: test.tuples, 275 }, 276 }) 277 require.NoError(t, err) 278 } 279 280 for _, assertion := range test.assertions { 281 var tk *openfgav1.CheckRequestTupleKey 282 if assertion.Tuple != nil { 283 tk = tuple.NewCheckRequestTupleKey( 284 assertion.Tuple.GetObject(), 285 assertion.Tuple.GetRelation(), 286 assertion.Tuple.GetUser(), 287 ) 288 } 289 290 checkResp, err := client.Check(context.Background(), &openfgav1.CheckRequest{ 291 StoreId: storeID, 292 AuthorizationModelId: modelID, 293 TupleKey: tk, 294 ContextualTuples: &openfgav1.ContextualTupleKeys{ 295 TupleKeys: assertion.ContextualTuples, 296 }, 297 }) 298 299 if assertion.ErrorCode == 0 { 300 require.NoError(t, err) 301 require.Equal(t, assertion.Expectation, checkResp.GetAllowed()) 302 } else { 303 require.Error(t, err) 304 e, ok := status.FromError(err) 305 require.True(t, ok) 306 require.Equal(t, assertion.ErrorCode, int(e.Code())) 307 } 308 } 309 }) 310 } 311 } 312 313 func TestFunctionalGRPC(t *testing.T) { 314 // uncomment when https://github.com/hashicorp/go-retryablehttp/issues/214 is solved 315 // defer goleak.VerifyNone(t) 316 client := newOpenFGAServerAndClient(t) 317 318 t.Run("TestCreateStore", func(t *testing.T) { GRPCCreateStoreTest(t, client) }) 319 t.Run("TestGetStore", func(t *testing.T) { GRPCGetStoreTest(t, client) }) 320 t.Run("TestDeleteStore", func(t *testing.T) { GRPCDeleteStoreTest(t, client) }) 321 322 t.Run("TestWrite", func(t *testing.T) { GRPCWriteTest(t, client) }) 323 t.Run("TestRead", func(t *testing.T) { GRPCReadTest(t, client) }) 324 t.Run("TestReadChanges", func(t *testing.T) { GRPCReadChangesTest(t, client) }) 325 326 t.Run("TestCheck", func(t *testing.T) { GRPCCheckTest(t, client) }) 327 t.Run("TestListObjects", func(t *testing.T) { GRPCListObjectsTest(t, client) }) 328 t.Run("TestListUsersValidation", func(t *testing.T) { GRPCListUsersTest(t, client) }) 329 t.Run("TestWriteAuthorizationModel", func(t *testing.T) { GRPCWriteAuthorizationModelTest(t, client) }) 330 t.Run("TestReadAuthorizationModel", func(t *testing.T) { GRPCReadAuthorizationModelTest(t, client) }) 331 t.Run("TestReadAuthorizationModels", func(t *testing.T) { GRPCReadAuthorizationModelsTest(t, client) }) 332 t.Run("TestWriteAssertions", func(t *testing.T) { GRPCWriteAssertionsTest(t, client) }) 333 334 t.Run("TestWriteAuthorizationModel", func(t *testing.T) { GRPCWriteAuthorizationModelTest(t, client) }) 335 t.Run("TestReadAuthorizationModel", func(t *testing.T) { GRPCReadAuthorizationModelTest(t, client) }) 336 t.Run("TestReadAuthorizationModels", func(t *testing.T) { GRPCReadAuthorizationModelsTest(t, client) }) 337 } 338 339 func TestGRPCWithPresharedKey(t *testing.T) { 340 cfg := config.MustDefaultConfig() 341 cfg.Authn.Method = "preshared" 342 cfg.Authn.AuthnPresharedKeyConfig = &config.AuthnPresharedKeyConfig{Keys: []string{"key1", "key2"}} 343 344 StartServer(t, cfg) 345 346 conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr) 347 348 testutils.EnsureServiceHealthy(t, cfg.GRPC.Addr, cfg.HTTP.Addr, nil, true) 349 350 openfgaClient := openfgav1.NewOpenFGAServiceClient(conn) 351 352 _, err := openfgaClient.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 353 Name: "openfga-demo", 354 }) 355 require.Error(t, err) 356 357 s, ok := status.FromError(err) 358 require.True(t, ok) 359 require.Equal(t, codes.Code(openfgav1.AuthErrorCode_bearer_token_missing), s.Code()) 360 361 ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key1") 362 _, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{ 363 Name: "openfga-demo1", 364 }) 365 require.NoError(t, err) 366 367 ctx = metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key2") 368 _, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{ 369 Name: "openfga-demo2", 370 }) 371 require.NoError(t, err) 372 373 ctx = metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key3") 374 _, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{ 375 Name: "openfga-demo3", 376 }) 377 require.Error(t, err) 378 379 s, ok = status.FromError(err) 380 require.True(t, ok) 381 require.Equal(t, codes.Code(openfgav1.AuthErrorCode_unauthenticated), s.Code()) 382 } 383 384 func GRPCWriteTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 385 type output struct { 386 errorCode codes.Code 387 errorMessage string 388 } 389 390 resp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 391 Name: "openfga-demo", 392 }) 393 require.NoError(t, err) 394 storeID := resp.GetId() 395 396 model := parser.MustTransformDSLToProto(`model 397 schema 1.1 398 type user 399 400 type document 401 relations 402 define viewer: [user] 403 `) 404 405 writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 406 StoreId: storeID, 407 TypeDefinitions: model.GetTypeDefinitions(), 408 Conditions: model.GetConditions(), 409 SchemaVersion: typesystem.SchemaVersion1_1, 410 }) 411 require.NoError(t, err) 412 require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) }) 413 modelID := writeModelResp.GetAuthorizationModelId() 414 415 tests := []struct { 416 name string 417 input *openfgav1.WriteRequest 418 output output 419 }{ 420 { 421 name: "happy_path_writes", 422 input: &openfgav1.WriteRequest{ 423 StoreId: storeID, 424 AuthorizationModelId: modelID, 425 Writes: &openfgav1.WriteRequestWrites{ 426 TupleKeys: []*openfgav1.TupleKey{ 427 {Object: "document:1", Relation: "viewer", User: "user:jon"}, 428 }, 429 }, 430 }, 431 output: output{ 432 errorCode: codes.OK, 433 }, 434 }, 435 { 436 name: "happy_path_deletes", 437 input: &openfgav1.WriteRequest{ 438 StoreId: storeID, 439 AuthorizationModelId: modelID, 440 Deletes: &openfgav1.WriteRequestDeletes{ 441 TupleKeys: []*openfgav1.TupleKeyWithoutCondition{ 442 { 443 Object: "document:1", Relation: "viewer", User: "user:jon", 444 }, 445 }, 446 }, 447 }, 448 output: output{ 449 errorCode: codes.OK, 450 }, 451 }, 452 { 453 name: "invalid_store_id", 454 input: &openfgav1.WriteRequest{ 455 StoreId: "invalid-store-id", 456 AuthorizationModelId: modelID, 457 Writes: &openfgav1.WriteRequestWrites{}, 458 }, 459 output: output{ 460 errorCode: codes.InvalidArgument, 461 errorMessage: `value does not match regex pattern "^[ABCDEFGHJKMNPQRSTVWXYZ0-9]{26}$`, 462 }, 463 }, 464 { 465 name: "invalid_model_id", 466 input: &openfgav1.WriteRequest{ 467 StoreId: storeID, 468 AuthorizationModelId: "invalid-model-id", 469 Writes: &openfgav1.WriteRequestWrites{}, 470 }, 471 output: output{ 472 errorCode: codes.InvalidArgument, 473 errorMessage: `value does not match regex pattern "^[ABCDEFGHJKMNPQRSTVWXYZ0-9]{26}$`, 474 }, 475 }, 476 { 477 name: "nil_writes_and_deletes", 478 input: &openfgav1.WriteRequest{ 479 StoreId: storeID, 480 AuthorizationModelId: modelID, 481 }, 482 output: output{ 483 errorCode: codes.Code(2003), 484 errorMessage: `Invalid input. Make sure you provide at least one write, or at least one delete`, 485 }, 486 }, 487 } 488 489 for _, test := range tests { 490 t.Run(test.name, func(t *testing.T) { 491 _, err := client.Write(context.Background(), test.input) 492 s, ok := status.FromError(err) 493 494 require.True(t, ok) 495 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 496 497 if s.Code() == codes.OK { 498 require.NoError(t, err) 499 } else { 500 require.Contains(t, err.Error(), test.output.errorMessage) 501 } 502 }) 503 } 504 } 505 506 func GRPCReadTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 507 508 } 509 510 func GRPCReadChangesTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 511 512 } 513 514 func GRPCCreateStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 515 type output struct { 516 errorCode codes.Code 517 } 518 519 tests := []struct { 520 name string 521 input *openfgav1.CreateStoreRequest 522 output output 523 }{ 524 { 525 name: "empty_request", 526 input: &openfgav1.CreateStoreRequest{}, 527 output: output{ 528 errorCode: codes.InvalidArgument, 529 }, 530 }, 531 { 532 name: "invalid_name_length", 533 input: &openfgav1.CreateStoreRequest{ 534 Name: "a", 535 }, 536 output: output{ 537 errorCode: codes.InvalidArgument, 538 }, 539 }, 540 { 541 name: "invalid_name_characters", 542 input: &openfgav1.CreateStoreRequest{ 543 Name: "$openfga", 544 }, 545 output: output{ 546 errorCode: codes.InvalidArgument, 547 }, 548 }, 549 { 550 name: "success", 551 input: &openfgav1.CreateStoreRequest{ 552 Name: "openfga", 553 }, 554 }, 555 { 556 name: "duplicate_store_name_is_allowed", 557 input: &openfgav1.CreateStoreRequest{ 558 Name: "openfga", 559 }, 560 }, 561 } 562 563 for _, test := range tests { 564 t.Run(test.name, func(t *testing.T) { 565 response, err := client.CreateStore(context.Background(), test.input) 566 567 s, ok := status.FromError(err) 568 require.True(t, ok) 569 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 570 571 if test.output.errorCode == codes.OK { 572 require.Equal(t, test.input.GetName(), response.GetName()) 573 _, err = ulid.Parse(response.GetId()) 574 require.NoError(t, err) 575 } 576 }) 577 } 578 } 579 580 func GRPCGetStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 581 resp1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 582 Name: "openfga-demo", 583 }) 584 require.NoError(t, err) 585 586 resp2, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{ 587 StoreId: resp1.GetId(), 588 }) 589 require.NoError(t, err) 590 591 require.Equal(t, resp1.GetName(), resp2.GetName()) 592 require.Equal(t, resp1.GetId(), resp2.GetId()) 593 594 resp3, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{ 595 StoreId: ulid.Make().String(), 596 }) 597 require.Error(t, err) 598 require.Nil(t, resp3) 599 } 600 601 func TestGRPCListStores(t *testing.T) { 602 client := newOpenFGAServerAndClient(t) 603 _, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 604 Name: "openfga-demo", 605 }) 606 require.NoError(t, err) 607 608 _, err = client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 609 Name: "openfga-test", 610 }) 611 require.NoError(t, err) 612 613 response1, err := client.ListStores(context.Background(), &openfgav1.ListStoresRequest{ 614 PageSize: wrapperspb.Int32(1), 615 }) 616 require.NoError(t, err) 617 618 require.NotEmpty(t, response1.GetContinuationToken()) 619 620 var received []*openfgav1.Store 621 received = append(received, response1.GetStores()...) 622 623 response2, err := client.ListStores(context.Background(), &openfgav1.ListStoresRequest{ 624 PageSize: wrapperspb.Int32(2), 625 ContinuationToken: response1.GetContinuationToken(), 626 }) 627 require.NoError(t, err) 628 629 require.Empty(t, response2.GetContinuationToken()) 630 631 received = append(received, response2.GetStores()...) 632 633 require.Len(t, received, 2) 634 // todo: add assertions on received Store objects 635 } 636 637 func GRPCDeleteStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 638 response1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 639 Name: "openfga-demo", 640 }) 641 require.NoError(t, err) 642 643 response2, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{ 644 StoreId: response1.GetId(), 645 }) 646 require.NoError(t, err) 647 648 require.Equal(t, response1.GetId(), response2.GetId()) 649 650 _, err = client.DeleteStore(context.Background(), &openfgav1.DeleteStoreRequest{ 651 StoreId: response1.GetId(), 652 }) 653 require.NoError(t, err) 654 655 response3, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{ 656 StoreId: response1.GetId(), 657 }) 658 require.Nil(t, response3) 659 660 s, ok := status.FromError(err) 661 require.True(t, ok) 662 require.Equal(t, codes.Code(openfgav1.NotFoundErrorCode_store_id_not_found), s.Code()) 663 664 // delete is idempotent, so if the store does not exist it's a noop 665 _, err = client.DeleteStore(context.Background(), &openfgav1.DeleteStoreRequest{ 666 StoreId: ulid.Make().String(), 667 }) 668 require.NoError(t, err) 669 } 670 671 func GRPCCheckTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 672 type output struct { 673 errorCode codes.Code 674 } 675 676 tests := []struct { 677 name string 678 input *openfgav1.CheckRequest 679 output output 680 }{ 681 { 682 name: "empty_request", 683 input: &openfgav1.CheckRequest{}, 684 output: output{ 685 errorCode: codes.InvalidArgument, 686 }, 687 }, 688 { 689 name: "invalid_storeID_because_too_short", 690 input: &openfgav1.CheckRequest{ 691 StoreId: "1", 692 AuthorizationModelId: ulid.Make().String(), 693 TupleKey: tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"), 694 }, 695 output: output{ 696 errorCode: codes.InvalidArgument, 697 }, 698 }, 699 { 700 name: "invalid_storeID_because_extra_chars", 701 input: &openfgav1.CheckRequest{ 702 StoreId: ulid.Make().String() + "A", 703 AuthorizationModelId: ulid.Make().String(), 704 TupleKey: tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"), 705 }, 706 output: output{ 707 errorCode: codes.InvalidArgument, 708 }, 709 }, 710 { 711 name: "invalid_storeID_because_invalid_chars", 712 input: &openfgav1.CheckRequest{ 713 StoreId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 714 AuthorizationModelId: ulid.Make().String(), 715 TupleKey: tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"), 716 }, 717 output: output{ 718 errorCode: codes.InvalidArgument, 719 }, 720 }, 721 { 722 name: "invalid_authorization_model_ID_because_extra_chars", 723 input: &openfgav1.CheckRequest{ 724 StoreId: ulid.Make().String(), 725 AuthorizationModelId: ulid.Make().String() + "A", 726 TupleKey: tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"), 727 }, 728 output: output{ 729 errorCode: codes.InvalidArgument, 730 }, 731 }, 732 { 733 name: "invalid_authorization_model_ID_because_invalid_chars", 734 input: &openfgav1.CheckRequest{ 735 StoreId: ulid.Make().String(), 736 AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 737 TupleKey: tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"), 738 }, 739 output: output{ 740 errorCode: codes.InvalidArgument, 741 }, 742 }, 743 { 744 name: "missing_tuplekey_field", 745 input: &openfgav1.CheckRequest{ 746 StoreId: ulid.Make().String(), 747 AuthorizationModelId: ulid.Make().String(), 748 }, 749 output: output{ 750 errorCode: codes.InvalidArgument, 751 }, 752 }, 753 { 754 name: "missing_user", 755 input: &openfgav1.CheckRequest{ 756 StoreId: ulid.Make().String(), 757 AuthorizationModelId: ulid.Make().String(), 758 TupleKey: &openfgav1.CheckRequestTupleKey{ 759 Relation: "relation", 760 Object: "obj:1", 761 }, 762 }, 763 output: output{ 764 errorCode: codes.InvalidArgument, 765 }, 766 }, 767 { 768 name: "missing_relation", 769 input: &openfgav1.CheckRequest{ 770 StoreId: ulid.Make().String(), 771 AuthorizationModelId: ulid.Make().String(), 772 TupleKey: &openfgav1.CheckRequestTupleKey{ 773 User: "user:anne", 774 Object: "obj:1", 775 }, 776 }, 777 output: output{ 778 errorCode: codes.InvalidArgument, 779 }, 780 }, 781 { 782 name: "missing_object", 783 input: &openfgav1.CheckRequest{ 784 StoreId: ulid.Make().String(), 785 AuthorizationModelId: ulid.Make().String(), 786 TupleKey: &openfgav1.CheckRequestTupleKey{ 787 User: "user:anne", 788 Relation: "relation", 789 }, 790 }, 791 output: output{ 792 errorCode: codes.InvalidArgument, 793 }, 794 }, 795 { 796 name: "model_not_found", 797 input: &openfgav1.CheckRequest{ 798 StoreId: ulid.Make().String(), 799 AuthorizationModelId: ulid.Make().String(), 800 TupleKey: &openfgav1.CheckRequestTupleKey{ 801 User: "user:anne", 802 Object: "obj:1", 803 Relation: "relation", 804 }, 805 }, 806 output: output{ 807 errorCode: 2001, // ErrorCode_authorization_model_not_found 808 }, 809 }, 810 } 811 812 for _, test := range tests { 813 t.Run(test.name, func(t *testing.T) { 814 _, err := client.Check(context.Background(), test.input) 815 816 s, ok := status.FromError(err) 817 require.True(t, ok) 818 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 819 820 if s.Code() == codes.OK { 821 require.NoError(t, err) 822 } else { 823 require.Error(t, err) 824 } 825 }) 826 } 827 } 828 829 func GRPCListObjectsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 830 type output struct { 831 errorCode codes.Code 832 } 833 834 tests := []struct { 835 name string 836 input *openfgav1.ListObjectsRequest 837 output output 838 }{ 839 { 840 name: "undefined_model_id_returns_error", 841 input: &openfgav1.ListObjectsRequest{ 842 StoreId: ulid.Make().String(), 843 AuthorizationModelId: ulid.Make().String(), // generate random ulid so it doesn't match 844 Type: "document", 845 Relation: "viewer", 846 User: "user:jon", 847 }, 848 output: output{ 849 errorCode: codes.Code(openfgav1.ErrorCode_authorization_model_not_found), 850 }, 851 }, 852 { 853 name: "empty_request", 854 input: &openfgav1.ListObjectsRequest{}, 855 output: output{ 856 errorCode: codes.InvalidArgument, 857 }, 858 }, 859 { 860 name: "invalid_storeID_because_too_short", 861 input: &openfgav1.ListObjectsRequest{ 862 StoreId: "1", 863 AuthorizationModelId: ulid.Make().String(), 864 Type: "document", 865 Relation: "viewer", 866 User: "user:jon", 867 }, 868 output: output{ 869 errorCode: codes.InvalidArgument, 870 }, 871 }, 872 { 873 name: "invalid_storeID_because_extra_chars", 874 input: &openfgav1.ListObjectsRequest{ 875 StoreId: ulid.Make().String() + "A", 876 AuthorizationModelId: ulid.Make().String(), 877 Type: "document", 878 Relation: "viewer", 879 User: "user:jon", 880 }, 881 output: output{ 882 errorCode: codes.InvalidArgument, 883 }, 884 }, 885 { 886 name: "invalid_storeID_because_invalid_chars", 887 input: &openfgav1.ListObjectsRequest{ 888 StoreId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 889 AuthorizationModelId: ulid.Make().String(), 890 Type: "document", 891 Relation: "viewer", 892 User: "user:jon", 893 }, 894 output: output{ 895 errorCode: codes.InvalidArgument, 896 }, 897 }, 898 { 899 name: "invalid_authorization_model_ID_because_extra_chars", 900 input: &openfgav1.ListObjectsRequest{ 901 StoreId: ulid.Make().String(), 902 AuthorizationModelId: ulid.Make().String() + "A", 903 Type: "document", 904 Relation: "viewer", 905 User: "user:jon", 906 }, 907 output: output{ 908 errorCode: codes.InvalidArgument, 909 }, 910 }, 911 { 912 name: "invalid_authorization_model_ID_because_invalid_chars", 913 input: &openfgav1.ListObjectsRequest{ 914 StoreId: ulid.Make().String(), 915 AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 916 Type: "document", 917 Relation: "viewer", 918 User: "user:jon", 919 }, 920 output: output{ 921 errorCode: codes.InvalidArgument, 922 }, 923 }, 924 { 925 name: "missing_user", 926 input: &openfgav1.ListObjectsRequest{ 927 StoreId: ulid.Make().String(), 928 AuthorizationModelId: ulid.Make().String(), 929 Type: "document", 930 Relation: "viewer", 931 }, 932 output: output{ 933 errorCode: codes.InvalidArgument, 934 }, 935 }, 936 { 937 name: "missing_relation", 938 input: &openfgav1.ListObjectsRequest{ 939 StoreId: ulid.Make().String(), 940 AuthorizationModelId: ulid.Make().String(), 941 Type: "document", 942 User: "user:jon", 943 }, 944 output: output{ 945 errorCode: codes.InvalidArgument, 946 }, 947 }, 948 { 949 name: "missing_type", 950 input: &openfgav1.ListObjectsRequest{ 951 StoreId: ulid.Make().String(), 952 AuthorizationModelId: ulid.Make().String(), 953 Relation: "viewer", 954 User: "user:jon", 955 }, 956 output: output{ 957 errorCode: codes.InvalidArgument, 958 }, 959 }, 960 { 961 name: "model_not_found", 962 input: &openfgav1.ListObjectsRequest{ 963 StoreId: ulid.Make().String(), 964 AuthorizationModelId: ulid.Make().String(), 965 Type: "document", 966 Relation: "viewer", 967 User: "user:jon", 968 }, 969 output: output{ 970 errorCode: 2001, // ErrorCode_authorization_model_not_found 971 }, 972 }, 973 } 974 975 for _, test := range tests { 976 t.Run(test.name, func(t *testing.T) { 977 _, err := client.ListObjects(context.Background(), test.input) 978 979 s, ok := status.FromError(err) 980 require.True(t, ok) 981 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 982 983 if s.Code() == codes.OK { 984 require.NoError(t, err) 985 } else { 986 require.Error(t, err) 987 } 988 }) 989 } 990 } 991 992 func GRPCListUsersTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 993 tests := []struct { 994 name string 995 input *openfgav1.ListUsersRequest 996 expectedErrorCode codes.Code 997 }{ 998 { 999 name: "too_many_user_filters", 1000 input: &openfgav1.ListUsersRequest{ 1001 StoreId: ulid.Make().String(), 1002 AuthorizationModelId: ulid.Make().String(), 1003 Relation: "viewer", 1004 Object: &openfgav1.Object{ 1005 Type: "document", 1006 Id: "1", 1007 }, 1008 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}, {Type: "employee"}}, 1009 }, 1010 expectedErrorCode: codes.InvalidArgument, 1011 }, 1012 { 1013 name: "zero_user_filters", 1014 input: &openfgav1.ListUsersRequest{ 1015 StoreId: ulid.Make().String(), 1016 AuthorizationModelId: ulid.Make().String(), 1017 Relation: "viewer", 1018 Object: &openfgav1.Object{ 1019 Type: "document", 1020 Id: "1", 1021 }, 1022 UserFilters: []*openfgav1.UserTypeFilter{}, 1023 }, 1024 expectedErrorCode: codes.InvalidArgument, 1025 }, 1026 { 1027 name: "object_no_type_defined", 1028 input: &openfgav1.ListUsersRequest{ 1029 StoreId: ulid.Make().String(), 1030 AuthorizationModelId: ulid.Make().String(), 1031 Relation: "viewer", 1032 Object: &openfgav1.Object{ 1033 Id: "1", 1034 }, 1035 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1036 }, 1037 expectedErrorCode: codes.InvalidArgument, 1038 }, 1039 { 1040 name: "object_no_id_defined", 1041 input: &openfgav1.ListUsersRequest{ 1042 StoreId: ulid.Make().String(), 1043 AuthorizationModelId: ulid.Make().String(), 1044 Relation: "viewer", 1045 Object: &openfgav1.Object{ 1046 Type: "user", 1047 }, 1048 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1049 }, 1050 expectedErrorCode: codes.InvalidArgument, 1051 }, 1052 { 1053 name: "empty_request", 1054 input: &openfgav1.ListUsersRequest{}, 1055 expectedErrorCode: codes.InvalidArgument, 1056 }, 1057 { 1058 name: "invalid_storeID_because_too_short", 1059 input: &openfgav1.ListUsersRequest{ 1060 StoreId: "1", 1061 AuthorizationModelId: ulid.Make().String(), 1062 Relation: "viewer", 1063 Object: &openfgav1.Object{ 1064 Type: "document", 1065 Id: "1", 1066 }, 1067 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1068 }, 1069 expectedErrorCode: codes.InvalidArgument, 1070 }, 1071 { 1072 name: "invalid_storeID_because_extra_chars", 1073 input: &openfgav1.ListUsersRequest{ 1074 StoreId: ulid.Make().String() + "A", 1075 AuthorizationModelId: ulid.Make().String(), 1076 Relation: "viewer", 1077 Object: &openfgav1.Object{ 1078 Type: "document", 1079 Id: "1", 1080 }, 1081 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1082 }, 1083 expectedErrorCode: codes.InvalidArgument, 1084 }, 1085 { 1086 name: "invalid_storeID_because_invalid_chars", 1087 input: &openfgav1.ListUsersRequest{ 1088 StoreId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 1089 AuthorizationModelId: ulid.Make().String(), 1090 Relation: "viewer", 1091 Object: &openfgav1.Object{ 1092 Type: "document", 1093 Id: "1", 1094 }, 1095 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1096 }, 1097 expectedErrorCode: codes.InvalidArgument, 1098 }, 1099 { 1100 name: "invalid_authorization_model_ID_because_extra_chars", 1101 input: &openfgav1.ListUsersRequest{ 1102 StoreId: ulid.Make().String(), 1103 AuthorizationModelId: ulid.Make().String() + "A", 1104 Relation: "viewer", 1105 Object: &openfgav1.Object{ 1106 Type: "document", 1107 Id: "1", 1108 }, 1109 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1110 }, 1111 expectedErrorCode: codes.InvalidArgument, 1112 }, 1113 { 1114 name: "invalid_store_ID_because_extra_chars", 1115 input: &openfgav1.ListUsersRequest{ 1116 StoreId: ulid.Make().String() + "A", 1117 AuthorizationModelId: ulid.Make().String(), 1118 Relation: "viewer", 1119 Object: &openfgav1.Object{ 1120 Type: "document", 1121 Id: "1", 1122 }, 1123 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1124 }, 1125 expectedErrorCode: codes.InvalidArgument, 1126 }, 1127 { 1128 name: "invalid_authorization_model_ID_because_invalid_chars", 1129 input: &openfgav1.ListUsersRequest{ 1130 StoreId: ulid.Make().String(), 1131 AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 1132 Relation: "viewer", 1133 Object: &openfgav1.Object{ 1134 Type: "document", 1135 Id: "1", 1136 }, 1137 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1138 }, 1139 expectedErrorCode: codes.InvalidArgument, 1140 }, 1141 { 1142 name: "invalid_authorization_model_ID_because_invalid_chars", 1143 input: &openfgav1.ListUsersRequest{ 1144 StoreId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 1145 AuthorizationModelId: ulid.Make().String(), 1146 Relation: "viewer", 1147 Object: &openfgav1.Object{ 1148 Type: "document", 1149 Id: "1", 1150 }, 1151 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1152 }, 1153 expectedErrorCode: codes.InvalidArgument, 1154 }, 1155 { 1156 name: "missing_object", 1157 input: &openfgav1.ListUsersRequest{ 1158 StoreId: ulid.Make().String(), 1159 AuthorizationModelId: ulid.Make().String(), 1160 Relation: "viewer", 1161 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1162 }, 1163 expectedErrorCode: codes.InvalidArgument, 1164 }, 1165 { 1166 name: "empty_object", 1167 input: &openfgav1.ListUsersRequest{ 1168 StoreId: ulid.Make().String(), 1169 AuthorizationModelId: ulid.Make().String(), 1170 Relation: "viewer", 1171 Object: &openfgav1.Object{}, 1172 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1173 }, 1174 expectedErrorCode: codes.InvalidArgument, 1175 }, 1176 { 1177 name: "missing_relation", 1178 input: &openfgav1.ListUsersRequest{ 1179 StoreId: ulid.Make().String(), 1180 AuthorizationModelId: ulid.Make().String(), 1181 Object: &openfgav1.Object{ 1182 Type: "document", 1183 Id: "1", 1184 }, 1185 UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}}, 1186 }, 1187 expectedErrorCode: codes.InvalidArgument, 1188 }, 1189 } 1190 1191 for _, test := range tests { 1192 t.Run(test.name, func(t *testing.T) { 1193 _, err := client.ListUsers(context.Background(), test.input) 1194 1195 s, ok := status.FromError(err) 1196 require.True(t, ok) 1197 require.Equal(t, test.expectedErrorCode, s.Code()) 1198 1199 if s.Code() == codes.OK { 1200 require.NoError(t, err) 1201 } else { 1202 require.Error(t, err) 1203 } 1204 }) 1205 } 1206 } 1207 1208 // TestExpandWorkflows are tests that involve workflows that define assertions for 1209 // Expands against multi-model stores etc.. 1210 // TODO move to consolidated_1_1_tests.yaml. 1211 func TestExpandWorkflows(t *testing.T) { 1212 client := newOpenFGAServerAndClient(t) 1213 1214 /* 1215 * TypedWildcardsFromOtherModelsIgnored ensures that a typed wildcard introduced 1216 * from a prior model does not impact the Expand outcome of a model that should not 1217 * involve it. For example, 1218 * 1219 * type user 1220 * type document 1221 * relations 1222 * define viewer: [user, user:*] 1223 * 1224 * write(document:1#viewer@user:*) 1225 * write(document:1#viewer@user:jon) 1226 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: [user:*, user:jon]}}}} 1227 * 1228 * type user 1229 * type document 1230 * relations 1231 * define viewer: [user] 1232 * 1233 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: [user:jon]}}}} 1234 * 1235 * type employee 1236 * type document 1237 * relations 1238 * define viewer: [employee] 1239 * 1240 * type user 1241 * type employee 1242 * type document 1243 * relations 1244 * define viewer: [employee] 1245 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: []}}}} 1246 */ 1247 t.Run("TypedWildcardsFromOtherModelsIgnored", func(t *testing.T) { 1248 resp1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{ 1249 Name: "openfga-demo", 1250 }) 1251 require.NoError(t, err) 1252 1253 storeID := resp1.GetId() 1254 1255 _, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1256 StoreId: storeID, 1257 SchemaVersion: typesystem.SchemaVersion1_1, 1258 TypeDefinitions: []*openfgav1.TypeDefinition{ 1259 { 1260 Type: "user", 1261 }, 1262 { 1263 Type: "document", 1264 Relations: map[string]*openfgav1.Userset{ 1265 "viewer": typesystem.This(), 1266 }, 1267 Metadata: &openfgav1.Metadata{ 1268 Relations: map[string]*openfgav1.RelationMetadata{ 1269 "viewer": { 1270 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1271 typesystem.DirectRelationReference("user", ""), 1272 typesystem.WildcardRelationReference("user"), 1273 }, 1274 }, 1275 }, 1276 }, 1277 }, 1278 }, 1279 }) 1280 require.NoError(t, err) 1281 1282 _, err = client.Write(context.Background(), &openfgav1.WriteRequest{ 1283 StoreId: storeID, 1284 Writes: &openfgav1.WriteRequestWrites{ 1285 TupleKeys: []*openfgav1.TupleKey{ 1286 {Object: "document:1", Relation: "viewer", User: "user:*"}, 1287 {Object: "document:1", Relation: "viewer", User: "user:jon"}, 1288 }, 1289 }, 1290 }) 1291 require.NoError(t, err) 1292 1293 expandResp, err := client.Expand(context.Background(), &openfgav1.ExpandRequest{ 1294 StoreId: storeID, 1295 TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"), 1296 }) 1297 require.NoError(t, err) 1298 1299 if diff := cmp.Diff(&openfgav1.UsersetTree{ 1300 Root: &openfgav1.UsersetTree_Node{ 1301 Name: "document:1#viewer", 1302 Value: &openfgav1.UsersetTree_Node_Leaf{ 1303 Leaf: &openfgav1.UsersetTree_Leaf{ 1304 Value: &openfgav1.UsersetTree_Leaf_Users{ 1305 Users: &openfgav1.UsersetTree_Users{ 1306 Users: []string{"user:*", "user:jon"}, 1307 }, 1308 }, 1309 }, 1310 }, 1311 }, 1312 }, expandResp.GetTree(), protocmp.Transform(), protocmp.SortRepeated(func(x, y string) bool { 1313 return x <= y 1314 })); diff != "" { 1315 require.Fail(t, diff) 1316 } 1317 1318 _, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1319 StoreId: storeID, 1320 SchemaVersion: typesystem.SchemaVersion1_1, 1321 TypeDefinitions: []*openfgav1.TypeDefinition{ 1322 { 1323 Type: "user", 1324 }, 1325 { 1326 Type: "document", 1327 Relations: map[string]*openfgav1.Userset{ 1328 "viewer": typesystem.This(), 1329 }, 1330 Metadata: &openfgav1.Metadata{ 1331 Relations: map[string]*openfgav1.RelationMetadata{ 1332 "viewer": { 1333 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1334 typesystem.DirectRelationReference("user", ""), 1335 }, 1336 }, 1337 }, 1338 }, 1339 }, 1340 }, 1341 }) 1342 require.NoError(t, err) 1343 1344 expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{ 1345 StoreId: storeID, 1346 TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"), 1347 }) 1348 require.NoError(t, err) 1349 1350 if diff := cmp.Diff(&openfgav1.UsersetTree{ 1351 Root: &openfgav1.UsersetTree_Node{ 1352 Name: "document:1#viewer", 1353 Value: &openfgav1.UsersetTree_Node_Leaf{ 1354 Leaf: &openfgav1.UsersetTree_Leaf{ 1355 Value: &openfgav1.UsersetTree_Leaf_Users{ 1356 Users: &openfgav1.UsersetTree_Users{ 1357 Users: []string{"user:jon"}, 1358 }, 1359 }, 1360 }, 1361 }, 1362 }, 1363 }, expandResp.GetTree(), protocmp.Transform()); diff != "" { 1364 require.Fail(t, diff) 1365 } 1366 1367 _, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1368 StoreId: storeID, 1369 SchemaVersion: typesystem.SchemaVersion1_1, 1370 TypeDefinitions: []*openfgav1.TypeDefinition{ 1371 { 1372 Type: "employee", 1373 }, 1374 { 1375 Type: "document", 1376 Relations: map[string]*openfgav1.Userset{ 1377 "viewer": typesystem.This(), 1378 }, 1379 Metadata: &openfgav1.Metadata{ 1380 Relations: map[string]*openfgav1.RelationMetadata{ 1381 "viewer": { 1382 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1383 {Type: "employee"}, 1384 }, 1385 }, 1386 }, 1387 }, 1388 }, 1389 }, 1390 }) 1391 require.NoError(t, err) 1392 1393 expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{ 1394 StoreId: storeID, 1395 TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"), 1396 }) 1397 require.NoError(t, err) 1398 1399 if diff := cmp.Diff(&openfgav1.UsersetTree{ 1400 Root: &openfgav1.UsersetTree_Node{ 1401 Name: "document:1#viewer", 1402 Value: &openfgav1.UsersetTree_Node_Leaf{ 1403 Leaf: &openfgav1.UsersetTree_Leaf{ 1404 Value: &openfgav1.UsersetTree_Leaf_Users{ 1405 Users: &openfgav1.UsersetTree_Users{ 1406 Users: []string{}, 1407 }, 1408 }, 1409 }, 1410 }, 1411 }, 1412 }, expandResp.GetTree(), protocmp.Transform()); diff != "" { 1413 require.Fail(t, diff) 1414 } 1415 1416 _, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1417 StoreId: storeID, 1418 SchemaVersion: typesystem.SchemaVersion1_1, 1419 TypeDefinitions: []*openfgav1.TypeDefinition{ 1420 { 1421 Type: "user", 1422 }, 1423 { 1424 Type: "employee", 1425 }, 1426 { 1427 Type: "document", 1428 Relations: map[string]*openfgav1.Userset{ 1429 "viewer": typesystem.This(), 1430 }, 1431 Metadata: &openfgav1.Metadata{ 1432 Relations: map[string]*openfgav1.RelationMetadata{ 1433 "viewer": { 1434 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1435 {Type: "employee"}, 1436 }, 1437 }, 1438 }, 1439 }, 1440 }, 1441 }, 1442 }) 1443 require.NoError(t, err) 1444 1445 expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{ 1446 StoreId: storeID, 1447 TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"), 1448 }) 1449 require.NoError(t, err) 1450 1451 if diff := cmp.Diff(&openfgav1.UsersetTree{ 1452 Root: &openfgav1.UsersetTree_Node{ 1453 Name: "document:1#viewer", 1454 Value: &openfgav1.UsersetTree_Node_Leaf{ 1455 Leaf: &openfgav1.UsersetTree_Leaf{ 1456 Value: &openfgav1.UsersetTree_Leaf_Users{ 1457 Users: &openfgav1.UsersetTree_Users{ 1458 Users: []string{}, 1459 }, 1460 }, 1461 }, 1462 }, 1463 }, 1464 }, expandResp.GetTree(), protocmp.Transform()); diff != "" { 1465 require.Fail(t, diff) 1466 } 1467 }) 1468 } 1469 1470 func GRPCReadAuthorizationModelTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 1471 type testData struct { 1472 model string 1473 } 1474 1475 type output struct { 1476 errorCode codes.Code 1477 } 1478 1479 tests := []struct { 1480 name string 1481 input *openfgav1.ReadAuthorizationModelRequest 1482 output output 1483 testData *testData 1484 }{ 1485 { 1486 name: "happy_path", 1487 testData: &testData{ 1488 model: `model 1489 schema 1.1 1490 type user`, 1491 }, 1492 input: &openfgav1.ReadAuthorizationModelRequest{ 1493 StoreId: ulid.Make().String(), 1494 Id: ulid.Make().String(), 1495 }, 1496 output: output{ 1497 errorCode: codes.OK, 1498 }, 1499 }, 1500 { 1501 name: "empty_request", 1502 input: &openfgav1.ReadAuthorizationModelRequest{}, 1503 output: output{ 1504 errorCode: codes.InvalidArgument, 1505 }, 1506 }, 1507 { 1508 name: "invalid_storeID_because_too_short", 1509 input: &openfgav1.ReadAuthorizationModelRequest{ 1510 StoreId: "1", 1511 Id: ulid.Make().String(), 1512 }, 1513 output: output{ 1514 errorCode: codes.InvalidArgument, 1515 }, 1516 }, 1517 { 1518 name: "invalid_storeID_because_extra_chars", 1519 input: &openfgav1.ReadAuthorizationModelRequest{ 1520 StoreId: ulid.Make().String() + "A", 1521 Id: ulid.Make().String(), // ulids aren't required at this time 1522 }, 1523 output: output{ 1524 errorCode: codes.InvalidArgument, 1525 }, 1526 }, 1527 { 1528 name: "invalid_authorization_model_ID_because_too_short", 1529 input: &openfgav1.ReadAuthorizationModelRequest{ 1530 StoreId: ulid.Make().String(), 1531 Id: "1", 1532 }, 1533 output: output{ 1534 errorCode: codes.InvalidArgument, 1535 }, 1536 }, 1537 { 1538 name: "invalid_authorization_model_ID_because_extra_chars", 1539 input: &openfgav1.ReadAuthorizationModelRequest{ 1540 StoreId: ulid.Make().String(), 1541 Id: ulid.Make().String() + "A", 1542 }, 1543 output: output{ 1544 errorCode: codes.InvalidArgument, 1545 }, 1546 }, 1547 { 1548 name: "missing_authorization_id", 1549 input: &openfgav1.ReadAuthorizationModelRequest{ 1550 StoreId: ulid.Make().String(), 1551 }, 1552 output: output{ 1553 errorCode: codes.InvalidArgument, 1554 }, 1555 }, 1556 } 1557 1558 for _, test := range tests { 1559 t.Run(test.name, func(t *testing.T) { 1560 if test.testData != nil { 1561 modelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1562 StoreId: test.input.GetStoreId(), 1563 SchemaVersion: typesystem.SchemaVersion1_1, 1564 TypeDefinitions: parser.MustTransformDSLToProto(test.testData.model).GetTypeDefinitions(), 1565 }) 1566 test.input.Id = modelResp.GetAuthorizationModelId() 1567 require.NoError(t, err) 1568 } 1569 _, err := client.ReadAuthorizationModel(context.Background(), test.input) 1570 1571 s, ok := status.FromError(err) 1572 require.True(t, ok) 1573 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 1574 1575 if s.Code() == codes.OK { 1576 require.NoError(t, err) 1577 } 1578 }) 1579 } 1580 } 1581 1582 func GRPCReadAuthorizationModelsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 1583 storeID := ulid.Make().String() 1584 1585 _, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1586 StoreId: storeID, 1587 TypeDefinitions: []*openfgav1.TypeDefinition{ 1588 { 1589 Type: "user", 1590 }, 1591 { 1592 Type: "document", 1593 Relations: map[string]*openfgav1.Userset{ 1594 "viewer": {Userset: &openfgav1.Userset_This{}}, 1595 }, 1596 Metadata: &openfgav1.Metadata{ 1597 Relations: map[string]*openfgav1.RelationMetadata{ 1598 "viewer": { 1599 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1600 typesystem.DirectRelationReference("user", ""), 1601 }, 1602 }, 1603 }, 1604 }, 1605 }, 1606 }, 1607 1608 SchemaVersion: typesystem.SchemaVersion1_1, 1609 }) 1610 require.NoError(t, err) 1611 1612 _, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1613 StoreId: storeID, 1614 TypeDefinitions: []*openfgav1.TypeDefinition{ 1615 { 1616 Type: "user", 1617 }, 1618 { 1619 Type: "document", 1620 Relations: map[string]*openfgav1.Userset{ 1621 "editor": {Userset: &openfgav1.Userset_This{}}, 1622 }, 1623 Metadata: &openfgav1.Metadata{ 1624 Relations: map[string]*openfgav1.RelationMetadata{ 1625 "editor": { 1626 DirectlyRelatedUserTypes: []*openfgav1.RelationReference{ 1627 typesystem.DirectRelationReference("user", ""), 1628 }, 1629 }, 1630 }, 1631 }, 1632 }, 1633 }, 1634 SchemaVersion: typesystem.SchemaVersion1_1, 1635 }) 1636 require.NoError(t, err) 1637 1638 resp1, err := client.ReadAuthorizationModels(context.Background(), &openfgav1.ReadAuthorizationModelsRequest{ 1639 StoreId: storeID, 1640 PageSize: wrapperspb.Int32(1), 1641 }) 1642 require.NoError(t, err) 1643 1644 require.Len(t, resp1.GetAuthorizationModels(), 1) 1645 require.NotEmpty(t, resp1.GetContinuationToken()) 1646 1647 resp2, err := client.ReadAuthorizationModels(context.Background(), &openfgav1.ReadAuthorizationModelsRequest{ 1648 StoreId: storeID, 1649 ContinuationToken: resp1.GetContinuationToken(), 1650 }) 1651 require.NoError(t, err) 1652 1653 require.Len(t, resp2.GetAuthorizationModels(), 1) 1654 require.Empty(t, resp2.GetContinuationToken()) 1655 } 1656 1657 func GRPCWriteAuthorizationModelTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 1658 type output struct { 1659 errorCode codes.Code 1660 } 1661 1662 tests := []struct { 1663 name string 1664 input *openfgav1.WriteAuthorizationModelRequest 1665 output output 1666 }{ 1667 { 1668 name: "empty_request", 1669 input: &openfgav1.WriteAuthorizationModelRequest{}, 1670 output: output{ 1671 errorCode: codes.InvalidArgument, 1672 }, 1673 }, 1674 { 1675 name: "invalid_storeID_because_too_short", 1676 input: &openfgav1.WriteAuthorizationModelRequest{ 1677 StoreId: "1", 1678 }, 1679 output: output{ 1680 errorCode: codes.InvalidArgument, 1681 }, 1682 }, 1683 { 1684 name: "invalid_storeID_because_extra_chars", 1685 input: &openfgav1.WriteAuthorizationModelRequest{ 1686 StoreId: ulid.Make().String() + "A", 1687 }, 1688 output: output{ 1689 errorCode: codes.InvalidArgument, 1690 }, 1691 }, 1692 { 1693 name: "missing_type_definitions", 1694 input: &openfgav1.WriteAuthorizationModelRequest{ 1695 StoreId: ulid.Make().String(), 1696 }, 1697 output: output{ 1698 errorCode: codes.InvalidArgument, 1699 }, 1700 }, 1701 { 1702 name: "zero_type_definitions", 1703 input: &openfgav1.WriteAuthorizationModelRequest{ 1704 StoreId: ulid.Make().String(), 1705 TypeDefinitions: []*openfgav1.TypeDefinition{}, 1706 }, 1707 output: output{ 1708 errorCode: codes.InvalidArgument, 1709 }, 1710 }, 1711 { 1712 name: "invalid_type_definition_because_empty_type_name", 1713 input: &openfgav1.WriteAuthorizationModelRequest{ 1714 StoreId: ulid.Make().String(), 1715 TypeDefinitions: []*openfgav1.TypeDefinition{ 1716 { 1717 Type: "", 1718 Relations: map[string]*openfgav1.Userset{ 1719 "viewer": {Userset: &openfgav1.Userset_This{}}, 1720 }, 1721 }, 1722 }, 1723 }, 1724 output: output{ 1725 errorCode: codes.InvalidArgument, 1726 }, 1727 }, 1728 { 1729 name: "invalid_type_definition_because_too_many_chars_in_name", 1730 input: &openfgav1.WriteAuthorizationModelRequest{ 1731 StoreId: ulid.Make().String(), 1732 TypeDefinitions: []*openfgav1.TypeDefinition{ 1733 { 1734 Type: testutils.CreateRandomString(255), 1735 Relations: map[string]*openfgav1.Userset{ 1736 "viewer": {Userset: &openfgav1.Userset_This{}}, 1737 }, 1738 }, 1739 }, 1740 }, 1741 output: output{ 1742 errorCode: codes.InvalidArgument, 1743 }, 1744 }, 1745 { 1746 name: "invalid_type_definition_because_invalid_chars_in_name", 1747 input: &openfgav1.WriteAuthorizationModelRequest{ 1748 StoreId: ulid.Make().String(), 1749 TypeDefinitions: []*openfgav1.TypeDefinition{ 1750 { 1751 Type: "some type", 1752 Relations: map[string]*openfgav1.Userset{ 1753 "viewer": {Userset: &openfgav1.Userset_This{}}, 1754 }, 1755 }, 1756 }, 1757 }, 1758 output: output{ 1759 errorCode: codes.InvalidArgument, 1760 }, 1761 }, 1762 } 1763 1764 for _, test := range tests { 1765 t.Run(test.name, func(t *testing.T) { 1766 response, err := client.WriteAuthorizationModel(context.Background(), test.input) 1767 1768 s, ok := status.FromError(err) 1769 require.True(t, ok) 1770 require.Equal(t, test.output.errorCode.String(), s.Code().String()) 1771 1772 if test.output.errorCode == codes.OK { 1773 _, err = ulid.Parse(response.GetAuthorizationModelId()) 1774 require.NoError(t, err) 1775 } else { 1776 require.Error(t, err) 1777 } 1778 }) 1779 } 1780 } 1781 1782 func GRPCWriteAssertionsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) { 1783 type testData struct { 1784 model string 1785 } 1786 type output struct { 1787 statusCode codes.Code 1788 errorMessage string 1789 } 1790 1791 tests := []struct { 1792 name string 1793 input *openfgav1.WriteAssertionsRequest 1794 testData *testData 1795 output output 1796 }{ 1797 { 1798 name: "happy_path", 1799 testData: &testData{ 1800 model: `model 1801 schema 1.1 1802 type user 1803 1804 type document 1805 relations 1806 define viewer: [user]`, 1807 }, 1808 input: &openfgav1.WriteAssertionsRequest{ 1809 StoreId: ulid.Make().String(), 1810 AuthorizationModelId: ulid.Make().String(), 1811 Assertions: []*openfgav1.Assertion{ 1812 {Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{ 1813 Object: "document:1", 1814 Relation: "viewer", 1815 User: "user:anne", 1816 }}, 1817 }, 1818 }, 1819 output: output{ 1820 statusCode: codes.OK, 1821 }, 1822 }, 1823 { 1824 name: "empty_request", 1825 input: &openfgav1.WriteAssertionsRequest{}, 1826 output: output{ 1827 statusCode: codes.InvalidArgument, 1828 }, 1829 }, 1830 { 1831 name: "invalid_storeID_because_too_short", 1832 input: &openfgav1.WriteAssertionsRequest{ 1833 StoreId: "1", 1834 AuthorizationModelId: ulid.Make().String(), 1835 }, 1836 output: output{ 1837 statusCode: codes.InvalidArgument, 1838 }, 1839 }, 1840 { 1841 name: "invalid_storeID_because_extra_chars", 1842 input: &openfgav1.WriteAssertionsRequest{ 1843 StoreId: ulid.Make().String() + "A", 1844 AuthorizationModelId: ulid.Make().String(), 1845 }, 1846 output: output{ 1847 statusCode: codes.InvalidArgument, 1848 }, 1849 }, 1850 { 1851 name: "invalid_storeID_because_invalid_chars", 1852 input: &openfgav1.WriteAssertionsRequest{ 1853 StoreId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 1854 AuthorizationModelId: ulid.Make().String(), 1855 }, 1856 output: output{ 1857 statusCode: codes.InvalidArgument, 1858 }, 1859 }, 1860 { 1861 name: "invalid_authorization_model_ID_because_extra_chars", 1862 input: &openfgav1.WriteAssertionsRequest{ 1863 StoreId: ulid.Make().String(), 1864 AuthorizationModelId: ulid.Make().String() + "A", 1865 }, 1866 output: output{ 1867 statusCode: codes.InvalidArgument, 1868 }, 1869 }, 1870 { 1871 name: "invalid_authorization_model_ID_because_invalid_chars", 1872 input: &openfgav1.WriteAssertionsRequest{ 1873 StoreId: ulid.Make().String(), 1874 AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@", 1875 }, 1876 output: output{ 1877 statusCode: codes.InvalidArgument, 1878 }, 1879 }, 1880 { 1881 name: "missing_user_in_assertion", 1882 input: &openfgav1.WriteAssertionsRequest{ 1883 StoreId: ulid.Make().String(), 1884 AuthorizationModelId: ulid.Make().String(), 1885 Assertions: []*openfgav1.Assertion{ 1886 {Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{ 1887 Object: "obj:1", 1888 Relation: "viewer", 1889 }}, 1890 }, 1891 }, 1892 output: output{ 1893 statusCode: codes.InvalidArgument, 1894 }, 1895 }, 1896 { 1897 name: "missing_relation_in_assertion", 1898 input: &openfgav1.WriteAssertionsRequest{ 1899 StoreId: ulid.Make().String(), 1900 AuthorizationModelId: ulid.Make().String(), 1901 Assertions: []*openfgav1.Assertion{ 1902 {Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{ 1903 Object: "obj:1", 1904 User: "user:anne", 1905 }}, 1906 }, 1907 }, 1908 output: output{ 1909 statusCode: codes.InvalidArgument, 1910 }, 1911 }, 1912 { 1913 name: "missing_object_in_assertion", 1914 input: &openfgav1.WriteAssertionsRequest{ 1915 StoreId: ulid.Make().String(), 1916 AuthorizationModelId: ulid.Make().String(), 1917 Assertions: []*openfgav1.Assertion{ 1918 {Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{ 1919 Relation: "viewer", 1920 User: "user:anne", 1921 }}, 1922 }, 1923 }, 1924 output: output{ 1925 statusCode: codes.InvalidArgument, 1926 }, 1927 }, 1928 { 1929 name: "model_not_found", 1930 input: &openfgav1.WriteAssertionsRequest{ 1931 StoreId: ulid.Make().String(), 1932 AuthorizationModelId: ulid.Make().String(), 1933 Assertions: []*openfgav1.Assertion{ 1934 {Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{ 1935 Object: "obj:1", 1936 Relation: "viewer", 1937 User: "user:anne", 1938 }}, 1939 }, 1940 }, 1941 output: output{ 1942 statusCode: 2001, // ErrorCode_authorization_model_not_found 1943 }, 1944 }, 1945 } 1946 1947 for _, test := range tests { 1948 t.Run(test.name, func(t *testing.T) { 1949 if test.testData != nil { 1950 modelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{ 1951 StoreId: test.input.GetStoreId(), 1952 SchemaVersion: typesystem.SchemaVersion1_1, 1953 TypeDefinitions: parser.MustTransformDSLToProto(test.testData.model).GetTypeDefinitions(), 1954 }) 1955 test.input.AuthorizationModelId = modelResp.GetAuthorizationModelId() 1956 require.NoError(t, err) 1957 } 1958 _, err := client.WriteAssertions(context.Background(), test.input) 1959 1960 s, ok := status.FromError(err) 1961 1962 require.True(t, ok) 1963 require.Equal(t, test.output.statusCode.String(), s.Code().String()) 1964 1965 if s.Code() == codes.OK { 1966 require.NoError(t, err) 1967 } else { 1968 require.Error(t, err) 1969 require.Contains(t, err.Error(), test.output.errorMessage) 1970 } 1971 }) 1972 } 1973 }