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	"context"
8	"testing"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	"github.com/stretchr/testify/require"
13)
14
15func runTrack(tc libkb.TestContext, fu *FakeUser, username string, sigVersion libkb.SigVersion) (idUI *FakeIdentifyUI, them *libkb.User, err error) {
16	sv := keybase1.SigVersion(sigVersion)
17	return runTrackWithOptions(tc, fu, username, keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI(), false)
18}
19
20func runTrackWithOptions(tc libkb.TestContext, fu *FakeUser, username string, options keybase1.TrackOptions, secretUI libkb.SecretUI, forceRemoteCheck bool) (idUI *FakeIdentifyUI, them *libkb.User, err error) {
21	idUI = &FakeIdentifyUI{}
22
23	arg := &TrackEngineArg{
24		UserAssertion:    username,
25		Options:          options,
26		ForceRemoteCheck: forceRemoteCheck,
27	}
28	uis := libkb.UIs{
29		LogUI:      tc.G.UI.GetLogUI(),
30		IdentifyUI: idUI,
31		SecretUI:   secretUI,
32	}
33	eng := NewTrackEngine(tc.G, arg)
34	m := NewMetaContextForTest(tc).WithUIs(uis)
35	err = RunEngine2(m, eng)
36	them = eng.User()
37	return
38}
39
40func assertTracking(tc libkb.TestContext, username string) {
41	me, err := libkb.LoadMe(libkb.NewLoadUserArg(tc.G))
42	require.NoError(tc.T, err)
43
44	them, err := libkb.LoadUser(libkb.NewLoadUserByNameArg(tc.G, username))
45	require.NoError(tc.T, err)
46
47	m := NewMetaContextForTest(tc)
48	s, err := me.TrackChainLinkFor(m, them.GetNormalizedName(), them.GetUID())
49	require.NoError(tc.T, err)
50	require.NotNil(tc.T, s)
51}
52
53func assertNotTracking(tc libkb.TestContext, username string) {
54	me, err := libkb.LoadMe(libkb.NewLoadUserArg(tc.G))
55	require.NoError(tc.T, err)
56
57	them, err := libkb.LoadUser(libkb.NewLoadUserByNameArg(tc.G, username))
58	require.NoError(tc.T, err)
59
60	m := NewMetaContextForTest(tc)
61	s, err := me.TrackChainLinkFor(m, them.GetNormalizedName(), them.GetUID())
62	require.NoError(tc.T, err)
63	require.Nil(tc.T, s)
64}
65
66func trackAlice(tc libkb.TestContext, fu *FakeUser, sigVersion libkb.SigVersion) {
67	sv := keybase1.SigVersion(sigVersion)
68	trackAliceWithOptions(tc, fu, keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI())
69}
70
71func trackUser(tc libkb.TestContext, fu *FakeUser, un libkb.NormalizedUsername, sigVersion libkb.SigVersion) {
72	sv := keybase1.SigVersion(sigVersion)
73	_, _, err := runTrackWithOptions(tc, fu, un.String(), keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI(), false)
74	require.NoError(tc.T, err)
75}
76
77func trackUserGetUI(tc libkb.TestContext, fu *FakeUser, un libkb.NormalizedUsername, sigVersion libkb.SigVersion) *FakeIdentifyUI {
78	sv := keybase1.SigVersion(sigVersion)
79	ui, _, err := runTrackWithOptions(tc, fu, un.String(), keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI(), false)
80	require.NoError(tc.T, err)
81	return ui
82}
83
84func trackAliceWithOptions(tc libkb.TestContext, fu *FakeUser, options keybase1.TrackOptions, secretUI libkb.SecretUI) {
85	idUI, res, err := runTrackWithOptions(tc, fu, "t_alice", options, secretUI, false)
86	require.NoError(tc.T, err)
87	upk, err := res.ExportToUPKV2AllIncarnations()
88	require.NoError(tc.T, err)
89	checkAliceProofs(tc.T, idUI, &upk.Current)
90	assertTracking(tc, "t_alice")
91}
92
93func trackBob(tc libkb.TestContext, fu *FakeUser, sigVersion libkb.SigVersion) {
94	sv := keybase1.SigVersion(sigVersion)
95	trackBobWithOptions(tc, fu, keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI())
96}
97
98func trackBobWithOptions(tc libkb.TestContext, fu *FakeUser, options keybase1.TrackOptions, secretUI libkb.SecretUI) {
99	// Refer to t_bob as kbtester1@twitter. This helps test a different
100	// codepath through idenfity2. (For example, in one case it triggered a
101	// race condition that aborted tracking without waiting for the UI to
102	// confirm, which wasn't present in the regular "t_bob" case.)
103
104	idUI, res, err := runTrackWithOptions(tc, fu, "kbtester1@twitter", options, secretUI, false)
105	require.NoError(tc.T, err)
106	upk, err := res.ExportToUPKV2AllIncarnations()
107	require.NoError(tc.T, err)
108	checkBobProofs(tc.T, idUI, &upk.Current)
109	assertTracking(tc, "t_bob")
110}
111
112func TestTrack(t *testing.T) {
113	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
114		_testTrack(t, sigVersion)
115	})
116}
117
118func _testTrack(t *testing.T, sigVersion libkb.SigVersion) {
119	tc := SetupEngineTest(t, "track")
120	defer tc.Cleanup()
121	fu := CreateAndSignupFakeUser(tc, "track")
122
123	trackAlice(tc, fu, sigVersion)
124	defer untrackAlice(tc, fu, sigVersion)
125
126	// Assert that we gracefully handle the case of no login
127	Logout(tc)
128	_, _, err := runTrack(tc, fu, "t_bob", sigVersion)
129	require.Error(t, err)
130	_, ok := err.(libkb.DeviceRequiredError)
131	require.True(t, ok)
132
133	fu.LoginOrBust(tc)
134	trackBob(tc, fu, sigVersion)
135	defer untrackBob(tc, fu, sigVersion)
136
137	// try tracking a user with no keys (which is now allowed)
138	_, _, err = runTrack(tc, fu, "t_ellen", sigVersion)
139	require.NoError(t, err)
140}
141
142// tests tracking a user that doesn't have a public key (#386)
143func TestTrackNoPubKey(t *testing.T) {
144	tc := SetupEngineTest(t, "track")
145	defer tc.Cleanup()
146	sigVersion := libkb.GetDefaultSigVersion(tc.G)
147	fu := CreateAndSignupFakeUser(tc, "track")
148	Logout(tc)
149
150	tracker := CreateAndSignupFakeUser(tc, "track")
151	_, _, err := runTrack(tc, tracker, fu.Username, sigVersion)
152	require.NoError(t, err)
153}
154
155func TestTrackMultiple(t *testing.T) {
156	tc := SetupEngineTest(t, "track")
157	defer tc.Cleanup()
158	sigVersion := libkb.GetDefaultSigVersion(tc.G)
159	fu := CreateAndSignupFakeUser(tc, "track")
160
161	trackAlice(tc, fu, sigVersion)
162	defer untrackAlice(tc, fu, sigVersion)
163
164	trackAlice(tc, fu, sigVersion)
165}
166
167func TestTrackNewUserWithPGP(t *testing.T) {
168	tc := SetupEngineTest(t, "track")
169	defer tc.Cleanup()
170	sigVersion := libkb.GetDefaultSigVersion(tc.G)
171	fu := createFakeUserWithPGPSibkey(tc)
172	Logout(tc)
173
174	tracker := CreateAndSignupFakeUser(tc, "track")
175	t.Logf("first track:")
176	_, _, err := runTrack(tc, tracker, fu.Username, sigVersion)
177	require.NoError(t, err)
178
179	t.Logf("second track:")
180	_, _, err = runTrack(tc, tracker, fu.Username, sigVersion)
181	require.NoError(t, err)
182}
183
184// see issue #578
185func TestTrackRetrack(t *testing.T) {
186
187	tc := SetupEngineTest(t, "track")
188	defer tc.Cleanup()
189	sigVersion := libkb.GetDefaultSigVersion(tc.G)
190	fu := createFakeUserWithPGPSibkey(tc)
191
192	idUI := &FakeIdentifyUI{}
193	secretUI := fu.NewSecretUI()
194
195	var err error
196	fu.User, err = libkb.LoadMe(libkb.NewLoadUserPubOptionalArg(tc.G))
197	require.NoError(t, err)
198	seqnoBefore := fu.User.GetSigChainLastKnownSeqno()
199
200	sv := keybase1.SigVersion(sigVersion)
201	arg := &TrackEngineArg{
202		UserAssertion: "t_alice",
203		Options:       keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv},
204	}
205	uis := libkb.UIs{
206		LogUI:      tc.G.UI.GetLogUI(),
207		IdentifyUI: idUI,
208		SecretUI:   secretUI,
209	}
210	eng := NewTrackEngine(tc.G, arg)
211	m := NewMetaContextForTest(tc).WithUIs(uis)
212	err = RunEngine2(m, eng)
213	require.NoError(t, err)
214
215	fu.User, err = libkb.LoadMe(libkb.NewLoadUserPubOptionalArg(tc.G))
216	require.NoError(t, err)
217	seqnoAfter := fu.User.GetSigChainLastKnownSeqno()
218
219	require.NotEqual(t, seqnoAfter, seqnoBefore)
220
221	eng = NewTrackEngine(tc.G, arg)
222	err = RunEngine2(m, eng)
223	require.NoError(t, err)
224
225	fu.User, err = libkb.LoadMe(libkb.NewLoadUserPubOptionalArg(tc.G))
226	require.NoError(t, err)
227	seqnoRetrack := fu.User.GetSigChainLastKnownSeqno()
228
229	require.False(t, seqnoRetrack > seqnoAfter)
230}
231
232func TestTrackLocal(t *testing.T) {
233	tc := SetupEngineTest(t, "track")
234	defer tc.Cleanup()
235	fu := CreateAndSignupFakeUser(tc, "track")
236
237	_, them, err := runTrackWithOptions(tc, fu, "t_alice", keybase1.TrackOptions{LocalOnly: true, BypassConfirm: true}, fu.NewSecretUI(), false)
238	require.NoError(t, err)
239
240	require.NotNil(t, them)
241
242	me, err := libkb.LoadMe(libkb.NewLoadUserArg(tc.G))
243	require.NoError(t, err)
244
245	m := NewMetaContextForTest(tc)
246	s, err := me.TrackChainLinkFor(m, them.GetNormalizedName(), them.GetUID())
247	require.NoError(t, err)
248	require.NotNil(t, s)
249	require.False(t, s.IsRemote())
250}
251
252// Make sure the track engine uses the secret store.
253func TestTrackWithSecretStore(t *testing.T) {
254	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
255		_testTrackWithSecretStore(t, sigVersion)
256	})
257}
258
259func _testTrackWithSecretStore(t *testing.T, sigVersion libkb.SigVersion) {
260	testEngineWithSecretStore(t, func(
261		tc libkb.TestContext, fu *FakeUser, secretUI libkb.SecretUI) {
262		trackAliceWithOptions(tc, fu, keybase1.TrackOptions{BypassConfirm: true}, secretUI)
263		untrackAlice(tc, fu, sigVersion)
264	})
265}
266
267// Test for Core-2196 identify/track race detection
268func TestIdentifyTrackRaceDetection(t *testing.T) {
269	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
270		_testIdentifyTrackRaceDetection(t, sigVersion)
271	})
272}
273
274func _testIdentifyTrackRaceDetection(t *testing.T, sigVersion libkb.SigVersion) {
275	user, dev1, dev2, cleanup := SetupTwoDevices(t, "track")
276	defer cleanup()
277
278	trackee := "t_tracy"
279
280	doID := func(tc libkb.TestContext, fui *FakeIdentifyUI) {
281
282		iarg := &keybase1.Identify2Arg{
283			UserAssertion: trackee,
284			// We need to block on identification so that the track token
285			// is delivered to the UI before we return. Otherwise, the
286			// following call to track might happen before the token
287			// is known.
288			AlwaysBlock:      true,
289			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
290		}
291		eng := NewResolveThenIdentify2(tc.G, iarg)
292		uis := libkb.UIs{IdentifyUI: fui}
293		m := NewMetaContextForTest(tc).WithUIs(uis)
294		err := RunEngine2(m, eng)
295		require.NoError(t, err)
296	}
297
298	sv := keybase1.SigVersion(sigVersion)
299	track := func(tc libkb.TestContext, fui *FakeIdentifyUI) error {
300		arg := TrackTokenArg{
301			Token: fui.Token,
302			Options: keybase1.TrackOptions{
303				BypassConfirm: true,
304				ForceRetrack:  true,
305				SigVersion:    &sv,
306			},
307		}
308		uis := libkb.UIs{
309			LogUI:    tc.G.UI.GetLogUI(),
310			SecretUI: user.NewSecretUI(),
311		}
312		eng := NewTrackToken(tc.G, &arg)
313		m := NewMetaContextForTest(tc).WithUIs(uis)
314		return RunEngine2(m, eng)
315	}
316
317	trackSucceed := func(tc libkb.TestContext, fui *FakeIdentifyUI) {
318		err := track(tc, fui)
319		require.NoError(tc.T, err)
320		assertTracking(dev1, trackee)
321	}
322
323	trackFail := func(tc libkb.TestContext, fui *FakeIdentifyUI, firstTrack bool) {
324		err := track(tc, fui)
325		require.Error(tc.T, err)
326		tse, ok := err.(libkb.TrackStaleError)
327		require.True(tc.T, ok)
328		require.Equal(tc.T, tse.FirstTrack, firstTrack)
329	}
330
331	for i := 0; i < 2; i++ {
332		fui1 := &FakeIdentifyUI{}
333		fui2 := &FakeIdentifyUI{}
334		doID(dev1, fui1)
335		if i > 0 {
336			// Device2 won't know that device1 made a change to the ME user
337			// in time to make this test pass. So we hack in an invalidation.
338			// We might have used the fact the userchanged notifications are bounced
339			// off of the server, but that might slow down this test, so do the
340			// simple and non-flakey thing.
341			dev2.G.GetUPAKLoader().Invalidate(context.TODO(), libkb.UsernameToUID(user.Username))
342		}
343		doID(dev2, fui2)
344		trackSucceed(dev1, fui1)
345		trackFail(dev2, fui2, (i == 0))
346	}
347
348	err := runUntrack(dev1, user, trackee, sigVersion)
349	require.NoError(t, err)
350}
351
352func TestTrackNoKeys(t *testing.T) {
353	tc := SetupEngineTest(t, "track")
354	defer tc.Cleanup()
355	sigVersion := libkb.GetDefaultSigVersion(tc.G)
356	nk, pp := createFakeUserWithNoKeys(tc)
357	Logout(tc)
358
359	fu := CreateAndSignupFakeUser(tc, "track")
360	trackUser(tc, fu, libkb.NewNormalizedUsername(nk), sigVersion)
361
362	// provision nk on a new device
363	Logout(tc)
364	nku := &FakeUser{Username: nk, Passphrase: pp}
365	err := nku.Login(tc.G)
366	require.NoError(t, err)
367	Logout(tc)
368
369	// track nk again
370	err = fu.Login(tc.G)
371	require.NoError(t, err)
372	ui := trackUserGetUI(tc, fu, libkb.NewNormalizedUsername(nk), sigVersion)
373
374	// ensure track diff for new eldest key
375	require.Equal(t, 1, len(ui.DisplayKeyDiffs))
376	require.Equal(t, ui.DisplayKeyDiffs[0].Type, keybase1.TrackDiffType_NEW_ELDEST)
377}
378
379func TestTrackSelf(t *testing.T) {
380	tc := SetupEngineTest(t, "track")
381	defer tc.Cleanup()
382
383	sigVersion := libkb.GetDefaultSigVersion(tc.G)
384	sv := keybase1.SigVersion(sigVersion)
385	fu := CreateAndSignupFakeUser(tc, "track")
386	_, _, err := runTrackWithOptions(tc, fu, fu.NormalizedUsername().String(), keybase1.TrackOptions{
387		BypassConfirm: true,
388		SigVersion:    &sv,
389	}, fu.NewSecretUI(), false)
390	require.Error(t, err)
391	require.Equal(t, "You can't follow yourself.", err.Error())
392}
393