mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 09:18:28 +00:00
test: cover delete-group percent decoding (#2373)
This commit is contained in:
@@ -207,30 +207,11 @@ impl Operation for DeleteGroup {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let group_raw = params
|
let group = decode_delete_group_name(¶ms)?;
|
||||||
.get("group")
|
|
||||||
.ok_or_else(|| s3_error!(InvalidArgument, "missing group name in request"))?
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
// Path segments stay percent-encoded in `req.uri.path()` / matchit; IAM uses decoded names (same as GET query).
|
|
||||||
let group_decoded = percent_decode_str(group_raw)
|
|
||||||
.decode_utf8()
|
|
||||||
.map_err(|_| s3_error!(InvalidArgument, "invalid group name encoding"))?;
|
|
||||||
let group = group_decoded.trim();
|
|
||||||
|
|
||||||
// Validate the group name format
|
|
||||||
if group.is_empty() || group.len() > 256 {
|
|
||||||
return Err(s3_error!(InvalidArgument, "invalid group name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check the group name
|
|
||||||
if group.contains(['/', '\\', '\0']) {
|
|
||||||
return Err(s3_error!(InvalidArgument, "group name contains invalid characters"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
|
let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
|
||||||
|
|
||||||
let updated_at = iam_store.remove_users_from_group(group, vec![]).await.map_err(|e| {
|
let updated_at = iam_store.remove_users_from_group(&group, vec![]).await.map_err(|e| {
|
||||||
warn!("delete group failed, e: {:?}", e);
|
warn!("delete group failed, e: {:?}", e);
|
||||||
match e {
|
match e {
|
||||||
rustfs_iam::error::Error::GroupNotEmpty => {
|
rustfs_iam::error::Error::GroupNotEmpty => {
|
||||||
@@ -276,6 +257,33 @@ impl Operation for DeleteGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_delete_group_name<'a>(params: &'a Params<'_, '_>) -> S3Result<std::borrow::Cow<'a, str>> {
|
||||||
|
let group_raw = params
|
||||||
|
.get("group")
|
||||||
|
.ok_or_else(|| s3_error!(InvalidArgument, "missing group name in request"))?
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Path segments stay percent-encoded in `req.uri.path()` / matchit; IAM uses decoded names (same as GET query).
|
||||||
|
let decoded = percent_decode_str(group_raw)
|
||||||
|
.decode_utf8()
|
||||||
|
.map_err(|_| s3_error!(InvalidArgument, "invalid group name encoding"))?;
|
||||||
|
let group = decoded.trim();
|
||||||
|
|
||||||
|
if group.is_empty() || group.len() > 256 {
|
||||||
|
return Err(s3_error!(InvalidArgument, "invalid group name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.contains(['/', '\\', '\0']) {
|
||||||
|
return Err(s3_error!(InvalidArgument, "group name contains invalid characters"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.len() == decoded.len() {
|
||||||
|
Ok(decoded)
|
||||||
|
} else {
|
||||||
|
Ok(std::borrow::Cow::Owned(group.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SetGroupStatus {}
|
pub struct SetGroupStatus {}
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Operation for SetGroupStatus {
|
impl Operation for SetGroupStatus {
|
||||||
@@ -484,3 +492,62 @@ impl Operation for UpdateGroupMembers {
|
|||||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use matchit::Router;
|
||||||
|
|
||||||
|
fn with_delete_group_params<T>(path: &str, f: impl FnOnce(&Params<'_, '_>) -> T) -> T {
|
||||||
|
let mut router = Router::new();
|
||||||
|
router
|
||||||
|
.insert("/rustfs/admin/v3/group/{group}", ())
|
||||||
|
.expect("route should insert");
|
||||||
|
|
||||||
|
let matched = router.at(path).expect("route should match");
|
||||||
|
f(&matched.params)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_delete_group_name_percent_decodes_path_segment() {
|
||||||
|
let group = with_delete_group_params("/rustfs/admin/v3/group/dev%2Bops%20team", |params| {
|
||||||
|
decode_delete_group_name(params).map(|group| group.into_owned())
|
||||||
|
})
|
||||||
|
.expect("encoded group name should decode");
|
||||||
|
|
||||||
|
assert_eq!(group, "dev+ops team");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_delete_group_name_rejects_invalid_utf8() {
|
||||||
|
let err = with_delete_group_params("/rustfs/admin/v3/group/%FF", |params| {
|
||||||
|
decode_delete_group_name(params).map(|group| group.into_owned())
|
||||||
|
})
|
||||||
|
.expect_err("invalid utf-8 should fail");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidArgument);
|
||||||
|
assert_eq!(err.message(), Some("invalid group name encoding"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_delete_group_name_rejects_blank_name_after_decoding() {
|
||||||
|
let err = with_delete_group_params("/rustfs/admin/v3/group/%20", |params| {
|
||||||
|
decode_delete_group_name(params).map(|group| group.into_owned())
|
||||||
|
})
|
||||||
|
.expect_err("blank group should fail");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidArgument);
|
||||||
|
assert_eq!(err.message(), Some("invalid group name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_delete_group_name_rejects_path_separator_after_decoding() {
|
||||||
|
let err = with_delete_group_params("/rustfs/admin/v3/group/team%2Fops", |params| {
|
||||||
|
decode_delete_group_name(params).map(|group| group.into_owned())
|
||||||
|
})
|
||||||
|
.expect_err("decoded slash should fail");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidArgument);
|
||||||
|
assert_eq!(err.message(), Some("group name contains invalid characters"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user