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 const DELEGATED_TOKENS_DISABLED: &str =
29 "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
30
31 fn map_delegation_error(err: crate::InternalError) -> Error {
32 match err.class() {
33 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
34 Error::internal(err.to_string())
35 }
36 _ => Error::from(err),
37 }
38 }
39
40 pub fn sign_delegation_cert(cert: DelegationCert) -> Result<DelegationProof, Error> {
42 DelegatedTokenOps::sign_delegation_cert(cert).map_err(Self::map_delegation_error)
43 }
44
45 pub fn verify_delegation_proof(
50 proof: &DelegationProof,
51 authority_pid: Principal,
52 ) -> Result<(), Error> {
53 DelegatedTokenOps::verify_delegation_proof(proof, authority_pid)
54 .map_err(Self::map_delegation_error)
55 }
56
57 pub fn sign_token(
58 token_version: u16,
59 claims: DelegatedTokenClaims,
60 proof: DelegationProof,
61 ) -> Result<DelegatedToken, Error> {
62 DelegatedTokenOps::sign_token(token_version, claims, proof)
63 .map_err(Self::map_delegation_error)
64 }
65
66 pub fn verify_token(
71 token: &DelegatedToken,
72 authority_pid: Principal,
73 now_secs: u64,
74 ) -> Result<(), Error> {
75 DelegatedTokenOps::verify_token(token, authority_pid, now_secs)
76 .map(|_| ())
77 .map_err(Self::map_delegation_error)
78 }
79
80 pub fn verify_token_verified(
85 token: &DelegatedToken,
86 authority_pid: Principal,
87 now_secs: u64,
88 ) -> Result<(DelegatedTokenClaims, DelegationCert), Error> {
89 DelegatedTokenOps::verify_token(token, authority_pid, now_secs)
90 .map(|verified| (verified.claims, verified.cert))
91 .map_err(Self::map_delegation_error)
92 }
93
94 pub fn prepare_issue(cert: DelegationCert) -> Result<(), Error> {
95 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
96 if !cfg.enabled {
97 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
98 }
99
100 DelegationWorkflow::prepare_delegation(&cert).map_err(Self::map_delegation_error)
102 }
103
104 pub fn get_issue(cert: DelegationCert) -> Result<DelegationProof, Error> {
105 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
106 if !cfg.enabled {
107 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
108 }
109
110 DelegationWorkflow::get_delegation(cert).map_err(Self::map_delegation_error)
112 }
113
114 pub fn issue_and_store(cert: DelegationCert) -> Result<DelegationProof, Error> {
115 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
116 if !cfg.enabled {
117 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
118 }
119
120 DelegationWorkflow::issue_and_store(cert).map_err(Self::map_delegation_error)
121 }
122
123 pub fn store_proof(proof: DelegationProof) -> Result<(), Error> {
124 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
125 if !cfg.enabled {
126 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
127 }
128
129 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
130 let caller = IcOps::msg_caller();
131 if caller != root_pid {
132 return Err(Error::forbidden(
133 "delegation proof store requires root caller",
134 ));
135 }
136
137 DelegatedTokenOps::verify_delegation_proof(&proof, root_pid)
138 .map_err(Self::map_delegation_error)?;
139
140 DelegationStateOps::set_proof_from_dto(proof);
141
142 Ok(())
143 }
144
145 pub fn require_proof() -> Result<DelegationProof, Error> {
146 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
147 if !cfg.enabled {
148 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
149 }
150
151 DelegationStateOps::proof_dto().ok_or_else(|| Error::not_found("delegation proof not set"))
152 }
153}
154
155pub struct DelegationAdminApi;
162
163impl DelegationAdminApi {
164 pub async fn admin(cmd: DelegationAdminCommand) -> Result<DelegationAdminResponse, Error> {
165 match cmd {
166 DelegationAdminCommand::StartRotation { interval_secs } => {
167 let started = Self::start_rotation(interval_secs).await?;
168 Ok(if started {
169 DelegationAdminResponse::RotationStarted
170 } else {
171 DelegationAdminResponse::RotationAlreadyRunning
172 })
173 }
174 DelegationAdminCommand::StopRotation => {
175 let stopped = Self::stop_rotation().await?;
176 Ok(if stopped {
177 DelegationAdminResponse::RotationStopped
178 } else {
179 DelegationAdminResponse::RotationNotRunning
180 })
181 }
182 }
183 }
184
185 #[allow(clippy::unused_async)]
186 pub async fn start_rotation(interval_secs: u64) -> Result<bool, Error> {
187 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
188 if !cfg.enabled {
189 return Err(Error::forbidden(DelegationApi::DELEGATED_TOKENS_DISABLED));
190 }
191
192 if interval_secs == 0 {
193 return Err(Error::invalid(
194 "rotation interval must be greater than zero",
195 ));
196 }
197
198 let template = rotation_template()?;
199 let template = Arc::new(template);
200 let interval = Duration::from_secs(interval_secs);
201
202 let started = DelegationWorkflow::start_rotation(
203 interval,
204 Arc::new({
205 let template = Arc::clone(&template);
206 move || {
207 let now_secs = IcOps::now_secs();
208 Ok(build_rotation_cert(template.as_ref(), now_secs))
209 }
210 }),
211 Arc::new(|proof| {
212 DelegationStateOps::set_proof_from_dto(proof);
213 Ok(())
214 }),
215 );
216
217 Ok(started)
218 }
219
220 #[allow(clippy::unused_async)]
221 pub async fn stop_rotation() -> Result<bool, Error> {
222 Ok(DelegationWorkflow::stop_rotation())
223 }
224}
225
226struct DelegationRotationTemplate {
231 v: u16,
232 signer_pid: Principal,
233 audiences: Vec<String>,
234 scopes: Vec<String>,
235 ttl_secs: u64,
236}
237
238fn rotation_template() -> Result<DelegationRotationTemplate, Error> {
239 let proof = DelegationStateOps::proof_dto()
240 .ok_or_else(|| Error::not_found("delegation proof not set"))?;
241 let cert = proof.cert;
242
243 if cert.expires_at <= cert.issued_at {
244 return Err(Error::invalid(
245 "delegation cert expires_at must be greater than issued_at",
246 ));
247 }
248
249 let ttl_secs = cert.expires_at - cert.issued_at;
250
251 Ok(DelegationRotationTemplate {
252 v: cert.v,
253 signer_pid: cert.signer_pid,
254 audiences: cert.audiences,
255 scopes: cert.scopes,
256 ttl_secs,
257 })
258}
259
260fn build_rotation_cert(template: &DelegationRotationTemplate, now_secs: u64) -> DelegationCert {
261 DelegationCert {
262 v: template.v,
263 signer_pid: template.signer_pid,
264 audiences: template.audiences.clone(),
265 scopes: template.scopes.clone(),
266 issued_at: now_secs,
267 expires_at: now_secs.saturating_add(template.ttl_secs),
268 }
269}