pub mod mapper;
use crate::{
cdk::types::Principal,
domain::policy::auth::{
RootDelegationRenewalBatch, RootIssuerPolicy, RootIssuerRenewalAttempt,
RootIssuerRenewalState, RootIssuerRenewalTemplate,
},
dto::auth::ActiveDelegationProof,
ops::storage::auth::mapper::{
ActiveDelegationProofRecordMapper, RootDelegationRenewalBatchRecordMapper,
RootIssuerPolicyRecordMapper, RootIssuerRenewalAttemptRecordMapper,
RootIssuerRenewalStateRecordMapper, RootIssuerRenewalTemplateRecordMapper,
},
storage::stable::auth::{
AuthState, DelegatedSessionBootstrapBindingRecord, DelegatedSessionRecord,
RootProvisionerRecord,
},
};
pub use crate::storage::stable::auth::DelegatedSessionUpsertResult;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DelegatedSession {
pub wallet_pid: Principal,
pub delegated_pid: Principal,
pub issued_at: u64,
pub expires_at: u64,
pub bootstrap_token_fingerprint: Option<[u8; 32]>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DelegatedSessionBootstrapBinding {
pub wallet_pid: Principal,
pub delegated_pid: Principal,
pub token_fingerprint: [u8; 32],
pub bound_at: u64,
pub expires_at: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RootDelegationRenewalProvisioner {
pub principal: Principal,
pub enabled: bool,
}
pub struct AuthStateOps;
impl AuthStateOps {
#[must_use]
pub fn delegated_session(wallet_pid: Principal, now_secs: u64) -> Option<DelegatedSession> {
AuthState::get_active_delegated_session(wallet_pid, now_secs)
.map(delegated_session_record_to_view)
}
#[must_use]
pub fn delegated_session_subject(wallet_pid: Principal, now_secs: u64) -> Option<Principal> {
Self::delegated_session(wallet_pid, now_secs).map(|session| session.delegated_pid)
}
#[cfg(test)]
pub fn upsert_delegated_session(
session: DelegatedSession,
now_secs: u64,
) -> DelegatedSessionUpsertResult {
AuthState::upsert_delegated_session(delegated_session_view_to_record(session), now_secs)
}
pub fn upsert_delegated_session_with_bootstrap_binding(
session: DelegatedSession,
binding: DelegatedSessionBootstrapBinding,
now_secs: u64,
) -> DelegatedSessionUpsertResult {
AuthState::upsert_delegated_session_with_bootstrap_binding(
delegated_session_view_to_record(session),
delegated_session_bootstrap_binding_view_to_record(binding),
now_secs,
)
}
pub fn clear_delegated_session(wallet_pid: Principal) {
AuthState::clear_delegated_session(wallet_pid);
}
#[must_use]
pub fn prune_expired_delegated_sessions(now_secs: u64) -> usize {
AuthState::prune_expired_delegated_sessions(now_secs)
}
#[must_use]
pub fn delegated_session_bootstrap_binding(
token_fingerprint: [u8; 32],
now_secs: u64,
) -> Option<DelegatedSessionBootstrapBinding> {
AuthState::get_active_delegated_session_bootstrap_binding(token_fingerprint, now_secs)
.map(delegated_session_bootstrap_binding_record_to_view)
}
#[must_use]
pub fn prune_expired_delegated_session_bootstrap_bindings(now_secs: u64) -> usize {
AuthState::prune_expired_delegated_session_bootstrap_bindings(now_secs)
}
#[must_use]
pub fn active_delegation_proof(now_ns: u64) -> Option<ActiveDelegationProof> {
let proof = AuthState::get_active_delegation_proof()
.map(ActiveDelegationProofRecordMapper::record_to_dto)?;
if now_ns < proof.not_before_ns || now_ns >= proof.expires_at_ns {
return None;
}
Some(proof)
}
#[must_use]
pub fn active_delegation_proof_snapshot() -> Option<ActiveDelegationProof> {
AuthState::get_active_delegation_proof()
.map(ActiveDelegationProofRecordMapper::record_to_dto)
}
pub fn set_active_delegation_proof(proof: ActiveDelegationProof) {
AuthState::set_active_delegation_proof(ActiveDelegationProofRecordMapper::dto_to_record(
proof,
));
}
#[cfg(test)]
pub fn clear_active_delegation_proof() {
AuthState::clear_active_delegation_proof();
}
#[must_use]
pub fn root_issuer_policy(issuer_pid: Principal) -> Option<RootIssuerPolicy> {
AuthState::get_root_issuer(issuer_pid).map(RootIssuerPolicyRecordMapper::record_to_policy)
}
pub fn upsert_root_issuer_policy(policy: RootIssuerPolicy) {
AuthState::upsert_root_issuer(RootIssuerPolicyRecordMapper::policy_to_record(policy));
}
#[must_use]
pub fn root_issuer_renewal_template(
issuer_pid: Principal,
) -> Option<RootIssuerRenewalTemplate> {
AuthState::get_root_issuer_renewal_template(issuer_pid)
.map(RootIssuerRenewalTemplateRecordMapper::record_to_template)
}
#[must_use]
pub fn root_issuer_renewal_templates() -> Vec<RootIssuerRenewalTemplate> {
AuthState::list_root_issuer_renewal_templates()
.into_iter()
.map(RootIssuerRenewalTemplateRecordMapper::record_to_template)
.collect()
}
pub fn upsert_root_issuer_renewal_template(template: RootIssuerRenewalTemplate) {
AuthState::upsert_root_issuer_renewal_template(
RootIssuerRenewalTemplateRecordMapper::template_to_record(template),
);
}
#[must_use]
pub fn root_issuer_renewal_state(issuer_pid: Principal) -> Option<RootIssuerRenewalState> {
AuthState::get_root_issuer_renewal_state(issuer_pid)
.map(RootIssuerRenewalStateRecordMapper::record_to_state)
}
pub fn upsert_root_issuer_renewal_state(state: RootIssuerRenewalState) {
AuthState::upsert_root_issuer_renewal_state(
RootIssuerRenewalStateRecordMapper::state_to_record(state),
);
}
#[must_use]
pub fn root_issuer_renewal_attempt(attempt_id: [u8; 32]) -> Option<RootIssuerRenewalAttempt> {
AuthState::get_root_issuer_renewal_attempt(attempt_id)
.map(RootIssuerRenewalAttemptRecordMapper::record_to_attempt)
}
pub fn upsert_root_issuer_renewal_attempt(attempt: RootIssuerRenewalAttempt) {
AuthState::upsert_root_issuer_renewal_attempt(
RootIssuerRenewalAttemptRecordMapper::attempt_to_record(attempt),
);
}
#[must_use]
pub fn root_delegation_renewal_batch(batch_id: [u8; 32]) -> Option<RootDelegationRenewalBatch> {
AuthState::get_root_delegation_renewal_batch(batch_id)
.map(RootDelegationRenewalBatchRecordMapper::record_to_batch)
}
#[must_use]
pub fn root_delegation_renewal_batches() -> Vec<RootDelegationRenewalBatch> {
AuthState::list_root_delegation_renewal_batches()
.into_iter()
.map(RootDelegationRenewalBatchRecordMapper::record_to_batch)
.collect()
}
pub fn upsert_root_delegation_renewal_batch(batch: RootDelegationRenewalBatch) {
AuthState::upsert_root_delegation_renewal_batch(
RootDelegationRenewalBatchRecordMapper::batch_to_record(batch),
);
}
#[must_use]
pub fn root_delegation_renewal_provisioner(
principal: Principal,
) -> Option<RootDelegationRenewalProvisioner> {
AuthState::get_root_provisioner(principal).map(root_provisioner_record_to_view)
}
#[must_use]
pub fn root_delegation_renewal_provisioners() -> Vec<RootDelegationRenewalProvisioner> {
AuthState::list_root_provisioners()
.into_iter()
.map(root_provisioner_record_to_view)
.collect()
}
#[must_use]
pub fn is_root_delegation_renewal_provisioner(principal: Principal) -> bool {
Self::root_delegation_renewal_provisioner(principal).is_some_and(|view| view.enabled)
}
pub fn upsert_root_delegation_renewal_provisioner(
provisioner: RootDelegationRenewalProvisioner,
) {
AuthState::upsert_root_provisioner(root_provisioner_view_to_record(provisioner));
}
}
const fn delegated_session_record_to_view(record: DelegatedSessionRecord) -> DelegatedSession {
DelegatedSession {
wallet_pid: record.wallet_pid,
delegated_pid: record.delegated_pid,
issued_at: record.issued_at,
expires_at: record.expires_at,
bootstrap_token_fingerprint: record.bootstrap_token_fingerprint,
}
}
const fn delegated_session_view_to_record(view: DelegatedSession) -> DelegatedSessionRecord {
DelegatedSessionRecord {
wallet_pid: view.wallet_pid,
delegated_pid: view.delegated_pid,
issued_at: view.issued_at,
expires_at: view.expires_at,
bootstrap_token_fingerprint: view.bootstrap_token_fingerprint,
}
}
const fn delegated_session_bootstrap_binding_record_to_view(
record: DelegatedSessionBootstrapBindingRecord,
) -> DelegatedSessionBootstrapBinding {
DelegatedSessionBootstrapBinding {
wallet_pid: record.wallet_pid,
delegated_pid: record.delegated_pid,
token_fingerprint: record.token_fingerprint,
bound_at: record.bound_at,
expires_at: record.expires_at,
}
}
const fn delegated_session_bootstrap_binding_view_to_record(
view: DelegatedSessionBootstrapBinding,
) -> DelegatedSessionBootstrapBindingRecord {
DelegatedSessionBootstrapBindingRecord {
wallet_pid: view.wallet_pid,
delegated_pid: view.delegated_pid,
token_fingerprint: view.token_fingerprint,
bound_at: view.bound_at,
expires_at: view.expires_at,
}
}
const fn root_provisioner_record_to_view(
record: RootProvisionerRecord,
) -> RootDelegationRenewalProvisioner {
RootDelegationRenewalProvisioner {
principal: record.principal,
enabled: record.enabled,
}
}
const fn root_provisioner_view_to_record(
view: RootDelegationRenewalProvisioner,
) -> RootProvisionerRecord {
RootProvisionerRecord {
principal: view.principal,
enabled: view.enabled,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
domain::policy::auth::{
RootDelegatedRoleGrantPolicy, RootDelegationAudiencePolicy, RootDelegationRenewalBatch,
RootIssuerPolicy, RootIssuerRenewalAttempt, RootIssuerRenewalAttemptStatus,
RootIssuerRenewalOutcome, RootIssuerRenewalProofRef, RootIssuerRenewalState,
RootIssuerRenewalTemplate,
},
dto::auth::{
DelegatedRoleGrant, DelegationAudience, DelegationCert, DelegationProof,
IcCanisterSignatureProofV1, IssuerProofAlgorithm, IssuerProofBinding, RootProof,
},
ids::CanisterRole,
};
fn p(id: u8) -> Principal {
Principal::from_slice(&[id; 29])
}
fn active_proof() -> ActiveDelegationProof {
let issuer_proof_alg = IssuerProofAlgorithm::IcCanisterSignatureV1;
let issuer_proof_binding = IssuerProofBinding::IcCanisterSignatureV1 { seed_hash: [5; 32] };
ActiveDelegationProof {
proof: DelegationProof {
cert: DelegationCert {
root_pid: p(1),
issuer_pid: p(2),
issuer_proof_alg,
issuer_proof_binding_hash: [6; 32],
issuer_proof_binding,
issued_at_ns: 10,
not_before_ns: 20,
expires_at_ns: 100,
max_token_ttl_ns: 30,
aud: DelegationAudience::CanicSubnet(p(7)),
grants: vec![DelegatedRoleGrant {
target: CanisterRole::owned("project_instance".to_string()),
scopes: vec!["read".to_string(), "write".to_string()],
}],
},
root_proof: RootProof::IcCanisterSignatureV1(IcCanisterSignatureProofV1 {
signature_cbor: vec![8; 64],
public_key_der: vec![9; 32],
}),
},
cert_hash: [10; 32],
not_before_ns: 20,
expires_at_ns: 100,
refresh_after_ns: 80,
installed_at_ns: 15,
installed_by: p(11),
}
}
#[test]
fn active_delegation_proof_round_trips_and_filters_by_time() {
AuthStateOps::clear_active_delegation_proof();
let proof = active_proof();
AuthStateOps::set_active_delegation_proof(proof.clone());
assert_eq!(AuthStateOps::active_delegation_proof(19), None);
assert_eq!(AuthStateOps::active_delegation_proof(20), Some(proof));
assert!(AuthStateOps::active_delegation_proof(99).is_some());
assert_eq!(AuthStateOps::active_delegation_proof(100), None);
AuthStateOps::clear_active_delegation_proof();
assert_eq!(AuthStateOps::active_delegation_proof(20), None);
}
#[test]
fn root_issuer_policy_round_trips_through_auth_state() {
let policy = RootIssuerPolicy {
issuer_pid: p(31),
enabled: true,
allowed_audiences: vec![
RootDelegationAudiencePolicy::Canister(p(32)),
RootDelegationAudiencePolicy::CanicSubnet(p(33)),
RootDelegationAudiencePolicy::Project("test".to_string()),
],
allowed_grants: vec![RootDelegatedRoleGrantPolicy {
target: CanisterRole::owned("project_instance".to_string()),
scopes: vec!["canic.issue".to_string(), "canic.read".to_string()],
}],
max_cert_ttl_ns: 120_000_000_000,
refresh_after_ratio_bps: 8_000,
};
AuthStateOps::upsert_root_issuer_policy(policy.clone());
assert_eq!(AuthStateOps::root_issuer_policy(p(31)), Some(policy));
assert_eq!(AuthStateOps::root_issuer_policy(p(34)), None);
}
#[test]
fn root_issuer_renewal_template_round_trips_through_auth_state() {
let template = RootIssuerRenewalTemplate {
issuer_pid: p(41),
enabled: true,
audience: RootDelegationAudiencePolicy::Project("test".to_string()),
grants: vec![RootDelegatedRoleGrantPolicy {
target: CanisterRole::owned("project_instance".to_string()),
scopes: vec!["canic.read".to_string()],
}],
cert_ttl_ns: 120_000_000_000,
};
AuthStateOps::upsert_root_issuer_renewal_template(template.clone());
assert_eq!(
AuthStateOps::root_issuer_renewal_template(p(41)),
Some(template)
);
assert_eq!(AuthStateOps::root_issuer_renewal_template(p(42)), None);
}
#[test]
fn root_issuer_renewal_state_round_trips_through_auth_state() {
let state = RootIssuerRenewalState {
issuer_pid: p(51),
template_fingerprint: [1; 32],
last_installed_cert_hash: Some([2; 32]),
last_installed_expires_at_ns: Some(200),
last_installed_refresh_after_ns: Some(160),
active_attempt_id: Some([3; 32]),
last_outcome: RootIssuerRenewalOutcome::RetrievalExpired,
consecutive_failures: 2,
next_attempt_after_ns: 90,
updated_at_ns: 80,
};
AuthStateOps::upsert_root_issuer_renewal_state(state.clone());
assert_eq!(AuthStateOps::root_issuer_renewal_state(p(51)), Some(state));
assert_eq!(AuthStateOps::root_issuer_renewal_state(p(52)), None);
}
#[test]
fn root_issuer_renewal_attempt_round_trips_through_auth_state() {
let attempt = RootIssuerRenewalAttempt {
attempt_id: [4; 32],
issuer_pid: p(61),
template_fingerprint: [5; 32],
batch_id: [6; 32],
proof_ref: RootIssuerRenewalProofRef {
issuer_pid: p(61),
cert_hash: [7; 32],
},
status: RootIssuerRenewalAttemptStatus::Prepared,
prepared_at_ns: 10,
retrieval_expires_at_ns: 70,
install_deadline_ns: 90,
prepared_cert_hash: [7; 32],
prepared_expires_at_ns: 200,
prepared_refresh_after_ns: 160,
failure: Some(RootIssuerRenewalOutcome::RetrievalExpired),
};
AuthStateOps::upsert_root_issuer_renewal_attempt(attempt.clone());
assert_eq!(
AuthStateOps::root_issuer_renewal_attempt([4; 32]),
Some(attempt)
);
assert_eq!(AuthStateOps::root_issuer_renewal_attempt([8; 32]), None);
}
#[test]
fn root_delegation_renewal_batch_round_trips_through_auth_state() {
let batch = RootDelegationRenewalBatch {
batch_id: [9; 32],
attempt_ids: vec![[10; 32], [11; 32]],
prepared_at_ns: 20,
retrieval_expires_at_ns: 80,
};
AuthStateOps::upsert_root_delegation_renewal_batch(batch.clone());
assert_eq!(
AuthStateOps::root_delegation_renewal_batch([9; 32]),
Some(batch)
);
assert_eq!(
AuthStateOps::root_delegation_renewal_batches()
.into_iter()
.filter(|candidate| candidate.batch_id == [9; 32])
.count(),
1
);
assert_eq!(AuthStateOps::root_delegation_renewal_batch([12; 32]), None);
}
#[test]
fn root_delegation_renewal_provisioner_round_trips_through_auth_state() {
let provisioner = RootDelegationRenewalProvisioner {
principal: p(61),
enabled: true,
};
AuthStateOps::upsert_root_delegation_renewal_provisioner(provisioner);
assert_eq!(
AuthStateOps::root_delegation_renewal_provisioner(p(61)),
Some(provisioner)
);
assert!(AuthStateOps::is_root_delegation_renewal_provisioner(p(61)));
assert!(!AuthStateOps::is_root_delegation_renewal_provisioner(p(62)));
AuthStateOps::upsert_root_delegation_renewal_provisioner(
RootDelegationRenewalProvisioner {
principal: p(61),
enabled: false,
},
);
assert!(!AuthStateOps::is_root_delegation_renewal_provisioner(p(61)));
}
}