github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/multiuser_common_test.go (about) 1 package systests 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "testing" 8 "time" 9 10 client "github.com/keybase/client/go/client" 11 engine "github.com/keybase/client/go/engine" 12 libkb "github.com/keybase/client/go/libkb" 13 logger "github.com/keybase/client/go/logger" 14 chat1 "github.com/keybase/client/go/protocol/chat1" 15 keybase1 "github.com/keybase/client/go/protocol/keybase1" 16 service "github.com/keybase/client/go/service" 17 teams "github.com/keybase/client/go/teams" 18 clockwork "github.com/keybase/clockwork" 19 rpc "github.com/keybase/go-framed-msgpack-rpc/rpc" 20 "github.com/stretchr/testify/require" 21 contextOld "golang.org/x/net/context" 22 ) 23 24 // Tests for systests with multiuser, multidevice situations. 25 // The abbreviation "smu" means "Systests Multi User". So 26 // smuUser is a 'Systests Multi User User"' 27 28 type smuUser struct { 29 ctx *smuContext 30 devices []*smuDeviceWrapper 31 backupKeys []backupKey 32 usernamePrefix string 33 username string 34 userInfo *signupInfo 35 primary *smuDeviceWrapper 36 notifications *teamNotifyHandler 37 } 38 39 type smuContext struct { 40 t *testing.T 41 log logger.Logger 42 fakeClock clockwork.FakeClock 43 users map[string](*smuUser) 44 } 45 46 func newSMUContext(t *testing.T) *smuContext { 47 ret := &smuContext{ 48 t: t, 49 users: make(map[string](*smuUser)), 50 fakeClock: clockwork.NewFakeClockAt(time.Now()), 51 } 52 return ret 53 } 54 55 func (smc *smuContext) cleanup() { 56 for _, v := range smc.users { 57 v.cleanup() 58 } 59 } 60 61 func (u *smuUser) cleanup() { 62 if u == nil { 63 return 64 } 65 for _, d := range u.devices { 66 d.tctx.Cleanup() 67 if d.service != nil { 68 d.service.Stop(0) 69 err := d.stop() 70 require.NoError(d.tctx.T, err) 71 } 72 for _, cl := range d.clones { 73 cl.Cleanup() 74 } 75 for _, cl := range d.usedClones { 76 cl.Cleanup() 77 } 78 } 79 } 80 81 // smuDeviceWrapper wraps a mock "device", meaning an independent running service and 82 // some connected clients. 83 type smuDeviceWrapper struct { 84 ctx *smuContext 85 tctx *libkb.TestContext 86 clones, usedClones []*libkb.TestContext 87 deviceKey keybase1.PublicKey 88 stopCh chan error 89 service *service.Service 90 cli rpc.GenericClient 91 xp rpc.Transporter 92 } 93 94 func (d *smuDeviceWrapper) KID() keybase1.KID { 95 return d.deviceKey.KID 96 } 97 98 func (d *smuDeviceWrapper) startService(numClones int) { 99 for i := 0; i < numClones; i++ { 100 d.clones = append(d.clones, cloneContext(d.tctx)) 101 } 102 d.stopCh = make(chan error) 103 svc := service.NewService(d.tctx.G, false) 104 d.service = svc 105 startCh := svc.GetStartChannel() 106 go func() { 107 d.stopCh <- svc.Run() 108 }() 109 <-startCh 110 } 111 112 func (d *smuDeviceWrapper) stop() error { 113 return <-d.stopCh 114 } 115 116 func (d *smuDeviceWrapper) clearUPAKCache() { 117 _, err := d.tctx.G.LocalDb.Nuke() 118 require.NoError(d.tctx.T, err) 119 d.tctx.G.GetUPAKLoader().ClearMemory() 120 } 121 122 type smuTerminalUI struct{} 123 124 func (t smuTerminalUI) ErrorWriter() io.Writer { return nil } 125 func (t smuTerminalUI) Output(string) error { return nil } 126 func (t smuTerminalUI) OutputDesc(libkb.OutputDescriptor, string) error { return nil } 127 func (t smuTerminalUI) OutputWriter() io.Writer { return nil } 128 func (t smuTerminalUI) UnescapedOutputWriter() io.Writer { return nil } 129 func (t smuTerminalUI) Printf(fmt string, args ...interface{}) (int, error) { return 0, nil } 130 func (t smuTerminalUI) PrintfUnescaped(fmt string, args ...interface{}) (int, error) { return 0, nil } 131 func (t smuTerminalUI) Prompt(libkb.PromptDescriptor, string) (string, error) { return "", nil } 132 func (t smuTerminalUI) PromptForConfirmation(prompt string) error { return nil } 133 func (t smuTerminalUI) PromptPassword(libkb.PromptDescriptor, string) (string, error) { return "", nil } 134 func (t smuTerminalUI) PromptPasswordMaybeScripted(libkb.PromptDescriptor, string) (string, error) { 135 return "", nil 136 } 137 func (t smuTerminalUI) PromptYesNo(libkb.PromptDescriptor, string, libkb.PromptDefault) (bool, error) { 138 return false, nil 139 } 140 func (t smuTerminalUI) Tablify(headings []string, rowfunc func() []string) {} 141 func (t smuTerminalUI) TerminalSize() (width int, height int) { return } 142 143 type signupInfoSecretUI struct { 144 signupInfo *signupInfo 145 log logger.Logger 146 } 147 148 func (s signupInfoSecretUI) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) { 149 if p.Type == keybase1.PassphraseType_PAPER_KEY { 150 res.Passphrase = s.signupInfo.displayedPaperKey 151 } else { 152 res.Passphrase = s.signupInfo.passphrase 153 } 154 s.log.Debug("| GetPassphrase: %v -> %v", p, res) 155 return res, err 156 } 157 158 type usernameLoginUI struct { 159 username string 160 } 161 162 var _ libkb.LoginUI = (*usernameLoginUI)(nil) 163 164 func (s usernameLoginUI) GetEmailOrUsername(contextOld.Context, int) (string, error) { 165 return s.username, nil 166 } 167 func (s usernameLoginUI) PromptRevokePaperKeys(contextOld.Context, keybase1.PromptRevokePaperKeysArg) (ret bool, err error) { 168 return false, nil 169 } 170 func (s usernameLoginUI) DisplayPaperKeyPhrase(contextOld.Context, keybase1.DisplayPaperKeyPhraseArg) error { 171 return nil 172 } 173 func (s usernameLoginUI) DisplayPrimaryPaperKey(contextOld.Context, keybase1.DisplayPrimaryPaperKeyArg) error { 174 return nil 175 } 176 func (s usernameLoginUI) PromptResetAccount(_ context.Context, arg keybase1.PromptResetAccountArg) (keybase1.ResetPromptResponse, error) { 177 return keybase1.ResetPromptResponse_NOTHING, nil 178 } 179 func (s usernameLoginUI) DisplayResetProgress(_ context.Context, arg keybase1.DisplayResetProgressArg) error { 180 return nil 181 } 182 func (s usernameLoginUI) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error { 183 return nil 184 } 185 func (s usernameLoginUI) PromptPassphraseRecovery(_ context.Context, arg keybase1.PromptPassphraseRecoveryArg) (bool, error) { 186 return false, nil 187 } 188 func (s usernameLoginUI) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) { 189 return "", nil 190 } 191 func (s usernameLoginUI) DisplayResetMessage(_ context.Context, arg keybase1.DisplayResetMessageArg) error { 192 return nil 193 } 194 195 func (d *smuDeviceWrapper) popClone() *libkb.TestContext { 196 if len(d.clones) == 0 { 197 panic("ran out of cloned environments") 198 } 199 ret := d.clones[0] 200 d.clones = d.clones[1:] 201 // Hold a reference to this clone for cleanup 202 d.usedClones = append(d.usedClones, ret) 203 ui := genericUI{ 204 g: ret.G, 205 TerminalUI: smuTerminalUI{}, 206 } 207 ret.G.SetUI(&ui) 208 return ret 209 } 210 211 func (smc *smuContext) setupDeviceHelper(u *smuUser, puk bool) *smuDeviceWrapper { 212 tctx := setupTest(smc.t, u.usernamePrefix) 213 tctx.Tp.DisableUpgradePerUserKey = !puk 214 tctx.G.SetClock(smc.fakeClock) 215 ret := &smuDeviceWrapper{ctx: smc, tctx: tctx} 216 u.devices = append(u.devices, ret) 217 if u.primary == nil { 218 u.primary = ret 219 } 220 if smc.log == nil { 221 smc.log = tctx.G.Log 222 } 223 return ret 224 } 225 226 func (smc *smuContext) installKeybaseForUser(usernamePrefix string, numClones int) *smuUser { 227 user := &smuUser{ctx: smc, usernamePrefix: usernamePrefix} 228 smc.users[usernamePrefix] = user 229 smc.newDevice(user, numClones) 230 return user 231 } 232 233 func (smc *smuContext) installKeybaseForUserNoPUK(usernamePrefix string, numClones int) *smuUser { 234 user := &smuUser{ctx: smc, usernamePrefix: usernamePrefix} 235 smc.users[usernamePrefix] = user 236 smc.newDevice(user, numClones) 237 return user 238 } 239 240 func (smc *smuContext) newDevice(u *smuUser, numClones int) *smuDeviceWrapper { 241 return smc.newDeviceHelper(u, numClones, true) 242 } 243 244 func (smc *smuContext) newDeviceHelper(u *smuUser, numClones int, puk bool) *smuDeviceWrapper { 245 ret := smc.setupDeviceHelper(u, puk) 246 ret.startService(numClones) 247 ret.startClient() 248 return ret 249 } 250 251 func (u *smuUser) primaryDevice() *smuDeviceWrapper { 252 return u.primary 253 } 254 255 func (d *smuDeviceWrapper) userClient() keybase1.UserClient { 256 return keybase1.UserClient{Cli: d.cli} 257 } 258 259 func (d *smuDeviceWrapper) ctlClient() keybase1.CtlClient { 260 return keybase1.CtlClient{Cli: d.cli} 261 } 262 263 func (d *smuDeviceWrapper) rpcClient() rpc.GenericClient { 264 return d.cli 265 } 266 267 func (d *smuDeviceWrapper) transport() rpc.Transporter { 268 return d.xp 269 } 270 271 func (d *smuDeviceWrapper) startClient() { 272 var err error 273 tctx := d.popClone() 274 d.cli, d.xp, err = client.GetRPCClientWithContext(tctx.G) 275 if err != nil { 276 d.ctx.t.Fatal(err) 277 } 278 } 279 280 func (d *smuDeviceWrapper) loadEncryptionKIDs() (devices []keybase1.KID, backups []backupKey) { 281 keyMap := make(map[keybase1.KID]keybase1.PublicKey) 282 keys, err := d.userClient().LoadMyPublicKeys(context.TODO(), 0) 283 if err != nil { 284 d.ctx.t.Fatalf("Failed to LoadMyPublicKeys: %s", err) 285 } 286 for _, key := range keys { 287 keyMap[key.KID] = key 288 } 289 290 for _, key := range keys { 291 if key.IsSibkey { 292 continue 293 } 294 parent, found := keyMap[keybase1.KID(key.ParentID)] 295 if !found { 296 continue 297 } 298 299 switch parent.DeviceType { 300 case keybase1.DeviceTypeV2_PAPER: 301 backups = append(backups, backupKey{KID: key.KID, deviceID: parent.DeviceID}) 302 case keybase1.DeviceTypeV2_DESKTOP: 303 devices = append(devices, key.KID) 304 default: 305 } 306 } 307 return devices, backups 308 } 309 310 func (u *smuUser) signup() { 311 u.signupHelper(true, false) 312 } 313 314 func (u *smuUser) signupNoPUK() { 315 u.signupHelper(false, false) 316 } 317 318 func (u *smuUser) signupHelper(puk, paper bool) { 319 ctx := u.ctx 320 userInfo := randomUser(u.usernamePrefix) 321 u.userInfo = userInfo 322 dw := u.primaryDevice() 323 tctx := dw.popClone() 324 tctx.Tp.DisableUpgradePerUserKey = !puk 325 g := tctx.G 326 signupUI := signupUI{ 327 info: userInfo, 328 Contextified: libkb.NewContextified(g), 329 } 330 g.SetUI(&signupUI) 331 signup := client.NewCmdSignupRunner(g) 332 signup.SetTestWithPaper(paper) 333 if err := signup.Run(); err != nil { 334 ctx.t.Fatal(err) 335 } 336 ctx.t.Logf("signed up %s", userInfo.username) 337 u.username = userInfo.username 338 var backupKey backupKey 339 devices, backups := dw.loadEncryptionKIDs() 340 if len(devices) != 1 { 341 ctx.t.Fatalf("Expected 1 device back; got %d", len(devices)) 342 } 343 dw.deviceKey.KID = devices[0] 344 if paper { 345 if len(backups) != 1 { 346 ctx.t.Fatalf("Expected 1 backup back; got %d", len(backups)) 347 } 348 backupKey = backups[0] 349 backupKey.secret = signupUI.info.displayedPaperKey 350 u.backupKeys = append(u.backupKeys, backupKey) 351 } 352 353 // Reconfigure config subsystem in Primary Global Context and also 354 // in all clones. This has to be done after signup because the 355 // username changes, and so does config filename. 356 err := dw.tctx.G.ConfigureConfig() 357 require.NoError(ctx.t, err) 358 for _, clone := range dw.clones { 359 err = clone.G.ConfigureConfig() 360 require.NoError(ctx.t, err) 361 } 362 } 363 364 func (u *smuUser) perUserKeyUpgrade() error { 365 g := u.getPrimaryGlobalContext() 366 arg := &engine.PerUserKeyUpgradeArgs{} 367 eng := engine.NewPerUserKeyUpgrade(g, arg) 368 uis := libkb.UIs{ 369 LogUI: g.UI.GetLogUI(), 370 } 371 m := libkb.NewMetaContextTODO(g).WithUIs(uis) 372 err := engine.RunEngine2(m, eng) 373 return err 374 } 375 376 type smuTeam struct { 377 ID keybase1.TeamID 378 name string 379 } 380 381 type smuImplicitTeam struct { 382 ID keybase1.TeamID 383 } 384 385 func (u *smuUser) registerForNotifications() { 386 u.notifications = newTeamNotifyHandler() 387 srv := rpc.NewServer(u.primaryDevice().transport(), nil) 388 if err := srv.Register(keybase1.NotifyTeamProtocol(u.notifications)); err != nil { 389 u.ctx.t.Fatal(err) 390 } 391 ncli := keybase1.NotifyCtlClient{Cli: u.primaryDevice().rpcClient()} 392 if err := ncli.SetNotifications(context.TODO(), keybase1.NotificationChannels{Team: true}); err != nil { 393 u.ctx.t.Fatal(err) 394 } 395 } 396 397 // nolint 398 func (u *smuUser) waitForNewlyAddedToTeamByID(teamID keybase1.TeamID) { 399 u.ctx.t.Logf("waiting for newly added to team %s", teamID) 400 401 // process 10 team rotations or 10s worth of time 402 for i := 0; i < 10; i++ { 403 select { 404 case tid := <-u.notifications.newlyAddedToTeam: 405 u.ctx.t.Logf("team newly added notification received: %v", tid) 406 if tid.Eq(teamID) { 407 u.ctx.t.Logf("notification matched!") 408 return 409 } 410 u.ctx.t.Logf("ignoring newly added message (expected teamID = %q)", teamID) 411 case <-time.After(1 * time.Second * libkb.CITimeMultiplier(u.getPrimaryGlobalContext())): 412 } 413 } 414 u.ctx.t.Fatalf("timed out waiting for team newly added %s", teamID) 415 } 416 417 func (u *smuUser) waitForTeamAbandoned(teamID keybase1.TeamID) { 418 u.ctx.t.Logf("waiting for team abandoned %s", teamID) 419 420 // process 10 team rotations or 10s worth of time 421 for i := 0; i < 10; i++ { 422 select { 423 case abandonID := <-u.notifications.abandonCh: 424 u.ctx.t.Logf("team abandon notification received: %v", abandonID) 425 if abandonID.Eq(teamID) { 426 u.ctx.t.Logf("abandon matched!") 427 return 428 } 429 u.ctx.t.Logf("ignoring abandon message (expected teamID = %q)", teamID) 430 case <-time.After(1 * time.Second * libkb.CITimeMultiplier(u.getPrimaryGlobalContext())): 431 } 432 } 433 u.ctx.t.Fatalf("timed out waiting for team abandon %s", teamID) 434 } 435 436 func (u *smuUser) getTeamsClient() keybase1.TeamsClient { 437 return keybase1.TeamsClient{Cli: u.primaryDevice().rpcClient()} 438 } 439 440 func (u *smuUser) pollForMembershipUpdate(team smuTeam, keyGen keybase1.PerTeamKeyGeneration, 441 poller func(d keybase1.TeamDetails) bool) keybase1.TeamDetails { 442 wait := 100 * time.Millisecond 443 var totalWait time.Duration 444 i := 0 445 for { 446 cli := u.getTeamsClient() 447 details, err := cli.TeamGet(context.TODO(), keybase1.TeamGetArg{Name: team.name}) 448 if err != nil { 449 u.ctx.t.Fatal(err) 450 } 451 // If the caller specified a "poller" that means we should keep polling until 452 // the predicate turns true 453 if details.KeyGeneration == keyGen && (poller == nil || poller(details)) { 454 u.ctx.log.Debug("found key generation %d", keyGen) 455 return details 456 } 457 if i == 9 { 458 break 459 } 460 i++ 461 u.ctx.log.Debug("in pollForMembershipUpdate: iter=%d; missed it, now waiting for %s (latest details.KG = %d; poller=%v)", i, wait, details.KeyGeneration, (poller != nil)) 462 kickTeamRekeyd(u.getPrimaryGlobalContext(), u.ctx.t) 463 time.Sleep(wait) 464 totalWait += wait 465 wait *= 2 466 } 467 require.FailNowf(u.ctx.t, "pollForMembershipUpdate timed out", 468 "Failed to find the needed key generation (%d) after %s of waiting (%d iterations)", 469 keyGen, totalWait, i) 470 return keybase1.TeamDetails{} 471 } 472 473 func (u *smuUser) pollForTeamSeqnoLink(team smuTeam, toSeqno keybase1.Seqno) { 474 for i := 0; i < 20; i++ { 475 details, err := teams.Load(context.TODO(), u.getPrimaryGlobalContext(), keybase1.LoadTeamArg{ 476 Name: team.name, 477 ForceRepoll: true, 478 }) 479 if err != nil { 480 u.ctx.t.Fatalf("error while loading team %q: %v", team.name, err) 481 } 482 483 if details.CurrentSeqno() >= toSeqno { 484 u.ctx.t.Logf("Found new seqno %d at poll loop iter %d", details.CurrentSeqno(), i) 485 return 486 } 487 488 time.Sleep(500 * time.Millisecond) 489 } 490 491 u.ctx.t.Fatalf("timed out waiting for team %s seqno link %d", team, toSeqno) 492 } 493 494 func (u *smuUser) createTeam(writers []*smuUser) smuTeam { 495 return u.createTeam2(nil, writers, nil, nil) 496 } 497 498 func (u *smuUser) createTeam2(readers, writers, admins, owners []*smuUser) smuTeam { 499 name := u.username + "t" 500 nameK1, err := keybase1.TeamNameFromString(name) 501 require.NoError(u.ctx.t, err) 502 cli := u.getTeamsClient() 503 x, err := cli.TeamCreate(context.TODO(), keybase1.TeamCreateArg{Name: nameK1.String()}) 504 require.NoError(u.ctx.t, err) 505 lists := [][]*smuUser{readers, writers, admins, owners} 506 roles := []keybase1.TeamRole{keybase1.TeamRole_READER, 507 keybase1.TeamRole_WRITER, keybase1.TeamRole_ADMIN, keybase1.TeamRole_OWNER} 508 for i, list := range lists { 509 for _, u2 := range list { 510 _, err = cli.TeamAddMember(context.TODO(), keybase1.TeamAddMemberArg{ 511 TeamID: x.TeamID, 512 Username: u2.username, 513 Role: roles[i], 514 }) 515 require.NoError(u.ctx.t, err) 516 } 517 } 518 return smuTeam{ID: x.TeamID, name: name} 519 } 520 521 func (u *smuUser) lookupImplicitTeam(create bool, displayName string, public bool) smuImplicitTeam { 522 cli := u.getTeamsClient() 523 var err error 524 var res keybase1.LookupImplicitTeamRes 525 if create { 526 res, err = cli.LookupOrCreateImplicitTeam(context.TODO(), keybase1.LookupOrCreateImplicitTeamArg{Name: displayName, Public: public}) 527 } else { 528 res, err = cli.LookupImplicitTeam(context.TODO(), keybase1.LookupImplicitTeamArg{Name: displayName, Public: public}) 529 } 530 if err != nil { 531 u.ctx.t.Fatal(err) 532 } 533 return smuImplicitTeam{ID: res.TeamID} 534 } 535 536 func (u *smuUser) loadTeam(teamname string, admin bool) *teams.Team { 537 team, err := teams.Load(context.Background(), u.getPrimaryGlobalContext(), keybase1.LoadTeamArg{ 538 Name: teamname, 539 NeedAdmin: admin, 540 ForceRepoll: true, 541 }) 542 require.NoError(u.ctx.t, err) 543 return team 544 } 545 546 func (u *smuUser) addTeamMember(team smuTeam, member *smuUser, role keybase1.TeamRole) { 547 cli := u.getTeamsClient() 548 _, err := cli.TeamAddMember(context.TODO(), keybase1.TeamAddMemberArg{ 549 TeamID: team.ID, 550 Username: member.username, 551 Role: role, 552 }) 553 require.NoError(u.ctx.t, err) 554 } 555 556 func (u *smuUser) addWriter(team smuTeam, w *smuUser) { 557 u.addTeamMember(team, w, keybase1.TeamRole_WRITER) 558 } 559 560 func (u *smuUser) addAdmin(team smuTeam, w *smuUser) { 561 u.addTeamMember(team, w, keybase1.TeamRole_ADMIN) 562 } 563 564 func (u *smuUser) editMember(team *smuTeam, username string, role keybase1.TeamRole) { 565 err := u.getTeamsClient().TeamEditMember(context.TODO(), keybase1.TeamEditMemberArg{ 566 Name: team.name, 567 Username: username, 568 Role: role, 569 }) 570 require.NoError(u.ctx.t, err) 571 } 572 573 func (u *smuUser) reAddUserAfterReset(team smuImplicitTeam, w *smuUser) { 574 cli := u.getTeamsClient() 575 err := cli.TeamReAddMemberAfterReset(context.TODO(), keybase1.TeamReAddMemberAfterResetArg{ 576 Id: team.ID, 577 Username: w.username, 578 }) 579 require.NoError(u.ctx.t, err) 580 } 581 582 func (u *smuUser) reset() { 583 g := u.getPrimaryGlobalContext() 584 ui := genericUI{ 585 g: g, 586 SecretUI: u.secretUI(), 587 } 588 g.SetUI(&ui) 589 cmd := client.NewCmdAccountResetRunner(g) 590 err := cmd.Run() 591 if err != nil { 592 u.ctx.t.Fatal(err) 593 } 594 } 595 596 func (u *smuUser) delete() { 597 g := u.getPrimaryGlobalContext() 598 ui := genericUI{ 599 g: g, 600 SecretUI: u.secretUI(), 601 TerminalUI: smuTerminalUI{}, 602 } 603 g.SetUI(&ui) 604 cmd := client.NewCmdAccountDeleteRunner(g) 605 err := cmd.Run() 606 if err != nil { 607 u.ctx.t.Fatal(err) 608 } 609 } 610 611 func (u *smuUser) dbNuke() { 612 err := u.primaryDevice().ctlClient().DbNuke(context.TODO(), 0) 613 require.NoError(u.ctx.t, err) 614 } 615 616 func (u *smuUser) userVersion() keybase1.UserVersion { 617 uv, err := u.primaryDevice().userClient().MeUserVersion(context.Background(), keybase1.MeUserVersionArg{ForcePoll: true}) 618 require.NoError(u.ctx.t, err) 619 return uv 620 } 621 622 func (u *smuUser) MetaContext() libkb.MetaContext { 623 return libkb.NewMetaContextForTest(*u.primaryDevice().tctx) 624 } 625 626 func (u *smuUser) getPrimaryGlobalContext() *libkb.GlobalContext { 627 return u.primaryDevice().tctx.G 628 } 629 630 func (u *smuUser) setUIDMapperNoCachingMode(enabled bool) { 631 u.getPrimaryGlobalContext().UIDMapper.SetTestingNoCachingMode(enabled) 632 } 633 634 func (u *smuUser) loginAfterReset(numClones int) *smuDeviceWrapper { 635 return u.loginAfterResetHelper(numClones, true) 636 } 637 638 func (u *smuUser) loginAfterResetNoPUK(numClones int) *smuDeviceWrapper { 639 return u.loginAfterResetHelper(numClones, false) 640 } 641 642 func (u *smuUser) loginAfterResetHelper(numClones int, puk bool) *smuDeviceWrapper { 643 dev := u.ctx.newDeviceHelper(u, numClones, puk) 644 u.primary = dev 645 g := dev.tctx.G 646 ui := genericUI{ 647 g: g, 648 SecretUI: u.secretUI(), 649 LoginUI: usernameLoginUI{u.userInfo.username}, 650 ProvisionUI: nullProvisionUI{randomDevice()}, 651 } 652 g.SetUI(&ui) 653 cmd := client.NewCmdLoginRunner(g) 654 err := cmd.Run() 655 require.NoError(u.ctx.t, err, "login after reset") 656 return dev 657 } 658 659 func (u *smuUser) secretUI() signupInfoSecretUI { 660 return signupInfoSecretUI{u.userInfo, u.ctx.log} 661 } 662 663 func (u *smuUser) teamGet(team smuTeam) (keybase1.TeamDetails, error) { 664 cli := u.getTeamsClient() 665 details, err := cli.TeamGet(context.TODO(), keybase1.TeamGetArg{Name: team.name}) 666 return details, err 667 } 668 669 func (u *smuUser) teamMemberDetails(team smuTeam, user *smuUser) ([]keybase1.TeamMemberDetails, error) { 670 teamDetails, err := u.teamGet(team) 671 if err != nil { 672 return nil, err 673 } 674 var all []keybase1.TeamMemberDetails 675 all = append(all, teamDetails.Members.Owners...) 676 all = append(all, teamDetails.Members.Admins...) 677 all = append(all, teamDetails.Members.Writers...) 678 all = append(all, teamDetails.Members.Readers...) 679 680 var matches []keybase1.TeamMemberDetails 681 for _, m := range all { 682 if m.Username == user.username { 683 matches = append(matches, m) 684 } 685 } 686 if len(matches) == 0 { 687 return nil, libkb.NotFoundError{} 688 } 689 return matches, nil 690 } 691 692 func (u *smuUser) isMemberActive(team smuTeam, user *smuUser) (bool, error) { 693 details, err := u.teamMemberDetails(team, user) 694 u.ctx.t.Logf("isMemberActive team member details for %s: %+v", user.username, details) 695 if err != nil { 696 return false, err 697 } 698 for _, d := range details { 699 if d.Status.IsActive() { 700 return true, nil 701 } 702 } 703 return false, nil 704 } 705 706 func (u *smuUser) assertMemberActive(team smuTeam, user *smuUser) { 707 active, err := u.isMemberActive(team, user) 708 require.NoError(u.ctx.t, err, "assertMemberInactive error: %s", err) 709 require.True(u.ctx.t, active, "user %s is inactive (expected active)", user.username) 710 } 711 712 func (u *smuUser) assertMemberInactive(team smuTeam, user *smuUser) { 713 active, err := u.isMemberActive(team, user) 714 require.NoError(u.ctx.t, err, "assertMemberInactive error: %s", err) 715 require.False(u.ctx.t, active, "user %s is active (expected inactive)", user.username) 716 } 717 718 func (u *smuUser) assertMemberMissing(team smuTeam, user *smuUser) { 719 _, err := u.teamMemberDetails(team, user) 720 require.Error(user.ctx.t, err, "member should not be found") 721 require.Equal(user.ctx.t, libkb.NotFoundError{}, err, "member should not be found") 722 } 723 724 func (u *smuUser) uid() keybase1.UID { 725 return u.primaryDevice().tctx.G.Env.GetUID() 726 } 727 728 func (u *smuUser) openTeam(team smuTeam, role keybase1.TeamRole) { 729 cli := u.getTeamsClient() 730 err := cli.TeamSetSettings(context.Background(), keybase1.TeamSetSettingsArg{ 731 TeamID: team.ID, 732 Settings: keybase1.TeamSettings{ 733 Open: true, 734 JoinAs: role, 735 }, 736 }) 737 if err != nil { 738 u.ctx.t.Fatal(err) 739 } 740 } 741 742 func (u *smuUser) requestAccess(team smuTeam) { 743 cli := u.getTeamsClient() 744 _, err := cli.TeamRequestAccess(context.Background(), keybase1.TeamRequestAccessArg{ 745 Name: team.name, 746 }) 747 if err != nil { 748 u.ctx.t.Fatal(err) 749 } 750 } 751 752 func (u *smuUser) readChatsWithError(team smuTeam) (messages []chat1.MessageUnboxed, err error) { 753 return u.readChatsWithErrorAndDevice(team, u.primaryDevice(), 0) 754 } 755 756 // readChatsWithErrorAndDevice reads chats from `team` to get *at least* 757 // `nMessages` messages. 758 func (u *smuUser) readChatsWithErrorAndDevice(team smuTeam, dev *smuDeviceWrapper, nMessages int) (messages []chat1.MessageUnboxed, err error) { 759 tctx := dev.popClone() 760 761 pollInterval := 100 * time.Millisecond 762 timeLimit := 10 * time.Second 763 timeout := time.After(timeLimit) 764 765 pollLoop: 766 for i := 0; ; i++ { 767 runner := client.NewCmdChatReadRunner(tctx.G) 768 runner.SetTeamChatForTest(team.name) 769 _, messages, err = runner.Fetch() 770 if err != nil { 771 u.ctx.t.Logf("readChatsWithErrorAndDevice failure: %s", err.Error()) 772 return nil, err 773 } 774 775 if len(messages) >= nMessages { 776 u.ctx.t.Logf("readChatsWithErrorAndDevice success after retrying %d times, got %d msgs, asked for %d", i, len(messages), nMessages) 777 return messages, nil 778 } 779 780 u.ctx.t.Logf("readChatsWithErrorAndDevice trying again in %s (i=%d)", pollInterval, i) 781 select { 782 case <-timeout: 783 break pollLoop 784 case <-time.After(pollInterval): 785 } 786 } 787 788 u.ctx.t.Logf("Failed to readChatsWithErrorAndDevice after polling for %s", timeLimit) 789 return nil, fmt.Errorf("failed to read messages after polling for %s", timeLimit) 790 } 791 792 func (u *smuUser) readChats(team smuTeam, nMessages int) { 793 u.readChatsWithDevice(team, u.primaryDevice(), nMessages) 794 } 795 796 func (u *smuUser) readChatsWithDevice(team smuTeam, dev *smuDeviceWrapper, nMessages int) { 797 messages, err := u.readChatsWithErrorAndDevice(team, dev, nMessages) 798 t := u.ctx.t 799 require.NoError(t, err) 800 801 // Filter out journeycards 802 originalLen := len(messages) 803 n := 0 // https://github.com/golang/go/wiki/SliceTricks#filter-in-place 804 for _, msg := range messages { 805 if !msg.IsJourneycard() { 806 messages[n] = msg 807 n++ 808 } 809 } 810 messages = messages[:n] 811 if originalLen < len(messages) { 812 t.Logf("filtered out %v journeycard messages", originalLen-len(messages)) 813 } 814 815 if len(messages) != nMessages { 816 t.Logf("messages: %v", chat1.MessageUnboxedDebugLines(messages)) 817 } 818 require.Len(t, messages, nMessages) 819 820 for i, msg := range messages { 821 require.Equal(t, msg.Valid().MessageBody.Text().Body, fmt.Sprintf("%d", len(messages)-i-1)) 822 } 823 divDebug(u.ctx, "readChat success for %s", u.username) 824 } 825 826 func (u *smuUser) sendChat(t smuTeam, msg string) { 827 tctx := u.primaryDevice().popClone() 828 runner := client.NewCmdChatSendRunner(tctx.G) 829 runner.SetTeamChatForTest(t.name) 830 runner.SetMessage(msg) 831 err := runner.Run() 832 if err != nil { 833 u.ctx.t.Fatal(err) 834 } 835 }