1use crate::rule::reference::{Reference, Salt};
2use crate::rule::rule_result::RuleResult;
3use crate::rule::{PasswordData, Rule};
4use std::any::Any;
5use std::collections::HashMap;
6use std::fmt::{Debug, Formatter};
7
8pub(super) const ERROR_CODE: &str = "SOURCE_VIOLATION";
9
10#[derive(Clone)]
39pub struct SourceRule {
40 report_all: bool,
41}
42
43impl SourceRule {
44 pub fn new(report_all: bool) -> SourceRule {
45 SourceRule { report_all }
46 }
47}
48
49impl Default for SourceRule {
50 fn default() -> Self {
51 SourceRule::new(true)
52 }
53}
54
55impl Rule for SourceRule {
56 fn validate(&self, password_data: &PasswordData) -> RuleResult {
57 validate_with_source_references(self.report_all, password_data, matches)
58 }
59}
60
61pub(super) fn validate_with_source_references<F: Fn(&str, &SourceReference) -> bool>(
62 report_all: bool,
63 password_data: &PasswordData,
64 matcher: F,
65) -> RuleResult {
66 let mut result = RuleResult::default();
67
68 for rf in password_data.password_references() {
69 if let Some(rf) = rf.as_any().downcast_ref::<SourceReference>() {
70 let cleartext = password_data.password();
71 if matcher(cleartext, rf) {
72 result.add_error(
73 ERROR_CODE,
74 Some(create_rule_result_detail_parameters(rf.label())),
75 );
76 if !report_all {
77 return result;
78 };
79 }
80 }
81 }
82
83 result
84}
85fn create_rule_result_detail_parameters(source: &str) -> HashMap<String, String> {
86 let mut map = HashMap::with_capacity(1);
87 map.insert("source".to_string(), source.to_string());
88 map
89}
90fn matches(password: &str, rf: &SourceReference) -> bool {
91 password == rf.password()
92}
93pub struct SourceReference {
94 label: String,
95 password: String,
96 salt: Option<Salt>,
97}
98
99impl SourceReference {
100 pub fn new(label: String, password: String, salt: Salt) -> Self {
101 SourceReference {
102 label,
103 password,
104 salt: Some(salt),
105 }
106 }
107 pub fn with_password_label(password: String, label: String) -> Self {
108 SourceReference {
109 label,
110 password,
111 salt: None,
112 }
113 }
114
115 pub fn label(&self) -> &str {
116 &self.label
117 }
118}
119
120impl Debug for SourceReference {
121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct("SourceReference")
123 .field("password", &self.password)
124 .field("label", &self.label)
125 .finish()
126 }
127}
128
129impl Reference for SourceReference {
130 fn password(&self) -> &str {
131 &self.password
132 }
133
134 fn salt(&self) -> &Option<Salt> {
135 &self.salt
136 }
137
138 fn as_any(&self) -> &dyn Any {
139 self
140 }
141}
142
143#[cfg(test)]
144mod test {
145 use crate::rule::PasswordData;
146 use crate::rule::reference::Reference;
147 use crate::rule::source::{ERROR_CODE, SourceReference, SourceRule};
148 use crate::test::{RulePasswordTestItem, check_messages, check_passwords};
149
150 #[test]
151 fn test_passwords() {
152 let rule = SourceRule::default();
153 let rule_report_first = SourceRule::new(false);
154 let empty_rule = SourceRule::default();
155 let test_cases: Vec<RulePasswordTestItem> = vec![
156 RulePasswordTestItem(
157 Box::new(rule.clone()),
158 PasswordData::new(
159 "t3stUs3r01".to_string(),
160 Some("testuser".to_string()),
161 create_sources(),
162 ),
163 vec![],
164 ),
165 RulePasswordTestItem(
166 Box::new(rule.clone()),
167 PasswordData::new(
168 "t3stUs3r04".to_string(),
169 Some("testuser".to_string()),
170 create_sources(),
171 ),
172 vec![ERROR_CODE],
173 ),
174 RulePasswordTestItem(
175 Box::new(rule.clone()),
176 PasswordData::new(
177 "t3stUs3r05".to_string(),
178 Some("testuser".to_string()),
179 create_sources(),
180 ),
181 vec![ERROR_CODE, ERROR_CODE],
182 ),
183 RulePasswordTestItem(
184 Box::new(rule_report_first.clone()),
185 PasswordData::new(
186 "t3stUs3r01".to_string(),
187 Some("testuser".to_string()),
188 create_sources(),
189 ),
190 vec![],
191 ),
192 RulePasswordTestItem(
193 Box::new(rule_report_first.clone()),
194 PasswordData::new(
195 "t3stUs3r04".to_string(),
196 Some("testuser".to_string()),
197 create_sources(),
198 ),
199 vec![ERROR_CODE],
200 ),
201 RulePasswordTestItem(
202 Box::new(rule_report_first.clone()),
203 PasswordData::new(
204 "t3stUs3r05".to_string(),
205 Some("testuser".to_string()),
206 create_sources(),
207 ),
208 vec![ERROR_CODE],
209 ),
210 RulePasswordTestItem(
211 Box::new(empty_rule.clone()),
212 PasswordData::with_password_and_user(
213 "t3stUs3r01".to_string(),
214 Some("testuser".to_string()),
215 ),
216 vec![],
217 ),
218 RulePasswordTestItem(
219 Box::new(empty_rule.clone()),
220 PasswordData::with_password_and_user(
221 "t3stUs3r04".to_string(),
222 Some("testuser".to_string()),
223 ),
224 vec![],
225 ),
226 RulePasswordTestItem(
227 Box::new(empty_rule.clone()),
228 PasswordData::with_password_and_user(
229 "t3stUs3r05".to_string(),
230 Some("testuser".to_string()),
231 ),
232 vec![],
233 ),
234 ];
235 check_passwords(test_cases);
236 }
237 #[test]
238 fn test_messages() {
239 let rule = SourceRule::default();
240 let rule_report_first = SourceRule::new(false);
241
242 let test_cases: Vec<RulePasswordTestItem> = vec![
243 RulePasswordTestItem(
244 Box::new(rule.clone()),
245 PasswordData::new(
246 "t3stUs3r04".to_string(),
247 Some("testuser".to_string()),
248 create_sources(),
249 ),
250 vec!["SOURCE_VIOLATION,System A"],
251 ),
252 RulePasswordTestItem(
253 Box::new(rule.clone()),
254 PasswordData::new(
255 "t3stUs3r05".to_string(),
256 Some("testuser".to_string()),
257 create_sources(),
258 ),
259 vec!["SOURCE_VIOLATION,System A", "SOURCE_VIOLATION,System A"],
260 ),
261 RulePasswordTestItem(
262 Box::new(rule_report_first),
263 PasswordData::new(
264 "t3stUs3r05".to_string(),
265 Some("testuser".to_string()),
266 create_sources(),
267 ),
268 vec!["SOURCE_VIOLATION,System A"],
269 ),
270 ];
271 check_messages(test_cases);
272 }
273
274 fn create_sources() -> Vec<Box<dyn Reference>> {
275 vec![
276 Box::new(SourceReference::with_password_label(
277 "t3stUs3r04".to_string(),
278 "System A".to_string(),
279 )),
280 Box::new(SourceReference::with_password_label(
281 "t3stUs3r05".to_string(),
282 "System A".to_string(),
283 )),
284 Box::new(SourceReference::with_password_label(
285 "t3stUs3r05".to_string(),
286 "System A".to_string(),
287 )),
288 ]
289 }
290}