acl

package
v3.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package acl indexes explicit subject/group grants and denials by action and resource type. New constructs a reusable read-only view; Evaluate matches exact identifiers and optional type-wide IDs, with explicit deny winning among matches. Standalone no-match is NotApplicable, not final authorization. Global inheritance is opt-in. Groups/tenant/identity are trusted mapped facts, not authenticated here. Entry containers are indexed but unused nested Subject attributes are borrowed. Calls own no I/O/goroutine; request containers must remain immutable. Document v1 is the application JSON format, not complete external ACL-standard conformance.

Example
package main

import (
	"context"
	"fmt"

	authorization "github.com/faustbrian/go-authorization/v3"
	"github.com/faustbrian/go-authorization/v3/acl"
)

func main() {
	accessList, err := acl.New([]acl.Entry{
		{
			ID: "reader",
			Subject: authorization.Subject{
				Kind: authorization.SubjectUser,
				ID:   "alice",
			},
			Action:       "document.read",
			ResourceType: "document",
			Effect:       authorization.Allow,
		},
	})
	if err != nil {
		panic(err)
	}

	decision, err := accessList.Evaluate(context.Background(), authorization.Request{
		Subject: authorization.Subject{
			Kind: authorization.SubjectUser,
			ID:   "alice",
		},
		Action: "document.read",
		Resource: authorization.Resource{
			Type: "document",
			ID:   "document-1",
		},
	})
	if err != nil {
		panic(err)
	}

	fmt.Println(decision.Outcome == authorization.Allow)
}
Output:
true

Index

Examples

Constants

View Source
const (
	// ReasonAllow marks a model grant.
	ReasonAllow authorization.ReasonCode = "acl-allow"
	// ReasonExplicitDeny marks a matching model denial.
	ReasonExplicitDeny authorization.ReasonCode = "acl-explicit-deny"
	// ReasonLimitExceeded marks model work rejected by a configured count/cost limit.
	ReasonLimitExceeded authorization.ReasonCode = "acl-limit-exceeded"
)
View Source
const (
	// EffectAllow is the serialized explicit allow label.
	EffectAllow = "allow"
	// EffectDeny is the serialized explicit deny label.
	EffectDeny = "deny"
)
View Source
const DocumentVersion uint64 = 1

DocumentVersion is version 1 of this application model JSON profile.

Variables

View Source
var (
	// ErrInvalidEntry identifies invalid ACL entry. Compare with errors.Is, not error text.
	ErrInvalidEntry = errors.New("invalid ACL entry")
	// ErrDuplicateEntry identifies duplicate ACL entry. Compare with errors.Is, not error text.
	ErrDuplicateEntry = errors.New("duplicate ACL entry")
	// ErrEntryLimitExceeded identifies ACL entry limit exceeded. Compare with errors.Is, not error text.
	ErrEntryLimitExceeded = errors.New("ACL entry limit exceeded")
	// ErrGroupLimitExceeded identifies ACL group limit exceeded. Compare with errors.Is, not error text.
	ErrGroupLimitExceeded = errors.New("ACL group limit exceeded")
	// ErrMatchLimitExceeded identifies ACL match limit exceeded. Compare with errors.Is, not error text.
	ErrMatchLimitExceeded = errors.New("ACL match limit exceeded")
	// ErrBatchLimitExceeded identifies ACL batch limit exceeded. Compare with errors.Is, not error text.
	ErrBatchLimitExceeded = errors.New("ACL batch limit exceeded")
	// ErrUnboundedResourceSet identifies ACL resource set is unbounded. Compare with errors.Is, not error text.
	ErrUnboundedResourceSet = errors.New("ACL resource set is unbounded")
)
View Source
var (
	// ErrInvalidDocument identifies invalid ACL document. Compare with errors.Is, not error text.
	ErrInvalidDocument = errors.New("invalid ACL document")
	// ErrUnsupportedDocumentVersion identifies unsupported ACL document version. Compare with errors.Is, not error text.
	ErrUnsupportedDocumentVersion = errors.New("unsupported ACL document version")
	// ErrDocumentLimitExceeded identifies ACL document size limit exceeded. Compare with errors.Is, not error text.
	ErrDocumentLimitExceeded = errors.New("ACL document size 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 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 entry/group/match/batch count overrides; other values retain defaults.
	Limits Limits `json:"limits,omitempty"`
	// Entries is the editable ordered ACL document input, copied/indexed by Build/New.
	Entries []EntryDocument `json:"entries"`
}

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 Entry

type Entry struct {
	// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
	ID EntryID
	// Subject supplies trusted kind/ID and optional group/fact state; it is not authenticated here.
	Subject authorization.Subject
	// 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
	// Tenant is exact scope; empty means global, not inferred tenant membership.
	Tenant authorization.TenantID
	// Effect must select an explicit allow or deny; model construction rejects other outcomes.
	Effect authorization.Outcome
}

Entry grants or denies one subject an action on a resource type or instance.

type EntryDocument

type EntryDocument struct {
	// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
	ID EntryID `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"`
	// 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"`
	// Tenant is exact scope; empty means global, not inferred tenant membership.
	Tenant authorization.TenantID `json:"tenant,omitempty"`
	// Effect must select an explicit allow or deny; model construction rejects other outcomes.
	Effect string `json:"effect"`
}

EntryDocument is the editable version-1 exact subject/action/resource grant.

type EntryID

type EntryID = authorization.PolicyID

EntryID preserves PolicyID identity for ACL entry diagnostics.

type Evaluator

type Evaluator struct {
	// contains filtered or unexported fields
}

Evaluator resolves immutable ACL entries without external I/O.

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(entries []Entry, options ...Option) (*Evaluator, error)

New checks entry count, unique IDs, required identity/action/type strings and allow/deny effects before publishing indexes. Entry values are copied, but nested Subject groups/attribute maps are not deeply isolated (they are unused for matching). Nil/custom options may panic; positive limits replace defaults and non-positive fields are ignored. No context/string-byte budget is applied.

func (*Evaluator) Evaluate

func (evaluator *Evaluator) Evaluate(
	ctx context.Context,
	request authorization.Request,
) (authorization.Decision, error)

Evaluate matches the subject and deduplicated trusted groups in request order. Actions/types/tenant are exact; empty entry ResourceID is type-wide. All matches are collected up to MaxMatches and any deny overrides allows. Limit/cancel errors deny and discard partial diagnostics; no match is NotApplicable. It does not call Request.Validate and checks ctx between principals, not every entry.

func (*Evaluator) EvaluateBatch

func (evaluator *Evaluator) EvaluateBatch(
	ctx context.Context,
	requests []authorization.Request,
) ([]authorization.Decision, error)

EvaluateBatch evaluates all requests against the same ACL view and returns each decision with joined per-request errors. Oversized input returns nil; cancellation is delegated to Evaluate and does not short-circuit the batch.

func (*Evaluator) ListResourceIDs

func (evaluator *Evaluator) ListResourceIDs(
	ctx context.Context,
	subject authorization.Subject,
	action authorization.Action,
	resourceType authorization.ResourceType,
	tenant authorization.TenantID,
) ([]authorization.ResourceID, error)

ListResourceIDs returns only explicitly enumerable instance grants. A resource-type allow is reported as unbounded instead of triggering a storage scan; a type-wide deny returns an empty set immediately. Results are sorted, duplicate principals are ignored and exact matched-entry caps apply. No request authentication/validation is performed; context is checked between principals.

type Limits

type Limits struct {
	// MaxEntries admits ACL entries at construction; default is 10000.
	MaxEntries int `json:"max_entries,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 bounds ACL construction and per-request evaluation work.

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 makes global entries apply to tenant-scoped requests. Without this option, global and tenant scopes are isolated.

func WithLimits

func WithLimits(limits Limits) Option

WithLimits configures positive bounds while retaining safe defaults for zero-valued fields.

Jump to

Keyboard shortcuts

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