morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
morph::session::IAuthorizer Struct Referenceabstract

Authorizes incoming actions on a RemoteServer. More...

#include <session.hpp>

Inheritance diagram for morph::session::IAuthorizer:
morph::session::AllowAllAuthorizer morph::session::SigningAuthorizer

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.

Detailed Description

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.

Member Function Documentation

◆ authorize()

virtual bool morph::session::IAuthorizer::authorize ( const Context & ctx,
std::string_view modelType,
std::string_view actionType ) const
nodiscardpure virtual

Returns true if ctx is allowed to invoke actionType on modelType.

Parameters
ctxPer-call session attached by the client.
modelTypeString id of the target model type, or empty for the deregister envelope.
actionTypeString 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.
Returns
true to allow dispatch, false to reject with err|unauthorized.

Implemented in morph::session::AllowAllAuthorizer, and morph::session::SigningAuthorizer.

◆ authenticate()

virtual std::optional< std::string > morph::session::IAuthorizer::authenticate ( const Context & ctx) const
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.

Parameters
ctxPer-call session attached by the client.
Returns
The verified principal to make authoritative, or nullopt.

Reimplemented in morph::session::SigningAuthorizer.

Definition at line 166 of file session.hpp.

◆ admit()

virtual Admission morph::session::IAuthorizer::admit ( const Context & ctx,
std::string_view modelType,
std::string_view actionType ) const
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.

Parameters
ctxPer-call session attached by the client.
modelTypeString id of the target model type.
actionTypeString id of the action being invoked.
Returns
Whether the call may run, and the verified principal.

Reimplemented in morph::session::SigningAuthorizer.

Definition at line 194 of file session.hpp.

◆ authorizeInstance()

virtual bool morph::session::IAuthorizer::authorizeInstance ( const Context & ctx,
std::string_view modelType,
std::string_view actionType,
std::uint64_t modelId,
std::string_view ownerPrincipal ) const
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:

bool authorizeInstance(const Context& ctx, std::string_view, std::string_view,
uint64_t, std::string_view ownerPrincipal) const override {
return ownerPrincipal.empty() || ownerPrincipal == ctx.principal;
}
Per-call session context carried through the bridge to the model.
Definition session.hpp:26
std::string principal
Auth principal (user id).
Definition session.hpp:35
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.
Definition session.hpp:233
Parameters
ctxPer-call session (its principal is authoritative only if a verifying authorizer is installed).
modelTypeTarget model type id (empty for deregister).
actionTypeTarget action type id (empty for deregister).
modelIdNumeric id of the target instance.
ownerPrincipalPrincipal recorded as the instance's owner at register time; empty if none was recorded.
Returns
true to allow the operation on this instance, false to reject.

Definition at line 233 of file session.hpp.

◆ authorizeRegister()

virtual bool morph::session::IAuthorizer::authorizeRegister ( const Context & ctx,
std::string_view modelType ) const
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:

bool authorizeRegister(const Context& ctx, std::string_view modelType) const override {
return !ctx.principal.empty(); // only authenticated callers may register
}
virtual bool authorizeRegister(const Context &ctx, std::string_view modelType) const
Optional gate on model creation — bounds who may create an instance.
Definition session.hpp:269
Parameters
ctxPer-call session for the register envelope; its principal is already the verified identity when the authorizer authenticates (empty otherwise).
modelTypeString id of the model type being registered.
Returns
true to allow the registration, false to reject with err "unauthorized".

Definition at line 269 of file session.hpp.


The documentation for this struct was generated from the following file: