1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"fmt"
8	"strings"
9	"testing"
10
11	"github.com/stretchr/testify/require"
12	"golang.org/x/net/context"
13)
14
15type testAssertionContext struct{}
16
17func (t testAssertionContext) Ctx() context.Context { return context.Background() }
18
19func (t testAssertionContext) NormalizeSocialName(service string, username string) (string, error) {
20	return strings.ToLower(username), nil
21}
22
23type testAlwaysFailAssertionContext struct{}
24
25const failAssertionContextErr = "Unknown social network BECAUSE IT'S A TEST"
26
27func (t testAlwaysFailAssertionContext) Ctx() context.Context { return context.Background() }
28
29func (t testAlwaysFailAssertionContext) NormalizeSocialName(service string, username string) (string, error) {
30	return "", fmt.Errorf("%s: %s", failAssertionContextErr, service)
31}
32
33func TestUsername(t *testing.T) {
34	a := "max"
35	expr, err := AssertionParse(testAssertionContext{}, a)
36	require.NoError(t, err)
37	require.IsType(t, AssertionKeybase{}, expr)
38	require.Equal(t, expr.String(), a)
39	proofset := NewProofSet([]Proof{
40		{"keybase", "max"},
41	})
42	require.True(t, expr.MatchSet(*proofset))
43}
44
45func TestUsernameMultiple(t *testing.T) {
46	a := "max,chris"
47	expr, err := AssertionParse(testAssertionContext{}, a)
48	require.NoError(t, err)
49
50	usernameSet := func(username string) ProofSet {
51		return *NewProofSet([]Proof{
52			{"keybase", username},
53		})
54	}
55	require.True(t, expr.MatchSet(usernameSet("max")))
56	require.True(t, expr.MatchSet(usernameSet("chris")))
57	require.False(t, expr.MatchSet(usernameSet("sam")))
58}
59
60func TestSuccess1(t *testing.T) {
61	a := "web://maxk.org && twitter://maxtaco"
62	expr, err := AssertionParse(testAssertionContext{}, a)
63	proofs := NewProofSet([]Proof{
64		{"http", "maxk.org"},
65		{"reddit", "maxtaco"},
66		{"twitter", "maxtaco"},
67	})
68	require.NoError(t, err)
69	require.True(t, expr.MatchSet(*proofs))
70}
71
72func TestAssertions1(t *testing.T) {
73	a := "web://maxk.org && (https://foo.com || http://bar.com) && (bb@twitter || max || pgp://aabbcc) && (keybear@octodon.social || gubble.social:keybear)"
74	goodProofsets := []ProofSet{
75		*NewProofSet([]Proof{
76			{"dns", "maxk.org"},
77			{"https", "bar.com"},
78			{"twitter", "bb"},
79			{"github", "xxx"},
80			{"keybase", "yyy"},
81			{"gubble.social", "keybear"},
82		}),
83		*NewProofSet([]Proof{
84			{"https", "maxk.org"},
85			{"https", "foo.com"},
86			{"keybase", "max"},
87			{"octodon.social", "keybear"},
88		}),
89		*NewProofSet([]Proof{
90			{"http", "maxk.org"},
91			{"https", "foo.com"},
92			{"pgp", "00aabbcc"},
93			{"gubble.social", "keybear"},
94		}),
95		*NewProofSet([]Proof{
96			{"http", "maxk.org"},
97			{"https", "foo.com"},
98			{"twitter", "bb"},
99			{"octodon.social", "keybear"},
100		}),
101		*NewProofSet([]Proof{
102			{"http", "maxk.org"},
103			{"http", "bar.com"},
104			{"twitter", "bb"},
105			{"octodon.social", "keybear"},
106		}),
107	}
108
109	badProofsets := []ProofSet{
110		*NewProofSet([]Proof{
111			{"dns", "max.org"},
112			{"http", "bar.com"},
113			{"twitter", "bb"},
114			{"github", "xxx"},
115			{"keybase", "yyy"},
116			{"gubble", "keybear"},
117		}),
118		*NewProofSet([]Proof{
119			{"https", "maxk.org"},
120			{"http", "foo.com"},
121			{"keybase", "maxi"},
122			{"gubble.social", "keybase"},
123		}),
124		*NewProofSet([]Proof{
125			{"http", "maxk.org"},
126			{"http", "foo.com"},
127			{"pgp", "00aabbcc"},
128			{"octodon.social", "keybase"},
129		}),
130		*NewProofSet([]Proof{
131			{"http", "maxk.org"},
132			{"https", "foo.com"},
133		}),
134		*NewProofSet([]Proof{
135			{"http", "maxk.org"},
136			{"https", "foo.com"},
137			{"pgp", "00aabbcce"},
138			{"pentagon.social", "keybear"},
139		}),
140		*NewProofSet([]Proof{
141			{"gubble.social", "keybear"},
142			{"octodon.social", "keybear"},
143		}),
144	}
145	expr, err := AssertionParse(testAssertionContext{}, a)
146	require.NoError(t, err)
147	for _, proofset := range goodProofsets {
148		require.True(t, expr.MatchSet(proofset))
149	}
150	for _, proofset := range badProofsets {
151		require.False(t, expr.MatchSet(proofset))
152	}
153}
154
155func TestAssertions2(t *testing.T) {
156	// Coyne-style grammar
157	a := "web:maxk.org+max,malgorithms+https:nutflex.com+pgp:aabbcc,samwise+dns:match.com"
158	goodProofsets := []ProofSet{
159		*NewProofSet([]Proof{
160			{"https", "maxk.org"},
161			{"keybase", "max"},
162		}),
163		*NewProofSet([]Proof{
164			{"https", "nutflex.com"},
165			{"pgp", "2233aabbcc"},
166			{"keybase", "malgorithms"},
167		}),
168		*NewProofSet([]Proof{
169			{"keybase", "samwise"},
170			{"dns", "match.com"},
171		}),
172	}
173	expr, err := AssertionParse(testAssertionContext{}, a)
174	require.NoError(t, err)
175	for _, proofset := range goodProofsets {
176		require.True(t, expr.MatchSet(proofset), "matching to %+v", proofset)
177	}
178}
179
180func TestAssertions3(t *testing.T) {
181	a := "t_bob+twitter:kbtester1"
182	goodProofsets := []ProofSet{
183		*NewProofSet([]Proof{
184			{"twitter", "kbtester1"},
185			{"keybase", "t_bob"},
186		}),
187	}
188	expr, err := AssertionParseAndOnly(testAssertionContext{}, a)
189	require.NoError(t, err)
190	for _, proofset := range goodProofsets {
191		require.True(t, expr.MatchSet(proofset))
192	}
193}
194
195func TestAssertions4(t *testing.T) {
196	// Coyne-style grammar (2), now with 100% more paramproof assertions
197	a := "alice@rooter+alice@cat.cafe+alice,https:nutflex.com+bob,jun@gubblers.eu+aabbcc@pgp"
198	goodProofsets := []ProofSet{
199		*NewProofSet([]Proof{
200			{"rooter", "alice"},
201			{"cat.cafe", "alice"},
202			{"keybase", "alice"},
203		}),
204		*NewProofSet([]Proof{
205			{"https", "nutflex.com"},
206			{"keybase", "bob"},
207		}),
208		*NewProofSet([]Proof{
209			{"gubblers.eu", "jun"},
210			{"pgp", "2233aabbcc"},
211		}),
212	}
213	badProofsets := []ProofSet{
214		*NewProofSet([]Proof{
215			{"cat.cafe", "alice"},
216		}),
217		*NewProofSet([]Proof{
218			{"keybase", "bob"},
219		}),
220		*NewProofSet([]Proof{
221			{"gubblers.eu", "jun"},
222			{"cat.cafe", "alice"}, // alice, no! your cat.cafe account got compromised!
223		}),
224	}
225	expr, err := AssertionParse(testAssertionContext{}, a)
226	require.NoError(t, err)
227	for _, proofset := range goodProofsets {
228		require.True(t, expr.MatchSet(proofset), "matching to %+v", proofset)
229	}
230	for _, proofset := range badProofsets {
231		require.False(t, expr.MatchSet(proofset), "matching to %+v", proofset)
232	}
233}
234
235func TestAssertionsUsernames(t *testing.T) {
236	exprs := []string{"x_", "A2", "ed", "bob", "o_o"}
237	fmts := []string{"%s", "%s@keybase", "keybase:%s"}
238	for _, str := range exprs {
239		for _, f := range fmts {
240			expr, err := AssertionParse(testAssertionContext{}, fmt.Sprintf(f, str))
241			require.NoError(t, err)
242			require.IsType(t, AssertionKeybase{}, expr)
243			require.Equal(t, strings.ToLower(str), expr.String())
244		}
245	}
246}
247
248func TestProofSetEmail(t *testing.T) {
249	exprs := []string{
250		"[m@keybasers.de]@email",
251		"email:[m@keybasers.de]",
252		"email://[m@keybasers.de]",
253	}
254	proofset := *NewProofSet([]Proof{
255		{"email", "m@keybasers.de"},
256	})
257	for _, expr := range exprs {
258		expr, err := AssertionParse(testAssertionContext{}, expr)
259		require.NoError(t, err)
260		require.Equal(t, "[m@keybasers.de]@email", expr.String())
261		require.True(t, expr.MatchSet(proofset), "when checking %q", expr)
262	}
263}
264
265func TestEmailAssertions(t *testing.T) {
266	exprs := []string{
267		"[m.a.x+2@kb.eu]@email",
268		"email:[h.e.l.l.o@kb.eu]",
269		"email://[test+spam@kb.eu]",
270		"email://[test+spam@kb.eu]+[other@example.com]@email",
271	}
272	for _, expr := range exprs {
273		_, err := AssertionParse(testAssertionContext{}, expr)
274		require.NoError(t, err)
275	}
276}
277
278func TestAssertionEmailsMultiple(t *testing.T) {
279	exprs := []string{
280		"[m@keybasers.de]@email,max",
281		"max,[m@keybasers.de]@email",
282		"email:[m@keybasers.de],max",
283		"max,email:[m@keybasers.de]",
284		"max,email://[m@keybasers.de]",
285		"(max||[max@keybase.io]@email),email://[m@keybasers.de]",
286	}
287	goodProofsets := []ProofSet{
288		*NewProofSet([]Proof{
289			{"email", "m@keybasers.de"},
290		}),
291		*NewProofSet([]Proof{
292			{"keybase", "max"},
293		}),
294		*NewProofSet([]Proof{
295			{"email", "m@keybasers.de"},
296			{"keybase", "m"},
297		}),
298	}
299	badProofsets := []ProofSet{
300		*NewProofSet([]Proof{
301			{"keybase", "m"},
302		}),
303		*NewProofSet([]Proof{
304			{"https", "keybase.io"},
305		}),
306		*NewProofSet([]Proof{
307			{"http", "keybase.io"},
308		}),
309	}
310	for _, expr := range exprs {
311		ret, err := AssertionParse(testAssertionContext{}, expr)
312		require.NoError(t, err, "when parsing %q", expr)
313		for _, proofset := range goodProofsets {
314			require.True(t, ret.MatchSet(proofset), "when checking %q with pset: %v", expr, proofset)
315		}
316		for _, proofset := range badProofsets {
317			require.False(t, ret.MatchSet(proofset), "when checking %q with pset: %v", expr, proofset)
318		}
319	}
320}
321
322func TestNeedsParens(t *testing.T) {
323	tests := []struct {
324		expr        string
325		needsParens bool
326	}{
327		{"max+foo@twitter,chris+chris@keybase", false},
328		{"max+foo@twitter+(chris,bob)", true},
329		{"max+foo@twitter+(chris)", false},
330		{"max+foo@twitter+((chris))", false},
331		{"max+foo@twitter+(chris+sam)", false},
332		{"max", false},
333	}
334
335	for _, test := range tests {
336		expr, err := AssertionParse(testAssertionContext{}, test.expr)
337		require.NoError(t, err)
338		require.Equal(t, expr.NeedsParens(), test.needsParens)
339	}
340}
341
342func TestAssertionCtxFailures(t *testing.T) {
343	a := "michal@twitter,mark@zapu.net"
344	_, err := AssertionParse(testAlwaysFailAssertionContext{}, a)
345	require.Error(t, err)
346	require.Contains(t, err.Error(), failAssertionContextErr)
347	require.Contains(t, err.Error(), "zapu.net")
348}
349