abac

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: 13 Imported by: 0

Documentation

Overview

Package abac evaluates a closed typed expression model over caller-owned facts. New binds inline/versioned named conditions and sorts rules by priority then ID. Exact tenant/action/resource scope is required; explicit deny wins among matches, and standalone no-match is NotApplicable. Authentication, attribute truth and external I/O are not owned. Built-in constructors copy expression slices and use immutable Values; the request maps are borrowed during each call. Costs count visited expression nodes, not string bytes, CPU time or allocation. Condition is closed to direct external implementations, not an execution sandbox for embedded/trusted collaborators. Model JSON is the application v1 profile.

Example
package main

import (
	"context"
	"fmt"

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

func main() {
	evaluator, err := abac.New(
		[]abac.Rule{
			{
				ID:           "finance-read",
				Action:       "report.read",
				ResourceType: "report",
				Effect:       authorization.Allow,
				Condition: abac.Equal(
					abac.Reference{Source: abac.Subject, Name: "department"},
					authorization.StringValue("finance"),
				),
			},
		},
		nil,
	)
	if err != nil {
		panic(err)
	}

	decision, err := evaluator.Evaluate(context.Background(), authorization.Request{
		Subject: authorization.Subject{
			Kind: authorization.SubjectUser,
			ID:   "alice",
			Attributes: authorization.Attributes{
				"department": authorization.StringValue("finance"),
			},
		},
		Action:   "report.read",
		Resource: authorization.Resource{Type: "report", ID: "quarterly"},
	})
	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 = "abac-allow"
	// ReasonExplicitDeny marks a matching model denial.
	ReasonExplicitDeny authorization.ReasonCode = "abac-explicit-deny"
	// ReasonLimitExceeded marks model work rejected by a configured count/cost limit.
	ReasonLimitExceeded authorization.ReasonCode = "abac-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 (
	// SourceSubject is the serialized subject attribute-map label.
	SourceSubject = "subject"
	// SourceResource is the serialized resource attribute-map label.
	SourceResource = "resource"
	// SourceRequest is the serialized request attribute-map label.
	SourceRequest = "request"
	// SourceEnvironment is the serialized environment attribute-map label.
	SourceEnvironment = "environment"
)
View Source
const (
	// OperatorEqual selects the serialized built-in equality operator; see the corresponding typed constructor.
	OperatorEqual = "equal"
	// OperatorExists selects the serialized built-in existence operator; see the corresponding typed constructor.
	OperatorExists = "exists"
	// OperatorIsNull selects the serialized built-in null operator; see the corresponding typed constructor.
	OperatorIsNull = "is_null"
	// OperatorAll selects the serialized built-in all operator; see the corresponding typed constructor.
	OperatorAll = "all"
	// OperatorAny selects the serialized built-in any operator; see the corresponding typed constructor.
	OperatorAny = "any"
	// OperatorNot selects the serialized built-in negation operator; see the corresponding typed constructor.
	OperatorNot = "not"
	// OperatorGreaterThan selects the serialized built-in greater-than operator; see the corresponding typed constructor.
	OperatorGreaterThan = "greater_than"
	// OperatorLessThan selects the serialized built-in less-than operator; see the corresponding typed constructor.
	OperatorLessThan = "less_than"
	// OperatorIn selects the serialized built-in membership operator; see the corresponding typed constructor.
	OperatorIn = "in"
	// OperatorSetContains selects the serialized built-in set-membership operator; see the corresponding typed constructor.
	OperatorSetContains = "set_contains"
	// OperatorHasPrefix selects the serialized built-in prefix operator; see the corresponding typed constructor.
	OperatorHasPrefix = "has_prefix"
	// OperatorHasSuffix selects the serialized built-in suffix operator; see the corresponding typed constructor.
	OperatorHasSuffix = "has_suffix"
	// OperatorStringContains selects the serialized built-in substring operator; see the corresponding typed constructor.
	OperatorStringContains = "string_contains"
	// OperatorIPIn selects the serialized built-in CIDR-membership operator; see the corresponding typed constructor.
	OperatorIPIn = "ip_in"
)
View Source
const (
	// ValueNull is the serialized null value-kind label.
	ValueNull = "null"
	// ValueString is the serialized string value-kind label.
	ValueString = "string"
	// ValueBool is the serialized bool value-kind label.
	ValueBool = "bool"
	// ValueInt is the serialized int value-kind label.
	ValueInt = "int"
	// ValueFloat is the serialized float value-kind label.
	ValueFloat = "float"
	// ValueTime is the serialized time value-kind label.
	ValueTime = "time"
	// ValueIP is the serialized ip value-kind label.
	ValueIP = "ip"
	// ValueStringSet is the serialized string_set value-kind label.
	ValueStringSet = "string_set"
)
View Source
const DocumentVersion uint64 = 1

DocumentVersion is version 1 of this application model JSON profile.

Variables

View Source
var (
	// ErrInvalidRule identifies invalid ABAC rule. Compare with errors.Is, not error text.
	ErrInvalidRule = errors.New("invalid ABAC rule")
	// ErrInvalidCondition identifies invalid ABAC condition. Compare with errors.Is, not error text.
	ErrInvalidCondition = errors.New("invalid ABAC condition")
	// ErrCostExceeded identifies ABAC evaluation cost exceeded. Compare with errors.Is, not error text.
	ErrCostExceeded = errors.New("ABAC evaluation cost exceeded")
	// ErrDepthExceeded identifies ABAC condition depth exceeded. Compare with errors.Is, not error text.
	ErrDepthExceeded = errors.New("ABAC condition depth exceeded")
	// ErrRuleLimitExceeded identifies ABAC rule limit exceeded. Compare with errors.Is, not error text.
	ErrRuleLimitExceeded = errors.New("ABAC rule limit exceeded")
	// ErrSetLimitExceeded identifies ABAC set limit exceeded. Compare with errors.Is, not error text.
	ErrSetLimitExceeded = errors.New("ABAC set limit exceeded")
	// ErrMatchLimitExceeded identifies ABAC match limit exceeded. Compare with errors.Is, not error text.
	ErrMatchLimitExceeded = errors.New("ABAC match limit exceeded")
	// ErrBatchLimitExceeded identifies ABAC batch limit exceeded. Compare with errors.Is, not error text.
	ErrBatchLimitExceeded = errors.New("ABAC batch limit exceeded")
	// ErrUnknownNamedCondition identifies unknown named ABAC condition. Compare with errors.Is, not error text.
	ErrUnknownNamedCondition = errors.New("unknown named ABAC condition")
	// ErrDuplicateNamedCondition identifies duplicate named ABAC condition. Compare with errors.Is, not error text.
	ErrDuplicateNamedCondition = errors.New("duplicate named ABAC condition")
)
View Source
var (
	// ErrInvalidDocument identifies invalid ABAC document. Compare with errors.Is, not error text.
	ErrInvalidDocument = errors.New("invalid ABAC document")
	// ErrUnsupportedDocumentVersion identifies unsupported ABAC document version. Compare with errors.Is, not error text.
	ErrUnsupportedDocumentVersion = errors.New("unsupported ABAC document version")
	// ErrDocumentLimitExceeded identifies ABAC document size limit exceeded. Compare with errors.Is, not error text.
	ErrDocumentLimitExceeded = errors.New("ABAC 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 Condition

type Condition interface {
	// contains filtered or unexported methods
}

Condition is the built-in expression contract with package-private methods. Constructors defer validation to New/EvaluateCondition; do not treat embedding an arbitrary implementation as safe plugin admission.

func All

func All(conditions ...Condition) Condition

All copies child order and short-circuits on the first false/error. Empty or nil children fail later validation, not this constructor.

func Any

func Any(conditions ...Condition) Condition

Any copies child order, short-circuits on match/error and otherwise retains the first non-NoMatch diagnostic status. Empty/nil children fail validation.

func Equal

func Equal(reference Reference, want authorization.Value) Condition

Equal compares exact kinds and structural Values; explicit null can match null, while missing values and mismatched kinds keep distinct statuses.

func Exists

func Exists(reference Reference) Condition

Exists matches a present non-missing value, including explicit null.

func GreaterThan

func GreaterThan(reference Reference, want authorization.Value) Condition

GreaterThan compares same-kind strings/int/float/time strictly above want; unsupported comparison kinds fail validation.

func HasPrefix

func HasPrefix(reference Reference, value string) Condition

HasPrefix tests a case-sensitive string prefix without coercion or byte budgets.

func HasSuffix

func HasSuffix(reference Reference, value string) Condition

HasSuffix tests a case-sensitive string suffix without coercion or byte budgets.

func IPIn

func IPIn(reference Reference, prefix netip.Prefix) Condition

IPIn masks the prefix and tests a typed address; invalid prefixes fail validation. Address/prefix family compatibility is netip semantics, not identity trust.

func In

func In(reference Reference, values ...authorization.Value) Condition

In copies same-kind non-null candidates and tests structural membership. Validation counts candidates, not all bytes/nested string-set element work.

func IsNull

func IsNull(reference Reference) Condition

IsNull matches only a present explicit-null value, not absence.

func LessThan

func LessThan(reference Reference, want authorization.Value) Condition

LessThan compares same-kind strings/int/float/time strictly below want; unsupported comparison kinds fail validation.

func Not

func Not(condition Condition) Condition

Not negates Match/NoMatch only; missing/null/type-mismatch statuses remain nonmatching rather than becoming an accidental allow. Nil fails later validation.

func SetContains

func SetContains(reference Reference, value string) Condition

SetContains tests one string in a sorted typed string set, after set-size admission; copied accessors may allocate and string-byte work is not cost-counted.

func StringContains

func StringContains(reference Reference, value string) Condition

StringContains tests a case-sensitive string substring without coercion or byte budgets.

type ConditionDocument

type ConditionDocument struct {
	// Operator selects one supported expression operation; unrelated fields may remain unused.
	Operator string `json:"operator"`
	// Source selects the request fact map, not an external data provider.
	Source string `json:"source,omitempty"`
	// Attribute names an exact map key for a leaf operation.
	Attribute authorization.AttributeName `json:"attribute,omitempty"`
	// Value supplies the comparison literal for equal/greater/less operations.
	Value *ValueDocument `json:"value,omitempty"`
	// Values supplies the same-kind membership candidates for in.
	Values []ValueDocument `json:"values,omitempty"`
	// Conditions supplies ordered children for all/any; Build validates visited children.
	Conditions []ConditionDocument `json:"conditions,omitempty"`
	// Condition supplies the single child for not; other operators do not consume it.
	Condition *ConditionDocument `json:"condition,omitempty"`
	// Text supplies the string operand for set/string operations, not executable input.
	Text string `json:"text,omitempty"`
	// Prefix is the CIDR operand for ip_in, normalized by IPIn.
	Prefix string `json:"prefix,omitempty"`
}

ConditionDocument is editable recursive expression data; Build visits only operator-selected children before constructing validated Conditions.

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"`
	// Limits supplies positive count/depth/cost overrides; non-positive fields retain model defaults.
	Limits Limits `json:"limits,omitempty"`
	// Rules supplies editable rule definitions; Build/New validates and sorts them.
	Rules []RuleDocument `json:"rules"`
	// NamedConditions supplies editable named/versioned expression definitions.
	NamedConditions []NamedConditionDocument `json:"named_conditions"`
}

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 holds a sorted constructed rule view with no I/O/lifecycle. Request attribute maps must remain immutable during concurrent evaluation.

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(
	rules []Rule,
	namedConditions []NamedCondition,
	options ...Option,
) (*Evaluator, error)

New validates exact identities/effects, unique rule IDs, named-condition bindings and expression depth/literal cardinality, then copies/sorts rules. MaxCost is evaluation admission, not a total constructor-node budget. Typed-nil embedded conditions/custom options can panic at uncovered construction seams.

func (*Evaluator) Evaluate

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

Evaluate matches scopes and evaluates conditions in sorted order; explicit deny wins among all matches and no-match is NotApplicable. One cost counter is shared across matching rules. Request.Validate is not called; ctx is checked at consumed nodes; a request with no matching scope can return NotApplicable even when canceled.

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 {
	// MaxRules admits rule count; default is 1000.
	MaxRules int `json:"max_rules,omitempty"`
	// MaxDepth admits expression depth; default is 32.
	MaxDepth int `json:"max_depth,omitempty"`
	// MaxCost caps node visits per evaluation; default is 1000, not CPU/string-time admission.
	MaxCost int `json:"max_cost,omitempty"`
	// MaxMatches caps collected matching entries/permissions/rules; default is 100.
	MaxMatches int `json:"max_matches,omitempty"`
	// MaxSetSize admits selected literal/runtime collection cardinality; default is 1000, not total bytes.
	MaxSetSize int `json:"max_set_size,omitempty"`
	// MaxBatchSize admits request count before result allocation; default is 1000.
	MaxBatchSize int `json:"max_batch_size,omitempty"`
	// MaxNamedConditions admits named definition count; default is 1000.
	MaxNamedConditions int `json:"max_named_conditions,omitempty"`
}

Limits bounds selected rule/name/depth/cost/set/match/batch counts. Cost counts expression visits; string operations and literal construction are not byte/time budgets, and non-positive overrides retain defaults.

type NamedCondition

type NamedCondition struct {
	// Name is the exact application label; do not place secrets in error-visible names.
	Name string
	// Version is a nonzero exact named-condition identity, independent of model format version.
	Version uint64
	// Condition is the selected expression; use built-in constructors and avoid nil/embedded unsafe state.
	Condition Condition
}

NamedCondition binds a nonempty name/nonzero version to a validated reusable expression; unused definitions are also validated.

type NamedConditionDocument

type NamedConditionDocument struct {
	// Name is the exact application label; do not place secrets in error-visible names.
	Name string `json:"name"`
	// Version is a nonzero named-condition version, independent of DocumentVersion.
	Version uint64 `json:"version"`
	// Condition supplies editable inline expression data; Build validates the selected tree.
	Condition ConditionDocument `json:"condition"`
}

NamedConditionDocument binds editable expression data to a nonzero name/version.

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 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 Reference

type Reference struct {
	// Source selects the request fact map, not an external data provider.
	Source Source
	// Name is the exact application label; do not place secrets in error-visible names.
	Name authorization.AttributeName
}

Reference names an exact attribute in a selected map; no path traversal or external fetch is performed.

type Result

type Result struct {
	// Matched reports predicate truth, not a final authorization grant.
	Matched bool
	// Status distinguishes predicate match, missing, null and type mismatch.
	Status Status
	// Cost reports consumed expression-node visits, not string-byte work or wall time.
	Cost int
}

Result reports predicate status and accounted node visits, not elapsed cost or a final authorization outcome.

func EvaluateCondition

func EvaluateCondition(
	ctx context.Context,
	condition Condition,
	request authorization.Request,
	limits Limits,
) (Result, error)

EvaluateCondition validates depth/literal set policy, then evaluates with a fresh per-call node budget. Cost is reported from the state even on failure; nil conditions fail. No request authentication or complete request validation occurs.

type Rule

type Rule struct {
	// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
	ID authorization.PolicyID
	// Priority orders higher values first where the selected model supports ordering.
	Priority int
	// 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
	// Condition is the selected expression; use built-in constructors and avoid nil/embedded unsafe state.
	Condition Condition
	// ConditionName selects an exact named condition instead of an inline Condition.
	ConditionName string
	// ConditionVersion must be nonzero with ConditionName, otherwise zero.
	ConditionVersion uint64
}

Rule matches exact tenant/action/type and optional instance before evaluating either Condition or a versioned named condition.

type RuleDocument

type RuleDocument struct {
	// ID is an exact application-defined label; keep diagnostic identifiers non-secret.
	ID authorization.PolicyID `json:"id"`
	// Priority orders higher values first where the selected model supports ordering.
	Priority int `json:"priority,omitempty"`
	// 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"`
	// Condition supplies editable inline expression data; Build validates the selected tree.
	Condition *ConditionDocument `json:"condition,omitempty"`
	// ConditionName selects an exact named condition instead of an inline Condition.
	ConditionName string `json:"condition_name,omitempty"`
	// ConditionVersion must be nonzero with ConditionName, otherwise zero.
	ConditionVersion uint64 `json:"condition_version,omitempty"`
}

RuleDocument is editable scoped rule input with either inline or versioned named-condition selection, not authenticated facts.

type Source

type Source uint8

Source selects one of the four independent request attribute maps.

const (
	// Subject selects subject facts.
	Subject Source = iota
	// Resource selects resource facts.
	Resource
	// Request selects request facts.
	Request
	// Environment selects environment facts.
	Environment
)

type Status

type Status uint8

Status distinguishes match/no-match, missing, explicit null and kind mismatch.

const (
	// StatusNoMatch reports a well-typed predicate that is false.
	StatusNoMatch Status = iota
	// StatusMatch reports a true predicate.
	StatusMatch
	// StatusMissing reports an absent or ValueMissing fact.
	StatusMissing
	// StatusNull reports explicit null for an operator that does not match it.
	StatusNull
	// StatusTypeMismatch reports a fact/literal kind mismatch.
	StatusTypeMismatch
)

type ValueDocument

type ValueDocument struct {
	// Kind selects one supported serialized literal representation; unrelated fields are unused.
	Kind string `json:"kind"`
	// String supplies the string literal when Kind selects string; it is retained plaintext.
	String string `json:"string,omitempty"`
	// Bool supplies the boolean literal when Kind selects bool.
	Bool bool `json:"bool,omitempty"`
	// Int supplies the signed int64 literal when Kind selects int.
	Int int64 `json:"int,omitempty"`
	// Float supplies the finite floating literal when Kind selects float.
	Float float64 `json:"float,omitempty"`
	// Time is parsed as RFC3339Nano then normalized to UTC when Kind selects time.
	Time string `json:"time,omitempty"`
	// IP supplies the parseable address literal when Kind selects ip.
	IP string `json:"ip,omitempty"`
	// StringSet supplies copied/sorted/deduplicated literals when Kind selects string_set.
	StringSet []string `json:"string_set,omitempty"`
}

ValueDocument is a tagged literal; only the selected Kind field is consumed.

Jump to

Keyboard shortcuts

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