mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-28 00:58:59 +00:00
fix(notify): accept case-insensitive filter rule names (#2990)
This commit is contained in:
@@ -161,6 +161,39 @@ mod integration_tests {
|
||||
assert!(targets.is_empty(), "Files not in images/ should not match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_capitalized_filter_names_xml() {
|
||||
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<NotificationConfiguration>
|
||||
<QueueConfiguration>
|
||||
<Id>test-queue</Id>
|
||||
<Queue>arn:rustfs:sqs:ap-northeast-1:primary:webhook</Queue>
|
||||
<Event>s3:ObjectCreated:*</Event>
|
||||
<Filter>
|
||||
<S3Key>
|
||||
<FilterRule>
|
||||
<Name>Prefix</Name>
|
||||
<Value>uploads/</Value>
|
||||
</FilterRule>
|
||||
<FilterRule>
|
||||
<Name>Suffix</Name>
|
||||
<Value>.csv</Value>
|
||||
</FilterRule>
|
||||
</S3Key>
|
||||
</Filter>
|
||||
</QueueConfiguration>
|
||||
</NotificationConfiguration>"#;
|
||||
|
||||
let current_region = "ap-northeast-1";
|
||||
let arn_list = vec!["arn:rustfs:sqs:ap-northeast-1:primary:webhook".to_string()];
|
||||
|
||||
let config = BucketNotificationConfig::from_xml(Cursor::new(xml.as_bytes()), current_region, &arn_list).unwrap();
|
||||
let rules_map = config.get_rules_map();
|
||||
|
||||
let targets = rules_map.match_rules(EventName::ObjectCreatedPut, "uploads/report.csv");
|
||||
assert!(!targets.is_empty(), "capitalized filter names should still build prefix/suffix rules");
|
||||
}
|
||||
|
||||
/// Test suffix only filter
|
||||
#[test]
|
||||
fn test_suffix_only_filter_xml() {
|
||||
|
||||
@@ -66,7 +66,7 @@ pub struct FilterRule {
|
||||
|
||||
impl FilterRule {
|
||||
fn validate(&self) -> Result<(), ParseConfigError> {
|
||||
if self.name != "prefix" && self.name != "suffix" {
|
||||
if !self.name.eq_ignore_ascii_case("prefix") && !self.name.eq_ignore_ascii_case("suffix") {
|
||||
return Err(ParseConfigError::InvalidFilterName(self.name.clone()));
|
||||
}
|
||||
// ValidateFilterRuleValue from Go:
|
||||
@@ -98,12 +98,12 @@ impl S3KeyFilter {
|
||||
let mut has_suffix = false;
|
||||
for rule in &self.filter_rule_list {
|
||||
rule.validate()?;
|
||||
if rule.name == "prefix" {
|
||||
if rule.name.eq_ignore_ascii_case("prefix") {
|
||||
if has_prefix {
|
||||
return Err(ParseConfigError::DuplicatePrefixFilter);
|
||||
}
|
||||
has_prefix = true;
|
||||
} else if rule.name == "suffix" {
|
||||
} else if rule.name.eq_ignore_ascii_case("suffix") {
|
||||
if has_suffix {
|
||||
return Err(ParseConfigError::DuplicateSuffixFilter);
|
||||
}
|
||||
@@ -126,9 +126,9 @@ impl S3KeyFilter {
|
||||
let mut suffix_val: Option<&str> = None;
|
||||
|
||||
for rule in &self.filter_rule_list {
|
||||
if rule.name == "prefix" {
|
||||
if rule.name.eq_ignore_ascii_case("prefix") {
|
||||
prefix_val = Some(&rule.value);
|
||||
} else if rule.name == "suffix" {
|
||||
} else if rule.name.eq_ignore_ascii_case("suffix") {
|
||||
suffix_val = Some(&rule.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,22 +145,18 @@ fn validate_notification_filter_rules(
|
||||
return Err(s3_error!(InvalidArgument, "{}", invalid_filter_value_message(&cfg_scope, value)));
|
||||
}
|
||||
|
||||
match name.as_str() {
|
||||
"prefix" => {
|
||||
if has_prefix {
|
||||
return Err(s3_error!(InvalidArgument, "duplicate notification filter name 'prefix' ({cfg_scope})"));
|
||||
}
|
||||
has_prefix = true;
|
||||
if name.as_str().eq_ignore_ascii_case("prefix") {
|
||||
if has_prefix {
|
||||
return Err(s3_error!(InvalidArgument, "duplicate notification filter name 'prefix' ({cfg_scope})"));
|
||||
}
|
||||
"suffix" => {
|
||||
if has_suffix {
|
||||
return Err(s3_error!(InvalidArgument, "duplicate notification filter name 'suffix' ({cfg_scope})"));
|
||||
}
|
||||
has_suffix = true;
|
||||
}
|
||||
other => {
|
||||
return Err(s3_error!(InvalidArgument, "{}", invalid_filter_name_message(&cfg_scope, other)));
|
||||
has_prefix = true;
|
||||
} else if name.as_str().eq_ignore_ascii_case("suffix") {
|
||||
if has_suffix {
|
||||
return Err(s3_error!(InvalidArgument, "duplicate notification filter name 'suffix' ({cfg_scope})"));
|
||||
}
|
||||
has_suffix = true;
|
||||
} else {
|
||||
return Err(s3_error!(InvalidArgument, "{}", invalid_filter_name_message(&cfg_scope, name.as_str())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3047,7 +3043,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn validate_notification_configuration_filters_rejects_invalid_filter_name() {
|
||||
let raw_name = "Prefix".repeat(100);
|
||||
let raw_name = "unsupported".repeat(100);
|
||||
let cfg = NotificationConfiguration {
|
||||
queue_configurations: Some(vec![QueueConfiguration {
|
||||
id: Some("q1".to_string()),
|
||||
@@ -3072,6 +3068,34 @@ mod tests {
|
||||
assert!(!msg.contains(&raw_name), "error message should not echo full raw filter name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_notification_configuration_filters_accepts_case_insensitive_filter_names() {
|
||||
let cfg = NotificationConfiguration {
|
||||
queue_configurations: Some(vec![QueueConfiguration {
|
||||
id: Some("q1".to_string()),
|
||||
queue_arn: "arn:rustfs:sqs:us-east-1:1:webhook".to_string(),
|
||||
events: vec!["s3:ObjectCreated:*".to_string().into()],
|
||||
filter: Some(NotificationConfigurationFilter {
|
||||
key: Some(S3KeyFilter {
|
||||
filter_rules: Some(vec![
|
||||
FilterRule {
|
||||
name: Some(FilterRuleName::from("Prefix".to_string())),
|
||||
value: Some("uploads/".to_string()),
|
||||
},
|
||||
FilterRule {
|
||||
name: Some(FilterRuleName::from("Suffix".to_string())),
|
||||
value: Some(".csv".to_string()),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
validate_notification_configuration_filters(&cfg).expect("capitalized filter names should be accepted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_notification_configuration_filters_rejects_duplicate_prefix_rules() {
|
||||
let cfg = NotificationConfiguration {
|
||||
@@ -3229,7 +3253,7 @@ mod tests {
|
||||
filter: Some(NotificationConfigurationFilter {
|
||||
key: Some(S3KeyFilter {
|
||||
filter_rules: Some(vec![FilterRule {
|
||||
name: Some(FilterRuleName::from("Prefix".to_string())),
|
||||
name: Some(FilterRuleName::from("unsupported".to_string())),
|
||||
value: Some("uploads/".to_string()),
|
||||
}]),
|
||||
}),
|
||||
|
||||
@@ -624,10 +624,10 @@ pub(crate) fn extract_prefix_suffix(filter: Option<&NotificationConfigurationFil
|
||||
if let Some(rules) = &filter_rules.filter_rules {
|
||||
for rule in rules {
|
||||
if let (Some(name), Some(value)) = (rule.name.as_ref(), rule.value.as_ref()) {
|
||||
match name.as_str() {
|
||||
"prefix" => prefix = value.clone(),
|
||||
"suffix" => suffix = value.clone(),
|
||||
_ => {}
|
||||
if name.as_str().eq_ignore_ascii_case("prefix") {
|
||||
prefix = value.clone();
|
||||
} else if name.as_str().eq_ignore_ascii_case("suffix") {
|
||||
suffix = value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,12 @@ mod tests {
|
||||
};
|
||||
use rustfs_zip::CompressionFormat;
|
||||
use s3s::dto::{
|
||||
CORSConfiguration, CORSRule, DefaultRetention, DeleteObjectTaggingInput, Delimiter, GetBucketAclInput, GetObjectAclInput,
|
||||
GetObjectLegalHoldInput, GetObjectRetentionInput, GetObjectTaggingInput, LambdaFunctionConfiguration,
|
||||
ObjectLockConfiguration, ObjectLockEnabled, ObjectLockLegalHold, ObjectLockLegalHoldStatus, ObjectLockRetention,
|
||||
ObjectLockRetentionMode, ObjectLockRule, PutBucketAclInput, PutObjectAclInput, PutObjectLegalHoldInput,
|
||||
PutObjectLockConfigurationInput, PutObjectRetentionInput, PutObjectTaggingInput, QueueConfiguration, Tag, Tagging,
|
||||
TopicConfiguration,
|
||||
CORSConfiguration, CORSRule, DefaultRetention, DeleteObjectTaggingInput, Delimiter, FilterRule, FilterRuleName,
|
||||
GetBucketAclInput, GetObjectAclInput, GetObjectLegalHoldInput, GetObjectRetentionInput, GetObjectTaggingInput,
|
||||
LambdaFunctionConfiguration, NotificationConfigurationFilter, ObjectLockConfiguration, ObjectLockEnabled,
|
||||
ObjectLockLegalHold, ObjectLockLegalHoldStatus, ObjectLockRetention, ObjectLockRetentionMode, ObjectLockRule,
|
||||
PutBucketAclInput, PutObjectAclInput, PutObjectLegalHoldInput, PutObjectLockConfigurationInput, PutObjectRetentionInput,
|
||||
PutObjectTaggingInput, QueueConfiguration, S3KeyFilter, Tag, Tagging, TopicConfiguration,
|
||||
};
|
||||
use s3s::{S3, S3Error, S3ErrorCode, S3Request, s3_error};
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
@@ -1734,6 +1734,47 @@ mod tests {
|
||||
assert_eq!(event_rules.len(), 1, "Should add one rule");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_queue_configurations_accepts_capitalized_filter_names() {
|
||||
use rustfs_targets::arn::{ARN, TargetIDError};
|
||||
|
||||
let mut event_rules = Vec::new();
|
||||
let valid_arn = "arn:rustfs:sqs:us-east-1:1:webhook";
|
||||
|
||||
let result = process_queue_configurations(
|
||||
&mut event_rules,
|
||||
Some(vec![QueueConfiguration {
|
||||
events: vec!["s3:ObjectCreated:*".to_string().into()],
|
||||
queue_arn: valid_arn.to_string(),
|
||||
filter: Some(NotificationConfigurationFilter {
|
||||
key: Some(S3KeyFilter {
|
||||
filter_rules: Some(vec![
|
||||
FilterRule {
|
||||
name: Some(FilterRuleName::from("Prefix".to_string())),
|
||||
value: Some("uploads/".to_string()),
|
||||
},
|
||||
FilterRule {
|
||||
name: Some(FilterRuleName::from("Suffix".to_string())),
|
||||
value: Some(".csv".to_string()),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
id: None,
|
||||
}]),
|
||||
|arn_str| {
|
||||
ARN::parse(arn_str)
|
||||
.map(|arn| arn.target_id)
|
||||
.map_err(|e| TargetIDError::InvalidFormat(e.to_string()))
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "capitalized filter names should be compatible");
|
||||
assert_eq!(event_rules.len(), 1, "Should add one rule");
|
||||
assert_eq!(event_rules[0].1, "uploads/");
|
||||
assert_eq!(event_rules[0].2, ".csv");
|
||||
}
|
||||
|
||||
// --- Object tag conditions for bucket policy (s3:ExistingObjectTag) ---
|
||||
|
||||
/// Verifies that object tags are formatted as ExistingObjectTag/<key> condition keys
|
||||
|
||||
Reference in New Issue
Block a user