1package rbac_test
2
3import (
4 "database/sql"
5 "testing"
6
7 "tangled.sh/tangled.sh/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.NewEnforcer(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 TestGetByRole(t *testing.T) {
155 e := setup(t)
156
157 knot := "example.com"
158 repo := "did:plc:foo/my-repo"
159 owner := "did:plc:foo"
160 collaborator1 := "did:plc:bar"
161 collaborator2 := "did:plc:baz"
162
163 _ = e.AddKnot(knot)
164 _ = e.AddRepo(owner, knot, repo)
165
166 err := e.AddCollaborator(collaborator1, knot, repo)
167 assert.NoError(t, err)
168
169 err = e.AddCollaborator(collaborator2, knot, repo)
170 assert.NoError(t, err)
171
172 collaborators, err := e.GetUserByRoleInRepo("repo:collaborator", knot, repo)
173 assert.NoError(t, err)
174 assert.ElementsMatch(t, []string{
175 "did:plc:foo", // owner
176 "did:plc:bar", // collaborator1
177 "did:plc:baz", // collaborator2
178 }, collaborators)
179}
180
181func TestGetPermissionsInRepo(t *testing.T) {
182 e := setup(t)
183
184 user := "did:plc:foo"
185 knot := "example.com"
186 repo := "did:plc:foo/my-repo"
187
188 _ = e.AddKnot(knot)
189 _ = e.AddRepo(user, knot, repo)
190
191 perms := e.GetPermissionsInRepo(user, knot, repo)
192 assert.ElementsMatch(t, []string{
193 "repo:settings", "repo:push", "repo:owner", "repo:invite", "repo:delete",
194 }, perms)
195}
196
197func TestInvalidRepoFormat(t *testing.T) {
198 e := setup(t)
199
200 err := e.AddRepo("did:plc:foo", "example.com", "not-valid-format")
201 assert.Error(t, err)
202}
203
204func TestGetKnotssForUser(t *testing.T) {
205 e := setup(t)
206 _ = e.AddKnot("example.com")
207 _ = e.AddKnotOwner("example.com", "did:plc:foo")
208 _ = e.AddKnotMember("example.com", "did:plc:bar")
209
210 knots1, _ := e.GetKnotsForUser("did:plc:foo")
211 assert.Contains(t, knots1, "example.com")
212
213 knots2, _ := e.GetKnotsForUser("did:plc:bar")
214 assert.Contains(t, knots2, "example.com")
215}
216
217func TestGetKnotUsersByRole(t *testing.T) {
218 e := setup(t)
219 _ = e.AddKnot("example.com")
220 _ = e.AddKnotMember("example.com", "did:plc:foo")
221 _ = e.AddKnotOwner("example.com", "did:plc:bar")
222
223 members, _ := e.GetKnotUsersByRole("server:member", "example.com")
224 assert.Contains(t, members, "did:plc:foo")
225 assert.Contains(t, members, "did:plc:bar") // due to inheritance
226}
227
228func TestGetSpindleUsersByRole(t *testing.T) {
229 e := setup(t)
230 _ = e.AddSpindle("example.com")
231 _ = e.AddSpindleMember("example.com", "did:plc:foo")
232 _ = e.AddSpindleOwner("example.com", "did:plc:bar")
233
234 members, _ := e.GetSpindleUsersByRole("server:member", "example.com")
235 assert.Contains(t, members, "did:plc:foo")
236 assert.Contains(t, members, "did:plc:bar") // due to inheritance
237}
238
239func TestEmptyUserPermissions(t *testing.T) {
240 e := setup(t)
241 allowed, _ := e.IsPushAllowed("did:plc:nobody", "unknown.com", "did:plc:nobody/repo")
242 assert.False(t, allowed)
243}
244
245func TestDuplicatePolicyAddition(t *testing.T) {
246 e := setup(t)
247 _ = e.AddKnot("example.com")
248 _ = e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
249
250 // add again
251 err := e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
252 assert.NoError(t, err) // should not fail, but won't duplicate
253}
254
255func TestRemoveRepo(t *testing.T) {
256 e := setup(t)
257 repo := "did:plc:foo/repo"
258 _ = e.AddKnot("example.com")
259 _ = e.AddRepo("did:plc:foo", "example.com", repo)
260
261 allowed, _ := e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
262 assert.True(t, allowed)
263
264 _ = e.RemoveRepo("did:plc:foo", "example.com", repo)
265
266 allowed, _ = e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
267 assert.False(t, allowed)
268}
269
270func TestAddKnotAndSpindle(t *testing.T) {
271 e := setup(t)
272
273 err := e.AddKnot("k.com")
274 assert.NoError(t, err)
275
276 err = e.AddSpindle("s.com")
277 assert.NoError(t, err)
278
279 err = e.AddKnotOwner("k.com", "did:plc:foo")
280 assert.NoError(t, err)
281
282 err = e.AddSpindleOwner("s.com", "did:plc:foo")
283 assert.NoError(t, err)
284
285 knots, err := e.GetKnotsForUser("did:plc:foo")
286 assert.NoError(t, err)
287 assert.ElementsMatch(t, []string{
288 "k.com",
289 }, knots)
290
291 spindles, err := e.GetSpindlesForUser("did:plc:foo")
292 assert.NoError(t, err)
293 assert.ElementsMatch(t, []string{
294 "s.com",
295 }, spindles)
296}
297
298func TestAddSpindleAndRoles(t *testing.T) {
299 e := setup(t)
300
301 err := e.AddSpindle("s.com")
302 assert.NoError(t, err)
303
304 err = e.AddSpindleOwner("s.com", "did:plc:foo")
305 assert.NoError(t, err)
306
307 ok, err := e.IsSpindleOwner("did:plc:foo", "s.com")
308 assert.NoError(t, err)
309 assert.True(t, ok)
310
311 ok, err = e.IsSpindleMember("did:plc:foo", "s.com")
312 assert.NoError(t, err)
313 assert.True(t, ok)
314}
315
316func TestRemoveKnotOwner(t *testing.T) {
317 e := setup(t)
318
319 err := e.AddKnot("k.com")
320 assert.NoError(t, err)
321
322 err = e.AddKnotOwner("k.com", "did:plc:foo")
323 assert.NoError(t, err)
324
325 knots, err := e.GetKnotsForUser("did:plc:foo")
326 assert.NoError(t, err)
327 assert.ElementsMatch(t, []string{
328 "k.com",
329 }, knots)
330
331 err = e.RemoveKnotOwner("k.com", "did:plc:foo")
332 assert.NoError(t, err)
333
334 knots, err = e.GetKnotsForUser("did:plc:foo")
335 assert.NoError(t, err)
336 assert.Empty(t, knots)
337}
338
339func TestRemoveKnotMember(t *testing.T) {
340 e := setup(t)
341
342 err := e.AddKnot("k.com")
343 assert.NoError(t, err)
344
345 err = e.AddKnotOwner("k.com", "did:plc:foo")
346 assert.NoError(t, err)
347
348 err = e.AddKnotMember("k.com", "did:plc:bar")
349 assert.NoError(t, err)
350
351 knots, err := e.GetKnotsForUser("did:plc:bar")
352 assert.NoError(t, err)
353 assert.ElementsMatch(t, []string{
354 "k.com",
355 }, knots)
356
357 err = e.RemoveKnotMember("k.com", "did:plc:bar")
358 assert.NoError(t, err)
359
360 knots, err = e.GetKnotsForUser("did:plc:bar")
361 assert.NoError(t, err)
362 assert.Empty(t, knots)
363}
364
365func TestRemoveSpindleOwner(t *testing.T) {
366 e := setup(t)
367
368 err := e.AddSpindle("s.com")
369 assert.NoError(t, err)
370
371 err = e.AddSpindleOwner("s.com", "did:plc:foo")
372 assert.NoError(t, err)
373
374 spindles, err := e.GetSpindlesForUser("did:plc:foo")
375 assert.NoError(t, err)
376 assert.ElementsMatch(t, []string{
377 "s.com",
378 }, spindles)
379
380 err = e.RemoveSpindleOwner("s.com", "did:plc:foo")
381 assert.NoError(t, err)
382
383 spindles, err = e.GetSpindlesForUser("did:plc:foo")
384 assert.NoError(t, err)
385 assert.Empty(t, spindles)
386}
387
388func TestRemoveSpindleMember(t *testing.T) {
389 e := setup(t)
390
391 err := e.AddSpindle("s.com")
392 assert.NoError(t, err)
393
394 err = e.AddSpindleOwner("s.com", "did:plc:foo")
395 assert.NoError(t, err)
396
397 err = e.AddSpindleMember("s.com", "did:plc:bar")
398 assert.NoError(t, err)
399
400 spindles, err := e.GetSpindlesForUser("did:plc:foo")
401 assert.NoError(t, err)
402 assert.ElementsMatch(t, []string{
403 "s.com",
404 }, spindles)
405
406 spindles, err = e.GetSpindlesForUser("did:plc:bar")
407 assert.NoError(t, err)
408 assert.ElementsMatch(t, []string{
409 "s.com",
410 }, spindles)
411
412 err = e.RemoveSpindleMember("s.com", "did:plc:bar")
413 assert.NoError(t, err)
414
415 spindles, err = e.GetSpindlesForUser("did:plc:bar")
416 assert.NoError(t, err)
417 assert.Empty(t, spindles)
418}
419
420func TestRemoveSpindle(t *testing.T) {
421 e := setup(t)
422
423 err := e.AddSpindle("s.com")
424 assert.NoError(t, err)
425
426 err = e.AddSpindleOwner("s.com", "did:plc:foo")
427 assert.NoError(t, err)
428
429 err = e.AddSpindleMember("s.com", "did:plc:bar")
430 assert.NoError(t, err)
431
432 users, err := e.GetSpindleUsersByRole("server:member", "s.com")
433 assert.NoError(t, err)
434 assert.ElementsMatch(t, []string{
435 "did:plc:foo",
436 "did:plc:bar",
437 }, users)
438
439 err = e.RemoveSpindle("s.com")
440 assert.NoError(t, err)
441
442 // TODO: see this issue https://github.com/casbin/casbin/issues/1492
443 // s, err := e.E.GetAllDomains()
444 // assert.Empty(t, s)
445
446 spindles, err := e.GetSpindleUsersByRole("server:member", "s.com")
447 assert.NoError(t, err)
448 assert.Empty(t, spindles)
449}