fix(manager-core): F-Q-003 promote authz::script_gate helper, migrate 7 service call sites
Replace nine open-coded `if cx.principal.is_some() {
authz::require(...).await.map_err(...) }` blocks with a single
`authz::script_gate(repo, cx, cap, forbidden_fn, backend_fn)` helper.
The helper enshrines the script-as-gate semantics (anonymous public-
HTTP scripts skip the check) and the AuthzDenied::{Denied,Repo}
mapping in one place — eliminating drift between services.
Call sites migrated:
- kv_service::check_read / check_write
- docs_service::check_read / check_write
- files_service::check_read / check_write
- pubsub_service::check_publish
- queue_service::enqueue
The pubsub_service::mint_subscriber_token path keeps the explicit
match because it does a separate `principal` early-bind for other
validation; converting it would obscure intent.
AUDIT.md anchor: F-Q-003. Depends on F-Q-004 (Backend variant) and
F-Q-005 (Repo-passthrough pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,37 @@ pub enum AuthzDenied {
|
||||
Repo(#[from] AuthzError),
|
||||
}
|
||||
|
||||
/// Script-as-gate authz: anonymous public-HTTP scripts skip the check
|
||||
/// (`cx.principal` is `None`); authenticated callers must hold `cap`.
|
||||
///
|
||||
/// Replaces the open-coded
|
||||
/// `if let Some(p) = cx.principal { authz::require(...).await.map_err(...)? }`
|
||||
/// pattern across every stateful service. `forbidden` is called when
|
||||
/// the membership lookup returns `Denied`; `backend` is called when the
|
||||
/// underlying repo errors. Both closures map to the caller's service-
|
||||
/// specific error enum.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns the result of `forbidden(())` on `AuthzDenied::Denied`, or
|
||||
/// `backend(repo_err.to_string())` on `AuthzDenied::Repo(repo_err)`.
|
||||
pub async fn script_gate<E>(
|
||||
repo: &dyn AuthzRepo,
|
||||
cx: &picloud_shared::SdkCallCx,
|
||||
cap: Capability,
|
||||
forbidden: impl FnOnce() -> E,
|
||||
backend: impl FnOnce(String) -> E,
|
||||
) -> Result<(), E> {
|
||||
let Some(principal) = cx.principal.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
match require(repo, principal, cap).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(AuthzDenied::Denied) => Err(forbidden()),
|
||||
Err(AuthzDenied::Repo(e)) => Err(backend(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Layer 1: role-derived grant
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user