@@ -101,26 +101,32 @@ pub struct ValidateApiKeyRequest {
101101#[ derive( Debug , Deserialize ) ]
102102pub struct ListApiKeysQuery {
103103 #[ serde( default , deserialize_with = "deserialize_api_key_privilege" ) ]
104- priviledge : Option < DefaultPrivilege > ,
104+ priviledge : Option < Vec < DefaultPrivilege > > ,
105105}
106106
107107fn deserialize_api_key_privilege < ' de , D > (
108108 deserializer : D ,
109- ) -> Result < Option < DefaultPrivilege > , D :: Error >
109+ ) -> Result < Option < Vec < DefaultPrivilege > > , D :: Error >
110110where
111111 D : Deserializer < ' de > ,
112112{
113113 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- ) ) ,
114+ . map ( |privileges| {
115+ privileges
116+ . split ( ',' )
117+ . map ( str:: trim)
118+ . map ( |privilege| match privilege {
119+ "reader" => Ok ( DefaultPrivilege :: Reader { resource : None } ) ,
120+ "ingestor" => Ok ( DefaultPrivilege :: Ingestor { resource : None } ) ,
121+ "admin" => Ok ( DefaultPrivilege :: Admin ) ,
122+ "writer" => Ok ( DefaultPrivilege :: Writer { resource : None } ) ,
123+ "editor" => Ok ( DefaultPrivilege :: Editor ) ,
124+ _ => Err ( de:: Error :: unknown_variant (
125+ privilege,
126+ & [ "reader" , "ingestor" , "admin" , "writer" , "editor" ] ,
127+ ) ) ,
128+ } )
129+ . collect ( )
124130 } )
125131 . transpose ( )
126132}
@@ -320,7 +326,8 @@ pub async fn delete_api_key(
320326/// GET /api/prism/v1/apikeys
321327///
322328/// List all API keys (masked). Only admins can list keys. Optionally filter by
323- /// role privilege with `?priviledge=reader|ingestor|admin|writer|editor`.
329+ /// one or more comma-separated role privileges, such as
330+ /// `?priviledge=reader,editor,admin`.
324331pub async fn list_api_keys (
325332 req : HttpRequest ,
326333 web:: Query ( query) : web:: Query < ListApiKeysQuery > ,
@@ -331,12 +338,16 @@ pub async fn list_api_keys(
331338 let entries: Vec < ApiKeyListEntry > = collect_tenant_api_keys ( & tenant_id)
332339 . iter ( )
333340 . filter ( |user| {
334- query. priviledge . as_ref ( ) . is_none_or ( |privilege | {
341+ query. priviledge . as_ref ( ) . is_none_or ( |privileges | {
335342 user. roles . iter ( ) . any ( |role_name| {
336343 tenant_roles
337344 . get ( tenant)
338345 . and_then ( |roles| roles. get ( role_name) )
339- . is_some_and ( |role| role_has_privilege ( role, privilege) )
346+ . is_some_and ( |role| {
347+ privileges
348+ . iter ( )
349+ . any ( |privilege| role_has_privilege ( role, privilege) )
350+ } )
340351 } )
341352 } )
342353 } )
@@ -382,3 +393,62 @@ pub async fn get_api_key(
382393 let response = ApiKeyResponse :: try_from ( & user) ?;
383394 Ok ( HttpResponse :: Ok ( ) . json ( response) )
384395}
396+
397+ #[ cfg( test) ]
398+ mod tests {
399+ use super :: * ;
400+
401+ #[ test]
402+ fn deserializes_single_api_key_privilege ( ) {
403+ let query: ListApiKeysQuery =
404+ serde_json:: from_value ( serde_json:: json!( { "priviledge" : "reader" } ) ) . unwrap ( ) ;
405+ let privileges = query. priviledge . unwrap ( ) ;
406+
407+ assert_eq ! ( privileges. len( ) , 1 ) ;
408+ assert ! ( matches!(
409+ privileges. first( ) ,
410+ Some ( DefaultPrivilege :: Reader { .. } )
411+ ) ) ;
412+ }
413+
414+ #[ test]
415+ fn deserializes_comma_separated_api_key_privileges ( ) {
416+ let query =
417+ web:: Query :: < ListApiKeysQuery > :: from_query ( "priviledge=reader%2Ceditor%2Cadmin" )
418+ . unwrap ( )
419+ . into_inner ( ) ;
420+ let privileges = query. priviledge . unwrap ( ) ;
421+
422+ assert_eq ! ( privileges. len( ) , 3 ) ;
423+ assert ! ( matches!(
424+ privileges. first( ) ,
425+ Some ( DefaultPrivilege :: Reader { .. } )
426+ ) ) ;
427+ assert ! ( matches!( privileges. get( 1 ) , Some ( DefaultPrivilege :: Editor ) ) ) ;
428+ assert ! ( matches!( privileges. get( 2 ) , Some ( DefaultPrivilege :: Admin ) ) ) ;
429+ }
430+
431+ #[ test]
432+ fn allows_spaces_around_api_key_privileges ( ) {
433+ let query: ListApiKeysQuery = serde_json:: from_value ( serde_json:: json!( {
434+ "priviledge" : "reader, writer"
435+ } ) )
436+ . unwrap ( ) ;
437+ let privileges = query. priviledge . unwrap ( ) ;
438+
439+ assert_eq ! ( privileges. len( ) , 2 ) ;
440+ assert ! ( matches!(
441+ privileges. get( 1 ) ,
442+ Some ( DefaultPrivilege :: Writer { .. } )
443+ ) ) ;
444+ }
445+
446+ #[ test]
447+ fn rejects_unknown_api_key_privilege ( ) {
448+ let query = serde_json:: from_value :: < ListApiKeysQuery > ( serde_json:: json!( {
449+ "priviledge" : "reader,owner"
450+ } ) ) ;
451+
452+ assert ! ( query. is_err( ) ) ;
453+ }
454+ }
0 commit comments