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  }