fix: bound Subject database retention to synchronous plugin checks

This commit is contained in:
2026-10-31 04:58:19 +09:00
parent b508fef698
commit 2c1f5dfafe
3 changed files with 65 additions and 44 deletions
@@ -955,7 +955,9 @@ impl WorkspaceApi {
"foreign Plugin Worker".into(), "foreign Plugin Worker".into(),
)); ));
} }
plugin_execution_identity(&api, &worker).map_err(|error| error.error) super::plugin_host::with_authority_database(&api, || {
plugin_execution_identity(&api, &worker).map_err(|error| error.error)
})
}) })
.await .await
} }
@@ -134,6 +134,20 @@ fn operation_name(op: &PluginHostOperation) -> &'static str {
} }
} }
/// Retain the physical Subject database only for this synchronous check. Each
/// policy/lineage query still reads its owner; no authorization result or lock
/// survives the closure, and FeatureStorage continues to enforce lifecycle fences.
pub(super) fn with_authority_database<T>(
api: &WorkspaceApi,
check: impl FnOnce() -> crate::Result<T>,
) -> crate::Result<T> {
let _database = api
.feature_storage
.open(&api.subjektiv_registration)
.map_err(|error| Error::Store(error.to_string()))?;
check()
}
async fn check( async fn check(
api: Arc<WorkspaceApi>, api: Arc<WorkspaceApi>,
context: ServerRequestContext, context: ServerRequestContext,
@@ -220,28 +234,31 @@ async fn check_tools(
let executor = api.config_store.db_executor(); let executor = api.config_store.db_executor();
executor executor
.run(move || { .run(move || {
subject_authorization::authorize_scope( with_authority_database(&api, || {
&api, subject_authorization::authorize_scope(
&worker, &api,
&server_api::authorization::OperationAuthorizationRequest { &worker,
operation: operation_name(&operation).into(), &server_api::authorization::OperationAuthorizationRequest {
resource: server_api::authorization::PermissionResource::PluginInstallation { operation: operation_name(&operation).into(),
workspace_id: workspace.clone(), resource:
installation_id: installation.clone(), server_api::authorization::PermissionResource::PluginInstallation {
workspace_id: workspace.clone(),
installation_id: installation.clone(),
},
}, },
},
)
.map_err(|e| e.error)?;
api.config_store.with_immediate_transaction(|tx| {
policy::authorize(
tx,
&workspace,
&execution,
&plan_id,
&installation,
&tools,
&operation,
) )
.map_err(|e| e.error)?;
api.config_store.with_immediate_transaction(|tx| {
policy::authorize(
tx,
&workspace,
&execution,
&plan_id,
&installation,
&tools,
&operation,
)
})
}) })
}) })
.await .await
@@ -31,30 +31,32 @@ async fn check_service(
let executor = api.config_store.db_executor(); let executor = api.config_store.db_executor();
executor executor
.run(move || { .run(move || {
for op in ["plugin.service.consume", operation_name(&operation)] { with_authority_database(&api, || {
subject_authorization::authorize_scope( for op in ["plugin.service.consume", operation_name(&operation)] {
&api, subject_authorization::authorize_scope(
&worker, &api,
&OperationAuthorizationRequest { &worker,
operation: op.into(), &OperationAuthorizationRequest {
resource: PermissionResource::PluginInstallation { operation: op.into(),
workspace_id: workspace.clone(), resource: PermissionResource::PluginInstallation {
installation_id: installation.clone(), workspace_id: workspace.clone(),
installation_id: installation.clone(),
},
}, },
}, )
) .map_err(|e| e.error)?;
.map_err(|e| e.error)?; }
} api.config_store.with_immediate_transaction(|tx| {
api.config_store.with_immediate_transaction(|tx| { policy::authorize_service(
policy::authorize_service( tx,
tx, &workspace,
&workspace, &execution,
&execution, &plan_id,
&plan_id, &installation,
&installation, &name,
&name, &operation,
&operation, )
) })
}) })
}) })
.await .await