forked from tangled.org/core
Monorepo for Tangled
1package rbac2 2 3import ( 4 "database/sql" 5 "fmt" 6 7 adapter "github.com/Blank-Xu/sql-adapter" 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "github.com/casbin/casbin/v2" 10 "github.com/casbin/casbin/v2/model" 11 "github.com/casbin/casbin/v2/util" 12 "tangled.org/core/api/tangled" 13) 14 15const ( 16 Model = ` 17[request_definition] 18r = sub, dom, obj, act 19 20[policy_definition] 21p = sub, dom, obj, act 22 23[role_definition] 24g = _, _, _ 25 26[policy_effect] 27e = some(where (p.eft == allow)) 28 29[matchers] 30m = g(r.sub, p.sub, r.dom) && keyMatch4(r.dom, p.dom) && r.obj == p.obj && r.act == p.act 31` 32) 33 34type Enforcer struct { 35 e *casbin.Enforcer 36} 37 38func NewEnforcer(path string) (*Enforcer, error) { 39 m, err := model.NewModelFromString(Model) 40 if err != nil { 41 return nil, err 42 } 43 44 db, err := sql.Open("sqlite3", path+"?_foreign_keys=1") 45 if err != nil { 46 return nil, err 47 } 48 49 a, err := adapter.NewAdapter(db, "sqlite3", "acl") 50 if err != nil { 51 return nil, err 52 } 53 54 e, err := casbin.NewEnforcer(m, a) 55 if err != nil { 56 return nil, err 57 } 58 59 if err := seedTangledPolicies(e); err != nil { 60 return nil, err 61 } 62 63 return &Enforcer{e}, nil 64} 65 66func seedTangledPolicies(e *casbin.Enforcer) error { 67 // policies 68 aturi := func(nsid string) string { 69 return fmt.Sprintf("at://{did}/%s/{rkey}", nsid) 70 } 71 72 _, err := e.AddPoliciesEx([][]string{ 73 // sub | dom | obj | act 74 {"repo:owner", aturi(tangled.RepoNSID), "/", "write"}, 75 {"repo:owner", aturi(tangled.RepoNSID), "/collaborator", "write"}, // invite 76 {"repo:collaborator", aturi(tangled.RepoNSID), "/settings", "write"}, 77 {"repo:collaborator", aturi(tangled.RepoNSID), "/git", "write"}, // git push 78 79 {"server:owner", "/knot/{did}", "/member", "write"}, // invite 80 {"server:member", "/knot/{did}", "/git", "write"}, 81 82 {"server:owner", "/spindle/{did}", "/member", "write"}, // invite 83 }) 84 if err != nil { 85 return err 86 } 87 88 // grouping policies 89 // TODO(boltless): define our own matcher to replace keyMatch4 90 e.AddNamedDomainMatchingFunc("g", "keyMatch4", util.KeyMatch4) 91 _, err = e.AddGroupingPoliciesEx([][]string{ 92 // sub | role | dom 93 {"repo:owner", "repo:collaborator", aturi(tangled.RepoNSID)}, 94 95 // using '/knot/' prefix here because knot/spindle identifiers don't 96 // include the collection type 97 {"server:owner", "server:member", "/knot/{did}"}, 98 {"server:owner", "server:member", "/spindle/{did}"}, 99 }) 100 return err 101} 102 103func (e *Enforcer) hasImplicitRoleForUser(name string, role string, domain ...string) (bool, error) { 104 roles, err := e.e.GetImplicitRolesForUser(name, domain...) 105 if err != nil { 106 return false, err 107 } 108 for _, r := range roles { 109 if r == role { 110 return true, nil 111 } 112 } 113 return false, nil 114} 115 116// setRoleForUser sets single user role for specified domain. 117// All existing users with that role will be removed. 118func (e *Enforcer) setRoleForUser(name string, role string, domain ...string) error { 119 currentUsers, err := e.e.GetUsersForRole(role, domain...) 120 if err != nil { 121 return err 122 } 123 124 for _, oldUser := range currentUsers { 125 _, err = e.e.DeleteRoleForUser(oldUser, role, domain...) 126 if err != nil { 127 return err 128 } 129 } 130 131 _, err = e.e.AddRoleForUser(name, role, domain...) 132 return err 133} 134 135// validateAtUri enforeces AT-URI to have valid did as authority and match collection NSID. 136func validateAtUri(uri syntax.ATURI, expected string) error { 137 if !uri.Authority().IsDID() { 138 return fmt.Errorf("expected at-uri with did") 139 } 140 if expected != "" && uri.Collection().String() != expected { 141 return fmt.Errorf("incorrect repo at-uri collection nsid '%s' (expected '%s')", uri.Collection(), expected) 142 } 143 return nil 144}