rbac

package
v4.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 10 Imported by: 0

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/v4"
	"github.com/faustbrian/go-authorization/v4/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

Examples

Constants

View Source
const (
	// EffectAllow is the serialized explicit allow label.
	EffectAllow = "allow"
	// EffectDeny is the serialized explicit deny label.
	EffectDeny = "deny"
)
View Source
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"
)
View Source
const DocumentVersion uint64 = 1

DocumentVersion is version 1 of this application model JSON profile.

Variables

View Source
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")
)
View Source
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

func EncodeDocument(document Document) ([]byte, error)

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.

func (Document) Build

func (document Document) Build() (*Evaluator, error)

Build constructs and validates a fresh model evaluator for DocumentVersion. It does not impose the encoded-byte cap or authenticate provenance. Caller-owned document containers must stay immutable during construction.

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

func DecodeDocument(encoded []byte) (*Evaluator, error)

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

func (manager *Manager) Evaluator() (*Evaluator, error)

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

func WithLimits(limits Limits) Option

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.

type RoleID

type RoleID string

RoleID is an exact application role label, not authenticated membership.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL