kanidm_cli/system_config/
badlist.rs

1use crate::common::OpType;
2use crate::{handle_client_error, PwBadlistOpt};
3
4// use std::thread;
5use std::fs::File;
6use std::io::Read;
7use tokio::task;
8use zxcvbn::Score;
9
10const CHUNK_SIZE: usize = 1000;
11
12impl PwBadlistOpt {
13    pub fn debug(&self) -> bool {
14        match self {
15            PwBadlistOpt::Show(copt) => copt.debug,
16            PwBadlistOpt::Upload { copt, .. } => copt.debug,
17            PwBadlistOpt::Remove { copt, .. } => copt.debug,
18        }
19    }
20
21    pub async fn exec(&self) {
22        match self {
23            PwBadlistOpt::Show(copt) => {
24                let client = copt.to_client(OpType::Read).await;
25                match client.system_password_badlist_get().await {
26                    Ok(list) => {
27                        for i in list {
28                            println!("{}", i);
29                        }
30                        eprintln!("--");
31                        eprintln!("Success");
32                    }
33                    Err(e) => crate::handle_client_error(e, copt.output_mode),
34                }
35            }
36            PwBadlistOpt::Upload {
37                copt,
38                paths,
39                dryrun,
40            } => {
41                info!("pre-processing - this may take a while ...");
42
43                let mut pwset: Vec<String> = Vec::new();
44
45                for f in paths.iter() {
46                    let mut file = match File::open(f) {
47                        Ok(v) => v,
48                        Err(e) => {
49                            debug!(?e);
50                            info!("Skipping file -> {:?}", f);
51                            continue;
52                        }
53                    };
54                    let mut contents = String::new();
55                    if let Err(e) = file.read_to_string(&mut contents) {
56                        error!("{:?} -> {:?}", f, e);
57                        continue;
58                    }
59                    let mut inner_pw: Vec<_> =
60                        contents.as_str().lines().map(str::to_string).collect();
61                    pwset.append(&mut inner_pw);
62                }
63
64                debug!("Deduplicating pre-set ...");
65                pwset.sort_unstable();
66                pwset.dedup();
67
68                info!("Have {} unique passwords to process", pwset.len());
69
70                // Break the list into chunks per thread availability
71                let task_handles: Vec<_> = pwset
72                    .chunks(CHUNK_SIZE)
73                    .map(|chunk| chunk.to_vec())
74                    .map(|chunk| {
75                        task::spawn_blocking(move || {
76                            let x = chunk
77                                .iter()
78                                .filter(|v| {
79                                    if v.len() < 10 {
80                                        return false;
81                                    }
82                                    zxcvbn::zxcvbn(v.as_str(), &[]).score() >= Score::Four
83                                })
84                                .map(|s| s.to_string())
85                                .collect::<Vec<_>>();
86                            eprint!(".");
87                            x
88                        })
89                    })
90                    .collect();
91
92                let mut filt_pwset = Vec::with_capacity(pwset.len());
93
94                for task_handle in task_handles {
95                    let Ok(mut results) = task_handle.await else {
96                        error!("Failed to join a worker thread, unable to proceed");
97                        return;
98                    };
99                    filt_pwset.append(&mut results);
100                }
101
102                filt_pwset.sort_unstable();
103
104                info!(
105                    "{} passwords passed zxcvbn, uploading ...",
106                    filt_pwset.len()
107                );
108
109                if *dryrun {
110                    for pw in filt_pwset {
111                        println!("{}", pw);
112                    }
113                } else {
114                    let client = copt.to_client(OpType::Write).await;
115                    match client.system_password_badlist_append(filt_pwset).await {
116                        Ok(_) => println!("Success"),
117                        Err(e) => handle_client_error(e, copt.output_mode),
118                    }
119                }
120            } // End Upload
121            PwBadlistOpt::Remove { copt, paths } => {
122                let client = copt.to_client(OpType::Write).await;
123
124                let mut pwset: Vec<String> = Vec::new();
125
126                for f in paths.iter() {
127                    let mut file = match File::open(f) {
128                        Ok(v) => v,
129                        Err(e) => {
130                            debug!(?e);
131                            info!("Skipping file -> {:?}", f);
132                            continue;
133                        }
134                    };
135                    let mut contents = String::new();
136                    if let Err(e) = file.read_to_string(&mut contents) {
137                        error!("{:?} -> {:?}", f, e);
138                        continue;
139                    }
140                    let mut inner_pw: Vec<_> =
141                        contents.as_str().lines().map(str::to_string).collect();
142                    pwset.append(&mut inner_pw);
143                }
144
145                debug!("Deduplicating pre-set ...");
146                pwset.sort_unstable();
147                pwset.dedup();
148
149                if pwset.is_empty() {
150                    eprintln!("No entries to remove?");
151                    return;
152                }
153
154                match client.system_password_badlist_remove(pwset).await {
155                    Ok(_) => println!("Success"),
156                    Err(e) => handle_client_error(e, copt.output_mode),
157                }
158            } // End Remove
159        }
160    }
161}