go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth_service/impl/model/changelog_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 model 16 17 import ( 18 "context" 19 "sort" 20 "testing" 21 "time" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 "google.golang.org/protobuf/proto" 26 27 "go.chromium.org/luci/common/clock" 28 "go.chromium.org/luci/common/clock/testclock" 29 "go.chromium.org/luci/common/errors" 30 "go.chromium.org/luci/gae/filter/txndefer" 31 "go.chromium.org/luci/gae/impl/memory" 32 "go.chromium.org/luci/gae/service/datastore" 33 "go.chromium.org/luci/server/auth" 34 "go.chromium.org/luci/server/auth/authtest" 35 "go.chromium.org/luci/server/auth/service/protocol" 36 "go.chromium.org/luci/server/tq" 37 38 "go.chromium.org/luci/auth_service/api/configspb" 39 "go.chromium.org/luci/auth_service/impl/info" 40 41 . "github.com/smartystreets/goconvey/convey" 42 . "go.chromium.org/luci/common/testing/assertions" 43 ) 44 45 func testAuthDBGroupChange(ctx context.Context, target string, changeType ChangeType, authDBRev int64) *AuthDBChange { 46 change := &AuthDBChange{ 47 Kind: "AuthDBChange", 48 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 49 Class: []string{"AuthDBChange", "AuthDBGroupChange"}, 50 ChangeType: changeType, 51 Target: target, 52 AuthDBRev: authDBRev, 53 Who: "user:test@example.com", 54 When: time.Date(2020, time.August, 16, 15, 20, 0, 0, time.UTC), 55 Comment: "comment", 56 AppVersion: "123-45abc", 57 } 58 59 var err error 60 change.ID, err = ChangeID(ctx, change) 61 So(err, ShouldBeNil) 62 return change 63 } 64 65 func testAuthDBIPAllowlistChange(ctx context.Context, authDBRev int64) *AuthDBChange { 66 change := &AuthDBChange{ 67 Kind: "AuthDBChange", 68 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 69 Class: []string{"AuthDBChange", "AuthDBIPWhitelistChange"}, 70 ChangeType: 3000, 71 Comment: "comment", 72 Description: "description", 73 OldDescription: "", 74 Target: "AuthIPWhitelist$a", 75 When: time.Date(2021, time.December, 12, 1, 0, 0, 0, time.UTC), 76 Who: "user:test@example.com", 77 AppVersion: "123-45abc", 78 } 79 80 var err error 81 change.ID, err = ChangeID(ctx, change) 82 So(err, ShouldBeNil) 83 return change 84 } 85 86 func testAuthDBIPAllowlistAssignmentChange(ctx context.Context, authDBRev int64) *AuthDBChange { 87 change := &AuthDBChange{ 88 Kind: "AuthDBChange", 89 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 90 Class: []string{"AuthDBChange", "AuthDBIPWhitelistAssignmentChange"}, 91 ChangeType: 5100, 92 Comment: "comment", 93 Identity: "test", 94 IPAllowlist: "test", 95 Target: "AuthIPWhitelistAssignments$default$user:test@example.com", 96 When: time.Date(2019, time.December, 12, 1, 0, 0, 0, time.UTC), 97 Who: "user:test@example.com", 98 AppVersion: "123-45abc", 99 } 100 101 var err error 102 change.ID, err = ChangeID(ctx, change) 103 So(err, ShouldBeNil) 104 return change 105 } 106 107 func testAuthDBConfigChange(ctx context.Context, authDBRev int64) *AuthDBChange { 108 change := &AuthDBChange{ 109 Kind: "AuthDBChange", 110 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 111 Class: []string{"AuthDBChange", "AuthDBConfigChange"}, 112 ChangeType: 7000, 113 Comment: "comment", 114 OauthClientID: "123.test.example.com", 115 OauthClientSecret: "aBcD", 116 Target: "AuthGlobalConfig$test", 117 When: time.Date(2019, time.December, 11, 1, 0, 0, 0, time.UTC), 118 Who: "user:test@example.com", 119 AppVersion: "123-45abc", 120 } 121 122 var err error 123 change.ID, err = ChangeID(ctx, change) 124 So(err, ShouldBeNil) 125 return change 126 } 127 128 func testAuthRealmsGlobalsChange(ctx context.Context, authDBRev int64) *AuthDBChange { 129 change := &AuthDBChange{ 130 Kind: "AuthDBChange", 131 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 132 Class: []string{"AuthDBChange", "AuthRealmsGlobalsChange"}, 133 ChangeType: 9000, 134 Comment: "comment", 135 PermissionsAdded: []string{"a.existInRealm"}, 136 Target: "AuthRealmsGlobals$globals", 137 When: time.Date(2021, time.January, 11, 1, 0, 0, 0, time.UTC), 138 Who: "user:test@example.com", 139 AppVersion: "123-45abc", 140 } 141 142 var err error 143 change.ID, err = ChangeID(ctx, change) 144 So(err, ShouldBeNil) 145 return change 146 } 147 148 func testAuthProjectRealmsChange(ctx context.Context, authDBRev int64) *AuthDBChange { 149 change := &AuthDBChange{ 150 Kind: "AuthDBChange", 151 Parent: ChangeLogRevisionKey(ctx, authDBRev, false), 152 Class: []string{"AuthDBChange", "AuthProjectRealmsChange"}, 153 ChangeType: 10200, 154 Comment: "comment", 155 ConfigRevOld: "", 156 ConfigRevNew: "", 157 PermsRevOld: "auth_service_ver:100-00abc", 158 PermsRevNew: "auth_service_ver:123-45abc", 159 Target: "AuthProjectRealms$repo", 160 When: time.Date(2019, time.January, 11, 1, 0, 0, 0, time.UTC), 161 Who: "user:test@example.com", 162 AppVersion: "123-45abc", 163 } 164 165 var err error 166 change.ID, err = ChangeID(ctx, change) 167 So(err, ShouldBeNil) 168 return change 169 } 170 171 //////////////////////////////////////////////////////////////////////////////// 172 173 func TestGetAllAuthDBChange(t *testing.T) { 174 t.Parallel() 175 Convey("Testing GetAllAuthDBChange", t, func() { 176 ctx := memory.Use(context.Background()) 177 datastore.GetTestable(ctx).AutoIndex(true) 178 datastore.GetTestable(ctx).Consistent(true) 179 180 So(datastore.Put(ctx, 181 testAuthDBGroupChange(ctx, "AuthGroup$groupA", ChangeGroupCreated, 1120), 182 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1120), 183 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1136), 184 testAuthDBGroupChange(ctx, "AuthGroup$groupC", ChangeGroupMembersAdded, 1135), 185 testAuthDBGroupChange(ctx, "AuthGroup$groupC", ChangeGroupOwnersChanged, 1110), 186 testAuthDBIPAllowlistChange(ctx, 1120), 187 testAuthDBIPAllowlistAssignmentChange(ctx, 1121), 188 testAuthDBConfigChange(ctx, 1115), 189 testAuthRealmsGlobalsChange(ctx, 1120), 190 testAuthProjectRealmsChange(ctx, 1115), 191 ), ShouldBeNil) 192 193 Convey("Sort by key", func() { 194 changes, pageToken, err := GetAllAuthDBChange(ctx, "", 0, 5, "") 195 So(err, ShouldBeNil) 196 So(pageToken, ShouldNotBeEmpty) 197 So(changes, ShouldResemble, []*AuthDBChange{ 198 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1136), 199 testAuthDBGroupChange(ctx, "AuthGroup$groupC", ChangeGroupMembersAdded, 1135), 200 testAuthDBIPAllowlistAssignmentChange(ctx, 1121), 201 testAuthRealmsGlobalsChange(ctx, 1120), 202 testAuthDBIPAllowlistChange(ctx, 1120), 203 }) 204 205 changes, _, err = GetAllAuthDBChange(ctx, "", 0, 5, pageToken) 206 So(err, ShouldBeNil) 207 So(changes, ShouldResemble, []*AuthDBChange{ 208 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1120), 209 testAuthDBGroupChange(ctx, "AuthGroup$groupA", ChangeGroupCreated, 1120), 210 testAuthProjectRealmsChange(ctx, 1115), 211 testAuthDBConfigChange(ctx, 1115), 212 testAuthDBGroupChange(ctx, "AuthGroup$groupC", ChangeGroupOwnersChanged, 1110), 213 }) 214 }) 215 Convey("Filter by target", func() { 216 changes, _, err := GetAllAuthDBChange(ctx, "AuthGroup$groupB", 0, 10, "") 217 So(err, ShouldBeNil) 218 So(changes, ShouldResemble, []*AuthDBChange{ 219 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1136), 220 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1120), 221 }) 222 }) 223 Convey("Filter by authDBRev", func() { 224 changes, _, err := GetAllAuthDBChange(ctx, "", 1136, 10, "") 225 So(err, ShouldBeNil) 226 So(changes, ShouldResemble, []*AuthDBChange{ 227 testAuthDBGroupChange(ctx, "AuthGroup$groupB", ChangeGroupMembersAdded, 1136), 228 }) 229 }) 230 Convey("Return error when target is invalid", func() { 231 _, _, err := GetAllAuthDBChange(ctx, "groupname", 0, 10, "") 232 So(err, ShouldErrLike, "Invalid target groupname") 233 }) 234 }) 235 } 236 237 func TestGenerateChanges(t *testing.T) { 238 t.Parallel() 239 240 Convey("GenerateChanges", t, func() { 241 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{ 242 Identity: "user:someone@example.com", 243 IdentityGroups: []string{AdminGroup}, 244 }) 245 ctx = info.SetImageVersion(ctx, "test-version") 246 ctx = clock.Set(ctx, testclock.New(testCreatedTS)) 247 ctx, taskScheduler := tq.TestingContext(txndefer.FilterRDS(ctx), nil) 248 249 So(datastore.Put(ctx, testAuthGroup(ctx, AdminGroup)), ShouldBeNil) 250 251 ////////////////////////////////////////////////////////// 252 // Helper functions 253 getChanges := func(ctx context.Context, authDBRev int64, dryRun bool) []*AuthDBChange { 254 ancestor := constructLogRevisionKey(ctx, authDBRev, dryRun) 255 query := datastore.NewQuery(entityKind("AuthDBChange", dryRun)).Ancestor(ancestor) 256 changes := []*AuthDBChange{} 257 err := datastore.Run(ctx, query, func(change *AuthDBChange) { 258 changes = append(changes, change) 259 }) 260 So(err, ShouldBeNil) 261 return changes 262 } 263 264 // The fields in AuthDBChange to ignore when comparing results; 265 // these fields are not signficant to the test cases below. 266 ignoredAuthDBChangeFields := cmpopts.IgnoreFields(AuthDBChange{}, 267 "Kind", "ID", "Parent", "Class", "Target", "Who", "When", "Comment", "AppVersion") 268 269 validateChanges := func(ctx context.Context, msg string, authDBRev int64, actualChanges []*AuthDBChange, expectedChanges []*AuthDBChange) { 270 changeCount := len(expectedChanges) 271 SoMsg(msg, actualChanges, ShouldHaveLength, changeCount) 272 273 // Check each actual and exxpected changes are similar. 274 for i := 0; i < changeCount; i++ { 275 // Set the expected AuthDB revision to the given value. 276 expectedChanges[i].AuthDBRev = authDBRev 277 278 diff := cmp.Diff(actualChanges[i], expectedChanges[i], ignoredAuthDBChangeFields) 279 if diff != "" { 280 t.Errorf("%s - difference at index %d: %s", msg, i, diff) 281 } 282 } 283 284 // Check AuthDBChange records are in datastore. 285 sort.Slice(actualChanges, func(i, j int) bool { 286 return actualChanges[i].ChangeType < actualChanges[j].ChangeType 287 }) 288 SoMsg(msg, getChanges(ctx, authDBRev, false), ShouldResemble, actualChanges) 289 } 290 ////////////////////////////////////////////////////////// 291 292 Convey("AuthGroup changes", func() { 293 Convey("AuthGroup Created/Deleted", func() { 294 ag1 := makeAuthGroup(ctx, "group-1") 295 _, err := CreateAuthGroup(ctx, ag1, false, "Go pRPC API", false) 296 So(err, ShouldBeNil) 297 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 298 actualChanges, err := generateChanges(ctx, 1, false) 299 So(err, ShouldBeNil) 300 // Check that common fields were set as expected; these fields 301 // are ignored for most other test cases. 302 So(actualChanges, ShouldResembleProto, []*AuthDBChange{{ 303 ID: "AuthGroup$group-1!1000", 304 Class: []string{"AuthDBChange", "AuthDBGroupChange"}, 305 ChangeType: ChangeGroupCreated, 306 Target: "AuthGroup$group-1", 307 Kind: "AuthDBChange", 308 Parent: ChangeLogRevisionKey(ctx, 1, false), 309 AuthDBRev: 1, 310 Who: "user:someone@example.com", 311 When: testCreatedTS, 312 Comment: "Go pRPC API", 313 AppVersion: "test-version", 314 Owners: AdminGroup, 315 }}) 316 validateChanges(ctx, "create group", 1, actualChanges, []*AuthDBChange{{ 317 ChangeType: ChangeGroupCreated, 318 Owners: AdminGroup, 319 }}) 320 321 So(DeleteAuthGroup(ctx, ag1.ID, "", false, "Go pRPC API", false), ShouldBeNil) 322 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 323 actualChanges, err = generateChanges(ctx, 2, false) 324 So(err, ShouldBeNil) 325 validateChanges(ctx, "delete group", 2, actualChanges, []*AuthDBChange{{ 326 ChangeType: ChangeGroupDeleted, 327 OldOwners: AdminGroup, 328 }}) 329 330 // Check calling generateChanges for an already-processed 331 // AuthDB revision does not make duplicate changes. 332 repeated, err := generateChanges(ctx, 2, false) 333 So(err, ShouldBeNil) 334 So(repeated, ShouldBeNil) 335 // Check the changelog for the revision actually exists. 336 expectedStoredChanges := []*AuthDBChange(actualChanges) 337 sort.Slice(expectedStoredChanges, func(i, j int) bool { 338 return expectedStoredChanges[i].ChangeType < expectedStoredChanges[j].ChangeType 339 }) 340 So(getChanges(ctx, 2, false), ShouldResemble, expectedStoredChanges) 341 }) 342 343 Convey("AuthGroup Owners / Description changed", func() { 344 og := makeAuthGroup(ctx, "owning-group") 345 So(datastore.Put(ctx, og), ShouldBeNil) 346 347 ag1 := makeAuthGroup(ctx, "group-1") 348 _, err := CreateAuthGroup(ctx, ag1, false, "Go pRPC API", false) 349 So(err, ShouldBeNil) 350 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 351 actualChanges, err := generateChanges(ctx, 1, false) 352 So(err, ShouldBeNil) 353 validateChanges(ctx, "create group no owners", 1, actualChanges, []*AuthDBChange{{ 354 ChangeType: ChangeGroupCreated, 355 Owners: AdminGroup, 356 }}) 357 358 ag1.Owners = og.ID 359 ag1.Description = "test-desc" 360 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 361 So(err, ShouldBeNil) 362 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 363 actualChanges, err = generateChanges(ctx, 2, false) 364 So(err, ShouldBeNil) 365 validateChanges(ctx, "update group owners & desc", 2, actualChanges, []*AuthDBChange{{ 366 ChangeType: ChangeGroupDescriptionChanged, 367 Description: "test-desc", 368 }, { 369 ChangeType: ChangeGroupOwnersChanged, 370 Owners: "owning-group", 371 OldOwners: AdminGroup, 372 }}) 373 374 ag1.Description = "new-desc" 375 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 376 So(err, ShouldBeNil) 377 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 378 actualChanges, err = generateChanges(ctx, 3, false) 379 So(err, ShouldBeNil) 380 validateChanges(ctx, "update group desc", 3, actualChanges, []*AuthDBChange{{ 381 ChangeType: ChangeGroupDescriptionChanged, 382 Description: "new-desc", 383 OldDescription: "test-desc", 384 }}) 385 }) 386 387 Convey("AuthGroup add/remove Members", func() { 388 // Add members in Create 389 ag1 := makeAuthGroup(ctx, "group-1") 390 ag1.Members = []string{"user:someone@example.com"} 391 _, err := CreateAuthGroup(ctx, ag1, false, "Go pRPC API", false) 392 So(err, ShouldBeNil) 393 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 394 actualChanges, err := generateChanges(ctx, 1, false) 395 So(err, ShouldBeNil) 396 validateChanges(ctx, "create group +mems", 1, actualChanges, []*AuthDBChange{{ 397 ChangeType: ChangeGroupCreated, 398 Owners: AdminGroup, 399 }, { 400 ChangeType: ChangeGroupMembersAdded, 401 Members: []string{"user:someone@example.com"}, 402 }}) 403 404 // Add members to already existing group 405 ag1.Members = append(ag1.Members, "user:another-one@example.com") 406 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 407 So(err, ShouldBeNil) 408 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 409 actualChanges, err = generateChanges(ctx, 2, false) 410 So(err, ShouldBeNil) 411 validateChanges(ctx, "update group +mems", 2, actualChanges, []*AuthDBChange{{ 412 ChangeType: ChangeGroupMembersAdded, 413 Members: []string{"user:another-one@example.com"}, 414 }}) 415 416 // Remove members from existing group 417 ag1.Members = []string{"user:someone@example.com"} 418 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 419 So(err, ShouldBeNil) 420 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 421 actualChanges, err = generateChanges(ctx, 3, false) 422 So(err, ShouldBeNil) 423 validateChanges(ctx, "update group -mems", 3, actualChanges, []*AuthDBChange{{ 424 ChangeType: ChangeGroupMembersRemoved, 425 Members: []string{"user:another-one@example.com"}, 426 }}) 427 428 // Remove members when deleting group 429 So(DeleteAuthGroup(ctx, ag1.ID, "", false, "Go pRPC API", false), ShouldBeNil) 430 So(taskScheduler.Tasks(), ShouldHaveLength, 8) 431 actualChanges, err = generateChanges(ctx, 4, false) 432 So(err, ShouldBeNil) 433 validateChanges(ctx, "delete group -mems", 4, actualChanges, []*AuthDBChange{{ 434 ChangeType: ChangeGroupMembersRemoved, 435 Members: []string{"user:someone@example.com"}, 436 }, { 437 ChangeType: ChangeGroupDeleted, 438 OldOwners: AdminGroup, 439 }}) 440 }) 441 442 Convey("AuthGroup add/remove globs", func() { 443 // Add globs in create 444 ag1 := makeAuthGroup(ctx, "group-1") 445 ag1.Globs = []string{"user:*@example.com"} 446 _, err := CreateAuthGroup(ctx, ag1, false, "Go pRPC API", false) 447 So(err, ShouldBeNil) 448 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 449 actualChanges, err := generateChanges(ctx, 1, false) 450 So(err, ShouldBeNil) 451 validateChanges(ctx, "create group +globs", 1, actualChanges, []*AuthDBChange{{ 452 ChangeType: ChangeGroupCreated, 453 Owners: AdminGroup, 454 }, { 455 ChangeType: ChangeGroupGlobsAdded, 456 Globs: []string{"user:*@example.com"}, 457 }}) 458 459 // Add globs to already existing group 460 ag1.Globs = append(ag1.Globs, "user:test-*@test.com") 461 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 462 So(err, ShouldBeNil) 463 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 464 actualChanges, err = generateChanges(ctx, 2, false) 465 So(err, ShouldBeNil) 466 validateChanges(ctx, "update group +globs", 2, actualChanges, []*AuthDBChange{{ 467 ChangeType: ChangeGroupGlobsAdded, 468 Globs: []string{"user:test-*@test.com"}, 469 }}) 470 471 ag1.Globs = []string{"user:test-*@test.com"} 472 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 473 So(err, ShouldBeNil) 474 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 475 So(err, ShouldBeNil) 476 actualChanges, err = generateChanges(ctx, 3, false) 477 So(err, ShouldBeNil) 478 validateChanges(ctx, "update group -globs", 3, actualChanges, []*AuthDBChange{{ 479 ChangeType: ChangeGroupGlobsRemoved, 480 Globs: []string{"user:*@example.com"}, 481 }}) 482 483 So(DeleteAuthGroup(ctx, ag1.ID, "", false, "Go pRPC API", false), ShouldBeNil) 484 So(taskScheduler.Tasks(), ShouldHaveLength, 8) 485 actualChanges, err = generateChanges(ctx, 4, false) 486 So(err, ShouldBeNil) 487 validateChanges(ctx, "delete group -globs", 4, actualChanges, []*AuthDBChange{{ 488 ChangeType: ChangeGroupGlobsRemoved, 489 Globs: []string{"user:test-*@test.com"}, 490 }, { 491 ChangeType: ChangeGroupDeleted, 492 OldOwners: AdminGroup, 493 }}) 494 }) 495 496 Convey("AuthGroup add/remove nested", func() { 497 ag2 := makeAuthGroup(ctx, "group-2") 498 ag3 := makeAuthGroup(ctx, "group-3") 499 So(datastore.Put(ctx, ag2, ag3), ShouldBeNil) 500 501 // Add globs in create 502 ag1 := makeAuthGroup(ctx, "group-1") 503 ag1.Nested = []string{ag2.ID} 504 _, err := CreateAuthGroup(ctx, ag1, false, "Go pRPC API", false) 505 So(err, ShouldBeNil) 506 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 507 actualChanges, err := generateChanges(ctx, 1, false) 508 So(err, ShouldBeNil) 509 validateChanges(ctx, "create group +nested", 1, actualChanges, []*AuthDBChange{{ 510 ChangeType: ChangeGroupCreated, 511 Owners: AdminGroup, 512 }, { 513 ChangeType: ChangeGroupNestedAdded, 514 Nested: []string{"group-2"}, 515 }}) 516 517 // Add globs to already existing group 518 ag1.Nested = append(ag1.Nested, ag3.ID) 519 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 520 So(err, ShouldBeNil) 521 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 522 actualChanges, err = generateChanges(ctx, 2, false) 523 So(err, ShouldBeNil) 524 validateChanges(ctx, "update group +nested", 2, actualChanges, []*AuthDBChange{{ 525 ChangeType: ChangeGroupNestedAdded, 526 Nested: []string{"group-3"}, 527 }}) 528 529 ag1.Nested = []string{"group-2"} 530 _, err = UpdateAuthGroup(ctx, ag1, nil, "", false, "Go pRPC API", false) 531 So(err, ShouldBeNil) 532 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 533 So(err, ShouldBeNil) 534 actualChanges, err = generateChanges(ctx, 3, false) 535 So(err, ShouldBeNil) 536 validateChanges(ctx, "update group -nested", 3, actualChanges, []*AuthDBChange{{ 537 ChangeType: ChangeGroupNestedRemoved, 538 Nested: []string{"group-3"}, 539 }}) 540 541 So(DeleteAuthGroup(ctx, ag1.ID, "", false, "Go pRPC API", false), ShouldBeNil) 542 So(taskScheduler.Tasks(), ShouldHaveLength, 8) 543 actualChanges, err = generateChanges(ctx, 4, false) 544 So(err, ShouldBeNil) 545 validateChanges(ctx, "delete group -nested", 4, actualChanges, []*AuthDBChange{{ 546 ChangeType: ChangeGroupNestedRemoved, 547 Nested: []string{"group-2"}, 548 }, { 549 ChangeType: ChangeGroupDeleted, 550 OldOwners: AdminGroup, 551 }}) 552 }) 553 }) 554 555 Convey("AuthIPAllowlist changes", func() { 556 Convey("AuthIPAllowlist Created/Deleted +/- subnets", func() { 557 // Creation with no subnet 558 baseSubnetMap := make(map[string][]string) 559 baseSubnetMap["test-allowlist-1"] = []string{} 560 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 561 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 562 actualChanges, err := generateChanges(ctx, 1, false) 563 So(err, ShouldBeNil) 564 validateChanges(ctx, "create allowlist", 1, actualChanges, []*AuthDBChange{{ 565 ChangeType: ChangeIPALCreated, 566 Description: "Imported from ip_allowlist.cfg", 567 }}) 568 569 // Deletion with no subnet 570 baseSubnetMap = map[string][]string{} 571 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 572 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 573 actualChanges, err = generateChanges(ctx, 2, false) 574 So(err, ShouldBeNil) 575 validateChanges(ctx, "delete allowlist", 2, actualChanges, []*AuthDBChange{{ 576 ChangeType: ChangeIPALDeleted, 577 OldDescription: "Imported from ip_allowlist.cfg", 578 }}) 579 580 // Creation with subnets 581 baseSubnetMap["test-allowlist-1"] = []string{"123.4.5.6"} 582 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 583 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 584 actualChanges, err = generateChanges(ctx, 3, false) 585 So(err, ShouldBeNil) 586 validateChanges(ctx, "create allowlist w/ subnet", 3, actualChanges, []*AuthDBChange{{ 587 ChangeType: ChangeIPALCreated, 588 Description: "Imported from ip_allowlist.cfg", 589 }, { 590 ChangeType: ChangeIPALSubnetsAdded, 591 Subnets: []string{"123.4.5.6"}, 592 }}) 593 594 // Add subnet 595 baseSubnetMap["test-allowlist-1"] = append(baseSubnetMap["test-allowlist-1"], "567.8.9.10") 596 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 597 So(taskScheduler.Tasks(), ShouldHaveLength, 8) 598 actualChanges, err = generateChanges(ctx, 4, false) 599 So(err, ShouldBeNil) 600 validateChanges(ctx, "add subnet", 4, actualChanges, []*AuthDBChange{{ 601 ChangeType: ChangeIPALSubnetsAdded, 602 Subnets: []string{"567.8.9.10"}, 603 }}) 604 605 // Remove subnet 606 baseSubnetMap["test-allowlist-1"] = baseSubnetMap["test-allowlist-1"][1:] 607 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 608 So(taskScheduler.Tasks(), ShouldHaveLength, 10) 609 actualChanges, err = generateChanges(ctx, 5, false) 610 So(err, ShouldBeNil) 611 validateChanges(ctx, "remove subnet", 5, actualChanges, []*AuthDBChange{{ 612 ChangeType: ChangeIPALSubnetsRemoved, 613 Subnets: []string{"123.4.5.6"}, 614 }}) 615 616 // Delete allowlist with subnet 617 baseSubnetMap = map[string][]string{} 618 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 619 So(taskScheduler.Tasks(), ShouldHaveLength, 12) 620 actualChanges, err = generateChanges(ctx, 6, false) 621 So(err, ShouldBeNil) 622 validateChanges(ctx, "delete allowlist w/ subnet", 6, actualChanges, []*AuthDBChange{{ 623 ChangeType: ChangeIPALDeleted, 624 OldDescription: "Imported from ip_allowlist.cfg", 625 }, { 626 ChangeType: ChangeIPALSubnetsRemoved, 627 Subnets: []string{"567.8.9.10"}, 628 }}) 629 }) 630 631 Convey("AuthIPAllowlist description changed", func() { 632 baseSubnetMap := make(map[string][]string) 633 baseSubnetMap["test-allowlist-1"] = []string{} 634 So(UpdateAllowlistEntities(ctx, baseSubnetMap, false, "Go pRPC API"), ShouldBeNil) 635 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 636 _, err := generateChanges(ctx, 1, false) 637 So(err, ShouldBeNil) 638 639 al, err := GetAuthIPAllowlist(ctx, "test-allowlist-1") 640 So(err, ShouldBeNil) 641 So(runAuthDBChange(ctx, "test for AuthIPAllowlist changelog", func(ctx context.Context, cae commitAuthEntity) error { 642 al.Description = "new-desc" 643 return cae(al, clock.Now(ctx).UTC(), auth.CurrentIdentity(ctx), false) 644 }), ShouldBeNil) 645 actualChanges, err := generateChanges(ctx, 2, false) 646 So(err, ShouldBeNil) 647 validateChanges(ctx, "change description", 2, actualChanges, []*AuthDBChange{{ 648 ChangeType: ChangeIPALDescriptionChanged, 649 Description: "new-desc", 650 OldDescription: "Imported from ip_allowlist.cfg", 651 }}) 652 }) 653 }) 654 655 Convey("AuthGlobalConfig changes", func() { 656 Convey("AuthGlobalConfig ClientID/ClientSecret mismatch", func() { 657 baseCfg := &configspb.OAuthConfig{ 658 PrimaryClientId: "test-client-id", 659 PrimaryClientSecret: "test-client-secret", 660 } 661 662 // Old doesn't exist yet 663 So(UpdateAuthGlobalConfig(ctx, baseCfg, nil, false, "Go pRPC API"), ShouldBeNil) 664 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 665 actualChanges, err := generateChanges(ctx, 1, false) 666 So(err, ShouldBeNil) 667 validateChanges(ctx, "update config with no old config present", 1, actualChanges, []*AuthDBChange{{ 668 ChangeType: ChangeConfOauthClientChanged, 669 OauthClientID: "test-client-id", 670 OauthClientSecret: "test-client-secret", 671 }}) 672 673 newCfg := &configspb.OAuthConfig{ 674 PrimaryClientId: "diff-client-id", 675 } 676 So(UpdateAuthGlobalConfig(ctx, newCfg, nil, false, "Go pRPC API"), ShouldBeNil) 677 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 678 actualChanges, err = generateChanges(ctx, 2, false) 679 So(err, ShouldBeNil) 680 validateChanges(ctx, "update config with client id changed", 2, actualChanges, []*AuthDBChange{{ 681 ChangeType: ChangeConfOauthClientChanged, 682 OauthClientID: "diff-client-id", 683 }}) 684 }) 685 686 Convey("AuthGlobalConfig additional +/- ClientID's", func() { 687 baseCfg := &configspb.OAuthConfig{ 688 ClientIds: []string{"test.example.com"}, 689 } 690 691 So(UpdateAuthGlobalConfig(ctx, baseCfg, nil, false, "Go pRPC API"), ShouldBeNil) 692 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 693 actualChanges, err := generateChanges(ctx, 1, false) 694 So(err, ShouldBeNil) 695 validateChanges(ctx, "update config with client ids, old config not present", 1, actualChanges, []*AuthDBChange{{ 696 ChangeType: ChangeConfClientIDsAdded, 697 OauthAdditionalClientIDs: []string{"test.example.com"}, 698 }}) 699 700 newCfg := &configspb.OAuthConfig{ 701 ClientIds: []string{"not-test.example.com"}, 702 } 703 So(UpdateAuthGlobalConfig(ctx, newCfg, nil, false, "Go pRPC API"), ShouldBeNil) 704 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 705 actualChanges, err = generateChanges(ctx, 2, false) 706 So(err, ShouldBeNil) 707 validateChanges(ctx, "update config with client id added and client id removed, old config is present", 708 2, actualChanges, []*AuthDBChange{{ 709 ChangeType: ChangeConfClientIDsAdded, 710 OauthAdditionalClientIDs: []string{"not-test.example.com"}, 711 }, { 712 ChangeType: ChangeConfClientIDsRemoved, 713 OauthAdditionalClientIDs: []string{"test.example.com"}, 714 }}) 715 }) 716 717 Convey("AuthGlobalConfig TokenServerURL change", func() { 718 baseCfg := &configspb.OAuthConfig{ 719 TokenServerUrl: "test-token-server-url.example.com", 720 } 721 722 So(UpdateAuthGlobalConfig(ctx, baseCfg, nil, false, "Go pRPC API"), ShouldBeNil) 723 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 724 actualChanges, err := generateChanges(ctx, 1, false) 725 So(err, ShouldBeNil) 726 validateChanges(ctx, "update config with token server url, old config not present", 1, actualChanges, []*AuthDBChange{{ 727 ChangeType: ChangeConfTokenServerURLChanged, 728 TokenServerURLNew: "test-token-server-url.example.com", 729 }}) 730 }) 731 732 Convey("AuthGlobalConfig Security Config change", func() { 733 baseCfg := &configspb.OAuthConfig{} 734 secCfg := &protocol.SecurityConfig{ 735 InternalServiceRegexp: []string{"abc"}, 736 } 737 So(UpdateAuthGlobalConfig(ctx, baseCfg, secCfg, false, "Go pRPC API"), ShouldBeNil) 738 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 739 actualChanges, err := generateChanges(ctx, 1, false) 740 So(err, ShouldBeNil) 741 expectedNewConfig, _ := proto.Marshal(secCfg) 742 validateChanges(ctx, "update config with security config, old config not present", 1, actualChanges, []*AuthDBChange{{ 743 ChangeType: ChangeConfSecurityConfigChanged, 744 SecurityConfigNew: expectedNewConfig, 745 }}) 746 747 secCfg = &protocol.SecurityConfig{ 748 InternalServiceRegexp: []string{"def"}, 749 } 750 expectedOldConfig := expectedNewConfig 751 expectedNewConfig, _ = proto.Marshal(secCfg) 752 So(UpdateAuthGlobalConfig(ctx, baseCfg, secCfg, false, "Go pRPC API"), ShouldBeNil) 753 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 754 actualChanges, err = generateChanges(ctx, 2, false) 755 So(err, ShouldBeNil) 756 validateChanges(ctx, "update config with security config, old config present", 2, actualChanges, []*AuthDBChange{{ 757 ChangeType: ChangeConfSecurityConfigChanged, 758 SecurityConfigNew: expectedNewConfig, 759 SecurityConfigOld: expectedOldConfig, 760 }}) 761 }) 762 763 Convey("AuthGlobalConfig all changes at once", func() { 764 baseCfg := &configspb.OAuthConfig{ 765 PrimaryClientId: "test-client-id", 766 PrimaryClientSecret: "test-client-secret", 767 ClientIds: []string{"a", "b", "c"}, 768 TokenServerUrl: "token-server.example.com", 769 } 770 secCfg := &protocol.SecurityConfig{ 771 InternalServiceRegexp: []string{"test"}, 772 } 773 expectedNewConfig, _ := proto.Marshal(secCfg) 774 So(UpdateAuthGlobalConfig(ctx, baseCfg, secCfg, false, "Go pRPC API"), ShouldBeNil) 775 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 776 actualChanges, err := generateChanges(ctx, 1, false) 777 So(err, ShouldBeNil) 778 validateChanges(ctx, "all changes at once, old config not present", 1, actualChanges, []*AuthDBChange{{ 779 ChangeType: ChangeConfOauthClientChanged, 780 OauthClientID: "test-client-id", 781 OauthClientSecret: "test-client-secret", 782 }, { 783 ChangeType: ChangeConfClientIDsAdded, 784 OauthAdditionalClientIDs: []string{"a", "b", "c"}, 785 }, { 786 ChangeType: ChangeConfTokenServerURLChanged, 787 TokenServerURLNew: "token-server.example.com", 788 }, { 789 ChangeType: ChangeConfSecurityConfigChanged, 790 SecurityConfigNew: expectedNewConfig, 791 }}) 792 }) 793 }) 794 795 Convey("AuthProjectRealms changes", func() { 796 proj1Realms := &protocol.Realms{ 797 Permissions: makeTestPermissions("luci.dev.p2", "luci.dev.z", "luci.dev.p1"), 798 Realms: []*protocol.Realm{ 799 { 800 Name: "proj1:@root", 801 Bindings: []*protocol.Binding{ 802 { 803 // Permissions p2, z, p1. 804 Permissions: []uint32{0, 1, 2}, 805 Principals: []string{"group:gr1"}, 806 }, 807 }, 808 }, 809 }, 810 } 811 812 // Set up existing project realms. 813 expandedRealms := []*ExpandedRealms{ 814 { 815 CfgRev: &RealmsCfgRev{ 816 ProjectID: "proj1", 817 ConfigRev: "10001", 818 ConfigDigest: "test config digest", 819 }, 820 Realms: proj1Realms, 821 }, 822 } 823 err := UpdateAuthProjectRealms(ctx, expandedRealms, "permissions.cfg:abc", false, "Go pRPC API") 824 So(err, ShouldBeNil) 825 826 Convey("project realms created", func() { 827 expandedRealms := []*ExpandedRealms{ 828 { 829 CfgRev: &RealmsCfgRev{ 830 ProjectID: "proj2", 831 ConfigRev: "1", 832 ConfigDigest: "test config digest", 833 }, 834 }, 835 } 836 837 err := UpdateAuthProjectRealms(ctx, expandedRealms, "permissions.cfg:abc", false, "Go pRPC API") 838 So(err, ShouldBeNil) 839 840 actualChanges, err := generateChanges(ctx, 2, false) 841 So(err, ShouldBeNil) 842 validateChanges(ctx, "project realms created", 2, actualChanges, []*AuthDBChange{{ 843 ChangeType: ChangeProjectRealmsCreated, 844 ConfigRevNew: "1", 845 PermsRevNew: "permissions.cfg:abc", 846 }}) 847 }) 848 849 Convey("project realms deleted", func() { 850 err = DeleteAuthProjectRealms(ctx, "proj1", false, "Go pRPC API") 851 So(err, ShouldBeNil) 852 853 actualChanges, err := generateChanges(ctx, 2, false) 854 So(err, ShouldBeNil) 855 validateChanges(ctx, "project realms removed", 2, actualChanges, []*AuthDBChange{{ 856 ChangeType: ChangeProjectRealmsRemoved, 857 ConfigRevOld: "10001", 858 PermsRevOld: "permissions.cfg:abc", 859 }}) 860 }) 861 862 Convey("project config superficially changed", func() { 863 // New config revision, but the resulting realms are identical. 864 updatedExpandedRealms := []*ExpandedRealms{ 865 { 866 CfgRev: &RealmsCfgRev{ 867 ProjectID: "proj1", 868 ConfigRev: "10002", 869 ConfigDigest: "test config digest", 870 }, 871 Realms: proj1Realms, 872 }, 873 } 874 err = UpdateAuthProjectRealms(ctx, updatedExpandedRealms, "permissions.cfg:abc", false, "Go pRPC API") 875 So(err, ShouldBeNil) 876 877 actualChanges, err := generateChanges(ctx, 2, false) 878 So(err, ShouldBeNil) 879 So(actualChanges, ShouldBeEmpty) 880 }) 881 882 Convey("project config revision changed", func() { 883 updatedExpandedRealms := []*ExpandedRealms{ 884 { 885 CfgRev: &RealmsCfgRev{ 886 ProjectID: "proj1", 887 ConfigRev: "10002", 888 ConfigDigest: "test config digest", 889 }, 890 Realms: nil, 891 }, 892 } 893 err = UpdateAuthProjectRealms(ctx, updatedExpandedRealms, "permissions.cfg:abc", false, "Go pRPC API") 894 So(err, ShouldBeNil) 895 896 actualChanges, err := generateChanges(ctx, 2, false) 897 So(err, ShouldBeNil) 898 validateChanges(ctx, "project realms changed", 2, actualChanges, []*AuthDBChange{{ 899 ChangeType: ChangeProjectRealmsChanged, 900 ConfigRevOld: "10001", 901 ConfigRevNew: "10002", 902 }}) 903 }) 904 905 Convey("realms changed but revisions identical", func() { 906 updatedExpandedRealms := []*ExpandedRealms{ 907 { 908 CfgRev: &RealmsCfgRev{ 909 ProjectID: "proj1", 910 ConfigRev: "10001", 911 ConfigDigest: "test config digest", 912 }, 913 Realms: nil, 914 }, 915 } 916 err = UpdateAuthProjectRealms(ctx, updatedExpandedRealms, "permissions.cfg:abc", false, "Go pRPC API") 917 So(err, ShouldBeNil) 918 919 actualChanges, err := generateChanges(ctx, 2, false) 920 So(err, ShouldBeNil) 921 validateChanges(ctx, "project realms changed", 2, actualChanges, []*AuthDBChange{{ 922 ChangeType: ChangeProjectRealmsChanged, 923 ConfigRevOld: "10001", 924 ConfigRevNew: "10001", 925 }}) 926 }) 927 928 Convey("both project config and permissions config changed", func() { 929 updatedProj1Realms := &protocol.Realms{ 930 Permissions: makeTestPermissions("luci.dev.p2", "luci.dev.p1"), 931 Realms: []*protocol.Realm{ 932 { 933 Name: "proj1:@root", 934 Bindings: []*protocol.Binding{ 935 { 936 // Permissions p2, p1. 937 Permissions: []uint32{0, 1}, 938 Principals: []string{"group:gr1"}, 939 }, 940 }, 941 }, 942 }, 943 } 944 updatedExpandedRealms := []*ExpandedRealms{ 945 { 946 CfgRev: &RealmsCfgRev{ 947 ProjectID: "proj1", 948 ConfigRev: "10002", 949 ConfigDigest: "test config digest", 950 }, 951 Realms: updatedProj1Realms, 952 }, 953 } 954 err = UpdateAuthProjectRealms(ctx, updatedExpandedRealms, "permissions.cfg:def", false, "Go pRPC API") 955 So(err, ShouldBeNil) 956 957 actualChanges, err := generateChanges(ctx, 2, false) 958 So(err, ShouldBeNil) 959 validateChanges(ctx, "project realms changed and reevaluated", 2, actualChanges, []*AuthDBChange{ 960 { 961 ChangeType: ChangeProjectRealmsChanged, 962 ConfigRevOld: "10001", 963 ConfigRevNew: "10002", 964 }, 965 { 966 ChangeType: ChangeProjectRealmsReevaluated, 967 PermsRevOld: "permissions.cfg:abc", 968 PermsRevNew: "permissions.cfg:def", 969 }, 970 }) 971 }) 972 }) 973 974 Convey("AuthRealmsGlobals changes", func() { 975 Convey("v1 permissions", func() { 976 // Helper function to mimic AuthRealmsGlobals being 977 // updated by Auth Service v1. 978 updateAuthRealmsGlobalsV1Perms := func(ctx context.Context, permissions []*protocol.Permission) error { 979 return runAuthDBChange(ctx, "mimicking Python update-realms cron", func(ctx context.Context, commitEntity commitAuthEntity) error { 980 stored, err := GetAuthRealmsGlobals(ctx) 981 if err != nil && !errors.Is(err, datastore.ErrNoSuchEntity) { 982 return errors.Annotate(err, "error while fetching AuthRealmsGlobals entity").Err() 983 } 984 985 if stored == nil { 986 stored = makeAuthRealmsGlobals(ctx) 987 } 988 989 perms := make([]string, len(permissions)) 990 for i, p := range permissions { 991 perm, err := proto.Marshal(p) 992 if err != nil { 993 return err 994 } 995 perms[i] = string(perm) 996 } 997 stored.Permissions = perms 998 return commitEntity(stored, testModifiedTS, auth.CurrentIdentity(ctx), false) 999 }) 1000 } 1001 1002 // Add permissions when there's no config. 1003 So(updateAuthRealmsGlobalsV1Perms(ctx, []*protocol.Permission{ 1004 {Name: "test.perm.create"}, 1005 {Name: "test.perm.edit"}, 1006 }), ShouldBeNil) 1007 actualChanges, err := generateChanges(ctx, 1, false) 1008 So(err, ShouldBeNil) 1009 validateChanges(ctx, "update realms globals, old config not present", 1, actualChanges, []*AuthDBChange{{ 1010 ChangeType: ChangeRealmsGlobalsChanged, 1011 PermissionsAdded: []string{"test.perm.create", "test.perm.edit"}, 1012 }}) 1013 1014 // Modify the existing permissions. 1015 So(updateAuthRealmsGlobalsV1Perms(ctx, []*protocol.Permission{ 1016 {Name: "test.perm.create", Internal: true}, 1017 }), ShouldBeNil) 1018 actualChanges, err = generateChanges(ctx, 2, false) 1019 So(err, ShouldBeNil) 1020 validateChanges(ctx, "update realms globals, old config present", 2, actualChanges, []*AuthDBChange{{ 1021 ChangeType: ChangeRealmsGlobalsChanged, 1022 PermissionsChanged: []string{"test.perm.create"}, 1023 PermissionsRemoved: []string{"test.perm.edit"}, 1024 }}) 1025 }) 1026 1027 Convey("v2 permissions", func() { 1028 permCfg := &configspb.PermissionsConfig{ 1029 Role: []*configspb.PermissionsConfig_Role{ 1030 { 1031 Name: "role/test.role.editor", 1032 Permissions: []*protocol.Permission{ 1033 { 1034 Name: "test.perm.edit", 1035 }, 1036 }, 1037 }, 1038 { 1039 Name: "role/test.role.creator", 1040 Permissions: []*protocol.Permission{ 1041 { 1042 Name: "test.perm.create", 1043 }, 1044 }, 1045 }, 1046 }, 1047 } 1048 So(UpdateAuthRealmsGlobals(ctx, permCfg, false, "Go pRPC API"), ShouldBeNil) 1049 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 1050 actualChanges, err := generateChanges(ctx, 1, false) 1051 So(err, ShouldBeNil) 1052 validateChanges(ctx, "update realms globals, old config not present", 1, actualChanges, []*AuthDBChange{{ 1053 ChangeType: ChangeRealmsGlobalsChanged, 1054 PermissionsAdded: []string{"test.perm.create", "test.perm.edit"}, 1055 }}) 1056 1057 permCfg = &configspb.PermissionsConfig{ 1058 Role: []*configspb.PermissionsConfig_Role{ 1059 { 1060 Name: "role/test.role.creator", 1061 Permissions: []*protocol.Permission{ 1062 { 1063 Name: "test.perm.create", 1064 Internal: true, 1065 }, 1066 }, 1067 }, 1068 }, 1069 } 1070 So(UpdateAuthRealmsGlobals(ctx, permCfg, false, "Go pRPC API"), ShouldBeNil) 1071 So(taskScheduler.Tasks(), ShouldHaveLength, 4) 1072 actualChanges, err = generateChanges(ctx, 2, false) 1073 So(err, ShouldBeNil) 1074 validateChanges(ctx, "update realms globals, old config present", 2, actualChanges, []*AuthDBChange{{ 1075 ChangeType: ChangeRealmsGlobalsChanged, 1076 PermissionsChanged: []string{"test.perm.create"}, 1077 PermissionsRemoved: []string{"test.perm.edit"}, 1078 }}) 1079 }) 1080 }) 1081 1082 Convey("Changelog generation cascades", func() { 1083 // Create and add 3 AuthGroups. 1084 groupNames := []string{"group-1", "group-2", "group-3"} 1085 for _, groupName := range groupNames { 1086 ag := makeAuthGroup(ctx, groupName) 1087 _, err := CreateAuthGroup(ctx, ag, false, "Go pRPC API", false) 1088 So(err, ShouldBeNil) 1089 } 1090 // There should be a changelog task and replication task for 1091 // each group. 1092 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 1093 1094 actualChanges, err := generateChanges(ctx, 1, false) 1095 So(err, ShouldBeNil) 1096 validateChanges(ctx, "created first group in cascade test", 1, actualChanges, []*AuthDBChange{{ 1097 ChangeType: ChangeGroupCreated, 1098 Owners: AdminGroup, 1099 }}) 1100 // First change has no prior revision, so a task should not 1101 // have been added. 1102 So(taskScheduler.Tasks(), ShouldHaveLength, 6) 1103 1104 actualChanges, err = generateChanges(ctx, 3, false) 1105 So(err, ShouldBeNil) 1106 validateChanges(ctx, "created third group in cascade test", 3, actualChanges, []*AuthDBChange{{ 1107 ChangeType: ChangeGroupCreated, 1108 Owners: AdminGroup, 1109 }}) 1110 // Changelog for rev 2 has not been generated yet, so there 1111 // should be an added task. 1112 So(taskScheduler.Tasks(), ShouldHaveLength, 7) 1113 1114 actualChanges, err = generateChanges(ctx, 2, false) 1115 So(err, ShouldBeNil) 1116 validateChanges(ctx, "created second group in cascade test", 2, actualChanges, []*AuthDBChange{{ 1117 ChangeType: ChangeGroupCreated, 1118 Owners: AdminGroup, 1119 }}) 1120 // Changelog for rev 1 has already been generated, so a task 1121 // should not have been added. 1122 So(taskScheduler.Tasks(), ShouldHaveLength, 7) 1123 }) 1124 1125 Convey("dry run of changelog generation works", func() { 1126 agDryRun := makeAuthGroup(ctx, "group-dry-run") 1127 _, err := CreateAuthGroup(ctx, agDryRun, false, "Go pRPC API", false) 1128 So(err, ShouldBeNil) 1129 So(taskScheduler.Tasks(), ShouldHaveLength, 2) 1130 _, err = generateChanges(ctx, 1, true) 1131 So(err, ShouldBeNil) 1132 1133 // Check the changelog was created in dry run mode. 1134 actualChanges := getChanges(ctx, 1, true) 1135 So(actualChanges, ShouldHaveLength, 1) 1136 So(actualChanges[0].Kind, ShouldEqual, "V2AuthDBChange") 1137 So(actualChanges[0].Parent, ShouldEqual, ChangeLogRevisionKey(ctx, 1, true)) 1138 So(actualChanges[0].ChangeType, ShouldEqual, ChangeGroupCreated) 1139 So(actualChanges[0].Owners, ShouldEqual, AdminGroup) 1140 1141 // Check there were no changes written with dry run mode off. 1142 So(getChanges(ctx, 1, false), ShouldBeEmpty) 1143 }) 1144 }) 1145 } 1146 1147 func TestPropertyMapHelpers(t *testing.T) { 1148 Convey("getStringSliceProp robustly returns strings", t, func() { 1149 pm := datastore.PropertyMap{} 1150 pm["testField"] = datastore.PropertySlice{ 1151 datastore.MkPropertyNI([]byte("test data first entry")), 1152 datastore.MkPropertyNI("test data second entry"), 1153 } 1154 1155 So(getStringSliceProp(pm, "testField"), ShouldEqual, []string{ 1156 "test data first entry", 1157 "test data second entry", 1158 }) 1159 }) 1160 }