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::{config::ConfigOps, ic::IcOps, storage::auth::DelegationStateOps},
10 workflow::auth::DelegationWorkflow,
11};
12use std::{sync::Arc, time::Duration};
13
14pub struct DelegationApi;
21
22impl DelegationApi {
23 fn map_delegation_error(err: crate::InternalError) -> Error {
24 if err.to_string().contains("certified query required") {
25 return Error::invalid("certified query required");
26 }
27
28 match err.class() {
29 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
30 Error::internal(err.to_string())
31 }
32 _ => Error::from(err),
33 }
34 }
35
36 pub fn prepare_issue(cert: DelegationCert) -> Result<(), Error> {
37 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
38 if !cfg.enabled {
39 return Err(Error::forbidden("delegation disabled"));
40 }
41
42 DelegationWorkflow::prepare_delegation(&cert).map_err(Self::map_delegation_error)
44 }
45
46 pub fn get_issue(cert: DelegationCert) -> Result<DelegationProof, Error> {
47 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
48 if !cfg.enabled {
49 return Err(Error::forbidden("delegation disabled"));
50 }
51
52 DelegationWorkflow::get_delegation(cert).map_err(Self::map_delegation_error)
54 }
55
56 pub fn issue_and_store(cert: DelegationCert) -> Result<DelegationProof, Error> {
57 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
58 if !cfg.enabled {
59 return Err(Error::forbidden("delegation disabled"));
60 }
61
62 DelegationWorkflow::issue_and_store(cert).map_err(Self::map_delegation_error)
63 }
64
65 pub fn store_proof(proof: DelegationProof) -> Result<(), Error> {
66 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
67 if !cfg.enabled {
68 return Err(Error::forbidden("delegation disabled"));
69 }
70
71 DelegationStateOps::set_proof(proof);
72 Ok(())
73 }
74
75 pub fn require_proof() -> Result<DelegationProof, Error> {
76 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
77 if !cfg.enabled {
78 return Err(Error::forbidden("delegation disabled"));
79 }
80
81 DelegationStateOps::proof().ok_or_else(|| Error::not_found("delegation proof not set"))
82 }
83}
84
85pub struct DelegationAdminApi;
92
93impl DelegationAdminApi {
94 pub async fn admin(cmd: DelegationAdminCommand) -> Result<DelegationAdminResponse, Error> {
95 match cmd {
96 DelegationAdminCommand::StartRotation { interval_secs } => {
97 let started = Self::start_rotation(interval_secs).await?;
98 Ok(if started {
99 DelegationAdminResponse::RotationStarted
100 } else {
101 DelegationAdminResponse::RotationAlreadyRunning
102 })
103 }
104 DelegationAdminCommand::StopRotation => {
105 let stopped = Self::stop_rotation().await?;
106 Ok(if stopped {
107 DelegationAdminResponse::RotationStopped
108 } else {
109 DelegationAdminResponse::RotationNotRunning
110 })
111 }
112 }
113 }
114
115 pub async fn start_rotation(interval_secs: u64) -> Result<bool, Error> {
116 AuthAccessApi::is_root(IcOps::msg_caller()).await?;
117
118 let cfg = ConfigOps::delegation_config().map_err(Error::from)?;
119 if !cfg.enabled {
120 return Err(Error::forbidden("delegation disabled"));
121 }
122
123 if interval_secs == 0 {
124 return Err(Error::invalid(
125 "rotation interval must be greater than zero",
126 ));
127 }
128
129 let template = rotation_template()?;
130 let template = Arc::new(template);
131 let interval = Duration::from_secs(interval_secs);
132
133 let started = DelegationWorkflow::start_rotation(
134 interval,
135 Arc::new({
136 let template = Arc::clone(&template);
137 move || {
138 let now_secs = IcOps::now_secs();
139 Ok(build_rotation_cert(template.as_ref(), now_secs))
140 }
141 }),
142 Arc::new(|proof| {
143 DelegationStateOps::set_proof(proof);
144 Ok(())
145 }),
146 );
147
148 Ok(started)
149 }
150
151 pub async fn stop_rotation() -> Result<bool, Error> {
152 AuthAccessApi::is_root(IcOps::msg_caller()).await?;
153 Ok(DelegationWorkflow::stop_rotation())
154 }
155}
156
157struct DelegationRotationTemplate {
162 v: u16,
163 signer_pid: Principal,
164 audiences: Vec<String>,
165 scopes: Vec<String>,
166 ttl_secs: u64,
167}
168
169fn rotation_template() -> Result<DelegationRotationTemplate, Error> {
170 let proof =
171 DelegationStateOps::proof().ok_or_else(|| Error::not_found("delegation proof not set"))?;
172 let cert = proof.cert;
173
174 if cert.expires_at <= cert.issued_at {
175 return Err(Error::invalid(
176 "delegation cert expires_at must be greater than issued_at",
177 ));
178 }
179
180 let ttl_secs = cert.expires_at - cert.issued_at;
181
182 Ok(DelegationRotationTemplate {
183 v: cert.v,
184 signer_pid: cert.signer_pid,
185 audiences: cert.audiences,
186 scopes: cert.scopes,
187 ttl_secs,
188 })
189}
190
191fn build_rotation_cert(template: &DelegationRotationTemplate, now_secs: u64) -> DelegationCert {
192 DelegationCert {
193 v: template.v,
194 signer_pid: template.signer_pid,
195 audiences: template.audiences.clone(),
196 scopes: template.scopes.clone(),
197 issued_at: now_secs,
198 expires_at: now_secs.saturating_add(template.ttl_secs),
199 }
200}