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 adapter "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.Enforcer 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 := adapter.NewAdapter(db, "sqlite3", "acl") 54 if err != nil { 55 return nil, err 56 } 57 58 e, err := casbin.NewEnforcer(m, a) 59 if err != nil { 60 return nil, err 61 } 62 63 e.EnableAutoSave(false) 64 65 e.AddFunction("keyMatch2", keyMatch2Func) 66 67 return &Enforcer{e}, nil 68} 69 70func (e *Enforcer) AddDomain(domain string) error { 71 // Add policies with patterns 72 _, err := e.E.AddPolicies([][]string{ 73 {"server:owner", domain, domain, "server:invite"}, 74 {"server:member", domain, domain, "repo:create"}, 75 }) 76 if err != nil { 77 return err 78 } 79 80 // all owners are also members 81 _, err = e.E.AddGroupingPolicy("server:owner", "server:member", domain) 82 return err 83} 84 85func (e *Enforcer) GetDomainsForUser(did string) ([]string, error) { 86 return e.E.GetDomainsForUser(did) 87} 88 89func (e *Enforcer) AddOwner(domain, owner string) error { 90 _, err := e.E.AddGroupingPolicy(owner, "server:owner", domain) 91 return err 92} 93 94func (e *Enforcer) AddMember(domain, member string) error { 95 _, err := e.E.AddGroupingPolicy(member, "server:member", domain) 96 return err 97} 98 99func (e *Enforcer) AddRepo(member, domain, repo string) error { 100 // sanity check, repo must be of the form ownerDid/repo 101 if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") { 102 return fmt.Errorf("invalid repo: %s", repo) 103 } 104 105 _, err := e.E.AddPolicies([][]string{ 106 {member, domain, repo, "repo:settings"}, 107 {member, domain, repo, "repo:push"}, 108 {member, domain, repo, "repo:owner"}, 109 {member, domain, repo, "repo:invite"}, 110 {member, domain, repo, "repo:delete"}, 111 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 112 }) 113 return err 114} 115 116func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error { 117 // sanity check, repo must be of the form ownerDid/repo 118 if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") { 119 return fmt.Errorf("invalid repo: %s", repo) 120 } 121 122 _, err := e.E.AddPolicies([][]string{ 123 {collaborator, domain, repo, "repo:collaborator"}, 124 {collaborator, domain, repo, "repo:settings"}, 125 {collaborator, domain, repo, "repo:push"}, 126 }) 127 return err 128} 129 130func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) { 131 var membersWithoutRoles []string 132 133 // this includes roles too, casbin does not differentiate. 134 // the filtering criteria is to remove strings not starting with `did:` 135 members, err := e.E.GetImplicitUsersForRole(role, domain) 136 for _, m := range members { 137 if strings.HasPrefix(m, "did:") { 138 membersWithoutRoles = append(membersWithoutRoles, m) 139 } 140 } 141 if err != nil { 142 return nil, err 143 } 144 145 return membersWithoutRoles, nil 146} 147 148func (e *Enforcer) isRole(user, role, domain string) (bool, error) { 149 return e.E.HasGroupingPolicy(user, role, domain) 150} 151 152func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) { 153 return e.isRole(user, "server:owner", domain) 154} 155 156func (e *Enforcer) IsServerMember(user, domain string) (bool, error) { 157 return e.isRole(user, "server:member", domain) 158} 159 160func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 161 return e.E.Enforce(user, domain, repo, "repo:push") 162} 163 164func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) { 165 return e.E.Enforce(user, domain, repo, "repo:settings") 166} 167 168// given a repo, what permissions does this user have? repo:owner? repo:invite? etc. 169func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string { 170 var permissions []string 171 res := e.E.GetPermissionsForUserInDomain(user, domain) 172 for _, p := range res { 173 // get only permissions for this resource/repo 174 if p[2] == repo { 175 permissions = append(permissions, p[3]) 176 } 177 } 178 179 return permissions 180} 181 182func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) { 183 return e.E.Enforce(user, domain, repo, "repo:invite") 184} 185 186// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin 187func keyMatch2Func(args ...interface{}) (interface{}, error) { 188 name1 := args[0].(string) 189 name2 := args[1].(string) 190 191 return keyMatch2(name1, name2), nil 192}