github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/user_test.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 systests 5 6 import ( 7 "crypto/rand" 8 "encoding/hex" 9 "fmt" 10 "io" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/require" 15 16 "github.com/keybase/client/go/client" 17 "github.com/keybase/client/go/libkb" 18 keybase1 "github.com/keybase/client/go/protocol/keybase1" 19 "github.com/keybase/client/go/service" 20 "github.com/keybase/go-framed-msgpack-rpc/rpc" 21 context "golang.org/x/net/context" 22 ) 23 24 type signupInfo struct { 25 username string 26 email string 27 passphrase string 28 displayedPaperKey string 29 } 30 31 type teamsUI struct { 32 baseNullUI 33 libkb.Contextified 34 } 35 36 func (t teamsUI) ConfirmRootTeamDelete(context.Context, keybase1.ConfirmRootTeamDeleteArg) (bool, error) { 37 return true, nil 38 } 39 40 func (t teamsUI) ConfirmSubteamDelete(context.Context, keybase1.ConfirmSubteamDeleteArg) (bool, error) { 41 return true, nil 42 } 43 44 func (t teamsUI) ConfirmInviteLinkAccept(context.Context, keybase1.ConfirmInviteLinkAcceptArg) (bool, error) { 45 return true, nil 46 } 47 48 type signupUI struct { 49 baseNullUI 50 info *signupInfo 51 libkb.Contextified 52 53 passphrasePrompts []keybase1.GUIEntryArg 54 terminalPrompts []libkb.PromptDescriptor 55 } 56 57 type signupTerminalUI struct { 58 info *signupInfo 59 libkb.Contextified 60 parent *signupUI 61 } 62 63 type signupSecretUI struct { 64 info *signupInfo 65 libkb.Contextified 66 parent *signupUI 67 } 68 69 func (n *signupUI) GetTerminalUI() libkb.TerminalUI { 70 return &signupTerminalUI{ 71 info: n.info, 72 Contextified: libkb.NewContextified(n.G()), 73 parent: n, 74 } 75 } 76 77 func (n *signupUI) GetSecretUI() libkb.SecretUI { 78 return &signupSecretUI{ 79 info: n.info, 80 Contextified: libkb.NewContextified(n.G()), 81 parent: n, 82 } 83 } 84 85 func (n *signupUI) GetLoginUI() libkb.LoginUI { 86 return client.NewLoginUI(n.GetTerminalUI(), false) 87 } 88 89 func (n *signupUI) GetGPGUI() libkb.GPGUI { 90 return client.NewGPGUI(n.G(), n.GetTerminalUI(), false, "") 91 } 92 93 func (n *signupSecretUI) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (res keybase1.GetPassphraseRes, err error) { 94 if p.Type == keybase1.PassphraseType_PAPER_KEY { 95 res.Passphrase = n.info.displayedPaperKey 96 } else { 97 res.Passphrase = n.info.passphrase 98 } 99 n.G().Log.Debug("| GetPassphrase: %v -> %v", p, res) 100 n.parent.passphrasePrompts = append(n.parent.passphrasePrompts, p) 101 if len(n.parent.passphrasePrompts) > 100 { 102 err = fmt.Errorf("too many passphrase prompts, something is likely wrong") 103 } 104 return res, err 105 } 106 107 func (n *signupTerminalUI) Prompt(pd libkb.PromptDescriptor, s string) (ret string, err error) { 108 switch pd { 109 case client.PromptDescriptorSignupUsername: 110 ret = n.info.username 111 case client.PromptDescriptorSignupEmail: 112 ret = n.info.email 113 case client.PromptDescriptorSignupDevice: 114 ret = "work computer" 115 case client.PromptDescriptorSignupCode: 116 ret = "202020202020202020202020" 117 default: 118 err = fmt.Errorf("unknown prompt %v", pd) 119 } 120 n.G().Log.Debug("Terminal Prompt %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err)) 121 n.parent.terminalPrompts = append(n.parent.terminalPrompts, pd) 122 if len(n.parent.terminalPrompts) > 100 { 123 err = fmt.Errorf("too many prompts, something is likely wrong") 124 } 125 return ret, err 126 } 127 128 func (n *signupTerminalUI) PromptPassword(pd libkb.PromptDescriptor, _ string) (string, error) { 129 return "", nil 130 } 131 func (n *signupTerminalUI) PromptPasswordMaybeScripted(pd libkb.PromptDescriptor, _ string) (string, error) { 132 return "", nil 133 } 134 func (n *signupTerminalUI) Output(s string) error { 135 n.G().Log.Debug("Terminal Output: %s", s) 136 return nil 137 } 138 func (n *signupTerminalUI) OutputDesc(od libkb.OutputDescriptor, s string) error { 139 if od == client.OutputDescriptorPrimaryPaperKey { 140 n.info.displayedPaperKey = s 141 } 142 n.G().Log.Debug("Terminal Output %d: %s", od, s) 143 return nil 144 } 145 func (n *signupTerminalUI) Printf(f string, args ...interface{}) (int, error) { 146 s := fmt.Sprintf(f, args...) 147 n.G().Log.Debug("Terminal Printf: %s", s) 148 return len(s), nil 149 } 150 func (n *signupTerminalUI) PrintfUnescaped(f string, args ...interface{}) (int, error) { 151 s := fmt.Sprintf(f, args...) 152 n.G().Log.Debug("Terminal PrintfUnescaped: %s", s) 153 return len(s), nil 154 } 155 156 func (n *signupTerminalUI) Write(b []byte) (int, error) { 157 n.G().Log.Debug("Terminal write: %s", string(b)) 158 return len(b), nil 159 } 160 161 func (n *signupTerminalUI) OutputWriter() io.Writer { 162 return n 163 } 164 func (n *signupTerminalUI) UnescapedOutputWriter() io.Writer { 165 return n 166 } 167 func (n *signupTerminalUI) ErrorWriter() io.Writer { 168 return n 169 } 170 171 func (n *signupTerminalUI) PromptYesNo(pd libkb.PromptDescriptor, s string, def libkb.PromptDefault) (ret bool, err error) { 172 switch pd { 173 case client.PromptDescriptorLoginWritePaper: 174 ret = true 175 case client.PromptDescriptorLoginWallet: 176 ret = true 177 case client.PromptDescriptorGPGOKToAdd: 178 ret = false 179 default: 180 err = fmt.Errorf("unknown prompt %v", pd) 181 } 182 n.G().Log.Debug("Terminal PromptYesNo %d: %s -> %s (%v)\n", pd, s, ret, libkb.ErrToOk(err)) 183 return ret, err 184 } 185 186 func (n *signupTerminalUI) PromptForConfirmation(prompt string) error { 187 return nil 188 } 189 190 func (n *signupTerminalUI) Tablify(headings []string, rowfunc func() []string) { 191 libkb.Tablify(n.OutputWriter(), headings, rowfunc) 192 } 193 194 func (n *signupTerminalUI) TerminalSize() (width int, height int) { 195 return 80, 24 196 } 197 198 func randomUser(prefix string) *signupInfo { 199 b := make([]byte, 5) 200 _, err := rand.Read(b) 201 if err != nil { 202 panic(err) 203 } 204 sffx := hex.EncodeToString(b) 205 username := fmt.Sprintf("%s_%s", prefix, sffx) 206 return &signupInfo{ 207 username: username, 208 email: username + "@noemail.keybase.io", 209 passphrase: sffx + sffx, 210 } 211 } 212 213 func randomDevice() string { 214 b := make([]byte, 5) 215 _, err := rand.Read(b) 216 if err != nil { 217 panic(err) 218 } 219 sffx := hex.EncodeToString(b) 220 return fmt.Sprintf("d_%s", sffx) 221 } 222 223 type notifyHandler struct { 224 logoutCh chan struct{} 225 loginCh chan keybase1.LoggedInArg 226 outOfDateCh chan keybase1.ClientOutOfDateArg 227 userCh chan keybase1.UID 228 errCh chan error 229 startCh chan bool 230 } 231 232 func newNotifyHandler() *notifyHandler { 233 return ¬ifyHandler{ 234 logoutCh: make(chan struct{}), 235 loginCh: make(chan keybase1.LoggedInArg), 236 outOfDateCh: make(chan keybase1.ClientOutOfDateArg), 237 userCh: make(chan keybase1.UID), 238 errCh: make(chan error), 239 startCh: make(chan bool), 240 } 241 } 242 243 func (h *notifyHandler) LoggedOut(_ context.Context) error { 244 h.logoutCh <- struct{}{} 245 return nil 246 } 247 248 func (h *notifyHandler) LoggedIn(_ context.Context, arg keybase1.LoggedInArg) error { 249 h.loginCh <- arg 250 return nil 251 } 252 253 func (h *notifyHandler) ClientOutOfDate(_ context.Context, arg keybase1.ClientOutOfDateArg) error { 254 h.outOfDateCh <- arg 255 return nil 256 } 257 258 func (h *notifyHandler) UserChanged(_ context.Context, uid keybase1.UID) error { 259 h.userCh <- uid 260 return nil 261 } 262 263 func (h *notifyHandler) PasswordChanged(_ context.Context, _ keybase1.PassphraseState) error { 264 return nil 265 } 266 267 func (h *notifyHandler) IdentifyUpdate(_ context.Context, _ keybase1.IdentifyUpdateArg) error { 268 return nil 269 } 270 271 func (h *notifyHandler) WebOfTrustChanged(_ context.Context, username string) error { 272 return nil 273 } 274 275 func TestSignupLogout(t *testing.T) { 276 tc := setupTest(t, "signup") 277 defer tc.Cleanup() 278 tc2 := cloneContext(tc) 279 defer tc2.Cleanup() 280 tc5 := cloneContext(tc) 281 defer tc5.Cleanup() 282 283 // Hack the various portions of the service that aren't 284 // properly contextified. 285 286 stopCh := make(chan error) 287 svc := service.NewService(tc.G, false) 288 startCh := svc.GetStartChannel() 289 go func() { 290 err := svc.Run() 291 if err != nil { 292 t.Logf("Running the service produced an error: %v", err) 293 } 294 stopCh <- err 295 }() 296 297 userInfo := randomUser("sgnup") 298 299 sui := signupUI{ 300 info: userInfo, 301 Contextified: libkb.NewContextified(tc2.G), 302 } 303 tc2.G.SetUI(&sui) 304 signup := client.NewCmdSignupRunner(tc2.G) 305 signup.SetTest() 306 signup.SetNoInvitationCodeBypass() 307 308 logout := client.NewCmdLogoutRunner(tc2.G) 309 310 tc.G.Log.Debug("sleeping on server startCh") 311 <-startCh 312 tc.G.Log.Debug("waking on server startCh") 313 314 nh := newNotifyHandler() 315 316 // Launch the server that will listen for notifications on updates, such as logout 317 launchServer := func(nh *notifyHandler) error { 318 cli, xp, err := client.GetRPCClientWithContext(tc5.G) 319 if err != nil { 320 return err 321 } 322 srv := rpc.NewServer(xp, nil) 323 if err = srv.Register(keybase1.NotifySessionProtocol(nh)); err != nil { 324 return err 325 } 326 if err = srv.Register(keybase1.NotifyUsersProtocol(nh)); err != nil { 327 return err 328 } 329 ncli := keybase1.NotifyCtlClient{Cli: cli} 330 return ncli.SetNotifications(context.TODO(), keybase1.NotificationChannels{ 331 Session: true, 332 Users: true, 333 }) 334 } 335 336 // Actually launch it in the background 337 go func() { 338 err := launchServer(nh) 339 if err != nil { 340 nh.errCh <- err 341 } else { 342 nh.startCh <- true 343 } 344 }() 345 346 select { 347 case <-nh.startCh: 348 t.Logf("notify handler server started") 349 case err := <-nh.errCh: 350 t.Fatalf("Error starting notify handler server: %v", err) 351 case <-time.After(20 * time.Second): 352 t.Fatalf("timed out waiting for notify handler server to start") 353 } 354 355 if err := signup.Run(); err != nil { 356 t.Fatal(err) 357 } 358 select { 359 case err := <-nh.errCh: 360 t.Fatalf("Error before notify: %v", err) 361 case u := <-nh.loginCh: 362 if u.Username != userInfo.username { 363 t.Fatalf("bad username in login notification: %q != %q", u.Username, userInfo.username) 364 } 365 tc.G.Log.Debug("Got notification of login for %q", u.Username) 366 } 367 368 require.Len(t, sui.passphrasePrompts, 2) 369 370 expectedPrompts := []libkb.PromptDescriptor{ 371 client.PromptDescriptorSignupEmail, 372 client.PromptDescriptorSignupCode, 373 client.PromptDescriptorSignupUsername, 374 client.PromptDescriptorSignupDevice, 375 } 376 require.Equal(t, expectedPrompts, sui.terminalPrompts) 377 378 // signup calls logout, so clear that from the notification channel 379 select { 380 case <-nh.logoutCh: 381 case <-time.After(20 * time.Second): 382 t.Fatal("timed out waiting for signup's logout notification") 383 } 384 385 btc := client.NewCmdCurrencyAddRunner(tc2.G) 386 btc.SetAddress("1HUCBSJeHnkhzrVKVjaVmWg2QtZS1mdfaz") 387 if err := btc.Run(); err != nil { 388 t.Fatal(err) 389 } 390 391 // Now let's be sure that we get a notification back as we expect. 392 select { 393 case err := <-nh.errCh: 394 t.Fatalf("Error before notify: %v", err) 395 case uid := <-nh.userCh: 396 tc.G.Log.Debug("Got notification from user changed handled (%s)", uid) 397 if e := libkb.CheckUIDAgainstUsername(uid, userInfo.username); e != nil { 398 t.Fatalf("Bad UID back: %s != %s (%s)", uid, userInfo.username, e) 399 } 400 } 401 402 // Fire a logout 403 if err := logout.Run(); err != nil { 404 t.Fatal(err) 405 } 406 407 // Now let's be sure that we get a notification back as we expect. 408 select { 409 case err := <-nh.errCh: 410 t.Fatalf("Error before notify: %v", err) 411 case <-nh.logoutCh: 412 tc.G.Log.Debug("Got notification from logout handler") 413 } 414 415 tc.G.Log.Debug("Waiting for tc2 Ctl service stop") 416 417 if err := CtlStop(tc2.G); err != nil { 418 t.Fatal(err) 419 } 420 421 tc.G.Log.Debug("Waiting for msg on stopCh") 422 423 // If the server failed, it's also an error 424 if err := <-stopCh; err != nil { 425 t.Fatal(err) 426 } 427 428 tc.G.Log.Debug("Waiting for msg on logoutCh") 429 430 // Check that we only get one notification, not two 431 select { 432 case _, ok := <-nh.logoutCh: 433 if ok { 434 t.Fatal("Received an extra logout notification!") 435 } 436 default: 437 } 438 439 } 440 441 // Try to elicit a race between Logout and Shutdown. 442 func TestLogoutMulti(t *testing.T) { 443 tt := newTeamTester(t) 444 defer tt.cleanup() 445 user1 := tt.addUser("one") 446 go func() { _ = user1.tc.Logout() }() 447 go func() { _ = user1.tc.Logout() }() 448 go func() { _ = user1.tc.Logout() }() 449 go func() { _ = user1.tc.Logout() }() 450 go func() { _ = user1.tc.Logout() }() 451 go func() { _ = user1.tc.Logout() }() 452 err := user1.tc.Logout() 453 require.NoError(t, err) 454 } 455 456 func TestNoPasswordCliSignup(t *testing.T) { 457 ctx := newSMUContext(t) 458 defer ctx.cleanup() 459 460 user := ctx.installKeybaseForUser("signup", 10) 461 462 userInfo := randomUser("sgnp") 463 user.userInfo = userInfo 464 465 dw := user.primaryDevice() 466 tctx := dw.popClone() 467 468 G := tctx.G 469 sui := signupUI{ 470 info: userInfo, 471 Contextified: libkb.NewContextified(G), 472 } 473 G.SetUI(&sui) 474 signup := client.NewCmdSignupRunner(G) 475 signup.SetTest() 476 signup.SetNoInvitationCodeBypass() 477 // Give us nopw signup without password prompt. 478 signup.SetNoPassphrasePrompt() 479 480 t.Logf("Running singup now") 481 err := signup.Run() 482 require.NoError(t, err) 483 t.Logf("after signup") 484 485 // Still same prompts for e-mail, username, and device name, but no 486 // password prompt. 487 require.Len(t, sui.passphrasePrompts, 0) 488 expectedPrompts := []libkb.PromptDescriptor{ 489 client.PromptDescriptorSignupEmail, 490 client.PromptDescriptorSignupCode, 491 client.PromptDescriptorSignupUsername, 492 client.PromptDescriptorSignupDevice, 493 } 494 require.Equal(t, expectedPrompts, sui.terminalPrompts) 495 496 ucli := keybase1.UserClient{Cli: user.primaryDevice().rpcClient()} 497 res, err := ucli.LoadPassphraseState(context.Background(), 0) 498 require.NoError(t, err) 499 require.Equal(t, res, keybase1.PassphraseState_RANDOM) 500 501 err = G.ConfigureConfig() 502 require.NoError(t, err) 503 logout := client.NewCmdLogoutRunner(G) 504 err = logout.Run() 505 require.Error(t, err) 506 require.Contains(t, err.Error(), "Cannot log out") 507 508 logout = client.NewCmdLogoutRunner(G) 509 logout.Force = true 510 err = logout.Run() 511 require.NoError(t, err) 512 }