Documentation
¶
Overview ¶
Package rbac provides role-based access control for AuthSome.
Roles contain named sets of permissions (action + resource pairs). Users are assigned roles, optionally scoped to an organization. The HasPermission check walks the user's role assignments to determine access.
When Warden is available, all RBAC operations delegate to the Warden authorization engine. The types in this package serve as the API facade.
Index ¶
- Constants
- Variables
- func IsPlatformRole(slug string) bool
- func PermissionString(action, resource string) string
- func ToWardenAssignment(ur *UserRole, tenantID string) *wardenassign.Assignment
- func ToWardenPermission(p *Permission, tenantID string) *wardenperm.Permission
- func ToWardenRole(r *Role) *wardenrole.Role
- type Permission
- type Role
- type Store
- type UserRole
- type WardenStore
- func (s *WardenStore) AddPermission(ctx context.Context, appID string, p *Permission) error
- func (s *WardenStore) AssignUserRole(ctx context.Context, appID string, ur *UserRole) error
- func (s *WardenStore) CreateRole(ctx context.Context, r *Role) error
- func (s *WardenStore) DeleteRole(ctx context.Context, appID, roleID string) error
- func (s *WardenStore) GetRole(ctx context.Context, appID, roleID string) (*Role, error)
- func (s *WardenStore) GetRoleBySlug(ctx context.Context, appID, slug string) (*Role, error)
- func (s *WardenStore) GetRoleChildren(ctx context.Context, appID, roleID string) ([]*Role, error)
- func (s *WardenStore) GetRoles(ctx context.Context, appID string, roleIDs []string) ([]*Role, error)
- func (s *WardenStore) HasPermission(ctx context.Context, userID, action, resource string) (bool, error)
- func (s *WardenStore) ListRolePermissions(ctx context.Context, appID, roleID string) ([]*Permission, error)
- func (s *WardenStore) ListRoles(ctx context.Context, appID string) ([]*Role, error)
- func (s *WardenStore) ListUserRoles(ctx context.Context, userID string) ([]*Role, error)
- func (s *WardenStore) ListUserRolesForApp(ctx context.Context, appID, userID string) ([]*Role, error)
- func (s *WardenStore) RemovePermission(ctx context.Context, appID, permID string) error
- func (s *WardenStore) UnassignUserRole(ctx context.Context, appID, userID, roleID string) error
- func (s *WardenStore) UpdateRole(ctx context.Context, r *Role) error
Constants ¶
const ( PlatformUserSlug = "platform-user" PlatformAdminSlug = "platform-admin" PlatformOwnerSlug = "platform-owner" // SuperAdminSlug documents the intended slug for a future super-admin // role that spans all platform capabilities. It is declared here to // prevent accidental reuse of the name in unrelated roles and to serve // as a single source of truth when the role is eventually introduced. // TODO: Add SuperAdminSlug to IsPlatformRole when the warden DSL defines // the role and it is wired up in the authorization system. SuperAdminSlug = "super-admin" )
Platform role slugs. These are ONLY created under the platform app, never for regular tenant apps. They grant cross-app access.
Slugs are kebab-case so they pass the warden DSL slug convention regex (^[a-z][a-z0-9-]{0,62}$). Existing deployments that have the old underscore-separated rows in their roles table will need to either re-bootstrap or migrate the rows manually — the bootstrap is idempotent at the slug level, so old rows aren't touched and the new rows live alongside them until cleaned up.
const ( AppOwnerSlug = "owner" AppAdminSlug = "admin" AppUserSlug = "user" )
App-scoped role slugs. Created for every app during bootstrap.
Variables ¶
var ( ErrRoleNotFound = errors.New("rbac: role not found") ErrPermissionNotFound = errors.New("rbac: permission not found") ErrRoleAlreadyAssigned = errors.New("rbac: role already assigned") ErrCyclicHierarchy = errors.New("rbac: cyclic role hierarchy detected") // ErrSystemRoleImmutable refuses a change to a role declared // is_system, as warden's own contract and REST paths refuse it. ErrSystemRoleImmutable = errors.New("rbac: system role cannot be changed") )
Store errors.
Functions ¶
func IsPlatformRole ¶
IsPlatformRole returns true if the slug is a platform-scoped role.
func PermissionString ¶
PermissionString renders an action on a resource as the single string a route declares to Forge.
Permissions are a pair everywhere else in authsome: RequirePermission takes (action, resource), and rbac.Permission stores them in two columns. Forge's WithAllPermissions takes one string per permission, because that string ends up in the OpenAPI document as x-forge-authz and then as a constant in every generated client. Flattening it in one function keeps the two sides from drifting into "manage:user" here and "user:manage" three files away, which nothing would catch: both are valid strings, and a client generated from one silently fails to match a server checking the other.
Action first, matching the argument order of RequirePermission, so a declaration reads in the same order as the middleware call it mirrors.
func ToWardenAssignment ¶
func ToWardenAssignment(ur *UserRole, tenantID string) *wardenassign.Assignment
ToWardenAssignment converts an authsome RBAC UserRole to a warden Assignment.
func ToWardenPermission ¶
func ToWardenPermission(p *Permission, tenantID string) *wardenperm.Permission
ToWardenPermission converts an authsome RBAC Permission to a warden Permission.
func ToWardenRole ¶
func ToWardenRole(r *Role) *wardenrole.Role
ToWardenRole converts an authsome RBAC Role to a warden Role.
Types ¶
type Permission ¶
type Permission struct {
ID string `json:"id"`
RoleID string `json:"role_id"`
Action string `json:"action"` // e.g. "read", "write", "delete", "admin"
Resource string `json:"resource"` // e.g. "user", "org", "document"
}
Permission represents a single action on a resource granted by a role.
func FromWardenPermission ¶
func FromWardenPermission(wp *wardenperm.Permission, roleID string) *Permission
FromWardenPermission converts a warden Permission to an authsome RBAC Permission. The roleID is provided because warden permissions are not directly associated with a single role (they are attached via a join table).
type Role ¶
type Role struct {
ID string `json:"id"`
AppID string `json:"app_id"`
EnvID string `json:"env_id"`
ParentID string `json:"parent_id,omitempty"` // Empty = root role (no parent)
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// NamespacePath is the warden namespace the role was read from. Warden
// resolves ParentID (a slug) in this namespace only. It is read-only:
// ToWardenRole places a role by EnvID, not by this field.
NamespacePath string `json:"-"`
}
Role represents a named collection of permissions.
func FromWardenRole ¶
func FromWardenRole(wr *wardenrole.Role) *Role
FromWardenRole converts a warden Role to an authsome RBAC Role.
type Store ¶
type Store interface {
// Role CRUD
CreateRole(ctx context.Context, r *Role) error
GetRole(ctx context.Context, appID, roleID string) (*Role, error)
// GetRoles loads several roles in one call; ids that do not resolve
// are left out rather than failing the whole call.
GetRoles(ctx context.Context, appID string, roleIDs []string) ([]*Role, error)
GetRoleBySlug(ctx context.Context, appID string, slug string) (*Role, error)
UpdateRole(ctx context.Context, r *Role) error
DeleteRole(ctx context.Context, appID, roleID string) error
ListRoles(ctx context.Context, appID string) ([]*Role, error)
// Permission management
AddPermission(ctx context.Context, appID string, p *Permission) error
RemovePermission(ctx context.Context, appID, permID string) error
ListRolePermissions(ctx context.Context, appID, roleID string) ([]*Permission, error)
// Role assignment
AssignUserRole(ctx context.Context, appID string, ur *UserRole) error
UnassignUserRole(ctx context.Context, appID, userID, roleID string) error
ListUserRoles(ctx context.Context, userID string) ([]*Role, error)
ListUserRolesForApp(ctx context.Context, appID string, userID string) ([]*Role, error)
// Hierarchy
GetRoleChildren(ctx context.Context, appID, roleID string) ([]*Role, error)
// Permission check — walks the parent chain for inherited permissions.
HasPermission(ctx context.Context, userID string, action, resource string) (bool, error)
}
Store persists RBAC data. All IDs are plain strings — the rbac package serves as a DTO facade. Implementations convert to/from their internal typed-ID formats as needed.
Every by-ID operation takes the owning app. A call whose app does not own the row behaves as if the row did not exist, so holding another tenant's ID reads, changes and deletes nothing.
type UserRole ¶
type UserRole struct {
UserID string `json:"user_id"`
RoleID string `json:"role_id"`
OrgID string `json:"org_id,omitempty"` // empty = global scope
AssignedAt time.Time `json:"assigned_at"`
}
UserRole represents a role assigned to a user, optionally scoped to an org.
type WardenStore ¶
type WardenStore struct {
// contains filtered or unexported fields
}
WardenStore implements rbac.Store by delegating to a Warden authorization engine. Role/permission CRUD is performed via Warden's store. HasPermission uses warden.Engine.Check() which evaluates the full RBAC+ReBAC+ABAC stack.
func NewWardenStore ¶
func NewWardenStore(eng *warden.Engine) *WardenStore
NewWardenStore creates a new WardenStore backed by the given Warden engine.
func (*WardenStore) AddPermission ¶
func (s *WardenStore) AddPermission(ctx context.Context, appID string, p *Permission) error
func (*WardenStore) AssignUserRole ¶
func (*WardenStore) CreateRole ¶
func (s *WardenStore) CreateRole(ctx context.Context, r *Role) error
func (*WardenStore) DeleteRole ¶
func (s *WardenStore) DeleteRole(ctx context.Context, appID, roleID string) error
func (*WardenStore) GetRoleBySlug ¶
func (*WardenStore) GetRoleChildren ¶
func (*WardenStore) GetRoles ¶
func (s *WardenStore) GetRoles(ctx context.Context, appID string, roleIDs []string) ([]*Role, error)
GetRoles implements Store with one warden round trip. Ids that do not parse, no longer exist, or belong to another app are left out.
func (*WardenStore) HasPermission ¶
func (*WardenStore) ListRolePermissions ¶
func (s *WardenStore) ListRolePermissions(ctx context.Context, appID, roleID string) ([]*Permission, error)
func (*WardenStore) ListUserRoles ¶
func (*WardenStore) ListUserRolesForApp ¶
func (*WardenStore) RemovePermission ¶
func (s *WardenStore) RemovePermission(ctx context.Context, appID, permID string) error
func (*WardenStore) UnassignUserRole ¶
func (s *WardenStore) UnassignUserRole(ctx context.Context, appID, userID, roleID string) error
func (*WardenStore) UpdateRole ¶
func (s *WardenStore) UpdateRole(ctx context.Context, r *Role) error
UpdateRole writes the fields authsome edits, name and description, onto the stored warden role and keeps everything else. Warden's store replaces the whole row, and a Role has no room for the namespace, parent, default flag, member cap or metadata, so writing a converted Role would wipe them. Slug stays too, since child roles find their parent by it.
A system role is refused, as warden's own write paths refuse it. On success r is refreshed from what was written.