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

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbtest"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestObsoletingInvites1(t *testing.T) {
    19  	// This chain has 3 keybase invites total:
    20  	// 1) 579651b0d574971040b531b66efbc519%1
    21  	// 2) 618d663af0f1ec88a5a19defa65a2f19%1
    22  	// 3) 40903c59d19feef1d67c455499304c19%1
    23  	//
    24  	// 1 gets obsoleted by "change_membership" link that adds the same
    25  	// person but does not complete the invite. 2 is canceled by
    26  	// "invite" link. 3 should be still active when the chain is done
    27  	// replaying.
    28  	team, _ := runUnitFromFilename(t, "invite_obsolete.json")
    29  
    30  	mctx := team.MetaContext(context.Background())
    31  
    32  	require.Equal(t, 1, team.NumActiveInvites())
    33  
    34  	allInvites := team.GetActiveAndObsoleteInvites()
    35  	require.Equal(t, 2, len(allInvites))
    36  
    37  	hasInvite, err := team.HasActiveInvite(mctx, keybase1.TeamInviteName("579651b0d574971040b531b66efbc519%1"), "keybase")
    38  	require.NoError(t, err)
    39  	require.False(t, hasInvite)
    40  
    41  	hasInvite, err = team.HasActiveInvite(mctx, keybase1.TeamInviteName("618d663af0f1ec88a5a19defa65a2f19%1"), "keybase")
    42  	require.NoError(t, err)
    43  	require.False(t, hasInvite)
    44  
    45  	hasInvite, err = team.HasActiveInvite(mctx, keybase1.TeamInviteName("40903c59d19feef1d67c455499304c19%1"), "keybase")
    46  	require.NoError(t, err)
    47  	require.True(t, hasInvite)
    48  
    49  	// Invite
    50  	invite, ok := allInvites["56eafff3400b5bcd8b40bff3d225ab27"]
    51  	require.True(t, ok)
    52  	require.Equal(t, keybase1.TeamRole_READER, invite.Role)
    53  	require.EqualValues(t, "56eafff3400b5bcd8b40bff3d225ab27", invite.Id)
    54  	require.EqualValues(t, "40903c59d19feef1d67c455499304c19%1", invite.Name)
    55  	require.EqualValues(t, keybase1.UserVersion{Uid: "25852c87d6e47fb8d7d55400be9c7a19", EldestSeqno: 1}, invite.Inviter)
    56  
    57  	inviteMD := team.chain().inner.InviteMetadatas["54eafff3400b5bcd8b40bff3d225ab27"]
    58  	code, err := inviteMD.Status.Code()
    59  	require.NoError(t, err)
    60  	require.Equal(t, keybase1.TeamInviteMetadataStatusCode_OBSOLETE, code)
    61  
    62  	inviteMD = team.chain().inner.InviteMetadatas["55eafff3400b5bcd8b40bff3d225ab27"]
    63  	code, err = inviteMD.Status.Code()
    64  	require.NoError(t, err)
    65  	require.Equal(t, keybase1.TeamInviteMetadataStatusCode_CANCELLED, code)
    66  	require.Equal(t, keybase1.Seqno(5), inviteMD.Status.Cancelled().TeamSigMeta.SigMeta.SigChainLocation.Seqno)
    67  
    68  	members, err := team.Members()
    69  	require.NoError(t, err)
    70  	require.Equal(t, 1, len(members.Owners))
    71  	require.Equal(t, 0, len(members.Admins))
    72  	require.Equal(t, 1, len(members.Writers))
    73  	require.Equal(t, 0, len(members.Readers))
    74  	require.Equal(t, 0, len(members.Bots))
    75  	require.Equal(t, 0, len(members.RestrictedBots))
    76  }
    77  
    78  func TestObsoletingInvites2(t *testing.T) {
    79  	// This chain is a backwards-compatibility test to see if even if
    80  	// someone got tricked into accepting obsolete invite, such chain
    81  	// should still play and result in predictable end state.
    82  	team, _ := runUnitFromFilename(t, "invite_obsolete_trick.json")
    83  	require.Equal(t, 0, len(team.chain().ActiveInvites()))
    84  	require.True(t, team.IsMember(context.Background(), keybase1.UserVersion{Uid: "579651b0d574971040b531b66efbc519", EldestSeqno: 1}))
    85  }
    86  
    87  // Keybase invites (PUKless members) are removed similarly to
    88  // cryptomembers, by using RemoveMember(username) API. It's important
    89  // that the invite can even be removed after user has reset or deleted
    90  // their account.
    91  
    92  func setupPuklessInviteTest(t *testing.T) (tc libkb.TestContext, owner, other *kbtest.FakeUser, teamname string) {
    93  	tc = SetupTest(t, "team", 1)
    94  
    95  	tc.Tp.DisableUpgradePerUserKey = true
    96  	tc.Tp.SkipSendingSystemChatMessages = true
    97  	other, err := kbtest.CreateAndSignupFakeUser("team", tc.G)
    98  	require.NoError(t, err)
    99  	err = tc.Logout()
   100  	require.NoError(t, err)
   101  
   102  	tc.Tp.DisableUpgradePerUserKey = false
   103  	owner, err = kbtest.CreateAndSignupFakeUser("team", tc.G)
   104  	require.NoError(t, err)
   105  
   106  	teamname = createTeam(tc)
   107  
   108  	t.Logf("Signed up PUKless user %s", other.Username)
   109  	t.Logf("Signed up user %s", owner.Username)
   110  	t.Logf("Created team %s", teamname)
   111  
   112  	return tc, owner, other, teamname
   113  }
   114  
   115  func TestKeybaseInviteAfterReset(t *testing.T) {
   116  	tc, owner, other, teamname := setupPuklessInviteTest(t)
   117  	defer tc.Cleanup()
   118  
   119  	// Add member - should be added as keybase-type invite with name "uid%1".
   120  	res, err := AddMember(context.Background(), tc.G, teamname, other.Username, keybase1.TeamRole_READER, nil)
   121  	require.NoError(t, err)
   122  	require.True(t, res.Invited)
   123  
   124  	// Reset account, should now have EldestSeqno=0
   125  	err = tc.Logout()
   126  	require.NoError(t, err)
   127  	require.NoError(t, other.Login(tc.G))
   128  	kbtest.ResetAccount(tc, other)
   129  
   130  	// Try to remove member
   131  	require.NoError(t, owner.Login(tc.G))
   132  	err = RemoveMember(context.Background(), tc.G, teamname, other.Username)
   133  	require.NoError(t, err)
   134  
   135  	// Expecting all invites to be gone.
   136  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{Name: teamname})
   137  	require.NoError(t, err)
   138  	require.Len(t, team.GetActiveAndObsoleteInvites(), 0)
   139  }
   140  
   141  func TestKeybaseInviteMalformed(t *testing.T) {
   142  	tc, owner, other, teamname := setupPuklessInviteTest(t)
   143  	defer tc.Cleanup()
   144  
   145  	// Pretend it's an old client.
   146  	invite := SCTeamInvite{
   147  		Type: "keybase",
   148  		// Use name that is not "uid%seqno" but just "uid" instead.
   149  		Name: keybase1.TeamInviteName(other.User.GetUID()),
   150  		ID:   NewInviteID(),
   151  	}
   152  	invites := []SCTeamInvite{invite}
   153  	payload := SCTeamInvites{
   154  		Readers: &invites,
   155  	}
   156  	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{Name: teamname})
   157  	require.NoError(t, err)
   158  	err = team.postTeamInvites(context.Background(), payload)
   159  	require.NoError(t, err)
   160  
   161  	// Try to remove member
   162  	require.NoError(t, owner.Login(tc.G))
   163  	err = RemoveMember(context.Background(), tc.G, teamname, other.Username)
   164  	require.NoError(t, err)
   165  
   166  	// Expecting all invites to be gone.
   167  	team, err = Load(context.Background(), tc.G, keybase1.LoadTeamArg{Name: teamname})
   168  	require.NoError(t, err)
   169  	require.Len(t, team.GetActiveAndObsoleteInvites(), 0)
   170  }
   171  
   172  func TestMultiUseInviteChains1(t *testing.T) {
   173  	team, _ := runUnitFromFilename(t, "multiple_use_invite.json")
   174  
   175  	state := &team.chain().inner
   176  	require.Len(t, state.InviteMetadatas, 1)
   177  
   178  	var inviteID keybase1.TeamInviteID
   179  	var inviteMD keybase1.TeamInviteMetadata
   180  	for _, inviteMD = range state.InviteMetadatas {
   181  		inviteID = inviteMD.Invite.Id
   182  		break // grab first invite
   183  	}
   184  
   185  	code, err := inviteMD.Status.Code()
   186  	require.NoError(t, err)
   187  	require.Equal(t, keybase1.TeamInviteMetadataStatusCode_ACTIVE, code)
   188  	require.Equal(t,
   189  		keybase1.UserVersion{Uid: "25852c87d6e47fb8d7d55400be9c7a19", EldestSeqno: 1},
   190  		inviteMD.TeamSigMeta.Uv,
   191  	)
   192  	require.Equal(t, keybase1.Seqno(2), inviteMD.TeamSigMeta.SigMeta.SigChainLocation.Seqno)
   193  
   194  	invite := inviteMD.Invite
   195  
   196  	require.Equal(t, inviteID, invite.Id)
   197  	require.Nil(t, invite.Etime)
   198  	require.NotNil(t, invite.MaxUses)
   199  	require.Equal(t, keybase1.TeamInviteMaxUses(10), *invite.MaxUses)
   200  
   201  	require.Len(t, inviteMD.UsedInvites, 3)
   202  
   203  	for _, usedInvitePair := range inviteMD.UsedInvites {
   204  		// Check if UserLog pointed at by usedInvitePair exists (otherwise
   205  		// crash on map/list access).
   206  		ulog := state.UserLog[usedInvitePair.Uv][usedInvitePair.LogPoint]
   207  		require.Equal(t, ulog.Role, invite.Role)
   208  	}
   209  }
   210  
   211  func TestMultiUseInviteChains2(t *testing.T) {
   212  	team, _ := runUnitFromFilename(t, "multiple_use_invite_3.json")
   213  
   214  	state := &team.chain().inner
   215  	require.Len(t, state.ActiveInvites(), 1)
   216  
   217  	var inviteID keybase1.TeamInviteID
   218  	var invite keybase1.TeamInvite
   219  	for _, invite = range state.ActiveInvites() {
   220  		inviteID = invite.Id
   221  		break // grab first invite
   222  	}
   223  
   224  	require.Equal(t, inviteID, invite.Id)
   225  	require.Nil(t, invite.Etime)
   226  	require.NotNil(t, invite.MaxUses)
   227  	require.Equal(t, keybase1.TeamInviteMaxUses(999), *invite.MaxUses)
   228  
   229  	usedInvitesForID := state.InviteMetadatas[inviteID].UsedInvites
   230  	require.Len(t, usedInvitesForID, 3)
   231  
   232  	require.Equal(t, keybase1.UserVersion{
   233  		Uid:         "579651b0d574971040b531b66efbc519",
   234  		EldestSeqno: keybase1.Seqno(1),
   235  	}, usedInvitesForID[0].Uv)
   236  	require.Equal(t, 0, usedInvitesForID[0].LogPoint)
   237  
   238  	require.Equal(t, keybase1.UserVersion{
   239  		Uid:         "40903c59d19feef1d67c455499304c19",
   240  		EldestSeqno: keybase1.Seqno(1),
   241  	}, usedInvitesForID[1].Uv)
   242  	require.Equal(t, 0, usedInvitesForID[1].LogPoint)
   243  
   244  	require.Equal(t, keybase1.UserVersion{
   245  		Uid:         "579651b0d574971040b531b66efbc519",
   246  		EldestSeqno: keybase1.Seqno(1),
   247  	}, usedInvitesForID[2].Uv)
   248  	// Logpoint 0 is when they first join, logpoint 1 is when they leave, and
   249  	// logpoint 2 is the second join.
   250  	require.Equal(t, 2, usedInvitesForID[2].LogPoint)
   251  
   252  	for _, usedInvitePair := range usedInvitesForID {
   253  		// Check if UserLog pointed at by usedInvitePair exists (otherwise
   254  		// crash on map/list access).
   255  		ulog := state.UserLog[usedInvitePair.Uv][usedInvitePair.LogPoint]
   256  		require.Equal(t, ulog.Role, invite.Role)
   257  	}
   258  
   259  	members, err := team.Members()
   260  	require.NoError(t, err)
   261  	require.Len(t, members.AllUIDs(), 3)
   262  }
   263  
   264  func TestTeamInviteMaxUsesUnit(t *testing.T) {
   265  	// -1 is valid and any positive number is valid.
   266  	good := []int{-1, 1, 100, 999, 9999, math.MaxInt64}
   267  	bad := []int{0, -2, -1000, math.MinInt64, math.MinInt32}
   268  
   269  	for _, v := range good {
   270  		m := keybase1.TeamInviteMaxUses(v)
   271  		mp := &m
   272  		require.True(t, mp.IsNotNilAndValid())
   273  	}
   274  
   275  	for _, v := range bad {
   276  		m := keybase1.TeamInviteMaxUses(v)
   277  		mp := &m
   278  		require.False(t, mp.IsNotNilAndValid())
   279  	}
   280  
   281  	// -1 is a special value that means infinite uses.
   282  	mkInvite := func(n int) keybase1.TeamInvite {
   283  		m := keybase1.TeamInviteMaxUses(n)
   284  		return keybase1.TeamInvite{
   285  			MaxUses: &m,
   286  		}
   287  	}
   288  
   289  	require.False(t, mkInvite(1).IsInfiniteUses())
   290  	require.False(t, mkInvite(2).IsInfiniteUses())
   291  	require.False(t, mkInvite(100).IsInfiniteUses())
   292  
   293  	require.True(t, mkInvite(-1).IsInfiniteUses())
   294  }
   295  
   296  func makeTestSCForInviteLink() SCTeamInvite {
   297  	return SCTeamInvite{
   298  		Type: "invitelink",
   299  		Name: keybase1.TeamInviteName("test"),
   300  		ID:   NewInviteID(),
   301  	}
   302  }
   303  
   304  func makeTestTeamSectionWithInviteLink(team *Team, role keybase1.TeamRole, maxUses *keybase1.TeamInviteMaxUses,
   305  	etime *keybase1.UnixTime) (SCTeamSection, keybase1.TeamInviteID) {
   306  
   307  	teamSectionForInvite := makeTestSCTeamSection(team)
   308  	sectionInvite := makeTestSCForInviteLink()
   309  	sectionInvite.MaxUses = maxUses
   310  	sectionInvite.Etime = etime
   311  
   312  	scTeamInvites := SCTeamInvites{}
   313  	switch role {
   314  	case keybase1.TeamRole_READER:
   315  		scTeamInvites.Readers = &[]SCTeamInvite{sectionInvite}
   316  	case keybase1.TeamRole_WRITER:
   317  		scTeamInvites.Writers = &[]SCTeamInvite{sectionInvite}
   318  	default:
   319  		panic(fmt.Errorf("invalid role for test invite link %v", role))
   320  	}
   321  
   322  	teamSectionForInvite.Invites = &scTeamInvites
   323  
   324  	inviteID := keybase1.TeamInviteID(sectionInvite.ID)
   325  	return teamSectionForInvite, inviteID
   326  }
   327  
   328  func TestTeamPlayerInviteMaxUses(t *testing.T) {
   329  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   330  	defer tc.Cleanup()
   331  
   332  	section := makeTestSCTeamSection(team)
   333  	invite := makeTestSCForInviteLink()
   334  	inviteID := invite.ID
   335  
   336  	badMaxUses := []int{0, -2, -1000}
   337  	for _, v := range badMaxUses {
   338  		maxUses := keybase1.TeamInviteMaxUses(v)
   339  		invite.MaxUses = &maxUses
   340  		section.Invites = &SCTeamInvites{
   341  			Readers: &[]SCTeamInvite{invite},
   342  		}
   343  
   344  		_, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   345  			section, me, nil /* merkleRoot */)
   346  		requirePrecheckError(t, err)
   347  		require.Contains(t, err.Error(), fmt.Sprintf("invalid max_uses %d", v))
   348  		require.Contains(t, err.Error(), inviteID)
   349  	}
   350  
   351  	goodMaxUses := []int{-1, 1, 100, 9999}
   352  	for _, v := range goodMaxUses {
   353  		maxUses := keybase1.TeamInviteMaxUses(v)
   354  		invite.MaxUses = &maxUses
   355  		section.Invites = &SCTeamInvites{
   356  			Readers: &[]SCTeamInvite{invite},
   357  		}
   358  
   359  		state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   360  			section, me, nil /* merkleRoot */)
   361  		require.NoError(t, err)
   362  		require.Len(t, state.ActiveInvites(), 1)
   363  		_, found := state.FindActiveInviteMDByID(keybase1.TeamInviteID(inviteID))
   364  		require.True(t, found)
   365  	}
   366  }
   367  
   368  var singleUse = keybase1.TeamInviteMaxUses(1)
   369  
   370  func TestTeamPlayerEtime(t *testing.T) {
   371  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   372  	defer tc.Cleanup()
   373  
   374  	section := makeTestSCTeamSection(team)
   375  	invite := makeTestSCForInviteLink()
   376  	inviteID := invite.ID
   377  
   378  	badEtime := []keybase1.UnixTime{0, -100}
   379  	for _, v := range badEtime {
   380  		invite.Etime = &v
   381  		invite.MaxUses = &singleUse
   382  		section.Invites = &SCTeamInvites{
   383  			Readers: &[]SCTeamInvite{invite},
   384  		}
   385  
   386  		_, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   387  			section, me, nil /* merkleRoot */)
   388  		requirePrecheckError(t, err)
   389  		require.Contains(t, err.Error(), fmt.Sprintf("invalid etime %d", v))
   390  		require.Contains(t, err.Error(), inviteID)
   391  	}
   392  
   393  	// Try a valid etime
   394  	etime := keybase1.ToUnixTime(time.Now())
   395  	invite.Etime = &etime
   396  	invite.MaxUses = &singleUse
   397  	section.Invites = &SCTeamInvites{
   398  		Readers: &[]SCTeamInvite{invite},
   399  	}
   400  
   401  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   402  		section, me, nil /* merkleRoot */)
   403  	require.NoError(t, err)
   404  	require.Len(t, state.ActiveInvites(), 1)
   405  	_, found := state.FindActiveInviteMDByID(keybase1.TeamInviteID(inviteID))
   406  	require.True(t, found)
   407  
   408  	// Can use Etime without MaxUses?
   409  	// This is allowed in the sigchain player but not the server. See
   410  	// `TestTeamPlayerBadUsedInvites` for another invites like that.
   411  	invite.Etime = &etime
   412  	invite.MaxUses = nil
   413  	section.Invites = &SCTeamInvites{
   414  		Readers: &[]SCTeamInvite{invite},
   415  	}
   416  	_, err = appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   417  		section, me, nil /* merkleRoot */)
   418  	require.NoError(t, err)
   419  }
   420  
   421  func TestTeamPlayerInviteLinksImplicitTeam(t *testing.T) {
   422  	tc, team, me := setupTestForPrechecks(t, true /* implicitTeam */)
   423  	defer tc.Cleanup()
   424  
   425  	section := makeTestSCTeamSection(team)
   426  	invite := makeTestSCForInviteLink()
   427  	maxUses := keybase1.TeamInviteMaxUses(100)
   428  	invite.MaxUses = &maxUses
   429  	inviteID := invite.ID
   430  	section.Invites = &SCTeamInvites{
   431  		Readers: &[]SCTeamInvite{invite},
   432  	}
   433  
   434  	_, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   435  		section, me, nil /* merkleRoot */)
   436  	requirePrecheckError(t, err)
   437  	require.Contains(t, err.Error(), "new-style in implicit team")
   438  	require.Contains(t, err.Error(), inviteID)
   439  }
   440  
   441  func TestTeamPlayerNoInvitelinksForAdmins(t *testing.T) {
   442  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   443  	defer tc.Cleanup()
   444  
   445  	section := makeTestSCTeamSection(team)
   446  	invite := makeTestSCForInviteLink()
   447  	maxUses := keybase1.TeamInviteMaxUses(100)
   448  	invite.MaxUses = &maxUses
   449  	inviteID := invite.ID
   450  	section.Invites = &SCTeamInvites{
   451  		Admins: &[]SCTeamInvite{invite},
   452  	}
   453  	_, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   454  		section, me, nil /* merkleRoot */)
   455  	requirePrecheckError(t, err)
   456  
   457  	var ie InviteError
   458  	require.True(t, errors.As(err, &ie))
   459  	require.Equal(t, inviteID, SCTeamInviteID(ie.id))
   460  
   461  	var ile InvitelinkBadRoleError
   462  	require.True(t, errors.As(err, &ile))
   463  	require.Equal(t, keybase1.TeamRole_ADMIN, ile.role)
   464  }
   465  
   466  func TestTeamPlayerInviteLinkBadAdds(t *testing.T) {
   467  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   468  	defer tc.Cleanup()
   469  
   470  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   471  	// testUV2 is same UID as testUV but different eldest_seqno.
   472  	testUV2 := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 5}
   473  	testUV3 := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_doug_t"), EldestSeqno: 1}
   474  	testUV4 := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_bob_t"), EldestSeqno: 1}
   475  
   476  	maxUses := keybase1.TeamInviteMaxUses(100)
   477  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER,
   478  		&maxUses, nil /* etime */)
   479  
   480  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   481  		teamSectionForInvite, me, nil /* merkleRoot */)
   482  	require.NoError(t, err)
   483  	_, found := state.FindActiveInviteMDByID(inviteID)
   484  	require.True(t, found)
   485  
   486  	{
   487  		// Trying to add the members as role=writer and "use invite", but the
   488  		// invite was for role=reader.
   489  		teamSectionCM := makeTestSCTeamSection(team)
   490  		teamSectionCM.Members = &SCTeamMembers{
   491  			Writers: &[]SCTeamMember{SCTeamMember(testUV)},
   492  		}
   493  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   494  			{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   495  		}
   496  		_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   497  			teamSectionCM, me, nil /* merkleRoot */)
   498  		requirePrecheckError(t, err)
   499  		require.Contains(t, err.Error(), fmt.Sprintf("%s that was not added as role reader", testUV.String()))
   500  	}
   501  
   502  	{
   503  		// Trying to append change_membership with used_invites for UV that's not
   504  		// being added in the link.
   505  		teamSectionCM := makeTestSCTeamSection(team)
   506  		for _, badUV := range []keybase1.UserVersion{testUV2, testUV3} {
   507  			// Member with correct role this time.
   508  			teamSectionCM.Members = &SCTeamMembers{
   509  				Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   510  			}
   511  			// But used_invites uv doesn't match.
   512  			teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   513  				{InviteID: SCTeamInviteID(inviteID), UV: badUV.PercentForm()},
   514  			}
   515  			_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   516  				teamSectionCM, me, nil /* merkleRoot */)
   517  			requirePrecheckError(t, err)
   518  			require.Contains(t, err.Error(), fmt.Sprintf("%s that was not added as role reader", badUV.String()))
   519  		}
   520  	}
   521  
   522  	{
   523  		// used_invites in link that does not add any members at all.
   524  		teamSectionCM := makeTestSCTeamSection(team)
   525  		teamSectionCM.Members = &SCTeamMembers{}
   526  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   527  			{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   528  		}
   529  		_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   530  			teamSectionCM, me, nil /* merkleRoot */)
   531  		requirePrecheckError(t, err)
   532  		require.Contains(t, err.Error(), fmt.Sprintf("%s that was not added as role reader", testUV.String()))
   533  	}
   534  
   535  	{
   536  		// One of the UVs doesn't match, the other one does.
   537  		teamSectionCM := makeTestSCTeamSection(team)
   538  		teamSectionCM.Members = &SCTeamMembers{
   539  			Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   540  			Writers: &[]SCTeamMember{SCTeamMember(testUV3)},
   541  		}
   542  		// But used_invites uv doesn't match.
   543  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   544  			{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   545  			{InviteID: SCTeamInviteID(inviteID), UV: testUV4.PercentForm()},
   546  		}
   547  		_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   548  			teamSectionCM, me, nil /* merkleRoot */)
   549  		requirePrecheckError(t, err)
   550  		require.Contains(t, err.Error(), fmt.Sprintf("%s that was not added as role reader", testUV4.String()))
   551  	}
   552  }
   553  
   554  func TestTeamPlayerBadUsedInvites(t *testing.T) {
   555  	// Test used_invites for invites that are not compatible. For used_invites
   556  	// entry to be valid, the invite should define `max_uses`. Any other
   557  	// invite, including invites with `etime`, is incompatible with
   558  	// `used_invites`, and `completed_invites` should be used instead.
   559  
   560  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   561  	defer tc.Cleanup()
   562  
   563  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   564  
   565  	etime := keybase1.ToUnixTime(time.Now())
   566  	testInvites := []SCTeamInvite{
   567  		// Seitan invite link invite without `max_uses` or `etime`. (not
   568  		// possible on the real server)
   569  		makeTestSCForInviteLink(),
   570  		// Rooter invite, also no `max_uses` or `etime`.
   571  		{
   572  			Type: "rooter",
   573  			Name: keybase1.TeamInviteName("alice"),
   574  			ID:   NewInviteID(),
   575  		},
   576  		// Rooter invite with `etime` - not allowed by the server right now,
   577  		// but allowed by sigchain player.
   578  		{
   579  			Type:  "rooter",
   580  			Name:  keybase1.TeamInviteName("alice"),
   581  			ID:    NewInviteID(),
   582  			Etime: &etime,
   583  		},
   584  	}
   585  
   586  	teamSectionForInvite := makeTestSCTeamSection(team)
   587  	for _, scInvite := range testInvites {
   588  		inviteID := scInvite.ID
   589  		teamSectionForInvite.Invites = &SCTeamInvites{
   590  			Readers: &[]SCTeamInvite{scInvite},
   591  		}
   592  
   593  		state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   594  			teamSectionForInvite, me, nil /* merkleRoot */)
   595  		require.NoError(t, err)
   596  		_, found := state.FindActiveInviteMDByID(keybase1.TeamInviteID(inviteID))
   597  		require.True(t, found)
   598  
   599  		// Try to do `used_invites` for an invite that does not have `max_uses`.
   600  		teamSectionCM := makeTestSCTeamSection(team)
   601  		teamSectionCM.Members = &SCTeamMembers{
   602  			Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   603  		}
   604  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   605  			{InviteID: inviteID, UV: testUV.PercentForm()},
   606  		}
   607  		_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   608  			teamSectionCM, me, nil /* merkleRoot */)
   609  		requirePrecheckError(t, err)
   610  		require.Contains(t, err.Error(), "`used_invites` for a non-new-style invite")
   611  	}
   612  }
   613  
   614  func TestTeamPlayerBadCompletedInvites(t *testing.T) {
   615  	// Test if `completed_invites` errors out when used on an invite that
   616  	// defines `max_uses`, and therefore `used_invites` should be used instead
   617  	// to mark the invite usage.
   618  
   619  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   620  	defer tc.Cleanup()
   621  
   622  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   623  
   624  	// Add multi use invite.
   625  	maxUses := keybase1.TeamInviteMaxUses(10)
   626  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER,
   627  		&maxUses, nil /*etime */)
   628  
   629  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   630  		teamSectionForInvite, me, nil /* merkleRoot */)
   631  	require.NoError(t, err)
   632  	_, found := state.FindActiveInviteMDByID(inviteID)
   633  	require.True(t, found)
   634  
   635  	teamSectionCM := makeTestSCTeamSection(team)
   636  	teamSectionCM.Members = &SCTeamMembers{
   637  		Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   638  	}
   639  	teamSectionCM.CompletedInvites = SCMapInviteIDToUV{
   640  		inviteID: testUV.PercentForm(),
   641  	}
   642  	_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   643  		teamSectionCM, me, nil /* merkleRoot */)
   644  	requirePrecheckError(t, err)
   645  	require.Contains(t, err.Error(), "`completed_invites` for a new-style invite")
   646  	require.Contains(t, err.Error(), inviteID)
   647  }
   648  
   649  func TestTeamInvite64BitEtime(t *testing.T) {
   650  	// Load a chain from JSON file where there is an invite with `etime` in far
   651  	// future - 3020, so 32bit signed int is not enough to store that - and see
   652  	// if we can work with that UnixTime value.
   653  
   654  	// NOTE: Right now server will not allow etimes after 2038 just to be safe,
   655  	// so this test only works in server-less context (sigchains loaded from
   656  	// file or constructed in tests).
   657  
   658  	team, _ := runUnitFromFilename(t, "multiple_use_invite_1000_years.json")
   659  
   660  	state := &team.chain().inner
   661  	require.Len(t, state.ActiveInvites(), 1)
   662  
   663  	var inviteMD keybase1.TeamInviteMetadata
   664  	for _, inviteMD = range state.InviteMetadatas {
   665  		break // get first invite
   666  	}
   667  	invite := inviteMD.Invite
   668  
   669  	require.NotNil(t, invite.MaxUses)
   670  	require.True(t, invite.IsInfiniteUses())
   671  
   672  	require.NotNil(t, invite.Etime)
   673  	require.Equal(t, 3020, invite.Etime.Time().Year())
   674  
   675  	require.Len(t, inviteMD.UsedInvites, 2)
   676  }
   677  
   678  func TestTeamPlayerExhaustedMaxUses(t *testing.T) {
   679  	// Try to "use invite" which has its max uses exhausted.
   680  
   681  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   682  	defer tc.Cleanup()
   683  
   684  	var testUVs [3]keybase1.UserVersion
   685  	for i := range testUVs {
   686  		testUVs[i] = keybase1.UserVersion{Uid: libkb.UsernameToUID(fmt.Sprintf("t_alice_%d", i)), EldestSeqno: 1}
   687  	}
   688  
   689  	maxUses := keybase1.TeamInviteMaxUses(1)
   690  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER,
   691  		&maxUses, nil /* etime */)
   692  
   693  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   694  		teamSectionForInvite, me, nil /* merkleRoot */)
   695  	require.NoError(t, err)
   696  	_, found := state.FindActiveInviteMDByID(inviteID)
   697  	require.True(t, found)
   698  
   699  	{
   700  		// Try to add two people in same link. Max uses is 1, so it should not
   701  		// allow us to do that.
   702  		teamSectionCM := makeTestSCTeamSection(team)
   703  		teamSectionCM.Members = &SCTeamMembers{
   704  			Readers: &[]SCTeamMember{SCTeamMember(testUVs[0]), SCTeamMember(testUVs[1])},
   705  		}
   706  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   707  			{InviteID: SCTeamInviteID(inviteID), UV: testUVs[0].PercentForm()},
   708  			{InviteID: SCTeamInviteID(inviteID), UV: testUVs[1].PercentForm()},
   709  		}
   710  		_, err := appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   711  			teamSectionCM, me, nil /* merkleRoot */)
   712  		requirePrecheckError(t, err)
   713  		require.Contains(t, err.Error(), "is expired after 1 use")
   714  		require.Contains(t, err.Error(), inviteID)
   715  	}
   716  
   717  	{
   718  		// If we add two people, but only one of them is "using the invite", we should be fine.
   719  		teamSectionCM := makeTestSCTeamSection(team)
   720  		teamSectionCM.Members = &SCTeamMembers{
   721  			Readers: &[]SCTeamMember{SCTeamMember(testUVs[0]), SCTeamMember(testUVs[1])},
   722  		}
   723  		teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   724  			{InviteID: SCTeamInviteID(inviteID), UV: testUVs[0].PercentForm()},
   725  		}
   726  		state, err := appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   727  			teamSectionCM, me, nil /* merkleRoot */)
   728  		require.NoError(t, err)
   729  		require.Len(t, state.inner.InviteMetadatas[inviteID].UsedInvites, 1)
   730  		require.Len(t, state.GetAllUVs(), 3) // team creator and two people added in this link
   731  	}
   732  
   733  	{
   734  		state := state
   735  
   736  		// Add users one by one, first one should go through.
   737  		for i, uv := range testUVs[:2] {
   738  			teamSectionCM := makeTestSCTeamSection(team)
   739  			teamSectionCM.Members = &SCTeamMembers{
   740  				Readers: &[]SCTeamMember{SCTeamMember(uv)},
   741  			}
   742  			teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   743  				{InviteID: SCTeamInviteID(inviteID), UV: uv.PercentForm()},
   744  			}
   745  			newState, err := appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   746  				teamSectionCM, me, nil /* merkleRoot */)
   747  			if i == 0 {
   748  				require.NoError(t, err)
   749  				state = newState
   750  			} else {
   751  				requirePrecheckError(t, err)
   752  			}
   753  		}
   754  
   755  		require.Len(t, state.inner.InviteMetadatas[inviteID].UsedInvites, 1)
   756  		require.Len(t, state.GetAllUVs(), 2) // team creator and one person added in loop above
   757  	}
   758  }
   759  
   760  func TestTeamPlayerUsedInviteWithNoRoleChange(t *testing.T) {
   761  	// See TestTeamPlayerNoRoleChange in members_test.go
   762  	//
   763  	// If a result of change_membership is no role change, a log point is not
   764  	// created for the UV. This is weird from perspective of using invites.
   765  
   766  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   767  	defer tc.Cleanup()
   768  
   769  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   770  
   771  	// Add multi use invite.
   772  	maxUses := keybase1.TeamInviteMaxUses(10)
   773  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER,
   774  		&maxUses, nil /*etime */)
   775  
   776  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   777  		teamSectionForInvite, me, nil /* merkleRoot */)
   778  	require.NoError(t, err)
   779  	_, found := state.FindActiveInviteMDByID(inviteID)
   780  	require.True(t, found)
   781  
   782  	// Add member without using the invite first.
   783  	teamSectionCM := makeTestSCTeamSection(team)
   784  	teamSectionCM.Members = &SCTeamMembers{
   785  		Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   786  	}
   787  	state, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   788  		teamSectionCM, me, nil /* merkleRoot */)
   789  	require.NoError(t, err)
   790  
   791  	userLog := state.inner.UserLog[testUV]
   792  	require.Len(t, userLog, 1)
   793  	require.Equal(t, keybase1.TeamRole_READER, userLog[0].Role)
   794  	require.Equal(t, state.GetLatestSeqno(), userLog[0].SigMeta.SigChainLocation.Seqno)
   795  	require.EqualValues(t, 3, state.GetLatestSeqno())
   796  
   797  	// Add member again, with similar link, but using the invite.
   798  	// (re-use teamSectionCM)
   799  	teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   800  		{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   801  	}
   802  	state, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   803  		teamSectionCM, me, nil /* merkleRoot */)
   804  	require.NoError(t, err)
   805  
   806  	// This creates a new log point in UserLog
   807  	userLog = state.inner.UserLog[testUV]
   808  	require.Len(t, userLog, 2)
   809  	for i, lp := range userLog {
   810  		require.Equal(t, keybase1.TeamRole_READER, lp.Role)
   811  		require.EqualValues(t, 3+i, lp.SigMeta.SigChainLocation.Seqno)
   812  	}
   813  
   814  	// And the used invite references this latest log point.
   815  	inviteMD, found := state.inner.InviteMetadatas[inviteID]
   816  	require.True(t, found)
   817  	require.Len(t, inviteMD.UsedInvites, 1)
   818  	require.Equal(t, 1, inviteMD.UsedInvites[0].LogPoint)
   819  	require.Equal(t, testUV, inviteMD.UsedInvites[0].Uv)
   820  }
   821  
   822  func TestTeamPlayerUsedInviteMultipleTimes(t *testing.T) {
   823  	// See TestTeamPlayerNoRoleChange in members_test.go and
   824  	// TestTeamPlayerUsedInviteWithNoRoleChange above.
   825  	//
   826  	// Very similar to the test above, but instead of accepting an invite link
   827  	// after we were already a member, we accept invite once to become a
   828  	// member, and then (presumably) accept the same invite again, and get
   829  	// added again, with no role change.
   830  
   831  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   832  	defer tc.Cleanup()
   833  
   834  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   835  
   836  	// Add multi use invite.
   837  	maxUses := keybase1.TeamMaxUsesInfinite
   838  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER,
   839  		&maxUses, nil /*etime */)
   840  
   841  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   842  		teamSectionForInvite, me, nil /* merkleRoot */)
   843  	require.NoError(t, err)
   844  	_, found := state.FindActiveInviteMDByID(inviteID)
   845  	require.True(t, found)
   846  
   847  	// Add member using that invite, twice
   848  	teamSectionCM := makeTestSCTeamSection(team)
   849  	teamSectionCM.Members = &SCTeamMembers{
   850  		Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   851  	}
   852  	teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   853  		{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   854  	}
   855  	for i := 0; i < 2; i++ {
   856  		state, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   857  			teamSectionCM, me, nil /* merkleRoot */)
   858  		require.NoError(t, err)
   859  	}
   860  
   861  	// There should be two UUserLog entries
   862  	userLog := state.inner.UserLog[testUV]
   863  	require.Len(t, userLog, 2)
   864  	for i, lp := range userLog {
   865  		require.Equal(t, keybase1.TeamRole_READER, lp.Role)
   866  		require.EqualValues(t, 3+i, lp.SigMeta.SigChainLocation.Seqno)
   867  	}
   868  
   869  	// And two UsedInvites entries
   870  	inviteMD, found := state.inner.InviteMetadatas[inviteID]
   871  	require.True(t, found)
   872  	require.Len(t, inviteMD.UsedInvites, 2)
   873  	for i, ulp := range inviteMD.UsedInvites {
   874  		// Log point is 0 for first add and 1 for the second.
   875  		require.Equal(t, i, ulp.LogPoint)
   876  		require.Equal(t, testUV, ulp.Uv)
   877  	}
   878  }
   879  
   880  func TestTeamPlayerDoubleUsedInvites(t *testing.T) {
   881  	// Check change_membership link that adds one member, but has same used_invites
   882  	// pairs for that member for some reason. Note that server checks for this as
   883  	// well - it fails when it tries to complete the acceptance for a user more
   884  	// than once (TEAM_INVITE_USE_ACCEPTANCE_MISSING), and it doesn't allow one
   885  	// user to have more than one acceptance pending (TEAM_SEITAN_REQUEST_PENDING).
   886  
   887  	tc, team, me := setupTestForPrechecks(t, false /* implicitTeam */)
   888  	defer tc.Cleanup()
   889  
   890  	testUV := keybase1.UserVersion{Uid: libkb.UsernameToUID("t_alice_t"), EldestSeqno: 1}
   891  
   892  	maxUses := keybase1.TeamMaxUsesInfinite
   893  	teamSectionForInvite, inviteID := makeTestTeamSectionWithInviteLink(team, keybase1.TeamRole_READER, &maxUses, nil /* etime */)
   894  
   895  	// Add invite link
   896  	state, err := appendSigToState(t, team, nil /* state */, libkb.LinkTypeInvite,
   897  		teamSectionForInvite, me, nil /* merkleRoot */)
   898  	require.NoError(t, err)
   899  	_, found := state.FindActiveInviteMDByID(inviteID)
   900  	require.True(t, found)
   901  
   902  	// Add member using that invite, with duplicated UsedInvites
   903  	teamSectionCM := makeTestSCTeamSection(team)
   904  	teamSectionCM.Members = &SCTeamMembers{
   905  		Readers: &[]SCTeamMember{SCTeamMember(testUV)},
   906  	}
   907  	teamSectionCM.UsedInvites = []SCMapInviteIDUVPair{
   908  		{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   909  		{InviteID: SCTeamInviteID(inviteID), UV: testUV.PercentForm()},
   910  	}
   911  
   912  	_, err = appendSigToState(t, team, state, libkb.LinkTypeChangeMembership,
   913  		teamSectionCM, me, nil /* merkleRoot */)
   914  	requirePrecheckError(t, err)
   915  	require.Contains(t, err.Error(), "duplicate used_invite for UV")
   916  	require.Contains(t, err.Error(), testUV.PercentForm())
   917  }