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	"testing"
8	"time"
9
10	"github.com/keybase/client/go/gregor"
11	"github.com/keybase/client/go/libkb"
12	gregor1 "github.com/keybase/client/go/protocol/gregor1"
13	keybase1 "github.com/keybase/client/go/protocol/keybase1"
14	"github.com/keybase/clockwork"
15	context "golang.org/x/net/context"
16)
17
18func doWithSigChainVersions(f func(libkb.SigVersion)) {
19	f(libkb.KeybaseSignatureV1)
20	f(libkb.KeybaseSignatureV2)
21}
22
23func TestTrackTokenIdentify2(t *testing.T) {
24	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
25		_testTrackTokenIdentify2(t, sigVersion)
26	})
27}
28
29func _testTrackTokenIdentify2(t *testing.T, sigVersion libkb.SigVersion) {
30	tc := SetupEngineTest(t, "track")
31	defer tc.Cleanup()
32	fu := CreateAndSignupFakeUser(tc, "track")
33
34	idUI := &FakeIdentifyUI{}
35	username := "t_tracy"
36	arg := &keybase1.Identify2Arg{
37		UserAssertion:    username,
38		NeedProofSet:     true,
39		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
40	}
41	uis := libkb.UIs{
42		LogUI:      tc.G.UI.GetLogUI(),
43		IdentifyUI: idUI,
44		SecretUI:   fu.NewSecretUI(),
45	}
46	eng := NewResolveThenIdentify2(tc.G, arg)
47	m := NewMetaContextForTest(tc).WithUIs(uis)
48	if err := RunEngine2(m, eng); err != nil {
49		tc.T.Fatal(err)
50	}
51	sv := keybase1.SigVersion(sigVersion)
52	targ := TrackTokenArg{
53		Token:   idUI.Token,
54		Options: keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv},
55	}
56	teng := NewTrackToken(tc.G, &targ)
57	if err := RunEngine2(m, teng); err != nil {
58		tc.T.Fatal(err)
59	}
60
61	defer func() { _ = runUntrack(tc, fu, username, sigVersion) }()
62	assertTracking(tc, username)
63}
64
65func TestTrackLocalThenLocalTemp(t *testing.T) {
66	tc := SetupEngineTest(t, "track")
67	defer tc.Cleanup()
68	sigVersion := libkb.GetDefaultSigVersion(tc.G)
69
70	fakeClock := clockwork.NewFakeClockAt(time.Now())
71	tc.G.SetClock(fakeClock)
72	fu := CreateAndSignupFakeUser(tc, "track")
73
74	flakeyAPI := flakeyRooterAPI{orig: tc.G.XAPI, flakeOut: false, G: tc.G}
75	tc.G.XAPI = &flakeyAPI
76
77	idUI := &FakeIdentifyUI{}
78	username := "t_tracy"
79
80	arg := &keybase1.Identify2Arg{
81		UserAssertion:    username,
82		NeedProofSet:     true,
83		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
84	}
85	uis := libkb.UIs{
86		LogUI:      tc.G.UI.GetLogUI(),
87		IdentifyUI: idUI,
88		SecretUI:   fu.NewSecretUI(),
89	}
90
91	// Identify tracy; all proofs should work
92	eng := NewResolveThenIdentify2(tc.G, arg)
93	m := NewMetaContextForTest(tc).WithUIs(uis)
94	if err := RunEngine2(m, eng); err != nil {
95		t.Fatal(err)
96	}
97	sv := keybase1.SigVersion(sigVersion)
98	targ := TrackTokenArg{
99		Token:   idUI.Token,
100		Options: keybase1.TrackOptions{BypassConfirm: true, LocalOnly: true, SigVersion: &sv},
101	}
102
103	// Track tracy
104	teng := NewTrackToken(tc.G, &targ)
105	if err := RunEngine2(m, teng); err != nil {
106		t.Fatal(err)
107	}
108
109	defer func() { _ = runUntrack(tc, fu, username, sigVersion) }()
110
111	// Now make her Rooter proof fail with a 429
112	flakeyAPI.flakeOut = true
113	idUI = &FakeIdentifyUI{}
114	m = m.WithIdentifyUI(idUI)
115
116	// Advance so that our previous cached success is out of
117	// cache
118	fakeClock.Advance(tc.G.Env.GetProofCacheLongDur() + time.Minute)
119
120	eng = NewResolveThenIdentify2(tc.G, arg)
121	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
122
123	// Should  get an error
124	if err := RunEngine2(m, eng); err == nil {
125		t.Fatal("Expected identify error")
126	}
127
128	result, found := idUI.ProofResults["rooter"]
129	if !found {
130		t.Fatal("Failed to find a rooter proof")
131	}
132	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
133		t.Fatal("expected a Rooter error result")
134	}
135
136	// This is like the UI saying to store the local track
137	targ.Options.ExpiringLocal = true
138	targ.Token = idUI.Token
139	// Track tracy
140	teng = NewTrackToken(tc.G, &targ)
141	if err := RunEngine2(m, teng); err != nil {
142		t.Fatal(err)
143	}
144
145	// Identify should work once more because we signed with failures
146	eng = NewResolveThenIdentify2(tc.G, arg)
147	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
148	var err error
149	// Should not get an error
150	if err = RunEngine2(m, eng); err != nil {
151		t.Logf("Identify failure: %v", err)
152		t.Fatal("Expected to pass identify")
153	}
154
155	result, found = idUI.ProofResults["rooter"]
156	if !found {
157		t.Fatal("Failed to find a rooter proof")
158	}
159	if result.Diff == nil {
160		t.Fatal("Failed to find a rooter proof result diff")
161	}
162	if result.Diff.Type != keybase1.TrackDiffType_NONE_VIA_TEMPORARY {
163		t.Fatal("Failed to find a rooter proof result diff of type TrackDiffType_NONE_VIA_TEMPORARY")
164	}
165	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
166		t.Fatal("expected a Rooter error result")
167	}
168
169	// Advance so that our temporary track is discarded
170	fakeClock.Advance(tc.G.Env.GetLocalTrackMaxAge() + time.Minute)
171
172	// Identify should fail once more
173	eng = NewResolveThenIdentify2(tc.G, arg)
174	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
175	// Should get an error
176	if err = RunEngine2(m, eng); err == nil {
177		t.Fatal("Expected rooter to fail")
178	}
179	t.Logf("Identify failure: %v", err)
180
181	result, found = idUI.ProofResults["rooter"]
182	if !found {
183		t.Fatal("Failed to find a rooter proof")
184	}
185	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
186		t.Fatal("expected a Rooter error result")
187	}
188
189	assertTracking(tc, username)
190}
191
192func TestTrackRemoteThenLocalTemp(t *testing.T) {
193	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
194		_testTrackRemoteThenLocalTemp(t, sigVersion)
195	})
196}
197
198func _testTrackRemoteThenLocalTemp(t *testing.T, sigVersion libkb.SigVersion) {
199	tc := SetupEngineTest(t, "track")
200	defer tc.Cleanup()
201
202	// Tracking remote means we have to agree what time it is
203	fakeClock := clockwork.NewFakeClockAt(time.Now())
204	tc.G.SetClock(fakeClock)
205	fu := CreateAndSignupFakeUser(tc, "track")
206
207	flakeyAPI := flakeyRooterAPI{orig: tc.G.XAPI, flakeOut: false, G: tc.G}
208	tc.G.XAPI = &flakeyAPI
209
210	idUI := &FakeIdentifyUI{}
211	username := "t_tracy"
212
213	arg := &keybase1.Identify2Arg{
214		UserAssertion:    username,
215		NeedProofSet:     true,
216		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
217	}
218	uis := libkb.UIs{
219		LogUI:      tc.G.UI.GetLogUI(),
220		IdentifyUI: idUI,
221		SecretUI:   fu.NewSecretUI(),
222	}
223
224	// Identify tracy; all proofs should work
225	eng := NewResolveThenIdentify2(tc.G, arg)
226	m := NewMetaContextForTest(tc).WithUIs(uis)
227	if err := RunEngine2(m, eng); err != nil {
228		t.Fatal(err)
229	}
230	// Leaving LocalOnly off here will result in remote tracking
231	sv := keybase1.SigVersion(sigVersion)
232	targ := TrackTokenArg{
233		Token:   idUI.Token,
234		Options: keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv},
235	}
236
237	// Track tracy
238	teng := NewTrackToken(tc.G, &targ)
239	if err := RunEngine2(m, teng); err != nil {
240		t.Fatal(err)
241	}
242
243	defer func() { _ = runUntrack(tc, fu, username, sigVersion) }()
244
245	// Now make her Rooter proof fail with a 429
246	flakeyAPI.flakeOut = true
247	idUI = &FakeIdentifyUI{}
248	m = m.WithIdentifyUI(idUI)
249
250	// Advance so that our previous cached success is out of
251	// cache
252	fakeClock.Advance(tc.G.Env.GetProofCacheLongDur() + time.Minute)
253
254	eng = NewResolveThenIdentify2(tc.G, arg)
255	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
256
257	// Should  get an error
258	if err := RunEngine2(m, eng); err == nil {
259		t.Fatal("Expected identify error")
260	}
261
262	result, found := idUI.ProofResults["rooter"]
263	if !found {
264		t.Fatal("Failed to find a rooter proof")
265	}
266	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
267		t.Fatal("expected a Rooter error result")
268	}
269
270	// This is like the UI saying to store the local track
271	targ.Options.ExpiringLocal = true
272	targ.Token = idUI.Token
273	// Track tracy
274	teng = NewTrackToken(tc.G, &targ)
275	if err := RunEngine2(m, teng); err != nil {
276		t.Fatal(err)
277	}
278
279	// Identify should work once more because we signed with failures
280	eng = NewResolveThenIdentify2(tc.G, arg)
281	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
282	var err error
283	// Should not get an error
284	if err = RunEngine2(m, eng); err != nil {
285		t.Logf("Identify failure: %v", err)
286		t.Fatal("Expected to pass identify")
287	}
288
289	result, found = idUI.ProofResults["rooter"]
290	if !found {
291		t.Fatal("Failed to find a rooter proof")
292	}
293	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
294		t.Fatal("expected a Rooter error result")
295	}
296
297	// Advance so that our temporary track is discarded
298	// cache
299	fakeClock.Advance(tc.G.Env.GetLocalTrackMaxAge() + time.Minute)
300
301	// Identify should fail once more
302	eng = NewResolveThenIdentify2(tc.G, arg)
303	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
304	// Should get an error
305	if err = RunEngine2(m, eng); err == nil {
306		t.Fatal("Expected rooter to fail")
307	}
308	t.Logf("Identify failure: %v", err)
309
310	result, found = idUI.ProofResults["rooter"]
311	if !found {
312		t.Fatal("Failed to find a rooter proof")
313	}
314	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
315		t.Fatal("expected a Rooter error result")
316	}
317
318	assertTracking(tc, username)
319}
320
321func TestTrackFailTempRecover(t *testing.T) {
322	tc := SetupEngineTest(t, "track")
323	defer tc.Cleanup()
324	sigVersion := libkb.GetDefaultSigVersion(tc.G)
325
326	fakeClock := clockwork.NewFakeClockAt(time.Now())
327	tc.G.SetClock(fakeClock)
328	fu := CreateAndSignupFakeUser(tc, "track")
329
330	flakeyAPI := flakeyRooterAPI{orig: tc.G.XAPI, flakeOut: false, G: tc.G}
331	tc.G.XAPI = &flakeyAPI
332
333	idUI := &FakeIdentifyUI{}
334	username := "t_tracy"
335
336	arg := &keybase1.Identify2Arg{
337		UserAssertion:    username,
338		NeedProofSet:     true,
339		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
340	}
341	uis := libkb.UIs{
342		LogUI:      tc.G.UI.GetLogUI(),
343		IdentifyUI: idUI,
344		SecretUI:   fu.NewSecretUI(),
345	}
346
347	// Identify tracy; all proofs should work
348	eng := NewResolveThenIdentify2(tc.G, arg)
349	m := NewMetaContextForTest(tc).WithUIs(uis)
350	if err := RunEngine2(m, eng); err != nil {
351		t.Fatal(err)
352	}
353	sv := keybase1.SigVersion(sigVersion)
354	targ := TrackTokenArg{
355		Token:   idUI.Token,
356		Options: keybase1.TrackOptions{BypassConfirm: true, LocalOnly: true, SigVersion: &sv},
357	}
358
359	// Track tracy
360	teng := NewTrackToken(tc.G, &targ)
361	if err := RunEngine2(m, teng); err != nil {
362		t.Fatal(err)
363	}
364
365	defer func() { _ = runUntrack(tc, fu, username, sigVersion) }()
366
367	// Now make her Rooter proof fail with a 429
368	flakeyAPI.flakeOut = true
369	idUI = &FakeIdentifyUI{}
370	m = m.WithIdentifyUI(idUI)
371
372	// Advance so that our previous cached success is out of
373	// cache
374	fakeClock.Advance(tc.G.Env.GetProofCacheLongDur() + time.Minute)
375
376	eng = NewResolveThenIdentify2(tc.G, arg)
377	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
378
379	// Should  get an error
380	if err := RunEngine2(m, eng); err == nil {
381		t.Fatal("Expected identify error")
382	}
383
384	result, found := idUI.ProofResults["rooter"]
385	if !found {
386		t.Fatal("Failed to find a rooter proof")
387	}
388	pe := libkb.ImportProofError(result.ProofResult)
389	if pe == nil {
390		t.Fatal("expected a Rooter error result")
391	}
392
393	t.Logf("Rooter proof result, error: %v, -- %v", result, pe)
394	if result.Diff != nil {
395		t.Logf("Rooter proof result diff: %v", result.Diff)
396	}
397	// This is like the UI saying to store the local track
398	targ.Options.ExpiringLocal = true
399
400	targ.Token = idUI.Token
401	// Track tracy
402	teng = NewTrackToken(tc.G, &targ)
403	if err := RunEngine2(m, teng); err != nil {
404		t.Fatal(err)
405	}
406
407	// Now make her Rooter proof work again
408	flakeyAPI.flakeOut = false
409
410	// Identify should work because of the original, permanent track
411	eng = NewResolveThenIdentify2(tc.G, arg)
412	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
413	var err error
414	// Should not get an error
415	if err = RunEngine2(m, eng); err != nil {
416		t.Logf("Identify failure: %v", err)
417		t.Fatal("Expected to pass identify")
418	}
419
420	// There shouldn't be a Diff in the result, but if there is, make sure
421	// it isn't due to temporary tracking
422	result, found = idUI.ProofResults["rooter"]
423	if !found || result.Diff.Type != keybase1.TrackDiffType_NONE {
424		t.Fatalf("Expected a TrackDiffType_NONE")
425	}
426
427	// Advance the clock to make sure local temp track goes away
428	fakeClock.Advance(tc.G.Env.GetLocalTrackMaxAge() + time.Minute)
429
430	if err := eng.i2eng.createIdentifyState(m); err != nil {
431		t.Fatal(err)
432	}
433	if eng.i2eng.state.TrackLookup() == nil {
434		t.Fatalf("Expected permanent LocalTrackChainLinkFor %s", username)
435	}
436	if eng.i2eng.state.TmpTrackLookup() != nil {
437		t.Fatalf("Expected no temporary LocalTrackChainLinkFor %s", username)
438	}
439	assertTracking(tc, username)
440}
441
442type FakeGregorState struct {
443	dismissedMsgID gregor.MsgID
444}
445
446var _ libkb.GregorState = (*FakeGregorState)(nil)
447
448func (d *FakeGregorState) State(ctx context.Context) (gregor.State, error) {
449	return nil, nil
450}
451
452func (d *FakeGregorState) UpdateCategory(ctx context.Context, cat string, body []byte,
453	dtime gregor1.TimeOrOffset) (res gregor1.MsgID, err error) {
454	return gregor1.MsgID{}, nil
455}
456
457func (d *FakeGregorState) InjectItem(ctx context.Context, cat string, body []byte, dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) {
458	return nil, nil
459}
460
461func (d *FakeGregorState) DismissItem(ctx context.Context, cli gregor1.IncomingInterface, id gregor.MsgID) error {
462	d.dismissedMsgID = id
463	return nil
464}
465
466func (d *FakeGregorState) DismissCategory(ct context.Context, cat gregor1.Category) error {
467	return nil
468}
469
470func (d *FakeGregorState) LocalDismissItem(ctx context.Context, id gregor.MsgID) error {
471	return nil
472}
473
474func TestTrackWithTokenDismissesGregor(t *testing.T) {
475	tc := SetupEngineTest(t, "track")
476	defer tc.Cleanup()
477	sigVersion := libkb.GetDefaultSigVersion(tc.G)
478	fu := CreateAndSignupFakeUser(tc, "track")
479
480	dismisser := &FakeGregorState{}
481	tc.G.GregorState = dismisser
482
483	msgID := gregor1.MsgID("my_random_id")
484	responsibleGregorItem := gregor1.ItemAndMetadata{
485		// All we need for this test is the msgID, to check for dismissal.
486		Md_: &gregor1.Metadata{
487			MsgID_: msgID,
488		},
489	}
490
491	idUI := &FakeIdentifyUI{}
492	username := "t_tracy"
493	arg := &keybase1.Identify2Arg{
494		UserAssertion:    username,
495		NeedProofSet:     true,
496		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
497	}
498	uis := libkb.UIs{
499		LogUI:      tc.G.UI.GetLogUI(),
500		IdentifyUI: idUI,
501		SecretUI:   fu.NewSecretUI(),
502	}
503	eng := NewResolveThenIdentify2(tc.G, arg)
504	m := NewMetaContextForTest(tc).WithUIs(uis)
505	eng.SetResponsibleGregorItem(&responsibleGregorItem)
506	if err := RunEngine2(m, eng); err != nil {
507		tc.T.Fatal(err)
508	}
509	sv := keybase1.SigVersion(sigVersion)
510	targ := TrackTokenArg{
511		Token:   idUI.Token,
512		Options: keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv},
513	}
514	teng := NewTrackToken(tc.G, &targ)
515	if err := RunEngine2(m, teng); err != nil {
516		tc.T.Fatal(err)
517	}
518
519	// Check that the dismissed ID matches what we defined above.
520	if msgID.String() != dismisser.dismissedMsgID.String() {
521		tc.T.Fatalf("Dismissed msgID (%s) != responsible msgID (%s)", msgID.String(), dismisser.dismissedMsgID.String())
522	}
523}
524