kanidm_cli/system_config/
badlist.rs1use crate::{handle_client_error, KanidmClientParser, OutputMode, PwBadlistOpt};
2use kanidm_proto::cli::OpType;
3
4use 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 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 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 } 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 } }
158 }
159}