Documentation
¶
Overview ¶
Package rbac resolves exact-scope subject/group assignments through a validated acyclic role graph. New copies parent/group slices and builds a reusable view; Evaluate applies exact action/resource matches and explicit deny over allow. Permissions are ordered by descending priority then ID, not first-match wins. No-match remains NotApplicable for the root engine to combine/default-deny. Tenant inheritance is opt-in; authentication/group membership stays caller-owned. Manager provides separate in-memory administration, not distributed persistence. Request and unused Subject attributes remain borrowed; no I/O worker is owned. Model documents use the application version-1 JSON profile.
Example ¶
package main
import (
"context"
"fmt"
authorization "github.com/faustbrian/go-authorization/v3"
"github.com/faustbrian/go-authorization/v3/rbac"
)
func main() {
evaluator, err := rbac.New(
[]rbac.Role{{ID: "reader", Tenant: "acme"}},
[]rbac.Permission{
{
ID: "read-documents",
RoleID: "reader",
Tenant: "acme",
Action: "document.read",
ResourceType: "document",
Effect: authorization.Allow,
},
},
[]rbac.Assignment{
{
ID: "alice-reader",
Subject: authorization.Subject{
Kind: authorization.SubjectUser,
ID: "alice",
},
RoleID: "reader",
Tenant: "acme",
},
},
)
if err != nil {
panic(err)
}
decision, err := evaluator.Evaluate(context.Background(), authorization.Request{
Subject: authorization.Subject{
Kind: authorization.SubjectUser,
ID: "alice",
},
Action: "document.read",
Resource: authorization.Resource{
Type: "document",
ID: "document-1",
},
Tenant: "acme",
})
if err != nil {
panic(err)
}
fmt.Println(decision.Outcome == authorization.Allow)
}
Output: true
Index ¶
- Constants
- Variables
- func EncodeDocument(document Document) ([]byte, error)
- type Assignment
- type AssignmentDocument
- type Decoder
- type Document
- type Evaluator
- func (evaluator *Evaluator) EffectivePermissions(ctx context.Context, subject authorization.Subject, ...) ([]Permission, error)
- func (evaluator *Evaluator) Evaluate(ctx context.Context, request authorization.Request) (authorization.Decision, error)
- func (evaluator *Evaluator) EvaluateBatch(ctx context.Context, requests []authorization.Request) ([]authorization.Decision, error)
- type Limits
- type Manager
- func (manager *Manager) Assign(assignment Assignment) error
- func (manager *Manager) Assignments(subject authorization.Subject, tenant authorization.TenantID) []Assignment
- func (manager *Manager) Evaluator() (*Evaluator, error)
- func (manager *Manager) Revision() authorization.Revision
- func (manager *Manager) RevokeAssignment(id authorization.PolicyID) bool
- type Option
- type Permission
- type PermissionDocument
- type Role
- type RoleDocument
- type RoleID
Examples ¶
Constants ¶
const ( // EffectAllow is the serialized explicit allow label. EffectAllow = "allow" // EffectDeny is the serialized explicit deny label. EffectDeny = "deny" )
const ( // ReasonAllow marks a model grant. ReasonAllow authorization.ReasonCode = "rbac-allow" // ReasonExplicitDeny marks a matching model denial. ReasonExplicitDeny authorization.ReasonCode = "rbac-explicit-deny" // ReasonLimitExceeded marks model work rejected by a configured count/cost limit. ReasonLimitExceeded authorization.ReasonCode = "rbac-limit-exceeded" )
const DocumentVersion uint64 = 1
DocumentVersion is version 1 of this application model JSON profile.
Variables ¶
var ( // ErrInvalidDocument identifies invalid RBAC document. Compare with errors.Is, not error text. ErrInvalidDocument = errors.New("invalid RBAC document") // ErrUnsupportedDocumentVersion identifies unsupported RBAC document version. Compare with errors.Is, not error text. ErrUnsupportedDocumentVersion = errors.New("unsupported RBAC document version") // ErrDocumentLimitExceeded identifies RBAC document size limit exceeded. Compare with errors.Is, not error text. ErrDocumentLimitExceeded = errors.New("RBAC document size limit exceeded") )
var ( // ErrRoleCycle identifies RBAC role inheritance cycle. Compare with errors.Is, not error text. ErrRoleCycle = errors.New("RBAC role inheritance cycle") // ErrInheritanceDepthExceeded identifies RBAC inheritance depth exceeded. Compare with errors.Is, not error text. ErrInheritanceDepthExceeded = errors.New("RBAC inheritance depth exceeded") // ErrUnknownParentRole identifies RBAC parent role does not exist. Compare with errors.Is, not error text. ErrUnknownParentRole = errors.New("RBAC parent role does not exist") // ErrCrossTenantInheritance identifies RBAC inheritance crosses tenants. Compare with errors.Is, not error text. ErrCrossTenantInheritance = errors.New("RBAC inheritance crosses tenants") // ErrInvalidRole identifies invalid RBAC role. Compare with errors.Is, not error text. ErrInvalidRole = errors.New("invalid RBAC role") // ErrDuplicateRole identifies duplicate RBAC role. Compare with errors.Is, not error text. ErrDuplicateRole = errors.New("duplicate RBAC role") // ErrInvalidPermission identifies invalid RBAC permission. Compare with errors.Is, not error text. ErrInvalidPermission = errors.New("invalid RBAC permission") // ErrDuplicatePermission identifies duplicate RBAC permission. Compare with errors.Is, not error text. ErrDuplicatePermission = errors.New("duplicate RBAC permission") // ErrInvalidAssignment identifies invalid RBAC assignment. Compare with errors.Is, not error text. ErrInvalidAssignment = errors.New("invalid RBAC assignment") // ErrDuplicateAssignment identifies duplicate RBAC assignment. Compare with errors.Is, not error text. ErrDuplicateAssignment = errors.New("duplicate RBAC assignment") // ErrUnknownRole identifies RBAC role does not exist. Compare with errors.Is, not error text. ErrUnknownRole = errors.New("RBAC role does not exist") // ErrRoleTenantMismatch identifies RBAC role tenant mismatch. Compare with errors.Is, not error text. ErrRoleTenantMismatch = errors.New("RBAC role tenant mismatch") // ErrRoleLimitExceeded identifies RBAC role limit exceeded. Compare with errors.Is, not error text. ErrRoleLimitExceeded = errors.New("RBAC role limit exceeded") // ErrPermissionLimitExceeded identifies RBAC permission limit exceeded. Compare with errors.Is, not error text. ErrPermissionLimitExceeded = errors.New("RBAC permission limit exceeded") // ErrAssignmentLimitExceeded identifies RBAC assignment limit exceeded. Compare with errors.Is, not error text. ErrAssignmentLimitExceeded = errors.New("RBAC assignment limit exceeded") // ErrGroupLimitExceeded identifies RBAC group limit exceeded. Compare with errors.Is, not error text. ErrGroupLimitExceeded = errors.New("RBAC group limit exceeded") // ErrMatchLimitExceeded identifies RBAC match limit exceeded. Compare with errors.Is, not error text. ErrMatchLimitExceeded = errors.New("RBAC match limit exceeded") // ErrBatchLimitExceeded identifies RBAC batch limit exceeded. Compare with errors.Is, not error text. ErrBatchLimitExceeded = errors.New("RBAC batch limit exceeded") )
Functions ¶
func EncodeDocument ¶
EncodeDocument build-validates, then marshals indented JSON and checks the 1 MiB result cap after allocation. It returns no successful oversize output; field/string memory and caller document ownership remain outside that cap.
Types ¶
type Assignment ¶
type Assignment struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID authorization.PolicyID
// Subject supplies trusted kind/ID and optional group/fact state; it is not authenticated here.
Subject authorization.Subject
// RoleID names an existing same-tenant role; constructors validate that relation.
RoleID RoleID
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID
}
Assignment binds an exact subject kind/ID to a same-tenant role; Subject attributes/groups do not expand assignment identity.
type AssignmentDocument ¶
type AssignmentDocument struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID authorization.PolicyID `json:"id"`
// SubjectKind is the exact principal-kind label, not enforced authentication policy.
SubjectKind authorization.SubjectKind `json:"subject_kind"`
// SubjectID is the exact principal identity; application authentication must establish it.
SubjectID authorization.SubjectID `json:"subject_id"`
// RoleID names an existing same-tenant role; constructors validate that relation.
RoleID RoleID `json:"role_id"`
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID `json:"tenant,omitempty"`
}
AssignmentDocument is editable exact subject/role scope input.
type Decoder ¶
type Decoder struct{}
Decoder is the stateless policy.Decoder binding for this version-1 model.
func (Decoder) Decode ¶
func (Decoder) Decode(document json.RawMessage) (authorization.Evaluator, error)
Decode delegates to DecodeDocument; no independent lifecycle or context is added.
type Document ¶
type Document struct {
// Version is the application model version; Build requires DocumentVersion (1).
Version uint64 `json:"version"`
// GlobalInheritance explicitly enables global assignments/grants for tenant requests.
GlobalInheritance bool `json:"global_inheritance,omitempty"`
// Limits supplies positive count/inheritance-depth overrides; other values retain defaults.
Limits Limits `json:"limits,omitempty"`
// Roles supplies editable role definitions; Build/New copies parent slices.
Roles []RoleDocument `json:"roles"`
// Permissions supplies editable role permission definitions.
Permissions []PermissionDocument `json:"permissions"`
// Assignments supplies editable exact-subject role bindings; attributes are not assignment identity.
Assignments []AssignmentDocument `json:"assignments"`
}
Document is the editable application version-1 JSON model, not an authenticated artifact. Build validates the selected model after caller-owned construction/JSON materialization; inactive fields can be present but unused.
type Evaluator ¶
type Evaluator struct {
// contains filtered or unexported fields
}
Evaluator is a reusable constructed role view with no background lifecycle. Use New; requests/maps and unused assignment Subject attributes remain borrowed.
func DecodeDocument ¶
DecodeDocument rejects input above 1 MiB before JSON decoding, unknown fields and trailing values, then builds the model. Duplicate JSON member names follow encoding/json last-wins semantics; duplicate model IDs are rejected. Parser diagnostics can include offending characters/field names. No context or pre-parser model-depth/count cap is supplied; decoded trees allocate before Build.
func New ¶
func New( roles []Role, permissions []Permission, assignments []Assignment, options ...Option, ) (*Evaluator, error)
New checks count/identity/tenant/role-reference constraints and acyclic inheritance before publishing a view. Parent and assignment-group slices are copied; unused Subject attributes remain borrowed. Initial maps are allocated before count rejection; graph cycle detection precedes inheritance-depth admission. Nil/custom options may panic; caller strings and constructor work need trusted budgets.
func (*Evaluator) EffectivePermissions ¶
func (evaluator *Evaluator) EffectivePermissions( ctx context.Context, subject authorization.Subject, tenant authorization.TenantID, ) ([]Permission, error)
EffectivePermissions returns a fresh priority-descending/ID-sorted permission slice from exact-scope subject/groups and their role closures. Duplicate roles are collapsed, not equal permissions across distinct roles. MaxGroups applies; MaxMatches is enforced later by Evaluate, not as this collection size budget. Groups/tenant membership are trusted facts, and context checks occur at entry.
func (*Evaluator) Evaluate ¶
func (evaluator *Evaluator) Evaluate( ctx context.Context, request authorization.Request, ) (authorization.Decision, error)
Evaluate obtains effective permissions, matches exact action/type and optional instance ID, and collects at most MaxMatches with deny overriding allow. It does not call Request.Validate. Context is checked in EffectivePermissions at entry, not at every recursive role/permission step; no-match is NotApplicable.
func (*Evaluator) EvaluateBatch ¶
func (evaluator *Evaluator) EvaluateBatch( ctx context.Context, requests []authorization.Request, ) ([]authorization.Decision, error)
EvaluateBatch checks MaxBatchSize before allocation, then returns one ordered result per request and joined errors. It continues after failures/cancellation; an empty batch performs no per-request ctx check. No cross-request transaction is provided.
type Limits ¶
type Limits struct {
// MaxInheritanceDepth admits validated role-graph depth; default is 32.
MaxInheritanceDepth int `json:"max_inheritance_depth,omitempty"`
// MaxRoles admits role count; default is 1000.
MaxRoles int `json:"max_roles,omitempty"`
// MaxPermissions admits permission count; default is 10000.
MaxPermissions int `json:"max_permissions,omitempty"`
// MaxAssignments admits assignment count; default is 10000.
MaxAssignments int `json:"max_assignments,omitempty"`
// MaxGroups caps admitted membership count; zero/default policy selects 100.
MaxGroups int `json:"max_groups,omitempty"`
// MaxMatches caps collected matching entries/permissions/rules; default is 100.
MaxMatches int `json:"max_matches,omitempty"`
// MaxBatchSize admits request count before result allocation; default is 1000.
MaxBatchSize int `json:"max_batch_size,omitempty"`
}
Limits admits role/permission/assignment/group/match/batch counts and graph depth; positive overrides are trusted policy, not a whole-process memory budget.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager provides synchronized in-memory assignment administration. Each Evaluator call builds a new structural view. Options run again under manager locks and must not block/reenter the manager. Unused nested Subject attribute maps are not deep-copied. No distributed storage, clock or goroutine is owned.
func NewManager ¶
func NewManager( roles []Role, permissions []Permission, assignments []Assignment, options ...Option, ) (*Manager, error)
NewManager validates/copies roles, permissions, assignments and options. Subject attribute maps remain borrowed; options are rerun during later validation under manager locks and must not reenter/block.
func (*Manager) Assign ¶
func (manager *Manager) Assign(assignment Assignment) error
Assign validates and atomically adds a subject-role assignment.
func (*Manager) Assignments ¶
func (manager *Manager) Assignments( subject authorization.Subject, tenant authorization.TenantID, ) []Assignment
Assignments returns exact-scope assignment copies, including copied Groups. Nested Subject.Attributes maps remain shared (and are not used for matching).
func (*Manager) Evaluator ¶
Evaluator rebuilds the current view under the read lock, rerunning borrowed options. Returned models fix matching state; unused subject attributes remain borrowed. Options must not reenter the manager or block.
func (*Manager) Revision ¶
func (manager *Manager) Revision() authorization.Revision
Revision returns the in-memory counter (initially 1), incremented after accepted assignment mutations. It is not a persisted/global token and has no wrap guard.
func (*Manager) RevokeAssignment ¶
func (manager *Manager) RevokeAssignment(id authorization.PolicyID) bool
RevokeAssignment removes an assignment by stable ID.
type Option ¶
type Option func(*Evaluator)
Option configures construction synchronously. Nil/custom options may panic; do not retain evaluator mutation access after New publishes its view.
func WithGlobalInheritance ¶
func WithGlobalInheritance() Option
WithGlobalInheritance permits global assignments for non-global requests; inheritance never crosses distinct nonempty tenant scopes.
func WithLimits ¶
WithLimits applies only positive fields; zero and negative fields retain defaults. Limits admit selected counts, not total string bytes, wall time or collaborator memory.
type Permission ¶
type Permission struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID authorization.PolicyID
// RoleID names an existing same-tenant role; constructors validate that relation.
RoleID RoleID
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID
// Action is matched byte-for-byte; no wildcard, normalization or permission discovery is implied.
Action authorization.Action
// ResourceType is matched exactly; no wildcard or external lookup is implied.
ResourceType authorization.ResourceType
// ResourceID is exact when nonempty; empty makes the grant/rule type-wide.
ResourceID authorization.ResourceID
// Effect must select an explicit allow or deny; model construction rejects other outcomes.
Effect authorization.Outcome
// Priority orders higher values first where the selected model supports ordering.
Priority int
}
Permission grants/denies one exact action/type and optional resource instance; its tenant must match the referenced role.
type PermissionDocument ¶
type PermissionDocument struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID authorization.PolicyID `json:"id"`
// RoleID names an existing same-tenant role; constructors validate that relation.
RoleID RoleID `json:"role_id"`
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID `json:"tenant,omitempty"`
// Action is matched byte-for-byte; no wildcard, normalization or permission discovery is implied.
Action authorization.Action `json:"action"`
// ResourceType is matched exactly; no wildcard or external lookup is implied.
ResourceType authorization.ResourceType `json:"resource_type"`
// ResourceID is exact when nonempty; empty makes the grant/rule type-wide.
ResourceID authorization.ResourceID `json:"resource_id,omitempty"`
// Effect must select an explicit allow or deny; model construction rejects other outcomes.
Effect string `json:"effect"`
// Priority orders higher values first where the selected model supports ordering.
Priority int `json:"priority,omitempty"`
}
PermissionDocument is editable exact role/action/resource permission input.
type Role ¶
type Role struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID RoleID
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID
// Parents lists same-tenant inherited roles; New copies and validates the acyclic graph.
Parents []RoleID
}
Role defines same-tenant inheritance through copied parent IDs.
type RoleDocument ¶
type RoleDocument struct {
// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
ID RoleID `json:"id"`
// Tenant is exact scope; empty means global, not inferred tenant membership.
Tenant authorization.TenantID `json:"tenant,omitempty"`
// Parents lists same-tenant inherited roles; New copies and validates the acyclic graph.
Parents []RoleID `json:"parents,omitempty"`
}
RoleDocument is editable same-tenant role inheritance input.