github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/teams_implicit_test.go (about) 1 package systests 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "testing" 8 "time" 9 10 "golang.org/x/net/context" 11 12 "github.com/davecgh/go-spew/spew" 13 "github.com/keybase/client/go/emails" 14 "github.com/keybase/client/go/kbtest" 15 "github.com/keybase/client/go/libkb" 16 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/keybase/client/go/teams" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestImplicitTeamRotateOnRevokePrivate(t *testing.T) { 22 testImplicitTeamRotateOnRevoke(t, false) 23 } 24 25 func TestImplicitTeamRotateOnRevokePublic(t *testing.T) { 26 testImplicitTeamRotateOnRevoke(t, true) 27 } 28 29 func testImplicitTeamRotateOnRevoke(t *testing.T, public bool) { 30 t.Logf("public: %v", public) 31 tt := newTeamTester(t) 32 defer tt.cleanup() 33 34 alice := tt.addUser("alice") 35 bob := tt.addUserWithPaper("bob") 36 37 iTeamName := strings.Join([]string{alice.username, bob.username}, ",") 38 39 t.Logf("make an implicit team") 40 team, err := alice.lookupImplicitTeam(true /*create*/, iTeamName, public) 41 require.NoError(t, err) 42 43 // get the before state of the team 44 before, err := GetTeamForTestByID(context.TODO(), alice.tc.G, team, public) 45 require.NoError(t, err) 46 require.Equal(t, keybase1.PerTeamKeyGeneration(1), before.Generation()) 47 secretBefore := before.Data.PerTeamKeySeedsUnverified[before.Generation()].Seed.ToBytes() 48 49 bob.revokePaperKey() 50 51 // We wait for different chain arrangements based on whether this was a public or private rotation 52 var visible, hidden keybase1.Seqno 53 if public { 54 visible = keybase1.Seqno(2) 55 hidden = keybase1.Seqno(0) 56 } else { 57 visible = keybase1.Seqno(1) 58 hidden = keybase1.Seqno(1) 59 } 60 61 alice.waitForAnyRotateByID(team, visible, hidden) 62 63 // check that key was rotated for team 64 after, err := GetTeamForTestByID(context.TODO(), alice.tc.G, team, public) 65 require.NoError(t, err) 66 require.Equal(t, keybase1.PerTeamKeyGeneration(2), after.Generation(), "generation after rotate") 67 68 secretAfter := after.Data.PerTeamKeySeedsUnverified[after.Generation()].Seed.ToBytes() 69 if libkb.SecureByteArrayEq(secretAfter, secretBefore) { 70 t.Fatal("team secret did not change when rotated") 71 } 72 } 73 74 // Invites should be visible to everyone for implicit teams. 75 // Even readers. 76 func TestImplicitTeamInviteVisibilityPrivate(t *testing.T) { 77 testImplicitTeamInviteVisibility(t, false) 78 } 79 80 func TestImplicitTeamInviteVisibilityPublic(t *testing.T) { 81 testImplicitTeamInviteVisibility(t, true) 82 } 83 84 func testImplicitTeamInviteVisibility(t *testing.T, public bool) { 85 t.Logf("public: %v", public) 86 87 tt := newTeamTester(t) 88 defer tt.cleanup() 89 90 // Alice is a writer 91 alice := tt.addUser("alice") 92 // Bob is a writer by social assertion (proved partway through test) 93 bob := tt.addUser("bob") 94 // Char is a pukless writer 95 char := tt.addPuklessUser("char") 96 // test-private: Drake is a reader 97 // test-public: Drake is not a member 98 drake := tt.addUser("drake") 99 100 bobSocial := fmt.Sprintf("%v@rooter", bob.username) 101 102 impteamName := fmt.Sprintf("%v,%v,%v#%v", alice.username, bobSocial, char.username, drake.username) 103 if public { 104 impteamName = fmt.Sprintf("%v,%v,%v", alice.username, bobSocial, char.username) 105 } 106 107 t.Logf("impteamName: %v", impteamName) 108 teamID, err := alice.lookupImplicitTeam(true /*create*/, impteamName, public) 109 require.NoError(t, err) 110 _ = teamID 111 112 assertions := func(rooterDone bool) { 113 lookupRes, err := drake.lookupImplicitTeam2(false /*create*/, impteamName, public) 114 require.NoError(t, err) 115 require.Equal(t, teamID, lookupRes.TeamID) 116 require.Equal(t, public, lookupRes.DisplayName.IsPublic) 117 118 team, err := teams.Load(context.TODO(), drake.tc.G, keybase1.LoadTeamArg{ 119 ID: lookupRes.TeamID, 120 Public: public, 121 ForceRepoll: true, 122 }) 123 require.NoError(t, err) 124 require.True(t, team.IsImplicit()) 125 126 // Assert that `list` and `users` are the same set. 127 // Ignores EldestSeqno, just uses UID. 128 // Sorts list in place 129 assertUvSet := func(actual []keybase1.UserVersion, expected ...*userPlusDevice) { 130 require.Len(t, actual, len(expected)) 131 // Sort both by uid and compare 132 sort.Slice(actual, func(i, j int) bool { 133 return actual[i].Uid < actual[j].Uid 134 }) 135 sort.Slice(expected, func(i, j int) bool { 136 return expected[i].uid < expected[j].uid 137 }) 138 for i, expected1 := range expected { 139 actual1 := actual[i] 140 require.Equal(t, expected1.uid, actual1.Uid, "%v", expected1.username) 141 } 142 } 143 144 t.Logf("check the Team object") 145 members, err := team.Members() 146 require.NoError(t, err) 147 t.Logf("members: %v", spew.Sdump(members)) 148 if !rooterDone { 149 assertUvSet(members.Owners, alice) 150 require.Equal(t, 2, team.NumActiveInvites(), "bob (social) and char (pukless)") 151 } else { 152 assertUvSet(members.Owners, alice, bob) 153 require.Equal(t, 1, team.NumActiveInvites(), "char (pukless)") 154 } 155 assertUvSet(members.Admins) 156 assertUvSet(members.Writers) 157 if public { 158 assertUvSet(members.Readers) 159 } else { 160 assertUvSet(members.Readers, drake) 161 } 162 163 t.Logf("check the ImplicitTeamDisplayName from LookupImplicitTeam: %v", spew.Sdump(lookupRes.DisplayName)) 164 if !rooterDone { 165 require.Len(t, lookupRes.DisplayName.Writers.KeybaseUsers, 2, "alice, char (pukless)") 166 require.Len(t, lookupRes.DisplayName.Writers.UnresolvedUsers, 1, "bob (rooter)") 167 } else { 168 require.Len(t, lookupRes.DisplayName.Writers.KeybaseUsers, 3, "alice, bob (resolved), char (pukless)") 169 require.Len(t, lookupRes.DisplayName.Writers.UnresolvedUsers, 0) 170 } 171 require.Len(t, lookupRes.DisplayName.Readers.UnresolvedUsers, 0) 172 if public { 173 require.Len(t, lookupRes.DisplayName.Readers.KeybaseUsers, 0) 174 } else { 175 require.Len(t, lookupRes.DisplayName.Readers.KeybaseUsers, 1) 176 } 177 } 178 179 assertions(false) 180 181 bob.proveRooter() 182 183 t.Logf("wait for someone to add bob") 184 pollForConditionWithTimeout(t, 10*time.Second, "bob to be added to the team after rooter proof", func(ctx context.Context) bool { 185 team, err := teams.Load(ctx, drake.tc.G, keybase1.LoadTeamArg{ 186 ID: teamID, 187 Public: public, 188 ForceRepoll: true, 189 }) 190 require.NoError(t, err) 191 role, err := team.MemberRole(ctx, bob.userVersion()) 192 require.NoError(t, err) 193 return role != keybase1.TeamRole_NONE 194 }) 195 196 assertions(true) 197 } 198 199 // Poll until the condition is satisfied. 200 // Fails the test and returns after the timeout. 201 func pollForConditionWithTimeout(t *testing.T, timeout time.Duration, description string, condition func(context.Context) bool) { 202 pollCtx, pollCancel := context.WithCancel(context.Background()) 203 defer pollCancel() 204 successCh := make(chan struct{}) 205 206 // Start polling 207 go func(ctx context.Context) { 208 for { 209 if condition(ctx) { 210 successCh <- struct{}{} 211 return 212 } 213 time.Sleep(300 * time.Millisecond) 214 } 215 }(pollCtx) 216 217 // Wait for success or timeout 218 select { 219 case <-successCh: 220 case <-time.After(30 * time.Second): 221 pollCancel() 222 t.Fatalf("timed out waiting for condition: %v", description) 223 } 224 } 225 226 func trySBSConsolidation(t *testing.T, impteamExpr string, public bool) { 227 t.Logf("trySBSConsolidation(expr=%q, public=%t)", impteamExpr, public) 228 229 tt := newTeamTester(t) 230 defer tt.cleanup() 231 232 ann := tt.addUser("ann") 233 bob := tt.addUser("bob") 234 tt.logUserNames() 235 236 impteamName := fmt.Sprintf(impteamExpr, ann.username, bob.username, bob.username) 237 teamID, err := ann.lookupImplicitTeam(true /* create */, impteamName, public) 238 require.NoError(t, err) 239 240 t.Logf("Created team %s -> %s", impteamName, teamID) 241 242 bob.kickTeamRekeyd() 243 bob.proveRooter() 244 t.Logf("Bob (%s) proved rooter", bob.username) 245 246 expectedTeamName := fmt.Sprintf("%v,%v", ann.username, bob.username) 247 pollForConditionWithTimeout(t, 10*time.Second, "team consolidated to ann,bob", func(ctx context.Context) bool { 248 team, err := teams.Load(ctx, ann.tc.G, keybase1.LoadTeamArg{ 249 ID: teamID, 250 ForceRepoll: true, 251 Public: public, 252 }) 253 require.NoError(t, err) 254 displayName, err := team.ImplicitTeamDisplayName(context.Background()) 255 require.NoError(t, err) 256 t.Logf("Got team back: %q (waiting for %q)", displayName.String(), expectedTeamName) 257 return displayName.String() == expectedTeamName 258 }) 259 260 teamID2, err := ann.lookupImplicitTeam(false /* create */, expectedTeamName, public) 261 require.NoError(t, err) 262 require.Equal(t, teamID, teamID2) 263 264 _, err = teams.ResolveIDToName(context.Background(), ann.tc.G, teamID2) 265 require.NoError(t, err) 266 267 if public { 268 pam := tt.addUser("pam") 269 t.Logf("Signed up %s (%s) for public team check", pam.username, pam.uid) 270 teamID3, err := pam.lookupImplicitTeam(false /* create */, impteamName, true /* public */) 271 require.NoError(t, err) 272 require.Equal(t, teamID2, teamID3) 273 274 _, err = teams.Load(context.Background(), pam.tc.G, keybase1.LoadTeamArg{ 275 ID: teamID3, 276 ForceRepoll: true, 277 Public: true, 278 }) 279 require.NoError(t, err) 280 281 _, err = teams.ResolveIDToName(context.Background(), pam.tc.G, teamID3) 282 require.NoError(t, err) 283 } 284 } 285 286 func trySBSConsolidationPubAndPriv(t *testing.T, impteamExpr string) { 287 trySBSConsolidation(t, impteamExpr, true /* public */) 288 trySBSConsolidation(t, impteamExpr, false /* public */) 289 } 290 291 func TestImplicitSBSConsolidation(t *testing.T) { 292 trySBSConsolidationPubAndPriv(t, "%v,%v,%v@rooter") 293 } 294 295 func TestImplicitSBSPromotion(t *testing.T) { 296 trySBSConsolidationPubAndPriv(t, "%v,%v@rooter#%v") 297 } 298 299 func TestImplicitSBSConsolidation2(t *testing.T) { 300 // Test "downgrade" case, where it should not downgrade if social 301 // assertion is a reader. Result should still be "ann,bob", not 302 // "ann#bob". 303 304 trySBSConsolidationPubAndPriv(t, "%v,%v#%v@rooter") 305 } 306 307 func TestImplicitSBSPukless(t *testing.T) { 308 tt := newTeamTester(t) 309 defer tt.cleanup() 310 311 ann := tt.addUser("ann") 312 bob := tt.addPuklessUser("bob") 313 t.Logf("Signed ann (%s) and pukless bob (%s)", ann.username, bob.username) 314 315 impteamName := fmt.Sprintf("%s,%s@rooter", ann.username, bob.username) 316 teamID, err := ann.lookupImplicitTeam(true /* create */, impteamName, false) 317 require.NoError(t, err) 318 319 t.Logf("Created team %s -> %s", impteamName, teamID) 320 321 bob.proveRooter() 322 323 // Because of a bug in team provisional status checking combined 324 // with how lookupImplicitTeam works, this load is busted right 325 // now: 326 327 // Loading "alice,bob@rooter" resolves "alice" to "alice", and 328 // "bob@rooter" to "bob", and it "redirects" the load to 329 // "alice,bob". But "alice,bob" cannot be loaded because 330 // "alice,bob" implicit team will not exist until alice completes 331 // "bob@rooter" invite. Team server blocks team load until that to 332 // prevent races. 333 334 t.Logf(":: Trying to load %q", impteamName) 335 _, err = ann.lookupImplicitTeam(false /* create */, impteamName, false) 336 require.Error(t, err) 337 // require.Equal(t, teamID, teamID2) 338 t.Logf("Loading %s failed with: %v", impteamName, err) 339 340 // The following load call will not work as well. So this team is 341 // essentially locked until bob gets PUK and alice keys him in. 342 343 expectedTeamName := fmt.Sprintf("%v,%v", ann.username, bob.username) 344 t.Logf(":: Trying to load %q", expectedTeamName) 345 _, err = ann.lookupImplicitTeam(false /* create */, expectedTeamName, false) 346 require.Error(t, err) 347 t.Logf("Loading %s failed with: %v", expectedTeamName, err) 348 349 bob.kickTeamRekeyd() 350 bob.perUserKeyUpgrade() 351 352 pollForConditionWithTimeout(t, 10*time.Second, "team resolved to ann,bob", func(ctx context.Context) bool { 353 team, err := teams.Load(ctx, ann.tc.G, keybase1.LoadTeamArg{ 354 ID: teamID, 355 ForceRepoll: true, 356 }) 357 require.NoError(t, err) 358 displayName, err := team.ImplicitTeamDisplayName(context.Background()) 359 require.NoError(t, err) 360 t.Logf("Got team back: %s", displayName.String()) 361 return displayName.String() == expectedTeamName 362 }) 363 364 teamID3, err := ann.lookupImplicitTeam(false /* create */, expectedTeamName, false) 365 require.NoError(t, err) 366 require.Equal(t, teamID, teamID3) 367 } 368 369 func TestResolveSBSTeamWithConflict(t *testing.T) { 370 tt := newTeamTester(t) 371 defer tt.cleanup() 372 373 ann := tt.addUser("ann") 374 bob := tt.addUser("bob") 375 376 // Create two implicit teams that will become conflicted later: 377 // - alice,bob 378 // - alice,bob@rooter 379 impteamName1 := fmt.Sprintf("%s,%s", ann.username, bob.username) 380 _, err := ann.lookupImplicitTeam(true /* create */, impteamName1, false /* public */) 381 require.NoError(t, err) 382 383 impteamName2 := fmt.Sprintf("%s,%s@rooter", ann.username, bob.username) 384 _, err = ann.lookupImplicitTeam(true /* create */, impteamName2, false /* public */) 385 require.NoError(t, err) 386 387 // Make sure we can resolve them right now and get two different team IDs. 388 teamid1, err := ann.lookupImplicitTeam(false /* create */, impteamName1, false /* public */) 389 require.NoError(t, err) 390 391 teamid2, err := ann.lookupImplicitTeam(false /* create */, impteamName2, false /* public */) 392 require.NoError(t, err) 393 394 require.NotEqual(t, teamid1, teamid2) 395 396 // Make sure we can load these teams. 397 teamObj1 := ann.loadTeamByID(teamid1, true /* admin */) 398 teamObj2 := ann.loadTeamByID(teamid2, true /* admin */) 399 400 // Check display names with conflicts, teams are not in conflict right now 401 // (ImplicitTeamDisplayNameString returns display name with suffix). 402 name, err := teamObj1.ImplicitTeamDisplayNameString(context.Background()) 403 require.NoError(t, err) 404 require.Equal(t, impteamName1, name) 405 t.Logf("Team 1 display name is: %s", name) 406 name, err = teamObj2.ImplicitTeamDisplayNameString(context.Background()) 407 require.NoError(t, err) 408 require.Equal(t, impteamName2, name) 409 t.Logf("Team 2 (w/ rooter) display name is: %s", name) 410 411 // Bob proves rooter. 412 bob.kickTeamRekeyd() 413 bob.proveRooter() 414 415 // Wait till team2 resolves. 416 ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ 417 ID: teamid2, 418 ForceRepoll: true, 419 }, keybase1.Seqno(2)) 420 421 // Make sure teams are still loadable by ID. 422 teamObj1 = ann.loadTeamByID(teamid1, true /* admin */) 423 teamObj2 = ann.loadTeamByID(teamid2, true /* admin */) 424 425 // Team1 display name with suffix should stay unchanged. 426 name, err = teamObj1.ImplicitTeamDisplayNameString(context.Background()) 427 require.NoError(t, err) 428 t.Logf("After resolution, team1 display name is: %s", name) 429 require.Equal(t, impteamName1, name) 430 431 // See if we can resolve implicit team by name without suffix and get the 432 // first team. 433 lookupTeamID, err := ann.lookupImplicitTeam(false /* create */, name, false /* public */) 434 require.NoError(t, err) 435 require.Equal(t, teamid1, lookupTeamID) 436 437 // Team 2 should be the one that gets conflict suffix. 438 name, err = teamObj2.ImplicitTeamDisplayNameString(context.Background()) 439 require.NoError(t, err) 440 t.Logf("After resolution, team2 display name is: %s", name) 441 require.Contains(t, name, "(conflicted copy") 442 require.Contains(t, name, "#1)") 443 444 // We should be able to resolve team2 by name with suffix. This is where 445 // the CORE-9732 cache bug was. 446 lookupTeamID, err = ann.lookupImplicitTeam(false /* create */, name, false /* public */) 447 require.NoError(t, err) 448 require.Equal(t, teamid2, lookupTeamID) 449 } 450 451 func TestResolveSBSConsolidatedTeamWithConflict(t *testing.T) { 452 tt := newTeamTester(t) 453 defer tt.cleanup() 454 455 ann := tt.addUser("ann") 456 bob := tt.addUser("bob") 457 458 // Create two implicit teams that will become conflicted later 459 // - alice,bob 460 // - alice,bob#bob@rooter 461 // The second team will consolidate to "alice,bob", but since there 462 // already is "alice,bob", it will become "conflicted copy #1". 463 464 impteamName1 := fmt.Sprintf("%s,%s", ann.username, bob.username) 465 _, err := ann.lookupImplicitTeam(true /* create */, impteamName1, false /* public */) 466 require.NoError(t, err) 467 468 impteamName2 := fmt.Sprintf("%s,%s#%s@rooter", ann.username, bob.username, bob.username) 469 _, err = ann.lookupImplicitTeam(true /* create */, impteamName2, false /* public */) 470 require.NoError(t, err) 471 472 // Make sure we can resolve them right now. 473 teamid1, err := ann.lookupImplicitTeam(false /* create */, impteamName1, false /* public */) 474 require.NoError(t, err) 475 476 teamid2, err := ann.lookupImplicitTeam(false /* create */, impteamName2, false /* public */) 477 require.NoError(t, err) 478 479 // Make sure we can load these teams. 480 teamObj1 := ann.loadTeamByID(teamid1, true /* admin */) 481 teamObj2 := ann.loadTeamByID(teamid2, true /* admin */) 482 483 name, err := teamObj1.ImplicitTeamDisplayNameString(context.Background()) 484 require.NoError(t, err) 485 require.Equal(t, impteamName1, name) 486 t.Logf("Team 1 display name is: %s", name) 487 name, err = teamObj2.ImplicitTeamDisplayNameString(context.Background()) 488 require.NoError(t, err) 489 require.Equal(t, impteamName2, name) 490 t.Logf("Team 2 (w/ rooter) display name is: %s", name) 491 492 // Bob proves rooter. 493 bob.kickTeamRekeyd() 494 bob.proveRooter() 495 496 // Wait till team2 resolves. 497 ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ 498 ID: teamid2, 499 ForceRepoll: true, 500 }, keybase1.Seqno(2)) 501 502 // Make sure teams are still loadable by ID. 503 _ = ann.loadTeamByID(teamid1, true /* admin */) 504 teamObj2 = ann.loadTeamByID(teamid2, true /* admin */) 505 506 name, err = teamObj2.ImplicitTeamDisplayNameString(context.Background()) 507 require.NoError(t, err) 508 t.Logf("Second team became: %s", name) 509 require.Contains(t, name, "(conflicted copy") 510 require.Contains(t, name, "#1)") 511 512 // See if we can lookup this team. 513 lookupTeamID, err := ann.lookupImplicitTeam(false /* create */, name, false /* public */) 514 require.NoError(t, err) 515 require.Equal(t, teamid2, lookupTeamID) 516 } 517 518 func TestCreateAndResolveEmailImpTeam(t *testing.T) { 519 tt := newTeamTester(t) 520 defer tt.cleanup() 521 522 ann := tt.addUser("ann") 523 bob := tt.addUser("bob") 524 525 email2 := keybase1.EmailAddress("BOB+" + bob.userInfo.email) 526 err := emails.AddEmail(bob.MetaContext(), email2, keybase1.IdentityVisibility_PRIVATE) 527 require.NoError(t, err) 528 err = kbtest.VerifyEmailAuto(bob.MetaContext(), email2) 529 require.NoError(t, err) 530 531 t.Logf("Bob's email is: %q", email2) 532 533 // Display names have to be lowercase 534 impteamName := fmt.Sprintf("%s,[%s]@email", ann.username, strings.ToLower(string(email2))) 535 t.Logf("Display name is: %q", impteamName) 536 537 teamID, err := ann.lookupImplicitTeam(true /* create */, impteamName, false /* public */) 538 require.NoError(t, err) 539 540 // Bob sets "BOB+..." email to public 541 bob.kickTeamRekeyd() 542 err = emails.SetVisibilityEmail(bob.MetaContext(), email2, keybase1.IdentityVisibility_PUBLIC) 543 require.NoError(t, err) 544 545 ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ 546 ID: teamID, 547 ForceRepoll: true, 548 }, keybase1.Seqno(2)) 549 550 teamID2, err := bob.lookupImplicitTeam(false /* create */, impteamName, false /* public */) 551 require.NoError(t, err) 552 require.Equal(t, teamID, teamID2) 553 } 554 555 func TestCreateImpteamWithoutTOFUResolver(t *testing.T) { 556 tt := newTeamTester(t) 557 defer tt.cleanup() 558 559 ann := tt.addUser("ann") 560 ann.disableTOFUSearch() 561 562 phone := kbtest.GenerateTestPhoneNumber() 563 email := "aa" + ann.userInfo.email 564 565 for _, impteamName := range []string{ 566 fmt.Sprintf("%s,%s@phone", ann.username, phone), 567 fmt.Sprintf("%s,[%s]@email", ann.username, email), 568 } { 569 _, err := ann.lookupImplicitTeam(true /* create */, impteamName, false /* public */) 570 require.Error(t, err) 571 require.Contains(t, err.Error(), "error 602") // user cannot search for assertions 572 } 573 574 // Make sure no teams got created. 575 res, err := ann.teamsClient.TeamListVerified(context.TODO(), keybase1.TeamListVerifiedArg{ 576 IncludeImplicitTeams: true, 577 }) 578 require.NoError(t, err) 579 require.Len(t, res.Teams, 0) 580 }