Monorepo for Tangled
tangled.org
1package rbac_test
2
3import (
4 "database/sql"
5 "testing"
6
7 "tangled.org/core/rbac"
8
9 adapter "github.com/Blank-Xu/sql-adapter"
10 "github.com/casbin/casbin/v2"
11 "github.com/casbin/casbin/v2/model"
12 _ "github.com/mattn/go-sqlite3"
13 "github.com/stretchr/testify/assert"
14)
15
16func setup(t *testing.T) *rbac.Enforcer {
17 db, err := sql.Open("sqlite3", ":memory:?_foreign_keys=1")
18 assert.NoError(t, err)
19
20 a, err := adapter.NewAdapter(db, "sqlite3", "acl")
21 assert.NoError(t, err)
22
23 m, err := model.NewModelFromString(rbac.Model)
24 assert.NoError(t, err)
25
26 e, err := casbin.NewSyncedEnforcer(m, a)
27 assert.NoError(t, err)
28
29 e.EnableAutoSave(false)
30
31 return &rbac.Enforcer{E: e}
32}
33
34func TestAddKnotAndRoles(t *testing.T) {
35 e := setup(t)
36
37 err := e.AddKnot("example.com")
38 assert.NoError(t, err)
39
40 err = e.AddKnotOwner("example.com", "did:plc:foo")
41 assert.NoError(t, err)
42
43 isOwner, err := e.IsKnotOwner("did:plc:foo", "example.com")
44 assert.NoError(t, err)
45 assert.True(t, isOwner)
46
47 isMember, err := e.IsKnotMember("did:plc:foo", "example.com")
48 assert.NoError(t, err)
49 assert.True(t, isMember)
50}
51
52func TestAddMember(t *testing.T) {
53 e := setup(t)
54
55 err := e.AddKnot("example.com")
56 assert.NoError(t, err)
57
58 err = e.AddKnotOwner("example.com", "did:plc:foo")
59 assert.NoError(t, err)
60
61 err = e.AddKnotMember("example.com", "did:plc:bar")
62 assert.NoError(t, err)
63
64 isMember, err := e.IsKnotMember("did:plc:foo", "example.com")
65 assert.NoError(t, err)
66 assert.True(t, isMember)
67
68 isMember, err = e.IsKnotMember("did:plc:bar", "example.com")
69 assert.NoError(t, err)
70 assert.True(t, isMember)
71
72 isOwner, err := e.IsKnotOwner("did:plc:foo", "example.com")
73 assert.NoError(t, err)
74 assert.True(t, isOwner)
75
76 // negated check here
77 isOwner, err = e.IsKnotOwner("did:plc:bar", "example.com")
78 assert.NoError(t, err)
79 assert.False(t, isOwner)
80}
81
82func TestAddRepoPermissions(t *testing.T) {
83 e := setup(t)
84
85 knot := "example.com"
86
87 fooUser := "did:plc:foo"
88 fooRepo := "did:plc:foo/my-repo"
89
90 barUser := "did:plc:bar"
91 barRepo := "did:plc:bar/my-repo"
92
93 _ = e.AddKnot(knot)
94 _ = e.AddKnotMember(knot, fooUser)
95 _ = e.AddKnotMember(knot, barUser)
96
97 err := e.AddRepo(fooUser, knot, fooRepo)
98 assert.NoError(t, err)
99
100 err = e.AddRepo(barUser, knot, barRepo)
101 assert.NoError(t, err)
102
103 canPush, err := e.IsPushAllowed(fooUser, knot, fooRepo)
104 assert.NoError(t, err)
105 assert.True(t, canPush)
106
107 canPush, err = e.IsPushAllowed(barUser, knot, barRepo)
108 assert.NoError(t, err)
109 assert.True(t, canPush)
110
111 // negated
112 canPush, err = e.IsPushAllowed(barUser, knot, fooRepo)
113 assert.NoError(t, err)
114 assert.False(t, canPush)
115
116 canDelete, err := e.E.Enforce(fooUser, knot, fooRepo, "repo:delete")
117 assert.NoError(t, err)
118 assert.True(t, canDelete)
119
120 // negated
121 canDelete, err = e.E.Enforce(barUser, knot, fooRepo, "repo:delete")
122 assert.NoError(t, err)
123 assert.False(t, canDelete)
124}
125
126func TestCollaboratorPermissions(t *testing.T) {
127 e := setup(t)
128
129 knot := "example.com"
130 repo := "did:plc:foo/my-repo"
131 owner := "did:plc:foo"
132 collaborator := "did:plc:bar"
133
134 _ = e.AddKnot(knot)
135 _ = e.AddRepo(owner, knot, repo)
136
137 err := e.AddCollaborator(collaborator, knot, repo)
138 assert.NoError(t, err)
139
140 // all collaborator permissions granted
141 perms := e.GetPermissionsInRepo(collaborator, knot, repo)
142 assert.ElementsMatch(t, []string{
143 "repo:settings", "repo:push", "repo:collaborator",
144 }, perms)
145
146 err = e.RemoveCollaborator(collaborator, knot, repo)
147 assert.NoError(t, err)
148
149 // all permissions removed
150 perms = e.GetPermissionsInRepo(collaborator, knot, repo)
151 assert.ElementsMatch(t, []string{}, perms)
152}
153
154func TestGetCollaboratorsByRepo(t *testing.T) {
155 e := setup(t)
156
157 knot := "example.com"
158 repo := "did:plc:foo/my-repo"
159 otherRepo := "did:plc:foo/other-repo"
160 owner := "did:plc:foo"
161 collaborator1 := "did:plc:bar"
162 collaborator2 := "did:plc:baz"
163
164 _ = e.AddKnot(knot)
165 _ = e.AddRepo(owner, knot, repo)
166 _ = e.AddRepo(owner, knot, otherRepo)
167
168 err := e.AddCollaborator(collaborator1, knot, repo)
169 assert.NoError(t, err)
170
171 err = e.AddCollaborator(collaborator2, knot, repo)
172 assert.NoError(t, err)
173
174 byRepo, err := e.GetCollaboratorsByRepo(knot)
175 assert.NoError(t, err)
176 assert.ElementsMatch(t, []string{
177 "did:plc:bar", // collaborator1
178 "did:plc:baz", // collaborator2
179 }, byRepo[repo])
180 assert.NotContains(t, byRepo[repo], owner, "owner does not hold repo:collaborator and must not be listed")
181 assert.Empty(t, byRepo[otherRepo], "a repo without collaborators must not appear")
182}
183
184func TestGetPermissionsInRepo(t *testing.T) {
185 e := setup(t)
186
187 user := "did:plc:foo"
188 knot := "example.com"
189 repo := "did:plc:foo/my-repo"
190
191 _ = e.AddKnot(knot)
192 _ = e.AddRepo(user, knot, repo)
193
194 perms := e.GetPermissionsInRepo(user, knot, repo)
195 assert.ElementsMatch(t, []string{
196 "repo:settings", "repo:push", "repo:owner", "repo:invite", "repo:delete",
197 }, perms)
198}
199
200func TestInvalidRepoFormat(t *testing.T) {
201 e := setup(t)
202
203 err := e.AddRepo("did:plc:foo", "example.com", "not-valid-format")
204 assert.Error(t, err)
205}
206
207func TestGetKnotssForUser(t *testing.T) {
208 e := setup(t)
209 _ = e.AddKnot("example.com")
210 _ = e.AddKnotOwner("example.com", "did:plc:foo")
211 _ = e.AddKnotMember("example.com", "did:plc:bar")
212
213 knots1, _ := e.GetKnotsForUser("did:plc:foo")
214 assert.Contains(t, knots1, "example.com")
215
216 knots2, _ := e.GetKnotsForUser("did:plc:bar")
217 assert.Contains(t, knots2, "example.com")
218}
219
220func TestGetKnotUsersByRole(t *testing.T) {
221 e := setup(t)
222 _ = e.AddKnot("example.com")
223 _ = e.AddKnotMember("example.com", "did:plc:foo")
224 _ = e.AddKnotOwner("example.com", "did:plc:bar")
225
226 members, _ := e.GetKnotUsersByRole("server:member", "example.com")
227 assert.Contains(t, members, "did:plc:foo")
228 assert.Contains(t, members, "did:plc:bar") // due to inheritance
229}
230
231func TestGetSpindleUsersByRole(t *testing.T) {
232 e := setup(t)
233 _ = e.AddSpindle("example.com")
234 _ = e.AddSpindleMember("example.com", "did:plc:foo")
235 _ = e.AddSpindleOwner("example.com", "did:plc:bar")
236
237 members, _ := e.GetSpindleUsersByRole("server:member", "example.com")
238 assert.Contains(t, members, "did:plc:foo")
239 assert.Contains(t, members, "did:plc:bar") // due to inheritance
240}
241
242func TestEmptyUserPermissions(t *testing.T) {
243 e := setup(t)
244 allowed, _ := e.IsPushAllowed("did:plc:nobody", "unknown.com", "did:plc:nobody/repo")
245 assert.False(t, allowed)
246}
247
248func TestDuplicatePolicyAddition(t *testing.T) {
249 e := setup(t)
250 _ = e.AddKnot("example.com")
251 _ = e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
252
253 // add again
254 err := e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
255 assert.NoError(t, err) // should not fail, but won't duplicate
256}
257
258func TestRemoveRepo(t *testing.T) {
259 e := setup(t)
260 repo := "did:plc:foo/repo"
261 _ = e.AddKnot("example.com")
262 _ = e.AddRepo("did:plc:foo", "example.com", repo)
263
264 allowed, _ := e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
265 assert.True(t, allowed)
266
267 _ = e.RemoveRepo("did:plc:foo", "example.com", repo)
268
269 allowed, _ = e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
270 assert.False(t, allowed)
271}
272
273func TestAddKnotAndSpindle(t *testing.T) {
274 e := setup(t)
275
276 err := e.AddKnot("k.com")
277 assert.NoError(t, err)
278
279 err = e.AddSpindle("s.com")
280 assert.NoError(t, err)
281
282 err = e.AddKnotOwner("k.com", "did:plc:foo")
283 assert.NoError(t, err)
284
285 err = e.AddSpindleOwner("s.com", "did:plc:foo")
286 assert.NoError(t, err)
287
288 knots, err := e.GetKnotsForUser("did:plc:foo")
289 assert.NoError(t, err)
290 assert.ElementsMatch(t, []string{
291 "k.com",
292 }, knots)
293
294 spindles, err := e.GetSpindlesForUser("did:plc:foo")
295 assert.NoError(t, err)
296 assert.ElementsMatch(t, []string{
297 "s.com",
298 }, spindles)
299}
300
301func TestAddSpindleAndRoles(t *testing.T) {
302 e := setup(t)
303
304 err := e.AddSpindle("s.com")
305 assert.NoError(t, err)
306
307 err = e.AddSpindleOwner("s.com", "did:plc:foo")
308 assert.NoError(t, err)
309
310 ok, err := e.IsSpindleOwner("did:plc:foo", "s.com")
311 assert.NoError(t, err)
312 assert.True(t, ok)
313
314 ok, err = e.IsSpindleMember("did:plc:foo", "s.com")
315 assert.NoError(t, err)
316 assert.True(t, ok)
317}
318
319func TestRemoveKnotOwner(t *testing.T) {
320 e := setup(t)
321
322 err := e.AddKnot("k.com")
323 assert.NoError(t, err)
324
325 err = e.AddKnotOwner("k.com", "did:plc:foo")
326 assert.NoError(t, err)
327
328 knots, err := e.GetKnotsForUser("did:plc:foo")
329 assert.NoError(t, err)
330 assert.ElementsMatch(t, []string{
331 "k.com",
332 }, knots)
333
334 err = e.RemoveKnotOwner("k.com", "did:plc:foo")
335 assert.NoError(t, err)
336
337 knots, err = e.GetKnotsForUser("did:plc:foo")
338 assert.NoError(t, err)
339 assert.Empty(t, knots)
340}
341
342func TestRemoveKnotMember(t *testing.T) {
343 e := setup(t)
344
345 err := e.AddKnot("k.com")
346 assert.NoError(t, err)
347
348 err = e.AddKnotOwner("k.com", "did:plc:foo")
349 assert.NoError(t, err)
350
351 err = e.AddKnotMember("k.com", "did:plc:bar")
352 assert.NoError(t, err)
353
354 knots, err := e.GetKnotsForUser("did:plc:bar")
355 assert.NoError(t, err)
356 assert.ElementsMatch(t, []string{
357 "k.com",
358 }, knots)
359
360 err = e.RemoveKnotMember("k.com", "did:plc:bar")
361 assert.NoError(t, err)
362
363 knots, err = e.GetKnotsForUser("did:plc:bar")
364 assert.NoError(t, err)
365 assert.Empty(t, knots)
366}
367
368func TestRemoveKnotRemovesRepoPolicies(t *testing.T) {
369 e := setup(t)
370
371 knot := "kelp.example"
372 owner := "did:plc:akshay"
373 collaborator := "did:plc:boltless"
374 repo := "did:plc:akshay/anemone"
375
376 assert.NoError(t, e.AddKnot(knot))
377 assert.NoError(t, e.AddKnotOwner(knot, owner))
378 assert.NoError(t, e.AddRepo(owner, knot, repo))
379 assert.NoError(t, e.AddCollaborator(collaborator, knot, repo))
380
381 isOwner, err := e.IsKnotOwner(owner, knot)
382 assert.NoError(t, err)
383 assert.True(t, isOwner)
384
385 err = e.RemoveKnot(knot)
386 assert.NoError(t, err)
387
388 isOwner, err = e.IsKnotOwner(owner, knot)
389 assert.NoError(t, err)
390 assert.False(t, isOwner)
391
392 assert.Empty(t, e.GetPermissionsInRepo(owner, knot, repo))
393 assert.Empty(t, e.GetPermissionsInRepo(collaborator, knot, repo))
394}
395
396func TestRemoveSpindleOwner(t *testing.T) {
397 e := setup(t)
398
399 err := e.AddSpindle("s.com")
400 assert.NoError(t, err)
401
402 err = e.AddSpindleOwner("s.com", "did:plc:foo")
403 assert.NoError(t, err)
404
405 spindles, err := e.GetSpindlesForUser("did:plc:foo")
406 assert.NoError(t, err)
407 assert.ElementsMatch(t, []string{
408 "s.com",
409 }, spindles)
410
411 err = e.RemoveSpindleOwner("s.com", "did:plc:foo")
412 assert.NoError(t, err)
413
414 spindles, err = e.GetSpindlesForUser("did:plc:foo")
415 assert.NoError(t, err)
416 assert.Empty(t, spindles)
417}
418
419func TestRemoveSpindleMember(t *testing.T) {
420 e := setup(t)
421
422 err := e.AddSpindle("s.com")
423 assert.NoError(t, err)
424
425 err = e.AddSpindleOwner("s.com", "did:plc:foo")
426 assert.NoError(t, err)
427
428 err = e.AddSpindleMember("s.com", "did:plc:bar")
429 assert.NoError(t, err)
430
431 spindles, err := e.GetSpindlesForUser("did:plc:foo")
432 assert.NoError(t, err)
433 assert.ElementsMatch(t, []string{
434 "s.com",
435 }, spindles)
436
437 spindles, err = e.GetSpindlesForUser("did:plc:bar")
438 assert.NoError(t, err)
439 assert.ElementsMatch(t, []string{
440 "s.com",
441 }, spindles)
442
443 err = e.RemoveSpindleMember("s.com", "did:plc:bar")
444 assert.NoError(t, err)
445
446 spindles, err = e.GetSpindlesForUser("did:plc:bar")
447 assert.NoError(t, err)
448 assert.Empty(t, spindles)
449}
450
451func TestRemoveSpindle(t *testing.T) {
452 e := setup(t)
453
454 err := e.AddSpindle("s.com")
455 assert.NoError(t, err)
456
457 err = e.AddSpindleOwner("s.com", "did:plc:foo")
458 assert.NoError(t, err)
459
460 err = e.AddSpindleMember("s.com", "did:plc:bar")
461 assert.NoError(t, err)
462
463 users, err := e.GetSpindleUsersByRole("server:member", "s.com")
464 assert.NoError(t, err)
465 assert.ElementsMatch(t, []string{
466 "did:plc:foo",
467 "did:plc:bar",
468 }, users)
469
470 err = e.RemoveSpindle("s.com")
471 assert.NoError(t, err)
472
473 // TODO: see this issue https://github.com/casbin/casbin/issues/1492
474 // s, err := e.E.GetAllDomains()
475 // assert.Empty(t, s)
476
477 spindles, err := e.GetSpindleUsersByRole("server:member", "s.com")
478 assert.NoError(t, err)
479 assert.Empty(t, spindles)
480}
481
482func TestWipeRepoPoliciesRemovesCollaborators(t *testing.T) {
483 e := setup(t)
484
485 knot := "example.com"
486 repo := "did:plc:akshay/my-repo"
487 owner := "did:plc:akshay"
488 collaborator := "did:plc:boltless"
489
490 _ = e.AddKnot(knot)
491 _ = e.AddRepo(owner, knot, repo)
492 err := e.AddCollaborator(collaborator, knot, repo)
493 assert.NoError(t, err)
494
495 err = e.WipeRepoPolicies(knot, repo)
496 assert.NoError(t, err)
497
498 assert.ElementsMatch(t, []string{}, e.GetPermissionsInRepo(collaborator, knot, repo),
499 "collaborator policies must be wiped on repo teardown")
500 assert.ElementsMatch(t, []string{}, e.GetPermissionsInRepo(owner, knot, repo),
501 "owner policies must be wiped on repo teardown")
502}