1// Copyright 2017 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package git
5
6import (
7	"context"
8	"fmt"
9	"testing"
10
11	"github.com/keybase/client/go/kbfs/tlf"
12	"github.com/keybase/client/go/kbtest"
13	"github.com/keybase/client/go/libkb"
14	"github.com/keybase/client/go/protocol/chat1"
15	"github.com/keybase/client/go/protocol/keybase1"
16	"github.com/keybase/client/go/teams"
17	"github.com/stretchr/testify/require"
18)
19
20func doPut(t *testing.T, g *libkb.GlobalContext, teamName string, repoID string, repoName string) {
21	err := PutMetadata(context.Background(), g, keybase1.PutGitMetadataArg{
22		Folder: keybase1.FolderHandle{
23			Name:       teamName,
24			FolderType: keybase1.FolderType_TEAM,
25		},
26		RepoID: keybase1.RepoID(repoID),
27		Metadata: keybase1.GitLocalMetadata{
28			RepoName: keybase1.GitRepoName(repoName),
29			PushType: keybase1.GitPushType_CREATEREPO,
30		},
31	})
32	require.NoError(t, err)
33}
34
35func TestPutAndGet(t *testing.T) {
36	tc := SetupTest(t, "team", 1)
37	defer tc.Cleanup()
38
39	tc2 := SetupTest(t, "team", 1)
40	defer tc2.Cleanup()
41
42	// Note that the length limit for a team name, with the additional suffix
43	// below, is 16 characters. We have 5 to play with, including the implicit
44	// underscore after the prefix.
45	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
46	require.NoError(t, err)
47
48	u2, err := kbtest.CreateAndSignupFakeUser("t", tc2.G)
49	require.NoError(t, err)
50
51	// Create two teams, so that we can test filtering by TeamID.
52	teamName1 := u.Username + "t1"
53	_, err = teams.CreateRootTeam(context.Background(), tc.G, teamName1, keybase1.TeamSettings{})
54	require.NoError(t, err)
55	_, err = teams.AddMember(context.Background(), tc.G, teamName1, u2.Username, keybase1.TeamRole_READER, nil)
56	require.NoError(t, err)
57	team1, _, err := tc.G.GetTeamLoader().Load(context.Background(), keybase1.LoadTeamArg{Name: teamName1})
58	require.NoError(t, err)
59
60	teamName2 := u.Username + "t2"
61	_, err = teams.CreateRootTeam(context.Background(), tc.G, teamName2, keybase1.TeamSettings{})
62	require.NoError(t, err)
63
64	// Create two git repos, one in each team. Remember that all we're
65	// "creating" here is metadata.
66	doPut(t, tc.G, teamName1, "abc123", "repoNameFirst")
67	doPut(t, tc.G, teamName2, "def456", "repoNameSecond")
68	expectedIDNames := map[string]string{
69		"abc123": "repoNameFirst",
70		"def456": "repoNameSecond",
71	}
72
73	// Get all repos, and make sure both come back.
74	allRepos, err := GetAllMetadata(context.Background(), tc.G)
75	require.NoError(t, err)
76	require.Equal(t, 2, len(allRepos), "expected to get both repos back, found: %d", len(allRepos))
77	for _, repoRes := range allRepos {
78		repo, err := repoRes.GetIfOk()
79		require.NoError(t, err)
80		require.Equal(t, expectedIDNames[string(repo.RepoID)], string(repo.LocalMetadata.RepoName))
81		require.Equal(t, repo.Folder.FolderType, keybase1.FolderType_TEAM)
82		require.Equal(t, repo.ServerMetadata.LastModifyingUsername, u.Username)
83		require.Equal(t, repo.CanDelete, true)
84	}
85
86	// Now get the repos for just one team. Should be only one of the two we just created.
87	oneRepoList, err := GetMetadata(context.Background(), tc.G, keybase1.FolderHandle{
88		Name:       teamName1,
89		FolderType: keybase1.FolderType_TEAM,
90	})
91	require.NoError(t, err)
92	require.Equal(t, 1, len(oneRepoList), "expected to get only one repo back, found: %d", len(oneRepoList))
93	oneRepo, err := oneRepoList[0].GetIfOk()
94	require.NoError(t, err)
95	require.Equal(t, "repoNameFirst", string(oneRepo.LocalMetadata.RepoName))
96	require.Equal(t, kbtest.DefaultDeviceName, oneRepo.ServerMetadata.LastModifyingDeviceName)
97	require.Equal(t, string(team1.Chain.Id+"_abc123"), oneRepo.GlobalUniqueID)
98	require.Equal(t, "keybase://team/"+teamName1+"/repoNameFirst", oneRepo.RepoUrl)
99	require.Equal(t, oneRepo.CanDelete, true)
100
101	// check that the chat system messages were sent for the two doPut calls above
102	msgs := kbtest.MockSentMessages(tc.G, tc.T)
103	require.Len(t, msgs, 2)
104	require.Equal(t, msgs[0].MsgType, chat1.MessageType_SYSTEM)
105	require.Equal(t, msgs[1].MsgType, chat1.MessageType_SYSTEM)
106	require.Equal(t, msgs[0].Body.System().Gitpush().RepoName, "repoNameFirst")
107	require.Equal(t, msgs[1].Body.System().Gitpush().RepoName, "repoNameSecond")
108
109	t.Logf("reset first user")
110	ResetAccountAndLogout(tc, u)
111
112	t.Logf("we can still read metadata last posted by reset user")
113	oneRepoList, err = GetMetadata(context.Background(), tc2.G, keybase1.FolderHandle{
114		Name:       teamName1,
115		FolderType: keybase1.FolderType_TEAM,
116	})
117	require.NoError(t, err)
118	require.Equal(t, 1, len(oneRepoList), "expected to get only one repo back, found: %d", len(oneRepoList))
119	oneRepo, err = oneRepoList[0].GetIfOk()
120	require.NoError(t, err)
121	require.Equal(t, "repoNameFirst", string(oneRepo.LocalMetadata.RepoName))
122	require.Equal(t, kbtest.DefaultDeviceName, oneRepo.ServerMetadata.LastModifyingDeviceName)
123	require.Equal(t, string(team1.Chain.Id+"_abc123"), oneRepo.GlobalUniqueID)
124	require.Equal(t, "keybase://team/"+teamName1+"/repoNameFirst", oneRepo.RepoUrl)
125	require.Equal(t, oneRepo.CanDelete, false)
126}
127
128func ResetAccountAndLogout(tc libkb.TestContext, u *kbtest.FakeUser) {
129	err := libkb.ResetAccount(libkb.NewMetaContextForTest(tc), u.NormalizedUsername(), u.Passphrase)
130	require.NoError(tc.T, err)
131	err = tc.Logout()
132	require.NoError(tc.T, err)
133}
134
135func TestDeleteRepo(t *testing.T) {
136	tc := SetupTest(t, "team", 1)
137	defer tc.Cleanup()
138
139	// Note that the length limit for a team name, with the additional suffix
140	// below, is 16 characters. We have 5 to play with, including the implicit
141	// underscore after the prefix.
142	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
143	require.NoError(t, err)
144
145	// Create a personal repo for this user. (This also exercises the
146	// personal-repos-create-an-implicit-team flow.)
147	repoName := keybase1.GitRepoName("testRepo123")
148	folder := keybase1.FolderHandle{
149		Name:       u.Username,
150		FolderType: keybase1.FolderType_PRIVATE,
151	}
152	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
153		Folder: folder,
154		RepoID: keybase1.RepoID("abc123"),
155		Metadata: keybase1.GitLocalMetadata{
156			RepoName: repoName,
157		},
158	})
159	require.NoError(t, err)
160
161	// Get all repos, and make sure we see that one.
162	allRepos, err := GetAllMetadata(context.Background(), tc.G)
163	require.NoError(t, err)
164	require.Equal(t, 1, len(allRepos), "expected to see 1 git repo, saw %d", len(allRepos))
165	firstRepo, err := allRepos[0].GetIfOk()
166	require.NoError(t, err)
167	require.Equal(t, repoName, firstRepo.LocalMetadata.RepoName)
168
169	// Now delete that repo.
170	err = DeleteMetadata(context.Background(), tc.G, folder, repoName)
171	require.NoError(t, err)
172
173	// Finally, get all repos again, and make sure it's gone.
174	allRepos, err = GetAllMetadata(context.Background(), tc.G)
175	require.NoError(t, err)
176	require.Equal(t, 0, len(allRepos), "expected the repo to get deleted")
177}
178
179func TestPutAndGetImplicitTeam(t *testing.T) {
180	testPutAndGetImplicitTeam(t, false)
181	testPutAndGetImplicitTeam(t, true)
182}
183func testPutAndGetImplicitTeam(t *testing.T, public bool) {
184	t.Logf("running with public:%v", public)
185
186	folderType := keybase1.FolderType_PRIVATE
187	if public {
188		folderType = keybase1.FolderType_PUBLIC
189	}
190
191	publicnessStr := "private"
192	if public {
193		publicnessStr = "public"
194	}
195
196	tc := SetupTest(t, "team", 1)
197	defer tc.Cleanup()
198
199	t.Logf("signup users")
200	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
201	require.NoError(t, err)
202	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
203	require.NoError(t, err)
204
205	// Create two repos, one in u2's personal directory (since we're signed in
206	// as u2 at this point), and one in the directory that u1 and u2 share. One
207	// of the things we'll be testing is that the second one does *not* show up
208	// in the results of GetAllMetadata. This is a product choice -- we want to
209	// pretend they kinda don't exist.
210
211	t.Logf("me only repo")
212	repoName1 := keybase1.GitRepoName("me only repo")
213	testFolder1 := keybase1.FolderHandle{
214		Name:       u2.Username,
215		FolderType: folderType,
216	}
217	err = PutMetadata(context.TODO(), tc.G, keybase1.PutGitMetadataArg{
218		Folder: testFolder1,
219		RepoID: keybase1.RepoID("abc123"),
220		Metadata: keybase1.GitLocalMetadata{
221			RepoName: repoName1,
222		},
223	})
224	require.NoError(t, err)
225
226	t.Logf("second repo")
227	repoName2 := keybase1.GitRepoName(fmt.Sprintf("two person %s repo", publicnessStr))
228	normalizedTLFName, err := tlf.NormalizeNamesInTLF(libkb.NewMetaContextForTest(tc), []string{u1.Username, u2.Username}, nil, "")
229	require.NoError(t, err)
230	testFolder2 := keybase1.FolderHandle{
231		Name:       normalizedTLFName,
232		FolderType: folderType,
233	}
234	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
235		Folder: testFolder2,
236		RepoID: keybase1.RepoID("abc123"),
237		Metadata: keybase1.GitLocalMetadata{
238			RepoName: repoName2,
239		},
240	})
241	require.NoError(t, err)
242
243	// Now make sure we can query these repos (or not, as appropriate for the
244	// multi-person case).
245
246	assertStuffAboutRepo := func(t *testing.T, repoRes keybase1.GitRepoResult, folder keybase1.FolderHandle, repoName keybase1.GitRepoName) {
247		repo, err := repoRes.GetIfOk()
248		require.NoError(t, err)
249		require.Equal(t, repoName, repo.LocalMetadata.RepoName)
250		require.Equal(t, folderType, repo.Folder.FolderType)
251		require.Equal(t, kbtest.DefaultDeviceName, repo.ServerMetadata.LastModifyingDeviceName)
252		require.Equal(t, "keybase://"+publicnessStr+"/"+folder.Name+"/"+string(repoName), repo.RepoUrl)
253		require.Equal(t, repo.CanDelete, true)
254	}
255
256	t.Logf("assertions")
257	// Test fetching the repo with GetMetadata.
258	oneRepo, err := GetMetadata(context.Background(), tc.G, testFolder1)
259	require.NoError(t, err)
260	require.Equal(t, 1, len(oneRepo))
261	assertStuffAboutRepo(t, oneRepo[0], testFolder1, repoName1)
262
263	// Also test fetching the 2-person repo with GetMetadata. This
264	// *should* work, even though it's hidden from GetAllMetadata.
265	oneRepo, err = GetMetadata(context.Background(), tc.G, testFolder2)
266	require.NoError(t, err)
267	require.Equal(t, 1, len(oneRepo))
268	assertStuffAboutRepo(t, oneRepo[0], testFolder2, repoName2)
269
270	// Finally test the GetAllMetadata results. This should *not* see the
271	// 2-person repo.
272	allRepos, err := GetAllMetadata(context.Background(), tc.G)
273	require.NoError(t, err)
274	require.Equal(t, 1, len(allRepos), "the two-person repo should be hidden!")
275	assertStuffAboutRepo(t, allRepos[0], testFolder1, repoName1)
276}
277
278func TestPutAndGetWritersCantDelete(t *testing.T) {
279	tc := SetupTest(t, "team", 1)
280	defer tc.Cleanup()
281
282	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
283	require.NoError(t, err)
284	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
285	require.NoError(t, err)
286
287	// u2 creates a team where u1 is just a writer
288	teamName := u2.Username + "t1"
289	_, err = teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{})
290	require.NoError(t, err)
291	_, err = teams.AddMember(context.Background(), tc.G, teamName, u1.Username, keybase1.TeamRole_WRITER, nil)
292	if err != nil {
293		t.Fatal(err)
294	}
295
296	// Create a git repo for that team
297	doPut(t, tc.G, teamName, "abc123", "dummyRepoName")
298
299	// Load the repo and confirm that u2 sees it as CanDelete=true.
300	repos, err := GetAllMetadata(context.Background(), tc.G)
301	require.NoError(t, err)
302	require.Equal(t, 1, len(repos), "expected exactly one repo")
303	firstRepo, err := repos[0].GetIfOk()
304	require.NoError(t, err)
305	require.Equal(t, true, firstRepo.CanDelete, "owners/admins should be able to delete")
306
307	// Now log in as u1, load the repo again, and confirm that u1 sees it as CanDelete=FALSE.
308	err = tc.Logout()
309	require.NoError(t, err)
310	err = u1.Login(tc.G)
311	require.NoError(t, err)
312	repos2, err := GetAllMetadata(context.Background(), tc.G)
313	require.NoError(t, err)
314	require.Equal(t, 1, len(repos2), "expected exactly one repo")
315	repo2, err := repos2[0].GetIfOk()
316	require.NoError(t, err)
317	require.Equal(t, false, repo2.CanDelete, "non-admins must not be able to delete")
318}
319