github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/transactions_test.go (about)

     1  // Copyright 2018 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package teams
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/emails"
    12  	"github.com/keybase/client/go/kbtest"
    13  
    14  	"github.com/keybase/client/go/externalstest"
    15  	"github.com/keybase/client/go/libkb"
    16  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestTransactions1(t *testing.T) {
    21  	tc, owner, other, _, name := memberSetupMultiple(t)
    22  	defer tc.Cleanup()
    23  
    24  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
    25  		Name:      name,
    26  		NeedAdmin: true,
    27  	})
    28  	require.NoError(t, err)
    29  
    30  	tx := CreateAddMemberTx(team)
    31  	tx.AllowPUKless = true
    32  	err = tx.AddMemberByUsername(context.Background(), "t_alice", keybase1.TeamRole_WRITER, nil)
    33  	require.NoError(t, err)
    34  	require.Equal(t, 1, len(tx.payloads))
    35  	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
    36  	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
    37  
    38  	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil)
    39  	require.NoError(t, err)
    40  	require.Equal(t, 2, len(tx.payloads))
    41  	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
    42  	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
    43  	require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag)
    44  	require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val)
    45  
    46  	err = tx.AddMemberByUsername(context.Background(), "t_tracy", keybase1.TeamRole_ADMIN, nil)
    47  	require.NoError(t, err)
    48  
    49  	// 3rd add (pukless member) should re-use first signature instead
    50  	// of creating new one.
    51  	require.Equal(t, 2, len(tx.payloads))
    52  	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
    53  	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
    54  	require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag)
    55  	require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val)
    56  
    57  	err = tx.Post(libkb.NewMetaContextForTest(tc))
    58  	require.NoError(t, err)
    59  
    60  	team, err = Load(context.Background(), tc.G, keybase1.LoadTeamArg{
    61  		Name:        name,
    62  		NeedAdmin:   true,
    63  		ForceRepoll: true,
    64  	})
    65  	require.NoError(t, err)
    66  
    67  	members, err := team.Members()
    68  	require.NoError(t, err)
    69  	require.Equal(t, 1, len(members.Owners))
    70  	require.Equal(t, owner.GetUserVersion(), members.Owners[0])
    71  	require.Equal(t, 0, len(members.Admins))
    72  	require.Equal(t, 1, len(members.Writers))
    73  	require.Equal(t, other.GetUserVersion(), members.Writers[0])
    74  	require.Equal(t, 0, len(members.Readers))
    75  	require.Equal(t, 0, len(members.Bots))
    76  	require.Equal(t, 0, len(members.RestrictedBots))
    77  
    78  	invites := team.GetActiveAndObsoleteInvites()
    79  	require.Equal(t, 2, len(invites))
    80  }
    81  
    82  func TestTransactionRotateKey(t *testing.T) {
    83  	tc, _, otherA, otherB, name := memberSetupMultiple(t)
    84  	defer tc.Cleanup()
    85  
    86  	loadTeam := func() *Team {
    87  		team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
    88  			Name:        name,
    89  			NeedAdmin:   true,
    90  			ForceRepoll: true,
    91  		})
    92  		require.NoError(t, err)
    93  		return team
    94  	}
    95  
    96  	team := loadTeam()
    97  	err := team.ChangeMembership(context.Background(), keybase1.TeamChangeReq{
    98  		Writers: []keybase1.UserVersion{otherA.GetUserVersion()},
    99  	})
   100  	require.NoError(t, err)
   101  
   102  	team = loadTeam()
   103  	require.EqualValues(t, 1, team.Generation())
   104  
   105  	tx := CreateAddMemberTx(team)
   106  	// Create payloads manually so user add and user del happen in
   107  	// separate links.
   108  	tx.payloads = []txPayload{
   109  		{
   110  			Tag: txPayloadTagCryptomembers,
   111  			Val: &keybase1.TeamChangeReq{
   112  				Writers: []keybase1.UserVersion{otherB.GetUserVersion()},
   113  			},
   114  		},
   115  		{
   116  			Tag: txPayloadTagCryptomembers,
   117  			Val: &keybase1.TeamChangeReq{
   118  				None: []keybase1.UserVersion{otherA.GetUserVersion()},
   119  			},
   120  		},
   121  	}
   122  	err = tx.Post(libkb.NewMetaContextForTest(tc))
   123  	require.NoError(t, err)
   124  
   125  	// Also if the transaction didn't create new PerTeamKey, bunch of
   126  	// assertions would have failed on the server. It doesn't matter
   127  	// which link the PerTeamKey is attached to, because key coverage
   128  	// is checked for the entire transaction, not individual links,
   129  	// but we always attach it to the first ChangeMembership link with
   130  	// member removals.
   131  	team = loadTeam()
   132  	require.EqualValues(t, 2, team.Generation())
   133  }
   134  
   135  func TestPreprocessAssertions(t *testing.T) {
   136  	tc := externalstest.SetupTest(t, "assertions", 0)
   137  	defer tc.Cleanup()
   138  
   139  	tests := []struct {
   140  		s             string
   141  		isServerTrust bool
   142  		hasSingle     bool
   143  		isError       bool
   144  	}{
   145  		{"bob", false, true, false},
   146  		{"bob+bob@twitter", false, false, false},
   147  		{"[bob@gmail.com]@email", true, true, false},
   148  		{"[bob@gmail.com]@email+bob", false, false, true},
   149  		{"18005558638@phone", true, true, false},
   150  		{"18005558638@phone+alice", false, false, true},
   151  		{"18005558638@phone+[bob@gmail.com]@email", false, false, true},
   152  	}
   153  	for _, test := range tests {
   154  		t.Logf("Testing: %s", test.s)
   155  		isServerTrust, single, full, err := preprocessAssertion(libkb.NewMetaContextForTest(tc), test.s)
   156  		require.Equal(t, isServerTrust, test.isServerTrust)
   157  		require.Equal(t, (single != nil), test.hasSingle)
   158  		if test.isError {
   159  			require.Error(t, err)
   160  			require.Nil(t, full)
   161  		} else {
   162  			require.NoError(t, err)
   163  			require.NotNil(t, full)
   164  		}
   165  	}
   166  }
   167  
   168  func TestAllowPukless(t *testing.T) {
   169  	tc, _, other, teamname := setupPuklessInviteTest(t)
   170  	defer tc.Cleanup()
   171  
   172  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
   173  		Name:      teamname,
   174  		NeedAdmin: true,
   175  	})
   176  	require.NoError(t, err)
   177  
   178  	assertError := func(err error) {
   179  		require.Error(t, err)
   180  		require.IsType(t, err, UserPUKlessError{})
   181  		require.Contains(t, err.Error(), other.Username)
   182  		require.Contains(t, err.Error(), other.GetUserVersion().String())
   183  	}
   184  
   185  	tx := CreateAddMemberTx(team)
   186  	tx.AllowPUKless = false // explicitly disallow, but it's also the default.
   187  	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   188  	assertError(err)
   189  
   190  	err = tx.AddMemberByUV(context.Background(), other.GetUserVersion(), keybase1.TeamRole_WRITER, nil /* botSettings */)
   191  	assertError(err)
   192  
   193  	{
   194  		username, uv, invite, err := tx.AddOrInviteMemberByAssertion(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   195  		assertError(err)
   196  		// All this stuff is still returned despite an error
   197  		require.Equal(t, other.NormalizedUsername(), username)
   198  		require.Equal(t, other.GetUserVersion(), uv)
   199  		// But we aren't actually "inviting" them because of transaction setting.
   200  		require.False(t, invite)
   201  	}
   202  
   203  	{
   204  		candidate, err := tx.ResolveUPKV2FromAssertion(tc.MetaContext(), other.Username)
   205  		require.NoError(t, err)
   206  		username, uv, invite, err := tx.AddOrInviteMemberCandidate(context.Background(), candidate, keybase1.TeamRole_WRITER, nil /* botSettings */)
   207  		assertError(err)
   208  		// All this stuff is still returned despite an error
   209  		require.Equal(t, other.NormalizedUsername(), username)
   210  		require.Equal(t, other.GetUserVersion(), uv)
   211  		// But we aren't actually "inviting" them because of transaction setting.
   212  		require.False(t, invite)
   213  	}
   214  }
   215  
   216  func TestPostAllowPUKless(t *testing.T) {
   217  	tc, _, other, teamname := setupPuklessInviteTest(t)
   218  	defer tc.Cleanup()
   219  
   220  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
   221  		Name:      teamname,
   222  		NeedAdmin: true,
   223  	})
   224  	require.NoError(t, err)
   225  
   226  	tx := CreateAddMemberTx(team)
   227  	tx.AllowPUKless = true
   228  	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   229  	require.NoError(t, err)
   230  
   231  	// Disallow PUKless after we have already added a PUKless user.
   232  	tx.AllowPUKless = false
   233  	err = tx.Post(tc.MetaContext())
   234  	require.Error(t, err)
   235  	// Make sure it's the error about AllowPUKless.
   236  	require.Contains(t, err.Error(), "AllowPUKless")
   237  }
   238  
   239  func TestTransactionRoleChanges(t *testing.T) {
   240  	tc := SetupTest(t, "team", 1)
   241  	defer tc.Cleanup()
   242  
   243  	tc.Tp.SkipSendingSystemChatMessages = true
   244  
   245  	user := kbtest.TCreateFakeUser(tc)
   246  	kbtest.TCreateFakeUser(tc) // owner
   247  
   248  	_, teamID := createTeam2(tc)
   249  
   250  	res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_READER,
   251  		nil /* botSettings */, nil /* emailInviteMsg */)
   252  	require.NoError(t, err)
   253  	require.False(t, res.Invited)
   254  
   255  	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   256  	require.NoError(t, err)
   257  
   258  	tx := CreateAddMemberTx(team)
   259  	// Try to upgrade role without `AllowRoleChanges` first.
   260  	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   261  	require.Error(t, err)
   262  	require.IsType(t, libkb.ExistsError{}, err)
   263  
   264  	require.Len(t, tx.payloads, 0) // should not have changed transaction
   265  	require.NoError(t, tx.err)     // should not be a permanent error
   266  
   267  	// Set `AllowRoleChanges`.
   268  	tx.AllowRoleChanges = true
   269  
   270  	// Trying to add with same role as current is still an error.
   271  	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */)
   272  	require.Error(t, err)
   273  	require.IsType(t, libkb.ExistsError{}, err)
   274  
   275  	// We can set a different role though (READER -> WRITER)
   276  	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   277  	require.NoError(t, err)
   278  
   279  	err = tx.Post(tc.MetaContext())
   280  	require.NoError(t, err)
   281  
   282  	// See if role change worked
   283  	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   284  	require.NoError(t, err)
   285  	role, err := team.MemberRole(tc.Context(), user.GetUserVersion())
   286  	require.NoError(t, err)
   287  	require.Equal(t, keybase1.TeamRole_WRITER, role)
   288  
   289  	// Should be able to go the other way as well (WRITER -> READER).
   290  	tx = CreateAddMemberTx(team)
   291  	tx.AllowRoleChanges = true
   292  	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */)
   293  	require.NoError(t, err)
   294  
   295  	err = tx.Post(tc.MetaContext())
   296  	require.NoError(t, err)
   297  
   298  	// See if it worked.
   299  	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   300  	require.NoError(t, err)
   301  	role, err = team.MemberRole(tc.Context(), user.GetUserVersion())
   302  	require.NoError(t, err)
   303  	require.Equal(t, keybase1.TeamRole_READER, role)
   304  
   305  	userLog := team.chain().inner.UserLog[user.GetUserVersion()]
   306  	require.Len(t, userLog, 3)
   307  }
   308  
   309  func TestTransactionEmailExists(t *testing.T) {
   310  	tc := SetupTest(t, "team", 1)
   311  	defer tc.Cleanup()
   312  
   313  	kbtest.TCreateFakeUser(tc)
   314  	_, teamID := createTeam2(tc)
   315  
   316  	randomEmail := kbtest.GenerateRandomEmailAddress()
   317  	err := InviteEmailPhoneMember(tc.Context(), tc.G, teamID, randomEmail.String(), "email", keybase1.TeamRole_WRITER)
   318  	require.NoError(t, err)
   319  
   320  	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   321  	require.NoError(t, err)
   322  
   323  	invite, err := team.chain().FindActiveInviteString(tc.MetaContext(), randomEmail.String(), "email")
   324  	require.NoError(t, err)
   325  	require.Equal(t, keybase1.TeamRole_WRITER, invite.Role)
   326  
   327  	tx := CreateAddMemberTx(team)
   328  	tx.AllowRoleChanges = true
   329  
   330  	assertion := fmt.Sprintf("[%s]@email", randomEmail)
   331  
   332  	// Check if we can catch this error and continue forward
   333  	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
   334  	require.Error(t, err)
   335  	require.IsType(t, libkb.ExistsError{}, err)
   336  
   337  	// Changing roles of an invite using AddMemberTx is not possible right now.
   338  	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */)
   339  	require.Error(t, err)
   340  	require.IsType(t, libkb.ExistsError{}, err)
   341  
   342  	// Two errors above should not have tainted the transaction.
   343  	require.Len(t, tx.payloads, 0)
   344  	require.NoError(t, tx.err)
   345  }
   346  
   347  func TestTransactionResolvableEmailExists(t *testing.T) {
   348  	// Similar test but with resolvable email.
   349  	tc := SetupTest(t, "team", 1)
   350  	defer tc.Cleanup()
   351  
   352  	user := kbtest.TCreateFakeUser(tc)
   353  
   354  	// Add and verify email address.
   355  	usersEmail := kbtest.GenerateRandomEmailAddress()
   356  	err := emails.AddEmail(tc.MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC)
   357  	require.NoError(t, err)
   358  	err = kbtest.VerifyEmailAuto(tc.MetaContext(), usersEmail)
   359  	require.NoError(t, err)
   360  
   361  	kbtest.TCreateFakeUser(tc) // owner
   362  
   363  	_, teamID := createTeam2(tc)
   364  	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   365  	require.NoError(t, err)
   366  
   367  	assertion := fmt.Sprintf("[%s]@email", usersEmail)
   368  
   369  	// Invite email for the first time, should resolve and add user.
   370  	tx := CreateAddMemberTx(team)
   371  
   372  	username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
   373  	require.NoError(t, err)
   374  	require.Equal(t, user.NormalizedUsername(), username)
   375  	require.Equal(t, user.GetUserVersion(), uv)
   376  	require.False(t, invited)
   377  
   378  	err = tx.Post(tc.MetaContext())
   379  	require.NoError(t, err)
   380  
   381  	// Ensure they were added as member (team.MemberRole).
   382  	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   383  	require.NoError(t, err)
   384  	role, err := team.MemberRole(tc.Context(), user.GetUserVersion())
   385  	require.NoError(t, err)
   386  	require.Equal(t, keybase1.TeamRole_WRITER, role)
   387  	// And that e-mail wasn't added as invite.
   388  	hasInvite, err := team.HasActiveInvite(tc.MetaContext(), keybase1.TeamInviteName(usersEmail), "email")
   389  	require.NoError(t, err)
   390  	require.False(t, hasInvite)
   391  
   392  	// Try again, should fail.
   393  	tx = CreateAddMemberTx(team)
   394  	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
   395  	require.Error(t, err)
   396  	require.IsType(t, libkb.ExistsError{}, err)
   397  
   398  	require.Len(t, tx.payloads, 0)
   399  	require.NoError(t, tx.err)
   400  
   401  	// Role changes are possible with `AllowRoleChanges` because they are
   402  	// crypto-member.
   403  	tx = CreateAddMemberTx(team)
   404  	tx.AllowRoleChanges = true
   405  	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */)
   406  	require.NoError(t, err)
   407  
   408  	err = tx.Post(tc.MetaContext())
   409  	require.NoError(t, err)
   410  
   411  	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   412  	require.NoError(t, err)
   413  	role, err = team.MemberRole(tc.Context(), user.GetUserVersion())
   414  	require.NoError(t, err)
   415  	require.Equal(t, keybase1.TeamRole_READER, role)
   416  }
   417  
   418  func TestTransactionAddEmailPukless(t *testing.T) {
   419  	// Add e-mail that resolves to a PUK-less user.
   420  
   421  	fus, tcs, cleanup := setupNTestsWithPukless(t, 2, 1)
   422  	defer cleanup()
   423  
   424  	usersEmail := kbtest.GenerateRandomEmailAddress()
   425  	err := emails.AddEmail(tcs[1].MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC)
   426  	require.NoError(t, err)
   427  	err = kbtest.VerifyEmailAuto(tcs[1].MetaContext(), usersEmail)
   428  	require.NoError(t, err)
   429  
   430  	_, teamID := createTeam2(*tcs[0])
   431  	team, err := GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */)
   432  	require.NoError(t, err)
   433  
   434  	assertion := fmt.Sprintf("[%s]@email", usersEmail)
   435  
   436  	tx := CreateAddMemberTx(team)
   437  	// Can't add without AllowPUKless.
   438  	_, _, _, err = tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
   439  	require.Error(t, err)
   440  	require.IsType(t, UserPUKlessError{}, err)
   441  
   442  	// Failure to add should have left the transaction unmodified.
   443  	require.Len(t, tx.payloads, 0)
   444  	require.NoError(t, tx.err)
   445  
   446  	tx.AllowPUKless = true
   447  	username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
   448  	require.NoError(t, err)
   449  	require.True(t, invited)
   450  	require.Equal(t, fus[1].NormalizedUsername(), username)
   451  	require.Equal(t, fus[1].GetUserVersion(), uv)
   452  
   453  	err = tx.Post(tcs[0].MetaContext())
   454  	require.NoError(t, err)
   455  
   456  	team, err = GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */)
   457  	require.NoError(t, err)
   458  	_, uv, found := team.FindActiveKeybaseInvite(fus[1].GetUID())
   459  	require.True(t, found)
   460  	require.Equal(t, fus[1].GetUserVersion(), uv)
   461  
   462  	found, err = team.HasActiveInvite(tcs[0].MetaContext(), keybase1.TeamInviteName(usersEmail), "email")
   463  	require.NoError(t, err)
   464  	require.False(t, found)
   465  }
   466  
   467  func TestTransactionDowngradeAdmin(t *testing.T) {
   468  	tc := SetupTest(t, "team", 1)
   469  	defer tc.Cleanup()
   470  
   471  	user := kbtest.TCreateFakeUser(tc)
   472  	kbtest.TCreateFakeUser(tc) // owner
   473  
   474  	_, teamID := createTeam2(tc)
   475  
   476  	// Add user as admin.
   477  	res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_ADMIN,
   478  		nil /* botSettings */, nil /* emailInviteMsg */)
   479  	require.NoError(t, err)
   480  	require.False(t, res.Invited)
   481  
   482  	// Load team, change role of user to writer (from admin).
   483  	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   484  	require.NoError(t, err)
   485  
   486  	memberRole, err := team.MemberRole(tc.Context(), user.GetUserVersion())
   487  	require.NoError(t, err)
   488  	require.Equal(t, keybase1.TeamRole_ADMIN, memberRole)
   489  
   490  	tx := CreateAddMemberTx(team)
   491  	tx.AllowRoleChanges = true
   492  	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
   493  	require.NoError(t, err)
   494  	require.Len(t, tx.payloads, 1)
   495  
   496  	err = tx.Post(tc.MetaContext())
   497  	require.NoError(t, err)
   498  
   499  	// See if it worked.
   500  	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
   501  	require.NoError(t, err)
   502  
   503  	memberRole, err = team.MemberRole(tc.Context(), user.GetUserVersion())
   504  	require.NoError(t, err)
   505  	require.Equal(t, keybase1.TeamRole_WRITER, memberRole)
   506  }