github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbtest/kbtest.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package kbtest 5 6 import ( 7 "crypto/rand" 8 "encoding/hex" 9 "fmt" 10 "os" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/require" 16 17 "golang.org/x/net/context" 18 19 "github.com/keybase/client/go/engine" 20 "github.com/keybase/client/go/kex2" 21 "github.com/keybase/client/go/libkb" 22 "github.com/keybase/client/go/protocol/keybase1" 23 ) 24 25 const testInviteCode = "202020202020202020202020" 26 27 const DefaultDeviceName = "my device" 28 29 type FakeUser struct { 30 Username string 31 Email string 32 Passphrase string 33 User *libkb.User 34 EldestSeqno keybase1.Seqno 35 } 36 37 func NewFakeUser(prefix string) (*FakeUser, error) { 38 buf := make([]byte, 5) 39 if _, err := rand.Read(buf); err != nil { 40 return nil, err 41 } 42 username := fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(buf)) 43 email := fmt.Sprintf("%s@noemail.keybase.io", username) 44 buf = make([]byte, 12) 45 if _, err := rand.Read(buf); err != nil { 46 return nil, err 47 } 48 passphrase := hex.EncodeToString(buf) 49 return &FakeUser{username, email, passphrase, nil, keybase1.Seqno(1)}, nil 50 } 51 52 func (fu *FakeUser) NewSecretUI() *libkb.TestSecretUI { 53 return &libkb.TestSecretUI{Passphrase: fu.Passphrase} 54 } 55 56 func (fu *FakeUser) GetUID() keybase1.UID { 57 return libkb.UsernameToUID(fu.Username) 58 } 59 60 func (fu FakeUser) NormalizedUsername() libkb.NormalizedUsername { 61 return libkb.NewNormalizedUsername(fu.Username) 62 } 63 64 func (fu *FakeUser) GetUserVersion() keybase1.UserVersion { 65 return keybase1.UserVersion{ 66 Uid: fu.GetUID(), 67 EldestSeqno: fu.EldestSeqno, 68 } 69 } 70 71 func (fu *FakeUser) Login(g *libkb.GlobalContext) error { 72 uis := libkb.UIs{ 73 ProvisionUI: &TestProvisionUI{}, 74 LogUI: g.UI.GetLogUI(), 75 GPGUI: &GPGTestUI{}, 76 SecretUI: fu.NewSecretUI(), 77 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 78 } 79 li := engine.NewLogin(g, keybase1.DeviceTypeV2_DESKTOP, fu.Username, keybase1.ClientType_CLI) 80 m := libkb.NewMetaContextTODO(g).WithUIs(uis) 81 return engine.RunEngine2(m, li) 82 } 83 84 func LogoutAndLoginAs(tc libkb.TestContext, fu *FakeUser) { 85 Logout(tc) 86 err := fu.Login(tc.G) 87 require.NoError(tc.T, err) 88 } 89 90 func CreateAndSignupFakeUser(prefix string, g *libkb.GlobalContext) (*FakeUser, error) { 91 return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */) 92 } 93 94 func CreateAndSignupFakeUserPaper(prefix string, g *libkb.GlobalContext) (*FakeUser, error) { 95 return createAndSignupFakeUser(prefix, g, false /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */) 96 } 97 98 func CreateAndSignupFakeUserMobile(prefix string, g *libkb.GlobalContext) (*FakeUser, error) { 99 return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_MOBILE, false /* randomPW */) 100 } 101 102 func CreateAndSignupFakeUserRandomPW(prefix string, g *libkb.GlobalContext) (*FakeUser, error) { 103 return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_DESKTOP, true /* randomPW */) 104 } 105 106 func createAndSignupFakeUser(prefix string, g *libkb.GlobalContext, skipPaper bool, deviceType keybase1.DeviceType, randomPW bool) (*FakeUser, error) { 107 fu, err := NewFakeUser(prefix) 108 if err != nil { 109 return nil, err 110 } 111 if randomPW { 112 fu.Passphrase = "" 113 } 114 arg := engine.SignupEngineRunArg{ 115 Username: fu.Username, 116 Email: fu.Email, 117 InviteCode: testInviteCode, 118 Passphrase: fu.Passphrase, 119 DeviceName: DefaultDeviceName, 120 DeviceType: deviceType, 121 SkipGPG: true, 122 SkipMail: true, 123 SkipPaper: skipPaper, 124 GenerateRandomPassphrase: randomPW, 125 StoreSecret: true, 126 } 127 uis := libkb.UIs{ 128 LogUI: g.UI.GetLogUI(), 129 GPGUI: &GPGTestUI{}, 130 SecretUI: fu.NewSecretUI(), 131 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 132 } 133 s := engine.NewSignupEngine(g, &arg) 134 m := libkb.NewMetaContextTODO(g).WithUIs(uis) 135 if err := engine.RunEngine2(m, s); err != nil { 136 return nil, err 137 } 138 fu.User, err = libkb.LoadUser(libkb.NewLoadUserByNameArg(g, fu.Username)) 139 if err != nil { 140 return nil, err 141 } 142 return fu, nil 143 } 144 145 func TCreateFakeUser(tc libkb.TestContext) *FakeUser { 146 Logout(tc) 147 // Not randomPW because these user's can't logout without setting a PW, 148 // and we do a lot of "switching" (logout-login) between users in tests. 149 user, err := createAndSignupFakeUser(tc.Tp.DevelPrefix, tc.G, true /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */) 150 require.NoError(tc.T, err) 151 return user 152 } 153 154 func GetContactSettings(tc libkb.TestContext, u *FakeUser) (ret keybase1.ContactSettings) { 155 m := libkb.NewMetaContextForTest(tc) 156 ret, err := libkb.GetContactSettings(m) 157 require.NoError(tc.T, err) 158 tc.T.Logf("Got contact settings for user %s", u.Username) 159 return ret 160 } 161 162 func SetContactSettings(tc libkb.TestContext, u *FakeUser, arg keybase1.ContactSettings) { 163 m := libkb.NewMetaContextForTest(tc) 164 err := libkb.SetContactSettings(m, arg) 165 require.NoError(tc.T, err) 166 tc.T.Logf("Set contact settings for user %s", u.Username) 167 } 168 169 // copied from engine/common_test.go 170 func ResetAccount(tc libkb.TestContext, u *FakeUser) { 171 m := libkb.NewMetaContextForTest(tc) 172 err := libkb.ResetAccount(m, u.NormalizedUsername(), u.Passphrase) 173 require.NoError(tc.T, err) 174 tc.T.Logf("Account reset for user %s", u.Username) 175 Logout(tc) 176 } 177 178 func DeleteAccount(tc libkb.TestContext, u *FakeUser) { 179 m := libkb.NewMetaContextForTest(tc) 180 err := libkb.DeleteAccount(m, u.NormalizedUsername(), &u.Passphrase) 181 require.NoError(tc.T, err) 182 tc.T.Logf("Account deleted for user %s", u.Username) 183 Logout(tc) 184 } 185 186 // copied from engine/common_test.go 187 func Logout(tc libkb.TestContext) { 188 mctx := libkb.NewMetaContextForTest(tc) 189 if err := mctx.LogoutKillSecrets(); err != nil { 190 tc.T.Fatalf("logout error: %s", err) 191 } 192 } 193 194 // summarized from engine/revoke_test.go 195 func RotatePaper(tc libkb.TestContext, u *FakeUser) { 196 uis := libkb.UIs{ 197 LogUI: tc.G.UI.GetLogUI(), 198 LoginUI: &libkb.TestLoginUI{}, 199 SecretUI: &libkb.TestSecretUI{}, 200 } 201 eng := engine.NewPaperKey(tc.G) 202 m := libkb.NewMetaContextForTest(tc).WithUIs(uis) 203 err := engine.RunEngine2(m, eng) 204 require.NoError(tc.T, err) 205 206 arg := libkb.NewLoadUserByNameArg(tc.G, u.Username).WithPublicKeyOptional() 207 user, err := libkb.LoadUser(arg) 208 require.NoError(tc.T, err) 209 210 activeDevices := make([]*libkb.Device, 0) 211 for _, device := range user.GetComputedKeyFamily().GetAllDevices() { 212 if device.Status != nil && *device.Status == libkb.DeviceStatusActive { 213 activeDevices = append(activeDevices, device.Device) 214 } 215 } 216 217 var revokeDevice *libkb.Device 218 for _, device := range activeDevices { 219 if device.Type == keybase1.DeviceTypeV2_PAPER { 220 revokeDevice = device 221 } 222 } 223 require.NotNil(tc.T, revokeDevice, "no paper key found to revoke") 224 225 revokeEngine := engine.NewRevokeDeviceEngine(tc.G, engine.RevokeDeviceEngineArgs{ID: revokeDevice.ID}) 226 uis = libkb.UIs{ 227 LogUI: tc.G.UI.GetLogUI(), 228 SecretUI: u.NewSecretUI(), 229 } 230 m = libkb.NewMetaContextForTest(tc).WithUIs(uis) 231 err = engine.RunEngine2(m, revokeEngine) 232 require.NoError(tc.T, err) 233 } 234 235 func AssertProvisioned(tc libkb.TestContext) error { 236 if !tc.G.ActiveDevice.Valid() { 237 return libkb.LoginRequiredError{} 238 } 239 return nil 240 } 241 242 func FakeSalt() []byte { 243 return []byte("fakeSALTfakeSALT") 244 } 245 246 // Provision a new device (in context tcY) from the active (and logged in) device in test context tcX. 247 // This was adapted from engine/kex2_test.go 248 // Note that it uses Errorf in goroutines, so if it fails 249 // the test will not fail until later. 250 // `tcX` is a TestContext where device X (the provisioner) is already provisioned and logged in. 251 // this function will provision a new device Y inside `tcY` 252 // `newDeviceType` is keybase1.DeviceTypeV2_MOBILE or keybase1.DeviceTypeV2_DESKTOP. 253 func ProvisionNewDeviceKex(tcX *libkb.TestContext, tcY *libkb.TestContext, userX *FakeUser, newDeviceType keybase1.DeviceTypeV2) { 254 // tcX is the device X (provisioner) context: 255 // tcX should already have been logged in. 256 t := tcX.T 257 258 var secretX kex2.Secret 259 if _, err := rand.Read(secretX[:]); err != nil { 260 t.Fatal(err) 261 } 262 263 var secretY kex2.Secret 264 if _, err := rand.Read(secretY[:]); err != nil { 265 t.Fatal(err) 266 } 267 268 var wg sync.WaitGroup 269 270 // start provisionee 271 wg.Add(1) 272 go func() { 273 defer wg.Done() 274 err := (func() error { 275 uis := libkb.UIs{ 276 ProvisionUI: &TestProvisionUI{SecretCh: make(chan kex2.Secret, 1)}, 277 } 278 m := libkb.NewMetaContextForTest(*tcY).WithUIs(uis).WithNewProvisionalLoginContext() 279 deviceID, err := libkb.NewDeviceID() 280 if err != nil { 281 return err 282 } 283 suffix, err := libkb.RandBytes(5) 284 if err != nil { 285 return err 286 } 287 dname := fmt.Sprintf("device_%x", suffix) 288 device := &libkb.Device{ 289 ID: deviceID, 290 Description: &dname, 291 Type: newDeviceType, 292 } 293 provisionee := engine.NewKex2Provisionee(tcY.G, device, secretY, userX.GetUID(), FakeSalt()) 294 return engine.RunEngine2(m, provisionee) 295 })() 296 require.NoError(t, err, "kex2 provisionee") 297 }() 298 299 // start provisioner 300 wg.Add(1) 301 go func() { 302 defer wg.Done() 303 uis := libkb.UIs{ 304 SecretUI: userX.NewSecretUI(), 305 ProvisionUI: &TestProvisionUI{}, 306 } 307 provisioner := engine.NewKex2Provisioner(tcX.G, secretX, nil) 308 go provisioner.AddSecret(secretY) 309 m := libkb.NewMetaContextForTest(*tcX).WithUIs(uis) 310 if err := engine.RunEngine2(m, provisioner); err != nil { 311 require.NoErrorf(t, err, "provisioner error") 312 return 313 } 314 }() 315 316 wg.Wait() 317 } 318 319 type TestProvisionUI struct { 320 SecretCh chan kex2.Secret 321 DeviceType keybase1.DeviceTypeV2 322 } 323 324 func (u *TestProvisionUI) ChooseProvisioningMethod(_ context.Context, _ keybase1.ChooseProvisioningMethodArg) (keybase1.ProvisionMethod, error) { 325 panic("ChooseProvisioningMethod deprecated") 326 } 327 328 func (u *TestProvisionUI) ChooseGPGMethod(_ context.Context, _ keybase1.ChooseGPGMethodArg) (keybase1.GPGMethod, error) { 329 return keybase1.GPGMethod_GPG_NONE, nil 330 } 331 332 func (u *TestProvisionUI) SwitchToGPGSignOK(ctx context.Context, arg keybase1.SwitchToGPGSignOKArg) (bool, error) { 333 return true, nil 334 } 335 336 func (u *TestProvisionUI) ChooseDevice(_ context.Context, arg keybase1.ChooseDeviceArg) (keybase1.DeviceID, error) { 337 for _, d := range arg.Devices { 338 if d.Type == u.DeviceType { 339 return d.DeviceID, nil 340 } 341 } 342 return "", nil 343 } 344 345 func (u *TestProvisionUI) ChooseDeviceType(_ context.Context, _ keybase1.ChooseDeviceTypeArg) (keybase1.DeviceType, error) { 346 return keybase1.DeviceType_DESKTOP, nil 347 } 348 349 func (u *TestProvisionUI) DisplayAndPromptSecret(_ context.Context, arg keybase1.DisplayAndPromptSecretArg) (keybase1.SecretResponse, error) { 350 var ks kex2.Secret 351 copy(ks[:], arg.Secret) 352 u.SecretCh <- ks 353 var sr keybase1.SecretResponse 354 return sr, nil 355 } 356 357 func (u *TestProvisionUI) PromptNewDeviceName(_ context.Context, arg keybase1.PromptNewDeviceNameArg) (string, error) { 358 return libkb.RandString("device", 5) 359 } 360 361 func (u *TestProvisionUI) DisplaySecretExchanged(_ context.Context, _ int) error { 362 return nil 363 } 364 365 func (u *TestProvisionUI) ProvisioneeSuccess(_ context.Context, _ keybase1.ProvisioneeSuccessArg) error { 366 return nil 367 } 368 369 func (u *TestProvisionUI) ProvisionerSuccess(_ context.Context, _ keybase1.ProvisionerSuccessArg) error { 370 return nil 371 } 372 373 type TeamNotifyListener struct { 374 libkb.NoopNotifyListener 375 changeByIDCh chan keybase1.TeamChangedByIDArg 376 changeByNameCh chan keybase1.TeamChangedByNameArg 377 } 378 379 var _ libkb.NotifyListener = (*TeamNotifyListener)(nil) 380 381 func (n *TeamNotifyListener) TeamChangedByID(teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) { 382 n.changeByIDCh <- keybase1.TeamChangedByIDArg{ 383 TeamID: teamID, 384 LatestSeqno: latestSeqno, 385 ImplicitTeam: implicitTeam, 386 Changes: changes, 387 LatestHiddenSeqno: latestHiddenSeqno, 388 } 389 } 390 func (n *TeamNotifyListener) TeamChangedByName(teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) { 391 n.changeByNameCh <- keybase1.TeamChangedByNameArg{ 392 TeamName: teamName, 393 LatestSeqno: latestSeqno, 394 ImplicitTeam: implicitTeam, 395 Changes: changes, 396 LatestHiddenSeqno: latestHiddenSeqno, 397 } 398 } 399 400 func NewTeamNotifyListener() *TeamNotifyListener { 401 return &TeamNotifyListener{ 402 changeByIDCh: make(chan keybase1.TeamChangedByIDArg, 10), 403 changeByNameCh: make(chan keybase1.TeamChangedByNameArg, 10), 404 } 405 } 406 407 func CheckTeamMiscNotifications(tc libkb.TestContext, notifications *TeamNotifyListener) { 408 changeByID := false 409 changeByName := false 410 for { 411 select { 412 case arg := <-notifications.changeByIDCh: 413 changeByID = arg.Changes.Misc 414 case arg := <-notifications.changeByNameCh: 415 changeByName = arg.Changes.Misc 416 case <-time.After(500 * time.Millisecond * libkb.CITimeMultiplier(tc.G)): 417 tc.T.Fatal("no notification on teamSetSettings") 418 } 419 if changeByID && changeByName { 420 return 421 } 422 } 423 } 424 425 type fakeIdentifyUI struct { 426 *engine.LoopbackIdentifyUI 427 } 428 429 func (l *fakeIdentifyUI) Confirm(_ libkb.MetaContext, o *keybase1.IdentifyOutcome) (keybase1.ConfirmResult, error) { 430 return keybase1.ConfirmResult{IdentityConfirmed: true, RemoteConfirmed: true}, nil 431 } 432 433 func newFakeIdentifyUI(g *libkb.GlobalContext) *fakeIdentifyUI { 434 var tb *keybase1.IdentifyTrackBreaks 435 return &fakeIdentifyUI{ 436 engine.NewLoopbackIdentifyUI(g, &tb), 437 } 438 } 439 440 func RunTrack(tc libkb.TestContext, fu *FakeUser, username string) (them *libkb.User, err error) { 441 sv := keybase1.SigVersion(2) 442 return RunTrackWithOptions(tc, fu, username, keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI(), false) 443 } 444 445 func RunTrackWithOptions(tc libkb.TestContext, fu *FakeUser, username string, options keybase1.TrackOptions, secretUI libkb.SecretUI, forceRemoteCheck bool) (them *libkb.User, err error) { 446 idUI := newFakeIdentifyUI(tc.G) 447 448 arg := &engine.TrackEngineArg{ 449 UserAssertion: username, 450 Options: options, 451 ForceRemoteCheck: forceRemoteCheck, 452 } 453 uis := libkb.UIs{ 454 LogUI: tc.G.UI.GetLogUI(), 455 IdentifyUI: idUI, 456 SecretUI: secretUI, 457 } 458 eng := engine.NewTrackEngine(tc.G, arg) 459 m := libkb.NewMetaContextForTest(tc).WithUIs(uis) 460 err = engine.RunEngine2(m, eng) 461 them = eng.User() 462 return them, err 463 } 464 465 // GenerateTestPhoneNumber generates a random phone number in US with 555 area 466 // code. It passes serverside "strict phone number" checker test, and it's 467 // considered `possible`, but not `valid` by libphonenumber. 468 func GenerateTestPhoneNumber() string { 469 ret := make([]byte, 7) 470 _, err := rand.Read(ret) 471 if err != nil { 472 panic(err) 473 } 474 for i := range ret { 475 ret[i] = "0123456789"[int(ret[i])%10] 476 } 477 return fmt.Sprintf("1555%s", string(ret)) 478 } 479 480 func GenerateRandomEmailAddress() keybase1.EmailAddress { 481 buf := make([]byte, 5) 482 _, err := rand.Read(buf) 483 if err != nil { 484 panic(err) 485 } 486 email := fmt.Sprintf("%s@example.org", hex.EncodeToString(buf)) 487 return keybase1.EmailAddress(email) 488 } 489 490 type getCodeResponse struct { 491 libkb.AppStatusEmbed 492 VerificationCode string `json:"verification_code"` 493 } 494 495 func GetPhoneVerificationCode(mctx libkb.MetaContext, phoneNumber keybase1.PhoneNumber) (code string, err error) { 496 arg := libkb.APIArg{ 497 Endpoint: "test/phone_number_code", 498 SessionType: libkb.APISessionTypeREQUIRED, 499 Args: libkb.HTTPArgs{ 500 "phone_number": libkb.S{Val: phoneNumber.String()}, 501 }, 502 } 503 var resp getCodeResponse 504 err = mctx.G().API.GetDecode(mctx, arg, &resp) 505 if err != nil { 506 return "", err 507 } 508 return resp.VerificationCode, nil 509 } 510 511 func VerifyEmailAuto(mctx libkb.MetaContext, email keybase1.EmailAddress) error { 512 arg := libkb.APIArg{ 513 Endpoint: "test/verify_email_auto", 514 SessionType: libkb.APISessionTypeREQUIRED, 515 Args: libkb.HTTPArgs{ 516 "email": libkb.S{Val: string(email)}, 517 }, 518 } 519 _, err := mctx.G().API.Post(mctx, arg) 520 return err 521 } 522 523 func EditProfile(mctx libkb.MetaContext, arg keybase1.ProfileEditArg) error { 524 eng := engine.NewProfileEdit(mctx.G(), arg) 525 return engine.RunEngine2(mctx, eng) 526 } 527 528 func RunningInCI() bool { 529 x := os.Getenv("KEYBASE_RUN_CI") 530 return len(x) > 0 && x != "0" && x[0] != byte('n') 531 } 532 533 func SkipTestOnNonMasterCI(t *testing.T, reason string) { 534 if RunningInCI() && os.Getenv("BRANCH_NAME") != "master" { 535 t.Skipf("skip test on non-master CI run: %v", reason) 536 } 537 } 538 539 // CORE-10146 540 func SkipIconRemoteTest() bool { 541 return RunningInCI() 542 }