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) GetKnotsForUser(did string) ([]string, error) {
94 keepFunc := isNotSpindle
95 stripFunc := unSpindle
96 return e.getDomainsForUser(did, keepFunc, stripFunc)
97}
98
99func (e *Enforcer) GetSpindlesForUser(did string) ([]string, error) {
100 keepFunc := isSpindle
101 stripFunc := unSpindle
102 return e.getDomainsForUser(did, keepFunc, stripFunc)
103}
104
105func (e *Enforcer) AddKnotOwner(domain, owner string) error {
106 return e.addOwner(domain, owner)
107}
108
109func (e *Enforcer) AddKnotMember(domain, member string) error {
110 return e.addMember(domain, member)
111}
112
113func (e *Enforcer) AddSpindleOwner(domain, owner string) error {
114 return e.addOwner(intoSpindle(domain), owner)
115}
116
117func (e *Enforcer) AddSpindleMember(domain, member string) error {
118 return e.addMember(intoSpindle(domain), member)
119}
120
121func repoPolicies(member, domain, repo string) [][]string {
122 return [][]string{
123 {member, domain, repo, "repo:settings"},
124 {member, domain, repo, "repo:push"},
125 {member, domain, repo, "repo:owner"},
126 {member, domain, repo, "repo:invite"},
127 {member, domain, repo, "repo:delete"},
128 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
129 }
130}
131func (e *Enforcer) AddRepo(member, domain, repo string) error {
132 err := checkRepoFormat(repo)
133 if err != nil {
134 return err
135 }
136
137 _, err = e.E.AddPolicies(repoPolicies(member, domain, repo))
138 return err
139}
140func (e *Enforcer) RemoveRepo(member, domain, repo string) error {
141 err := checkRepoFormat(repo)
142 if err != nil {
143 return err
144 }
145
146 _, err = e.E.RemovePolicies(repoPolicies(member, domain, repo))
147 return err
148}
149
150var (
151 collaboratorPolicies = func(collaborator, domain, repo string) [][]string {
152 return [][]string{
153 {collaborator, domain, repo, "repo:collaborator"},
154 {collaborator, domain, repo, "repo:settings"},
155 {collaborator, domain, repo, "repo:push"},
156 }
157 }
158)
159
160func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error {
161 err := checkRepoFormat(repo)
162 if err != nil {
163 return err
164 }
165
166 _, err = e.E.AddPolicies(collaboratorPolicies(collaborator, domain, repo))
167 return err
168}
169
170func (e *Enforcer) RemoveCollaborator(collaborator, domain, repo string) error {
171 err := checkRepoFormat(repo)
172 if err != nil {
173 return err
174 }
175
176 _, err = e.E.RemovePolicies(collaboratorPolicies(collaborator, domain, repo))
177 return err
178}
179
180func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
181 var membersWithoutRoles []string
182
183 // this includes roles too, casbin does not differentiate.
184 // the filtering criteria is to remove strings not starting with `did:`
185 members, err := e.E.GetImplicitUsersForRole(role, domain)
186 for _, m := range members {
187 if strings.HasPrefix(m, "did:") {
188 membersWithoutRoles = append(membersWithoutRoles, m)
189 }
190 }
191 if err != nil {
192 return nil, err
193 }
194
195 slices.Sort(membersWithoutRoles)
196 return slices.Compact(membersWithoutRoles), nil
197}
198
199func (e *Enforcer) GetUserByRoleInRepo(role, domain, repo string) ([]string, error) {
200 var users []string
201
202 policies, err := e.E.GetImplicitUsersForResourceByDomain(repo, domain)
203 for _, p := range policies {
204 user := p[0]
205 if strings.HasPrefix(user, "did:") {
206 users = append(users, user)
207 }
208 }
209 if err != nil {
210 return nil, err
211 }
212
213 slices.Sort(users)
214 return slices.Compact(users), nil
215}
216
217func (e *Enforcer) IsKnotOwner(user, domain string) (bool, error) {
218 return e.isRole(user, "server:owner", domain)
219}
220
221func (e *Enforcer) IsKnotMember(user, domain string) (bool, error) {
222 return e.isRole(user, "server:member", domain)
223}
224
225func (e *Enforcer) IsSpindleOwner(user, domain string) (bool, error) {
226 return e.isRole(user, "server:owner", intoSpindle(domain))
227}
228
229func (e *Enforcer) IsSpindleMember(user, domain string) (bool, error) {
230 return e.isRole(user, "server:member", intoSpindle(domain))
231}
232
233func (e *Enforcer) IsKnotInviteAllowed(user, domain string) (bool, error) {
234 return e.isInviteAllowed(user, domain)
235}
236
237func (e *Enforcer) IsSpindleInviteAllowed(user, domain string) (bool, error) {
238 return e.isInviteAllowed(user, intoSpindle(domain))
239}
240
241func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
242 return e.E.Enforce(user, domain, repo, "repo:push")
243}
244
245func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
246 return e.E.Enforce(user, domain, repo, "repo:settings")
247}
248
249func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
250 return e.E.Enforce(user, domain, repo, "repo:invite")
251}
252
253// given a repo, what permissions does this user have? repo:owner? repo:invite? etc.
254func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string {
255 var permissions []string
256 res := e.E.GetPermissionsForUserInDomain(user, domain)
257 for _, p := range res {
258 // get only permissions for this resource/repo
259 if p[2] == repo {
260 permissions = append(permissions, p[3])
261 }
262 }
263
264 return permissions
265}