forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package rbac
2
3import (
4 "database/sql"
5 "slices"
6 "strings"
7
8 adapter "github.com/Blank-Xu/sql-adapter"
9 "github.com/casbin/casbin/v2"
10 "github.com/casbin/casbin/v2/model"
11)
12
13const (
14 Model = `
15[request_definition]
16r = sub, dom, obj, act
17
18[policy_definition]
19p = sub, dom, obj, act
20
21[role_definition]
22g = _, _, _
23
24[policy_effect]
25e = some(where (p.eft == allow))
26
27[matchers]
28m = r.act == p.act && r.dom == p.dom && r.obj == p.obj && g(r.sub, p.sub, r.dom)
29`
30)
31
32type Enforcer struct {
33 E *casbin.Enforcer
34}
35
36func NewEnforcer(path string) (*Enforcer, error) {
37 m, err := model.NewModelFromString(Model)
38 if err != nil {
39 return nil, err
40 }
41
42 db, err := sql.Open("sqlite3", path)
43 if err != nil {
44 return nil, err
45 }
46
47 a, err := adapter.NewAdapter(db, "sqlite3", "acl")
48 if err != nil {
49 return nil, err
50 }
51
52 e, err := casbin.NewEnforcer(m, a)
53 if err != nil {
54 return nil, err
55 }
56
57 e.EnableAutoSave(false)
58
59 return &Enforcer{e}, nil
60}
61
62func (e *Enforcer) AddKnot(knot string) error {
63 // Add policies with patterns
64 _, err := e.E.AddPolicies([][]string{
65 {"server:owner", knot, knot, "server:invite"},
66 {"server:member", knot, knot, "repo:create"},
67 })
68 if err != nil {
69 return err
70 }
71
72 // all owners are also members
73 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", knot)
74 return err
75}
76
77func (e *Enforcer) AddSpindle(spindle string) error {
78 // the internal repr for spindles is spindle:foo.com
79 spindle = intoSpindle(spindle)
80
81 _, err := e.E.AddPolicies([][]string{
82 {"server:owner", spindle, spindle, "server:invite"},
83 })
84 if err != nil {
85 return err
86 }
87
88 // all owners are also members
89 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", spindle)
90 return err
91}
92
93func (e *Enforcer) RemoveSpindle(spindle string) error {
94 spindle = intoSpindle(spindle)
95 _, err := e.E.DeleteDomains(spindle)
96 return err
97}
98
99func (e *Enforcer) GetKnotsForUser(did string) ([]string, error) {
100 keepFunc := isNotSpindle
101 stripFunc := unSpindle
102 return e.getDomainsForUser(did, keepFunc, stripFunc)
103}
104
105func (e *Enforcer) GetSpindlesForUser(did string) ([]string, error) {
106 keepFunc := isSpindle
107 stripFunc := unSpindle
108 return e.getDomainsForUser(did, keepFunc, stripFunc)
109}
110
111func (e *Enforcer) AddKnotOwner(domain, owner string) error {
112 return e.addOwner(domain, owner)
113}
114
115func (e *Enforcer) RemoveKnotOwner(domain, owner string) error {
116 return e.removeOwner(domain, owner)
117}
118
119func (e *Enforcer) AddKnotMember(domain, member string) error {
120 return e.addMember(domain, member)
121}
122
123func (e *Enforcer) RemoveKnotMember(domain, member string) error {
124 return e.removeMember(domain, member)
125}
126
127func (e *Enforcer) AddSpindleOwner(domain, owner string) error {
128 return e.addOwner(intoSpindle(domain), owner)
129}
130
131func (e *Enforcer) RemoveSpindleOwner(domain, owner string) error {
132 return e.removeOwner(intoSpindle(domain), owner)
133}
134
135func (e *Enforcer) AddSpindleMember(domain, member string) error {
136 return e.addMember(intoSpindle(domain), member)
137}
138
139func (e *Enforcer) RemoveSpindleMember(domain, member string) error {
140 return e.removeMember(intoSpindle(domain), member)
141}
142
143func repoPolicies(member, domain, repo string) [][]string {
144 return [][]string{
145 {member, domain, repo, "repo:settings"},
146 {member, domain, repo, "repo:push"},
147 {member, domain, repo, "repo:owner"},
148 {member, domain, repo, "repo:invite"},
149 {member, domain, repo, "repo:delete"},
150 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
151 }
152}
153func (e *Enforcer) AddRepo(member, domain, repo string) error {
154 err := checkRepoFormat(repo)
155 if err != nil {
156 return err
157 }
158
159 _, err = e.E.AddPolicies(repoPolicies(member, domain, repo))
160 return err
161}
162func (e *Enforcer) RemoveRepo(member, domain, repo string) error {
163 err := checkRepoFormat(repo)
164 if err != nil {
165 return err
166 }
167
168 _, err = e.E.RemovePolicies(repoPolicies(member, domain, repo))
169 return err
170}
171
172var (
173 collaboratorPolicies = func(collaborator, domain, repo string) [][]string {
174 return [][]string{
175 {collaborator, domain, repo, "repo:collaborator"},
176 {collaborator, domain, repo, "repo:settings"},
177 {collaborator, domain, repo, "repo:push"},
178 }
179 }
180)
181
182func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error {
183 err := checkRepoFormat(repo)
184 if err != nil {
185 return err
186 }
187
188 _, err = e.E.AddPolicies(collaboratorPolicies(collaborator, domain, repo))
189 return err
190}
191
192func (e *Enforcer) RemoveCollaborator(collaborator, domain, repo string) error {
193 err := checkRepoFormat(repo)
194 if err != nil {
195 return err
196 }
197
198 _, err = e.E.RemovePolicies(collaboratorPolicies(collaborator, domain, repo))
199 return err
200}
201
202func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
203 var membersWithoutRoles []string
204
205 // this includes roles too, casbin does not differentiate.
206 // the filtering criteria is to remove strings not starting with `did:`
207 members, err := e.E.GetImplicitUsersForRole(role, domain)
208 for _, m := range members {
209 if strings.HasPrefix(m, "did:") {
210 membersWithoutRoles = append(membersWithoutRoles, m)
211 }
212 }
213 if err != nil {
214 return nil, err
215 }
216
217 slices.Sort(membersWithoutRoles)
218 return slices.Compact(membersWithoutRoles), nil
219}
220
221func (e *Enforcer) GetKnotUsersByRole(role, domain string) ([]string, error) {
222 return e.GetUserByRole(role, domain)
223}
224
225func (e *Enforcer) GetSpindleUsersByRole(role, domain string) ([]string, error) {
226 return e.GetUserByRole(role, intoSpindle(domain))
227}
228
229func (e *Enforcer) GetUserByRoleInRepo(role, domain, repo string) ([]string, error) {
230 var users []string
231
232 policies, err := e.E.GetImplicitUsersForResourceByDomain(repo, domain)
233 for _, p := range policies {
234 user := p[0]
235 if strings.HasPrefix(user, "did:") {
236 users = append(users, user)
237 }
238 }
239 if err != nil {
240 return nil, err
241 }
242
243 slices.Sort(users)
244 return slices.Compact(users), nil
245}
246
247func (e *Enforcer) IsKnotOwner(user, domain string) (bool, error) {
248 return e.isRole(user, "server:owner", domain)
249}
250
251func (e *Enforcer) IsKnotMember(user, domain string) (bool, error) {
252 return e.isRole(user, "server:member", domain)
253}
254
255func (e *Enforcer) IsSpindleOwner(user, domain string) (bool, error) {
256 return e.isRole(user, "server:owner", intoSpindle(domain))
257}
258
259func (e *Enforcer) IsSpindleMember(user, domain string) (bool, error) {
260 return e.isRole(user, "server:member", intoSpindle(domain))
261}
262
263func (e *Enforcer) IsKnotInviteAllowed(user, domain string) (bool, error) {
264 return e.isInviteAllowed(user, domain)
265}
266
267func (e *Enforcer) IsSpindleInviteAllowed(user, domain string) (bool, error) {
268 return e.isInviteAllowed(user, intoSpindle(domain))
269}
270
271func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
272 return e.E.Enforce(user, domain, repo, "repo:push")
273}
274
275func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
276 return e.E.Enforce(user, domain, repo, "repo:settings")
277}
278
279func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
280 return e.E.Enforce(user, domain, repo, "repo:invite")
281}
282
283// given a repo, what permissions does this user have? repo:owner? repo:invite? etc.
284func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string {
285 var permissions []string
286 res := e.E.GetPermissionsForUserInDomain(user, domain)
287 for _, p := range res {
288 // get only permissions for this resource/repo
289 if p[2] == repo {
290 permissions = append(permissions, p[3])
291 }
292 }
293
294 return permissions
295}