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
9pub 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 a.start >= b.start && a.start < b.end ||
251 a.end > b.start && a.end <= b.end ||
253 b.start >= a.start && b.start < a.end ||
255 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 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 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 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}