1use std::sync::Arc;
2
3use chrono::{DateTime, Utc};
4
5use garage_table::*;
6use garage_util::time::now_msec;
7
8use garage_model::admin_token_table::*;
9use garage_model::garage::Garage;
10
11use crate::api::*;
12use crate::error::*;
13use crate::{Admin, RequestHandler};
14
15impl RequestHandler for ListAdminTokensRequest {
16 type Response = ListAdminTokensResponse;
17
18 async fn handle(
19 self,
20 garage: &Arc<Garage>,
21 _admin: &Admin,
22 ) -> Result<ListAdminTokensResponse, Error> {
23 let now = now_msec();
24
25 let mut res = garage
26 .admin_token_table
27 .get_range(
28 &EmptyKey,
29 None,
30 Some(KeyFilter::Deleted(DeletedFilter::NotDeleted)),
31 10000,
32 EnumerationOrder::Forward,
33 )
34 .await?
35 .iter()
36 .map(|t| admin_token_info_results(t, now))
37 .collect::<Vec<_>>();
38
39 if garage.config.admin.metrics_token.is_some() {
40 res.insert(
41 0,
42 GetAdminTokenInfoResponse {
43 id: None,
44 created: None,
45 name: "metrics_token (from daemon configuration)".into(),
46 expiration: None,
47 expired: false,
48 scope: vec!["Metrics".into()],
49 },
50 );
51 }
52
53 if garage.config.admin.admin_token.is_some() {
54 res.insert(
55 0,
56 GetAdminTokenInfoResponse {
57 id: None,
58 created: None,
59 name: "admin_token (from daemon configuration)".into(),
60 expiration: None,
61 expired: false,
62 scope: vec!["*".into()],
63 },
64 );
65 }
66
67 Ok(ListAdminTokensResponse(res))
68 }
69}
70
71impl RequestHandler for GetAdminTokenInfoRequest {
72 type Response = GetAdminTokenInfoResponse;
73
74 async fn handle(
75 self,
76 garage: &Arc<Garage>,
77 _admin: &Admin,
78 ) -> Result<GetAdminTokenInfoResponse, Error> {
79 let token = match (self.id, self.search) {
80 (Some(id), None) => get_existing_admin_token(garage, &id).await?,
81 (None, Some(search)) => {
82 let candidates = garage
83 .admin_token_table
84 .get_range(
85 &EmptyKey,
86 None,
87 Some(KeyFilter::MatchesAndNotDeleted(search.to_string())),
88 10,
89 EnumerationOrder::Forward,
90 )
91 .await?
92 .into_iter()
93 .collect::<Vec<_>>();
94 if candidates.len() != 1 {
95 return Err(Error::bad_request(format!(
96 "{} matching admin tokens",
97 candidates.len()
98 )));
99 }
100 candidates.into_iter().next().unwrap()
101 }
102 _ => {
103 return Err(Error::bad_request(
104 "Either id or search must be provided (but not both)",
105 ));
106 }
107 };
108
109 Ok(admin_token_info_results(&token, now_msec()))
110 }
111}
112
113impl RequestHandler for CreateAdminTokenRequest {
114 type Response = CreateAdminTokenResponse;
115
116 async fn handle(
117 self,
118 garage: &Arc<Garage>,
119 _admin: &Admin,
120 ) -> Result<CreateAdminTokenResponse, Error> {
121 let (mut token, secret) = if self.0.name.is_some() {
122 AdminApiToken::new("")
123 } else {
124 AdminApiToken::new(&format!("token_{}", Utc::now().format("%Y%m%d_%H%M")))
125 };
126
127 apply_token_updates(&mut token, self.0)?;
128
129 garage.admin_token_table.insert(&token).await?;
130
131 Ok(CreateAdminTokenResponse {
132 secret_token: secret,
133 info: admin_token_info_results(&token, now_msec()),
134 })
135 }
136}
137
138impl RequestHandler for UpdateAdminTokenRequest {
139 type Response = UpdateAdminTokenResponse;
140
141 async fn handle(
142 self,
143 garage: &Arc<Garage>,
144 _admin: &Admin,
145 ) -> Result<UpdateAdminTokenResponse, Error> {
146 let mut token = get_existing_admin_token(&garage, &self.id).await?;
147
148 apply_token_updates(&mut token, self.body)?;
149
150 garage.admin_token_table.insert(&token).await?;
151
152 Ok(UpdateAdminTokenResponse(admin_token_info_results(
153 &token,
154 now_msec(),
155 )))
156 }
157}
158
159impl RequestHandler for DeleteAdminTokenRequest {
160 type Response = DeleteAdminTokenResponse;
161
162 async fn handle(
163 self,
164 garage: &Arc<Garage>,
165 _admin: &Admin,
166 ) -> Result<DeleteAdminTokenResponse, Error> {
167 let token = get_existing_admin_token(&garage, &self.id).await?;
168
169 garage
170 .admin_token_table
171 .insert(&AdminApiToken::delete(token.prefix))
172 .await?;
173
174 Ok(DeleteAdminTokenResponse)
175 }
176}
177
178impl RequestHandler for GetCurrentAdminTokenInfoRequest {
179 type Response = GetCurrentAdminTokenInfoResponse;
180
181 async fn handle(
182 self,
183 garage: &Arc<Garage>,
184 _admin: &Admin,
185 ) -> Result<GetCurrentAdminTokenInfoResponse, Error> {
186 let now = now_msec();
187
188 if garage
189 .config
190 .admin
191 .metrics_token
192 .as_ref()
193 .is_some_and(|s| s == &self.admin_token)
194 {
195 return Ok(GetCurrentAdminTokenInfoResponse(
196 GetAdminTokenInfoResponse {
197 id: None,
198 created: None,
199 name: "metrics_token (from daemon configuration)".into(),
200 expiration: None,
201 expired: false,
202 scope: vec!["Metrics".into()],
203 },
204 ));
205 }
206
207 if garage
208 .config
209 .admin
210 .admin_token
211 .as_ref()
212 .is_some_and(|s| s == &self.admin_token)
213 {
214 return Ok(GetCurrentAdminTokenInfoResponse(
215 GetAdminTokenInfoResponse {
216 id: None,
217 created: None,
218 name: "admin_token (from daemon configuration)".into(),
219 expiration: None,
220 expired: false,
221 scope: vec!["*".into()],
222 },
223 ));
224 }
225
226 let (prefix, _) = self.admin_token.split_once('.').unwrap();
227 let token = get_existing_admin_token(&garage, &prefix.to_string()).await?;
228
229 Ok(GetCurrentAdminTokenInfoResponse(admin_token_info_results(
230 &token, now,
231 )))
232 }
233}
234
235fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInfoResponse {
238 let params = token.params().unwrap();
239
240 GetAdminTokenInfoResponse {
241 id: Some(token.prefix.clone()),
242 created: Some(
243 DateTime::from_timestamp_millis(params.created as i64)
244 .expect("invalid timestamp stored in db"),
245 ),
246 name: params.name.get().to_string(),
247 expiration: params.expiration.get().map(|x| {
248 DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
249 }),
250 expired: params.is_expired(now),
251 scope: params.scope.get().0.clone(),
252 }
253}
254
255async fn get_existing_admin_token(garage: &Garage, id: &String) -> Result<AdminApiToken, Error> {
256 garage
257 .admin_token_table
258 .get(&EmptyKey, id)
259 .await?
260 .filter(|k| !k.state.is_deleted())
261 .ok_or_else(|| Error::NoSuchAdminToken(id.to_string()))
262}
263
264fn apply_token_updates(
265 token: &mut AdminApiToken,
266 updates: UpdateAdminTokenRequestBody,
267) -> Result<(), Error> {
268 if updates.never_expires && updates.expiration.is_some() {
269 return Err(Error::bad_request(
270 "cannot specify `expiration` and `never_expires`",
271 ));
272 }
273
274 let params = token.params_mut().unwrap();
275
276 if let Some(name) = updates.name {
277 params.name.update(name);
278 }
279 if let Some(expiration) = updates.expiration {
280 params
281 .expiration
282 .update(Some(expiration.timestamp_millis() as u64));
283 }
284 if updates.never_expires {
285 params.expiration.update(None);
286 }
287 if let Some(scope) = updates.scope {
288 params.scope.update(AdminApiTokenScope(scope));
289 }
290
291 Ok(())
292}