diff --git a/doc/api/garage-admin-v2.json b/doc/api/garage-admin-v2.json index 9f140081..853cc1f8 100644 --- a/doc/api/garage-admin-v2.json +++ b/doc/api/garage-admin-v2.json @@ -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": [ diff --git a/src/api/admin/admin_token.rs b/src/api/admin/admin_token.rs index a4fa17ea..0f9c66d2 100644 --- a/src/api/admin/admin_token.rs +++ b/src/api/admin/admin_token.rs @@ -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::>(); - 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(), - } -} diff --git a/src/api/admin/api.rs b/src/api/admin/api.rs index 1af3b7ba..5524e002 100644 --- a/src/api/admin/api.rs +++ b/src/api/admin/api.rs @@ -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, - /// Creation date - pub created: Option>, - /// Name of the admin API token - pub name: String, - /// Expiration time and date, formatted according to RFC 3339 - pub expiration: Option>, - /// 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, -} +pub struct GetCurrentAdminTokenInfoResponse(pub GetAdminTokenInfoResponse); + // ********************************************** // Layout operations // ********************************************** diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs index 9884500b..78d7b251 100644 --- a/src/api/admin/api_server.rs +++ b/src/api/admin/api_server.rs @@ -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