Skip to main content

pants_gen/
password.rs

1use std::fmt::Display;
2use std::str::FromStr;
3
4use rand::{
5    seq::{IteratorRandom, SliceRandom},
6    thread_rng,
7};
8use thiserror::Error;
9
10use crate::choice::{ChoiceParseError, Choices};
11use crate::interval::Interval;
12use crate::{charset::Charset, choice::Choice};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct PasswordSpec {
16    length: usize,
17    choices: Choices,
18}
19
20impl Default for PasswordSpec {
21    fn default() -> Self {
22        let mut choices = Choices::new();
23        choices.push(Charset::Upper.at_least(1));
24        choices.push(Charset::Lower.at_least(1));
25        choices.push(Charset::Number.at_least(1));
26        choices.push(Charset::Symbol.at_least(1));
27        PasswordSpec {
28            choices,
29            length: 32,
30        }
31    }
32}
33
34#[derive(Debug, Error)]
35pub enum PasswordParseError {
36    #[error("Password spec improperly formatted, expect something like length//interval|charset//interval|charset (likely an internal parsing error)")]
37    ImproperFormat,
38    #[error("Couldn't parse the length segment of the spec `{0}`, expects it to be the first segment of the spec (length//...).")]
39    InvalidLength(String),
40    #[error("Couldn't parse the interval `{0}`.")]
41    BadInterval(String),
42    #[error("Couldn't parse the charset `{0}`.")]
43    BadCharset(String),
44    #[error("{0}")]
45    BadChoice(ChoiceParseError),
46}
47
48// password spec specified as a string would look something like
49// 16//1+|:upper://5-|:lower://2|Aa
50// (Upper, at least 1) (Lower, at most 5) (Custom(Aa), exactly 2) length=16
51
52impl FromStr for PasswordSpec {
53    type Err = PasswordParseError;
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        let s = s.trim_start();
56        let sep = "//".to_string();
57        let sep_char = sep.chars().last().unwrap();
58        // let second_sep = "|".to_string();
59        let mut spec = PasswordSpec::new();
60        let mut stack = String::new();
61        let chars: Vec<char> = s.chars().collect();
62        // parse length first
63        let mut i = 0;
64        while i < chars.len() {
65            let c = chars[i];
66            stack.push(c);
67            i += 1;
68            if stack.ends_with(&sep) {
69                let length: usize = stack[..stack.len() - sep.len()].parse().map_err(|_| {
70                    PasswordParseError::InvalidLength(stack[..stack.len() - sep.len()].to_string())
71                })?;
72                spec = spec.length(length);
73                stack = String::new();
74                break;
75            }
76        }
77
78        // parse choices
79        while i < chars.len() {
80            let c = chars[i];
81            if c != sep_char && stack.ends_with(&sep) {
82                let choice = stack[..stack.len() - sep.len()]
83                    .parse()
84                    .map_err(PasswordParseError::BadChoice)?;
85                spec = spec.include(choice);
86                stack = String::new();
87            }
88            stack.push(c);
89            i += 1;
90        }
91
92        // since parsing requires a peek, need to handle the very end of the string
93        // having a trailing // is valid
94        if stack.ends_with(&sep) {
95            let choice = stack[..stack.len() - sep.len()]
96                .parse()
97                .map_err(PasswordParseError::BadChoice)?;
98            spec = spec.include(choice);
99            stack = String::new();
100        }
101
102        if !stack.is_empty() {
103            let choice = stack[..stack.len()]
104                .parse()
105                .map_err(PasswordParseError::BadChoice)?;
106            spec = spec.include(choice);
107            // stack = String::new();
108        }
109
110        Ok(spec)
111    }
112}
113
114impl Display for PasswordSpec {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", self.length)?;
117        write!(f, "{}", self.choices)
118    }
119}
120
121impl PasswordSpec {
122    pub fn new() -> Self {
123        Self {
124            choices: Choices::new(),
125            length: 32,
126        }
127    }
128    pub fn generate(&self) -> Option<String> {
129        if self.check() {
130            let mut characters = vec![];
131            let mut active = Choices::new();
132            for mut choice in self.choices.clone() {
133                characters.extend(choice.get_required());
134                active.push(choice);
135            }
136
137            let remaining = self.length - characters.len();
138            let mut active: Vec<_> = active.into_iter().filter(|x| x.active()).collect();
139
140            for _ in 0..remaining {
141                if let Some(index) = (0..active.len()).choose(&mut thread_rng()) {
142                    let c = active[index].next().unwrap();
143                    characters.push(c);
144                    if !active[index].active() {
145                        active.remove(index);
146                    }
147                }
148            }
149
150            characters.shuffle(&mut thread_rng());
151            Some(characters.into_iter().collect())
152        } else {
153            None
154        }
155    }
156
157    fn check(&self) -> bool {
158        let mut min_length: usize = 0;
159        let mut max_length: usize = 0;
160        for choice in &self.choices.choices {
161            min_length = min_length.saturating_add(choice.min);
162            max_length = max_length.saturating_add(choice.max);
163        }
164        min_length <= self.length && self.length <= max_length
165    }
166
167    pub fn length(mut self, length: usize) -> Self {
168        self.length = length;
169        self
170    }
171
172    pub fn include(mut self, choice: Choice) -> Self {
173        self.choices.push(choice);
174        self
175    }
176
177    pub fn upper(mut self, interval: Interval) -> Self {
178        self.choices
179            .push(Choice::from_interval(interval, Charset::Upper));
180        self
181    }
182    pub fn upper_at_least(mut self, size: usize) -> Self {
183        self.choices.push(Charset::Upper.at_least(size));
184        self
185    }
186    pub fn upper_at_most(mut self, size: usize) -> Self {
187        self.choices.push(Charset::Upper.at_most(size));
188        self
189    }
190    pub fn upper_exactly(mut self, size: usize) -> Self {
191        self.choices.push(Charset::Upper.exactly(size));
192        self
193    }
194    pub fn lower(mut self, interval: Interval) -> Self {
195        self.choices
196            .push(Choice::from_interval(interval, Charset::Lower));
197
198        self
199    }
200    pub fn lower_at_least(mut self, size: usize) -> Self {
201        self.choices.push(Charset::Lower.at_least(size));
202        self
203    }
204    pub fn lower_at_most(mut self, size: usize) -> Self {
205        self.choices.push(Charset::Lower.at_most(size));
206        self
207    }
208    pub fn lower_exactly(mut self, size: usize) -> Self {
209        self.choices.push(Charset::Lower.exactly(size));
210        self
211    }
212    pub fn number(mut self, interval: Interval) -> Self {
213        self.choices
214            .push(Choice::from_interval(interval, Charset::Number));
215
216        self
217    }
218    pub fn number_at_least(mut self, size: usize) -> Self {
219        self.choices.push(Charset::Number.at_least(size));
220        self
221    }
222    pub fn number_at_most(mut self, size: usize) -> Self {
223        self.choices.push(Charset::Number.at_most(size));
224        self
225    }
226    pub fn number_exactly(mut self, size: usize) -> Self {
227        self.choices.push(Charset::Number.exactly(size));
228        self
229    }
230    pub fn symbol(mut self, interval: Interval) -> Self {
231        self.choices
232            .push(Choice::from_interval(interval, Charset::Symbol));
233
234        self
235    }
236    pub fn symbol_at_least(mut self, size: usize) -> Self {
237        self.choices.push(Charset::Symbol.at_least(size));
238        self
239    }
240    pub fn symbol_at_most(mut self, size: usize) -> Self {
241        self.choices.push(Charset::Symbol.at_most(size));
242        self
243    }
244    pub fn symbol_exactly(mut self, size: usize) -> Self {
245        self.choices.push(Charset::Symbol.exactly(size));
246        self
247    }
248
249    pub fn custom(mut self, chars: Vec<char>, interval: Interval) -> Self {
250        self.choices
251            .push(Choice::from_interval(interval, Charset::Custom(chars)));
252
253        self
254    }
255    pub fn custom_at_least(mut self, chars: Vec<char>, size: usize) -> Self {
256        self.choices.push(Charset::Custom(chars).at_least(size));
257        self
258    }
259    pub fn custom_at_most(mut self, chars: Vec<char>, size: usize) -> Self {
260        self.choices.push(Charset::Custom(chars).at_most(size));
261        self
262    }
263    pub fn custom_exactly(mut self, chars: Vec<char>, size: usize) -> Self {
264        self.choices.push(Charset::Custom(chars).exactly(size));
265        self
266    }
267}