go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth_service/impl/servers/groups/server_test.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package groups 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "google.golang.org/grpc/codes" 23 "google.golang.org/protobuf/types/known/emptypb" 24 fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 "go.chromium.org/luci/gae/filter/txndefer" 28 "go.chromium.org/luci/gae/impl/memory" 29 "go.chromium.org/luci/gae/service/datastore" 30 "go.chromium.org/luci/server/auth" 31 "go.chromium.org/luci/server/auth/authtest" 32 "go.chromium.org/luci/server/tq" 33 34 "go.chromium.org/luci/auth_service/api/rpcpb" 35 "go.chromium.org/luci/auth_service/impl/info" 36 "go.chromium.org/luci/auth_service/impl/model" 37 38 . "github.com/smartystreets/goconvey/convey" 39 . "go.chromium.org/luci/common/testing/assertions" 40 ) 41 42 func TestGroupsServer(t *testing.T) { 43 t.Parallel() 44 srv := Server{} 45 createdTime := time.Date(2021, time.August, 16, 15, 20, 0, 0, time.UTC) 46 modifiedTime := time.Date(2022, time.July, 4, 15, 45, 0, 0, time.UTC) 47 versionedEntity := model.AuthVersionedEntityMixin{ 48 ModifiedTS: modifiedTime, 49 } 50 // Etag derived from the above modified time. 51 etag := `W/"MjAyMi0wNy0wNFQxNTo0NTowMFo="` 52 53 Convey("ListGroups RPC call", t, func() { 54 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{ 55 Identity: "user:someone@example.com", 56 IdentityGroups: []string{"testers"}, 57 }) 58 59 // Groups built from model.AuthGroup definition. 60 So(datastore.Put(ctx, 61 &model.AuthGroup{ 62 ID: "z-test-group", 63 Parent: model.RootKey(ctx), 64 Members: []string{ 65 "user:test-user-1", 66 "user:test-user-2", 67 }, 68 Globs: []string{ 69 "test-user-1@example.com", 70 "test-user-2@example.com", 71 }, 72 Nested: []string{ 73 "group/tester", 74 }, 75 Description: "This is a test group.", 76 Owners: "testers", 77 CreatedTS: createdTime, 78 CreatedBy: "user:test-user-1@example.com", 79 AuthVersionedEntityMixin: versionedEntity, 80 }, 81 &model.AuthGroup{ 82 ID: "test-group-2", 83 Parent: model.RootKey(ctx), 84 Members: []string{ 85 "user:test-user-2", 86 }, 87 Globs: []string{ 88 "test-user-2@example.com", 89 }, 90 Nested: []string{ 91 "group/test-group", 92 }, 93 Description: "This is another test group.", 94 Owners: "test-group", 95 CreatedTS: createdTime, 96 CreatedBy: "user:test-user-2@example.com", 97 AuthVersionedEntityMixin: versionedEntity, 98 }, 99 &model.AuthGroup{ 100 ID: "test-group-3", 101 Parent: model.RootKey(ctx), 102 Members: []string{}, 103 Globs: []string{}, 104 Nested: []string{ 105 "group/tester", 106 }, 107 Description: "This is yet another test group.", 108 Owners: "testers", 109 CreatedTS: createdTime, 110 CreatedBy: "user:test-user-1@example.com", 111 AuthVersionedEntityMixin: versionedEntity, 112 }), ShouldBeNil) 113 114 // What expected response should be, built with pb. 115 expectedResp := &rpcpb.ListGroupsResponse{ 116 Groups: []*rpcpb.AuthGroup{ 117 { 118 Name: "test-group-2", 119 Description: "This is another test group.", 120 Owners: "test-group", 121 CreatedTs: timestamppb.New(createdTime), 122 CreatedBy: "user:test-user-2@example.com", 123 CallerCanModify: false, 124 Etag: etag, 125 }, 126 { 127 Name: "test-group-3", 128 Description: "This is yet another test group.", 129 Owners: "testers", 130 CreatedTs: timestamppb.New(createdTime), 131 CreatedBy: "user:test-user-1@example.com", 132 CallerCanModify: true, 133 Etag: etag, 134 }, 135 { 136 Name: "z-test-group", 137 Description: "This is a test group.", 138 Owners: "testers", 139 CreatedTs: timestamppb.New(createdTime), 140 CreatedBy: "user:test-user-1@example.com", 141 CallerCanModify: true, 142 Etag: etag, 143 }, 144 }, 145 } 146 147 resp, err := srv.ListGroups(ctx, &emptypb.Empty{}) 148 So(err, ShouldBeNil) 149 So(resp.Groups, ShouldResembleProto, expectedResp.Groups) 150 }) 151 152 Convey("GetGroup RPC call", t, func() { 153 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{ 154 Identity: "user:someone@example.com", 155 IdentityGroups: []string{"testers"}, 156 }) 157 158 request := &rpcpb.GetGroupRequest{ 159 Name: "test-group", 160 } 161 162 _, err := srv.GetGroup(ctx, request) 163 So(err, ShouldHaveGRPCStatus, codes.NotFound) 164 165 // Groups built from model.AuthGroup definition. 166 So(datastore.Put(ctx, 167 &model.AuthGroup{ 168 ID: "test-group", 169 Parent: model.RootKey(ctx), 170 Members: []string{ 171 "user:test-user-1", 172 "user:test-user-2", 173 }, 174 Globs: []string{ 175 "test-user-1@example.com", 176 "test-user-2@example.com", 177 }, 178 Nested: []string{ 179 "group/tester", 180 }, 181 Description: "This is a test group.", 182 Owners: "testers", 183 CreatedTS: createdTime, 184 CreatedBy: "user:test-user-1@example.com", 185 AuthVersionedEntityMixin: versionedEntity, 186 }), ShouldBeNil) 187 188 expectedResponse := &rpcpb.AuthGroup{ 189 Name: "test-group", 190 Members: []string{ 191 "user:test-user-1", 192 "user:test-user-2", 193 }, 194 Globs: []string{ 195 "test-user-1@example.com", 196 "test-user-2@example.com", 197 }, 198 Nested: []string{ 199 "group/tester", 200 }, 201 Description: "This is a test group.", 202 Owners: "testers", 203 CreatedTs: timestamppb.New(createdTime), 204 CreatedBy: "user:test-user-1@example.com", 205 CallerCanModify: true, 206 Etag: etag, 207 } 208 209 actualGroupResponse, err := srv.GetGroup(ctx, request) 210 So(err, ShouldBeNil) 211 So(actualGroupResponse, ShouldResembleProto, expectedResponse) 212 213 }) 214 215 Convey("CreateGroup RPC call", t, func() { 216 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{ 217 Identity: "user:someone@example.com", 218 }) 219 ctx = info.SetImageVersion(ctx, "test-version") 220 ctx, _ = tq.TestingContext(txndefer.FilterRDS(ctx), nil) 221 222 Convey("Invalid name", func() { 223 request := &rpcpb.CreateGroupRequest{ 224 Group: &rpcpb.AuthGroup{ 225 Name: "#^&", 226 Description: "This is a group with an invalid name", 227 }, 228 } 229 _, err := srv.CreateGroup(ctx, request) 230 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 231 }) 232 233 Convey("Invalid name (looks like external group)", func() { 234 request := &rpcpb.CreateGroupRequest{ 235 Group: &rpcpb.AuthGroup{ 236 Name: "mdb/foo", 237 Description: "This is a group with an invalid name", 238 }, 239 } 240 _, err := srv.CreateGroup(ctx, request) 241 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 242 }) 243 244 Convey("Invalid members", func() { 245 request := &rpcpb.CreateGroupRequest{ 246 Group: &rpcpb.AuthGroup{ 247 Name: "test-group", 248 Description: "This is a group with invalid members.", 249 Owners: "test-group", 250 Members: []string{"no-prefix@identity.com"}, 251 }, 252 } 253 _, err := srv.CreateGroup(ctx, request) 254 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 255 }) 256 257 Convey("Invalid globs", func() { 258 request := &rpcpb.CreateGroupRequest{ 259 Group: &rpcpb.AuthGroup{ 260 Name: "test-group", 261 Description: "This is a group with invalid members.", 262 Owners: "test-group", 263 Globs: []string{"*@no-prefix.com"}, 264 }, 265 } 266 _, err := srv.CreateGroup(ctx, request) 267 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 268 }) 269 270 Convey("Group already exists", func() { 271 So(datastore.Put(ctx, 272 &model.AuthGroup{ 273 ID: "test-group", 274 Parent: model.RootKey(ctx), 275 Description: "This is a test group.", 276 Owners: "testers", 277 CreatedTS: createdTime, 278 CreatedBy: "user:test-user-1@example.com", 279 }), ShouldBeNil) 280 281 request := &rpcpb.CreateGroupRequest{ 282 Group: &rpcpb.AuthGroup{ 283 Name: "test-group", 284 Description: "This is a group that already exists", 285 }, 286 } 287 _, err := srv.CreateGroup(ctx, request) 288 So(err, ShouldHaveGRPCStatus, codes.AlreadyExists) 289 }) 290 291 Convey("Group refers to another group that doesn't exist", func() { 292 request := &rpcpb.CreateGroupRequest{ 293 Group: &rpcpb.AuthGroup{ 294 Name: "test-group", 295 Description: "This is a test group.", 296 Owners: "invalid-owner", 297 Nested: []string{"bad1, bad2"}, 298 }, 299 } 300 _, err := srv.CreateGroup(ctx, request) 301 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 302 So(err, ShouldErrLike, "some referenced groups don't exist: bad1, bad2, invalid-owner") 303 }) 304 305 Convey("Successful creation", func() { 306 request := &rpcpb.CreateGroupRequest{ 307 Group: &rpcpb.AuthGroup{ 308 Name: "test-group", 309 Description: "This is a test group.", 310 Owners: "test-group", 311 }, 312 } 313 314 resp, err := srv.CreateGroup(ctx, request) 315 So(err, ShouldBeNil) 316 So(resp.Name, ShouldEqual, "test-group") 317 So(resp.Description, ShouldEqual, "This is a test group.") 318 So(resp.Owners, ShouldEqual, "test-group") 319 So(resp.CreatedBy, ShouldEqual, "user:someone@example.com") 320 So(resp.CreatedTs.Seconds, ShouldNotBeZeroValue) 321 }) 322 }) 323 324 Convey("UpdateGroup RPC call", t, func() { 325 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{ 326 Identity: "user:someone@example.com", 327 IdentityGroups: []string{"owners"}, 328 }) 329 ctx = info.SetImageVersion(ctx, "test-version") 330 ctx, _ = tq.TestingContext(txndefer.FilterRDS(ctx), nil) 331 332 So(datastore.Put(ctx, 333 &model.AuthGroup{ 334 ID: "test-group", 335 Parent: model.RootKey(ctx), 336 Members: []string{ 337 "user:test-user-1", 338 "user:test-user-2", 339 }, 340 Globs: []string{ 341 "test-user-1@example.com", 342 "test-user-2@example.com", 343 }, 344 Nested: []string{ 345 "group/tester", 346 }, 347 Description: "This is a test group.", 348 Owners: "owners", 349 CreatedTS: createdTime, 350 CreatedBy: "user:test-user-1@example.com", 351 AuthVersionedEntityMixin: versionedEntity, 352 }), ShouldBeNil) 353 354 Convey("Cannot update external group", func() { 355 request := &rpcpb.UpdateGroupRequest{ 356 Group: &rpcpb.AuthGroup{ 357 Name: "mdb/foo", 358 Description: "update", 359 }, 360 } 361 362 _, err := srv.UpdateGroup(ctx, request) 363 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 364 So(err, ShouldErrLike, "cannot update external group") 365 }) 366 367 Convey("Group not found", func() { 368 request := &rpcpb.UpdateGroupRequest{ 369 Group: &rpcpb.AuthGroup{ 370 Name: "non-existent-group", 371 Description: "update", 372 }, 373 } 374 375 _, err := srv.UpdateGroup(ctx, request) 376 So(err, ShouldHaveGRPCStatus, codes.NotFound) 377 }) 378 379 Convey("Invalid field mask", func() { 380 request := &rpcpb.UpdateGroupRequest{ 381 Group: &rpcpb.AuthGroup{ 382 Name: "test-group", 383 Description: "update", 384 }, 385 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"bad"}}, 386 } 387 388 _, err := srv.UpdateGroup(ctx, request) 389 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 390 }) 391 392 Convey("Set owners to group that doesn't exist", func() { 393 request := &rpcpb.UpdateGroupRequest{ 394 Group: &rpcpb.AuthGroup{ 395 Name: "test-group", 396 Owners: "non-existent", 397 }, 398 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"owners"}}, 399 } 400 401 _, err := srv.UpdateGroup(ctx, request) 402 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 403 }) 404 405 Convey("Set invalid member identity", func() { 406 request := &rpcpb.UpdateGroupRequest{ 407 Group: &rpcpb.AuthGroup{ 408 Name: "test-group", 409 Members: []string{"bad"}, 410 }, 411 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"members"}}, 412 } 413 414 _, err := srv.UpdateGroup(ctx, request) 415 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 416 }) 417 418 Convey("Cyclic dependency", func() { 419 request := &rpcpb.UpdateGroupRequest{ 420 Group: &rpcpb.AuthGroup{ 421 Name: "test-group", 422 Nested: []string{"test-group"}, 423 }, 424 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"nested"}}, 425 } 426 427 _, err := srv.UpdateGroup(ctx, request) 428 So(err, ShouldHaveGRPCStatus, codes.FailedPrecondition) 429 }) 430 431 Convey("Permissions", func() { 432 request := &rpcpb.UpdateGroupRequest{ 433 Group: &rpcpb.AuthGroup{ 434 Name: "test-group", 435 Description: "update", 436 }, 437 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"description"}}, 438 } 439 Convey("Anonymous is denied", func() { 440 ctx := auth.WithState(ctx, &authtest.FakeState{}) 441 _, err := srv.UpdateGroup(ctx, request) 442 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 443 }) 444 445 Convey("Normal user is denied", func() { 446 ctx := auth.WithState(ctx, &authtest.FakeState{ 447 Identity: "user:someone@example.com", 448 }) 449 _, err := srv.UpdateGroup(ctx, request) 450 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 451 }) 452 453 Convey("Group owner succeeds", func() { 454 ctx := auth.WithState(ctx, &authtest.FakeState{ 455 Identity: "user:someone@example.com", 456 IdentityGroups: []string{"owners"}, 457 }) 458 _, err := srv.UpdateGroup(ctx, request) 459 So(err, ShouldBeNil) 460 }) 461 462 Convey("Admin succeeds", func() { 463 ctx := auth.WithState(ctx, &authtest.FakeState{ 464 Identity: "user:someone@example.com", 465 IdentityGroups: []string{model.AdminGroup}, 466 }) 467 _, err := srv.UpdateGroup(ctx, request) 468 So(err, ShouldBeNil) 469 }) 470 }) 471 472 Convey("Etags", func() { 473 request := &rpcpb.UpdateGroupRequest{ 474 Group: &rpcpb.AuthGroup{ 475 Name: "test-group", 476 Description: "update", 477 }, 478 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"description"}}, 479 } 480 481 Convey("Incorrect etag is aborted", func() { 482 request.Group.Etag = "blah" 483 _, err := srv.UpdateGroup(ctx, request) 484 So(err, ShouldHaveGRPCStatus, codes.Aborted) 485 }) 486 487 Convey("Empty etag succeeds", func() { 488 request.Group.Etag = "" 489 _, err := srv.UpdateGroup(ctx, request) 490 So(err, ShouldBeNil) 491 }) 492 493 Convey("Correct etag succeeds if present", func() { 494 request.Group.Etag = etag 495 _, err := srv.UpdateGroup(ctx, request) 496 So(err, ShouldBeNil) 497 }) 498 }) 499 }) 500 501 Convey("DeleteGroup RPC call", t, func() { 502 ctx := memory.Use(context.Background()) 503 ctx = info.SetImageVersion(ctx, "test-version") 504 ctx, _ = tq.TestingContext(txndefer.FilterRDS(ctx), nil) 505 506 So(datastore.Put(ctx, 507 &model.AuthGroup{ 508 ID: "test-group", 509 Parent: model.RootKey(ctx), 510 Members: []string{ 511 "user:test-user-1", 512 "user:test-user-2", 513 }, 514 Globs: []string{ 515 "test-user-1@example.com", 516 "test-user-2@example.com", 517 }, 518 Nested: []string{ 519 "group/tester", 520 }, 521 Description: "This is a test group.", 522 Owners: "owners", 523 CreatedTS: createdTime, 524 CreatedBy: "user:test-user-1@example.com", 525 AuthVersionedEntityMixin: versionedEntity, 526 }), ShouldBeNil) 527 528 Convey("Cannot delete external group", func() { 529 request := &rpcpb.DeleteGroupRequest{ 530 Name: "mdb/foo", 531 } 532 533 _, err := srv.DeleteGroup(ctx, request) 534 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 535 So(err, ShouldErrLike, "cannot delete external group") 536 }) 537 538 Convey("Group not found", func() { 539 request := &rpcpb.DeleteGroupRequest{ 540 Name: "non-existent-group", 541 } 542 543 _, err := srv.DeleteGroup(ctx, request) 544 So(err, ShouldHaveGRPCStatus, codes.NotFound) 545 }) 546 547 Convey("Group referenced elsewhere", func() { 548 ctx := auth.WithState(ctx, &authtest.FakeState{ 549 Identity: "user:someone@example.com", 550 IdentityGroups: []string{"owners"}, 551 }) 552 So(datastore.Put(ctx, 553 &model.AuthGroup{ 554 ID: "nesting-group", 555 Parent: model.RootKey(ctx), 556 Nested: []string{ 557 "test-group", 558 }, 559 }), ShouldBeNil) 560 request := &rpcpb.DeleteGroupRequest{ 561 Name: "test-group", 562 } 563 564 _, err := srv.DeleteGroup(ctx, request) 565 So(err, ShouldHaveGRPCStatus, codes.FailedPrecondition) 566 }) 567 568 Convey("Permissions", func() { 569 570 Convey("Anonymous is denied", func() { 571 ctx := auth.WithState(ctx, &authtest.FakeState{}) 572 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 573 Name: "test-group", 574 }) 575 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 576 }) 577 578 Convey("Normal user is denied", func() { 579 ctx := auth.WithState(ctx, &authtest.FakeState{ 580 Identity: "user:someone@example.com", 581 }) 582 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 583 Name: "test-group", 584 }) 585 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 586 }) 587 588 Convey("Group owner succeeds", func() { 589 ctx := auth.WithState(ctx, &authtest.FakeState{ 590 Identity: "user:someone@example.com", 591 IdentityGroups: []string{"owners"}, 592 }) 593 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 594 Name: "test-group", 595 }) 596 So(err, ShouldBeNil) 597 }) 598 599 Convey("Admin succeeds", func() { 600 ctx := auth.WithState(ctx, &authtest.FakeState{ 601 Identity: "user:someone@example.com", 602 IdentityGroups: []string{model.AdminGroup}, 603 }) 604 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 605 Name: "test-group", 606 }) 607 So(err, ShouldBeNil) 608 }) 609 }) 610 611 Convey("Etags", func() { 612 ctx := auth.WithState(ctx, &authtest.FakeState{ 613 Identity: "user:someone@example.com", 614 IdentityGroups: []string{"owners"}, 615 }) 616 617 Convey("Incorrect etag is aborted", func() { 618 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 619 Name: "test-group", 620 Etag: "blah", 621 }) 622 So(err, ShouldHaveGRPCStatus, codes.Aborted) 623 }) 624 625 Convey("Empty etag succeeds", func() { 626 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 627 Name: "test-group", 628 }) 629 So(err, ShouldBeNil) 630 }) 631 632 Convey("Correct etag succeeds if present", func() { 633 _, err := srv.DeleteGroup(ctx, &rpcpb.DeleteGroupRequest{ 634 Name: "test-group", 635 Etag: etag, 636 }) 637 So(err, ShouldBeNil) 638 }) 639 }) 640 }) 641 642 Convey("GetSubgraph RPC call", t, func() { 643 const ( 644 // Identities, groups, globs 645 owningGroup = "owning-group" 646 nestedGroup = "nested-group" 647 soloGroup = "solo-group" 648 testUser0 = "user:m0@example.com" 649 testUser1 = "user:m1@example.com" 650 testUser2 = "user:t2@example.com" 651 testGlob0 = "user:m*@example.com" 652 testGlob1 = "user:t2*" 653 ) 654 655 ctx := memory.Use(context.Background()) 656 So(datastore.Put(ctx, 657 &model.AuthGroup{ 658 ID: owningGroup, 659 Parent: model.RootKey(ctx), 660 Members: []string{ 661 testUser0, 662 testUser1, 663 }, 664 Nested: []string{ 665 nestedGroup, 666 }, 667 }, 668 &model.AuthGroup{ 669 ID: soloGroup, 670 Parent: model.RootKey(ctx), 671 Globs: []string{ 672 testGlob1, 673 }, 674 }, 675 &model.AuthGroup{ 676 ID: nestedGroup, 677 Parent: model.RootKey(ctx), 678 Members: []string{ 679 testUser2, 680 }, 681 Globs: []string{ 682 testGlob0, 683 testGlob1, 684 }, 685 Owners: owningGroup, 686 }), ShouldBeNil) 687 688 Convey("Identity principal", func() { 689 request := &rpcpb.GetSubgraphRequest{ 690 Principal: &rpcpb.Principal{ 691 Kind: rpcpb.PrincipalKind_IDENTITY, 692 Name: testUser0, 693 }, 694 } 695 696 actualSubgraph, err := srv.GetSubgraph(ctx, request) 697 So(err, ShouldBeNil) 698 699 expectedSubgraph := &rpcpb.Subgraph{ 700 Nodes: []*rpcpb.Node{ 701 { 702 Principal: &rpcpb.Principal{ 703 Kind: rpcpb.PrincipalKind_IDENTITY, 704 Name: testUser0, 705 }, 706 IncludedBy: []int32{1, 3}, 707 }, 708 { 709 Principal: &rpcpb.Principal{ 710 Kind: rpcpb.PrincipalKind_GLOB, 711 Name: testGlob0, 712 }, 713 IncludedBy: []int32{2}, 714 }, 715 { 716 Principal: &rpcpb.Principal{ 717 Kind: rpcpb.PrincipalKind_GROUP, 718 Name: nestedGroup, 719 }, 720 IncludedBy: []int32{3}, 721 }, 722 { 723 Principal: &rpcpb.Principal{ 724 Kind: rpcpb.PrincipalKind_GROUP, 725 Name: owningGroup, 726 }, 727 }, 728 }, 729 } 730 731 So(actualSubgraph, ShouldResemble, expectedSubgraph) 732 }) 733 734 Convey("Group principal", func() { 735 request := rpcpb.GetSubgraphRequest{ 736 Principal: &rpcpb.Principal{ 737 Kind: rpcpb.PrincipalKind_GROUP, 738 Name: nestedGroup, 739 }, 740 } 741 742 actualSubgraph, err := srv.GetSubgraph(ctx, &request) 743 So(err, ShouldBeNil) 744 745 expectedSubgraph := &rpcpb.Subgraph{ 746 Nodes: []*rpcpb.Node{ 747 { 748 Principal: request.Principal, 749 IncludedBy: []int32{1}, 750 }, 751 { 752 Principal: &rpcpb.Principal{ 753 Kind: rpcpb.PrincipalKind_GROUP, 754 Name: owningGroup, 755 }, 756 }, 757 }, 758 } 759 760 So(actualSubgraph, ShouldResemble, expectedSubgraph) 761 762 }) 763 764 Convey("Glob principal", func() { 765 request := rpcpb.GetSubgraphRequest{ 766 Principal: &rpcpb.Principal{ 767 Kind: rpcpb.PrincipalKind_GLOB, 768 Name: testGlob1, 769 }, 770 } 771 772 actualSubgraph, err := srv.GetSubgraph(ctx, &request) 773 So(err, ShouldBeNil) 774 775 expectedSubgraph := &rpcpb.Subgraph{ 776 Nodes: []*rpcpb.Node{ 777 { 778 Principal: request.Principal, 779 IncludedBy: []int32{1, 3}, 780 }, 781 { 782 Principal: &rpcpb.Principal{ 783 Kind: rpcpb.PrincipalKind_GROUP, 784 Name: nestedGroup, 785 }, 786 IncludedBy: []int32{2}, 787 }, 788 { 789 Principal: &rpcpb.Principal{ 790 Kind: rpcpb.PrincipalKind_GROUP, 791 Name: owningGroup, 792 }, 793 }, 794 { 795 Principal: &rpcpb.Principal{ 796 Kind: rpcpb.PrincipalKind_GROUP, 797 Name: soloGroup, 798 }, 799 }, 800 }, 801 } 802 803 So(actualSubgraph, ShouldResemble, expectedSubgraph) 804 }) 805 806 Convey("Unspecified Principal kind", func() { 807 request := rpcpb.GetSubgraphRequest{ 808 Principal: &rpcpb.Principal{ 809 Kind: rpcpb.PrincipalKind_PRINCIPAL_KIND_UNSPECIFIED, 810 Name: "aeua//", 811 }, 812 } 813 814 _, err := srv.GetSubgraph(ctx, &request) 815 So(err.Error(), ShouldContainSubstring, "invalid principal kind") 816 817 }) 818 819 Convey("Group principal not in groups graph", func() { 820 request := rpcpb.GetSubgraphRequest{ 821 Principal: &rpcpb.Principal{ 822 Kind: rpcpb.PrincipalKind_GROUP, 823 Name: "i-dont-exist", 824 }, 825 } 826 827 _, err := srv.GetSubgraph(ctx, &request) 828 So(err.Error(), ShouldContainSubstring, "no such group") 829 }) 830 }) 831 }