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 "fmt"
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) AddDomain(domain string) error {
63 // Add policies with patterns
64 _, err := e.E.AddPolicies([][]string{
65 {"server:owner", domain, domain, "server:invite"},
66 {"server:member", domain, domain, "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", domain)
74 return err
75}
76
77func (e *Enforcer) GetDomainsForUser(did string) ([]string, error) {
78 return e.E.GetDomainsForUser(did)
79}
80
81func (e *Enforcer) AddOwner(domain, owner string) error {
82 _, err := e.E.AddGroupingPolicy(owner, "server:owner", domain)
83 return err
84}
85
86func (e *Enforcer) AddMember(domain, member string) error {
87 _, err := e.E.AddGroupingPolicy(member, "server:member", domain)
88 return err
89}
90
91func repoPolicies(member, domain, repo string) [][]string {
92 return [][]string{
93 {member, domain, repo, "repo:settings"},
94 {member, domain, repo, "repo:push"},
95 {member, domain, repo, "repo:owner"},
96 {member, domain, repo, "repo:invite"},
97 {member, domain, repo, "repo:delete"},
98 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
99 }
100}
101func (e *Enforcer) AddRepo(member, domain, repo string) error {
102 err := checkRepoFormat(repo)
103 if err != nil {
104 return err
105 }
106
107 _, err = e.E.AddPolicies(repoPolicies(member, domain, repo))
108 return err
109}
110func (e *Enforcer) RemoveRepo(member, domain, repo string) error {
111 err := checkRepoFormat(repo)
112 if err != nil {
113 return err
114 }
115
116 _, err = e.E.RemovePolicies(repoPolicies(member, domain, repo))
117 return err
118}
119
120var (
121 collaboratorPolicies = func(collaborator, domain, repo string) [][]string {
122 return [][]string{
123 {collaborator, domain, repo, "repo:collaborator"},
124 {collaborator, domain, repo, "repo:settings"},
125 {collaborator, domain, repo, "repo:push"},
126 }
127 }
128)
129
130func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error {
131 err := checkRepoFormat(repo)
132 if err != nil {
133 return err
134 }
135
136 _, err = e.E.AddPolicies(collaboratorPolicies(collaborator, domain, repo))
137 return err
138}
139
140func (e *Enforcer) RemoveCollaborator(collaborator, domain, repo string) error {
141 err := checkRepoFormat(repo)
142 if err != nil {
143 return err
144 }
145
146 _, err = e.E.RemovePolicies(collaboratorPolicies(collaborator, domain, repo))
147 return err
148}
149
150func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
151 var membersWithoutRoles []string
152
153 // this includes roles too, casbin does not differentiate.
154 // the filtering criteria is to remove strings not starting with `did:`
155 members, err := e.E.GetImplicitUsersForRole(role, domain)
156 for _, m := range members {
157 if strings.HasPrefix(m, "did:") {
158 membersWithoutRoles = append(membersWithoutRoles, m)
159 }
160 }
161 if err != nil {
162 return nil, err
163 }
164
165 return membersWithoutRoles, nil
166}
167
168func (e *Enforcer) isRole(user, role, domain string) (bool, error) {
169 return e.E.HasGroupingPolicy(user, role, domain)
170}
171
172func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) {
173 return e.isRole(user, "server:owner", domain)
174}
175
176func (e *Enforcer) IsServerMember(user, domain string) (bool, error) {
177 return e.isRole(user, "server:member", domain)
178}
179
180func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
181 return e.E.Enforce(user, domain, repo, "repo:push")
182}
183
184func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
185 return e.E.Enforce(user, domain, repo, "repo:settings")
186}
187
188func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
189 return e.E.Enforce(user, domain, repo, "repo:invite")
190}
191
192// given a repo, what permissions does this user have? repo:owner? repo:invite? etc.
193func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string {
194 var permissions []string
195 res := e.E.GetPermissionsForUserInDomain(user, domain)
196 for _, p := range res {
197 // get only permissions for this resource/repo
198 if p[2] == repo {
199 permissions = append(permissions, p[3])
200 }
201 }
202
203 return permissions
204}
205
206func checkRepoFormat(repo string) error {
207 // sanity check, repo must be of the form ownerDid/repo
208 if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") {
209 return fmt.Errorf("invalid repo: %s", repo)
210 }
211
212 return nil
213}