package rbac2 import ( "database/sql" _ "embed" "fmt" adapter "github.com/Blank-Xu/sql-adapter" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/util" "tangled.org/core/rbac2/bytesadapter" ) const ( Model = ` [request_definition] r = sub, dom, obj, act [policy_definition] p = sub, dom, obj, act [role_definition] g = _, _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub, r.dom) && keyMatch4(r.dom, p.dom) && r.obj == p.obj && r.act == p.act ` ) type Enforcer struct { e *casbin.Enforcer } //go:embed tangled_policy.csv var tangledPolicy []byte func NewEnforcer(path string) (*Enforcer, error) { db, err := sql.Open("sqlite3", path+"?_foreign_keys=1") if err != nil { return nil, err } return NewEnforcerWithDB(db) } func NewEnforcerWithDB(db *sql.DB) (*Enforcer, error) { m, err := model.NewModelFromString(Model) if err != nil { return nil, err } a, err := adapter.NewAdapter(db, "sqlite3", "acl") if err != nil { return nil, err } // // PATCH: create unique index to make `AddPoliciesEx` work // _, err = db.Exec(fmt.Sprintf( // `create unique index if not exists uq_%[1]s on %[1]s (p_type,v0,v1,v2,v3,v4,v5);`, // tableName, // )) // if err != nil { // return nil, err // } e, _ := casbin.NewEnforcer() // NewEnforcer() without param won't return error // e.EnableLog(true) // NOTE: casbin clears the model on init, so we should intialize with temporary adapter first // and then override the adapter to sql-adapter. // `e.SetModel(m)` after init doesn't work for some reason if err := e.InitWithModelAndAdapter(m, bytesadapter.NewAdapter(tangledPolicy)); err != nil { return nil, err } // load dynamic policy from db e.EnableAutoSave(false) if err := a.LoadPolicy(e.GetModel()); err != nil { return nil, err } e.AddNamedDomainMatchingFunc("g", "keyMatch4", util.KeyMatch4) e.BuildRoleLinks() e.SetAdapter(a) e.EnableAutoSave(true) return &Enforcer{e}, nil } // CaptureModel returns copy of current model. Used for testing func (e *Enforcer) CaptureModel() model.Model { return e.e.GetModel().Copy() } func (e *Enforcer) hasImplicitRoleForUser(name string, role string, domain ...string) (bool, error) { roles, err := e.e.GetImplicitRolesForUser(name, domain...) if err != nil { return false, err } for _, r := range roles { if r == role { return true, nil } } return false, nil } // setRoleForUser sets single user role for specified domain. // All existing users with that role will be removed. func (e *Enforcer) setRoleForUser(name string, role string, domain ...string) error { currentUsers, err := e.e.GetUsersForRole(role, domain...) if err != nil { return err } for _, oldUser := range currentUsers { _, err = e.e.DeleteRoleForUser(oldUser, role, domain...) if err != nil { return err } } _, err = e.e.AddRoleForUser(name, role, domain...) return err } // validateAtUri enforeces AT-URI to have valid did as authority and match collection NSID. func validateAtUri(uri syntax.ATURI, expected string) error { if !uri.Authority().IsDID() { return fmt.Errorf("expected at-uri with did") } if expected != "" && uri.Collection().String() != expected { return fmt.Errorf("incorrect repo at-uri collection nsid '%s' (expected '%s')", uri.Collection(), expected) } return nil }