1use crate::{
2 api::access::auth::AuthAccessApi,
3 cdk::types::Principal,
4 dto::{
5 auth::{DelegationAdminCommand, DelegationAdminResponse, DelegationCert, DelegationProof},
6 error::Error,
7 },
8 error::InternalErrorClass,
9 ops::{
10 auth::DelegatedTokenOps, config::ConfigOps, ic::IcOps, runtime::env::EnvOps,
11 storage::auth::DelegationStateOps,
12 },
13 workflow::auth::DelegationWorkflow,
14};
15use std::{sync::Arc, time::Duration};
16
17pub struct DelegationApi;
24
25impl DelegationApi {
26 fn map_delegation_error(err: crate::InternalError) -> Error {
27 match err.class() {
28 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
29 Error::internal(err.to_string())
30 }
31 _ => Error::from(err),
32 }
33 }
34
35 pub fn prepare_issue(cert: DelegationCert) -> Result<(), Error> {
36 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
37 if !cfg.enabled {
38 return Err(Error::forbidden("delegation disabled"));
39 }
40
41 DelegationWorkflow::prepare_delegation(&cert).map_err(Self::map_delegation_error)
43 }
44
45 pub fn get_issue(cert: DelegationCert) -> Result<DelegationProof, Error> {
46 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
47 if !cfg.enabled {
48 return Err(Error::forbidden("delegation disabled"));
49 }
50
51 DelegationWorkflow::get_delegation(cert).map_err(Self::map_delegation_error)
53 }
54
55 pub fn issue_and_store(cert: DelegationCert) -> Result<DelegationProof, Error> {
56 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
57 if !cfg.enabled {
58 return Err(Error::forbidden("delegation disabled"));
59 }
60
61 DelegationWorkflow::issue_and_store(cert).map_err(Self::map_delegation_error)
62 }
63
64 pub fn store_proof(proof: DelegationProof) -> Result<(), Error> {
65 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
66 if !cfg.enabled {
67 return Err(Error::forbidden("delegation disabled"));
68 }
69
70 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
71 let caller = IcOps::msg_caller();
72 if caller != root_pid {
73 return Err(Error::forbidden(
74 "delegation proof store requires root caller",
75 ));
76 }
77
78 DelegatedTokenOps::verify_delegation_proof(&proof, root_pid)
79 .map_err(Self::map_delegation_error)?;
80
81 DelegationStateOps::set_proof_from_dto(proof);
82 Ok(())
83 }
84
85 pub fn require_proof() -> Result<DelegationProof, Error> {
86 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
87 if !cfg.enabled {
88 return Err(Error::forbidden("delegation disabled"));
89 }
90
91 DelegationStateOps::proof_dto().ok_or_else(|| Error::not_found("delegation proof not set"))
92 }
93}
94
95pub struct DelegationAdminApi;
102
103impl DelegationAdminApi {
104 pub async fn admin(cmd: DelegationAdminCommand) -> Result<DelegationAdminResponse, Error> {
105 match cmd {
106 DelegationAdminCommand::StartRotation { interval_secs } => {
107 let started = Self::start_rotation(interval_secs).await?;
108 Ok(if started {
109 DelegationAdminResponse::RotationStarted
110 } else {
111 DelegationAdminResponse::RotationAlreadyRunning
112 })
113 }
114 DelegationAdminCommand::StopRotation => {
115 let stopped = Self::stop_rotation().await?;
116 Ok(if stopped {
117 DelegationAdminResponse::RotationStopped
118 } else {
119 DelegationAdminResponse::RotationNotRunning
120 })
121 }
122 }
123 }
124
125 pub async fn start_rotation(interval_secs: u64) -> Result<bool, Error> {
126 AuthAccessApi::caller_is_root(IcOps::msg_caller()).await?;
127
128 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
129 if !cfg.enabled {
130 return Err(Error::forbidden("delegation disabled"));
131 }
132
133 if interval_secs == 0 {
134 return Err(Error::invalid(
135 "rotation interval must be greater than zero",
136 ));
137 }
138
139 let template = rotation_template()?;
140 let template = Arc::new(template);
141 let interval = Duration::from_secs(interval_secs);
142
143 let started = DelegationWorkflow::start_rotation(
144 interval,
145 Arc::new({
146 let template = Arc::clone(&template);
147 move || {
148 let now_secs = IcOps::now_secs();
149 Ok(build_rotation_cert(template.as_ref(), now_secs))
150 }
151 }),
152 Arc::new(|proof| {
153 DelegationStateOps::set_proof_from_dto(proof);
154 Ok(())
155 }),
156 );
157
158 Ok(started)
159 }
160
161 pub async fn stop_rotation() -> Result<bool, Error> {
162 AuthAccessApi::caller_is_root(IcOps::msg_caller()).await?;
163 Ok(DelegationWorkflow::stop_rotation())
164 }
165}
166
167struct DelegationRotationTemplate {
172 v: u16,
173 signer_pid: Principal,
174 audiences: Vec<String>,
175 scopes: Vec<String>,
176 ttl_secs: u64,
177}
178
179fn rotation_template() -> Result<DelegationRotationTemplate, Error> {
180 let proof = DelegationStateOps::proof_dto()
181 .ok_or_else(|| Error::not_found("delegation proof not set"))?;
182 let cert = proof.cert;
183
184 if cert.expires_at <= cert.issued_at {
185 return Err(Error::invalid(
186 "delegation cert expires_at must be greater than issued_at",
187 ));
188 }
189
190 let ttl_secs = cert.expires_at - cert.issued_at;
191
192 Ok(DelegationRotationTemplate {
193 v: cert.v,
194 signer_pid: cert.signer_pid,
195 audiences: cert.audiences,
196 scopes: cert.scopes,
197 ttl_secs,
198 })
199}
200
201fn build_rotation_cert(template: &DelegationRotationTemplate, now_secs: u64) -> DelegationCert {
202 DelegationCert {
203 v: template.v,
204 signer_pid: template.signer_pid,
205 audiences: template.audiences.clone(),
206 scopes: template.scopes.clone(),
207 issued_at: now_secs,
208 expires_at: now_secs.saturating_add(template.ttl_secs),
209 }
210}