Skip to content

Commit d1f478b

Browse files
add query param to list api keys (#1833)
support for `?priviledge=reader|ingestor|admin|writer|editor` eg. GET /api/prism/v1/apikeys?priviledge=reader returns all api keys with reader priviledge
1 parent 32979cb commit d1f478b

1 file changed

Lines changed: 53 additions & 3 deletions

File tree

‎src/handlers/http/apikeys.rs‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashSet;
22

33
use actix_web::{HttpRequest, HttpResponse, Responder, web};
44
use chrono::{DateTime, Utc};
5-
use serde::{Deserialize, Serialize};
5+
use serde::{Deserialize, Deserializer, Serialize, de};
66
use ulid::Ulid;
77

88
use crate::{
@@ -15,6 +15,7 @@ use crate::{
1515
rbac::{
1616
Users,
1717
map::{roles, users},
18+
role::model::{DefaultPrivilege, Role},
1819
user::{User, UserType},
1920
},
2021
utils::{get_user_and_tenant_from_request, is_admin},
@@ -97,6 +98,39 @@ pub struct ValidateApiKeyRequest {
9798
api_key: String,
9899
}
99100

101+
#[derive(Debug, Deserialize)]
102+
pub struct ListApiKeysQuery {
103+
#[serde(default, deserialize_with = "deserialize_api_key_privilege")]
104+
priviledge: Option<DefaultPrivilege>,
105+
}
106+
107+
fn deserialize_api_key_privilege<'de, D>(
108+
deserializer: D,
109+
) -> Result<Option<DefaultPrivilege>, D::Error>
110+
where
111+
D: Deserializer<'de>,
112+
{
113+
Option::<String>::deserialize(deserializer)?
114+
.map(|privilege| match privilege.as_str() {
115+
"reader" => Ok(DefaultPrivilege::Reader { resource: None }),
116+
"ingestor" => Ok(DefaultPrivilege::Ingestor { resource: None }),
117+
"admin" => Ok(DefaultPrivilege::Admin),
118+
"writer" => Ok(DefaultPrivilege::Writer { resource: None }),
119+
"editor" => Ok(DefaultPrivilege::Editor),
120+
_ => Err(de::Error::unknown_variant(
121+
&privilege,
122+
&["reader", "ingestor", "admin", "writer", "editor"],
123+
)),
124+
})
125+
.transpose()
126+
}
127+
128+
fn role_has_privilege(role: &Role, privilege: &DefaultPrivilege) -> bool {
129+
role.privileges()
130+
.iter()
131+
.any(|candidate| std::mem::discriminant(candidate) == std::mem::discriminant(privilege))
132+
}
133+
100134
impl ApiKeyListEntry {
101135
fn from_user(user: &User) -> Option<Self> {
102136
let api_key = user.as_api_key()?;
@@ -285,11 +319,27 @@ pub async fn delete_api_key(
285319

286320
/// GET /api/prism/v1/apikeys
287321
///
288-
/// List all API keys (masked). Only admins can list keys.
289-
pub async fn list_api_keys(req: HttpRequest) -> Result<impl Responder, ApiKeyError> {
322+
/// List all API keys (masked). Only admins can list keys. Optionally filter by
323+
/// role privilege with `?priviledge=reader|ingestor|admin|writer|editor`.
324+
pub async fn list_api_keys(
325+
req: HttpRequest,
326+
web::Query(query): web::Query<ListApiKeysQuery>,
327+
) -> Result<impl Responder, ApiKeyError> {
290328
let (_, tenant_id) = verify_admin(&req)?;
329+
let tenant = tenant_or_default(&tenant_id);
330+
let tenant_roles = roles();
291331
let entries: Vec<ApiKeyListEntry> = collect_tenant_api_keys(&tenant_id)
292332
.iter()
333+
.filter(|user| {
334+
query.priviledge.as_ref().is_none_or(|privilege| {
335+
user.roles.iter().any(|role_name| {
336+
tenant_roles
337+
.get(tenant)
338+
.and_then(|roles| roles.get(role_name))
339+
.is_some_and(|role| role_has_privilege(role, privilege))
340+
})
341+
})
342+
})
293343
.filter_map(ApiKeyListEntry::from_user)
294344
.collect();
295345
Ok(HttpResponse::Ok().json(entries))

0 commit comments

Comments
 (0)