Skip to main content

passay_rs/rule/
digest_source.rs

1use crate::hash::Hasher;
2use crate::rule::reference::Reference;
3use crate::rule::rule_result::RuleResult;
4use crate::rule::source::{SourceReference, validate_with_source_references};
5use crate::rule::{PasswordData, Rule};
6
7/// Rule for determining if a password matches a digested password from a different source. Useful for when separate
8/// systems cannot have matching passwords. If no password reference has been set that matches the label on the rule,
9/// then passwords will meet this rule. See also [PasswordData::password_references]
10///
11/// # Example
12///
13/// ```
14///#  use base64::Engine;
15///#  use passay_rs::hash::Hasher;
16///#  struct Sha1Hasher;
17///#  impl Hasher<String> for Sha1Hasher {
18///#      fn hash(&self, data: &[u8]) -> Result<Vec<u8>, String> {
19///#          todo!()
20///#      }
21///#
22///#      fn compare(&self, hash: &[u8], data: &[u8]) -> Result<bool, String> {
23///#          let hash_bytes = base64::prelude::BASE64_STANDARD.decode(hash).unwrap();
24///#          let data_sha1 = sha1_smol::Sha1::from(data).digest().bytes();
25///#          Ok(hash_bytes.eq(&data_sha1))
26///#      }
27///#  }
28///  use passay_rs::rule::digest_source::DigestSourceRule;
29///  use passay_rs::rule::source::SourceReference;
30///  use passay_rs::rule::reference::Reference;
31///  use passay_rs::rule::PasswordData;
32///  use passay_rs::rule::Rule;
33///
34///  let rule = DigestSourceRule::new(Sha1Hasher, true);
35///  let source: Vec<Box<dyn Reference>> = vec![Box::new(SourceReference::with_password_label(
36///      "CJGTDMQRP+rmHApkcijC80aDV0o=".to_string(),
37///      "System B".to_string(),
38///  ))];
39///  let password = PasswordData::new(
40///      "t3stUs3r04".to_string(),
41///      Some("testuser".to_string()),
42///      source,
43///  );
44///  let result = rule.validate(&password);
45///  assert!(!result.valid());
46/// ```
47pub struct DigestSourceRule<H>
48where
49    H: Hasher<String>,
50{
51    hasher: H,
52    report_all: bool,
53}
54
55impl<H> DigestSourceRule<H>
56where
57    H: Hasher<String>,
58{
59    pub fn new(hasher: H, report_all: bool) -> Self {
60        Self { hasher, report_all }
61    }
62}
63
64impl<H> Rule for DigestSourceRule<H>
65where
66    H: Hasher<String>,
67{
68    fn validate(&self, password_data: &PasswordData) -> RuleResult {
69        let matcher = |password: &str, rf: &SourceReference| {
70            let pass = password.to_string();
71            let undigested = match rf.salt() {
72                None => pass,
73                Some(salt) => salt.apply_to(pass),
74            };
75            let h = &self.hasher;
76            h.compare(rf.password().as_bytes(), undigested.as_bytes()).unwrap_or(false)
77        };
78        validate_with_source_references(self.report_all, password_data, matcher)
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use crate::rule::PasswordData;
85    use crate::rule::digest_history::test::Sha1Hasher;
86    use crate::rule::digest_source::DigestSourceRule;
87    use crate::rule::reference::Reference;
88    use crate::rule::source::{ERROR_CODE, SourceReference};
89    use crate::test::{RulePasswordTestItem, check_messages, check_passwords};
90
91    #[test]
92    fn test_passwords() {
93        let test_cases: Vec<RulePasswordTestItem> = vec![
94            RulePasswordTestItem(
95                Box::new(create_digest_rule()),
96                PasswordData::new(
97                    "t3stUs3r01".to_string(),
98                    Some("testuser".to_string()),
99                    create_sources(),
100                ),
101                vec![],
102            ),
103            RulePasswordTestItem(
104                Box::new(create_digest_rule()),
105                PasswordData::new(
106                    "t3stUs3r04".to_string(),
107                    Some("testuser".to_string()),
108                    create_sources(),
109                ),
110                vec![ERROR_CODE],
111            ),
112            // without source reference
113            RulePasswordTestItem(
114                Box::new(create_digest_rule()),
115                PasswordData::with_password_and_user(
116                    "t3stUs3r01".to_string(),
117                    Some("testuser".to_string()),
118                ),
119                vec![],
120            ),
121            // without source reference
122            RulePasswordTestItem(
123                Box::new(create_digest_rule()),
124                PasswordData::with_password_and_user(
125                    "t3stUs3r04".to_string(),
126                    Some("testuser".to_string()),
127                ),
128                vec![],
129            ),
130        ];
131        check_passwords(test_cases);
132    }
133    #[test]
134    fn test_messages() {
135        let test_cases: Vec<RulePasswordTestItem> = vec![RulePasswordTestItem(
136            Box::new(create_digest_rule()),
137            PasswordData::new(
138                "t3stUs3r04".to_string(),
139                Some("testuser".to_string()),
140                create_sources(),
141            ),
142            vec!["SOURCE_VIOLATION,System B"],
143        )];
144        check_messages(test_cases);
145    }
146    fn create_sources() -> Vec<Box<dyn Reference>> {
147        vec![Box::new(SourceReference::with_password_label(
148            "CJGTDMQRP+rmHApkcijC80aDV0o=".to_string(),
149            "System B".to_string(),
150        ))]
151    }
152    fn create_digest_rule() -> DigestSourceRule<Sha1Hasher> {
153        DigestSourceRule::new(Sha1Hasher, true)
154    }
155}