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