passay_rs/rule/
repeat_characters.rs1use crate::rule::rule_result::RuleResult;
2use crate::rule::{PasswordData, Rule};
3use std::collections::HashMap;
4
5const ERROR_CODE: &str = "ILLEGAL_REPEATED_CHARS";
6const DEFAULT_SEQUENCE_LENGTH: usize = 5;
7const DEFAULT_SEQUENCE_COUNT: usize = 1;
8
9pub struct RepeatCharactersRule {
28 sequence_length: usize,
29 sequence_count: usize,
30}
31
32impl RepeatCharactersRule {
33 pub fn new(sequence_length: usize, sequence_count: usize) -> Result<Self, String> {
34 if sequence_count < 1 {
35 return Err("sequence count must be > 0".into());
36 }
37 if sequence_length < 2 {
38 return Err("sequence length must be > 2".into());
39 }
40
41 Ok(Self {
42 sequence_length,
43 sequence_count,
44 })
45 }
46 pub fn with_sequence_length(sequence_length: usize) -> Result<Self, String> {
47 Self::new(sequence_length, DEFAULT_SEQUENCE_COUNT)
48 }
49
50 fn create_rule_result_detail_parameters(&self, matches: &[String]) -> HashMap<String, String> {
51 let mut map = HashMap::with_capacity(4);
52 map.insert(
53 "sequenceLength".to_string(),
54 self.sequence_length.to_string(),
55 );
56 map.insert("sequenceCount".to_string(), self.sequence_count.to_string());
57 map.insert("matchesCount".to_string(), matches.len().to_string());
58 map.insert("matches".to_string(), matches.join(","));
59 map
60 }
61}
62
63impl Default for RepeatCharactersRule {
64 fn default() -> Self {
65 Self::new(DEFAULT_SEQUENCE_LENGTH, DEFAULT_SEQUENCE_COUNT).unwrap()
66 }
67}
68
69impl Rule for RepeatCharactersRule {
70 fn validate(&self, password_data: &PasswordData) -> RuleResult {
71 let mut result = RuleResult::default();
72 let mut matches = vec![];
73 let password = format!("{}{}", password_data.password(), '\u{ffff}');
74 let mut count = 0;
75 let mut repeat = 1;
76 let mut prev: Option<char> = None;
77 let chars: Vec<char> = password.chars().collect();
78 let max = chars.len() - 1;
79
80 for i in 0..=max {
81 let c = chars[i];
82 if prev.is_some() && c == prev.unwrap() {
83 repeat += 1;
84 } else {
85 if repeat >= self.sequence_length {
86 let m: String = chars[i - repeat..i].iter().collect();
87 matches.push(m);
88 count += 1;
89 }
90 repeat = 1;
91 }
92 prev = Some(c);
93 }
94 if count >= self.sequence_count {
95 result.add_error(
96 ERROR_CODE,
97 Some(self.create_rule_result_detail_parameters(&matches)),
98 );
99 }
100 result
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use crate::rule::PasswordData;
107 use crate::rule::repeat_characters::{ERROR_CODE, RepeatCharactersRule};
108 use crate::test::{RulePasswordTestItem, check_messages, check_passwords};
109
110 #[test]
111 fn test_passwords() {
112 let test_cases: Vec<RulePasswordTestItem> = vec![
113 RulePasswordTestItem(
115 Box::new(RepeatCharactersRule::default()),
116 PasswordData::with_password("p4zRcv8#n65".to_string()),
117 vec![],
118 ),
119 RulePasswordTestItem(
121 Box::new(RepeatCharactersRule::default()),
122 PasswordData::with_password("p4&&&&&#n65".to_string()),
123 vec![ERROR_CODE],
124 ),
125 RulePasswordTestItem(
127 Box::new(RepeatCharactersRule::default()),
128 PasswordData::with_password("p4vvvvvvv#n65".to_string()),
129 vec![ERROR_CODE],
130 ),
131 RulePasswordTestItem(
133 Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
134 PasswordData::with_password("p4zRcv8#n65".to_string()),
135 vec![],
136 ),
137 RulePasswordTestItem(
139 Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
140 PasswordData::with_password("p4&&&&&#n65".to_string()),
141 vec![],
142 ),
143 RulePasswordTestItem(
145 Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
146 PasswordData::with_password("p4vvvvvvv#n65".to_string()),
147 vec![ERROR_CODE],
148 ),
149 RulePasswordTestItem(
151 Box::new(RepeatCharactersRule::default()),
152 PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
153 vec![ERROR_CODE],
154 ),
155 RulePasswordTestItem(
157 Box::new(RepeatCharactersRule::new(5, 3).unwrap()),
158 PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
159 vec![],
160 ),
161 RulePasswordTestItem(
163 Box::new(RepeatCharactersRule::new(5, 2).unwrap()),
164 PasswordData::with_password("p4&&&&&#n65FFFF".to_string()),
165 vec![],
166 ),
167 RulePasswordTestItem(
169 Box::new(RepeatCharactersRule::new(5, 2).unwrap()),
170 PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
171 vec![ERROR_CODE],
172 ),
173 RulePasswordTestItem(
175 Box::new(RepeatCharactersRule::new(5, 3).unwrap()),
176 PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
177 vec![],
178 ),
179 ];
180
181 check_passwords(test_cases);
182 }
183
184 #[test]
185 fn test_messages() {
186 let test_cases: Vec<RulePasswordTestItem> = vec![RulePasswordTestItem(
187 Box::new(RepeatCharactersRule::new(2, 2).unwrap()),
188 PasswordData::with_password("paaxvbbdkccx".to_string()),
189 vec!["ILLEGAL_REPEATED_CHARS,3,2,2,aa,bb,cc"],
190 )];
191 check_messages(test_cases);
192 }
193}