1use crate::{
2 cdk::types::Principal,
3 dto::{
4 auth::{
5 DelegatedToken, DelegatedTokenClaims, DelegationAdminCommand, DelegationAdminResponse,
6 DelegationCert, DelegationProof,
7 },
8 error::Error,
9 },
10 error::InternalErrorClass,
11 ops::{
12 auth::DelegatedTokenOps, config::ConfigOps, ic::IcOps, runtime::env::EnvOps,
13 storage::auth::DelegationStateOps,
14 },
15 workflow::auth::DelegationWorkflow,
16};
17use std::{sync::Arc, time::Duration};
18
19pub struct DelegationApi;
26
27impl DelegationApi {
28 fn map_delegation_error(err: crate::InternalError) -> Error {
29 match err.class() {
30 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
31 Error::internal(err.to_string())
32 }
33 _ => Error::from(err),
34 }
35 }
36
37 pub fn sign_delegation_cert(cert: DelegationCert) -> Result<DelegationProof, Error> {
39 DelegatedTokenOps::sign_delegation_cert(cert).map_err(Self::map_delegation_error)
40 }
41
42 pub fn verify_delegation_proof(
47 proof: &DelegationProof,
48 authority_pid: Principal,
49 ) -> Result<(), Error> {
50 DelegatedTokenOps::verify_delegation_proof(proof, authority_pid)
51 .map_err(Self::map_delegation_error)
52 }
53
54 pub fn sign_token(
55 token_version: u16,
56 claims: DelegatedTokenClaims,
57 proof: DelegationProof,
58 ) -> Result<DelegatedToken, Error> {
59 DelegatedTokenOps::sign_token(token_version, claims, proof)
60 .map_err(Self::map_delegation_error)
61 }
62
63 pub fn verify_token(
68 token: &DelegatedToken,
69 authority_pid: Principal,
70 now_secs: u64,
71 ) -> Result<(), Error> {
72 DelegatedTokenOps::verify_token(token, authority_pid, now_secs)
73 .map(|_| ())
74 .map_err(Self::map_delegation_error)
75 }
76
77 pub fn verify_token_claims(
82 token: &DelegatedToken,
83 authority_pid: Principal,
84 now_secs: u64,
85 ) -> Result<DelegatedTokenClaims, Error> {
86 DelegatedTokenOps::verify_token(token, authority_pid, now_secs)
87 .map(|verified| verified.claims)
88 .map_err(Self::map_delegation_error)
89 }
90
91 pub fn prepare_issue(cert: DelegationCert) -> Result<(), Error> {
92 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
93 if !cfg.enabled {
94 return Err(Error::forbidden("delegation disabled"));
95 }
96
97 DelegationWorkflow::prepare_delegation(&cert).map_err(Self::map_delegation_error)
99 }
100
101 pub fn get_issue(cert: DelegationCert) -> Result<DelegationProof, Error> {
102 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
103 if !cfg.enabled {
104 return Err(Error::forbidden("delegation disabled"));
105 }
106
107 DelegationWorkflow::get_delegation(cert).map_err(Self::map_delegation_error)
109 }
110
111 pub fn issue_and_store(cert: DelegationCert) -> Result<DelegationProof, Error> {
112 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
113 if !cfg.enabled {
114 return Err(Error::forbidden("delegation disabled"));
115 }
116
117 DelegationWorkflow::issue_and_store(cert).map_err(Self::map_delegation_error)
118 }
119
120 pub fn store_proof(proof: DelegationProof) -> Result<(), Error> {
121 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
122 if !cfg.enabled {
123 return Err(Error::forbidden("delegation disabled"));
124 }
125
126 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
127 let caller = IcOps::msg_caller();
128 if caller != root_pid {
129 return Err(Error::forbidden(
130 "delegation proof store requires root caller",
131 ));
132 }
133
134 DelegatedTokenOps::verify_delegation_proof(&proof, root_pid)
135 .map_err(Self::map_delegation_error)?;
136
137 DelegationStateOps::set_proof_from_dto(proof);
138 Ok(())
139 }
140
141 pub fn require_proof() -> Result<DelegationProof, Error> {
142 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
143 if !cfg.enabled {
144 return Err(Error::forbidden("delegation disabled"));
145 }
146
147 DelegationStateOps::proof_dto().ok_or_else(|| Error::not_found("delegation proof not set"))
148 }
149}
150
151pub struct DelegationAdminApi;
158
159impl DelegationAdminApi {
160 pub async fn admin(cmd: DelegationAdminCommand) -> Result<DelegationAdminResponse, Error> {
161 match cmd {
162 DelegationAdminCommand::StartRotation { interval_secs } => {
163 let started = Self::start_rotation(interval_secs).await?;
164 Ok(if started {
165 DelegationAdminResponse::RotationStarted
166 } else {
167 DelegationAdminResponse::RotationAlreadyRunning
168 })
169 }
170 DelegationAdminCommand::StopRotation => {
171 let stopped = Self::stop_rotation().await?;
172 Ok(if stopped {
173 DelegationAdminResponse::RotationStopped
174 } else {
175 DelegationAdminResponse::RotationNotRunning
176 })
177 }
178 }
179 }
180
181 #[allow(clippy::unused_async)]
182 pub async fn start_rotation(interval_secs: u64) -> Result<bool, Error> {
183 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
184 if !cfg.enabled {
185 return Err(Error::forbidden("delegation disabled"));
186 }
187
188 if interval_secs == 0 {
189 return Err(Error::invalid(
190 "rotation interval must be greater than zero",
191 ));
192 }
193
194 let template = rotation_template()?;
195 let template = Arc::new(template);
196 let interval = Duration::from_secs(interval_secs);
197
198 let started = DelegationWorkflow::start_rotation(
199 interval,
200 Arc::new({
201 let template = Arc::clone(&template);
202 move || {
203 let now_secs = IcOps::now_secs();
204 Ok(build_rotation_cert(template.as_ref(), now_secs))
205 }
206 }),
207 Arc::new(|proof| {
208 DelegationStateOps::set_proof_from_dto(proof);
209 Ok(())
210 }),
211 );
212
213 Ok(started)
214 }
215
216 #[allow(clippy::unused_async)]
217 pub async fn stop_rotation() -> Result<bool, Error> {
218 Ok(DelegationWorkflow::stop_rotation())
219 }
220}
221
222struct DelegationRotationTemplate {
227 v: u16,
228 signer_pid: Principal,
229 audiences: Vec<String>,
230 scopes: Vec<String>,
231 ttl_secs: u64,
232}
233
234fn rotation_template() -> Result<DelegationRotationTemplate, Error> {
235 let proof = DelegationStateOps::proof_dto()
236 .ok_or_else(|| Error::not_found("delegation proof not set"))?;
237 let cert = proof.cert;
238
239 if cert.expires_at <= cert.issued_at {
240 return Err(Error::invalid(
241 "delegation cert expires_at must be greater than issued_at",
242 ));
243 }
244
245 let ttl_secs = cert.expires_at - cert.issued_at;
246
247 Ok(DelegationRotationTemplate {
248 v: cert.v,
249 signer_pid: cert.signer_pid,
250 audiences: cert.audiences,
251 scopes: cert.scopes,
252 ttl_secs,
253 })
254}
255
256fn build_rotation_cert(template: &DelegationRotationTemplate, now_secs: u64) -> DelegationCert {
257 DelegationCert {
258 v: template.v,
259 signer_pid: template.signer_pid,
260 audiences: template.audiences.clone(),
261 scopes: template.scopes.clone(),
262 issued_at: now_secs,
263 expires_at: now_secs.saturating_add(template.ttl_secs),
264 }
265}