1// Copyright 2018 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package teams
5
6import (
7	"context"
8	"fmt"
9	"testing"
10
11	"github.com/keybase/client/go/emails"
12	"github.com/keybase/client/go/kbtest"
13
14	"github.com/keybase/client/go/externalstest"
15	"github.com/keybase/client/go/libkb"
16	keybase1 "github.com/keybase/client/go/protocol/keybase1"
17	"github.com/stretchr/testify/require"
18)
19
20func TestTransactions1(t *testing.T) {
21	tc, owner, other, _, name := memberSetupMultiple(t)
22	defer tc.Cleanup()
23
24	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
25		Name:      name,
26		NeedAdmin: true,
27	})
28	require.NoError(t, err)
29
30	tx := CreateAddMemberTx(team)
31	tx.AllowPUKless = true
32	err = tx.AddMemberByUsername(context.Background(), "t_alice", keybase1.TeamRole_WRITER, nil)
33	require.NoError(t, err)
34	require.Equal(t, 1, len(tx.payloads))
35	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
36	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
37
38	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil)
39	require.NoError(t, err)
40	require.Equal(t, 2, len(tx.payloads))
41	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
42	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
43	require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag)
44	require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val)
45
46	err = tx.AddMemberByUsername(context.Background(), "t_tracy", keybase1.TeamRole_ADMIN, nil)
47	require.NoError(t, err)
48
49	// 3rd add (pukless member) should re-use first signature instead
50	// of creating new one.
51	require.Equal(t, 2, len(tx.payloads))
52	require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag)
53	require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val)
54	require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag)
55	require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val)
56
57	err = tx.Post(libkb.NewMetaContextForTest(tc))
58	require.NoError(t, err)
59
60	team, err = Load(context.Background(), tc.G, keybase1.LoadTeamArg{
61		Name:        name,
62		NeedAdmin:   true,
63		ForceRepoll: true,
64	})
65	require.NoError(t, err)
66
67	members, err := team.Members()
68	require.NoError(t, err)
69	require.Equal(t, 1, len(members.Owners))
70	require.Equal(t, owner.GetUserVersion(), members.Owners[0])
71	require.Equal(t, 0, len(members.Admins))
72	require.Equal(t, 1, len(members.Writers))
73	require.Equal(t, other.GetUserVersion(), members.Writers[0])
74	require.Equal(t, 0, len(members.Readers))
75	require.Equal(t, 0, len(members.Bots))
76	require.Equal(t, 0, len(members.RestrictedBots))
77
78	invites := team.GetActiveAndObsoleteInvites()
79	require.Equal(t, 2, len(invites))
80}
81
82func TestTransactionRotateKey(t *testing.T) {
83	tc, _, otherA, otherB, name := memberSetupMultiple(t)
84	defer tc.Cleanup()
85
86	loadTeam := func() *Team {
87		team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
88			Name:        name,
89			NeedAdmin:   true,
90			ForceRepoll: true,
91		})
92		require.NoError(t, err)
93		return team
94	}
95
96	team := loadTeam()
97	err := team.ChangeMembership(context.Background(), keybase1.TeamChangeReq{
98		Writers: []keybase1.UserVersion{otherA.GetUserVersion()},
99	})
100	require.NoError(t, err)
101
102	team = loadTeam()
103	require.EqualValues(t, 1, team.Generation())
104
105	tx := CreateAddMemberTx(team)
106	// Create payloads manually so user add and user del happen in
107	// separate links.
108	tx.payloads = []txPayload{
109		{
110			Tag: txPayloadTagCryptomembers,
111			Val: &keybase1.TeamChangeReq{
112				Writers: []keybase1.UserVersion{otherB.GetUserVersion()},
113			},
114		},
115		{
116			Tag: txPayloadTagCryptomembers,
117			Val: &keybase1.TeamChangeReq{
118				None: []keybase1.UserVersion{otherA.GetUserVersion()},
119			},
120		},
121	}
122	err = tx.Post(libkb.NewMetaContextForTest(tc))
123	require.NoError(t, err)
124
125	// Also if the transaction didn't create new PerTeamKey, bunch of
126	// assertions would have failed on the server. It doesn't matter
127	// which link the PerTeamKey is attached to, because key coverage
128	// is checked for the entire transaction, not individual links,
129	// but we always attach it to the first ChangeMembership link with
130	// member removals.
131	team = loadTeam()
132	require.EqualValues(t, 2, team.Generation())
133}
134
135func TestPreprocessAssertions(t *testing.T) {
136	tc := externalstest.SetupTest(t, "assertions", 0)
137	defer tc.Cleanup()
138
139	tests := []struct {
140		s             string
141		isServerTrust bool
142		hasSingle     bool
143		isError       bool
144	}{
145		{"bob", false, true, false},
146		{"bob+bob@twitter", false, false, false},
147		{"[bob@gmail.com]@email", true, true, false},
148		{"[bob@gmail.com]@email+bob", false, false, true},
149		{"18005558638@phone", true, true, false},
150		{"18005558638@phone+alice", false, false, true},
151		{"18005558638@phone+[bob@gmail.com]@email", false, false, true},
152	}
153	for _, test := range tests {
154		t.Logf("Testing: %s", test.s)
155		isServerTrust, single, full, err := preprocessAssertion(libkb.NewMetaContextForTest(tc), test.s)
156		require.Equal(t, isServerTrust, test.isServerTrust)
157		require.Equal(t, (single != nil), test.hasSingle)
158		if test.isError {
159			require.Error(t, err)
160			require.Nil(t, full)
161		} else {
162			require.NoError(t, err)
163			require.NotNil(t, full)
164		}
165	}
166}
167
168func TestAllowPukless(t *testing.T) {
169	tc, _, other, teamname := setupPuklessInviteTest(t)
170	defer tc.Cleanup()
171
172	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
173		Name:      teamname,
174		NeedAdmin: true,
175	})
176	require.NoError(t, err)
177
178	assertError := func(err error) {
179		require.Error(t, err)
180		require.IsType(t, err, UserPUKlessError{})
181		require.Contains(t, err.Error(), other.Username)
182		require.Contains(t, err.Error(), other.GetUserVersion().String())
183	}
184
185	tx := CreateAddMemberTx(team)
186	tx.AllowPUKless = false // explicitly disallow, but it's also the default.
187	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
188	assertError(err)
189
190	err = tx.AddMemberByUV(context.Background(), other.GetUserVersion(), keybase1.TeamRole_WRITER, nil /* botSettings */)
191	assertError(err)
192
193	{
194		username, uv, invite, err := tx.AddOrInviteMemberByAssertion(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
195		assertError(err)
196		// All this stuff is still returned despite an error
197		require.Equal(t, other.NormalizedUsername(), username)
198		require.Equal(t, other.GetUserVersion(), uv)
199		// But we aren't actually "inviting" them because of transaction setting.
200		require.False(t, invite)
201	}
202
203	{
204		candidate, err := tx.ResolveUPKV2FromAssertion(tc.MetaContext(), other.Username)
205		require.NoError(t, err)
206		username, uv, invite, err := tx.AddOrInviteMemberCandidate(context.Background(), candidate, keybase1.TeamRole_WRITER, nil /* botSettings */)
207		assertError(err)
208		// All this stuff is still returned despite an error
209		require.Equal(t, other.NormalizedUsername(), username)
210		require.Equal(t, other.GetUserVersion(), uv)
211		// But we aren't actually "inviting" them because of transaction setting.
212		require.False(t, invite)
213	}
214}
215
216func TestPostAllowPUKless(t *testing.T) {
217	tc, _, other, teamname := setupPuklessInviteTest(t)
218	defer tc.Cleanup()
219
220	team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{
221		Name:      teamname,
222		NeedAdmin: true,
223	})
224	require.NoError(t, err)
225
226	tx := CreateAddMemberTx(team)
227	tx.AllowPUKless = true
228	err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
229	require.NoError(t, err)
230
231	// Disallow PUKless after we have already added a PUKless user.
232	tx.AllowPUKless = false
233	err = tx.Post(tc.MetaContext())
234	require.Error(t, err)
235	// Make sure it's the error about AllowPUKless.
236	require.Contains(t, err.Error(), "AllowPUKless")
237}
238
239func TestTransactionRoleChanges(t *testing.T) {
240	tc := SetupTest(t, "team", 1)
241	defer tc.Cleanup()
242
243	tc.Tp.SkipSendingSystemChatMessages = true
244
245	user := kbtest.TCreateFakeUser(tc)
246	kbtest.TCreateFakeUser(tc) // owner
247
248	_, teamID := createTeam2(tc)
249
250	res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_READER,
251		nil /* botSettings */, nil /* emailInviteMsg */)
252	require.NoError(t, err)
253	require.False(t, res.Invited)
254
255	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
256	require.NoError(t, err)
257
258	tx := CreateAddMemberTx(team)
259	// Try to upgrade role without `AllowRoleChanges` first.
260	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
261	require.Error(t, err)
262	require.IsType(t, libkb.ExistsError{}, err)
263
264	require.Len(t, tx.payloads, 0) // should not have changed transaction
265	require.NoError(t, tx.err)     // should not be a permanent error
266
267	// Set `AllowRoleChanges`.
268	tx.AllowRoleChanges = true
269
270	// Trying to add with same role as current is still an error.
271	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */)
272	require.Error(t, err)
273	require.IsType(t, libkb.ExistsError{}, err)
274
275	// We can set a different role though (READER -> WRITER)
276	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
277	require.NoError(t, err)
278
279	err = tx.Post(tc.MetaContext())
280	require.NoError(t, err)
281
282	// See if role change worked
283	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
284	require.NoError(t, err)
285	role, err := team.MemberRole(tc.Context(), user.GetUserVersion())
286	require.NoError(t, err)
287	require.Equal(t, keybase1.TeamRole_WRITER, role)
288
289	// Should be able to go the other way as well (WRITER -> READER).
290	tx = CreateAddMemberTx(team)
291	tx.AllowRoleChanges = true
292	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */)
293	require.NoError(t, err)
294
295	err = tx.Post(tc.MetaContext())
296	require.NoError(t, err)
297
298	// See if it worked.
299	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
300	require.NoError(t, err)
301	role, err = team.MemberRole(tc.Context(), user.GetUserVersion())
302	require.NoError(t, err)
303	require.Equal(t, keybase1.TeamRole_READER, role)
304
305	userLog := team.chain().inner.UserLog[user.GetUserVersion()]
306	require.Len(t, userLog, 3)
307}
308
309func TestTransactionEmailExists(t *testing.T) {
310	tc := SetupTest(t, "team", 1)
311	defer tc.Cleanup()
312
313	kbtest.TCreateFakeUser(tc)
314	_, teamID := createTeam2(tc)
315
316	randomEmail := kbtest.GenerateRandomEmailAddress()
317	err := InviteEmailPhoneMember(tc.Context(), tc.G, teamID, randomEmail.String(), "email", keybase1.TeamRole_WRITER)
318	require.NoError(t, err)
319
320	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
321	require.NoError(t, err)
322
323	invite, err := team.chain().FindActiveInviteString(tc.MetaContext(), randomEmail.String(), "email")
324	require.NoError(t, err)
325	require.Equal(t, keybase1.TeamRole_WRITER, invite.Role)
326
327	tx := CreateAddMemberTx(team)
328	tx.AllowRoleChanges = true
329
330	assertion := fmt.Sprintf("[%s]@email", randomEmail)
331
332	// Check if we can catch this error and continue forward
333	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
334	require.Error(t, err)
335	require.IsType(t, libkb.ExistsError{}, err)
336
337	// Changing roles of an invite using AddMemberTx is not possible right now.
338	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */)
339	require.Error(t, err)
340	require.IsType(t, libkb.ExistsError{}, err)
341
342	// Two errors above should not have tainted the transaction.
343	require.Len(t, tx.payloads, 0)
344	require.NoError(t, tx.err)
345}
346
347func TestTransactionResolvableEmailExists(t *testing.T) {
348	// Similar test but with resolvable email.
349	tc := SetupTest(t, "team", 1)
350	defer tc.Cleanup()
351
352	user := kbtest.TCreateFakeUser(tc)
353
354	// Add and verify email address.
355	usersEmail := kbtest.GenerateRandomEmailAddress()
356	err := emails.AddEmail(tc.MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC)
357	require.NoError(t, err)
358	err = kbtest.VerifyEmailAuto(tc.MetaContext(), usersEmail)
359	require.NoError(t, err)
360
361	kbtest.TCreateFakeUser(tc) // owner
362
363	_, teamID := createTeam2(tc)
364	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
365	require.NoError(t, err)
366
367	assertion := fmt.Sprintf("[%s]@email", usersEmail)
368
369	// Invite email for the first time, should resolve and add user.
370	tx := CreateAddMemberTx(team)
371
372	username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
373	require.NoError(t, err)
374	require.Equal(t, user.NormalizedUsername(), username)
375	require.Equal(t, user.GetUserVersion(), uv)
376	require.False(t, invited)
377
378	err = tx.Post(tc.MetaContext())
379	require.NoError(t, err)
380
381	// Ensure they were added as member (team.MemberRole).
382	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
383	require.NoError(t, err)
384	role, err := team.MemberRole(tc.Context(), user.GetUserVersion())
385	require.NoError(t, err)
386	require.Equal(t, keybase1.TeamRole_WRITER, role)
387	// And that e-mail wasn't added as invite.
388	hasInvite, err := team.HasActiveInvite(tc.MetaContext(), keybase1.TeamInviteName(usersEmail), "email")
389	require.NoError(t, err)
390	require.False(t, hasInvite)
391
392	// Try again, should fail.
393	tx = CreateAddMemberTx(team)
394	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
395	require.Error(t, err)
396	require.IsType(t, libkb.ExistsError{}, err)
397
398	require.Len(t, tx.payloads, 0)
399	require.NoError(t, tx.err)
400
401	// Role changes are possible with `AllowRoleChanges` because they are
402	// crypto-member.
403	tx = CreateAddMemberTx(team)
404	tx.AllowRoleChanges = true
405	_, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */)
406	require.NoError(t, err)
407
408	err = tx.Post(tc.MetaContext())
409	require.NoError(t, err)
410
411	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
412	require.NoError(t, err)
413	role, err = team.MemberRole(tc.Context(), user.GetUserVersion())
414	require.NoError(t, err)
415	require.Equal(t, keybase1.TeamRole_READER, role)
416}
417
418func TestTransactionAddEmailPukless(t *testing.T) {
419	// Add e-mail that resolves to a PUK-less user.
420
421	fus, tcs, cleanup := setupNTestsWithPukless(t, 2, 1)
422	defer cleanup()
423
424	usersEmail := kbtest.GenerateRandomEmailAddress()
425	err := emails.AddEmail(tcs[1].MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC)
426	require.NoError(t, err)
427	err = kbtest.VerifyEmailAuto(tcs[1].MetaContext(), usersEmail)
428	require.NoError(t, err)
429
430	_, teamID := createTeam2(*tcs[0])
431	team, err := GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */)
432	require.NoError(t, err)
433
434	assertion := fmt.Sprintf("[%s]@email", usersEmail)
435
436	tx := CreateAddMemberTx(team)
437	// Can't add without AllowPUKless.
438	_, _, _, err = tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
439	require.Error(t, err)
440	require.IsType(t, UserPUKlessError{}, err)
441
442	// Failure to add should have left the transaction unmodified.
443	require.Len(t, tx.payloads, 0)
444	require.NoError(t, tx.err)
445
446	tx.AllowPUKless = true
447	username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */)
448	require.NoError(t, err)
449	require.True(t, invited)
450	require.Equal(t, fus[1].NormalizedUsername(), username)
451	require.Equal(t, fus[1].GetUserVersion(), uv)
452
453	err = tx.Post(tcs[0].MetaContext())
454	require.NoError(t, err)
455
456	team, err = GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */)
457	require.NoError(t, err)
458	_, uv, found := team.FindActiveKeybaseInvite(fus[1].GetUID())
459	require.True(t, found)
460	require.Equal(t, fus[1].GetUserVersion(), uv)
461
462	found, err = team.HasActiveInvite(tcs[0].MetaContext(), keybase1.TeamInviteName(usersEmail), "email")
463	require.NoError(t, err)
464	require.False(t, found)
465}
466
467func TestTransactionDowngradeAdmin(t *testing.T) {
468	tc := SetupTest(t, "team", 1)
469	defer tc.Cleanup()
470
471	user := kbtest.TCreateFakeUser(tc)
472	kbtest.TCreateFakeUser(tc) // owner
473
474	_, teamID := createTeam2(tc)
475
476	// Add user as admin.
477	res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_ADMIN,
478		nil /* botSettings */, nil /* emailInviteMsg */)
479	require.NoError(t, err)
480	require.False(t, res.Invited)
481
482	// Load team, change role of user to writer (from admin).
483	team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
484	require.NoError(t, err)
485
486	memberRole, err := team.MemberRole(tc.Context(), user.GetUserVersion())
487	require.NoError(t, err)
488	require.Equal(t, keybase1.TeamRole_ADMIN, memberRole)
489
490	tx := CreateAddMemberTx(team)
491	tx.AllowRoleChanges = true
492	err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */)
493	require.NoError(t, err)
494	require.Len(t, tx.payloads, 1)
495
496	err = tx.Post(tc.MetaContext())
497	require.NoError(t, err)
498
499	// See if it worked.
500	team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */)
501	require.NoError(t, err)
502
503	memberRole, err = team.MemberRole(tc.Context(), user.GetUserVersion())
504	require.NoError(t, err)
505	require.Equal(t, keybase1.TeamRole_WRITER, memberRole)
506}
507