1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"github.com/keybase/client/go/libkb"
8	"github.com/keybase/client/go/protocol/keybase1"
9)
10
11// LoginProvisionedDevice is an engine that tries to login using the
12// current device, if there is an existing provisioned device.
13type LoginProvisionedDevice struct {
14	libkb.Contextified
15	username        libkb.NormalizedUsername
16	uid             keybase1.UID
17	deviceID        keybase1.DeviceID
18	SecretStoreOnly bool // this should only be set by the service on its startup login attempt
19}
20
21// newLoginCurrentDevice creates a loginProvisionedDevice engine.
22func NewLoginProvisionedDevice(g *libkb.GlobalContext, username string) *LoginProvisionedDevice {
23	return &LoginProvisionedDevice{
24		username:     libkb.NewNormalizedUsername(username),
25		Contextified: libkb.NewContextified(g),
26	}
27}
28
29// Name is the unique engine name.
30func (e *LoginProvisionedDevice) Name() string {
31	return "LoginProvisionedDevice"
32}
33
34// GetPrereqs returns the engine prereqs.
35func (e *LoginProvisionedDevice) Prereqs() Prereqs {
36	return Prereqs{}
37}
38
39// RequiredUIs returns the required UIs.
40func (e *LoginProvisionedDevice) RequiredUIs() []libkb.UIKind {
41	if e.SecretStoreOnly {
42		return []libkb.UIKind{}
43	}
44
45	return []libkb.UIKind{
46		libkb.LoginUIKind,
47		libkb.SecretUIKind,
48	}
49}
50
51// SubConsumers returns the other UI consumers for this engine.
52func (e *LoginProvisionedDevice) SubConsumers() []libkb.UIConsumer {
53	return []libkb.UIConsumer{}
54}
55
56func (e *LoginProvisionedDevice) Run(m libkb.MetaContext) error {
57	if err := e.run(m); err != nil {
58		return err
59	}
60
61	m.Debug("LoginProvisionedDevice success, sending login notification")
62	m.G().NotifyRouter.HandleLogin(m.Ctx(), e.username.String())
63	m.Debug("LoginProvisionedDevice success, calling login hooks")
64	m.G().CallLoginHooks(m)
65
66	return nil
67}
68
69func (e *LoginProvisionedDevice) loadMe(m libkb.MetaContext) (err error) {
70	defer m.Trace("LoginProvisionedDevice#loadMe", &err)()
71
72	var config *libkb.UserConfig
73	var nu libkb.NormalizedUsername
74	loadUserArg := libkb.NewLoadUserArgWithMetaContext(m).
75		WithPublicKeyOptional().WithForcePoll(true).WithStaleOK(true)
76	if len(e.username) == 0 {
77		m.Debug("| using current username")
78		config, err = m.G().Env.GetConfig().GetUserConfig()
79		if config == nil {
80			m.Debug("user config is nil")
81			return errNoConfig
82		}
83		loadUserArg = loadUserArg.WithSelf(true).WithUID(config.GetUID())
84	} else {
85		m.Debug("| using new username %s", e.username)
86		nu = e.username
87		config, err = m.G().Env.GetConfig().GetUserConfigForUsername(nu)
88		loadUserArg = loadUserArg.WithName(e.username.String())
89		if config == nil {
90			m.Debug("user config is nil for %s", e.username)
91			return errNoConfig
92		}
93	}
94	if err != nil {
95		m.Debug("error getting user config: %s (%T)", err, err)
96		return errNoConfig
97	}
98	deviceID := config.GetDeviceID()
99	if deviceID.IsNil() {
100		m.Debug("no device in user config")
101		return errNoDevice
102	}
103
104	// Make sure the device ID is still valid.
105	upak, _, err := m.G().GetUPAKLoader().LoadV2(loadUserArg)
106	if err != nil {
107		m.Debug("error loading user profile: %#v", err)
108		return err
109	}
110	if upak.Current.Status == keybase1.StatusCode_SCDeleted {
111		m.Debug("User %s was deleted", upak.Current.Uid)
112		return libkb.UserDeletedError{}
113	}
114
115	nu = libkb.NewNormalizedUsername(upak.Current.Username)
116	device := upak.Current.FindSigningDeviceKey(deviceID)
117
118	nukeDevice := false
119	if device == nil {
120		m.Debug("Current device %s not found", deviceID)
121		nukeDevice = true
122	} else if device.Base.Revocation != nil {
123		m.Debug("Current device %s has been revoked", deviceID)
124		nukeDevice = true
125	}
126
127	if nukeDevice {
128		// If our config file is showing that we have a bogus
129		// deviceID (maybe from our account before an account reset),
130		// then we'll delete it from the config file here, so later parts
131		// of provisioning aren't confused by this device ID.
132		tmp := m.SwitchUserNukeConfig(nu)
133		if tmp != nil {
134			m.Warning("Error clearing user config: %s", tmp)
135		}
136		return errNoDevice
137	}
138
139	e.username = nu
140	e.deviceID = deviceID
141	e.uid = upak.Current.Uid
142	return nil
143}
144
145func (e *LoginProvisionedDevice) reattemptUnlockIfDifferentUID(m libkb.MetaContext, loggedInUID keybase1.UID) (success bool, err error) {
146	defer m.Trace("LoginProvisionedDevice#reattemptUnlockIfDifferentUID", &err)()
147	if loggedInUID.Equal(e.uid) {
148		m.Debug("no reattempting unlock; already tried for same UID")
149		return false, nil
150	}
151	return e.reattemptUnlock(m)
152}
153
154// reattemptUnlock reattempts to unlock the device's device keys. We already tried implicitly
155// early on in the run() function via `isLoggedIn`, which calls `Bootstrap...`. We try the whole
156// shebang again twice more: once after switching users (if there is indeeed a switch). And again
157// after asking the user for a passphrase login.
158func (e *LoginProvisionedDevice) reattemptUnlock(m libkb.MetaContext) (success bool, err error) {
159	defer m.Trace("LoginProvisionedDevice#reattemptUnlock", &err)()
160	ad, err := libkb.LoadProvisionalActiveDevice(m, e.uid, e.deviceID, true)
161	if err != nil {
162		m.Debug("Failed to load provisional device for user, but swallowing error: %s", err.Error())
163		return false, nil
164	}
165	if ad == nil {
166		m.Debug("Unexpected nil active device from LoadProvisionalActiveDevice without error")
167		return false, nil
168	}
169	err = m.SwitchUserToActiveDevice(e.username, ad)
170	if err != nil {
171		m.Debug("Error switching to new active device: %s", err.Error())
172		return false, err
173	}
174	return true, nil
175}
176
177// tryPassphraseLogin tries a username/passphrase login to the server, and makes a global
178// side effect: to store the user's full LKSec secret into the secret store. After which point,
179// usual attempts to run LoadProvisionalActiveDevice or BootstrapActiveDevice will succeed
180// without a prompt.
181func (e *LoginProvisionedDevice) tryPassphraseLogin(m libkb.MetaContext) (err error) {
182	defer m.Trace("LoginProvisionedDevice#tryPassphraseLogin", &err)()
183	err = libkb.PassphraseLoginPrompt(m, e.username.String(), 3)
184	if err != nil {
185		return err
186	}
187
188	options := libkb.LoadAdvisorySecretStoreOptionsFromRemote(m)
189	// A failure here is just a warning, since we still can use the app for this
190	// session. But it will undoubtedly cause pain.
191	w := libkb.StoreSecretAfterLoginWithOptions(m, e.username, e.uid, e.deviceID, &options)
192	if w != nil {
193		m.Warning("Secret store failed: %s", w.Error())
194	}
195
196	return nil
197}
198
199func (e *LoginProvisionedDevice) runBug3964Repairman(m libkb.MetaContext) (err error) {
200	defer m.Trace("LoginProvisionedDevice#runBug3964Repairman", &err)()
201	return libkb.RunBug3964Repairman(m)
202}
203
204func (e *LoginProvisionedDevice) passiveLoginWithUsername(m libkb.MetaContext) (ok bool, uid keybase1.UID) {
205
206	m.Debug("LoginProvisionedDevice#passiveLoginWithUsername %s", e.username)
207
208	cr := m.G().Env.GetConfig()
209	if cr == nil {
210		m.Debug("no config file reader")
211		return false, uid
212	}
213	uid = cr.GetUIDForUsername(e.username)
214	if uid.IsNil() {
215		m.Debug("No UID found locally for username %s", e.username)
216		return false, uid
217	}
218	if isLoggedInAs(m, uid) {
219		return true, uid
220	}
221	return false, keybase1.UID("")
222}
223
224func (e *LoginProvisionedDevice) passiveLogin(m libkb.MetaContext) (ok bool, uid keybase1.UID) {
225	defer m.Trace("LoginProvisionedDevice#passiveLogin", nil)()
226	if len(e.username) > 0 {
227		return e.passiveLoginWithUsername(m)
228	}
229	return isLoggedIn(m)
230}
231
232func (e *LoginProvisionedDevice) run(m libkb.MetaContext) (err error) {
233	defer m.Trace("LoginProvisionedDevice#run", &err)()
234
235	in, loggedInUID := e.passiveLogin(m)
236
237	if in {
238		m.Debug("user %s already logged in; short-circuting", loggedInUID)
239		return nil
240	}
241
242	err = e.loadMe(m)
243	if err != nil {
244		return err
245	}
246
247	var success bool
248	success, err = e.reattemptUnlockIfDifferentUID(m, loggedInUID)
249	if err != nil {
250		return err
251	}
252	if success {
253		return nil
254	}
255
256	if e.SecretStoreOnly {
257		return libkb.NewLoginRequiredError("explicit login is required")
258	}
259
260	e.connectivityWarning(m)
261
262	m = m.WithNewProvisionalLoginContext()
263	err = e.tryPassphraseLogin(m)
264	if err != nil {
265		return err
266	}
267
268	err = e.runBug3964Repairman(m)
269	if err != nil {
270		m.Debug("couldn't run bug 3964 repairman: %+v", err)
271	}
272
273	success, err = e.reattemptUnlock(m)
274	if err != nil {
275		return err
276	}
277	if !success {
278		return libkb.NewLoginRequiredError("login failed after passphrase verified")
279	}
280
281	return nil
282}
283
284func (e *LoginProvisionedDevice) connectivityWarning(m libkb.MetaContext) {
285	// CORE-5876 idea that lksec will be unusable if reachability state is NO
286	// and the user changed passphrase with a different device since it won't
287	// be able to sync the new server half.
288	if m.G().ConnectivityMonitor.IsConnected(m.Ctx()) != libkb.ConnectivityMonitorYes {
289		m.Debug("LoginProvisionedDevice: in unlockDeviceKeys, ConnectivityMonitor says not reachable, check to make sure")
290		if err := m.G().ConnectivityMonitor.CheckReachability(m.Ctx()); err != nil {
291			m.Debug("error checking reachability: %s", err)
292		} else {
293			connected := m.G().ConnectivityMonitor.IsConnected(m.Ctx())
294			m.Debug("after CheckReachability(), IsConnected() => %v (connected? %v)", connected, connected == libkb.ConnectivityMonitorYes)
295		}
296	}
297}
298
299// Returns the username that the user typed during the engine's execution
300func (e *LoginProvisionedDevice) GetUsername() libkb.NormalizedUsername {
301	return e.username
302}
303