pub enum AuthPolicy {
Public,
Require(ScopeSet),
}Expand description
What a deployment has decided about this endpoint’s exposure.
Separate from Anonymous because “we have not configured anything yet”
and “this endpoint is deliberately public” are different states, and
conflating them is how a default becomes a decision nobody made. A
deployment that means “public” writes AuthPolicy::Public; a deployment
that means “authenticated” writes AuthPolicy::Require and names the
scopes.
Variants§
Public
Every request is served under an anonymous context.
Correct for a loopback stdio-equivalent and for a conformance runner,
and wrong for anything reachable from another host. It is the default
because a Router that cannot be constructed until authentication is
wired is a router nobody constructs while T16 is unimplemented.
Require(ScopeSet)
Every request must hold these scopes.
Note what an empty ScopeSet does here: it requires nothing, so
refuse answers None and the policy behaves exactly
like Public. That is a footgun — “I require
authentication” followed by nothing to require is a configuration
mistake — and it is the kind that is best made visible by a test rather
than by a runtime check, because there is no error to return from a
const builder on a value that is otherwise perfectly well-formed.
an_empty_requirement_is_public_and_says_so pins the behaviour so it
cannot change silently: if a future dispatch wants Require to reject
an empty set, that test is the thing that has to be updated on purpose.
The same applies to
ServerConfig::with_required_scopes:
it stores what it is given and does not validate it.
Implementations§
Source§impl AuthPolicy
impl AuthPolicy
Sourcepub const fn refuse(&self, context: &AuthContext) -> Option<Refusal>
pub const fn refuse(&self, context: &AuthContext) -> Option<Refusal>
The refusal this policy produces for context, if any.
The 401-before-403 order is the point. A caller that holds nothing
is told to authenticate; a caller that holds a principal but lacks a
scope is told its credential is insufficient. Answering 403 to a
caller holding nothing tells the client to retry with more permission,
which it cannot do — it has nothing to add.
The test for “holds nothing” is AuthContext::scopes, not
AuthContext::principal. The domain’s AuthContext::anonymous is
a named principal — it is called anonymous rather than being the
empty string, deliberately, so a trace can distinguish a peer that
presented no credential from a named principal whose credential granted
nothing. So a check on the principal being empty is not merely
inelegant here, it is unreachable: it would classify the anonymous
caller as authenticated-but-insufficient and answer 403 to a client
that has no credential at all. The scopes are the thing the policy is
actually about, and a caller holding none of them is the caller that
needs to authenticate first.