forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package rbac
2
3import (
4 "database/sql"
5 "fmt"
6 "path"
7 "strings"
8
9 sqladapter "github.com/Blank-Xu/sql-adapter"
10 "github.com/casbin/casbin/v2"
11 "github.com/casbin/casbin/v2/model"
12)
13
14const (
15 Model = `
16[request_definition]
17r = sub, dom, obj, act
18
19[policy_definition]
20p = sub, dom, obj, act
21
22[role_definition]
23g = _, _, _
24
25[policy_effect]
26e = some(where (p.eft == allow))
27
28[matchers]
29m = r.act == p.act && r.dom == p.dom && keyMatch2(r.obj, p.obj) && g(r.sub, p.sub, r.dom)
30`
31)
32
33type Enforcer struct {
34 E *casbin.SyncedEnforcer
35}
36
37func keyMatch2(key1 string, key2 string) bool {
38 matched, _ := path.Match(key2, key1)
39 return matched
40}
41
42func NewEnforcer(path string) (*Enforcer, error) {
43 m, err := model.NewModelFromString(Model)
44 if err != nil {
45 return nil, err
46 }
47
48 db, err := sql.Open("sqlite3", path)
49 if err != nil {
50 return nil, err
51 }
52
53 a, err := sqladapter.NewAdapter(db, "sqlite3", "acl")
54 if err != nil {
55 return nil, err
56 }
57
58 e, err := casbin.NewSyncedEnforcer(m, a)
59 if err != nil {
60 return nil, err
61 }
62
63 e.EnableAutoSave(true)
64 e.AddFunction("keyMatch2", keyMatch2Func)
65
66 return &Enforcer{e}, nil
67}
68
69func (e *Enforcer) AddDomain(domain string) error {
70 // Add policies with patterns
71 _, err := e.E.AddPolicies([][]string{
72 {"server:owner", domain, domain, "server:invite"},
73 {"server:member", domain, domain, "repo:create"},
74 })
75 if err != nil {
76 return err
77 }
78
79 // all owners are also members
80 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", domain)
81 return err
82}
83
84func (e *Enforcer) GetDomainsForUser(did string) ([]string, error) {
85 return e.E.Enforcer.GetDomainsForUser(did)
86}
87
88func (e *Enforcer) AddOwner(domain, owner string) error {
89 _, err := e.E.AddGroupingPolicy(owner, "server:owner", domain)
90 return err
91}
92
93func (e *Enforcer) AddMember(domain, member string) error {
94 _, err := e.E.AddGroupingPolicy(member, "server:member", domain)
95 return err
96}
97
98func (e *Enforcer) AddRepo(member, domain, repo string) error {
99 // sanity check, repo must be of the form ownerDid/repo
100 if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") {
101 return fmt.Errorf("invalid repo: %s", repo)
102 }
103
104 _, err := e.E.AddPolicies([][]string{
105 {member, domain, repo, "repo:settings"},
106 {member, domain, repo, "repo:push"},
107 {member, domain, repo, "repo:owner"},
108 {member, domain, repo, "repo:invite"},
109 {member, domain, repo, "repo:delete"},
110 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
111 })
112 return err
113}
114
115func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error {
116 // sanity check, repo must be of the form ownerDid/repo
117 if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") {
118 return fmt.Errorf("invalid repo: %s", repo)
119 }
120
121 _, err := e.E.AddPolicies([][]string{
122 {collaborator, domain, repo, "repo:settings"},
123 {collaborator, domain, repo, "repo:push"},
124 })
125 return err
126}
127
128func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
129 var membersWithoutRoles []string
130
131 // this includes roles too, casbin does not differentiate.
132 // the filtering criteria is to remove strings not starting with `did:`
133 members, err := e.E.Enforcer.GetImplicitUsersForRole(role, domain)
134 for _, m := range members {
135 if strings.HasPrefix(m, "did:") {
136 membersWithoutRoles = append(membersWithoutRoles, m)
137 }
138 }
139 if err != nil {
140 return nil, err
141 }
142
143 return membersWithoutRoles, nil
144}
145
146func (e *Enforcer) isRole(user, role, domain string) (bool, error) {
147 return e.E.HasGroupingPolicy(user, role, domain)
148}
149
150func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) {
151 return e.isRole(user, "server:owner", domain)
152}
153
154func (e *Enforcer) IsServerMember(user, domain string) (bool, error) {
155 return e.isRole(user, "server:member", domain)
156}
157
158func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
159 return e.E.Enforce(user, domain, repo, "repo:push")
160}
161
162func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
163 return e.E.Enforce(user, domain, repo, "repo:settings")
164}
165
166func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
167 return e.E.Enforce(user, domain, repo, "repo:invite")
168}
169
170// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin
171func keyMatch2Func(args ...interface{}) (interface{}, error) {
172 name1 := args[0].(string)
173 name2 := args[1].(string)
174
175 return keyMatch2(name1, name2), nil
176}