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 &notifyHandler{
   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  }