github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }