forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
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 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) AddKnot(knot string) error { 63 // Add policies with patterns 64 _, err := e.E.AddPolicies([][]string{ 65 {"server:owner", knot, knot, "server:invite"}, 66 {"server:member", knot, knot, "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", knot) 74 return err 75} 76 77func (e *Enforcer) AddSpindle(spindle string) error { 78 // the internal repr for spindles is spindle:foo.com 79 spindle = intoSpindle(spindle) 80 81 _, err := e.E.AddPolicies([][]string{ 82 {"server:owner", spindle, spindle, "server:invite"}, 83 }) 84 if err != nil { 85 return err 86 } 87 88 // all owners are also members 89 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", spindle) 90 return err 91} 92 93func (e *Enforcer) RemoveSpindle(spindle string) error { 94 spindle = intoSpindle(spindle) 95 _, err := e.E.DeleteDomains(spindle) 96 return err 97} 98 99func (e *Enforcer) GetKnotsForUser(did string) ([]string, error) { 100 keepFunc := isNotSpindle 101 stripFunc := unSpindle 102 return e.getDomainsForUser(did, keepFunc, stripFunc) 103} 104 105func (e *Enforcer) GetSpindlesForUser(did string) ([]string, error) { 106 keepFunc := isSpindle 107 stripFunc := unSpindle 108 return e.getDomainsForUser(did, keepFunc, stripFunc) 109} 110 111func (e *Enforcer) AddKnotOwner(domain, owner string) error { 112 return e.addOwner(domain, owner) 113} 114 115func (e *Enforcer) RemoveKnotOwner(domain, owner string) error { 116 return e.removeOwner(domain, owner) 117} 118 119func (e *Enforcer) AddKnotMember(domain, member string) error { 120 return e.addMember(domain, member) 121} 122 123func (e *Enforcer) RemoveKnotMember(domain, member string) error { 124 return e.removeMember(domain, member) 125} 126 127func (e *Enforcer) AddSpindleOwner(domain, owner string) error { 128 return e.addOwner(intoSpindle(domain), owner) 129} 130 131func (e *Enforcer) RemoveSpindleOwner(domain, owner string) error { 132 return e.removeOwner(intoSpindle(domain), owner) 133} 134 135func (e *Enforcer) AddSpindleMember(domain, member string) error { 136 return e.addMember(intoSpindle(domain), member) 137} 138 139func (e *Enforcer) RemoveSpindleMember(domain, member string) error { 140 return e.removeMember(intoSpindle(domain), member) 141} 142 143func repoPolicies(member, domain, repo string) [][]string { 144 return [][]string{ 145 {member, domain, repo, "repo:settings"}, 146 {member, domain, repo, "repo:push"}, 147 {member, domain, repo, "repo:owner"}, 148 {member, domain, repo, "repo:invite"}, 149 {member, domain, repo, "repo:delete"}, 150 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 151 } 152} 153func (e *Enforcer) AddRepo(member, domain, repo string) error { 154 err := checkRepoFormat(repo) 155 if err != nil { 156 return err 157 } 158 159 _, err = e.E.AddPolicies(repoPolicies(member, domain, repo)) 160 return err 161} 162func (e *Enforcer) RemoveRepo(member, domain, repo string) error { 163 err := checkRepoFormat(repo) 164 if err != nil { 165 return err 166 } 167 168 _, err = e.E.RemovePolicies(repoPolicies(member, domain, repo)) 169 return err 170} 171 172var ( 173 collaboratorPolicies = func(collaborator, domain, repo string) [][]string { 174 return [][]string{ 175 {collaborator, domain, repo, "repo:collaborator"}, 176 {collaborator, domain, repo, "repo:settings"}, 177 {collaborator, domain, repo, "repo:push"}, 178 } 179 } 180) 181 182func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error { 183 err := checkRepoFormat(repo) 184 if err != nil { 185 return err 186 } 187 188 _, err = e.E.AddPolicies(collaboratorPolicies(collaborator, domain, repo)) 189 return err 190} 191 192func (e *Enforcer) RemoveCollaborator(collaborator, domain, repo string) error { 193 err := checkRepoFormat(repo) 194 if err != nil { 195 return err 196 } 197 198 _, err = e.E.RemovePolicies(collaboratorPolicies(collaborator, domain, repo)) 199 return err 200} 201 202func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) { 203 var membersWithoutRoles []string 204 205 // this includes roles too, casbin does not differentiate. 206 // the filtering criteria is to remove strings not starting with `did:` 207 members, err := e.E.GetImplicitUsersForRole(role, domain) 208 for _, m := range members { 209 if strings.HasPrefix(m, "did:") { 210 membersWithoutRoles = append(membersWithoutRoles, m) 211 } 212 } 213 if err != nil { 214 return nil, err 215 } 216 217 slices.Sort(membersWithoutRoles) 218 return slices.Compact(membersWithoutRoles), nil 219} 220 221func (e *Enforcer) GetKnotUsersByRole(role, domain string) ([]string, error) { 222 return e.GetUserByRole(role, domain) 223} 224 225func (e *Enforcer) GetSpindleUsersByRole(role, domain string) ([]string, error) { 226 return e.GetUserByRole(role, intoSpindle(domain)) 227} 228 229func (e *Enforcer) GetUserByRoleInRepo(role, domain, repo string) ([]string, error) { 230 var users []string 231 232 policies, err := e.E.GetImplicitUsersForResourceByDomain(repo, domain) 233 for _, p := range policies { 234 user := p[0] 235 if strings.HasPrefix(user, "did:") { 236 users = append(users, user) 237 } 238 } 239 if err != nil { 240 return nil, err 241 } 242 243 slices.Sort(users) 244 return slices.Compact(users), nil 245} 246 247func (e *Enforcer) IsKnotOwner(user, domain string) (bool, error) { 248 return e.isRole(user, "server:owner", domain) 249} 250 251func (e *Enforcer) IsKnotMember(user, domain string) (bool, error) { 252 return e.isRole(user, "server:member", domain) 253} 254 255func (e *Enforcer) IsSpindleOwner(user, domain string) (bool, error) { 256 return e.isRole(user, "server:owner", intoSpindle(domain)) 257} 258 259func (e *Enforcer) IsSpindleMember(user, domain string) (bool, error) { 260 return e.isRole(user, "server:member", intoSpindle(domain)) 261} 262 263func (e *Enforcer) IsKnotInviteAllowed(user, domain string) (bool, error) { 264 return e.isInviteAllowed(user, domain) 265} 266 267func (e *Enforcer) IsSpindleInviteAllowed(user, domain string) (bool, error) { 268 return e.isInviteAllowed(user, intoSpindle(domain)) 269} 270 271func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 272 return e.E.Enforce(user, domain, repo, "repo:push") 273} 274 275func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) { 276 return e.E.Enforce(user, domain, repo, "repo:settings") 277} 278 279func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) { 280 return e.E.Enforce(user, domain, repo, "repo:invite") 281} 282 283// given a repo, what permissions does this user have? repo:owner? repo:invite? etc. 284func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string { 285 var permissions []string 286 res := e.E.GetPermissionsForUserInDomain(user, domain) 287 for _, p := range res { 288 // get only permissions for this resource/repo 289 if p[2] == repo { 290 permissions = append(permissions, p[3]) 291 } 292 } 293 294 return permissions 295}