github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }