Skip to content

Commit bbbb79e

Browse files
fix: allow multiple priviledges in list apikey query param (#1839)
current: server allows one priviledge `?priviledge=reader` and server returns api keys where role has reader priviledge change: now server can allow multiple priviledges `?priviledge=reader,writer,admin,editor` server returns all api keys where role has these priviledges
1 parent ea8e0ea commit bbbb79e

1 file changed

Lines changed: 85 additions & 15 deletions

File tree

‎src/handlers/http/apikeys.rs‎

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,26 +101,32 @@ pub struct ValidateApiKeyRequest {
101101
#[derive(Debug, Deserialize)]
102102
pub struct ListApiKeysQuery {
103103
#[serde(default, deserialize_with = "deserialize_api_key_privilege")]
104-
priviledge: Option<DefaultPrivilege>,
104+
priviledge: Option<Vec<DefaultPrivilege>>,
105105
}
106106

107107
fn deserialize_api_key_privilege<'de, D>(
108108
deserializer: D,
109-
) -> Result<Option<DefaultPrivilege>, D::Error>
109+
) -> Result<Option<Vec<DefaultPrivilege>>, D::Error>
110110
where
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`.
324331
pub 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

Comments
 (0)