Documentation
¶
Overview ¶
Package authz provides a normative authorization contract and core types used by OpenCHAMI services integrating TokenSmith.
Package authz provides net/http-compatible middleware implementing the TokenSmith authorization wire contract.
Index ¶
- Constants
- func ContextWithHTTPRequest(ctx context.Context, r *http.Request) context.Context
- func ContextWithRequestID(ctx context.Context, requestID string) context.Context
- func DefaultRequestIDFromContext(ctx context.Context) string
- func NewBadRequestError(msg string) error
- func NormalizeEscapedPath(u *url.URL) (string, error)
- func RequestIDFromContext(ctx context.Context) (string, bool)
- func RequestIDFromHeader(r *http.Request, header string) string
- func SetPrincipal(ctx context.Context, p *Principal) context.Context
- type Authorizer
- type AuthorizerOption
- type AuthzResult
- type BadRequestError
- type Decision
- type DecisionRecord
- type DenyCode
- type DenyResponseV1
- type DenyWriter
- type ErrorCode
- type ErrorResponse
- type Input
- type MethodToAction
- type Middleware
- type MiddlewareOption
- func WithAllowUnmapped(allow bool) MiddlewareOption
- func WithIncludeRolesInDecisionRecord(include bool) MiddlewareOption
- func WithMode(mode Mode) MiddlewareOption
- func WithOnDecision(h OnDecisionHook) MiddlewareOption
- func WithPublicPrefixes(pfx []string) MiddlewareOption
- func WithPublicRegexps(rs ...func(string) bool) MiddlewareOption
- func WithRequestIDFromContext(f func(context.Context) string) MiddlewareOption
- func WithRequireAuthn(req bool) MiddlewareOption
- type Mode
- type OnDecisionHook
- type PathMethodMapper
- type Principal
- type PrincipalSummary
- type Reason
- type RequestSummary
- type RouteDecision
- type RouteMapper
Constants ¶
const ( DenyCodeAuthNRequired DenyCode = "AUTHN_REQUIRED" DenyCodeAuthNInvalid DenyCode = "AUTHN_INVALID" DenyCodeAuthzDenied DenyCode = "AUTHZ_DENIED" DenyCodeAuthzUnmapped DenyCode = "AUTHZ_UNMAPPED" DenyCodeAuthzEngineErr DenyCode = "AUTHZ_ENGINE_ERROR" DenyCodeBadRequest DenyCode = "BAD_REQUEST" DenySchemaVersionV1 string = "authz.deny.v1" )
const ( // EnvAuthzCacheSize configures the maximum number of cached authorization // decisions. Cache is disabled by default. EnvAuthzCacheSize = "TOKENSMITH_AUTHZ_CACHE_SIZE" )
Variables ¶
This section is empty.
Functions ¶
func ContextWithHTTPRequest ¶
ContextWithHTTPRequest stores an *http.Request in context for downstream helpers (e.g., observability hooks).
This is intended for internal TokenSmith middleware wiring; services generally should not rely on this.
func ContextWithRequestID ¶
ContextWithRequestID stores a request id string in ctx.
func DefaultRequestIDFromContext ¶
DefaultRequestIDFromContext returns a request id using a best-effort chain:
- ctx value set via ContextWithRequestID
- (optional) X-Request-Id request header, if r is available to the caller
Note: because authz.Middleware currently only supports a ctx extractor, header-based extraction is typically implemented in the service and stored in context using ContextWithRequestID.
func NewBadRequestError ¶
NewBadRequestError returns an error that will be treated as bad_request.
func NormalizeEscapedPath ¶
NormalizeEscapedPath implements the TokenSmith path normalization rules for path/method style authorization.
Spec: docs/authz-spec.md §3.
Behavior summary:
- Uses u.EscapedPath() if non-empty, else returns "/".
- Rejects malformed %-escapes with BadRequestError.
- Preserves encoded slashes (%2F) by only unescaping *non-slash* sequences. (i.e., %2F remains %2F, preventing path segment ambiguity).
- Cleans dot segments via path.Clean while keeping a leading slash.
The returned string is safe to feed to Casbin keyMatch/keyMatch2 matchers.
func RequestIDFromContext ¶
RequestIDFromContext returns a request id string from ctx, if present.
func RequestIDFromHeader ¶
RequestIDFromHeader returns the request id from r using the provided header name (default: X-Request-Id).
Types ¶
type Authorizer ¶
type Authorizer struct {
// contains filtered or unexported fields
}
Authorizer evaluates authorization decisions using Casbin and provides an optional bounded LRU cache.
Policy is loaded at startup. Hot reload is not supported in v1.
func NewAuthorizer ¶
func NewAuthorizer(enforcer *casbin.Enforcer, policyVersion string, opts ...AuthorizerOption) (*Authorizer, error)
NewAuthorizer constructs an Authorizer.
policyVersion MUST be the deterministic policy hash for the effective policy set used to create enforcer.
func (*Authorizer) Authorize ¶
func (a *Authorizer) Authorize(ctx context.Context, principal Principal, object, action string) (Decision, *AuthzResult)
func (*Authorizer) PolicyVersion ¶
func (a *Authorizer) PolicyVersion() string
Authorize evaluates whether principal may perform action on object.
Services should typically call this from HTTP middleware. PolicyVersion returns the policy version hash (deterministic identity) for the effective policy loaded into this authorizer.
type AuthorizerOption ¶
type AuthorizerOption func(*Authorizer)
AuthorizerOption configures an Authorizer.
func WithDecisionCache ¶
func WithDecisionCache(size int) AuthorizerOption
WithDecisionCache enables a bounded LRU cache for authorization decisions. size <= 0 disables the cache.
func WithDecisionCacheFromEnv ¶
func WithDecisionCacheFromEnv() AuthorizerOption
WithDecisionCacheFromEnv enables cache when TOKENSMITH_AUTHZ_CACHE_SIZE is a positive integer.
type AuthzResult ¶
type AuthzResult struct {
PolicyVersion string `json:"policy_version"`
MatchedRoles []string `json:"matched_roles,omitempty"`
Reason string `json:"reason"`
Cached bool `json:"cached"`
}
AuthzResult provides additional details about an authorization decision.
Services may log fields from this struct for troubleshooting.
Note: Policy loading is performed at startup and is not hot-reloaded in v1. A restart is required to pick up policy changes.
type BadRequestError ¶
BadRequestError marks an error as being caused by a malformed request. AuthZ middleware will translate this to reason=bad_request and HTTP 400.
Use cases:
- malformed URL escapes during path normalization
- invalid/missing required header used for domain routing
The concrete error message SHOULD be stable and SHOULD NOT include sensitive values.
type Decision ¶
type Decision string
Decision is the outcome taxonomy returned by the core evaluator.
See docs/authz_contract.md.
type DecisionRecord ¶
type DecisionRecord struct {
PrincipalID string `json:"principal_id"`
PrincipalType string `json:"principal_type,omitempty"`
Roles []string `json:"roles,omitempty"`
RolesCount int `json:"roles_count"`
Object string `json:"object"`
Action string `json:"action"`
Domain string `json:"domain,omitempty"`
Decision Decision `json:"decision"`
Reason Reason `json:"reason"`
Mode Mode `json:"mode"`
PolicyVersion string `json:"policy_version"`
Method string `json:"method"`
Path string `json:"path"`
RequestID string `json:"request_id,omitempty"`
}
DecisionRecord is a safe-to-log summary of an AuthZ decision.
Contract:
- MUST NOT include raw JWT strings.
- MUST NOT include arbitrary claim values.
- SHOULD only include roles if they are considered non-sensitive in your deployment.
DecisionRecord is intended to be emitted once per request in SHADOW and ENFORCE modes when the request is evaluated (i.e., not in OFF mode and not bypassed as public).
It is provided to the OnDecision hook.
type DenyCode ¶
type DenyCode string
DenyCode is a stable, machine-readable denial code.
See docs/authz-spec.md.
type DenyResponseV1 ¶
type DenyResponseV1 struct {
SchemaVersion string `json:"schema_version"`
Code DenyCode `json:"code"`
Message string `json:"message"`
Decision Decision `json:"decision"`
Reason Reason `json:"reason"`
Mode string `json:"mode"`
Principal PrincipalSummary `json:"principal"`
Input Input `json:"input"`
PolicyVersion string `json:"policy_version"`
Request RequestSummary `json:"request"`
RequestID string `json:"request_id,omitempty"`
Details map[string]any `json:"details,omitempty"`
}
DenyResponseV1 is the frozen deny response schema (authz.deny.v1).
This struct is used by both AuthN and AuthZ layers.
type DenyWriter ¶
type DenyWriter struct{}
DenyWriter writes a DenyResponseV1 as JSON.
It is safe to use for both AuthN and AuthZ denial responses.
Contract: - Sets Content-Type to application/json; charset=utf-8 - For HEAD requests, writes headers/status but suppresses the body. - Must not log sensitive values (DenyWriter does not log). - Ensures policy_version and mode are present even if empty.
func (DenyWriter) Write ¶
func (DenyWriter) Write(w http.ResponseWriter, r *http.Request, status int, resp DenyResponseV1) error
type ErrorCode ¶
type ErrorCode string
ErrorCode is a stable, machine-readable code used in AuthZ error responses.
See docs/authz_contract.md.
type ErrorResponse ¶
type ErrorResponse struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id,omitempty"`
PolicyVersion string `json:"policy_version"`
Decision Decision `json:"decision"`
}
ErrorResponse is the standard JSON error schema returned by TokenSmith AuthZ middleware when a request is denied in enforce mode.
See docs/authz_contract.md.
type Input ¶
type Input struct {
Object string `json:"object"`
Action string `json:"action"`
Domain string `json:"domain,omitempty"`
}
Input is the normalized authorization tuple passed to the evaluator and returned in deny responses.
Domain is always present in the type shape to keep the contract stable; it may be empty when domains are unused.
type MethodToAction ¶
MethodToAction maps an HTTP method to a Casbin action string.
func MethodToActionLiteral ¶
func MethodToActionLiteral() MethodToAction
MethodToActionLiteral returns action = method (as received).
func MethodToActionREST ¶
func MethodToActionREST() MethodToAction
MethodToActionREST returns a REST-ish action mapping:
- GET/HEAD -> read
- POST/PUT/PATCH -> write
- DELETE -> delete
- other -> method literal
type Middleware ¶
type Middleware struct {
Authorizer *Authorizer
Mapper RouteMapper
Mode Mode
RequireAuthn bool
AllowUnmapped bool
PublicPrefixes []string
PublicRegexps []func(string) bool
RequestIDFromContext func(context.Context) string
// Observability hook (optional). Called once per evaluated request in SHADOW
// and ENFORCE.
OnDecision OnDecisionHook
// IncludeRolesInDecisionRecord controls whether DecisionRecord contains the
// role names. If false, only RolesCount is set.
IncludeRolesInDecisionRecord bool
DenyWriter DenyWriter
}
Middleware evaluates authorization decisions for incoming requests.
It is net/http compatible and can be used with any router.
Public bypass:
This middleware does not implement router-specific per-route bypass. Use PublicPrefixes/PublicRegexps (or a router-specific helper) to skip authz.
When the request is public, middleware will call next without evaluation even in enforce mode.
Mapping:
Services must supply a RouteMapper to convert requests into Casbin input. See PathMethodMapper for a Casbin-native path/method style.
RequireAuthn:
When enabled, the absence of a principal yields 401 even in shadow/enforce. AuthN middleware should run before this middleware.
See docs/authz-spec.md for decision semantics.
func NewMiddleware ¶
func NewMiddleware(authorizer *Authorizer, mapper RouteMapper, opts ...MiddlewareOption) *Middleware
NewMiddleware constructs authz middleware.
type MiddlewareOption ¶
type MiddlewareOption func(*Middleware)
MiddlewareOption configures authz Middleware.
func WithAllowUnmapped ¶
func WithAllowUnmapped(allow bool) MiddlewareOption
func WithIncludeRolesInDecisionRecord ¶
func WithIncludeRolesInDecisionRecord(include bool) MiddlewareOption
WithIncludeRolesInDecisionRecord controls whether DecisionRecord includes role names in addition to RolesCount.
func WithMode ¶
func WithMode(mode Mode) MiddlewareOption
func WithOnDecision ¶
func WithOnDecision(h OnDecisionHook) MiddlewareOption
WithOnDecision installs an optional observability hook invoked once per evaluated request in SHADOW and ENFORCE (not in OFF and not for public bypass).
func WithPublicPrefixes ¶
func WithPublicPrefixes(pfx []string) MiddlewareOption
func WithPublicRegexps ¶
func WithPublicRegexps(rs ...func(string) bool) MiddlewareOption
func WithRequestIDFromContext ¶
func WithRequestIDFromContext(f func(context.Context) string) MiddlewareOption
func WithRequireAuthn ¶
func WithRequireAuthn(req bool) MiddlewareOption
type Mode ¶
type Mode string
Mode controls how authorization outcomes impact request handling.
See docs/authz_contract.md.
type OnDecisionHook ¶
type OnDecisionHook func(ctx context.Context, rec DecisionRecord)
OnDecisionHook is invoked with a DecisionRecord for observability.
Implementations MUST be fast and MUST NOT block request handling. Callers should treat ctx as request-scoped and not retain it.
type PathMethodMapper ¶
type PathMethodMapper struct {
MethodToAction MethodToAction
DomainFunc func(r *http.Request, p Principal) (string, error)
}
PathMethodMapper is a RouteMapper implementation that feeds Casbin with:
- object = normalized URL path
- action = normalized method (literal or REST-ish)
- domain = optional extractor
Public bypass is NOT handled here; middleware remains the single owner.
func (PathMethodMapper) Map ¶
func (m PathMethodMapper) Map(r *http.Request, p Principal) (RouteDecision, error)
type Principal ¶
type Principal struct {
// ID is the stable identifier for the principal (user id, client id, etc.).
ID string
// Roles is the set of RBAC roles assigned to the principal.
Roles []string
}
Principal is the normalized caller identity used for authorization.
Services are responsible for mapping authenticated identity (e.g. JWT/OIDC claims) into this structure.
Roles are expected WITHOUT the "role:" prefix (e.g. "admin", "viewer"). The Authorizer will apply the required Casbin subject prefix.
type PrincipalSummary ¶
type PrincipalSummary struct {
ID string `json:"id"`
Type string `json:"type"`
Roles []string `json:"roles,omitempty"`
}
PrincipalSummary is safe to log and safe to return to clients.
Redaction rules (contract):
- MUST NOT include raw JWTs.
- MUST NOT include arbitrary claims.
- SHOULD only include role/group identifiers if they are considered non-sensitive in your deployment.
Note: PrincipalSummary is intentionally decoupled from JWT claim structs. Services can populate it from any identity system.
type Reason ¶
type Reason string
Reason is the coarse reason category for a denial.
See docs/authz-spec.md.
type RequestSummary ¶
type RouteDecision ¶
RouteDecision is the service-owned mapping output that TokenSmith uses as Casbin input.
Ownership contract:
- TokenSmith middleware owns *public bypass* decisions.
- RouteMapper MUST NOT attempt to enforce bypass; it only maps.
- RouteMapper MUST be pure/fast and MUST NOT perform I/O.
Public is included for historical compatibility with early experiments, but is ignored by TokenSmith middleware.
Mapped indicates whether TokenSmith should treat the request as having a known mapping to (Object, Action[, Domain]). In enforce mode, unmapped requests are denied-by-default unless explicitly configured otherwise.
Object and Action are service-defined identifiers (or normalized path/method in path/method mode). They MUST NOT be derived from user-provided params.
Domain is optional and may be empty if domains are unused.
See docs/authz-spec.md for wire semantics.
type RouteMapper ¶
type RouteMapper interface {
Map(r *http.Request, p Principal) (RouteDecision, error)
}
RouteMapper maps an HTTP request + verified principal to a RouteDecision.
Contract:
- Map MUST be deterministic and fast.
- Map MUST NOT perform I/O.
- Map MUST NOT mutate the request.
- Map MUST NOT decide public bypass; middleware is the single owner of that behavior.
Error contract:
- If Map returns a non-nil error, middleware MUST treat the request as a deterministic denial with reason either:
- bad_request (HTTP 400) for errors that implement BadRequestError
- engine_error (HTTP 500) for all other errors
Mapper implementations SHOULD use BadRequestError for malformed inputs that can be attributed to the request itself (e.g., invalid header used for domain selection).
NOTE: callers should prefer returning (RouteDecision{Mapped:false}, nil) for unknown routes rather than an error.
See also: BadRequestError.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package chi provides chi-specific authorization middleware and route helpers implementing the TokenSmith AuthZ contract.
|
Package chi provides chi-specific authorization middleware and route helpers implementing the TokenSmith AuthZ contract. |
|
Package engine constructs a Casbin-backed Authorizer.
|
Package engine constructs a Casbin-backed Authorizer. |
|
Package policyloader loads Casbin model and policy artifacts (policy + grouping fragments) deterministically.
|
Package policyloader loads Casbin model and policy artifacts (policy + grouping fragments) deterministically. |
|
Package presets provides convenience Casbin model presets.
|
Package presets provides convenience Casbin model presets. |