176 lines
4.4 kB
1
package rbac
2
3
import (
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
14
const (
15
Model = `
16
[request_definition]
17
r = sub, dom, obj, act
18
19
[policy_definition]
20
p = sub, dom, obj, act
21
22
[role_definition]
23
g = _, _, _
24
25
[policy_effect]
26
e = some(where (p.eft == allow))
27
28
[matchers]
29
m = r.act == p.act && r.dom == p.dom && keyMatch2(r.obj, p.obj) && g(r.sub, p.sub, r.dom)
30
`
31
)
32
33
type Enforcer struct {
34
E *casbin.SyncedEnforcer
35
}
36
37
func keyMatch2(key1 string, key2 string) bool {
38
matched, _ := path.Match(key2, key1)
39
return matched
40
}
41
42
func 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
69
func (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
84
func (e *Enforcer) GetDomainsForUser(did string) ([]string, error) {
85
return e.E.Enforcer.GetDomainsForUser(did)
86
}
87
88
func (e *Enforcer) AddOwner(domain, owner string) error {
89
_, err := e.E.AddGroupingPolicy(owner, "server:owner", domain)
90
return err
91
}
92
93
func (e *Enforcer) AddMember(domain, member string) error {
94
_, err := e.E.AddGroupingPolicy(member, "server:member", domain)
95
return err
96
}
97
98
func (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
115
func (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
128
func (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
146
func (e *Enforcer) isRole(user, role, domain string) (bool, error) {
147
return e.E.HasGroupingPolicy(user, role, domain)
148
}
149
150
func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) {
151
return e.isRole(user, "server:owner", domain)
152
}
153
154
func (e *Enforcer) IsServerMember(user, domain string) (bool, error) {
155
return e.isRole(user, "server:member", domain)
156
}
157
158
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
159
return e.E.Enforce(user, domain, repo, "repo:push")
160
}
161
162
func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
163
return e.E.Enforce(user, domain, repo, "repo:settings")
164
}
165
166
func (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
171
func keyMatch2Func(args ...interface{}) (interface{}, error) {
172
name1 := args[0].(string)
173
name2 := args[1].(string)
174
175
return keyMatch2(name1, name2), nil
176
}
177