177 lines
4.5 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:collaborator"},
123
{collaborator, domain, repo, "repo:settings"},
124
{collaborator, domain, repo, "repo:push"},
125
})
126
return err
127
}
128
129
func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
130
var membersWithoutRoles []string
131
132
// this includes roles too, casbin does not differentiate.
133
// the filtering criteria is to remove strings not starting with `did:`
134
members, err := e.E.Enforcer.GetImplicitUsersForRole(role, domain)
135
for _, m := range members {
136
if strings.HasPrefix(m, "did:") {
137
membersWithoutRoles = append(membersWithoutRoles, m)
138
}
139
}
140
if err != nil {
141
return nil, err
142
}
143
144
return membersWithoutRoles, nil
145
}
146
147
func (e *Enforcer) isRole(user, role, domain string) (bool, error) {
148
return e.E.HasGroupingPolicy(user, role, domain)
149
}
150
151
func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) {
152
return e.isRole(user, "server:owner", domain)
153
}
154
155
func (e *Enforcer) IsServerMember(user, domain string) (bool, error) {
156
return e.isRole(user, "server:member", domain)
157
}
158
159
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
160
return e.E.Enforce(user, domain, repo, "repo:push")
161
}
162
163
func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
164
return e.E.Enforce(user, domain, repo, "repo:settings")
165
}
166
167
func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
168
return e.E.Enforce(user, domain, repo, "repo:invite")
169
}
170
171
// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin
172
func keyMatch2Func(args ...interface{}) (interface{}, error) {
173
name1 := args[0].(string)
174
name2 := args[1].(string)
175
176
return keyMatch2(name1, name2), nil
177
}
178