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	"fmt"
8	"runtime/debug"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12)
13
14type Prereqs = libkb.EnginePrereqs
15type Engine2 = libkb.Engine2
16
17type UIDelegateWanter interface {
18	WantDelegate(libkb.UIKind) bool
19}
20
21func requiresUI(c libkb.UIConsumer, kind libkb.UIKind) bool {
22	for _, ui := range c.RequiredUIs() {
23		if ui == kind {
24			return true
25		}
26	}
27	for _, sub := range c.SubConsumers() {
28		if requiresUI(sub, kind) {
29			return true
30		}
31	}
32	return false
33}
34
35// isLoggedInWithUIDAndError conveys if the user is in a logged-in state or not.
36// If this function returns `true`, it's because the user is logged in,
37// is on a provisioned device, and has an unlocked device key, If this
38// function returns `false`, it's because either no one has ever logged onto
39// this device, or someone has, and then clicked `logout`. If the return
40// value is `false`, and `err` is `nil`, then the service is in one of
41// those expected "logged out" states.  If the return value is `false`
42// and `err` is non-`nil`, then something went wrong, and the app is in some
43// sort of unexpected state. If `ret` is `true`, then `uid` will convey
44// which user is logged in.
45//
46// Under the hood, IsLoggedIn is going through the BootstrapActiveDevice
47// flow and therefore will try its best to unlocked locked keys if it can
48// without user interaction.
49//
50// If the user is intentionally not logged into any user,  don't try to
51// bootstrap from the secret store and just check if there is an active device.
52func isLoggedInWithUIDAndError(m libkb.MetaContext) (ret bool, uid keybase1.UID, err error) {
53	if m.G().Env.GetStayLoggedOut() {
54		return m.ActiveDevice().Valid(), m.G().Env.GetUID(), nil
55	}
56	ret, uid, err = libkb.BootstrapActiveDeviceWithMetaContext(m)
57	return ret, uid, err
58}
59
60func isLoggedIn(m libkb.MetaContext) (ret bool, uid keybase1.UID) {
61	if m.G().Env.GetStayLoggedOut() {
62		return m.ActiveDevice().Valid(), m.G().Env.GetUID()
63	}
64	ret, uid, _ = libkb.BootstrapActiveDeviceWithMetaContext(m)
65	return ret, uid
66}
67
68func isLoggedInAs(m libkb.MetaContext, uid keybase1.UID) (ret bool) {
69	if m.G().Env.GetStayLoggedOut() {
70		return m.ActiveDevice().Valid() && uid == m.G().Env.GetUID()
71	}
72	ret, err := libkb.BootstrapActiveDeviceWithMetaContextAndAssertUID(m, uid)
73	if err != nil {
74		m.Debug("isLoggedAs error: %s", err)
75	}
76	return ret
77}
78
79func isLoggedInWithError(m libkb.MetaContext) (ret bool, err error) {
80	ret, _, err = isLoggedInWithUIDAndError(m)
81	return ret, err
82}
83
84func runPrereqs(m libkb.MetaContext, e Engine2) error {
85	prq := e.Prereqs()
86
87	if prq.TemporarySession {
88		if !m.HasAnySession() {
89			return libkb.NewLoginRequiredError("need either a temporary session or a device")
90		}
91	}
92
93	if prq.Device {
94		ok, err := isLoggedInWithError(m)
95		if err != nil {
96			return err
97		}
98		if !ok {
99			return libkb.DeviceRequiredError{}
100		}
101	}
102
103	return nil
104
105}
106
107func RunEngine2(m libkb.MetaContext, e Engine2) (err error) {
108	m = m.WithLogTag("ENG")
109	defer m.Trace(fmt.Sprintf("RunEngine(%s)", e.Name()), &err)()
110
111	if m, err = delegateUIs(m, e); err != nil {
112		return err
113	}
114	if err = check(m, e); err != nil {
115		return err
116	}
117	if err = runPrereqs(m, e); err != nil {
118		return err
119	}
120
121	err = e.Run(m)
122	return err
123}
124
125func getIdentifyUI3or1(m libkb.MetaContext) (libkb.IdentifyUI, error) {
126	uir := m.G().UIRouter
127	ret, err := uir.GetIdentify3UIAdapter(m)
128	if ret != nil && err == nil {
129		return ret, err
130	}
131	return uir.GetIdentifyUI()
132}
133
134func delegateUIs(m libkb.MetaContext, e Engine2) (libkb.MetaContext, error) {
135	if m.G().UIRouter == nil {
136		return m, nil
137	}
138
139	// currently, only doing this for SecretUI, but in future,
140	// perhaps should iterate over all registered UIs in UIRouter.
141	if requiresUI(e, libkb.SecretUIKind) {
142		sessionID := m.UIs().SessionID
143		if ui, err := m.G().UIRouter.GetSecretUI(sessionID); err != nil {
144			return m, err
145		} else if ui != nil {
146			m.Debug("using delegated secret UI for engine %q (session id = %d)", e.Name(), sessionID)
147			m = m.WithSecretUI(ui)
148		}
149	}
150
151	if wantsDelegateUI(e, libkb.IdentifyUIKind) {
152		m.Debug("IdentifyUI wanted for engine %q", e.Name())
153		ui, err := getIdentifyUI3or1(m)
154		if err != nil {
155			return m, err
156		}
157		if ui != nil {
158			m.Debug("using delegated identify UI for engine %q", e.Name())
159			m = m.WithDelegatedIdentifyUI(ui)
160		}
161	}
162	return m, nil
163}
164
165func wantsDelegateUI(e Engine2, kind libkb.UIKind) bool {
166	if !requiresUI(e, kind) {
167		return false
168	}
169	if i, ok := e.(UIDelegateWanter); ok {
170		return i.WantDelegate(kind)
171	}
172	return false
173}
174
175func check(m libkb.MetaContext, c libkb.UIConsumer) error {
176	if err := checkUI(m, c); err != nil {
177		return err
178	}
179
180	for _, sub := range c.SubConsumers() {
181		if err := check(m, sub); err != nil {
182			if _, ok := err.(CheckError); ok {
183				return err
184			}
185			return CheckError{fmt.Sprintf("%s: %s", sub.Name(), err)}
186		}
187	}
188
189	return nil
190}
191
192func checkUI(m libkb.MetaContext, c libkb.UIConsumer) error {
193	for _, ui := range c.RequiredUIs() {
194		if !m.UIs().HasUI(ui) {
195			return CheckError{fmt.Sprintf("%s: requires ui %q\n\n%s", c.Name(), ui, string(debug.Stack()))}
196		}
197	}
198	return nil
199}
200