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