forked from tangled.org/core
this repo has no description
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 ThisServer = "thisserver" // resource identifier for local rbac enforcement 15) 16 17const ( 18 Model = ` 19[request_definition] 20r = sub, dom, obj, act 21 22[policy_definition] 23p = sub, dom, obj, act 24 25[role_definition] 26g = _, _, _ 27 28[policy_effect] 29e = some(where (p.eft == allow)) 30 31[matchers] 32m = r.act == p.act && r.dom == p.dom && r.obj == p.obj && g(r.sub, p.sub, r.dom) 33` 34) 35 36type Enforcer struct { 37 E *casbin.Enforcer 38} 39 40func NewEnforcer(path string) (*Enforcer, error) { 41 m, err := model.NewModelFromString(Model) 42 if err != nil { 43 return nil, err 44 } 45 46 db, err := sql.Open("sqlite3", path) 47 if err != nil { 48 return nil, err 49 } 50 51 a, err := adapter.NewAdapter(db, "sqlite3", "acl") 52 if err != nil { 53 return nil, err 54 } 55 56 e, err := casbin.NewEnforcer(m, a) 57 if err != nil { 58 return nil, err 59 } 60 61 e.EnableAutoSave(false) 62 63 return &Enforcer{e}, nil 64} 65 66func (e *Enforcer) AddKnot(knot string) error { 67 // Add policies with patterns 68 _, err := e.E.AddPolicies([][]string{ 69 {"server:owner", knot, knot, "server:invite"}, 70 {"server:member", knot, knot, "repo:create"}, 71 }) 72 if err != nil { 73 return err 74 } 75 76 // all owners are also members 77 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", knot) 78 return err 79} 80 81func (e *Enforcer) AddSpindle(spindle string) error { 82 // the internal repr for spindles is spindle:foo.com 83 spindle = intoSpindle(spindle) 84 85 _, err := e.E.AddPolicies([][]string{ 86 {"server:owner", spindle, spindle, "server:invite"}, 87 }) 88 if err != nil { 89 return err 90 } 91 92 // all owners are also members 93 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", spindle) 94 return err 95} 96 97func (e *Enforcer) RemoveSpindle(spindle string) error { 98 spindle = intoSpindle(spindle) 99 _, err := e.E.DeleteDomains(spindle) 100 return err 101} 102 103func (e *Enforcer) GetKnotsForUser(did string) ([]string, error) { 104 keepFunc := isNotSpindle 105 stripFunc := unSpindle 106 return e.getDomainsForUser(did, keepFunc, stripFunc) 107} 108 109func (e *Enforcer) GetSpindlesForUser(did string) ([]string, error) { 110 keepFunc := isSpindle 111 stripFunc := unSpindle 112 return e.getDomainsForUser(did, keepFunc, stripFunc) 113} 114 115func (e *Enforcer) AddKnotOwner(domain, owner string) error { 116 return e.addOwner(domain, owner) 117} 118 119func (e *Enforcer) RemoveKnotOwner(domain, owner string) error { 120 return e.removeOwner(domain, owner) 121} 122 123func (e *Enforcer) AddKnotMember(domain, member string) error { 124 return e.addMember(domain, member) 125} 126 127func (e *Enforcer) RemoveKnotMember(domain, member string) error { 128 return e.removeMember(domain, member) 129} 130 131func (e *Enforcer) AddSpindleOwner(domain, owner string) error { 132 return e.addOwner(intoSpindle(domain), owner) 133} 134 135func (e *Enforcer) RemoveSpindleOwner(domain, owner string) error { 136 return e.removeOwner(intoSpindle(domain), owner) 137} 138 139func (e *Enforcer) AddSpindleMember(domain, member string) error { 140 return e.addMember(intoSpindle(domain), member) 141} 142 143func (e *Enforcer) RemoveSpindleMember(domain, member string) error { 144 return e.removeMember(intoSpindle(domain), member) 145} 146 147func repoPolicies(member, domain, repo string) [][]string { 148 return [][]string{ 149 {member, domain, repo, "repo:settings"}, 150 {member, domain, repo, "repo:push"}, 151 {member, domain, repo, "repo:owner"}, 152 {member, domain, repo, "repo:invite"}, 153 {member, domain, repo, "repo:delete"}, 154 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 155 } 156} 157func (e *Enforcer) AddRepo(member, domain, repo string) error { 158 err := checkRepoFormat(repo) 159 if err != nil { 160 return err 161 } 162 163 _, err = e.E.AddPolicies(repoPolicies(member, domain, repo)) 164 return err 165} 166func (e *Enforcer) RemoveRepo(member, domain, repo string) error { 167 err := checkRepoFormat(repo) 168 if err != nil { 169 return err 170 } 171 172 _, err = e.E.RemovePolicies(repoPolicies(member, domain, repo)) 173 return err 174} 175 176var ( 177 collaboratorPolicies = func(collaborator, domain, repo string) [][]string { 178 return [][]string{ 179 {collaborator, domain, repo, "repo:collaborator"}, 180 {collaborator, domain, repo, "repo:settings"}, 181 {collaborator, domain, repo, "repo:push"}, 182 } 183 } 184) 185 186func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error { 187 err := checkRepoFormat(repo) 188 if err != nil { 189 return err 190 } 191 192 _, err = e.E.AddPolicies(collaboratorPolicies(collaborator, domain, repo)) 193 return err 194} 195 196func (e *Enforcer) RemoveCollaborator(collaborator, domain, repo string) error { 197 err := checkRepoFormat(repo) 198 if err != nil { 199 return err 200 } 201 202 _, err = e.E.RemovePolicies(collaboratorPolicies(collaborator, domain, repo)) 203 return err 204} 205 206func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) { 207 var membersWithoutRoles []string 208 209 // this includes roles too, casbin does not differentiate. 210 // the filtering criteria is to remove strings not starting with `did:` 211 members, err := e.E.GetImplicitUsersForRole(role, domain) 212 for _, m := range members { 213 if strings.HasPrefix(m, "did:") { 214 membersWithoutRoles = append(membersWithoutRoles, m) 215 } 216 } 217 if err != nil { 218 return nil, err 219 } 220 221 slices.Sort(membersWithoutRoles) 222 return slices.Compact(membersWithoutRoles), nil 223} 224 225func (e *Enforcer) GetKnotUsersByRole(role, domain string) ([]string, error) { 226 return e.GetUserByRole(role, domain) 227} 228 229func (e *Enforcer) GetSpindleUsersByRole(role, domain string) ([]string, error) { 230 return e.GetUserByRole(role, intoSpindle(domain)) 231} 232 233func (e *Enforcer) GetUserByRoleInRepo(role, domain, repo string) ([]string, error) { 234 var users []string 235 236 policies, err := e.E.GetImplicitUsersForResourceByDomain(repo, domain) 237 for _, p := range policies { 238 user := p[0] 239 if strings.HasPrefix(user, "did:") { 240 users = append(users, user) 241 } 242 } 243 if err != nil { 244 return nil, err 245 } 246 247 slices.Sort(users) 248 return slices.Compact(users), nil 249} 250 251func (e *Enforcer) IsKnotOwner(user, domain string) (bool, error) { 252 return e.isRole(user, "server:owner", domain) 253} 254 255func (e *Enforcer) IsKnotMember(user, domain string) (bool, error) { 256 return e.isRole(user, "server:member", domain) 257} 258 259func (e *Enforcer) IsSpindleOwner(user, domain string) (bool, error) { 260 return e.isRole(user, "server:owner", intoSpindle(domain)) 261} 262 263func (e *Enforcer) IsSpindleMember(user, domain string) (bool, error) { 264 return e.isRole(user, "server:member", intoSpindle(domain)) 265} 266 267func (e *Enforcer) IsKnotInviteAllowed(user, domain string) (bool, error) { 268 return e.isInviteAllowed(user, domain) 269} 270 271func (e *Enforcer) IsSpindleInviteAllowed(user, domain string) (bool, error) { 272 return e.isInviteAllowed(user, intoSpindle(domain)) 273} 274 275func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 276 return e.E.Enforce(user, domain, repo, "repo:push") 277} 278 279func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) { 280 return e.E.Enforce(user, domain, repo, "repo:settings") 281} 282 283func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) { 284 return e.E.Enforce(user, domain, repo, "repo:invite") 285} 286 287// given a repo, what permissions does this user have? repo:owner? repo:invite? etc. 288func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string { 289 var permissions []string 290 res := e.E.GetPermissionsForUserInDomain(user, domain) 291 for _, p := range res { 292 // get only permissions for this resource/repo 293 if p[2] == repo { 294 permissions = append(permissions, p[3]) 295 } 296 } 297 298 return permissions 299}