canic-core 0.100.91

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use crate::{
    cdk::types::Principal,
    dto::auth::RoleAttestation,
    ids::CanisterRole,
    ops::auth::{
        AUTH_TIME_SKEW_ALLOWANCE_NS, AuthExpiryError, AuthOpsError, AuthScopeError,
        AuthValidationError,
    },
};

fn p(id: u8) -> Principal {
    Principal::from_slice(&[id; 29])
}

fn role_attestation() -> RoleAttestation {
    RoleAttestation {
        subject: p(1),
        role: CanisterRole::new("project_hub"),
        subnet_id: Some(p(3)),
        audience: p(2),
        issued_at_ns: 10,
        expires_at_ns: 20,
        epoch: 4,
    }
}

#[test]
fn role_attestation_claims_accept_future_issued_at_within_skew() {
    let mut payload = role_attestation();
    payload.issued_at_ns = 15 + 30_000_000_000;
    payload.expires_at_ns = payload.issued_at_ns + 10;

    super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(3)), 15, 4)
        .expect("future issued_at within skew allowance should verify");
}

#[test]
fn role_attestation_claims_reject_subject_audience_subnet_and_epoch_drift() {
    let payload = role_attestation();

    let subject = super::verify_role_attestation_claims(&payload, p(9), p(2), Some(p(3)), 15, 4)
        .expect_err("wrong caller must reject");
    std::assert_matches!(
        subject,
        AuthOpsError::Scope(AuthScopeError::AttestationSubjectMismatch { .. })
    );

    let audience = super::verify_role_attestation_claims(&payload, p(1), p(9), Some(p(3)), 15, 4)
        .expect_err("wrong audience must reject");
    std::assert_matches!(
        audience,
        AuthOpsError::Scope(AuthScopeError::AttestationAudienceMismatch { .. })
    );

    let subnet = super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(9)), 15, 4)
        .expect_err("wrong subnet must reject");
    std::assert_matches!(
        subnet,
        AuthOpsError::Scope(AuthScopeError::AttestationSubnetMismatch { .. })
    );

    let epoch = super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(3)), 15, 5)
        .expect_err("stale epoch must reject");
    std::assert_matches!(
        epoch,
        AuthOpsError::Expiry(AuthExpiryError::AttestationEpochRejected { .. })
    );
}

#[test]
fn local_subnet_role_attestation_requires_an_exact_subnet_claim() {
    let payload = role_attestation();
    super::verify_local_subnet_role_attestation_claims(&payload, p(1), p(2), p(3), 15, 4)
        .expect("exact local-Subnet claim should verify");

    let mut missing = payload.clone();
    missing.subnet_id = None;
    let missing =
        super::verify_local_subnet_role_attestation_claims(&missing, p(1), p(2), p(3), 15, 4)
            .expect_err("local-Subnet verification must reject a missing claim");
    std::assert_matches!(
        missing,
        AuthOpsError::Validation(AuthValidationError::AttestationSubnetRequired)
    );

    let mismatch =
        super::verify_local_subnet_role_attestation_claims(&payload, p(1), p(2), p(9), 15, 4)
            .expect_err("local-Subnet verification must reject a different receiver Subnet");
    std::assert_matches!(
        mismatch,
        AuthOpsError::Scope(AuthScopeError::AttestationSubnetMismatch { .. })
    );
}

#[test]
fn role_attestation_claims_reject_future_issued_at_beyond_skew() {
    let mut payload = role_attestation();
    payload.issued_at_ns = 15 + AUTH_TIME_SKEW_ALLOWANCE_NS + 1;
    payload.expires_at_ns = payload.issued_at_ns + 10;

    let err = super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(3)), 15, 4)
        .expect_err("future issued_at beyond skew allowance must reject");

    std::assert_matches!(
        err,
        AuthOpsError::Expiry(AuthExpiryError::AttestationNotYetValid { .. })
    );
}

#[test]
fn role_attestation_claims_reject_invalid_time_window() {
    let mut payload = role_attestation();
    payload.expires_at_ns = payload.issued_at_ns;

    let err = super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(3)), 15, 4)
        .expect_err("invalid attestation time window must reject");

    std::assert_matches!(
        err,
        AuthOpsError::Validation(AuthValidationError::AttestationInvalidWindow { .. })
    );
}

#[test]
fn role_attestation_claims_reject_expiry_boundary() {
    let mut payload = role_attestation();
    payload.issued_at_ns = 10;
    payload.expires_at_ns = 15;

    let err = super::verify_role_attestation_claims(&payload, p(1), p(2), Some(p(3)), 15, 4)
        .expect_err("attestation at expiry boundary must reject");

    std::assert_matches!(
        err,
        AuthOpsError::Expiry(AuthExpiryError::AttestationExpired { .. })
    );
}