github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/team_implicit_reset_test.go (about)

     1  package systests
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	libkb "github.com/keybase/client/go/libkb"
    10  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    11  	teams "github.com/keybase/client/go/teams"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  // bob resets, implicit team lookup should still work for ann
    16  func TestImplicitTeamReset(t *testing.T) {
    17  	tt := newTeamTester(t)
    18  	defer tt.cleanup()
    19  
    20  	ann := tt.addUser("ann")
    21  	t.Logf("Signed up ann (%s)", ann.username)
    22  
    23  	bob := tt.addUser("bob")
    24  	t.Logf("Signed up bob (%s)", bob.username)
    25  
    26  	displayName := strings.Join([]string{ann.username, bob.username}, ",")
    27  	iteam, err := ann.lookupImplicitTeam(true /*create*/, displayName, false /*isPublic*/)
    28  	require.NoError(t, err)
    29  	t.Logf("team created (%s)", iteam)
    30  
    31  	iteam2, err := ann.lookupImplicitTeam(false /*create*/, displayName, false /*isPublic*/)
    32  	require.NoError(t, err)
    33  	require.Equal(t, iteam, iteam2, "second lookup should return same team")
    34  	t.Logf("team looked up before reset")
    35  
    36  	bob.reset()
    37  	t.Logf("Reset bob (%s)", bob.username)
    38  
    39  	iteam3, err := ann.lookupImplicitTeam(false /*create*/, displayName, false /*isPublic*/)
    40  	require.NoError(t, err)
    41  	require.Equal(t, iteam, iteam3, "lookup after reset should return same team")
    42  	t.Logf("team looked up before reset")
    43  }
    44  
    45  func TestImplicitTeamUserReset(t *testing.T) {
    46  	ctx := newSMUContext(t)
    47  	defer ctx.cleanup()
    48  
    49  	// Sign up two users, bob and alice.
    50  	alice := ctx.installKeybaseForUser("alice", 10)
    51  	alice.signup()
    52  	divDebug(ctx, "Signed up alice (%s)", alice.username)
    53  	bob := ctx.installKeybaseForUser("bob", 10)
    54  	bob.signup()
    55  	divDebug(ctx, "Signed up bob (%s)", bob.username)
    56  
    57  	displayName := strings.Join([]string{alice.username, bob.username}, ",")
    58  	team := alice.lookupImplicitTeam(true /*create*/, displayName, false /*isPublic*/)
    59  
    60  	divDebug(ctx, "Created implicit team %s\n", team.ID)
    61  
    62  	// Reset bob and reprovision.
    63  	bob.reset()
    64  	divDebug(ctx, "Reset bob (%s)", bob.username)
    65  
    66  	bob.loginAfterReset(10)
    67  	divDebug(ctx, "Bob logged in after reset")
    68  
    69  	// Setup team loader on alice
    70  	G := alice.getPrimaryGlobalContext()
    71  	teams.NewTeamLoaderAndInstall(G)
    72  
    73  	tryLoad := func(teamID keybase1.TeamID) (res *teams.Team) {
    74  		res, err := teams.Load(context.TODO(), G, keybase1.LoadTeamArg{
    75  			ID:          teamID,
    76  			Public:      teamID.IsPublic(),
    77  			ForceRepoll: true,
    78  		})
    79  		require.NoError(t, err)
    80  		return res
    81  	}
    82  
    83  	tryLoad(team.ID)
    84  
    85  	getRole := func(username string) keybase1.TeamRole {
    86  		g := G
    87  		loadUserArg := libkb.NewLoadUserArg(g).
    88  			WithNetContext(context.TODO()).
    89  			WithName(username).
    90  			WithPublicKeyOptional().
    91  			WithForcePoll(true)
    92  		upak, _, err := g.GetUPAKLoader().LoadV2(loadUserArg)
    93  		require.NoError(t, err)
    94  
    95  		team, err := teams.GetForTeamManagementByTeamID(context.TODO(), g, team.ID, false)
    96  		require.NoError(t, err)
    97  		role, err := team.MemberRole(context.TODO(), upak.Current.ToUserVersion())
    98  		require.NoError(t, err)
    99  		return role
   100  	}
   101  
   102  	// Bob's role should be NONE since he's still reset.
   103  	role := getRole(bob.username)
   104  	require.Equal(t, role, keybase1.TeamRole_NONE)
   105  
   106  	// Alice re-adds bob.
   107  	alice.reAddUserAfterReset(team, bob)
   108  	divDebug(ctx, "Re-Added bob as an owner")
   109  
   110  	// Check if sigchain still plays back correctly
   111  	tryLoad(team.ID)
   112  
   113  	// Check if bob is back as OWNER.
   114  	role = getRole(bob.username)
   115  	require.Equal(t, role, keybase1.TeamRole_OWNER)
   116  
   117  	// Reset and re-provision bob again.
   118  	bob.reset()
   119  	divDebug(ctx, "Reset bob again (%s) (poor bob)", bob.username)
   120  
   121  	bob.loginAfterReset(10)
   122  	divDebug(ctx, "Bob logged in after reset")
   123  
   124  	// Check if sigchain plays correctly, check if role is NONE.
   125  	tryLoad(team.ID)
   126  
   127  	role = getRole(bob.username)
   128  	require.Equal(t, role, keybase1.TeamRole_NONE)
   129  
   130  	// Alice re-adds bob, again.
   131  	alice.reAddUserAfterReset(team, bob)
   132  	divDebug(ctx, "Re-Added bob as an owner again")
   133  
   134  	// Check if sigchain plays correctly, at this point there are two
   135  	// sigs similar to:
   136  	//   "change_membership: { owner: ['xxxx%6'], none: ['xxxx%3'] }"
   137  	// with uids and eldest from before and after reset.
   138  	tryLoad(team.ID)
   139  
   140  	role = getRole(bob.username)
   141  	require.Equal(t, role, keybase1.TeamRole_OWNER)
   142  }
   143  
   144  // ann and bob both reset
   145  func TestImplicitTeamResetAll(t *testing.T) {
   146  	ctx := newSMUContext(t)
   147  	defer ctx.cleanup()
   148  
   149  	ann := ctx.installKeybaseForUser("ann", 10)
   150  	ann.signup()
   151  	ann.registerForNotifications()
   152  	divDebug(ctx, "Signed up ann (%s)", ann.username)
   153  
   154  	bob := ctx.installKeybaseForUser("bob", 10)
   155  	bob.signup()
   156  	bob.registerForNotifications()
   157  	divDebug(ctx, "Signed up bob (%s)", bob.username)
   158  
   159  	displayName := strings.Join([]string{ann.username, bob.username}, ",")
   160  	iteam := ann.lookupImplicitTeam(true /*create*/, displayName, false /*isPublic*/)
   161  	divDebug(ctx, "team created (%s)", iteam.ID)
   162  
   163  	iteam2 := ann.lookupImplicitTeam(false /*create*/, displayName, false /*isPublic*/)
   164  	require.Equal(t, iteam.ID, iteam2.ID, "second lookup should return same team")
   165  	divDebug(ctx, "team looked up before reset")
   166  
   167  	bob.reset()
   168  	divDebug(ctx, "Reset bob (%s)", bob.username)
   169  
   170  	ann.reset()
   171  	divDebug(ctx, "Reset ann (%s)", ann.username)
   172  
   173  	ann.loginAfterReset(10)
   174  	divDebug(ctx, "Ann logged in after reset")
   175  
   176  	ann.waitForTeamAbandoned(iteam.ID)
   177  
   178  	iteam3 := ann.lookupImplicitTeam(true /*create*/, displayName, false /*isPublic*/)
   179  	require.NotEqual(t, iteam.ID, iteam3.ID, "lookup after resets should return different team")
   180  	divDebug(ctx, "team looked up after resets")
   181  }
   182  
   183  func TestImplicitTeamResetAndSBSBringback(t *testing.T) {
   184  	// 1. ann and bob (both PUKful) make imp team
   185  	// 2. bob resets
   186  	// 3. bob doesn't get a PUK
   187  	// 4. ann re-adds bob (this just adds invite link, doesn't remove old PUKful bob)
   188  	// 5. bob gets a PUK
   189  	// 6. he should be automatically brought back as crypto member by alice
   190  	tt := newTeamTester(t)
   191  	defer tt.cleanup()
   192  
   193  	ann := tt.addUser("ann")
   194  	t.Logf("Signed up ann (%s)", ann.username)
   195  
   196  	bob := tt.addUser("bob")
   197  	t.Logf("Signed up bob (%s)", bob.username)
   198  
   199  	// (1)
   200  	displayName := strings.Join([]string{ann.username, bob.username}, ",")
   201  	iteam, err := ann.lookupImplicitTeam(true /* create */, displayName, false /* isPublic */)
   202  	require.NoError(t, err)
   203  	t.Logf("impteam created for %q (id: %s)", displayName, iteam)
   204  
   205  	bob.kickTeamRekeyd()
   206  	bob.reset()                  // (2)
   207  	bob.loginAfterResetPukless() // (3)
   208  
   209  	ann.reAddUserAfterReset(iteam, bob) // (4)
   210  
   211  	teamObj := ann.loadTeamByID(iteam, true)
   212  	nextSeqno := teamObj.NextSeqno()
   213  
   214  	bob.perUserKeyUpgrade() // (5)
   215  
   216  	t.Logf("Bob upgraded puk, polling for seqno %d", nextSeqno)
   217  	ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ID: iteam}, nextSeqno) // (6)
   218  
   219  	pollForTrue(t, ann.tc.G, func(i int) bool {
   220  		teamObj = ann.loadTeamByID(iteam, true)
   221  		role, err := teamObj.MemberRole(context.Background(), bob.userVersion())
   222  		require.NoError(t, err)
   223  		return role == keybase1.TeamRole_OWNER
   224  	})
   225  
   226  	invites := teamObj.GetActiveAndObsoleteInvites()
   227  	require.Equal(t, 0, len(invites), "leftover invite")
   228  }
   229  
   230  func testImplicitResetParameterized(t *testing.T, startPUK, getPUKAfter bool) {
   231  	tt := newTeamTester(t)
   232  	defer tt.cleanup()
   233  
   234  	ann := tt.addUser("ann")
   235  	t.Logf("Signed up ann (%s)", ann.username)
   236  
   237  	var bob *userPlusDevice
   238  	if startPUK {
   239  		bob = tt.addUser("bob")
   240  		t.Logf("Signed up bob (%s)", bob.username)
   241  	} else {
   242  		bob = tt.addPuklessUser("bob")
   243  		t.Logf("Signed up PUKless bob (%s)", bob.username)
   244  	}
   245  
   246  	displayName := strings.Join([]string{ann.username, bob.username}, ",")
   247  	iteam, err := ann.lookupImplicitTeam(true /* create */, displayName, false /* isPublic */)
   248  	require.NoError(t, err)
   249  	t.Logf("impteam created for %q (id: %s)", displayName, iteam)
   250  
   251  	ann.kickTeamRekeyd()
   252  	bob.reset()
   253  	if getPUKAfter {
   254  		// Bob resets and gets a PUK afterwards
   255  		bob.loginAfterReset()
   256  	} else {
   257  		// Bob resets and does not get a PUK.
   258  		bob.loginAfterResetPukless()
   259  	}
   260  
   261  	iteam2, err := ann.lookupImplicitTeam(false /* create */, displayName, false /* isPublic */)
   262  	require.NoError(t, err)
   263  	require.Equal(t, iteam, iteam2)
   264  
   265  	if startPUK {
   266  		// Wait for rotation after bob resets.
   267  		ann.waitForAnyRotateByID(iteam2, keybase1.Seqno(1), keybase1.Seqno(1))
   268  	}
   269  	ann.reAddUserAfterReset(iteam, bob)
   270  
   271  	if !getPUKAfter {
   272  		teamObj := ann.loadTeamByID(iteam, true)
   273  
   274  		// Bob is not a crypto member so no "real" role
   275  		role, err := teamObj.MemberRole(context.Background(), bob.userVersion())
   276  		require.NoError(t, err)
   277  		require.Equal(t, keybase1.TeamRole_NONE, role)
   278  
   279  		// but should have active invite
   280  		invite, uv, found := teamObj.FindActiveKeybaseInvite(bob.uid)
   281  		require.True(t, found)
   282  		require.EqualValues(t, bob.userVersion(), uv)
   283  		require.Equal(t, keybase1.TeamRole_OWNER, invite.Role)
   284  
   285  		// bob upgrades PUK
   286  		bob.kickTeamRekeyd()
   287  		bob.perUserKeyUpgrade()
   288  
   289  		// Wait for SBS
   290  		expectedSeqno := keybase1.Seqno(3)
   291  		ann.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ID: iteam}, expectedSeqno)
   292  	}
   293  
   294  	teamObj := ann.loadTeamByID(iteam, true)
   295  
   296  	// Bob is now a real crypto member!
   297  	role, err := teamObj.MemberRole(context.Background(), bob.userVersion())
   298  	require.NoError(t, err)
   299  	require.Equal(t, keybase1.TeamRole_OWNER, role)
   300  
   301  	// Make sure we are still getting the same team.
   302  	iteam3, err := ann.lookupImplicitTeam(false /* create */, displayName, false /* isPublic */)
   303  	require.Equal(t, iteam, iteam3)
   304  	require.NoError(t, err)
   305  }
   306  
   307  func TestImplicitTeamResetNoPUKtoNoPUK(t *testing.T) {
   308  	testImplicitResetParameterized(t, false /* startPUK */, false /* getPUKAfter */)
   309  }
   310  
   311  func TestImplicitTeamResetNoPUKtoPUK(t *testing.T) {
   312  	testImplicitResetParameterized(t, false /* startPUK */, true /* getPUKAfter */)
   313  }
   314  
   315  func TestImplicitTeamResetPUKtoNoPUK(t *testing.T) {
   316  	// We are lucky this case even works, it breaks the rules a little
   317  	// bit: there is no way to post removeMember+addInvite in one
   318  	// link, so when PUKful bob resets and ann re-adds him as PUKless,
   319  	// only invite link is posted. So technically there are 3 active
   320  	// people in the team at the time:
   321  	//   ann, PUKful bob, PUKless (invited) bob.
   322  
   323  	testImplicitResetParameterized(t, true /* startPUK */, false /* getPUKAfter */)
   324  }
   325  
   326  func TestImplicitTeamResetNoPukEncore(t *testing.T) {
   327  	// 1. ann and bob (both PUKful) make imp team
   328  	// 2. bob resets
   329  	// 3. bob doesn't get a PUK
   330  	// 4. ann re-adds bob (this just adds invite link, doesn't remove old PUKful bob)
   331  	// (up to this point, this case is tested in
   332  	// TestImplicitResetPUKtoNoPUK and TestChatSrvUserReset)
   333  	// 5. now bob resets again, but this time gets a PUK
   334  	// 6. when they are re-added, old PUKful bob is removed to make
   335  	//    room for new PUK-ful bob, and old invite is also sweeped
   336  	//    (completed).
   337  	tt := newTeamTester(t)
   338  	defer tt.cleanup()
   339  
   340  	ann := tt.addUser("ann")
   341  	t.Logf("Signed up ann (%s)", ann.username)
   342  
   343  	bob := tt.addUser("bob")
   344  	t.Logf("Signed up bob (%s)", bob.username)
   345  
   346  	// (1)
   347  	displayName := strings.Join([]string{ann.username, bob.username}, ",")
   348  	iteam, err := ann.lookupImplicitTeam(true /* create */, displayName, false /* isPublic */)
   349  	require.NoError(t, err)
   350  	t.Logf("impteam created for %q (id: %s)", displayName, iteam)
   351  
   352  	bob.reset()                  // (2)
   353  	bob.loginAfterResetPukless() // (3)
   354  
   355  	ann.reAddUserAfterReset(iteam, bob) // (4)
   356  
   357  	bob.reset() // (5)
   358  	bob.loginAfterReset()
   359  
   360  	ann.reAddUserAfterReset(iteam, bob) // (6)
   361  
   362  	teamObj := ann.loadTeamByID(iteam, true)
   363  	role, err := teamObj.MemberRole(context.Background(), bob.userVersion())
   364  	require.NoError(t, err)
   365  	require.Equal(t, keybase1.TeamRole_OWNER, role)
   366  
   367  	invites := teamObj.GetActiveAndObsoleteInvites()
   368  	require.Equal(t, 0, len(invites), "leftover invite")
   369  }
   370  
   371  func TestImplicitTeamResetBadReadds(t *testing.T) {
   372  	// Check if we can't ruin implicit team state by bad re-adds.
   373  	tt := newTeamTester(t)
   374  	defer tt.cleanup()
   375  
   376  	ann := tt.addUser("ann")
   377  	bob := tt.addUser("bob")
   378  	pam := tt.addPuklessUser("pam")
   379  
   380  	displayName := strings.Join([]string{ann.username, bob.username, pam.username}, ",")
   381  	iteam, err := ann.lookupImplicitTeam(true /* create */, displayName, false /* isPublic */)
   382  	require.NoError(t, err)
   383  	t.Logf("impteam created for %q (id: %s)", displayName, iteam)
   384  
   385  	bob.reset()
   386  	bob.loginAfterResetPukless()
   387  	t.Logf("%s reset and is now PUKless", bob.username)
   388  
   389  	teamObj := ann.loadTeamByID(iteam, true /* admin */)
   390  	_, err = teamObj.InviteMember(context.Background(), bob.username, keybase1.TeamRole_READER, libkb.NewNormalizedUsername(bob.username), bob.userVersion())
   391  	require.Error(t, err)
   392  	t.Logf("Error of InviteMember(bob, READER) is: %v", err)
   393  
   394  	pam.reset()
   395  	pam.loginAfterResetPukless()
   396  	t.Logf("%s reset and is now PUKless again", pam.username)
   397  
   398  	_, err = teamObj.InviteMember(context.Background(), pam.username, keybase1.TeamRole_READER, libkb.NewNormalizedUsername(pam.username), pam.userVersion())
   399  	require.Error(t, err)
   400  	t.Logf("Error of InviteMember(pam, READER) is: %v", err)
   401  }