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:")
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 TestGetUserByRole(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.GetUserByRole("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 TestEmptyUserPermissions(t *testing.T) {
229 e := setup(t)
230 allowed, _ := e.IsPushAllowed("did:plc:nobody", "unknown.com", "did:plc:nobody/repo")
231 assert.False(t, allowed)
232}
233
234func TestDuplicatePolicyAddition(t *testing.T) {
235 e := setup(t)
236 _ = e.AddKnot("example.com")
237 _ = e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
238
239 // add again
240 err := e.AddRepo("did:plc:foo", "example.com", "did:plc:foo/repo")
241 assert.NoError(t, err) // should not fail, but won't duplicate
242}
243
244func TestRemoveRepo(t *testing.T) {
245 e := setup(t)
246 repo := "did:plc:foo/repo"
247 _ = e.AddKnot("example.com")
248 _ = e.AddRepo("did:plc:foo", "example.com", repo)
249
250 allowed, _ := e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
251 assert.True(t, allowed)
252
253 _ = e.RemoveRepo("did:plc:foo", "example.com", repo)
254
255 allowed, _ = e.IsSettingsAllowed("did:plc:foo", "example.com", repo)
256 assert.False(t, allowed)
257}
258
259func TestAddKnotAndSpindle(t *testing.T) {
260 e := setup(t)
261
262 err := e.AddKnot("k.com")
263 assert.NoError(t, err)
264
265 err = e.AddSpindle("s.com")
266 assert.NoError(t, err)
267
268 err = e.AddKnotOwner("k.com", "did:plc:foo")
269 assert.NoError(t, err)
270
271 err = e.AddSpindleOwner("s.com", "did:plc:foo")
272 assert.NoError(t, err)
273
274 knots, err := e.GetKnotsForUser("did:plc:foo")
275 assert.NoError(t, err)
276 assert.ElementsMatch(t, []string{
277 "k.com",
278 }, knots)
279
280 spindles, err := e.GetSpindlesForUser("did:plc:foo")
281 assert.NoError(t, err)
282 assert.ElementsMatch(t, []string{
283 "s.com",
284 }, spindles)
285}
286
287func TestAddSpindleAndRoles(t *testing.T) {
288 e := setup(t)
289
290 err := e.AddSpindle("s.com")
291 assert.NoError(t, err)
292
293 err = e.AddSpindleOwner("s.com", "did:plc:foo")
294 assert.NoError(t, err)
295
296 ok, err := e.IsSpindleOwner("did:plc:foo", "s.com")
297 assert.NoError(t, err)
298 assert.True(t, ok)
299
300 ok, err = e.IsSpindleMember("did:plc:foo", "s.com")
301 assert.NoError(t, err)
302 assert.True(t, ok)
303}