Monorepo for Tangled tangled.org
1package rbac2 2 3import ( 4 "database/sql" 5 _ "embed" 6 "fmt" 7 8 adapter "github.com/Blank-Xu/sql-adapter" 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "github.com/casbin/casbin/v2" 11 "github.com/casbin/casbin/v2/model" 12 "github.com/casbin/casbin/v2/util" 13 "tangled.org/core/rbac2/bytesadapter" 14) 15 16const ( 17 Model = ` 18[request_definition] 19r = sub, dom, obj, act 20 21[policy_definition] 22p = sub, dom, obj, act 23 24[role_definition] 25g = _, _, _ 26 27[policy_effect] 28e = some(where (p.eft == allow)) 29 30[matchers] 31m = g(r.sub, p.sub, r.dom) && keyMatch4(r.dom, p.dom) && r.obj == p.obj && r.act == p.act 32` 33) 34 35type Enforcer struct { 36 e *casbin.Enforcer 37} 38 39//go:embed tangled_policy.csv 40var tangledPolicy []byte 41 42func NewEnforcer(path string) (*Enforcer, error) { 43 db, err := sql.Open("sqlite3", path+"?_foreign_keys=1") 44 if err != nil { 45 return nil, err 46 } 47 return NewEnforcerWithDB(db) 48} 49 50func NewEnforcerWithDB(db *sql.DB) (*Enforcer, error) { 51 m, err := model.NewModelFromString(Model) 52 if err != nil { 53 return nil, err 54 } 55 56 a, err := adapter.NewAdapter(db, "sqlite3", "acl") 57 if err != nil { 58 return nil, err 59 } 60 61 // // PATCH: create unique index to make `AddPoliciesEx` work 62 // _, err = db.Exec(fmt.Sprintf( 63 // `create unique index if not exists uq_%[1]s on %[1]s (p_type,v0,v1,v2,v3,v4,v5);`, 64 // tableName, 65 // )) 66 // if err != nil { 67 // return nil, err 68 // } 69 70 e, _ := casbin.NewEnforcer() // NewEnforcer() without param won't return error 71 // e.EnableLog(true) 72 73 // NOTE: casbin clears the model on init, so we should intialize with temporary adapter first 74 // and then override the adapter to sql-adapter. 75 // `e.SetModel(m)` after init doesn't work for some reason 76 if err := e.InitWithModelAndAdapter(m, bytesadapter.NewAdapter(tangledPolicy)); err != nil { 77 return nil, err 78 } 79 80 // load dynamic policy from db 81 e.EnableAutoSave(false) 82 if err := a.LoadPolicy(e.GetModel()); err != nil { 83 return nil, err 84 } 85 e.AddNamedDomainMatchingFunc("g", "keyMatch4", util.KeyMatch4) 86 e.BuildRoleLinks() 87 e.SetAdapter(a) 88 e.EnableAutoSave(true) 89 90 return &Enforcer{e}, nil 91} 92 93// CaptureModel returns copy of current model. Used for testing 94func (e *Enforcer) CaptureModel() model.Model { 95 return e.e.GetModel().Copy() 96} 97 98func (e *Enforcer) hasImplicitRoleForUser(name string, role string, domain ...string) (bool, error) { 99 roles, err := e.e.GetImplicitRolesForUser(name, domain...) 100 if err != nil { 101 return false, err 102 } 103 for _, r := range roles { 104 if r == role { 105 return true, nil 106 } 107 } 108 return false, nil 109} 110 111// setRoleForUser sets single user role for specified domain. 112// All existing users with that role will be removed. 113func (e *Enforcer) setRoleForUser(name string, role string, domain ...string) error { 114 currentUsers, err := e.e.GetUsersForRole(role, domain...) 115 if err != nil { 116 return err 117 } 118 119 for _, oldUser := range currentUsers { 120 _, err = e.e.DeleteRoleForUser(oldUser, role, domain...) 121 if err != nil { 122 return err 123 } 124 } 125 126 _, err = e.e.AddRoleForUser(name, role, domain...) 127 return err 128} 129 130// validateAtUri enforeces AT-URI to have valid did as authority and match collection NSID. 131func validateAtUri(uri syntax.ATURI, expected string) error { 132 if !uri.Authority().IsDID() { 133 return fmt.Errorf("expected at-uri with did") 134 } 135 if expected != "" && uri.Collection().String() != expected { 136 return fmt.Errorf("incorrect repo at-uri collection nsid '%s' (expected '%s')", uri.Collection(), expected) 137 } 138 return nil 139}