use super::helpers::TrustTaskOutcome;
use serde_json::{Value, json};
use trust_tasks_rs::{RejectReason, TrustTask};
use vta_sdk::protocols::acl_management::create::CreateAclBody;
use vta_sdk::protocols::acl_management::delete::DeleteAclBody;
use vta_sdk::protocols::acl_management::get::GetAclBody;
use vta_sdk::protocols::acl_management::list::ListAclBody;
use vta_sdk::protocols::acl_management::swap::SwapKeyBody;
use vta_sdk::protocols::acl_management::update::UpdateAclBody;
use crate::acl::Role;
use crate::auth::AuthClaims;
use crate::error::AppError;
use crate::operations;
use crate::operations::step_up::{StepUpDecision, op, resolve_step_up};
use crate::server::AppState;
use super::helpers::{
TRANSPORT_TRUST_TASK, app_error_to_reject, parse_payload, reject_with, success_response,
};
pub(super) async fn handle_list(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_manage() {
return app_error_to_reject(&doc, e);
}
let req: ListAclBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::acl::list_acl(
&state.acl_ks,
auth,
req.context.as_deref(),
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_create(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_manage() {
return app_error_to_reject(&doc, e);
}
let req: CreateAclBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let role = match Role::parse(&req.role) {
Ok(r) => r,
Err(_) => {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!("invalid role: {}", req.role),
},
);
}
};
match operations::acl::create_acl(
&state.acl_ks,
&state.audit_ks,
&state.contexts_ks,
auth,
&req.did,
role,
req.label,
req.allowed_contexts,
req.expires_at,
req.step_up_approver,
req.step_up_require,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_get(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_manage() {
return app_error_to_reject(&doc, e);
}
let req: GetAclBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::acl::get_acl(&state.acl_ks, auth, &req.did, TRANSPORT_TRUST_TASK).await {
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_update(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_admin() {
return app_error_to_reject(&doc, e);
}
let req: UpdateAclBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let role = match req.role.as_deref() {
Some(r) => match Role::parse(r) {
Ok(parsed) => Some(parsed),
Err(_) => {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!("invalid role: {r}"),
},
);
}
},
None => None,
};
match operations::acl::update_acl(
&state.acl_ks,
&state.audit_ks,
&state.contexts_ks,
auth,
&req.did,
operations::acl::UpdateAclParams {
role,
label: req.label,
allowed_contexts: req.allowed_contexts,
step_up_approver: req.step_up_approver,
step_up_require: req.step_up_require,
},
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_delete(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_manage() {
return app_error_to_reject(&doc, e);
}
let req: DeleteAclBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::acl::delete_acl(
&state.acl_ks,
&state.audit_ks,
auth,
&req.did,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_swap_key(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let req: SwapKeyBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
if req.current_subject != auth.did {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!(
"acl/swap-key: currentSubject {} does not equal authenticated caller {}",
req.current_subject, auth.did
),
},
);
}
if !matches!(
resolve_step_up(
&state.config,
&state.acl_ks,
op::ACL_SWAP_KEY,
&auth.did,
true, )
.await,
StepUpDecision::Allow
) {
return reject_with(
&doc,
RejectReason::TaskFailed {
reason: "auth:step_up_required".to_string(),
details: Some(json!({
"requiredAcr": "aal2",
"reason": "acl/swap-key requires a stepped-up (AAL2) session under this \
VTA's step-up policy. Sender-authenticated transports \
(DIDComm/TSP) are AAL1 and cannot be elevated in-band — perform \
this self-service rotation over the authenticated REST session.",
})),
},
);
}
let did_resolver = match state.did_resolver.as_ref() {
Some(r) => r,
None => {
return app_error_to_reject(
&doc,
AppError::Internal("DID resolver not available".into()),
);
}
};
let vta_did = match state.config.read().await.vta_did.clone() {
Some(v) => v,
None => {
return app_error_to_reject(&doc, AppError::Internal("VTA DID not configured".into()));
}
};
match operations::acl::swap_acl(
&state.acl_ks,
&state.audit_ks,
auth,
&req.link_proof,
did_resolver,
&vta_did,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(result) => {
if req.new_subject != result.did {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!(
"acl/swap-key: newSubject {} does not match verified VP holder {}",
req.new_subject, result.did
),
},
);
}
success_response(&doc, result)
}
Err(e) => app_error_to_reject(&doc, e),
}
}