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 sqladapter "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.SyncedEnforcer 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 := 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 69func (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 84func (e *Enforcer) GetDomainsForUser(did string) ([]string, error) { 85 return e.E.Enforcer.GetDomainsForUser(did) 86} 87 88func (e *Enforcer) AddOwner(domain, owner string) error { 89 _, err := e.E.AddGroupingPolicy(owner, "server:owner", domain) 90 return err 91} 92 93func (e *Enforcer) AddMember(domain, member string) error { 94 _, err := e.E.AddGroupingPolicy(member, "server:member", domain) 95 return err 96} 97 98func (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 115func (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 129func (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 147func (e *Enforcer) isRole(user, role, domain string) (bool, error) { 148 return e.E.HasGroupingPolicy(user, role, domain) 149} 150 151func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) { 152 return e.isRole(user, "server:owner", domain) 153} 154 155func (e *Enforcer) IsServerMember(user, domain string) (bool, error) { 156 return e.isRole(user, "server:member", domain) 157} 158 159func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 160 return e.E.Enforce(user, domain, repo, "repo:push") 161} 162 163func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) { 164 return e.E.Enforce(user, domain, repo, "repo:settings") 165} 166 167func (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 172func keyMatch2Func(args ...interface{}) (interface{}, error) { 173 name1 := args[0].(string) 174 name2 := args[1].(string) 175 176 return keyMatch2(name1, name2), nil 177}