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