Documentation
¶
Overview ¶
Package rbac provides a lightweight, runtime-configurable Role-Based Access Control (RBAC) engine for Go applications.
Core concepts ¶
Roles and permissions are plain strings — the library imposes no vocabulary. Applications define them as typed constants for compile-time safety:
const (
RoleAdmin rbac.Role = "admin"
PermWrite rbac.Permission = "posts:write"
)
The Manager is constructed once (or rebuilt on demand) from a map of RoleDefinition values, each declaring direct permissions and optional parent roles to inherit from. Permission checks are O(1) after construction.
Quick Start ¶
manager := rbac.New(map[rbac.Role]rbac.RoleDefinition{
"viewer": {Permissions: []rbac.Permission{"posts:read"}},
"editor": {Permissions: []rbac.Permission{"posts:read", "posts:write"}},
"admin": {
Permissions: []rbac.Permission{"users:write"},
Inherits: []rbac.Role{"editor"},
},
})
manager.Can(user, "posts:write") // true/false
manager.HasRole(user, "admin") // direct assignment only
Wildcard permissions ¶
A permission ending in "*" is a prefix grant matched against the requested permission at check time:
"posts:*" → grants "posts:read", "posts:write", "posts:delete", … "*" → grants every permission
Matching is a plain string prefix test and is not aware of any separator: "posts*" (no colon) also grants "postscript:read". Include the separator in the grant ("posts:*") to scope it to a single segment.
Wildcards are inherited transitively and appear verbatim in AllPermissions and RolePermissions output. When definitions come from untrusted or user-editable data, WithoutWildcards makes every permission match exactly.
Dynamic and database-driven roles ¶
Implement RoleLoader (or use RoleLoaderFunc) to load definitions from any external source:
manager, err := rbac.NewFromLoader(ctx, rbac.RoleLoaderFunc(loadFromDB))
To update a live manager without downtime, call Rebuild or RebuildFromLoader. Both are safe to call concurrently with ongoing permission checks, which are lock-free and never observe a partially built state. Concurrent rebuilds are serialized, so the definitions applied last always win.
Development options ¶
WithStrictMode causes Can (and all methods that delegate to it) to panic when the user holds an unregistered role. Intended for tests.
ValidateUser returns unknown roles without panicking, and is safe for production observability.
Index ¶
- Variables
- type Manager
- func (m *Manager) AllPermissions(user User) []Permission
- func (m *Manager) Can(user User, permission Permission) bool
- func (m *Manager) CanAll(user User, permissions ...Permission) bool
- func (m *Manager) CanAny(user User, permissions ...Permission) bool
- func (m *Manager) Cannot(user User, permission Permission) bool
- func (m *Manager) HasAllRoles(user User, roles ...Role) bool
- func (m *Manager) HasAnyRole(user User, roles ...Role) bool
- func (m *Manager) HasRole(user User, role Role) bool
- func (m *Manager) PermissionExists(perm Permission) bool
- func (m *Manager) Rebuild(roles map[Role]RoleDefinition) error
- func (m *Manager) RebuildFromLoader(ctx context.Context, loader RoleLoader) error
- func (m *Manager) RoleExists(role Role) bool
- func (m *Manager) RoleHasPermission(role Role, permission Permission) bool
- func (m *Manager) RolePermissions(role Role) []Permission
- func (m *Manager) Roles() []Role
- func (m *Manager) ValidateUser(user User) []Role
- type Option
- type Permission
- type Role
- type RoleDefinition
- type RoleLoader
- type RoleLoaderFunc
- type Roles
- type User
Constants ¶
This section is empty.
Variables ¶
var ErrInheritanceTooDeep = errors.New("rbac: inheritance depth exceeded")
ErrInheritanceTooDeep is returned when a role's (non-cyclic) inheritance chain is longer than the supported maximum depth.
var ErrNilRoles = errors.New("rbac: roles map must not be nil")
ErrNilRoles is returned by Rebuild and the loader functions when the role definitions map is nil.
Functions ¶
This section is empty.
Types ¶
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the RBAC engine. Create one with New or NewFromLoader, then use its methods to perform access checks throughout your application.
Manager is safe for concurrent use. Read operations are lock-free: each one works against an immutable snapshot of the resolved definitions. Rebuild and RebuildFromLoader build a complete new snapshot without blocking readers and then publish it atomically.
func New ¶
func New(roles map[Role]RoleDefinition, opts ...Option) *Manager
New constructs a Manager from the provided role definitions and optional configuration options. It resolves role inheritance eagerly so that runtime permission checks are O(roles assigned to the user) with a cheap map lookup per role.
New panics if roles is nil or an inheritance chain exceeds the maximum depth. Use NewFromLoader to get these as errors instead.
rbac.New(defs) rbac.New(defs, rbac.WithStrictMode())
func NewFromLoader ¶
NewFromLoader constructs a Manager by loading role definitions from the provided loader. It returns an error if the loader fails, returns a nil map, or the definitions cannot be resolved.
func (*Manager) AllPermissions ¶
func (m *Manager) AllPermissions(user User) []Permission
AllPermissions returns the complete, deduplicated set of permissions the user holds across all their roles (including inherited permissions and wildcard grants such as "posts:*"). The result is sorted for deterministic output.
func (*Manager) Can ¶
func (m *Manager) Can(user User, permission Permission) bool
Can returns true if the user holds the given permission via any of their assigned roles (including inherited permissions and wildcard grants).
if manager.Can(user, "posts:write") {
// allow
}
In strict mode (WithStrictMode), Can panics if the user holds a role that is not registered with this manager.
func (*Manager) CanAll ¶
func (m *Manager) CanAll(user User, permissions ...Permission) bool
CanAll returns true only if the user holds every one of the given permissions. Useful for operations that require multiple capabilities.
func (*Manager) CanAny ¶
func (m *Manager) CanAny(user User, permissions ...Permission) bool
CanAny returns true if the user holds at least one of the given permissions.
func (*Manager) Cannot ¶
func (m *Manager) Cannot(user User, permission Permission) bool
Cannot is the logical negation of Can. Useful for guard clauses.
if manager.Cannot(user, "settings:write") {
return ErrForbidden
}
func (*Manager) HasAllRoles ¶
HasAllRoles returns true only if the user has every one of the listed roles.
func (*Manager) HasAnyRole ¶
HasAnyRole returns true if the user has at least one of the listed roles.
func (*Manager) HasRole ¶
HasRole returns true if the user has been directly assigned the given role and that role is registered with this manager. It does not consider inherited roles — only the exact roles returned by user.GetRoles(). If the role is not registered with this manager, HasRole returns false even if the user carries that role string.
if manager.HasRole(user, "admin") {
showAdminPanel()
}
func (*Manager) PermissionExists ¶
func (m *Manager) PermissionExists(perm Permission) bool
PermissionExists returns true if the given permission is satisfiable by at least one role registered with this manager — either as an exact permission (directly or via inheritance) or via a wildcard grant (e.g. "posts:*" satisfies "posts:read").
Passing a wildcard string itself (e.g. "posts:*") returns true only if a role has that exact wildcard grant, not because a broader wildcard like "*" covers it.
func (*Manager) Rebuild ¶
func (m *Manager) Rebuild(roles map[Role]RoleDefinition) error
Rebuild replaces the manager's role definitions with the new set. The new definitions are fully resolved before being published, so concurrent permission checks see either the old or the new definitions, never a mix, and are not blocked while the rebuild runs.
Rebuild returns ErrNilRoles if roles is nil and ErrInheritanceTooDeep if an inheritance chain is too long. On error the previous definitions remain in effect.
func (*Manager) RebuildFromLoader ¶
func (m *Manager) RebuildFromLoader(ctx context.Context, loader RoleLoader) error
RebuildFromLoader loads new role definitions from the given loader and applies them as Rebuild does. The load and the swap happen under the same lock as Rebuild, so when rebuilds race the definitions loaded last are the ones that stay in effect. On error the previous definitions remain in effect.
func (*Manager) RoleExists ¶
RoleExists returns true if the given role is explicitly defined in this manager (i.e. was present in the map passed to New or the most recent Rebuild call).
func (*Manager) RoleHasPermission ¶ added in v0.0.2
func (m *Manager) RoleHasPermission(role Role, permission Permission) bool
RoleHasPermission returns true if the given role has the specified permission, either directly or through inheritance (including wildcard grants). Returns false for unknown roles. This is an O(1) alternative to constructing a fake user and calling Can().
func (*Manager) RolePermissions ¶
func (m *Manager) RolePermissions(role Role) []Permission
RolePermissions returns all permissions for the given role, including permissions inherited from parent roles and wildcard grants. Returns nil for unknown roles. The result is sorted for deterministic output.
func (*Manager) ValidateUser ¶
ValidateUser returns the roles assigned to the user that are not explicitly defined in this manager. Returns nil if all roles are known. A role is considered "known" only if it was present in the map passed to New or the most recent Rebuild call — roles that exist solely as inherited references (i.e. appear in another role's Inherits but have no own definition) are treated as unknown.
Unlike strict mode, ValidateUser never panics and is safe to call in production for observability or pre-flight checks.
type Option ¶
type Option func(*Manager)
Option configures a Manager at construction time.
func WithStrictMode ¶
func WithStrictMode() Option
WithStrictMode returns an Option that causes Can (and all methods that delegate to it) to panic when the user holds a role not registered with the manager. Intended for use in development and testing to catch misconfigured role assignments early.
For production validation without panics, use ValidateUser instead.
func WithoutWildcards ¶ added in v0.1.0
func WithoutWildcards() Option
WithoutWildcards returns an Option that disables wildcard grants: a permission ending in "*" is then an ordinary permission that only matches a check for that exact string. Use it when role definitions come from data that should not be able to grant broad access by accident.
type Permission ¶
type Permission string
Permission is the type for permission identifiers. Permissions are arbitrary strings; the library imposes no structure. Common conventions include:
- Resource + action: "posts:write", "users:delete"
- Flat strings: "can_publish", "manage_billing"
- Hierarchical: "org:team:resource:action"
- Wildcard grants: "posts:*", "*"
type Role ¶
type Role string
Role is the type for role identifiers. Consuming applications should define their roles as typed constants of this type for compile-time safety:
const RoleAdmin rbac.Role = "admin"
type RoleDefinition ¶
type RoleDefinition struct {
// Permissions granted directly to this role. A permission ending in "*"
// is treated as a wildcard prefix grant (see package doc).
Permissions []Permission
// Inherits lists roles whose resolved permissions are folded into this
// role's effective permission set. Circular inheritance is handled
// gracefully (cycles are detected and skipped).
Inherits []Role
}
RoleDefinition declares the permissions granted to a role and, optionally, which other roles it inherits from. Inheritance is deep — inherited roles may themselves have their own Inherits, and the engine resolves the full transitive closure at startup.
type RoleLoader ¶
type RoleLoader interface {
Load(ctx context.Context) (map[Role]RoleDefinition, error)
}
RoleLoader is the interface for loading role definitions from an external source such as a database, configuration file, or remote service. Implement it to drive the RBAC manager from dynamic data:
type DBLoader struct{ db *sql.DB }
func (l *DBLoader) Load(ctx context.Context) (map[rbac.Role]rbac.RoleDefinition, error) {
// query DB and return definitions
}
manager, err := rbac.NewFromLoader(ctx, &DBLoader{db: db})
type RoleLoaderFunc ¶
type RoleLoaderFunc func(ctx context.Context) (map[Role]RoleDefinition, error)
RoleLoaderFunc is a function that implements RoleLoader. It allows a plain function to be used wherever a RoleLoader is expected:
loader := rbac.RoleLoaderFunc(func(ctx context.Context) (map[rbac.Role]rbac.RoleDefinition, error) {
return loadFromDB(ctx, db)
})
func (RoleLoaderFunc) Load ¶
func (f RoleLoaderFunc) Load(ctx context.Context) (map[Role]RoleDefinition, error)
Load implements RoleLoader.
type Roles ¶ added in v0.1.0
type Roles []Role
Roles is a list of roles that satisfies User. Use it when the caller already has the role names at hand and no richer user type is needed:
manager.Can(rbac.Roles{"editor", "billing"}, "posts:write")
type User ¶
type User interface {
GetRoles() []Role
}
User is the interface that consuming applications must implement so the RBAC engine can inspect which roles are assigned to a given principal. Typically this is satisfied by an existing user/session struct:
type AppUser struct {
ID int
Name string
Roles []rbac.Role
}
func (u *AppUser) GetRoles() []rbac.Role { return u.Roles }
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
Package main demonstrates the core RBAC API without any HTTP layer.
|
Package main demonstrates the core RBAC API without any HTTP layer. |
|
dynamic
command
Package main demonstrates wildcard permissions, dynamic role loading and hot-reload, strict mode, and inline Guard usage.
|
Package main demonstrates wildcard permissions, dynamic role loading and hot-reload, strict mode, and inline Guard usage. |
|
inheritance
command
Package main demonstrates deep role inheritance hierarchies, including diamond inheritance (a role inheriting from two parents that share a common ancestor) and cycle detection.
|
Package main demonstrates deep role inheritance hierarchies, including diamond inheritance (a role inheriting from two parents that share a common ancestor) and cycle detection. |
|
webserver
command
Package main demonstrates using the RBAC library with a net/http web server.
|
Package main demonstrates using the RBAC library with a net/http web server. |
|
Package middleware provides net/http-compatible RBAC middleware built on top of the github.com/martinsuchenak/go-rbac core engine.
|
Package middleware provides net/http-compatible RBAC middleware built on top of the github.com/martinsuchenak/go-rbac core engine. |