Documentation
¶
Overview ¶
Package canery provides a small, reusable authorization core built around generic subjects, actions, resources, and scopes.
The primitive API is request-based: callers build a Request and ask an Authorizer to evaluate it. A fluent Builder is also provided for readability, but it is only a thin layer over the same Request model. Small helper constructors such as Actor, Resource, and Scope are also available for ergonomics, but they remain thin wrappers over the exported structs. User is available as a convenience helper, not as the preferred generic entrypoint.
Authorization state is not stored inside the package. Evaluation is delegated to pluggable readers and resolvers so the package can stay storage-agnostic and reusable across projects.
canery is not a full IAM system. It does not model tenants, roles, directories, or product semantics by itself. Those concerns belong in adapters and backends built on top of the core package.
Engine is the default shipped evaluator. It applies one membership-scoped evaluation pipeline over the shared request model, but the model itself is intended to remain broad enough for alternate evaluators later.
Index ¶
- Constants
- Variables
- type Action
- type ActionDecision
- type ActorRef
- type Authorizer
- type BatchCheckError
- type Builder
- func (b Builder) Can(action Action) Builder
- func (b Builder) CanMany(actions ...Action) MultiActionBuilder
- func (b Builder) Check(ctx context.Context) (bool, error)
- func (b Builder) In(scope ScopeRef) Builder
- func (b Builder) On(resource ResourceRef) Builder
- func (b Builder) Request() Request
- func (b Builder) Target(resource ResourceRef) Builder
- func (b Builder) Within(scope ScopeRef) Builder
- type Decision
- type DecisionEvaluator
- type Engine
- func (e *Engine) BatchCheck(ctx context.Context, requests []Request) ([]Decision, error)
- func (e *Engine) Check(ctx context.Context, request Request) (bool, error)
- func (e *Engine) CheckDecision(ctx context.Context, request Request) (Decision, error)
- func (e *Engine) CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)
- func (e *Engine) For(subject Subject) Builder
- type GroupReader
- type GroupRef
- type MembershipReader
- type MultiActionBuilder
- func (b MultiActionBuilder) Check(ctx context.Context) (MultiActionResult, error)
- func (b MultiActionBuilder) In(scope ScopeRef) MultiActionBuilder
- func (b MultiActionBuilder) On(resource ResourceRef) MultiActionBuilder
- func (b MultiActionBuilder) Requests() []Request
- func (b MultiActionBuilder) Target(resource ResourceRef) MultiActionBuilder
- func (b MultiActionBuilder) Within(scope ScopeRef) MultiActionBuilder
- type MultiActionResult
- type PermissionReader
- type Policy
- type PolicyAuthorizer
- func (a *PolicyAuthorizer) Check(ctx context.Context, request Request) (bool, error)
- func (a *PolicyAuthorizer) CheckDecision(ctx context.Context, request Request) (Decision, error)
- func (a *PolicyAuthorizer) CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)
- func (a *PolicyAuthorizer) For(subject Subject) Builder
- type PolicyBinding
- func ForAction(action Action, policy Policy) PolicyBinding
- func ForActionInScopeType(action Action, scopeType string, policy Policy) PolicyBinding
- func ForActionOnResourceType(action Action, resourceType string, policy Policy) PolicyBinding
- func ForResourceType(resourceType string, policy Policy) PolicyBinding
- func ForScopeType(scopeType string, policy Policy) PolicyBinding
- func MatchRequests(match RequestMatcher, policy Policy) PolicyBinding
- type PolicyFunc
- type PrincipalKind
- type PrincipalRef
- type Request
- type RequestMatcher
- type ResourceRef
- type ResourceScopeResolver
- type ScopeRef
- type Subject
- type Trace
- type TraceStep
- type ValidationError
Examples ¶
Constants ¶
const ( // DecisionSourceDirect indicates that a direct subject permission matched. DecisionSourceDirect = "direct" // DecisionSourceGroup indicates that a group-derived permission matched. DecisionSourceGroup = "group" // DecisionSourceNone indicates that no permission source produced an allow. DecisionSourceNone = "none" )
Variables ¶
var ( // ErrInvalidRequest indicates that a request is missing required fields. ErrInvalidRequest = errors.New("canery: invalid request") // ErrMissingMembership indicates that the engine has no MembershipReader. ErrMissingMembership = errors.New("canery: missing membership reader") // ErrMissingGroupReader indicates that the engine has no GroupReader. ErrMissingGroupReader = errors.New("canery: missing group reader") // ErrMissingPermissions indicates that the engine has no PermissionReader. ErrMissingPermissions = errors.New("canery: missing permission reader") // ErrMissingScopeResolver indicates that the engine cannot validate a // concrete resource against a scope because no resolver was configured. ErrMissingScopeResolver = errors.New("canery: missing resource scope resolver") )
var ErrMissingAuthorizer = errors.New("canery: missing authorizer")
Functions ¶
This section is empty.
Types ¶
type ActionDecision ¶
ActionDecision contains the authorization outcome for a single action.
type ActorRef ¶
type ActorRef = Subject
ActorRef is a readability alias for Subject.
Subject remains the core request field name for backward compatibility.
type Authorizer ¶
type Authorizer interface {
// CheckDecision evaluates a low-level request directly and returns the
// explicit decision object.
CheckDecision(ctx context.Context, request Request) (Decision, error)
// CheckTrace evaluates a low-level request directly and also returns a
// high-level trace of the evaluation flow for debugging.
CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)
// Check evaluates a low-level request directly.
Check(ctx context.Context, request Request) (bool, error)
// For starts a fluent builder for the given subject.
For(subject Subject) Builder
}
Authorizer evaluates authorization requests and exposes a fluent request builder rooted on a subject.
Engine is the default implementation shipped by the package, but the interface is intentionally broad enough for alternate evaluators over the same request model.
type BatchCheckError ¶
BatchCheckError identifies the request that caused a batch evaluation to fail.
func (BatchCheckError) Error ¶
func (e BatchCheckError) Error() string
func (BatchCheckError) Unwrap ¶
func (e BatchCheckError) Unwrap() error
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder incrementally assembles a Request before evaluating it through an Authorizer.
func (Builder) CanMany ¶
func (b Builder) CanMany(actions ...Action) MultiActionBuilder
CanMany starts a multi-action request for the same subject, resource, and scope.
Example ¶
engine := NewEngine(
exampleMembershipReader{},
exampleGroupReader{},
examplePermissionReader{},
exampleScopeResolver{},
)
result, err := engine.
For(User("user-1")).
CanMany(Action("update"), Action("delete")).
Target(Resource("document", "doc-1")).
In(Scope("project", "project-1")).
Check(context.Background())
canUpdate, _ := result.Allowed(Action("update"))
canDelete, _ := result.Allowed(Action("delete"))
fmt.Println(err == nil, canUpdate, canDelete)
Output: true true false
func (Builder) On ¶
func (b Builder) On(resource ResourceRef) Builder
On sets the resource being targeted.
func (Builder) Request ¶
Request returns the low-level request represented by the builder.
Example ¶
engine := NewEngine(
exampleMembershipReader{},
exampleGroupReader{},
examplePermissionReader{},
exampleScopeResolver{},
)
request := engine.
For(ActorRef{Type: "user", ID: "user-1"}).
Can(Action("create")).
Target(ResourceRef{Type: "document"}).
In(ScopeRef{Type: "project", ID: "project-1"}).
Request()
fmt.Println(request.Subject.Type, request.Action, request.Resource.Type, request.Resource.ID == "")
Output: user create document true
func (Builder) Target ¶
func (b Builder) Target(resource ResourceRef) Builder
Target is a readability wrapper around On.
type Decision ¶
Decision is the explicit authorization outcome for a single request.
Reason and Source provide generic explanation metadata about how the decision was reached without exposing backend-specific storage details.
type DecisionEvaluator ¶
type DecisionEvaluator interface {
// CheckDecision evaluates a low-level request directly and returns the
// explicit decision object.
CheckDecision(ctx context.Context, request Request) (Decision, error)
}
DecisionEvaluator is the minimal continuation interface used by policies to delegate back to the next policy or the wrapped authorizer.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the default Authorizer implementation backed by pluggable readers and a resource scope resolver.
Engine intentionally implements one default evaluation strategy: a membership-scoped pipeline that validates the request, optionally checks a concrete resource against the requested scope, confirms scope membership, and then evaluates direct and group-based permissions.
The request model and readers are broader than this one strategy. Future evaluators can reuse the same Request, Decision, and reader interfaces while applying a different evaluation order.
func NewEngine ¶
func NewEngine(memberships MembershipReader, groups GroupReader, permissions PermissionReader, resolver ResourceScopeResolver) *Engine
NewEngine constructs an Engine that evaluates requests using the provided membership, group, permission, and resource-scope backends.
func (*Engine) BatchCheck ¶
BatchCheck evaluates a slice of requests and returns one explicit decision per request.
It reuses the same validation and evaluation path as Check. If a request is invalid, BatchCheck stops and returns a BatchCheckError identifying the failing item.
func (*Engine) Check ¶
Check evaluates a low-level request directly and preserves the original boolean API for backward compatibility.
Example ¶
engine := NewEngine(
exampleMembershipReader{},
exampleGroupReader{},
examplePermissionReader{},
exampleScopeResolver{},
)
ok, err := engine.Check(context.Background(), Request{
Subject: ActorRef{Type: "user", ID: "user-1"},
Action: Action("update"),
Resource: ResourceRef{Type: "document", ID: "doc-1"},
Scope: ScopeRef{Type: "project", ID: "project-1"},
})
fmt.Println(ok, err == nil)
Output: true true
func (*Engine) CheckDecision ¶
CheckDecision evaluates a request in this order:
- validate required request fields
- verify resource-to-scope membership when a resource ID is provided
- verify subject membership in the scope
- check direct subject permissions
- resolve groups and check group permissions
The first matching allow returns an allowed Decision. Missing matches return a denied Decision. The engine does not implement deny rules or policy precedence beyond this order.
func (*Engine) CheckTrace ¶
CheckTrace evaluates a request and also returns a high-level trace that can help callers debug how the final decision was reached.
func (*Engine) For ¶
For starts a fluent authorization request for the given subject.
Example ¶
engine := NewEngine(
exampleMembershipReader{},
exampleGroupReader{},
examplePermissionReader{},
exampleScopeResolver{},
)
ok, err := engine.
For(ActorRef{Type: "user", ID: "user-1"}).
Can(Action("update")).
Target(ResourceRef{Type: "document", ID: "doc-1"}).
In(ScopeRef{Type: "project", ID: "project-1"}).
Check(context.Background())
fmt.Println(ok, err == nil)
Output: true true
type GroupReader ¶
type GroupReader interface {
GroupsForSubject(ctx context.Context, subject Subject, scope ScopeRef) ([]GroupRef, error)
}
GroupReader resolves the groups a subject belongs to within a given scope.
Today the scope is a single ScopeRef. A future hierarchical model may use that scope as the leaf context that determines which inherited or local groups should be visible.
type MembershipReader ¶
type MembershipReader interface {
HasMembership(ctx context.Context, subject Subject, scope ScopeRef) (bool, error)
}
MembershipReader reports whether a subject belongs to a given scope.
The public API stays single-scope for now. Implementations can already treat that scope as the current boundary in a broader hierarchy if they need to prepare for future nesting support.
type MultiActionBuilder ¶
type MultiActionBuilder struct {
// contains filtered or unexported fields
}
MultiActionBuilder incrementally assembles a repeated request over multiple actions for the same subject, resource, and scope.
func (MultiActionBuilder) Check ¶
func (b MultiActionBuilder) Check(ctx context.Context) (MultiActionResult, error)
Check evaluates each action through the configured Authorizer and returns one decision per action.
func (MultiActionBuilder) In ¶
func (b MultiActionBuilder) In(scope ScopeRef) MultiActionBuilder
In is a readability wrapper around Within.
func (MultiActionBuilder) On ¶
func (b MultiActionBuilder) On(resource ResourceRef) MultiActionBuilder
On sets the resource being targeted for all actions.
func (MultiActionBuilder) Requests ¶
func (b MultiActionBuilder) Requests() []Request
Requests returns one low-level request per action represented by the builder.
func (MultiActionBuilder) Target ¶
func (b MultiActionBuilder) Target(resource ResourceRef) MultiActionBuilder
Target is a readability wrapper around On.
func (MultiActionBuilder) Within ¶
func (b MultiActionBuilder) Within(scope ScopeRef) MultiActionBuilder
Within sets the scope in which all actions should be evaluated.
type MultiActionResult ¶
type MultiActionResult struct {
Decisions map[Action]ActionDecision
}
MultiActionResult contains the decision for each requested action.
type PermissionReader ¶
type PermissionReader interface {
HasPermission(ctx context.Context, principal PrincipalRef, request Request) (bool, error)
}
PermissionReader reports whether a principal is allowed to perform a request.
The principal may represent either the subject directly or a group previously resolved for that subject.
type Policy ¶
type Policy interface {
CheckDecision(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)
}
Policy can wrap authorization decisions for a matched request before delegating to the next evaluator.
Policies are optional and compose on top of the core request-driven authorizer. They can handle a request directly or call next.CheckDecision to continue the evaluation chain.
type PolicyAuthorizer ¶
type PolicyAuthorizer struct {
// contains filtered or unexported fields
}
PolicyAuthorizer wraps an Authorizer with an ordered set of optional policies.
Policies run only when their binding matches the request. A matched policy can return a decision directly or delegate to the next evaluator.
func NewPolicyAuthorizer ¶
func NewPolicyAuthorizer(base Authorizer, bindings ...PolicyBinding) *PolicyAuthorizer
NewPolicyAuthorizer constructs an Authorizer that evaluates matched policies before falling back to the wrapped base authorizer.
func (*PolicyAuthorizer) Check ¶
Check preserves the original boolean API for backward compatibility.
func (*PolicyAuthorizer) CheckDecision ¶
CheckDecision evaluates the request through the matching policy chain and then the wrapped authorizer.
func (*PolicyAuthorizer) CheckTrace ¶
func (a *PolicyAuthorizer) CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)
CheckTrace evaluates the request through the matching policy chain and also returns a high-level trace for debugging.
func (*PolicyAuthorizer) For ¶
func (a *PolicyAuthorizer) For(subject Subject) Builder
For starts a fluent authorization request for the given subject.
type PolicyBinding ¶
type PolicyBinding struct {
Match RequestMatcher
Policy Policy
}
PolicyBinding associates a request matcher with a policy.
func ForAction ¶
func ForAction(action Action, policy Policy) PolicyBinding
ForAction creates a policy binding that applies to one action.
func ForActionInScopeType ¶
func ForActionInScopeType(action Action, scopeType string, policy Policy) PolicyBinding
ForActionInScopeType creates a policy binding that applies when both the action and scope type match.
func ForActionOnResourceType ¶
func ForActionOnResourceType(action Action, resourceType string, policy Policy) PolicyBinding
ForActionOnResourceType creates a policy binding that applies when both the action and resource type match.
func ForResourceType ¶
func ForResourceType(resourceType string, policy Policy) PolicyBinding
ForResourceType creates a policy binding that applies to one resource type.
func ForScopeType ¶
func ForScopeType(scopeType string, policy Policy) PolicyBinding
ForScopeType creates a policy binding that applies to one scope type.
func MatchRequests ¶
func MatchRequests(match RequestMatcher, policy Policy) PolicyBinding
MatchRequests creates a policy binding backed by an arbitrary request matcher.
type PolicyFunc ¶
type PolicyFunc func(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)
PolicyFunc adapts a function to the Policy interface.
func (PolicyFunc) CheckDecision ¶
func (f PolicyFunc) CheckDecision(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)
CheckDecision calls f(ctx, request, next).
type PrincipalKind ¶
type PrincipalKind string
PrincipalKind distinguishes direct subject checks from group-based checks.
const ( // PrincipalKindSubject represents a direct subject principal. PrincipalKindSubject PrincipalKind = "subject" // PrincipalKindGroup represents a group principal. PrincipalKindGroup PrincipalKind = "group" )
type PrincipalRef ¶
type PrincipalRef struct {
Kind PrincipalKind
Type string
ID string
}
PrincipalRef identifies the principal used by a PermissionReader.
Principals can represent either the subject directly or a group resolved for that subject within a scope.
func GroupPrincipal ¶
func GroupPrincipal(group GroupRef) PrincipalRef
GroupPrincipal converts a group into a principal for group-based permission evaluation.
func SubjectPrincipal ¶
func SubjectPrincipal(subject Subject) PrincipalRef
SubjectPrincipal converts a subject into a principal for direct permission evaluation.
type Request ¶
type Request struct {
Subject Subject
Action Action
Resource ResourceRef
Scope ScopeRef
}
Request is the low-level authorization primitive evaluated by an Authorizer.
type RequestMatcher ¶
RequestMatcher decides whether a policy binding applies to a request.
type ResourceRef ¶
ResourceRef identifies the target resource for a check.
ID may be left empty for create-style checks where the resource does not yet exist and authorization is based on resource type and scope alone.
func Resource ¶
func Resource(kind string, id string) ResourceRef
Resource returns a ResourceRef for the given resource kind and identifier.
type ResourceScopeResolver ¶
type ResourceScopeResolver interface {
ResourceInScope(ctx context.Context, resource ResourceRef, scope ScopeRef) (bool, error)
}
ResourceScopeResolver verifies that a resource belongs to the provided scope.
It is only required when a request targets a concrete resource ID. Implementations should treat the provided scope as the request's current boundary. That leaves room for future evolution toward ancestor-aware scope resolution without changing the request model today.
type ScopeRef ¶
ScopeRef identifies the boundary in which the request is evaluated.
The current API models one explicit scope per request. The engine keeps its internals structured so that this boundary can evolve toward hierarchical scope evaluation later without changing the request shape immediately.
type Subject ¶
Subject identifies the actor asking to perform an action.
func Actor ¶
Actor returns a Subject for the given actor kind and identifier.
Example ¶
engine := NewEngine(
exampleMembershipReader{},
exampleGroupReader{},
examplePermissionReader{},
exampleScopeResolver{},
)
ok, err := engine.Check(context.Background(), Request{
Subject: Actor("user", "user-1"),
Action: Action("update"),
Resource: Resource("document", "doc-1"),
Scope: Scope("project", "project-1"),
})
fmt.Println(ok, err == nil)
Output: true true
type Trace ¶
type Trace struct {
Steps []TraceStep
}
Trace captures high-level evaluation steps for a single authorization check.
It is intended for debugging and inspection only. The trace stays generic and does not expose storage-specific details.
type ValidationError ¶
ValidationError identifies a specific invalid request field with a stable field/code pair.
func (ValidationError) Error ¶
func (e ValidationError) Error() string
func (ValidationError) Unwrap ¶
func (e ValidationError) Unwrap() error
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
batch-check
command
|
|
|
decision-explanation-check
command
|
|
|
direct-request-check
command
|
|
|
fluent-builder-check
command
|
|
|
helper-based-check
command
|
|