|
morph 0.1.0
Asynchronous bridge between GUI thread and business models
|
Authorizes incoming actions on a RemoteServer. More...
#include <session.hpp>
Classes | |
| struct | Admission |
| What admit decides about one call: whether it may run, and under which verified principal. More... | |
Public Member Functions | |
| virtual bool | authorize (const Context &ctx, std::string_view modelType, std::string_view actionType) const =0 |
Returns true if ctx is allowed to invoke actionType on modelType. | |
| virtual std::optional< std::string > | authenticate (const Context &ctx) const |
Returns the authenticated principal for ctx, or nullopt. | |
| virtual Admission | admit (const Context &ctx, std::string_view modelType, std::string_view actionType) const |
| Authenticates and authorizes one call together. | |
| virtual bool | authorizeInstance (const Context &ctx, std::string_view modelType, std::string_view actionType, std::uint64_t modelId, std::string_view ownerPrincipal) const |
| Optional per-**instance** authorization hook — the multi-tenant gate. | |
| virtual bool | authorizeRegister (const Context &ctx, std::string_view modelType) const |
| Optional gate on model creation — bounds who may create an instance. | |
Authorizes incoming actions on a RemoteServer.
Called before dispatch on four of the server's own envelope kinds, not only execute. A false return causes the server to reply with err|unauthorized (the client surfaces the error through the .onError(...) callback):
| Envelope | modelType | actionType |
|---|---|---|
| execute | env.modelType | env.actionType |
| instances | env.typeId | empty |
| schemas | env.typeId | empty |
| deregister | empty | empty |
A server configured with the UI service (ui::UiService) also calls it for that service's kinds, under the pseudo-model ui:
| Envelope | modelType | actionType |
|---|---|---|
| ui-hello, and ui-fetch of a public file | ui | hello |
| ui-catalog | ui | catalog |
| ui-fetch of any other file | ui | fetch |
and, to filter the catalog, with each screen's model types and action ids, as execute would. See docs/spec/ui/service.md, "Authorisation".
A deregister names no type, so both ids are empty; its type-level call is what lets a verifying authorizer refuse a release from a caller with no valid token before the per-instance authorizeInstance check runs.
The two read channels are gated deliberately, so a deployer can refuse enumeration or schema disclosure without refusing use – schemas returns field names, bounds, rules and the payload fingerprint of every action, so it must not be reachable by a caller the server would not let execute.
An implementation that switches or matches on modelType or actionType must handle the empty case explicitly, or it will hit its default arm on exactly those verbs. Whether that fails open or closed is the implementation's choice, but it has to be a choice.
Default implementation supplied by the framework is AllowAllAuthorizer. Real deployments install a custom subclass that checks principal claims, action permissions, rate limits, etc.
Definition at line 137 of file session.hpp.
|
nodiscardpure virtual |
Returns true if ctx is allowed to invoke actionType on modelType.
| ctx | Per-call session attached by the client. |
| modelType | String id of the target model type, or empty for the deregister envelope. |
| actionType | String id of the action being invoked, or empty for the instances, schemas and deregister envelopes – see the table on this interface's own doc comment. |
Implemented in morph::session::AllowAllAuthorizer, and morph::session::SigningAuthorizer.
|
inlinenodiscardvirtual |
Returns the authenticated principal for ctx, or nullopt.
Called by RemoteServer after authorize succeeds. If it returns a value, the server overwrites Context::principal with it before dispatch, making the identity authoritative for model code that reads session::current(). The default returns nullopt — for authorizers that do not authenticate (e.g. AllowAllAuthorizer), RemoteServer clears Context::principal to the empty string before dispatch rather than passing the client's unverified claim through, so model code never sees an unauthenticated principal presented as authoritative. A verifying authorizer (SigningAuthorizer) overrides this to supply the token-derived identity.
| ctx | Per-call session attached by the client. |
Reimplemented in morph::session::SigningAuthorizer.
Definition at line 166 of file session.hpp.
|
inlinenodiscardvirtual |
Authenticates and authorizes one call together.
RemoteServer calls this once per execute, in place of authenticate and authorize, so both decisions come from one look at the session. The default calls authenticate and then authorize, which keeps an authorizer that overrides only those two exactly as it was. A verifying authorizer overrides it to verify the token once and take both answers from that verification: a token that expires between two verifications cannot be judged valid by one and expired by the other.
| ctx | Per-call session attached by the client. |
| modelType | String id of the target model type. |
| actionType | String id of the action being invoked. |
Reimplemented in morph::session::SigningAuthorizer.
Definition at line 194 of file session.hpp.
|
inlinenodiscardvirtual |
Optional per-**instance** authorization hook — the multi-tenant gate.
authorize sees only the model type, so it cannot answer "may this caller touch *this instance* (row)?". Model instances on a RemoteServer are addressable by guessable sequential ids, so without an ownership check any authenticated caller can execute/deregister against an id it did not create — a cross-tenant targeting gap. This hook closes it: when installed, RemoteServer consults it on every execute and every deregister, passing the id of the target instance and the principal recorded as its owner at register time (empty if the instance was registered by an unauthenticated caller or predates ownership tracking).
The default allows everything, so an authorizer that does not override it — including AllowAllAuthorizer and a plain SigningAuthorizer — keeps the pre-existing behaviour exactly (no per-instance restriction). A deployer opts into ownership enforcement by overriding this, typically to compare ownerPrincipal against ctx.principal:
| ctx | Per-call session (its principal is authoritative only if a verifying authorizer is installed). |
| modelType | Target model type id (empty for deregister). |
| actionType | Target action type id (empty for deregister). |
| modelId | Numeric id of the target instance. |
| ownerPrincipal | Principal recorded as the instance's owner at register time; empty if none was recorded. |
Definition at line 233 of file session.hpp.
|
inlinenodiscardvirtual |
Optional gate on model creation — bounds who may create an instance.
authorize and authorizeInstance both act on an already-existing instance; neither can answer "may this caller create one at all?". RemoteServer consults this hook on every register envelope, after authenticating the caller (so ctx carries the verified principal, not the client's raw claim) and before constructing the instance. A false return replies err "unauthorized" and no instance is created — ModelRegistryFactory::create never runs.
The default allows everything, so an authorizer that does not override it — including AllowAllAuthorizer and a plain SigningAuthorizer — keeps the pre-existing behaviour exactly (any reachable client may register any known model type). A deployer opts into bounding registration by overriding this, typically requiring authentication and/or restricting modelType:
| ctx | Per-call session for the register envelope; its principal is already the verified identity when the authorizer authenticates (empty otherwise). |
| modelType | String id of the model type being registered. |
Definition at line 269 of file session.hpp.