Documentation
¶
Overview ¶
Package authorizationcfg builds an authorization.PolicyResolver from configuration.
What it builds is the static resolver, optionally wrapped in authorization/cached. Both are declarations: a set of roles that came from a build or a config file, and a decorator over whatever it was handed. Neither owns a table, and neither needs a database.Client, so a service that authorizes against policy it ships can wire this package and nothing else.
The zero value works and grants nothing. That is deliberate on both counts: the most accessible implementation is the default so the package runs with no infrastructure, and an unconfigured authorization layer denies rather than admits.
Because the caching decision is made here rather than by the caller, a process that edits policy reaches invalidation by asserting authorization.PolicyInvalidator on the returned resolver rather than by naming a concrete type.
Why there is no Provider field ¶
There used to be one, selecting between the static resolver and the SQL-backed one in authorization/database. It has moved, along with the database block it gated, to authzdbcfg — the config subpackage beside the store it builds.
The rule behind the move is one sentence, and it is the same one applied to webauthncfg and oauth2servercfg: the provider string exists because a second implementation exists, so it belongs with the implementation that created the choice. Take the SQL resolver away and this package has exactly one thing to build, which is nothing to select between; a Provider field with one legal value is a field whose only reachable value is the default.
The move was forced rather than chosen. authorization is a provider behind an interface and leaves for primitives-go; authorization/database owns a table and stays. A config that dispatched on a provider string named both, so it could travel with neither, and the module README's "Primitives and Domains" section is where that constraint is written down.
Why it was not the other two exits ¶
Keeping the config whole on the domain side would leave authorization shipping no config subpackage at all, against the convention every other package here follows — so a consumer wanting only the static resolver would import the module that owns the roles table to configure the one that does not. That cost is paid forever, by every wiring site, and it grows with each primitives-only consumer.
Conceding that an interface with a table-owning implementation is a domain noun would keep authorization, webauthn and oauth2server here whole. It is coherent, and it is a bigger claim than this ticket: it moves four packages back across a line drawn deliberately, on the strength of one field.
What would overturn the split is a second primitive implementation arriving here — a resolver reading policy from a file watcher, say. Then this package would have a genuine choice to make and would want its own provider string back, and the one in authzdbcfg would be selecting between this package's answer and its own.
The seam the two halves meet at ¶
authzdbcfg calls NewPolicyResolver for the branch it does not own, and NewCachedResolver for the one it does. The second is exported for that reason alone: it exists so that the CacheTTL read and the cached.NewResolver call have one home rather than two. A second copy on the database branch could drift — a different default, a dropped option, a TTL read off the wrong field — which is exactly the kind of duplication the module's CLAUDE.md rules out, as against duplication that can only be different.
Index ¶
- func NewCachedResolver(cfg *Config, resolver authorization.PolicyResolver, ...) (authorization.PolicyResolver, error)
- func NewPolicyResolver(ctx context.Context, cfg *Config, c cache.Cache[authorization.PermissionSet], ...) (authorization.PolicyResolver, error)
- func RegisterPolicyResolver(i do.Injector)
- type Config
- type Option
- func WithCachedOptions(opts ...cached.Option) Option
- func WithLogger(logger logging.Logger) Option
- func WithMetricsProvider(metricsProvider metrics.Provider) Option
- func WithPillars(p *observability.Pillars) Option
- func WithStaticOptions(opts ...static.Option) Option
- func WithTracerProvider(tracerProvider tracing.Provider) Option
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewCachedResolver ¶
func NewCachedResolver( cfg *Config, resolver authorization.PolicyResolver, c cache.Cache[authorization.PermissionSet], opts ...Option, ) (authorization.PolicyResolver, error)
NewCachedResolver wraps resolver in authorization/cached when c is non-nil, and hands it back untouched when c is nil.
It is exported for authzdbcfg, which builds the SQL-backed resolver this package cannot and then needs the same wrapping applied to it. Keeping the CacheTTL read and the cached.NewResolver call in one place is the point: a second copy on the database branch could drift from this one, and nothing would say so.
func NewPolicyResolver ¶
func NewPolicyResolver( ctx context.Context, cfg *Config, c cache.Cache[authorization.PermissionSet], opts ...Option, ) (authorization.PolicyResolver, error)
NewPolicyResolver builds the static resolver from cfg, wrapped in authorization/cached when c is non-nil.
c is optional. When it is nil the resolver answers from its own declarations every time, which for the static resolver is a map lookup.
The result is an interface, so a caller that needs to drop cached policy after an edit type-asserts authorization.PolicyInvalidator rather than a concrete type — whether a cache is in the chain is this function's decision, not the caller's.
A deployment resolving policy from SQL calls authzdbcfg.NewPolicyResolver instead; it selects between that store and this resolver, and delegates here for the half it does not own.
func RegisterPolicyResolver ¶
RegisterPolicyResolver registers an authorization.PolicyResolver with the injector, resolving policy from cfg.Roles. The cache is optional: a registered cache.Cache[authorization.PermissionSet] wraps the resolver in the cached decorator, and its absence means every resolution hits the underlying resolver, which is NewPolicyResolver's documented uncached behavior.
This registration builds no store and resolves no database.Client, so a container running static policy needs neither. A container resolving policy from SQL registers authzdbcfg.RegisterPolicyResolver instead — the two provide the same key, so exactly one of them belongs in any given injector.
Prerequisites: context.Context and *Config must be registered in the injector before the resolver is invoked.
Types ¶
type Config ¶
type Config struct {
// Roles is the policy for the static resolver. It is loadable from JSON or
// YAML, so a static deployment can change policy by shipping config rather
// than code.
Roles []authorization.Role `json:"roles,omitempty" yaml:"roles,omitempty"`
// CacheTTL sets how long a resolution is cached when a cache is supplied to
// NewPolicyResolver. Zero uses the cached package's default.
CacheTTL time.Duration `env:"CACHE_TTL" json:"cacheTTL,omitempty" yaml:"cacheTTL,omitempty"`
}
Config configures a policy resolver.
The zero value is valid and yields a working static resolver that grants nothing.
type Option ¶
type Option func(*options)
Option configures how NewPolicyResolver assembles its resolver.
The backend options are passthroughs, each applying only when the backend it names is built, so one wiring site can carry options for whichever shape a given deployment turns out to run. They are appended after the options this package derives from its arguments, so a caller can override what it would otherwise be given.
func WithCachedOptions ¶
WithCachedOptions passes opts to the caching decorator, which is applied only when a cache is supplied.
func WithLogger ¶
WithLogger attaches a logger. An absent logger logs nowhere.
func WithMetricsProvider ¶
WithMetricsProvider attaches a metrics provider. An absent provider records nothing.
func WithPillars ¶
func WithPillars(p *observability.Pillars) Option
WithPillars attaches a logger, tracer provider, and metrics provider in one go, for the common case where a caller has already built them together. A nil Pillars attaches nothing.
It is applied in order with the individual options, so a caller can hand over its pillars and then override one of them.
func WithStaticOptions ¶
WithStaticOptions passes opts to the static resolver.
func WithTracerProvider ¶
WithTracerProvider attaches a tracer provider, enabling spans on policy resolution. An absent tracer provider traces nowhere.