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