Documentation
¶
Overview ¶
Package role defines the Role entity and its store interface for RBAC.
Index ¶
- func CheckParent(ctx context.Context, s SlugGetter, tenantID string, r *Role, parentSlug string) error
- func CheckPermissionUngranted(ctx context.Context, s GrantLister, tenantID string, pm *permission.Permission) error
- func CheckWritable(r *Role) error
- func HeldGrant(ctx context.Context, s GrantReader, tenantID string, r *Role, ...) (*permission.Permission, error)
- type CycleError
- type GrantLister
- type GrantNotHeldError
- type GrantReader
- type ListFilter
- type ParentNotFoundError
- type PermissionGrantedError
- type Role
- type SlugGetter
- type Store
- type SystemRoleError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CheckParent ¶ added in v1.7.0
func CheckParent(ctx context.Context, s SlugGetter, tenantID string, r *Role, parentSlug string) error
CheckParent refuses setting parentSlug as the parent of r, in tenant tenantID and r's namespace. An empty parentSlug (no parent) always passes and reads nothing.
It returns a *ParentNotFoundError when no role in r's namespace has that slug, and a *CycleError when the parent is r itself or one of r's descendants. It walks up from the parent through ParentSlug; reaching r means the engine's inheritance walk would loop. A missing role further up the chain ends the walk, since a chain through a role that does not exist cannot lead back to r. The walk stops at the first slug it has already seen, so a cycle already in the data cannot hang it. A store failure is returned as is.
The read and the caller's write are not atomic. Two concurrent updates (a's parent set to b, b's to a) can each pass and store a two-role cycle. The evaluator bounds that: its inheritance walk skips a role it has already visited and stops at depth 20, so a check still finishes. Clearing either parent breaks the cycle.
func CheckPermissionUngranted ¶ added in v1.7.0
func CheckPermissionUngranted(ctx context.Context, s GrantLister, tenantID string, pm *permission.Permission) error
CheckPermissionUngranted refuses, with a *PermissionGrantedError, the delete of pm while any role in tenantID grants it. A store failure is returned as is.
func CheckWritable ¶ added in v1.7.0
CheckWritable refuses, with a *SystemRoleError, any write to a system role: an update of any field, a delete, or a change to its grants. Assigning a system role to a subject is a membership change, not a write to the role, and does not call this. A nil role passes.
func HeldGrant ¶ added in v1.7.0
func HeldGrant(ctx context.Context, s GrantReader, tenantID string, r *Role, ref permission.Ref) (*permission.Permission, error)
HeldGrant returns the permission r grants under ref's exact (namespace, name) key, or a *GrantNotHeldError when it grants none. A detach checks this first because DetachPermission removes nothing and returns no error when the key does not match, so a detach naming the wrong namespace would report success while the grant survived. A store failure is returned as is.
Types ¶
type CycleError ¶ added in v1.7.0
CycleError refuses a parent that would make a role its own ancestor, either directly (the role names itself) or through a chain of parents that leads back to it. It wraps wardenerr.ErrCyclicRoleInheritance (warden.ErrCyclicRoleInheritance).
func (*CycleError) Error ¶ added in v1.7.0
func (e *CycleError) Error() string
func (*CycleError) Unwrap ¶ added in v1.7.0
func (e *CycleError) Unwrap() error
type GrantLister ¶ added in v1.7.0
type GrantLister interface {
ListRoles(ctx context.Context, filter *ListFilter) ([]*Role, error)
ListRolePermissionsForRoles(ctx context.Context, tenantID string, roleIDs []id.RoleID) (map[id.RoleID][]*permission.Permission, error)
}
GrantLister is the two store reads GrantingRoles needs.
type GrantNotHeldError ¶ added in v1.7.0
GrantNotHeldError refuses detaching a grant the role does not hold. It wraps wardenerr.ErrNotFound, so the REST API answers it with 404 and the dashboard with NOT_FOUND.
func (*GrantNotHeldError) Error ¶ added in v1.7.0
func (e *GrantNotHeldError) Error() string
func (*GrantNotHeldError) Unwrap ¶ added in v1.7.0
func (e *GrantNotHeldError) Unwrap() error
type GrantReader ¶ added in v1.7.0
type GrantReader interface {
ListRolePermissions(ctx context.Context, tenantID string, roleID id.RoleID) ([]*permission.Permission, error)
}
GrantReader is the one store read HeldGrant needs.
type ListFilter ¶
type ListFilter struct {
TenantID string `json:"tenant_id,omitempty"`
NamespacePath *string `json:"namespace_path,omitempty"`
NamespacePrefix string `json:"namespace_prefix,omitempty"`
IsSystem *bool `json:"is_system,omitempty"`
IsDefault *bool `json:"is_default,omitempty"`
ParentSlug *string `json:"parent_slug,omitempty"`
Search string `json:"search,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
}
ListFilter contains filters for listing roles.
NamespacePath does an exact-match filter; NamespacePrefix matches all descendants under a path (e.g. "engineering/" matches "engineering", "engineering/platform", "engineering/platform/sre").
type ParentNotFoundError ¶ added in v1.7.0
type ParentNotFoundError struct {
Parent string
}
ParentNotFoundError refuses a parent slug that names no role in the child's tenant and namespace. The engine resolves a parent only there, so a role stored with such a parent would inherit nothing from it.
It deliberately does not wrap ErrRoleNotFound: the role being written was found, and the request is what names a missing parent, so it is bad input rather than a missing entity.
func (*ParentNotFoundError) Error ¶ added in v1.7.0
func (e *ParentNotFoundError) Error() string
type PermissionGrantedError ¶ added in v1.7.0
PermissionGrantedError refuses deleting a permission that roles still grant. Every store's DeletePermission also removes the grants, so the delete would silently strip it from those roles. Roles holds their slugs in GrantingRoles order. It wraps nothing: the REST API answers it with 409 and the dashboard with CONFLICT.
func (*PermissionGrantedError) Error ¶ added in v1.7.0
func (e *PermissionGrantedError) Error() string
type Role ¶
type Role struct {
ID id.RoleID `json:"id" db:"id"`
TenantID string `json:"tenant_id" db:"tenant_id"`
NamespacePath string `json:"namespace_path,omitempty" db:"namespace_path"`
AppID string `json:"app_id" db:"app_id"`
Name string `json:"name" db:"name"`
Description string `json:"description,omitempty" db:"description"`
Slug string `json:"slug" db:"slug"`
IsSystem bool `json:"is_system" db:"is_system"`
IsDefault bool `json:"is_default" db:"is_default"`
ParentSlug string `json:"parent_slug,omitempty" db:"parent_slug"`
MaxMembers int `json:"max_members,omitempty" db:"max_members"`
Metadata map[string]any `json:"metadata,omitempty" db:"metadata"`
CreatedBy string `json:"created_by,omitempty" db:"created_by"`
UpdatedBy string `json:"updated_by,omitempty" db:"updated_by"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
Role represents an authorization role that can be assigned to subjects.
Inheritance is expressed by ParentSlug, the slug of another role in the same tenant and namespace. Slugs are stable, human-readable, and unique per (tenant_id, namespace_path, slug) — making role hierarchies declarable in source-controlled config without knowing typeids upfront.
NamespacePath locates the role within the tenant's namespace tree (e.g. "engineering/platform"). Empty string is the tenant root. Resolution at Check time considers a subject's roles at the request's namespace and every ancestor of that namespace (cascading scope inheritance).
func GrantingRoles ¶ added in v1.7.0
func GrantingRoles(ctx context.Context, s GrantLister, tenantID string, pm *permission.Permission) ([]*Role, error)
GrantingRoles returns every role in tenantID whose grants include pm, matched on (namespace, name), sorted by slug, then namespace, then ID.
There is no store method for this direction, so it walks the tenant's roles a page at a time and checks each page's grants. It reads every page and never stops at a limit: CheckPermissionUngranted refuses a delete when this returns anyone, and a scan that gave up early would let the delete strip the grant from a role nobody was warned about.
type SlugGetter ¶ added in v1.7.0
type SlugGetter interface {
GetRoleBySlug(ctx context.Context, tenantID, namespacePath, slug string) (*Role, error)
}
SlugGetter is the one store read CheckParent needs.
type Store ¶
type Store interface {
// CreateRole persists a new role.
CreateRole(ctx context.Context, r *Role) error
// GetRole retrieves a role by ID within a tenant.
GetRole(ctx context.Context, tenantID string, roleID id.RoleID) (*Role, error)
// GetRoles retrieves several roles by ID within a tenant in one round
// trip. IDs that do not exist, or belong to another tenant, are omitted
// from the result rather than reported as an error. Order is undefined.
GetRoles(ctx context.Context, tenantID string, roleIDs []id.RoleID) ([]*Role, error)
// GetRoleBySlug retrieves a role by tenant, namespace, and slug.
// Slugs are unique per (tenant_id, namespace_path); the namespace
// argument disambiguates roles that share the same slug across
// different namespace scopes.
GetRoleBySlug(ctx context.Context, tenantID, namespacePath, slug string) (*Role, error)
// UpdateRole persists changes to a role. The row is matched on both the
// ID and r.TenantID, and tenant_id is never part of the write, so an
// update can neither reach into another tenant nor move a row between
// tenants. Returns ErrRoleNotFound when nothing matches.
UpdateRole(ctx context.Context, r *Role) error
// DeleteRole removes a role by ID within a tenant.
DeleteRole(ctx context.Context, tenantID string, roleID id.RoleID) error
// ListRoles returns roles matching the filter.
ListRoles(ctx context.Context, filter *ListFilter) ([]*Role, error)
// CountRoles returns the number of roles matching the filter.
CountRoles(ctx context.Context, filter *ListFilter) (int64, error)
// ListRolePermissions returns the full Permission records granted to a
// role, resolved via JOIN against warden_permissions. Returning the full
// records avoids the engine's previous N+1 GetPermission loop in the
// RBAC evaluator.
ListRolePermissions(ctx context.Context, tenantID string, roleID id.RoleID) ([]*permission.Permission, error)
// ListRolePermissionsForRoles resolves the grants of several roles in one
// round trip, keyed by role ID. Roles that grant nothing, or that belong
// to another tenant, are absent from the map.
ListRolePermissionsForRoles(ctx context.Context, tenantID string, roleIDs []id.RoleID) (map[id.RoleID][]*permission.Permission, error)
// AttachPermission links a permission to a role by natural key.
// The (NamespacePath, Name) pair uniquely identifies a permission within
// the role's tenant. Returns ErrRoleNotFound when the role is not in the
// tenant.
AttachPermission(ctx context.Context, tenantID string, roleID id.RoleID, ref permission.Ref) error
// DetachPermission removes a permission grant from a role.
DetachPermission(ctx context.Context, tenantID string, roleID id.RoleID, ref permission.Ref) error
// SetRolePermissions replaces all permission grants for a role.
SetRolePermissions(ctx context.Context, tenantID string, roleID id.RoleID, refs []permission.Ref) error
// ListChildRoles returns direct child roles of a parent within a tenant.
// Children are inherently per-tenant since slugs are unique per tenant.
ListChildRoles(ctx context.Context, tenantID, parentSlug string) ([]*Role, error)
// DeleteRolesByTenant removes all roles for a tenant.
DeleteRolesByTenant(ctx context.Context, tenantID string) error
}
Store defines persistence operations for roles.
Every by-ID operation takes the tenant as a mandatory parameter and filters on it alongside the primary key. A call whose tenant does not own the row behaves exactly as if the row did not exist: reads and writes return ErrRoleNotFound, and nothing on disk changes.
type SystemRoleError ¶ added in v1.7.0
type SystemRoleError struct {
Name string
}
SystemRoleError refuses a write that would change or delete a system role. It wraps wardenerr.ErrSystemRoleImmutable (warden.ErrSystemRoleImmutable).
func (*SystemRoleError) Error ¶ added in v1.7.0
func (e *SystemRoleError) Error() string
func (*SystemRoleError) Unwrap ¶ added in v1.7.0
func (e *SystemRoleError) Unwrap() error