#![cfg(feature = "webvh")]
use super::helpers::TrustTaskOutcome;
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use vta_sdk::provision_integration::http::{
AssertionMode, ProvisionIntegrationRequest, ProvisionIntegrationResponse,
};
use crate::auth::AuthClaims;
use crate::operations::provision_integration::{
AmbiguousContext, ProvisionIntegrationDeps, ProvisionIntegrationParams,
ensure_target_context_or_create, infer_target_context,
provision_integration as provision_integration_op,
};
use crate::server::AppState;
use super::helpers::{
app_error_to_reject, parse_payload, reject_with, reject_with_code, success_response,
};
pub(super) async fn handle_request(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let req: ProvisionIntegrationRequest = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let request_raw = match doc.payload.get("request") {
Some(v) => v.clone(),
None => {
return reject_with(
&doc,
trust_tasks_rs::RejectReason::MalformedRequest {
reason: "provision-integration request missing 'request' field".into(),
},
);
}
};
let verified = match vta_sdk::provision_integration::BootstrapRequest::verify_value(request_raw)
{
Ok(v) => v,
Err(e) => {
return reject_with(
&doc,
trust_tasks_rs::RejectReason::MalformedRequest {
reason: format!("verify BootstrapRequest: {e}"),
},
);
}
};
let assertion_mode = req.assertion.unwrap_or_default();
let vc_validity = req.vc_validity_seconds.map(chrono::Duration::seconds);
let deps = ProvisionIntegrationDeps::from(state);
let context = match req.context {
Some(c) => c,
None => match infer_target_context(auth, &deps.contexts_ks).await {
Ok(Ok(c)) => c,
Ok(Err(AmbiguousContext {
candidates,
message,
})) => {
return context_required(&doc, &message, &candidates);
}
Err(e) => return app_error_to_reject(&doc, e),
},
};
let context_created = match ensure_target_context_or_create(
&deps.contexts_ks,
auth,
&context,
req.create_context,
)
.await
{
Ok(c) => c,
Err(e) => return app_error_to_reject(&doc, e),
};
let output = match provision_integration_op(
&deps,
auth,
ProvisionIntegrationParams {
request: verified,
context,
admin_scope: req.admin_scope,
assertion_mode: AssertionModeOpAdapter(assertion_mode).into(),
vc_validity,
},
)
.await
{
Ok(o) => o,
Err(e) => return app_error_to_reject(&doc, e),
};
let body = ProvisionIntegrationResponse {
bundle: output.armored,
digest_multibase: Some(output.digest_multibase),
summary: vta_sdk::provision_integration::http::ProvisionSummary {
client_did: output.summary.client_did,
admin_did: output.summary.admin_did,
admin_rolled_over: output.summary.admin_rolled_over,
integration_did: output.summary.integration_did,
template_name: output.summary.template_name,
template_kind: output.summary.template_kind,
admin_template_name: output.summary.admin_template_name,
bundle_id_hex: output.summary.bundle_id_hex,
secret_count: output.summary.secret_count,
output_count: output.summary.output_count,
webvh_server_id: output.summary.webvh_server_id,
context_created,
context: Some(output.summary.context),
admin_scope: Some(output.summary.admin_scope),
},
};
success_response(&doc, body)
}
struct AssertionModeOpAdapter(AssertionMode);
impl From<AssertionModeOpAdapter> for crate::operations::provision_integration::AssertionMode {
fn from(a: AssertionModeOpAdapter) -> Self {
match a.0 {
AssertionMode::DidSigned => {
crate::operations::provision_integration::AssertionMode::DidSigned
}
AssertionMode::PinnedOnly => {
crate::operations::provision_integration::AssertionMode::PinnedOnly
}
}
}
}
fn context_required(
doc: &TrustTask<Value>,
message: &str,
candidates: &[String],
) -> TrustTaskOutcome {
let details = serde_json::json!({ "candidates": candidates });
match vta_sdk::protocols::problem_report_codes::PROVISION_CONTEXT_REQUIRED
.parse::<trust_tasks_rs::TrustTaskCode>()
{
Ok(code) => reject_with_code(doc, code, message, Some(details)),
Err(e) => {
tracing::error!(
error = %e,
code = vta_sdk::protocols::problem_report_codes::PROVISION_CONTEXT_REQUIRED,
"provision-integration contextRequired code is not a legal extended code; \
falling back to taskFailed"
);
reject_with(
doc,
trust_tasks_rs::RejectReason::TaskFailed {
reason: message.to_string(),
details: Some(details),
},
)
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn the_context_required_constant_is_a_legal_extended_code() {
let raw = vta_sdk::protocols::problem_report_codes::PROVISION_CONTEXT_REQUIRED;
let code: trust_tasks_rs::TrustTaskCode = raw
.parse()
.expect("PROVISION_CONTEXT_REQUIRED must parse as an extended code");
assert_eq!(
code.to_string(),
raw,
"the parsed code must round-trip to the constant the DIDComm \
problem-report also sends, or the two transports refuse in two \
different spellings"
);
}
}