github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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  }