use crate::{
cdk::types::Principal,
dto::{error::Error, rpc::CreateCanisterParent},
ops::{ic::IcOps, storage::children::CanisterChildrenOps},
workflow::rpc::{RootCapabilityAuthority, request::handler::capability::RootCapability},
};
pub(super) fn verify_root_structural_proof(
capability: &RootCapability,
authority: &RootCapabilityAuthority,
) -> Result<(), Error> {
let caller = IcOps::msg_caller();
if authority.caller_canister_id() != caller {
return Err(Error::forbidden(
"structural proof caller differs from protected root capability authority",
));
}
match capability {
RootCapability::AcknowledgePlacementReceipt(_) | RootCapability::RequestCycles(_) => {
require_no_scoped_authority(authority)
}
RootCapability::AllocatePlacementChild(request)
| RootCapability::ProvisionCanister(request) => {
verify_root_structural_create(request, authority)
}
RootCapability::UpgradeCanister(request) => {
verify_root_structural_child_target(caller, request.canister_pid, "upgrade", authority)
}
RootCapability::RecycleCanister(request) => {
verify_replayable_recycle_target(caller, request.canister_pid, authority)
}
}
}
fn verify_replayable_recycle_target(
caller: Principal,
target_pid: Principal,
authority: &RootCapabilityAuthority,
) -> Result<(), Error> {
require_no_provision_parent_authority(authority)?;
if !authority.has_target() {
return Ok(());
}
verify_root_structural_child_target(caller, target_pid, "recycle", authority)
}
fn verify_root_structural_create(
request: &crate::dto::rpc::CreateCanisterRequest,
authority: &RootCapabilityAuthority,
) -> Result<(), Error> {
if !matches!(&request.parent, CreateCanisterParent::ThisCanister) {
return Err(Error::forbidden(
"structural provision proof requires parent=ThisCanister",
));
}
require_no_target_authority(authority)?;
if authority.provision_parent_canister_id() != Some(authority.caller_canister_id()) {
return Err(Error::forbidden(
"structural provision parent differs from the protected caller authority",
));
}
Ok(())
}
fn verify_root_structural_child_target(
caller: Principal,
target_pid: Principal,
operation: &str,
authority: &RootCapabilityAuthority,
) -> Result<(), Error> {
require_no_provision_parent_authority(authority)?;
if authority.target_canister_id() != Some(target_pid) {
return Err(Error::forbidden(format!(
"structural proof requires an exact active {operation} target"
)));
}
if authority.target_parent_canister_id() != Some(caller) {
return Err(Error::forbidden(format!(
"structural proof requires {operation} target to be a direct child of caller"
)));
}
Ok(())
}
fn require_no_scoped_authority(authority: &RootCapabilityAuthority) -> Result<(), Error> {
require_no_target_authority(authority)?;
require_no_provision_parent_authority(authority)
}
fn require_no_target_authority(authority: &RootCapabilityAuthority) -> Result<(), Error> {
if authority.has_target() {
return Err(Error::forbidden(
"root capability carries unexpected target authority",
));
}
Ok(())
}
fn require_no_provision_parent_authority(authority: &RootCapabilityAuthority) -> Result<(), Error> {
if authority.has_provision_parent() {
return Err(Error::forbidden(
"root capability carries unexpected provision-parent authority",
));
}
Ok(())
}
pub(super) fn verify_nonroot_structural_cycles_proof() -> Result<(), Error> {
let caller = IcOps::msg_caller();
if !CanisterChildrenOps::contains_pid(&caller) {
return Err(Error::forbidden(
"structural proof requires caller to be a direct child of receiver",
));
}
Ok(())
}