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

     1  package systests
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	"golang.org/x/net/context"
     9  
    10  	"github.com/keybase/client/go/libkb"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/keybase/client/go/teams"
    13  )
    14  
    15  func testTeamTx1(t *testing.T, byUV bool) {
    16  	tt := newTeamTester(t)
    17  	defer tt.cleanup()
    18  
    19  	ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{
    20  		disableGregor:            true,
    21  		suppressTeamChatAnnounce: true,
    22  	})
    23  	t.Logf("Signed up ann (%s)", ann.username)
    24  
    25  	bob := tt.addPuklessUser("bob")
    26  	t.Logf("Signed up PUK-less user bob (%s)", bob.username)
    27  
    28  	tracy := tt.addUser("trc")
    29  	t.Logf("Signed up PUK-ful user trc (%s)", tracy.username)
    30  
    31  	botua := tt.addUser("ua")
    32  	t.Logf("Signed up user ua (%s) to be a bot", tracy.username)
    33  
    34  	restrictedBotua := tt.addUser("r_ua")
    35  	t.Logf("Signed up user ua (%s) to be a restricted bot", tracy.username)
    36  
    37  	team := ann.createTeam()
    38  	t.Logf("Team created (%s)", team)
    39  
    40  	// TRANSACTION 1 - add bob (keybase-type invite) and tracy (crypto member)
    41  
    42  	teamObj := ann.loadTeam(team, true /* admin */)
    43  
    44  	var err error
    45  	tx := teams.CreateAddMemberTx(teamObj)
    46  	tx.AllowPUKless = true
    47  	if byUV {
    48  		err = tx.AddMemberByUV(context.Background(), bob.userVersion(), keybase1.TeamRole_WRITER, nil)
    49  		require.NoError(t, err)
    50  		err = tx.AddMemberByUV(context.Background(), tracy.userVersion(), keybase1.TeamRole_READER, nil)
    51  		require.NoError(t, err)
    52  		err = tx.AddMemberByUV(context.Background(), botua.userVersion(), keybase1.TeamRole_BOT, nil)
    53  		require.NoError(t, err)
    54  		err = tx.AddMemberByUV(context.Background(), restrictedBotua.userVersion(), keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{})
    55  		require.NoError(t, err)
    56  	} else {
    57  		err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil)
    58  		require.NoError(t, err)
    59  		err = tx.AddMemberByUsername(context.Background(), tracy.username, keybase1.TeamRole_READER, nil)
    60  		require.NoError(t, err)
    61  		err = tx.AddMemberByUsername(context.Background(), botua.username, keybase1.TeamRole_BOT, nil)
    62  		require.NoError(t, err)
    63  		err = tx.AddMemberByUsername(context.Background(), restrictedBotua.username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{})
    64  		require.NoError(t, err)
    65  	}
    66  
    67  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
    68  	require.NoError(t, err)
    69  
    70  	teamObj = ann.loadTeam(team, true /* admin */)
    71  	require.Equal(t, 1, teamObj.NumActiveInvites())
    72  	invites := teamObj.GetActiveAndObsoleteInvites()
    73  	require.Equal(t, 1, len(invites))
    74  	for _, invite := range teamObj.GetActiveAndObsoleteInvites() {
    75  		uv, err := invite.KeybaseUserVersion()
    76  		require.NoError(t, err)
    77  		require.EqualValues(t, bob.userVersion(), uv)
    78  	}
    79  
    80  	members, err := teamObj.Members()
    81  	require.NoError(t, err)
    82  	require.Equal(t, 1, len(members.Owners))
    83  	require.Equal(t, 0, len(members.Admins))
    84  	require.Equal(t, 0, len(members.Writers))
    85  	require.Equal(t, 1, len(members.Readers))
    86  	require.EqualValues(t, tracy.userVersion(), members.Readers[0])
    87  	require.Equal(t, 1, len(members.Bots))
    88  	require.EqualValues(t, botua.userVersion(), members.Bots[0])
    89  	require.Equal(t, 1, len(members.RestrictedBots))
    90  	require.EqualValues(t, restrictedBotua.userVersion(), members.RestrictedBots[0])
    91  
    92  	// TRANSACTION 2 - bob gets puk, add bob but not through SBS - we
    93  	// expect the invite to be sweeped away by this transaction.
    94  
    95  	bob.perUserKeyUpgrade()
    96  
    97  	teamObj = ann.loadTeam(team, true /* admin */)
    98  	tx = teams.CreateAddMemberTx(teamObj)
    99  	err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil)
   100  	require.NoError(t, err)
   101  
   102  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   103  	require.NoError(t, err)
   104  
   105  	teamObj = ann.loadTeam(team, true /* admin */)
   106  	members, err = teamObj.Members()
   107  	require.NoError(t, err)
   108  	require.Equal(t, 1, len(members.Owners))
   109  	require.Equal(t, 0, len(members.Admins))
   110  	require.Equal(t, 1, len(members.Writers))
   111  	require.EqualValues(t, bob.userVersion(), members.Writers[0])
   112  	require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites()))
   113  	require.Equal(t, 1, len(members.Readers))
   114  	require.EqualValues(t, tracy.userVersion(), members.Readers[0])
   115  	require.Equal(t, 1, len(members.Bots))
   116  	require.EqualValues(t, botua.userVersion(), members.Bots[0])
   117  	require.Equal(t, 1, len(members.RestrictedBots))
   118  	require.EqualValues(t, restrictedBotua.userVersion(), members.RestrictedBots[0])
   119  }
   120  
   121  func TestTeamTxAddByUsername(t *testing.T) {
   122  	testTeamTx1(t, false /* byUV */)
   123  }
   124  
   125  func TestTeamTxAddByUV(t *testing.T) {
   126  	testTeamTx1(t, true /* byUV */)
   127  }
   128  
   129  func TestTeamTxDependency(t *testing.T) {
   130  	tt := newTeamTester(t)
   131  	defer tt.cleanup()
   132  
   133  	ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{
   134  		disableGregor:            true,
   135  		suppressTeamChatAnnounce: true,
   136  	})
   137  	t.Logf("Signed up ann (%s)", ann.username)
   138  
   139  	bob := tt.addPuklessUser("bob")
   140  	t.Logf("Signed up PUK-less user bob (%s)", bob.username)
   141  
   142  	tracy := tt.addUser("trc")
   143  	t.Logf("Signed up PUK-ful user trc (%s)", tracy.username)
   144  
   145  	team := ann.createTeam()
   146  	t.Logf("Team created (%s)", team)
   147  
   148  	ann.addTeamMember(team, bob.username, keybase1.TeamRole_WRITER)
   149  
   150  	teamObj := ann.loadTeam(team, true /* admin */)
   151  	members, err := teamObj.Members()
   152  	require.NoError(t, err)
   153  	require.Equal(t, 1, len(members.Owners))
   154  	require.Equal(t, 0, len(members.Admins)+len(members.Writers)+len(members.Readers)+len(members.Bots)+len(members.RestrictedBots))
   155  	require.EqualValues(t, ann.userVersion(), members.Owners[0])
   156  	require.Equal(t, 1, teamObj.NumActiveInvites())
   157  
   158  	bob.perUserKeyUpgrade()
   159  
   160  	// Transaction time!
   161  
   162  	// The transaction will try to achieve the following:
   163  	// 1) Add Tracy as crypto member,
   164  	// 2) sweep old bob@keybase invite (pukless member),
   165  	// 3) add bob as crypto member.
   166  
   167  	// The catch is that (3) depends on (2), so signature that does
   168  	// (3) has to happen after (2). Signatures in flight after (2) are
   169  	// as follows:
   170  	// 1. change_membership (adds: trc)
   171  	// 2. invite (cancel: bob@keybase)
   172  
   173  	// Adding bob as a crypto member should not mutate change_membership 1.,
   174  	// but instead create new change_membership.
   175  
   176  	teamObj = ann.loadTeam(team, true /* admin */)
   177  
   178  	tx := teams.CreateAddMemberTx(teamObj)
   179  	err = tx.AddMemberByUsername(context.Background(), tracy.username, keybase1.TeamRole_READER, nil)
   180  	require.NoError(t, err)
   181  	err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil)
   182  	require.NoError(t, err)
   183  
   184  	payloads := tx.DebugPayloads()
   185  	require.Equal(t, 3, len(payloads))
   186  
   187  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   188  	require.NoError(t, err)
   189  
   190  	// State is still fine even without ordering, because nor server
   191  	// neither team player cares about that.
   192  
   193  	teamObj = ann.loadTeam(team, true /* admin */)
   194  	members, err = teamObj.Members()
   195  	require.NoError(t, err)
   196  	require.Equal(t, 1, len(members.Owners))
   197  	require.EqualValues(t, ann.userVersion(), members.Owners[0])
   198  	require.Equal(t, 0, len(members.Admins))
   199  	require.Equal(t, 1, len(members.Writers))
   200  	require.EqualValues(t, bob.userVersion(), members.Writers[0])
   201  	require.Equal(t, 1, len(members.Readers))
   202  	require.EqualValues(t, tracy.userVersion(), members.Readers[0])
   203  	require.Equal(t, 0, teamObj.NumActiveInvites())
   204  	require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites()))
   205  	require.Equal(t, 0, len(members.Bots))
   206  	require.Equal(t, 0, len(members.RestrictedBots))
   207  
   208  	// Try the opposite logic: reset bob, and try to re-add them as
   209  	// pukless. The `invite` link should happen after crypto member
   210  	// sweeping `change_membership`.
   211  	bob.reset()
   212  	bob.loginAfterResetPukless()
   213  
   214  	tx = teams.CreateAddMemberTx(teamObj)
   215  	tx.AllowPUKless = true
   216  	_, _, _, err = tx.AddOrInviteMemberByAssertion(context.Background(), fmt.Sprintf("%s@rooter", tracy.username), keybase1.TeamRole_WRITER, nil)
   217  	require.NoError(t, err)
   218  	err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil)
   219  	require.NoError(t, err)
   220  
   221  	payloads = tx.DebugPayloads()
   222  	require.Equal(t, 3, len(payloads))
   223  
   224  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   225  	require.NoError(t, err)
   226  }
   227  
   228  func TestTeamTxSweepMembers(t *testing.T) {
   229  	tt := newTeamTester(t)
   230  	defer tt.cleanup()
   231  
   232  	ann := tt.addUser("ann")
   233  	t.Logf("Signed up user ann (%s)", ann.username)
   234  
   235  	bob := tt.addUser("bob")
   236  	t.Logf("Signed up user bob (%s)", bob.username)
   237  
   238  	pat := tt.addPuklessUser("pat")
   239  	t.Logf("Signed up PUKless user pat (%s)", pat.username)
   240  
   241  	team := ann.createTeam()
   242  	t.Logf("Team created (%s)", team)
   243  
   244  	ann.addTeamMember(team, bob.username, keybase1.TeamRole_WRITER)
   245  
   246  	bob.reset()
   247  	bob.loginAfterReset()
   248  
   249  	t.Logf("Bob (%s) resets and reprovisions, he is now: %v", bob.username, bob.userVersion())
   250  
   251  	// Wait for CLKR and RotateKey link.
   252  	teamID := ann.loadTeam(team, false /* admin */).ID
   253  	ann.waitForAnyRotateByID(teamID, keybase1.Seqno(2) /* toSeqno */, keybase1.Seqno(1) /* toHiddenSeqno */)
   254  
   255  	teamObj := ann.loadTeam(team, true /* admin */)
   256  	tx := teams.CreateAddMemberTx(teamObj)
   257  	err := tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_READER, nil)
   258  	require.NoError(t, err)
   259  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   260  	require.NoError(t, err)
   261  
   262  	teamObj = ann.loadTeam(team, true /* admin */)
   263  	members, err := teamObj.Members()
   264  	require.NoError(t, err)
   265  	require.Equal(t, 1, len(members.Owners))
   266  	require.Equal(t, 1, len(members.Readers))
   267  	require.Equal(t, 0, len(members.Admins)+len(members.Writers)+len(members.Bots)+len(members.RestrictedBots))
   268  	require.EqualValues(t, ann.userVersion(), members.Owners[0])
   269  	require.EqualValues(t, bob.userVersion(), members.Readers[0])
   270  	require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites()))
   271  }
   272  
   273  func TestTeamTxMultipleMembers(t *testing.T) {
   274  	tt := newTeamTester(t)
   275  	defer tt.cleanup()
   276  
   277  	ann := tt.addUser("ann")
   278  	t.Logf("Signed up user ann (%s)", ann.username)
   279  
   280  	// user 0 - ann, team owner
   281  	// user 1,2,3 - zzz, normal user
   282  	// user 4,5,6 - yyy, pukless user
   283  
   284  	for i := 0; i < 3; i++ {
   285  		user := tt.addUser("zzz")
   286  		t.Logf("Signed up normal user %d (%s, %v)", i, user.username, user.userVersion())
   287  	}
   288  
   289  	for i := 0; i < 3; i++ {
   290  		user := tt.addPuklessUser("yyy")
   291  		t.Logf("Signed up pukless user %d (%s, %v)", i, user.username, user.userVersion())
   292  	}
   293  
   294  	team := ann.createTeam()
   295  	t.Logf("Team created (%s)", team)
   296  
   297  	teamObj := ann.loadTeam(team, true /* admin */)
   298  	tx := teams.CreateAddMemberTx(teamObj)
   299  	tx.AllowPUKless = true
   300  	for i := 1; i < 7; i++ {
   301  		err := tx.AddMemberByUsername(context.Background(), tt.users[i].username, keybase1.TeamRole_WRITER, nil)
   302  		require.NoError(t, err)
   303  	}
   304  	err := tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   305  	require.NoError(t, err)
   306  
   307  	for i := 4; i <= 5; i++ {
   308  		user := tt.users[i]
   309  		user.reset()
   310  		user.loginAfterReset()
   311  		t.Logf("Reset pukless user %d (%s, %v)", i, user.username, user.userVersion())
   312  	}
   313  
   314  	teamObj = ann.loadTeam(team, true /* admin */)
   315  	tx = teams.CreateAddMemberTx(teamObj)
   316  	for i := 4; i <= 5; i++ {
   317  		err := tx.AddMemberByUsername(context.Background(), tt.users[i].username, keybase1.TeamRole_WRITER, nil)
   318  		require.NoError(t, err)
   319  	}
   320  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   321  	require.NoError(t, err)
   322  
   323  	teamObj = ann.loadTeam(team, true /* admin */)
   324  	members, err := teamObj.Members()
   325  	require.NoError(t, err)
   326  	require.Equal(t, 1, len(members.Owners))
   327  	require.Equal(t, 5, len(members.Writers))
   328  	require.Equal(t, 0, len(members.Readers)+len(members.Admins)+len(members.Bots)+len(members.RestrictedBots))
   329  
   330  	invites := teamObj.GetActiveAndObsoleteInvites()
   331  	require.Equal(t, 1, len(invites))
   332  	for _, invite := range invites {
   333  		uv, err := invite.KeybaseUserVersion()
   334  		require.NoError(t, err)
   335  		require.Equal(t, tt.users[6].userVersion(), uv)
   336  	}
   337  }
   338  
   339  func TestTeamTxSubteamAdmins(t *testing.T) {
   340  	// Test if AddMemberTx properly keys implicit admins to teams
   341  	// through the use of 'implicit_team_keys'.
   342  
   343  	tt := newTeamTester(t)
   344  	defer tt.cleanup()
   345  
   346  	ann := tt.addUser("ann")
   347  	t.Logf("Signed up user ann (%s)", ann.username)
   348  
   349  	bob := tt.addUser("bob")
   350  	t.Logf("Signed up user bob (%s)", bob.username)
   351  
   352  	team := ann.createTeam()
   353  	t.Logf("Team created (%s)", team)
   354  
   355  	teamName, err := keybase1.TeamNameFromString(team)
   356  	require.NoError(t, err)
   357  	_, err = teams.CreateSubteam(context.Background(), ann.tc.G, "golfers", teamName, keybase1.TeamRole_NONE /* addSelfAs */)
   358  	require.NoError(t, err)
   359  	_, err = teams.CreateSubteam(context.Background(), ann.tc.G, "pokerpals", teamName, keybase1.TeamRole_NONE /* addSelfAs */)
   360  	require.NoError(t, err)
   361  
   362  	teamObj := ann.loadTeam(team, true /* admin */)
   363  	tx := teams.CreateAddMemberTx(teamObj)
   364  	err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_ADMIN, nil)
   365  	require.NoError(t, err)
   366  	err = tx.Post(libkb.NewMetaContextForTest(*ann.tc))
   367  	require.NoError(t, err)
   368  }
   369  
   370  func TestTeamTxBadAdds(t *testing.T) {
   371  	tt := newTeamTester(t)
   372  	defer tt.cleanup()
   373  
   374  	ann := tt.addUser("ann")
   375  	t.Logf("Signed up user ann (%s)", ann.username)
   376  
   377  	bob := tt.addUser("bob")
   378  	t.Logf("Signed up user bob (%s)", bob.username)
   379  
   380  	bobUV := bob.userVersion()
   381  	bob.reset()
   382  
   383  	team := ann.createTeam()
   384  	t.Logf("Team created (%s)", team)
   385  
   386  	teamObj := ann.loadTeam(team, true /* admin */)
   387  	tx := teams.CreateAddMemberTx(teamObj)
   388  
   389  	// Tring to add bob using old UV (from before reset)
   390  	err := tx.AddMemberByUV(context.Background(), bobUV, keybase1.TeamRole_WRITER, nil)
   391  	require.Error(t, err)
   392  	require.True(t, tx.IsEmpty())
   393  
   394  	bob.loginAfterReset()
   395  	bobUV = bob.userVersion()
   396  
   397  	bob.delete()
   398  
   399  	// Trying to add deleted bob.
   400  	err = tx.AddMemberByUV(context.Background(), bobUV, keybase1.TeamRole_WRITER, nil)
   401  	require.Error(t, err)
   402  	require.IsType(t, libkb.UserDeletedError{}, err)
   403  	require.True(t, tx.IsEmpty())
   404  }