github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/random_pw_test.go (about)

     1  package service
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/keybase/client/go/engine"
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/keybase/client/go/kbtest"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func setPassphraseInTest(tc libkb.TestContext) error {
    17  	newPassphrase := "okokokok"
    18  	arg := &keybase1.PassphraseChangeArg{
    19  		Passphrase: newPassphrase,
    20  		Force:      true,
    21  	}
    22  	uis := libkb.UIs{
    23  		SecretUI: &libkb.TestSecretUI{},
    24  	}
    25  	eng := engine.NewPassphraseChange(tc.G, arg)
    26  	m := libkb.NewMetaContextForTest(tc).WithUIs(uis)
    27  	return engine.RunEngine2(m, eng)
    28  }
    29  
    30  func TestSignupRandomPWUser(t *testing.T) {
    31  	tc := libkb.SetupTest(t, "randompw", 3)
    32  	defer tc.Cleanup()
    33  
    34  	_, err := kbtest.CreateAndSignupFakeUserRandomPW("rpw", tc.G)
    35  	require.NoError(t, err)
    36  
    37  	userHandler := NewUserHandler(nil, tc.G, nil, nil)
    38  	ret, err := userHandler.LoadPassphraseState(context.Background(), 0)
    39  	require.NoError(t, err)
    40  	require.Equal(t, ret, keybase1.PassphraseState_RANDOM)
    41  
    42  	// Another call to test the caching
    43  	ret, err = userHandler.LoadPassphraseState(context.Background(), 0)
    44  	require.NoError(t, err)
    45  	require.Equal(t, ret, keybase1.PassphraseState_RANDOM)
    46  
    47  	// Another one with ForceRepoll
    48  	ret, err = userHandler.LoadPassphraseState(context.Background(), 0)
    49  	require.NoError(t, err)
    50  	require.Equal(t, ret, keybase1.PassphraseState_RANDOM)
    51  
    52  	ret2, err := userHandler.CanLogout(context.Background(), 0)
    53  	require.NoError(t, err)
    54  	require.False(t, ret2.CanLogout)
    55  	require.NotEmpty(t, ret2.Reason)
    56  
    57  	// Set passphrase
    58  	err = setPassphraseInTest(tc)
    59  	require.NoError(t, err)
    60  
    61  	ret2, err = userHandler.CanLogout(context.Background(), 0)
    62  	require.NoError(t, err)
    63  	require.True(t, ret2.CanLogout)
    64  	require.Empty(t, ret2.Reason)
    65  }
    66  
    67  type errorAPIMock struct {
    68  	*libkb.APIArgRecorder
    69  	realAPI       libkb.API
    70  	callCount     int
    71  	shouldTimeout bool
    72  }
    73  
    74  func (r *errorAPIMock) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error {
    75  	if arg.Endpoint == "user/has_random_pw" {
    76  		r.callCount++
    77  		if r.shouldTimeout {
    78  			return errors.New("timeout or something")
    79  		}
    80  	}
    81  	return r.realAPI.GetDecode(mctx, arg, w)
    82  }
    83  
    84  func (r *errorAPIMock) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) {
    85  	if arg.Endpoint == "user/has_random_pw" {
    86  		r.callCount++
    87  		if r.shouldTimeout {
    88  			return nil, errors.New("timeout or something")
    89  		}
    90  	}
    91  	return r.realAPI.Get(mctx, arg)
    92  }
    93  
    94  func TestCanLogoutTimeout(t *testing.T) {
    95  	tc := libkb.SetupTest(t, "randompw", 3)
    96  	defer tc.Cleanup()
    97  
    98  	_, err := kbtest.CreateAndSignupFakeUserRandomPW("rpw", tc.G)
    99  	require.NoError(t, err)
   100  
   101  	realAPI := tc.G.API
   102  	fakeAPI := &errorAPIMock{
   103  		realAPI:       realAPI,
   104  		shouldTimeout: true,
   105  	}
   106  	tc.G.API = fakeAPI
   107  
   108  	userHandler := NewUserHandler(nil, tc.G, nil, nil)
   109  
   110  	// It will fail with an error and Frontend would still send user
   111  	// to passphrase screen.
   112  	ret2, err := userHandler.CanLogout(context.Background(), 0)
   113  	require.NoError(t, err)
   114  	require.False(t, ret2.CanLogout)
   115  	require.Contains(t, ret2.Reason, "We couldn't ensure that your account has a passphrase")
   116  	require.Equal(t, 1, fakeAPI.callCount)
   117  
   118  	// Switch off the timeouting for one call
   119  	fakeAPI.shouldTimeout = false
   120  
   121  	// Call this again, should still fail, but after making actual call to API.
   122  	ret2, err = userHandler.CanLogout(context.Background(), 0)
   123  	require.NoError(t, err)
   124  	require.False(t, ret2.CanLogout)
   125  	require.Contains(t, ret2.Reason, "set a password first")
   126  	require.Equal(t, 2, fakeAPI.callCount)
   127  
   128  	// Back to "offline" state.
   129  	fakeAPI.shouldTimeout = true
   130  
   131  	// Now it should try to do an API call, fail to do so, but get
   132  	// the cached value and derive that the passphrase is not set.
   133  	ret2, err = userHandler.CanLogout(context.Background(), 0)
   134  	require.NoError(t, err)
   135  	require.False(t, ret2.CanLogout)
   136  	// Since we weren't able to do an API call and prefetch didn't work, we
   137  	// aren't sure about the state of the passphrase.
   138  	require.Contains(t, ret2.Reason, "couldn't ensure")
   139  	require.Equal(t, 3, fakeAPI.callCount)
   140  
   141  	// Back to real API because we are going to change passphrase.
   142  	tc.G.API = realAPI
   143  
   144  	err = setPassphraseInTest(tc)
   145  	require.NoError(t, err)
   146  
   147  	// Call this again with real API, we should be clear to logout
   148  	// and the value should be cached.
   149  	ret2, err = userHandler.CanLogout(context.Background(), 0)
   150  	require.NoError(t, err)
   151  	require.True(t, ret2.CanLogout)
   152  
   153  	// Switch to fake API, but on-line, reset call count.
   154  	tc.G.API = fakeAPI
   155  	fakeAPI.shouldTimeout = false
   156  	fakeAPI.callCount = 0
   157  
   158  	// Next try should not call API at all, just use the cached value.
   159  	ret2, err = userHandler.CanLogout(context.Background(), 0)
   160  	require.NoError(t, err)
   161  	require.True(t, ret2.CanLogout)
   162  	require.Equal(t, 0, fakeAPI.callCount)
   163  
   164  	// Same with timeouting API.
   165  	fakeAPI.shouldTimeout = true
   166  
   167  	ret2, err = userHandler.CanLogout(context.Background(), 0)
   168  	require.NoError(t, err)
   169  	require.True(t, ret2.CanLogout)
   170  	require.Equal(t, 0, fakeAPI.callCount) // still 0 calls.
   171  
   172  	// Since it's now KNOWN, ForceRepoll has no effect.
   173  	_, err = userHandler.LoadPassphraseState(context.Background(), 0)
   174  	require.NoError(t, err)
   175  	require.Equal(t, 0, fakeAPI.callCount)
   176  }
   177  
   178  func TestCanLogoutWhenRevoked(t *testing.T) {
   179  	tc := libkb.SetupTest(t, "randompw", 3)
   180  	defer tc.Cleanup()
   181  
   182  	user, err := kbtest.CreateAndSignupFakeUserRandomPW("rpw", tc.G)
   183  	require.NoError(t, err)
   184  
   185  	userHandler := NewUserHandler(nil, tc.G, nil, nil)
   186  	ret, err := userHandler.CanLogout(context.Background(), 0)
   187  	require.NoError(t, err)
   188  	require.False(t, ret.CanLogout)
   189  
   190  	// Provision second device
   191  	tc2 := libkb.SetupTest(t, "randompw2", 3)
   192  	defer tc2.Cleanup()
   193  	kbtest.ProvisionNewDeviceKex(&tc, &tc2, user, keybase1.DeviceTypeV2_DESKTOP)
   194  
   195  	// Should still see "can't logout" on second device (also populate
   196  	// HasRandomPW cache).
   197  	userHandler2 := NewUserHandler(nil, tc2.G, nil, nil)
   198  	ret, err = userHandler2.CanLogout(context.Background(), 0)
   199  	require.NoError(t, err)
   200  	require.False(t, ret.CanLogout)
   201  
   202  	// Revoke device 2
   203  	revokeEng := engine.NewRevokeDeviceEngine(tc.G, engine.RevokeDeviceEngineArgs{
   204  		ID: tc2.G.ActiveDevice.DeviceID(),
   205  	})
   206  	uis := libkb.UIs{
   207  		SecretUI: &libkb.TestSecretUI{},
   208  		LogUI:    tc.G.UI.GetLogUI(),
   209  	}
   210  	m := libkb.NewMetaContextForTest(tc).WithUIs(uis)
   211  	err = engine.RunEngine2(m, revokeEng)
   212  	require.NoError(t, err)
   213  
   214  	// Try CanLogout from device 2. Should detect that we are revoked and let
   215  	// us log out.
   216  	ret, err = userHandler2.CanLogout(context.Background(), 0)
   217  	require.NoError(t, err)
   218  	require.True(t, ret.CanLogout)
   219  
   220  	// Device 1 still can't logout.
   221  	ret, err = userHandler.CanLogout(context.Background(), 0)
   222  	require.NoError(t, err)
   223  	require.False(t, ret.CanLogout)
   224  }