Skip to main content

passay_rs/rule/
length_complexity.rs

1use crate::rule::rule_result::RuleResult;
2use crate::rule::{PasswordData, Rule};
3use std::collections::HashMap;
4use std::ops::Range;
5
6const ERROR_CODE: &str = "INSUFFICIENT_COMPLEXITY";
7const ERROR_CODE_RULES: &str = "INSUFFICIENT_COMPLEXITY_RULES";
8
9/// Rule for determining if a password contains the desired complexity for a certain length. In order to meet the
10/// criteria of this rule, passwords must meet all the supplied rules for a given password length.
11/// # Example
12///
13/// ```
14///  use passay_rs::rule::PasswordData;
15///  use passay_rs::rule::illegal_sequence::IllegalSequenceRule;
16///  use passay_rs::rule::sequence_data::EnglishSequenceData;
17///  use passay_rs::rule::character::CharacterRule;
18///  use passay_rs::rule::character_data::EnglishCharacterData;
19///  use passay_rs::rule::character_characteristics::CharacterCharacteristics;
20///  use passay_rs::rule::length_complexity::LengthComplexityRule;
21///  use passay_rs::rule::length::LengthRule;
22///  use passay_rs::rule::repeat_character::RepeatCharacterRule;
23///  use passay_rs::rule::username::UsernameRule;
24///  use passay_rs::rule::Rule;
25///
26///   let char_rules = vec![
27///      CharacterRule::new(Box::new(EnglishCharacterData::Digit), 1).unwrap(),
28///      CharacterRule::new(Box::new(EnglishCharacterData::Special), 1).unwrap(),
29///      CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
30///      CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
31///  ];
32///
33///  let rules: Vec<Box<dyn Rule>> = vec![
34///      Box::new(LengthRule::new(8, 64)),
35///      Box::new(
36///          CharacterCharacteristics::with_rules_and_characteristics(char_rules, 4).unwrap(),
37///      ),
38///      Box::new(UsernameRule::with_match_backwards_and_ignore_case(
39///          true, true,
40///      )),
41///      Box::new(IllegalSequenceRule::with_sequence_data(
42///          EnglishSequenceData::Alphabetical,
43///      )),
44///      Box::new(IllegalSequenceRule::with_sequence_data(
45///          EnglishSequenceData::Numerical,
46///      )),
47///      Box::new(IllegalSequenceRule::with_sequence_data(
48///          EnglishSequenceData::USQwerty,
49///      )),
50///      Box::new(RepeatCharacterRule::default()),
51///  ];
52///  let mut rule = LengthComplexityRule::default();
53///  let _ = rule.add_rules(0..12, rules);
54///
55///  let char_rules = vec![
56///      CharacterRule::new(Box::new(EnglishCharacterData::Digit), 1).unwrap(),
57///      CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
58///      CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
59///  ];
60///  let rules: Vec<Box<dyn Rule>> = vec![
61///      Box::new(LengthRule::new(8, 64)),
62///      Box::new(
63///          CharacterCharacteristics::with_rules_and_characteristics(char_rules, 3).unwrap(),
64///      ),
65///      Box::new(UsernameRule::with_match_backwards_and_ignore_case(
66///          true, true,
67///      )),
68///      Box::new(IllegalSequenceRule::with_sequence_data(
69///          EnglishSequenceData::Alphabetical,
70///      )),
71///      Box::new(IllegalSequenceRule::with_sequence_data(
72///          EnglishSequenceData::Numerical,
73///      )),
74///      Box::new(IllegalSequenceRule::with_sequence_data(
75///          EnglishSequenceData::USQwerty,
76///      )),
77///      Box::new(RepeatCharacterRule::default()),
78///  ];
79///  let _ = rule.add_rules(12..16, rules);
80///
81///  let char_rules = vec![
82///      CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
83///      CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
84///  ];
85///  let rules: Vec<Box<dyn Rule>> = vec![
86///      Box::new(LengthRule::new(8, 64)),
87///      Box::new(
88///          CharacterCharacteristics::with_rules_and_characteristics(char_rules, 2).unwrap(),
89///      ),
90///      Box::new(UsernameRule::with_match_backwards_and_ignore_case(
91///          true, true,
92///      )),
93///      Box::new(IllegalSequenceRule::with_sequence_data(
94///          EnglishSequenceData::Alphabetical,
95///      )),
96///      Box::new(IllegalSequenceRule::with_sequence_data(
97///          EnglishSequenceData::Numerical,
98///      )),
99///      Box::new(IllegalSequenceRule::with_sequence_data(
100///          EnglishSequenceData::USQwerty,
101///      )),
102///      Box::new(RepeatCharacterRule::default()),
103///  ];
104///  let _ = rule.add_rules(16..20, rules);
105///
106///  let rules: Vec<Box<dyn Rule>> = vec![
107///      Box::new(LengthRule::new(8, 64)),
108///      Box::new(UsernameRule::with_match_backwards_and_ignore_case(
109///          true, true,
110///      )),
111///      Box::new(IllegalSequenceRule::with_sequence_data(
112///          EnglishSequenceData::Alphabetical,
113///      )),
114///      Box::new(IllegalSequenceRule::with_sequence_data(
115///          EnglishSequenceData::Numerical,
116///      )),
117///      Box::new(IllegalSequenceRule::with_sequence_data(
118///          EnglishSequenceData::USQwerty,
119///      )),
120///      Box::new(RepeatCharacterRule::default()),
121///  ];
122///  let _ = rule.add_rules(20..128, rules);
123///
124///  let password = PasswordData::with_password_and_user(
125///      "rPscvEW2e".to_string(),
126///      Some("alfred".to_string()),
127///  );
128///  let result = rule.validate(&password);
129///  assert!(!result.valid());
130/// ```
131pub struct LengthComplexityRule {
132    rules: HashMap<Range<usize>, Vec<Box<dyn Rule>>>,
133    report_failure: bool,
134    report_rule_failures: bool,
135}
136
137impl LengthComplexityRule {
138    pub fn new(
139        rules: HashMap<Range<usize>, Vec<Box<dyn Rule>>>,
140        report_failure: bool,
141        report_rule_failures: bool,
142    ) -> Self {
143        Self {
144            rules,
145            report_failure,
146            report_rule_failures,
147        }
148    }
149
150    pub fn rules_mut(&mut self) -> &mut HashMap<Range<usize>, Vec<Box<dyn Rule>>> {
151        &mut self.rules
152    }
153    pub fn add_rules(
154        &mut self,
155        interval: Range<usize>,
156        rules: Vec<Box<dyn Rule>>,
157    ) -> Result<(), String> {
158        if rules.is_empty() {
159            return Err("Rules cannot be empty".to_string());
160        }
161
162        for existing_interval in self.rules.keys() {
163            if ranges_intersect(existing_interval, &interval) {
164                return Err(format!(
165                    "Interval {:?} intersects existing interval {:?}",
166                    interval, existing_interval
167                ));
168            }
169        }
170        let _ = &mut self.rules.insert(interval, rules);
171        Ok(())
172    }
173
174    fn get_rules_by_len(&self, len: usize) -> Option<&Vec<Box<dyn Rule>>> {
175        for (range, rules) in &self.rules {
176            if range.contains(&len) {
177                return Some(rules);
178            }
179        }
180        None
181    }
182}
183impl Rule for LengthComplexityRule {
184    fn validate(&self, password_data: &PasswordData) -> RuleResult {
185        let password_len = password_data.password.len();
186        let rules_by_len = self.get_rules_by_len(password_len);
187        let mut result = RuleResult::default();
188        if rules_by_len.is_none() {
189            result.add_error(
190                ERROR_CODE_RULES,
191                Some(create_rule_result_detail_parameters(password_len, 0, 0)),
192            );
193            return result;
194        }
195        let rules_by_len = rules_by_len.unwrap();
196        let rules_len = rules_by_len.len();
197
198        let mut success_count: usize = 0;
199        for rule in rules_by_len {
200            let mut rr = rule.validate(password_data);
201            if !rr.valid() {
202                if self.report_rule_failures {
203                    result.details_mut().append(rr.details_mut());
204                    dbg!(rr.details());
205                }
206            } else {
207                success_count += 1;
208            }
209            result.metadata_mut().merge(rr.metadata());
210        }
211        if success_count < rules_len {
212            result.set_valid(false);
213            if self.report_failure {
214                result.add_error(
215                    ERROR_CODE,
216                    Some(create_rule_result_detail_parameters(
217                        password_len,
218                        success_count,
219                        rules_len,
220                    )),
221                )
222            }
223        }
224        result
225    }
226}
227fn create_rule_result_detail_parameters(
228    len: usize,
229    success: usize,
230    rule_count: usize,
231) -> HashMap<String, String> {
232    let mut map = HashMap::with_capacity(3);
233    map.insert("passwordLength".to_string(), len.to_string());
234    map.insert("successCount".to_string(), success.to_string());
235    map.insert("ruleCount".to_string(), rule_count.to_string());
236    map
237}
238impl Default for LengthComplexityRule {
239    fn default() -> Self {
240        LengthComplexityRule {
241            rules: HashMap::new(),
242            report_failure: true,
243            report_rule_failures: true,
244        }
245    }
246}
247
248fn ranges_intersect(a: &Range<usize>, b: &Range<usize>) -> bool {
249    // Check if a's start is within b
250    a.start >= b.start && a.start < b.end ||
251        // Check if a's end is within b
252        a.end > b.start && a.end <= b.end ||
253        // Check if b's start is within a
254        b.start >= a.start && b.start < a.end ||
255        // Check if b's end is within a
256        b.end > a.start && b.end <= a.end
257}
258
259#[cfg(test)]
260mod tests {
261    use crate::rule::character_data::CharacterData;
262    use crate::rule::length::{ERROR_CODE_MAX, ERROR_CODE_MIN};
263    use crate::rule::length_complexity::{ERROR_CODE, ERROR_CODE_RULES};
264    use crate::rule::sequence_data::SequenceData;
265    use crate::rule::{
266        PasswordData, Rule, character::CharacterRule, character_characteristics,
267        character_characteristics::CharacterCharacteristics, character_data::EnglishCharacterData,
268        illegal_sequence::IllegalSequenceRule, length::LengthRule,
269        length_complexity::LengthComplexityRule, repeat_character,
270        repeat_character::RepeatCharacterRule, sequence_data::EnglishSequenceData, username,
271        username::UsernameRule,
272    };
273    use crate::test::{RulePasswordTestItem, check_passwords};
274
275    #[test]
276    fn test_passwords() {
277        let test_cases: Vec<RulePasswordTestItem> = vec![
278            // valid passwords in each length range
279            RulePasswordTestItem(
280                Box::new(rule1()),
281                PasswordData::with_password_and_user(
282                    "r%scvEW2e".to_string(),
283                    Some("alfred".to_string()),
284                ),
285                vec![],
286            ),
287            RulePasswordTestItem(
288                Box::new(rule1()),
289                PasswordData::with_password_and_user(
290                    "rkscvEW2e93C".to_string(),
291                    Some("alfred".to_string()),
292                ),
293                vec![],
294            ),
295            RulePasswordTestItem(
296                Box::new(rule1()),
297                PasswordData::with_password_and_user(
298                    "rkscvEWbePwCOUovqt".to_string(),
299                    Some("alfred".to_string()),
300                ),
301                vec![],
302            ),
303            RulePasswordTestItem(
304                Box::new(rule1()),
305                PasswordData::with_password_and_user(
306                    "horse staple battery".to_string(),
307                    Some("alfred".to_string()),
308                ),
309                vec![],
310            ),
311            RulePasswordTestItem(
312                Box::new(rule1()),
313                PasswordData::with_password_and_user(
314                    "it was the best of times".to_string(),
315                    Some("alfred".to_string()),
316                ),
317                vec![],
318            ),
319            // invalid passwords
320            RulePasswordTestItem(
321                Box::new(rule1()),
322                PasswordData::with_password_and_user(
323                    "r%vE2".to_string(),
324                    Some("alfred".to_string()),
325                ),
326                vec![ERROR_CODE, ERROR_CODE_MIN],
327            ),
328            RulePasswordTestItem(
329                Box::new(rule1()),
330                PasswordData::with_password_and_user(
331                    "It was the best of times, it was the worst of times, it was the age of wisdom,".to_string(),
332                    Some("alfred".to_string()),
333                ),
334                vec![ERROR_CODE, ERROR_CODE_MAX],
335            ),
336            RulePasswordTestItem(
337                Box::new(rule1()),
338                PasswordData::with_password_and_user(
339                    "It was the best of times, it was the worst of times, it was the age of wisdom, \
340                    it was the age of foolishness, it was the epoch of belief, \
341                    it was the epoch of incredulity, it was the season of Light,".to_string(),
342                    Some("alfred".to_string()),
343                ),
344                vec![ERROR_CODE_RULES],
345            ),
346            RulePasswordTestItem(
347                Box::new(rule1()),
348                PasswordData::with_password_and_user(
349                    "rPscvEW2e".to_string(),
350                    Some("alfred".to_string()),
351                ),
352                vec![ERROR_CODE, character_characteristics::ERROR_CODE, EnglishCharacterData::Digit.error_code()],
353            ),
354            RulePasswordTestItem(
355                Box::new(rule1()),
356                PasswordData::with_password_and_user(
357                    "r%scvEWte".to_string(),
358                    Some("alfred".to_string()),
359                ),
360                vec![ERROR_CODE, character_characteristics::ERROR_CODE, EnglishCharacterData::Digit.error_code()],
361            ),
362            RulePasswordTestItem(
363                Box::new(rule1()),
364                PasswordData::with_password_and_user(
365                    "r%scvew2e".to_string(),
366                    Some("alfred".to_string()),
367                ),
368                vec![ERROR_CODE, character_characteristics::ERROR_CODE, EnglishCharacterData::UpperCase.error_code()],
369            ),
370            RulePasswordTestItem(
371                Box::new(rule1()),
372                PasswordData::with_password_and_user(
373                    "R%SCVEW2E".to_string(),
374                    Some("alfred".to_string()),
375                ),
376                vec![ERROR_CODE, character_characteristics::ERROR_CODE, EnglishCharacterData::LowerCase.error_code()],
377            ),
378            RulePasswordTestItem(
379                Box::new(rule1()),
380                PasswordData::with_password_and_user(
381                    "rALfredTe".to_string(),
382                    Some("alfred".to_string()),
383                ),
384                vec![ERROR_CODE, username::ERROR_CODE,character_characteristics::ERROR_CODE,
385                     EnglishCharacterData::Special.error_code(), EnglishCharacterData::Digit.error_code()],
386            ),
387            RulePasswordTestItem(
388                Box::new(rule1()),
389                PasswordData::with_password_and_user(
390                    "It was the best of eeeee, it was the worst of 87654".to_string(),
391                    Some("alfred".to_string()),
392                ),
393                vec![ERROR_CODE, repeat_character::ERROR_CODE,
394                     EnglishSequenceData::USQwerty.error_code(), EnglishSequenceData::Numerical.error_code()],
395            ),
396            RulePasswordTestItem(
397                // RULE 2
398                Box::new(rule2()),
399                PasswordData::with_password_and_user(
400                    "It was the best of eeeee, it was the worst of 87654".to_string(),
401                    Some("alfred".to_string()),
402                ),
403                vec![
404                    ERROR_CODE,
405                    EnglishSequenceData::USQwerty.error_code(),
406                    EnglishSequenceData::Numerical.error_code(),
407                ],
408            ),
409        ];
410        check_passwords(test_cases);
411    }
412
413    fn rule1() -> LengthComplexityRule {
414        let char_rules = vec![
415            CharacterRule::new(Box::new(EnglishCharacterData::Digit), 1).unwrap(),
416            CharacterRule::new(Box::new(EnglishCharacterData::Special), 1).unwrap(),
417            CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
418            CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
419        ];
420
421        let rules: Vec<Box<dyn Rule>> = vec![
422            Box::new(LengthRule::new(8, 64)),
423            Box::new(
424                CharacterCharacteristics::with_rules_and_characteristics(char_rules, 4).unwrap(),
425            ),
426            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
427                true, true,
428            )),
429            Box::new(IllegalSequenceRule::with_sequence_data(
430                EnglishSequenceData::Alphabetical,
431            )),
432            Box::new(IllegalSequenceRule::with_sequence_data(
433                EnglishSequenceData::Numerical,
434            )),
435            Box::new(IllegalSequenceRule::with_sequence_data(
436                EnglishSequenceData::USQwerty,
437            )),
438            Box::new(RepeatCharacterRule::default()),
439        ];
440        let mut rule = LengthComplexityRule::default();
441        let _ = rule.add_rules(0..12, rules);
442
443        let char_rules = vec![
444            CharacterRule::new(Box::new(EnglishCharacterData::Digit), 1).unwrap(),
445            CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
446            CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
447        ];
448        let rules: Vec<Box<dyn Rule>> = vec![
449            Box::new(LengthRule::new(8, 64)),
450            Box::new(
451                CharacterCharacteristics::with_rules_and_characteristics(char_rules, 3).unwrap(),
452            ),
453            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
454                true, true,
455            )),
456            Box::new(IllegalSequenceRule::with_sequence_data(
457                EnglishSequenceData::Alphabetical,
458            )),
459            Box::new(IllegalSequenceRule::with_sequence_data(
460                EnglishSequenceData::Numerical,
461            )),
462            Box::new(IllegalSequenceRule::with_sequence_data(
463                EnglishSequenceData::USQwerty,
464            )),
465            Box::new(RepeatCharacterRule::default()),
466        ];
467        let _ = rule.add_rules(12..16, rules);
468
469        let char_rules = vec![
470            CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
471            CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
472        ];
473        let rules: Vec<Box<dyn Rule>> = vec![
474            Box::new(LengthRule::new(8, 64)),
475            Box::new(
476                CharacterCharacteristics::with_rules_and_characteristics(char_rules, 2).unwrap(),
477            ),
478            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
479                true, true,
480            )),
481            Box::new(IllegalSequenceRule::with_sequence_data(
482                EnglishSequenceData::Alphabetical,
483            )),
484            Box::new(IllegalSequenceRule::with_sequence_data(
485                EnglishSequenceData::Numerical,
486            )),
487            Box::new(IllegalSequenceRule::with_sequence_data(
488                EnglishSequenceData::USQwerty,
489            )),
490            Box::new(RepeatCharacterRule::default()),
491        ];
492        let _ = rule.add_rules(16..20, rules);
493
494        let rules: Vec<Box<dyn Rule>> = vec![
495            Box::new(LengthRule::new(8, 64)),
496            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
497                true, true,
498            )),
499            Box::new(IllegalSequenceRule::with_sequence_data(
500                EnglishSequenceData::Alphabetical,
501            )),
502            Box::new(IllegalSequenceRule::with_sequence_data(
503                EnglishSequenceData::Numerical,
504            )),
505            Box::new(IllegalSequenceRule::with_sequence_data(
506                EnglishSequenceData::USQwerty,
507            )),
508            Box::new(RepeatCharacterRule::default()),
509        ];
510        let _ = rule.add_rules(20..128, rules);
511        rule
512    }
513    fn rule2() -> LengthComplexityRule {
514        let char_rules = vec![
515            CharacterRule::new(Box::new(EnglishCharacterData::Digit), 1).unwrap(),
516            CharacterRule::new(Box::new(EnglishCharacterData::Special), 1).unwrap(),
517            CharacterRule::new(Box::new(EnglishCharacterData::UpperCase), 1).unwrap(),
518            CharacterRule::new(Box::new(EnglishCharacterData::LowerCase), 1).unwrap(),
519        ];
520
521        let rules: Vec<Box<dyn Rule>> = vec![
522            Box::new(LengthRule::new(8, 64)),
523            Box::new(
524                CharacterCharacteristics::with_rules_and_characteristics(char_rules, 4).unwrap(),
525            ),
526            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
527                true, true,
528            )),
529            Box::new(IllegalSequenceRule::with_sequence_data(
530                EnglishSequenceData::Alphabetical,
531            )),
532            Box::new(IllegalSequenceRule::with_sequence_data(
533                EnglishSequenceData::Numerical,
534            )),
535            Box::new(IllegalSequenceRule::with_sequence_data(
536                EnglishSequenceData::USQwerty,
537            )),
538            Box::new(RepeatCharacterRule::default()),
539        ];
540        let mut rule = LengthComplexityRule::default();
541        rule.report_failure = false;
542        let _ = rule.add_rules(0..20, rules);
543
544        let rules: Vec<Box<dyn Rule>> = vec![
545            Box::new(LengthRule::new(8, 64)),
546            Box::new(UsernameRule::with_match_backwards_and_ignore_case(
547                true, true,
548            )),
549            Box::new(IllegalSequenceRule::with_sequence_data(
550                EnglishSequenceData::Alphabetical,
551            )),
552            Box::new(IllegalSequenceRule::with_sequence_data(
553                EnglishSequenceData::Numerical,
554            )),
555            Box::new(IllegalSequenceRule::with_sequence_data(
556                EnglishSequenceData::USQwerty,
557            )),
558            Box::new(RepeatCharacterRule::default()),
559        ];
560        let _ = rule.add_rules(20..usize::MAX, rules);
561        rule
562    }
563}