From 8ceccd14a7e4d046b7fecd6e7d76465d121094af Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Fri, 23 Oct 2026 11:57:23 +0530 Subject: [PATCH] add query param to list api keys support for `?priviledge=reader|ingestor|admin|writer|editor` eg. GET /api/prism/v1/apikeys?priviledge=reader returns all api keys with reader priviledge --- src/handlers/http/apikeys.rs | 56 ++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/handlers/http/apikeys.rs b/src/handlers/http/apikeys.rs index 7cf4fa99d..333831ee6 100644 --- a/src/handlers/http/apikeys.rs +++ b/src/handlers/http/apikeys.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use actix_web::{HttpRequest, HttpResponse, Responder, web}; use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, de}; use ulid::Ulid; use crate::{ @@ -15,6 +15,7 @@ use crate::{ rbac::{ Users, map::{roles, users}, + role::model::{DefaultPrivilege, Role}, user::{User, UserType}, }, utils::{get_user_and_tenant_from_request, is_admin}, @@ -97,6 +98,39 @@ pub struct ValidateApiKeyRequest { api_key: String, } +#[derive(Debug, Deserialize)] +pub struct ListApiKeysQuery { + #[serde(default, deserialize_with = "deserialize_api_key_privilege")] + priviledge: Option, +} + +fn deserialize_api_key_privilege<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Option::::deserialize(deserializer)? + .map(|privilege| match privilege.as_str() { + "reader" => Ok(DefaultPrivilege::Reader { resource: None }), + "ingestor" => Ok(DefaultPrivilege::Ingestor { resource: None }), + "admin" => Ok(DefaultPrivilege::Admin), + "writer" => Ok(DefaultPrivilege::Writer { resource: None }), + "editor" => Ok(DefaultPrivilege::Editor), + _ => Err(de::Error::unknown_variant( + &privilege, + &["reader", "ingestor", "admin", "writer", "editor"], + )), + }) + .transpose() +} + +fn role_has_privilege(role: &Role, privilege: &DefaultPrivilege) -> bool { + role.privileges() + .iter() + .any(|candidate| std::mem::discriminant(candidate) == std::mem::discriminant(privilege)) +} + impl ApiKeyListEntry { fn from_user(user: &User) -> Option { let api_key = user.as_api_key()?; @@ -285,11 +319,27 @@ pub async fn delete_api_key( /// GET /api/prism/v1/apikeys /// -/// List all API keys (masked). Only admins can list keys. -pub async fn list_api_keys(req: HttpRequest) -> Result { +/// List all API keys (masked). Only admins can list keys. Optionally filter by +/// role privilege with `?priviledge=reader|ingestor|admin|writer|editor`. +pub async fn list_api_keys( + req: HttpRequest, + web::Query(query): web::Query, +) -> Result { let (_, tenant_id) = verify_admin(&req)?; + let tenant = tenant_or_default(&tenant_id); + let tenant_roles = roles(); let entries: Vec = collect_tenant_api_keys(&tenant_id) .iter() + .filter(|user| { + query.priviledge.as_ref().is_none_or(|privilege| { + user.roles.iter().any(|role_name| { + tenant_roles + .get(tenant) + .and_then(|roles| roles.get(role_name)) + .is_some_and(|role| role_has_privilege(role, privilege)) + }) + }) + }) .filter_map(ApiKeyListEntry::from_user) .collect(); Ok(HttpResponse::Ok().json(entries))