github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/team_invite_test.go (about) 1 package systests 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 "time" 8 9 "golang.org/x/net/context" 10 11 "github.com/keybase/client/go/engine" 12 "github.com/keybase/client/go/jsonhelpers" 13 libkb "github.com/keybase/client/go/libkb" 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15 "github.com/keybase/client/go/teams" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestTeamInviteRooter(t *testing.T) { 21 tt := newTeamTester(t) 22 defer tt.cleanup() 23 24 tt.addUser("own") 25 tt.addUser("roo") 26 27 // user 0 creates a team 28 teamID, teamName := tt.users[0].createTeam2() 29 30 // user 0 adds a rooter assertion before the rooter user has proved their 31 // keybase account 32 rooterUser := tt.users[1].username + "@rooter" 33 tt.users[0].addTeamMember(teamName.String(), rooterUser, keybase1.TeamRole_WRITER) 34 35 // user 1 proves rooter, kicking rekeyd so it notices the proof 36 // beforehand so user 0 can notice proof faster. 37 tt.users[1].kickTeamRekeyd() 38 tt.users[1].proveRooter() 39 40 // user 0 should get gregor notification that the team changed 41 tt.users[0].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 42 43 // user 1 should also get gregor notification that the team changed 44 tt.users[1].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 45 46 // the team should have user 1 in it now as a writer 47 t0, err := teams.GetTeamByNameForTest(context.TODO(), tt.users[0].tc.G, teamName.String(), false, true) 48 if err != nil { 49 t.Fatal(err) 50 } 51 writers, err := t0.UsersWithRole(keybase1.TeamRole_WRITER) 52 if err != nil { 53 t.Fatal(err) 54 } 55 if len(writers) != 1 { 56 t.Fatalf("num writers: %d, expected 1", len(writers)) 57 } 58 if !writers[0].Uid.Equal(tt.users[1].uid) { 59 t.Errorf("writer uid: %s, expected %s", writers[0].Uid, tt.users[1].uid) 60 } 61 62 // the invite should not be in the active invite map 63 exists, err := t0.HasActiveInvite(tt.users[0].tc.MetaContext(), keybase1.TeamInviteName(tt.users[1].username), "rooter") 64 require.NoError(t, err) 65 require.False(t, exists) 66 require.Equal(t, 0, t0.NumActiveInvites()) 67 require.Equal(t, 0, len(t0.GetActiveAndObsoleteInvites())) 68 } 69 70 func TestTeamInviteGenericSocial(t *testing.T) { 71 tt := newTeamTester(t) 72 defer tt.cleanup() 73 74 tt.addUser("own") 75 tt.addUser("roo") 76 77 // user 0 creates a team 78 teamID, teamName := tt.users[0].createTeam2() 79 80 // user 0 adds an unresolved assertion to the team 81 assertion := tt.users[1].username + "@gubble.social" 82 tt.users[0].addTeamMember(teamName.String(), assertion, keybase1.TeamRole_WRITER) 83 84 // user 1 proves the assertion, kicking rekeyd so it notices the proof 85 // beforehand so user 0 can notice proof faster. 86 tt.users[1].kickTeamRekeyd() 87 tt.users[1].proveGubbleSocial() 88 89 // user 0 should get gregor notification that the team changed 90 tt.users[0].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 91 92 // user 1 should also get gregor notification that the team changed 93 tt.users[1].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 94 95 // the team should have user 1 in it now as a writer 96 t0, err := teams.GetTeamByNameForTest(context.TODO(), tt.users[0].tc.G, teamName.String(), false, true) 97 if err != nil { 98 t.Fatal(err) 99 } 100 writers, err := t0.UsersWithRole(keybase1.TeamRole_WRITER) 101 if err != nil { 102 t.Fatal(err) 103 } 104 if len(writers) != 1 { 105 t.Fatalf("num writers: %d, expected 1", len(writers)) 106 } 107 if !writers[0].Uid.Equal(tt.users[1].uid) { 108 t.Errorf("writer uid: %s, expected %s", writers[0].Uid, tt.users[1].uid) 109 } 110 111 // the invite should not be in the active invite map 112 exists, err := t0.HasActiveInvite(tt.users[0].tc.MetaContext(), keybase1.TeamInviteName(tt.users[1].username), "gubble.social") 113 require.NoError(t, err) 114 require.False(t, exists) 115 require.Equal(t, 0, t0.NumActiveInvites()) 116 require.Equal(t, 0, len(t0.GetActiveAndObsoleteInvites())) 117 } 118 119 func TestTeamInviteEmail(t *testing.T) { 120 tt := newTeamTester(t) 121 defer tt.cleanup() 122 123 tt.addUser("own") 124 tt.addUser("eml") 125 126 // user 0 creates a team 127 teamID, teamName := tt.users[0].createTeam2() 128 129 // user 0 adds a user by email 130 email := tt.users[1].username + "@keybase.io" 131 tt.users[0].addTeamMemberEmail(teamName.String(), email, keybase1.TeamRole_WRITER) 132 133 // user 1 gets the email 134 tokens := tt.users[1].readInviteEmails(email) 135 136 // user 1 accepts all invitations 137 tt.users[1].kickTeamRekeyd() 138 for _, token := range tokens { 139 tt.users[1].acceptEmailInvite(token) 140 } 141 142 // user 0 should get gregor notification that the team changed 143 tt.users[0].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 144 145 // user 1 should also get gregor notification that the team changed 146 tt.users[1].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 147 148 // the team should have user 1 in it now as a writer 149 t0, err := teams.GetTeamByNameForTest(context.TODO(), tt.users[0].tc.G, teamName.String(), false, true) 150 if err != nil { 151 t.Fatal(err) 152 } 153 writers, err := t0.UsersWithRole(keybase1.TeamRole_WRITER) 154 if err != nil { 155 t.Fatal(err) 156 } 157 if len(writers) != 1 { 158 t.Fatalf("num writers: %d, expected 1", len(writers)) 159 } 160 if !writers[0].Uid.Equal(tt.users[1].uid) { 161 t.Errorf("writer uid: %s, expected %s", writers[0].Uid, tt.users[1].uid) 162 } 163 164 // the invite should not be in the active invite map 165 exists, err := t0.HasActiveInvite(tt.users[0].tc.MetaContext(), keybase1.TeamInviteName(email), "email") 166 if err != nil { 167 t.Fatal(err) 168 } 169 if exists { 170 t.Error("after accepting invite, active invite still exists") 171 } 172 } 173 174 func TestTeamInviteAcceptOrRequest(t *testing.T) { 175 tt := newTeamTester(t) 176 defer tt.cleanup() 177 178 tt.addUser("own") 179 tt.addUser("eml") 180 181 // user 0 creates a team 182 teamID, teamName := tt.users[0].createTeam2() 183 184 // user 1 requests access 185 ret := tt.users[1].acceptInviteOrRequestAccess(teamName.String()) 186 require.EqualValues(t, ret, keybase1.TeamAcceptOrRequestResult{WasTeamName: true}) 187 188 // user 0 adds a user by email 189 email := tt.users[1].username + "@keybase.io" 190 tt.users[0].addTeamMemberEmail(teamName.String(), email, keybase1.TeamRole_WRITER) 191 192 // user 1 gets the email 193 tokens := tt.users[1].readInviteEmails(email) 194 require.Len(t, tokens, 1) 195 196 // user 1 accepts the invitation 197 tt.users[1].kickTeamRekeyd() 198 ret = tt.users[1].acceptInviteOrRequestAccess(tokens[0]) 199 require.EqualValues(t, ret, keybase1.TeamAcceptOrRequestResult{WasToken: true}) 200 201 // user 0 should get gregor notification that the team changed 202 tt.users[0].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 203 204 // user 1 should also get gregor notification that the team changed 205 tt.users[1].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 206 207 // the team should have user 1 in it now as a writer 208 t0, err := teams.GetTeamByNameForTest(context.TODO(), tt.users[0].tc.G, teamName.String(), false, true) 209 require.NoError(t, err) 210 writers, err := t0.UsersWithRole(keybase1.TeamRole_WRITER) 211 require.NoError(t, err) 212 require.Len(t, writers, 1) 213 if !writers[0].Uid.Equal(tt.users[1].uid) { 214 t.Errorf("writer uid: %s, expected %s", writers[0].Uid, tt.users[1].uid) 215 } 216 } 217 218 // bob resets and added to team with no keys, logs in and invite should 219 // be processed. 220 func TestTeamInviteResetNoKeys(t *testing.T) { 221 ctx := newSMUContext(t) 222 defer ctx.cleanup() 223 tt := newTeamTester(t) 224 defer tt.cleanup() 225 226 tt.addUser("own") 227 228 // user 0 creates a team 229 teamID, teamName := tt.users[0].createTeam2() 230 231 // user 0 should get gregor notification that the team changed and rotated key 232 tt.users[0].waitForTeamChangedAndRotated(teamID, keybase1.Seqno(1)) 233 234 bob := ctx.installKeybaseForUser("bob", 10) 235 bob.signup() 236 divDebug(ctx, "Signed up bob (%s)", bob.username) 237 bob.reset() 238 divDebug(ctx, "Reset bob (%s)", bob.username) 239 240 tt.users[0].addTeamMember(teamName.String(), bob.username, keybase1.TeamRole_WRITER) 241 divDebug(ctx, "Added bob as a writer") 242 243 // user 0 kicks rekeyd so it notices the puk 244 tt.users[0].kickTeamRekeyd() 245 246 bob.loginAfterReset(10) 247 divDebug(ctx, "Bob logged in after reset") 248 249 // user 0 should get gregor notification that the team changed 250 tt.users[0].waitForTeamChangedGregor(teamID, keybase1.Seqno(3)) 251 } 252 253 // See if we can re-invite user after they reset and thus make their 254 // first invitation obsolete. 255 func TestTeamReInviteAfterReset(t *testing.T) { 256 ctx := newSMUContext(t) 257 defer ctx.cleanup() 258 tt := newTeamTester(t) 259 defer tt.cleanup() 260 261 ann := tt.addUser("ann") 262 263 // Ann creates a team. 264 teamID, teamName := ann.createTeam2() 265 t.Logf("Created team %q", teamName.String()) 266 267 bob := ctx.installKeybaseForUserNoPUK("bob", 10) 268 bob.signupNoPUK() 269 divDebug(ctx, "Signed up bob (%s)", bob.username) 270 271 // Try to add bob to team, should add an invitation because bob is PUK-less. 272 ann.addTeamMember(teamName.String(), bob.username, keybase1.TeamRole_WRITER) // Invitation 1 273 274 // Reset, invalidates invitation 1. 275 bob.reset() 276 bob.loginAfterResetNoPUK(10) 277 278 // Try to add again (bob still doesn't have a PUK). Adding this 279 // invitation should automatically cancel first invitation. 280 ann.addTeamMember(teamName.String(), bob.username, keybase1.TeamRole_ADMIN) // Invitation 2 281 282 // Load team, see if we really have just one invite. 283 teamObj := ann.loadTeamByID(teamID, true /* admin */) 284 invites := teamObj.GetActiveAndObsoleteInvites() 285 require.Len(t, invites, 1) 286 for _, invite := range invites { 287 require.Equal(t, keybase1.TeamRole_ADMIN, invite.Role) 288 require.EqualValues(t, bob.userVersion().PercentForm(), invite.Name) 289 typ, err := invite.Type.C() 290 require.NoError(t, err) 291 require.Equal(t, keybase1.TeamInviteCategory_KEYBASE, typ) 292 break // check the first (and only) invite 293 } 294 295 t.Logf("Trying to get a PUK") 296 297 bob.primaryDevice().tctx.Tp.DisableUpgradePerUserKey = false 298 299 ann.kickTeamRekeyd() 300 err := bob.perUserKeyUpgrade() 301 require.NoError(t, err) 302 303 t.Logf("Bob got a PUK, now let's see if Ann's client adds him to team") 304 305 ann.waitForTeamChangedGregor(teamID, keybase1.Seqno(4)) 306 307 details, err := ann.teamsClient.TeamGet(context.TODO(), keybase1.TeamGetArg{Name: teamName.String()}) 308 require.NoError(t, err) 309 310 // Bob should have become an admin, because the second invitations 311 // should have been used, not the first one. 312 require.Equal(t, len(details.Members.Admins), 1) 313 require.Equal(t, details.Members.Admins[0].Username, bob.username) 314 } 315 316 func testImpTeamWithRooterParameterized(t *testing.T, public bool) { 317 t.Logf("testImpTeamWithRooterParameterized(public=%t)", public) 318 319 tt := newTeamTester(t) 320 defer tt.cleanup() 321 322 alice := tt.addUser("alice") 323 bob := tt.addUser("bob") 324 tt.logUserNames() 325 326 rooterUser := bob.username + "@rooter" 327 displayName := strings.Join([]string{alice.username, rooterUser}, ",") 328 329 team, err := alice.lookupImplicitTeam(true /*create*/, displayName, public) 330 require.NoError(t, err) 331 332 t.Logf("Created implicit team %v\n", team) 333 334 // TODO: Test chats, but it might be hard since implicit team tlf 335 // name resolution for chat commands needs KBFS running. 336 bob.kickTeamRekeyd() 337 bob.proveRooter() 338 339 alice.waitForTeamChangedGregor(team, keybase1.Seqno(2)) 340 341 // Poll for new team name, without the "@rooter" 342 newDisplayName := strings.Join([]string{alice.username, bob.username}, ",") 343 344 lookupAs := func(u *userPlusDevice) { 345 team2, err := u.lookupImplicitTeam(false /*create*/, newDisplayName, public) 346 require.NoError(t, err) 347 require.Equal(t, team, team2) 348 349 // Lookup by old name should get the same result 350 team2, err = u.lookupImplicitTeam(false /*create*/, displayName, public) 351 require.NoError(t, err) 352 require.Equal(t, team, team2) 353 354 // Test resolver 355 _, err = teams.ResolveIDToName(context.Background(), u.tc.G, team) 356 require.NoError(t, err) 357 } 358 359 lookupAs(alice) 360 lookupAs(bob) 361 362 if public { 363 doug := tt.addUser("doug") 364 t.Logf("Signed up %s (%s) to test public access", doug.username, doug.uid) 365 366 lookupAs(doug) 367 } 368 } 369 370 func TestImpTeamWithRooter(t *testing.T) { 371 testImpTeamWithRooterParameterized(t, false /* public */) 372 testImpTeamWithRooterParameterized(t, true /* public */) 373 } 374 375 func TestImpTeamWithRooterConflict(t *testing.T) { 376 tt := newTeamTester(t) 377 defer tt.cleanup() 378 379 alice := tt.addUser("alice") 380 bob := tt.addUser("bob") 381 382 displayNameRooter := strings.Join([]string{alice.username, bob.username + "@rooter"}, ",") 383 384 team, err := alice.lookupImplicitTeam(true /*create*/, displayNameRooter, false /*isPublic*/) 385 require.NoError(t, err) 386 387 t.Logf("Created implicit team %q -> %s\n", displayNameRooter, team) 388 389 // Bob has not proven rooter yet, so this will create a new, separate team. 390 displayNameKeybase := strings.Join([]string{alice.username, bob.username}, ",") 391 team2, err := alice.lookupImplicitTeam(true /*create*/, displayNameKeybase, false /*isPublic*/) 392 require.NoError(t, err) 393 require.NotEqual(t, team, team2) 394 395 t.Logf("Created implicit team %q -> %s\n", displayNameKeybase, team2) 396 397 bob.kickTeamRekeyd() 398 bob.proveRooter() 399 400 alice.waitForTeamChangedGregor(team, keybase1.Seqno(2)) 401 402 // Display name with rooter name now points to the conflict winner. 403 team3, err := alice.lookupImplicitTeam(false /*create*/, displayNameRooter, false /*isPublic*/) 404 require.NoError(t, err) 405 require.Equal(t, team2, team3) 406 407 // "LookupOrCreate" rooter name should work as well. 408 team3, err = alice.lookupImplicitTeam(true /*create*/, displayNameRooter, false /*isPublic*/) 409 require.NoError(t, err) 410 require.Equal(t, team2, team3) 411 412 // The original name works as well. 413 _, err = alice.lookupImplicitTeam(false /*create*/, displayNameKeybase, false /*isPublic*/) 414 require.NoError(t, err) 415 } 416 417 func TestImpTeamWithMultipleRooters(t *testing.T) { 418 tt := newTeamTester(t) 419 defer tt.cleanup() 420 421 alice := tt.addUser("ali") 422 bob := tt.addUser("bob") 423 charlie := tt.addUser("cha") 424 425 // Both teams include social assertions, so there is no definitive conflict winner. 426 displayNameRooter1 := strings.Join([]string{alice.username, bob.username, charlie.username + "@rooter"}, ",") 427 displayNameRooter2 := strings.Join([]string{alice.username, bob.username + "@rooter", charlie.username}, ",") 428 429 team1, err := alice.lookupImplicitTeam(true /*create*/, displayNameRooter1, false /*isPublic*/) 430 require.NoError(t, err) 431 432 team2, err := alice.lookupImplicitTeam(true /*create*/, displayNameRooter2, false /*isPublic*/) 433 require.NoError(t, err) 434 435 require.NotEqual(t, team1, team2) 436 437 alice.kickTeamRekeyd() 438 bob.proveRooter() 439 charlie.proveRooter() 440 441 toSeqno := keybase1.Seqno(2) 442 var found bool 443 for i := 0; (i < 10) && !found; i++ { 444 select { 445 case arg := <-alice.notifications.changeCh: 446 t.Logf("membership change received: %+v", arg) 447 if (arg.TeamID.Eq(team1) || arg.TeamID.Eq(team2)) && arg.Changes.MembershipChanged && !arg.Changes.KeyRotated && !arg.Changes.Renamed && arg.LatestSeqno == toSeqno { 448 t.Logf("change matched with %q", arg.TeamID) 449 found = true 450 } 451 case <-time.After(1 * time.Second): 452 } 453 } 454 455 require.True(t, found) // Expect "winning team" to be found. 456 457 displayName := strings.Join([]string{alice.username, bob.username, charlie.username}, ",") 458 teamFinal, err := alice.lookupImplicitTeam(false /*create*/, displayName, false /*isPublic*/) 459 require.NoError(t, err) 460 require.True(t, teamFinal == team1 || teamFinal == team2) 461 462 tid, err := alice.lookupImplicitTeam(false /*create*/, displayNameRooter1, false /*isPublic*/) 463 t.Logf("looking up team %s gives %v %v", displayNameRooter1, tid, err) 464 require.NoError(t, err) 465 require.Equal(t, teamFinal, tid) 466 467 tid, err = alice.lookupImplicitTeam(false /*create*/, displayNameRooter2, false /*isPublic*/) 468 require.NoError(t, err) 469 require.Equal(t, teamFinal, tid) 470 t.Logf("looking up team %s gives %v %v", displayNameRooter2, tid, err) 471 } 472 473 func TestClearSocialInvitesOnAdd(t *testing.T) { 474 tt := newTeamTester(t) 475 defer tt.cleanup() 476 477 // Disable gregor in this test so Ann does not immediately add Bob 478 // through SBS handler when bob proves Rooter. 479 ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{ 480 disableGregor: true, 481 suppressTeamChatAnnounce: true, 482 }) 483 484 tracer := ann.tc.G.CTimeTracer(context.Background(), "test-tracer", true) 485 defer tracer.Finish() 486 487 tracer.Stage("bob") 488 bob := tt.addUser("bob") 489 490 tracer.Stage("team") 491 team := ann.createTeam() 492 493 t.Logf("Ann created team %q", team) 494 495 bobBadRooter := "other" + bob.username 496 497 tracer.Stage("add 1") 498 ann.addTeamMember(team, bob.username+"@rooter", keybase1.TeamRole_WRITER) 499 tracer.Stage("add 2") 500 ann.addTeamMember(team, bobBadRooter+"@rooter", keybase1.TeamRole_WRITER) 501 502 tracer.Stage("prove rooter") 503 bob.proveRooter() 504 505 // Because bob@rooter is now proven by bob, this will add bob as a 506 // member instead of making an invitation. 507 tracer.Stage("add 3") 508 ann.addTeamMember(team, bob.username+"@rooter", keybase1.TeamRole_WRITER) 509 510 tracer.Stage("get team") 511 t0, err := teams.GetTeamByNameForTest(context.TODO(), ann.tc.G, team, false, true) 512 require.NoError(t, err) 513 514 tracer.Stage("assertions") 515 writers, err := t0.UsersWithRole(keybase1.TeamRole_WRITER) 516 require.NoError(t, err) 517 require.Equal(t, len(writers), 1) 518 require.True(t, writers[0].Uid.Equal(bob.uid)) 519 520 hasInv, err := t0.HasActiveInvite(ann.tc.MetaContext(), keybase1.TeamInviteName(bob.username), "rooter") 521 require.NoError(t, err) 522 require.False(t, hasInv, "Adding should have cleared bob...@rooter") 523 524 hasInv, err = t0.HasActiveInvite(ann.tc.MetaContext(), keybase1.TeamInviteName(bobBadRooter), "rooter") 525 require.NoError(t, err) 526 require.True(t, hasInv, "But should not have cleared otherbob...@rooter") 527 } 528 529 func TestSweepObsoleteKeybaseInvites(t *testing.T) { 530 tt := newTeamTester(t) 531 defer tt.cleanup() 532 533 // Disable gregor in this test so Ann does not immediately add Bob 534 // through SBS handler when bob gets PUK. 535 ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{ 536 disableGregor: true, 537 suppressTeamChatAnnounce: true, 538 }) 539 540 // Get UIDMapper caching out of the equation - assume in real 541 // life, tested actions are spread out in time and caching is not 542 // an issue. 543 ann.tc.G.UIDMapper.SetTestingNoCachingMode(true) 544 545 bob := tt.addPuklessUser("bob") 546 t.Logf("Signed up PUK-less user bob (%s)", bob.username) 547 548 team := ann.createTeam() 549 t.Logf("Team created (%s)", team) 550 551 ann.addTeamMember(team, bob.username, keybase1.TeamRole_WRITER) 552 553 bob.perUserKeyUpgrade() 554 t.Logf("Bob (%s) gets PUK", bob.username) 555 556 teamObj, err := teams.Load(context.Background(), ann.tc.G, keybase1.LoadTeamArg{ 557 Name: team, 558 ForceRepoll: true, 559 NeedAdmin: true, 560 }) 561 require.NoError(t, err) 562 563 // Use ChangeMembership to add bob without sweeping his keybase 564 // invite. 565 err = teamObj.ChangeMembership(context.Background(), keybase1.TeamChangeReq{ 566 Writers: []keybase1.UserVersion{bob.userVersion()}, 567 }) 568 require.NoError(t, err) 569 570 // Bob then leaves team. 571 bob.leave(team) 572 573 teamObj, err = teams.Load(context.Background(), ann.tc.G, keybase1.LoadTeamArg{ 574 Name: team, 575 ForceRepoll: true, 576 }) 577 require.NoError(t, err) 578 579 // Invite should be obsolete, so there are 0 active invites... 580 require.Equal(t, 0, teamObj.NumActiveInvites()) 581 582 // ...but one in "all invites". 583 allInvites := teamObj.GetActiveAndObsoleteInvites() 584 require.Equal(t, 1, len(allInvites)) 585 586 var invite keybase1.TeamInvite 587 for _, invite = range allInvites { 588 break // get the only invite returned 589 } 590 require.Equal(t, bob.userVersion().TeamInviteName(), invite.Name) 591 592 // Simulate SBS message to Ann trying to re-add Bob. 593 sbsMsg := keybase1.TeamSBSMsg{ 594 TeamID: teamObj.ID, 595 Score: 0, 596 Invitees: []keybase1.TeamInvitee{ 597 { 598 InviteID: invite.Id, 599 Uid: bob.uid, 600 EldestSeqno: 1, 601 Role: keybase1.TeamRole_WRITER, 602 }, 603 }, 604 } 605 606 err = teams.HandleSBSRequest(context.Background(), ann.tc.G, sbsMsg) 607 require.Error(t, err) 608 require.IsType(t, libkb.NotFoundError{}, err) 609 610 teamObj, err = teams.Load(context.Background(), ann.tc.G, keybase1.LoadTeamArg{ 611 Name: team, 612 ForceRepoll: true, 613 }) 614 require.NoError(t, err) 615 616 // Bob should still be out of the team. 617 role, err := teamObj.MemberRole(context.Background(), bob.userVersion()) 618 require.NoError(t, err) 619 require.Equal(t, keybase1.TeamRole_NONE, role) 620 } 621 622 func teamInviteRemoveIfHigherRole(t *testing.T, waitForRekeyd bool) { 623 t.Logf("teamInviteRemoveIfHigherRole(waitForRekeyd=%t)", waitForRekeyd) 624 625 tt := newTeamTester(t) 626 defer tt.cleanup() 627 628 userParams := standaloneUserArgs{ 629 disableGregor: true, 630 suppressTeamChatAnnounce: true, 631 } 632 633 var own *userPlusDevice 634 if waitForRekeyd { 635 own = tt.addUser("own") 636 } else { 637 own = makeUserStandalone(t, tt, "own", userParams) 638 } 639 roo := makeUserStandalone(t, tt, "roo", userParams) 640 tt.logUserNames() 641 642 teamID, teamName := own.createTeam2() 643 own.addTeamMember(teamName.String(), roo.username, keybase1.TeamRole_ADMIN) 644 own.addTeamMember(teamName.String(), roo.username+"@rooter", keybase1.TeamRole_WRITER) 645 646 t.Logf("Created team %s", teamName.String()) 647 648 if waitForRekeyd { 649 own.kickTeamRekeyd() 650 } 651 roo.proveRooter() 652 653 if waitForRekeyd { 654 // 3 links at this point: root, change_membership (add "roo"), 655 // invite (add "roo@rooter"). Waiting for 4th link: invite 656 // (cancel "roo@rooter"). 657 own.pollForTeamSeqnoLink(teamName.String(), keybase1.Seqno(4)) 658 } else { 659 teamObj := own.loadTeamByID(teamID, true /* admin */) 660 var invite keybase1.TeamInvite 661 invites := teamObj.GetActiveAndObsoleteInvites() 662 require.Len(t, invites, 1) 663 for _, invite = range invites { 664 // Get the (only) invite from the map to local variable 665 } 666 667 rooUv := roo.userVersion() 668 669 err := teams.HandleSBSRequest(context.Background(), own.tc.G, keybase1.TeamSBSMsg{ 670 TeamID: teamID, 671 Score: 0, 672 Invitees: []keybase1.TeamInvitee{ 673 { 674 InviteID: invite.Id, 675 Uid: rooUv.Uid, 676 EldestSeqno: rooUv.EldestSeqno, 677 }, 678 }, 679 }) 680 require.NoError(t, err) 681 } 682 683 // SBS handler should have canceled the invite after discovering roo is 684 // already a member with higher role. 685 teamObj := own.loadTeamByID(teamID, true /* admin */) 686 require.Len(t, teamObj.GetActiveAndObsoleteInvites(), 0) 687 role, err := teamObj.MemberRole(context.Background(), roo.userVersion()) 688 require.NoError(t, err) 689 require.Equal(t, keybase1.TeamRole_ADMIN, role) 690 } 691 692 func TestTeamInviteRemoveIfHigherRole(t *testing.T) { 693 // This test is parameterized. waitForRekeyd=true will wait for 694 // real rekeyd notification, waitForRekeyd=false will call SBS 695 // handler manually. 696 teamInviteRemoveIfHigherRole(t, true /* waitForRekeyd */) 697 teamInviteRemoveIfHigherRole(t, false /* waitForRekeyd */) 698 } 699 700 func testTeamInviteSweepOldMembers(t *testing.T, startPUKless bool) { 701 t.Logf(":: testTeamInviteSweepOldMembers(startPUKless: %t)", startPUKless) 702 703 tt := newTeamTester(t) 704 defer tt.cleanup() 705 706 own := tt.addUser("own") 707 var roo *userPlusDevice 708 if startPUKless { 709 roo = tt.addPuklessUser("roo") 710 } else { 711 roo = tt.addUser("roo") 712 } 713 tt.logUserNames() 714 715 teamID, teamName := own.createTeam2() 716 own.addTeamMember(teamName.String(), roo.username, keybase1.TeamRole_WRITER) 717 own.addTeamMember(teamName.String(), roo.username+"@rooter", keybase1.TeamRole_ADMIN) 718 719 t.Logf("Created team %s", teamName.String()) 720 721 roo.kickTeamRekeyd() 722 roo.reset() 723 roo.loginAfterReset() 724 725 roo.proveRooter() 726 727 // 3 links to created team, add roo, and add roo@rooter. 728 // + 1 links (rotate, change_membership) to add roo in startPUKless=false case; 729 // or +2 links (change_membersip, cancel invite) to add roo in startPUKless=true case. 730 n := keybase1.Seqno(4) 731 if startPUKless { 732 n = keybase1.Seqno(5) 733 } 734 own.pollForTeamSeqnoLink(teamName.String(), n) 735 736 teamObj := own.loadTeamByID(teamID, true /* admin */) 737 // 0 total invites: rooter invite was completed, and keybase invite was sweeped 738 require.Len(t, teamObj.GetActiveAndObsoleteInvites(), 0) 739 role, err := teamObj.MemberRole(context.Background(), roo.userVersion()) 740 require.NoError(t, err) 741 require.Equal(t, keybase1.TeamRole_ADMIN, role) 742 743 members, err := teamObj.Members() 744 require.NoError(t, err) 745 require.Len(t, members.Owners, 1) 746 require.Len(t, members.Admins, 1) 747 } 748 749 func TestTeamInviteSweepOldMembers(t *testing.T) { 750 testTeamInviteSweepOldMembers(t, false /* startPUKless */) 751 testTeamInviteSweepOldMembers(t, true /* startPUKless */) 752 } 753 754 func TestSBSInviteReuse(t *testing.T) { 755 // Test if server can reuse TOFU invites. 756 tt := newTeamTester(t) 757 defer tt.cleanup() 758 759 makeUser := func(name string) *userPlusDevice { 760 user := makeUserStandalone(t, tt, name, standaloneUserArgs{ 761 disableGregor: true, 762 suppressTeamChatAnnounce: true, 763 }) 764 return user 765 } 766 767 ann := makeUser("ann") 768 bob := makeUser("bob") 769 joe := makeUser("joe") 770 771 teamID, teamName := ann.createTeam2() 772 t.Logf("Team created (%s)", teamID) 773 774 // Use sbs_test.go code to verify email. 775 sbsEmail := &userSBSEmail{} 776 sbsEmail.SetUser(bob) 777 778 email := bob.userInfo.email 779 ann.addTeamMemberEmail(teamName.String(), email, keybase1.TeamRole_WRITER) 780 781 sbsEmail.Verify() 782 783 // Get first invite ID, will be the one we've just added. 784 teamObj := ann.loadTeamByID(teamID, true /* admin */) 785 allInvites := teamObj.GetActiveAndObsoleteInvites() 786 require.Len(t, allInvites, 1) 787 var inviteID keybase1.TeamInviteID 788 for _, invite := range allInvites { 789 inviteID = invite.Id 790 require.True(t, invite.Type.Eq(keybase1.NewTeamInviteTypeDefault(keybase1.TeamInviteCategory_EMAIL))) 791 } 792 793 // Create a SBS message payload that we will be using to give directly to 794 // the SBS handler function for given user. So it will appear as if it 795 // comes from gregor. 796 sbsMsg := keybase1.TeamSBSMsg{ 797 TeamID: teamID, 798 Invitees: []keybase1.TeamInvitee{ 799 { 800 InviteID: inviteID, 801 Uid: bob.uid, 802 EldestSeqno: 1, 803 // Role can be whatever - client should not trust it. 804 Role: keybase1.TeamRole_ADMIN, 805 }, 806 }, 807 } 808 809 err := teams.HandleSBSRequest(context.Background(), ann.tc.G, sbsMsg) 810 require.NoError(t, err) 811 812 // Invite should have been completed. 813 teamObj = ann.loadTeamByID(teamID, true /* admin */) 814 require.Len(t, teamObj.GetActiveAndObsoleteInvites(), 0) 815 816 // Try to send the same message but with different UID. 817 sbsMsg.Invitees[0].Uid = joe.uid 818 err = teams.HandleSBSRequest(context.Background(), ann.tc.G, sbsMsg) 819 require.Error(t, err) 820 require.IsType(t, libkb.NotFoundError{}, err) 821 require.Contains(t, err.Error(), "Invite not found") 822 } 823 824 func proveGubbleUniverse(tc *libkb.TestContext, serviceName, endpoint string, username string, secretUI libkb.SecretUI) keybase1.SigID { 825 tc.T.Logf("proof for %s", serviceName) 826 g := tc.G 827 proofService := g.GetProofServices().GetServiceType(context.Background(), serviceName) 828 require.NotNil(tc.T, proofService) 829 830 // Post a proof to the testing generic social service 831 arg := keybase1.StartProofArg{ 832 Service: proofService.GetTypeName(), 833 Username: username, 834 Force: false, 835 PromptPosted: true, 836 } 837 eng := engine.NewProve(g, &arg) 838 839 // Post the proof to the gubble network and verify the sig hash 840 outputInstructionsHook := func(ctx context.Context, _ keybase1.OutputInstructionsArg) error { 841 sigID := eng.SigID() 842 require.False(tc.T, sigID.IsNil()) 843 mctx := libkb.NewMetaContext(ctx, g) 844 845 apiArg := libkb.APIArg{ 846 Endpoint: fmt.Sprintf("gubble_universe/%s", endpoint), 847 SessionType: libkb.APISessionTypeREQUIRED, 848 Args: libkb.HTTPArgs{ 849 "sig_hash": libkb.S{Val: sigID.String()}, 850 "username": libkb.S{Val: username}, 851 "kb_username": libkb.S{Val: username}, 852 "kb_ua": libkb.S{Val: libkb.UserAgent}, 853 "json_redirect": libkb.B{Val: true}, 854 }, 855 } 856 _, err := g.API.Post(libkb.NewMetaContext(ctx, g), apiArg) 857 require.NoError(tc.T, err) 858 859 apiArg = libkb.APIArg{ 860 Endpoint: fmt.Sprintf("gubble_universe/%s/%s/proofs", endpoint, username), 861 SessionType: libkb.APISessionTypeNONE, 862 } 863 res, err := g.GetAPI().Get(mctx, apiArg) 864 require.NoError(tc.T, err) 865 objects, err := jsonhelpers.AtSelectorPath(res.Body, []keybase1.SelectorEntry{ 866 { 867 IsKey: true, 868 Key: "res", 869 }, 870 { 871 IsKey: true, 872 Key: "keybase_proofs", 873 }, 874 }, tc.T.Logf, libkb.NewInvalidPVLSelectorError) 875 require.NoError(tc.T, err) 876 require.Len(tc.T, objects, 1) 877 878 var proofs []keybase1.ParamProofJSON 879 err = objects[0].UnmarshalAgain(&proofs) 880 require.NoError(tc.T, err) 881 require.True(tc.T, len(proofs) >= 1) 882 for _, proof := range proofs { 883 if proof.KbUsername == username && sigID.Eq(proof.SigHash) { 884 return nil 885 } 886 } 887 assert.Fail(tc.T, "proof not found") 888 return nil 889 } 890 891 proveUI := &ProveUIMock{outputInstructionsHook: outputInstructionsHook} 892 uis := libkb.UIs{ 893 LogUI: g.Log, 894 SecretUI: secretUI, 895 ProveUI: proveUI, 896 } 897 m := libkb.NewMetaContextTODO(g).WithUIs(uis) 898 err := engine.RunEngine2(m, eng) 899 checkFailed(tc.T.(testing.TB)) 900 require.NoError(tc.T, err) 901 require.False(tc.T, proveUI.overwrite) 902 require.False(tc.T, proveUI.warning) 903 require.False(tc.T, proveUI.recheck) 904 require.True(tc.T, proveUI.checked) 905 return eng.SigID() 906 } 907 908 type ProveUIMock struct { 909 username, recheck, overwrite, warning, checked bool 910 postID string 911 outputInstructionsHook func(context.Context, keybase1.OutputInstructionsArg) error 912 okToCheckHook func(context.Context, keybase1.OkToCheckArg) (bool, string, error) 913 checkingHook func(context.Context, keybase1.CheckingArg) error 914 } 915 916 func (p *ProveUIMock) PromptOverwrite(_ context.Context, arg keybase1.PromptOverwriteArg) (bool, error) { 917 p.overwrite = true 918 return true, nil 919 } 920 921 func (p *ProveUIMock) PromptUsername(_ context.Context, arg keybase1.PromptUsernameArg) (string, error) { 922 p.username = true 923 return "", nil 924 } 925 926 func (p *ProveUIMock) OutputPrechecks(_ context.Context, arg keybase1.OutputPrechecksArg) error { 927 return nil 928 } 929 930 func (p *ProveUIMock) PreProofWarning(_ context.Context, arg keybase1.PreProofWarningArg) (bool, error) { 931 p.warning = true 932 return true, nil 933 } 934 935 func (p *ProveUIMock) OutputInstructions(ctx context.Context, arg keybase1.OutputInstructionsArg) error { 936 if p.outputInstructionsHook != nil { 937 return p.outputInstructionsHook(ctx, arg) 938 } 939 return nil 940 } 941 942 func (p *ProveUIMock) OkToCheck(ctx context.Context, arg keybase1.OkToCheckArg) (bool, error) { 943 if !p.checked { 944 p.checked = true 945 ok, postID, err := p.okToCheckHook(ctx, arg) 946 p.postID = postID 947 return ok, err 948 } 949 return false, fmt.Errorf("Check should have worked the first time!") 950 } 951 952 func (p *ProveUIMock) Checking(ctx context.Context, arg keybase1.CheckingArg) (err error) { 953 if p.checkingHook != nil { 954 err = p.checkingHook(ctx, arg) 955 } 956 p.checked = true 957 return err 958 } 959 960 func (p *ProveUIMock) ContinueChecking(ctx context.Context, _ int) (bool, error) { 961 return true, nil 962 } 963 964 func (p *ProveUIMock) DisplayRecheckWarning(_ context.Context, arg keybase1.DisplayRecheckWarningArg) error { 965 p.recheck = true 966 return nil 967 } 968 969 func checkFailed(t testing.TB) { 970 if t.Failed() { 971 // The test failed. Possibly in anothe goroutine. Look earlier in the logs for the real failure. 972 require.FailNow(t, "test already failed") 973 } 974 }