1use crate::{Filter, Operator, Value};
4use std::fmt;
5
6const MAX_VALUE_NODES: usize = 10000;
8
9#[derive(Debug, Clone)]
17#[non_exhaustive]
18pub struct FilterValidator {
19 pub allowed_fields: Vec<String>,
21 pub denied_operators: Vec<Operator>,
23 pub max_depth: usize,
25}
26
27impl FilterValidator {
28 #[must_use]
48 pub fn new() -> Self {
49 Self {
50 allowed_fields: Vec::new(),
51 denied_operators: vec![crate::Operator::Regex],
52 max_depth: 5,
53 }
54 }
55
56 #[must_use]
70 pub const fn permissive() -> Self {
71 Self {
72 allowed_fields: Vec::new(),
73 denied_operators: Vec::new(),
74 max_depth: 5,
75 }
76 }
77
78 #[must_use]
83 pub fn allow_fields(mut self, fields: &[&str]) -> Self {
84 self.allowed_fields = fields.iter().map(|s| (*s).to_string()).collect();
85 self
86 }
87
88 #[must_use]
93 pub fn deny_operators(mut self, ops: &[Operator]) -> Self {
94 self.denied_operators = ops.to_vec();
95 self
96 }
97
98 #[must_use]
103 pub const fn max_depth(mut self, depth: usize) -> Self {
104 self.max_depth = depth;
105 self
106 }
107
108 pub fn validate(&self, filter: &Filter) -> Result<(), ValidationError> {
115 self.validate_with_depth(filter, 0)
116 }
117
118 fn validate_with_depth(&self, filter: &Filter, depth: usize) -> Result<(), ValidationError> {
120 if depth > self.max_depth {
122 return Err(ValidationError::NestingTooDeep {
123 max: self.max_depth,
124 actual: depth,
125 });
126 }
127
128 if !self.allowed_fields.is_empty() && !self.allowed_fields.contains(&filter.field) {
130 return Err(ValidationError::FieldNotAllowed {
131 field: filter.field.clone(),
132 allowed: self.allowed_fields.clone(),
133 });
134 }
135
136 if self.denied_operators.contains(&filter.op) {
138 return Err(ValidationError::OperatorDenied {
139 operator: filter.op,
140 field: filter.field.clone(),
141 });
142 }
143
144 if let Value::Array(values) = &filter.value {
146 let mut node_count = 0;
147 for value in values {
148 self.validate_value_with_count(value, depth + 1, &mut node_count)?;
149 }
150 }
151
152 Ok(())
153 }
154
155 fn validate_value_with_count(
157 &self,
158 value: &Value,
159 depth: usize,
160 count: &mut usize,
161 ) -> Result<(), ValidationError> {
162 *count += 1;
163 if *count > MAX_VALUE_NODES {
164 return Err(ValidationError::TooManyNodes {
165 max: MAX_VALUE_NODES,
166 });
167 }
168
169 if depth > self.max_depth {
170 return Err(ValidationError::NestingTooDeep {
171 max: self.max_depth,
172 actual: depth,
173 });
174 }
175
176 if let Value::Array(values) = value {
177 for v in values {
178 self.validate_value_with_count(v, depth + 1, count)?;
179 }
180 }
181
182 Ok(())
183 }
184}
185
186impl Default for FilterValidator {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194#[non_exhaustive]
195pub enum ValidationError {
196 FieldNotAllowed {
198 field: String,
200 allowed: Vec<String>,
202 },
203 OperatorDenied {
205 operator: Operator,
207 field: String,
209 },
210 NestingTooDeep {
212 max: usize,
214 actual: usize,
216 },
217 TooManyNodes {
219 max: usize,
221 },
222}
223
224impl fmt::Display for ValidationError {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 Self::FieldNotAllowed { field, allowed } => {
228 if allowed.is_empty() {
229 write!(f, "field `{field}` is not allowed (no fields permitted)")
230 } else {
231 write!(
232 f,
233 "field `{field}` is not allowed; permitted: {}",
234 allowed.join(", ")
235 )
236 }
237 },
238 Self::OperatorDenied { operator, field } => {
239 let reason = match operator {
240 Operator::Regex => " (ReDoS prevention)",
241 _ => "",
242 };
243 write!(
244 f,
245 "operator `{operator:?}` denied for field `{field}`{reason}"
246 )
247 },
248 Self::NestingTooDeep { max, actual } => {
249 write!(
250 f,
251 "filter nesting depth {actual} exceeds maximum {max} (DoS prevention)"
252 )
253 },
254 Self::TooManyNodes { max } => {
255 write!(
256 f,
257 "filter contains too many value nodes (max {max}, DoS prevention)"
258 )
259 },
260 }
261 }
262}
263
264impl std::error::Error for ValidationError {}
265
266pub fn merge_filters(
305 trusted: Vec<Filter>,
306 user: Vec<Filter>,
307 validator: &FilterValidator,
308) -> Result<Vec<Filter>, ValidationError> {
309 for filter in &user {
311 validator.validate(filter)?;
312 }
313
314 let mut result = trusted;
316 result.extend(user);
317 Ok(result)
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_validator_default_is_secure() {
326 let validator = FilterValidator::new();
327 assert!(validator.allowed_fields.is_empty());
328 assert_eq!(validator.denied_operators, vec![Operator::Regex]);
330 assert_eq!(validator.max_depth, 5);
331 }
332
333 #[test]
334 fn test_validator_permissive() {
335 let validator = FilterValidator::permissive();
336 assert!(validator.allowed_fields.is_empty());
337 assert!(validator.denied_operators.is_empty());
338 assert_eq!(validator.max_depth, 5);
339 }
340
341 #[test]
342 fn test_validator_builder() {
343 let validator = FilterValidator::new()
344 .allow_fields(&["name", "email"])
345 .deny_operators(&[Operator::Regex, Operator::ILike])
346 .max_depth(3);
347
348 assert_eq!(validator.allowed_fields.len(), 2);
349 assert_eq!(validator.denied_operators.len(), 2);
350 assert_eq!(validator.max_depth, 3);
351 }
352
353 #[test]
354 fn test_validate_allowed_field() {
355 let validator = FilterValidator::new().allow_fields(&["name", "email", "status"]);
356
357 let filter = Filter {
358 field: "name".into(),
359 op: Operator::Eq,
360 value: Value::String("Alice".into()),
361 };
362
363 assert!(validator.validate(&filter).is_ok());
364 }
365
366 #[test]
367 fn test_validate_disallowed_field() {
368 let validator = FilterValidator::new().allow_fields(&["name", "email"]);
369
370 let filter = Filter {
371 field: "password".into(),
372 op: Operator::Eq,
373 value: Value::String("secret".into()),
374 };
375
376 let result = validator.validate(&filter);
377 assert!(result.is_err());
378
379 let ValidationError::FieldNotAllowed { field, allowed } = result.unwrap_err() else {
380 panic!("expected FieldNotAllowed, got different error variant")
381 };
382 assert_eq!(field, "password");
383 assert_eq!(allowed.len(), 2);
384 }
385
386 #[test]
387 fn test_validate_empty_whitelist_allows_all() {
388 let validator = FilterValidator::new(); let filter = Filter {
391 field: "any_field".into(),
392 op: Operator::Eq,
393 value: Value::String("value".into()),
394 };
395
396 assert!(validator.validate(&filter).is_ok());
397 }
398
399 #[test]
400 fn test_validate_denied_operator() {
401 let validator = FilterValidator::new()
402 .allow_fields(&["name"])
403 .deny_operators(&[Operator::Regex, Operator::ILike]);
404
405 let filter = Filter {
406 field: "name".into(),
407 op: Operator::Regex,
408 value: Value::String("^A".into()),
409 };
410
411 let result = validator.validate(&filter);
412 assert!(result.is_err());
413
414 let ValidationError::OperatorDenied { operator, field } = result.unwrap_err() else {
415 panic!("expected OperatorDenied, got different error variant")
416 };
417 assert_eq!(operator, Operator::Regex);
418 assert_eq!(field, "name");
419 }
420
421 #[test]
422 fn test_validate_allowed_operator() {
423 let validator = FilterValidator::new()
424 .allow_fields(&["status"])
425 .deny_operators(&[Operator::Regex]);
426
427 let filter = Filter {
428 field: "status".into(),
429 op: Operator::Eq,
430 value: Value::String("active".into()),
431 };
432
433 assert!(validator.validate(&filter).is_ok());
434 }
435
436 #[test]
437 fn test_validate_nesting_depth() {
438 let validator = FilterValidator::new().max_depth(2);
439
440 let filter = Filter {
442 field: "tags".into(),
443 op: Operator::In,
444 value: Value::Array(vec![Value::String("rust".into())]),
445 };
446 assert!(validator.validate(&filter).is_ok());
447
448 let filter_deep = Filter {
450 field: "deep".into(),
451 op: Operator::In,
452 value: Value::Array(vec![Value::Array(vec![Value::Array(vec![Value::String(
453 "too deep".into(),
454 )])])]),
455 };
456 let result = validator.validate(&filter_deep);
457 assert!(result.is_err());
458
459 let ValidationError::NestingTooDeep { max, actual } = result.unwrap_err() else {
460 panic!("expected NestingTooDeep, got different error variant")
461 };
462 assert_eq!(max, 2);
463 assert!(actual > max);
464 }
465
466 #[test]
467 fn test_merge_filters_success() {
468 let validator = FilterValidator::new().allow_fields(&["status", "name"]);
469
470 let trusted = vec![
471 Filter {
472 field: "org_id".into(),
473 op: Operator::Eq,
474 value: Value::Int(123),
475 },
476 Filter {
477 field: "deleted_at".into(),
478 op: Operator::Eq,
479 value: Value::Null,
480 },
481 ];
482
483 let user = vec![Filter {
484 field: "status".into(),
485 op: Operator::Eq,
486 value: Value::String("active".into()),
487 }];
488
489 let result = merge_filters(trusted, user, &validator);
490 assert!(result.is_ok());
491
492 let filters = result.unwrap();
493 assert_eq!(filters.len(), 3);
494 assert_eq!(filters[0].field, "org_id");
495 assert_eq!(filters[1].field, "deleted_at");
496 assert_eq!(filters[2].field, "status");
497 }
498
499 #[test]
500 fn test_merge_filters_validation_error() {
501 let validator = FilterValidator::new().allow_fields(&["status"]);
502
503 let trusted = vec![Filter {
504 field: "org_id".into(),
505 op: Operator::Eq,
506 value: Value::Int(123),
507 }];
508
509 let user = vec![Filter {
511 field: "password".into(),
512 op: Operator::Eq,
513 value: Value::String("hack".into()),
514 }];
515
516 let result = merge_filters(trusted, user, &validator);
517 assert!(result.is_err());
518 }
519
520 #[test]
521 fn test_merge_filters_empty_user() {
522 let validator = FilterValidator::new();
523
524 let trusted = vec![Filter {
525 field: "org_id".into(),
526 op: Operator::Eq,
527 value: Value::Int(123),
528 }];
529
530 let user = vec![];
531
532 let result = merge_filters(trusted, user, &validator);
533 assert!(result.is_ok());
534 assert_eq!(result.unwrap().len(), 1);
535 }
536
537 #[test]
538 fn test_merge_filters_empty_trusted() {
539 let validator = FilterValidator::new().allow_fields(&["name"]);
540
541 let trusted = vec![];
542 let user = vec![Filter {
543 field: "name".into(),
544 op: Operator::Eq,
545 value: Value::String("Alice".into()),
546 }];
547
548 let result = merge_filters(trusted, user, &validator);
549 assert!(result.is_ok());
550 assert_eq!(result.unwrap().len(), 1);
551 }
552
553 #[test]
554 fn test_multiple_validation_errors() {
555 let validator = FilterValidator::new()
556 .allow_fields(&["status"])
557 .deny_operators(&[Operator::Regex]);
558
559 let filter1 = Filter {
561 field: "password".into(),
562 op: Operator::Eq,
563 value: Value::String("x".into()),
564 };
565 assert!(validator.validate(&filter1).is_err());
566
567 let filter2 = Filter {
569 field: "status".into(),
570 op: Operator::Regex,
571 value: Value::String("^A".into()),
572 };
573 assert!(validator.validate(&filter2).is_err());
574 }
575
576 #[test]
577 fn test_validation_error_display() {
578 let err = ValidationError::FieldNotAllowed {
579 field: "password".into(),
580 allowed: vec!["name".into(), "email".into()],
581 };
582 let msg = format!("{err}");
583 assert!(msg.contains("password"));
584 assert!(msg.contains("name"));
585
586 let err = ValidationError::OperatorDenied {
587 operator: Operator::Regex,
588 field: "name".into(),
589 };
590 let msg = format!("{err}");
591 assert!(msg.contains("Regex"));
592 assert!(msg.contains("name"));
593
594 let err = ValidationError::NestingTooDeep { max: 3, actual: 5 };
595 let msg = format!("{err}");
596 assert!(msg.contains('3'));
597 assert!(msg.contains('5'));
598 }
599
600 #[test]
601 fn test_in_operator_validation() {
602 let validator = FilterValidator::new().allow_fields(&["status"]);
603
604 let filter = Filter {
605 field: "status".into(),
606 op: Operator::In,
607 value: Value::Array(vec![
608 Value::String("active".into()),
609 Value::String("pending".into()),
610 ]),
611 };
612
613 assert!(validator.validate(&filter).is_ok());
614 }
615
616 #[test]
617 fn test_not_in_operator_validation() {
618 let validator = FilterValidator::new()
619 .allow_fields(&["status"])
620 .deny_operators(&[Operator::NotIn]);
621
622 let filter = Filter {
623 field: "status".into(),
624 op: Operator::NotIn,
625 value: Value::Array(vec![Value::String("deleted".into())]),
626 };
627
628 assert!(validator.validate(&filter).is_err());
629 }
630
631 #[test]
632 fn test_null_value_validation() {
633 let validator = FilterValidator::new().allow_fields(&["deleted_at"]);
634
635 let filter = Filter {
636 field: "deleted_at".into(),
637 op: Operator::Eq,
638 value: Value::Null,
639 };
640
641 assert!(validator.validate(&filter).is_ok());
642 }
643
644 #[test]
645 fn test_bool_value_validation() {
646 let validator = FilterValidator::new().allow_fields(&["active"]);
647
648 let filter = Filter {
649 field: "active".into(),
650 op: Operator::Eq,
651 value: Value::Bool(true),
652 };
653
654 assert!(validator.validate(&filter).is_ok());
655 }
656
657 #[test]
658 fn test_numeric_value_validation() {
659 let validator = FilterValidator::new().allow_fields(&["age", "price"]);
660
661 let filter1 = Filter {
662 field: "age".into(),
663 op: Operator::Gte,
664 value: Value::Int(18),
665 };
666 assert!(validator.validate(&filter1).is_ok());
667
668 let filter2 = Filter {
669 field: "price".into(),
670 op: Operator::Lt,
671 value: Value::Float(99.99),
672 };
673 assert!(validator.validate(&filter2).is_ok());
674 }
675
676 #[test]
677 fn test_new_denies_regex_by_default() {
678 let validator = FilterValidator::new().allow_fields(&["name"]);
680
681 let filter = Filter {
683 field: "name".into(),
684 op: Operator::Regex,
685 value: Value::String("^test".into()),
686 };
687
688 let result = validator.validate(&filter);
689 assert!(result.is_err());
690
691 let ValidationError::OperatorDenied { operator, .. } = result.unwrap_err() else {
692 panic!("expected OperatorDenied, got different error variant")
693 };
694 assert_eq!(operator, Operator::Regex);
695 }
696
697 #[test]
698 fn test_permissive_allows_regex() {
699 let validator = FilterValidator::permissive().allow_fields(&["name"]);
700
701 let filter = Filter {
702 field: "name".into(),
703 op: Operator::Regex,
704 value: Value::String("^test".into()),
705 };
706
707 assert!(validator.validate(&filter).is_ok());
709 }
710
711 #[test]
712 fn test_new_allows_safe_operators() {
713 let validator = FilterValidator::new().allow_fields(&["name", "status"]);
714
715 let filter = Filter {
717 field: "status".into(),
718 op: Operator::Eq,
719 value: Value::String("active".into()),
720 };
721 assert!(validator.validate(&filter).is_ok());
722
723 let filter = Filter {
725 field: "name".into(),
726 op: Operator::Like,
727 value: Value::String("%test%".into()),
728 };
729 assert!(validator.validate(&filter).is_ok());
730 }
731
732 #[test]
733 fn test_validate_compound_filter_deep_nesting() {
734 use crate::builder::{CompoundFilter, FilterExpr, simple};
735
736 let innermost = CompoundFilter::and(vec![
739 simple("a", Operator::Eq, Value::Int(1)),
740 simple("b", Operator::Eq, Value::Int(2)),
741 ]);
742
743 let middle = CompoundFilter::or(vec![
744 FilterExpr::Compound(innermost),
745 simple("c", Operator::Eq, Value::Int(3)),
746 ]);
747
748 let outer = CompoundFilter::and(vec![
749 FilterExpr::Compound(middle),
750 simple("d", Operator::Eq, Value::Int(4)),
751 ]);
752
753 let validator = FilterValidator::new();
757
758 let simple_filter = Filter {
761 field: "a".into(),
762 op: Operator::Eq,
763 value: Value::Int(1),
764 };
765 assert!(validator.validate(&simple_filter).is_ok());
766
767 assert_eq!(outer.filters.len(), 2);
769 assert_eq!(outer.op, crate::LogicalOp::And);
770 }
771
772 #[test]
773 fn test_validate_compound_not_single_element() {
774 use crate::builder::{CompoundFilter, simple};
775
776 let not_filter = CompoundFilter::not(simple("deleted", Operator::Eq, Value::Bool(true)));
778
779 assert_eq!(not_filter.filters.len(), 1);
780 assert_eq!(not_filter.op, crate::LogicalOp::Not);
781 }
782
783 #[test]
784 fn test_validate_compound_empty_filters() {
785 use crate::builder::CompoundFilter;
786
787 let empty_and = CompoundFilter::and(vec![]);
789 let empty_or = CompoundFilter::or(vec![]);
790
791 assert!(empty_and.filters.is_empty());
793 assert!(empty_or.filters.is_empty());
794 }
795
796 #[test]
797 fn test_validate_deeply_nested_array_values() {
798 let validator = FilterValidator::new().max_depth(2);
800
801 let filter_ok = Filter {
803 field: "tags".into(),
804 op: Operator::In,
805 value: Value::Array(vec![Value::Array(vec![Value::Int(1)])]),
806 };
807 assert!(validator.validate(&filter_ok).is_ok());
808
809 let filter_too_deep = Filter {
811 field: "tags".into(),
812 op: Operator::In,
813 value: Value::Array(vec![Value::Array(vec![Value::Array(vec![Value::Int(1)])])]),
814 };
815 assert!(validator.validate(&filter_too_deep).is_err());
816 }
817
818 #[test]
819 fn test_filter_value_injection() {
820 let validator = FilterValidator::new().allow_fields(&["name", "email"]);
823
824 let filter = Filter {
826 field: "name".into(),
827 op: Operator::Eq,
828 value: Value::String("'; DROP TABLE users--".into()),
829 };
830 assert!(validator.validate(&filter).is_ok());
832
833 }
838
839 #[test]
840 fn test_filter_field_injection() {
841 let validator = FilterValidator::new().allow_fields(&["name", "email"]);
843
844 let filter = Filter {
846 field: "name; DROP TABLE users--".into(),
847 op: Operator::Eq,
848 value: Value::String("test".into()),
849 };
850 assert!(validator.validate(&filter).is_err());
852
853 }
856
857 #[test]
858 fn test_operator_based_attacks() {
859 let validator = FilterValidator::new()
861 .allow_fields(&["name"])
862 .deny_operators(&[Operator::Regex]); let filter = Filter {
866 field: "name".into(),
867 op: Operator::Regex,
868 value: Value::String("^(a+)+$".into()), };
870 assert!(validator.validate(&filter).is_err());
871
872 let filter = Filter {
874 field: "name".into(),
875 op: Operator::Like,
876 value: Value::String("%test%".into()),
877 };
878 assert!(validator.validate(&filter).is_ok());
879 }
880}