api: correct according to review

This commit is contained in:
Xavier Stouder
2025-07-04 21:36:34 +02:00
parent 9a31b9c077
commit b4f6ab963c
4 changed files with 55 additions and 147 deletions
+27 -72
View File
@@ -816,6 +816,30 @@
}
}
},
"/v2/GetCurrentAdminTokenInfo": {
"get": {
"tags": [
"Admin API token"
],
"description": "\nReturn information about the calling admin API token.\n ",
"operationId": "GetCurrentAdminTokenInfo",
"responses": {
"200": {
"description": "Information about the admin token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetCurrentAdminTokenInfoResponse"
}
}
}
},
"500": {
"description": "Internal server error"
}
}
}
},
"/v2/GetKeyInfo": {
"get": {
"tags": [
@@ -1108,30 +1132,6 @@
}
}
},
"/v2/GetCurrentAdminTokenInfo": {
"get": {
"tags": [
"Admin API token"
],
"description": "\nReturn information about the calling admin API token.\n ",
"operationId": "GetCurrentAdminTokenInfo",
"responses": {
"200": {
"description": "Information about the admin token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetCurrentAdminTokenInfoResponse"
}
}
}
},
"500": {
"description": "Internal server error"
}
}
}
},
"/v2/LaunchRepairOperation": {
"post": {
"tags": [
@@ -2618,6 +2618,9 @@
}
}
},
"GetCurrentAdminTokenInfoResponse": {
"$ref": "#/components/schemas/GetAdminTokenInfoResponse"
},
"GetKeyInfoResponse": {
"type": "object",
"required": [
@@ -2832,54 +2835,6 @@
}
}
},
"GetCurrentAdminTokenInfoResponse": {
"type": "object",
"required": [
"name",
"expired",
"scope"
],
"properties": {
"created": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "Creation date"
},
"expiration": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "Expiration time and date, formatted according to RFC 3339"
},
"expired": {
"type": "boolean",
"description": "Whether this admin token is expired already"
},
"id": {
"type": [
"string",
"null"
],
"description": "Identifier of the admin token (which is also a prefix of the full bearer token)"
},
"name": {
"type": "string",
"description": "Name of the admin API token"
},
"scope": {
"type": "array",
"items": {
"type": "string"
},
"description": "Scope of the admin API token, a list of admin endpoint names (such as\n`GetClusterStatus`, etc), or the special value `*` to allow all\nadmin endpoints"
}
}
},
"KeyInfoBucketResponse": {
"type": "object",
"required": [
+24 -58
View File
@@ -192,14 +192,16 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.as_ref()
.is_some_and(|s| s == &self.admin_token)
{
return Ok(GetCurrentAdminTokenInfoResponse {
id: None,
created: None,
name: "metrics_token (from daemon configuration)".into(),
expiration: None,
expired: false,
scope: vec!["Metrics".into()],
});
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
id: None,
created: None,
name: "metrics_token (from daemon configuration)".into(),
expiration: None,
expired: false,
scope: vec!["Metrics".into()],
},
));
}
if garage
@@ -209,42 +211,24 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.as_ref()
.is_some_and(|s| s == &self.admin_token)
{
return Ok(GetCurrentAdminTokenInfoResponse {
id: None,
created: None,
name: "admin_token (from daemon configuration)".into(),
expiration: None,
expired: false,
scope: vec!["*".into()],
});
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
id: None,
created: None,
name: "admin_token (from daemon configuration)".into(),
expiration: None,
expired: false,
scope: vec!["*".into()],
},
));
}
let (prefix, _) = self.admin_token.split_once('.').unwrap();
let token = get_existing_admin_token(&garage, &prefix.to_string()).await?;
let candidates = garage
.admin_token_table
.get_range(
&EmptyKey,
None,
Some(KeyFilter::MatchesAndNotDeleted(
prefix.clone().parse().unwrap(),
)),
10,
EnumerationOrder::Forward,
)
.await?
.into_iter()
.collect::<Vec<_>>();
if candidates.len() != 1 {
return Err(Error::bad_request(format!(
"{} matching admin tokens",
candidates.len()
)));
}
Ok(my_admin_token_info_results(
&candidates.into_iter().next().unwrap(),
now,
))
Ok(GetCurrentAdminTokenInfoResponse(admin_token_info_results(
&token, now,
)))
}
}
@@ -306,21 +290,3 @@ fn apply_token_updates(
Ok(())
}
fn my_admin_token_info_results(token: &AdminApiToken, now: u64) -> GetCurrentAdminTokenInfoResponse {
let params = token.params().unwrap();
GetCurrentAdminTokenInfoResponse {
id: Some(token.prefix.clone()),
created: Some(
DateTime::from_timestamp_millis(params.created as i64)
.expect("invalid timestamp stored in db"),
),
name: params.name.get().to_string(),
expiration: params.expiration.get().map(|x| {
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
}),
expired: params.is_expired(now),
scope: params.scope.get().0.clone(),
}
}
+2 -16
View File
@@ -399,22 +399,8 @@ pub struct GetCurrentAdminTokenInfoRequest {
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct GetCurrentAdminTokenInfoResponse {
/// Identifier of the admin token (which is also a prefix of the full bearer token)
pub id: Option<String>,
/// Creation date
pub created: Option<DateTime<Utc>>,
/// Name of the admin API token
pub name: String,
/// Expiration time and date, formatted according to RFC 3339
pub expiration: Option<DateTime<Utc>>,
/// Whether this admin token is expired already
pub expired: bool,
/// Scope of the admin API token, a list of admin endpoint names (such as
/// `GetClusterStatus`, etc), or the special value `*` to allow all
/// admin endpoints
pub scope: Vec<String>,
}
pub struct GetCurrentAdminTokenInfoResponse(pub GetAdminTokenInfoResponse);
// **********************************************
// Layout operations
// **********************************************
+2 -1
View File
@@ -23,8 +23,8 @@ use garage_util::time::now_msec;
use garage_api_common::generic_server::*;
use garage_api_common::helpers::*;
use crate::api::*;
use crate::api::AdminApiRequest::GetCurrentAdminTokenInfo;
use crate::api::*;
use crate::error::*;
use crate::router_v0;
use crate::router_v1;
@@ -274,6 +274,7 @@ fn verify_authorization(
.get_local(&EmptyKey, &prefix.to_string())?
.and_then(|k| k.state.into_option())
.filter(|p| !p.is_expired(now_msec()))
// GetCurrentAdminTokenInfo endpoint must be accessible even if it is not in the token scopes
.filter(|p| p.has_scope(endpoint_name) || endpoint_name == "GetCurrentAdminTokenInfo")
.ok_or_else(|| Error::forbidden(invalid_msg))?
.token_hash