forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}