kanidm_cli/system_config/
badlist.rs

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