feat(http): connection-local Layer 2 overlay for browser-registered ops (ADR-024/034/044)
Enforce AccessControl on overlay ops in OverlayOperationEnv::invoke_with_policy
(alknet-call) so the hub's calls to browser-registered ops are gated by the
browser's AccessControl — matching OperationRegistry::invoke semantics for
internal composition (caller identity = parent handler_identity.as_identity()).
Add src/websocket/overlay.rs with 19 integration tests covering the connection-
local overlay acceptance criteria: browser ops land in the per-CallConnection
overlay (not PeerCompositeEnv), no PeerId for the browser, register_imported()/
register_imported_all() populate the overlay, hub outgoing calls route through
overlay_env() (not PeerRef::Specific), PeerRef::Specific('browser-X') routes to
NOT_FOUND, AccessControl gates hub calls (allowed/forbidden/default), overlay is
per-connection isolated and dropped on WS close, WS close aborts in-flight calls
with ADR-016 cascade, bidirectionality, and browser-with-no-ops use-case scoping.
This commit is contained in:
@@ -27,6 +27,7 @@ use crate::protocol::wire::ResponseEnvelope;
|
||||
use crate::registry::context::{generate_request_id, AbortPolicy, OperationContext, ScopedPeerEnv};
|
||||
use crate::registry::env::OperationEnv;
|
||||
use crate::registry::registration::{Handler, HandlerRegistration};
|
||||
use crate::registry::spec::AccessResult;
|
||||
|
||||
const DEFAULT_CALL_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
@@ -309,6 +310,7 @@ impl OperationEnv for OverlayOperationEnv {
|
||||
let handler: Handler;
|
||||
let composition_authority;
|
||||
let scoped_env;
|
||||
let access_control;
|
||||
{
|
||||
let overlay = self.overlay.read();
|
||||
let Some(registration) = overlay.get(&name) else {
|
||||
@@ -320,6 +322,19 @@ impl OperationEnv for OverlayOperationEnv {
|
||||
.scoped_env
|
||||
.clone()
|
||||
.unwrap_or_else(ScopedPeerEnv::empty);
|
||||
access_control = registration.spec.access_control.clone();
|
||||
}
|
||||
|
||||
let caller_identity = if parent.internal {
|
||||
parent
|
||||
.handler_identity
|
||||
.as_ref()
|
||||
.and_then(|ca| ca.as_identity())
|
||||
} else {
|
||||
parent.identity.clone()
|
||||
};
|
||||
if let AccessResult::Forbidden(message) = access_control.check(caller_identity.as_ref()) {
|
||||
return ResponseEnvelope::forbidden(parent.request_id.clone(), message);
|
||||
}
|
||||
|
||||
let context = OperationContext {
|
||||
|
||||
Reference in New Issue
Block a user