kanidm_cli/system_config/
badlist.rs1use crate::common::OpType;
2use crate::{handle_client_error, PwBadlistOpt};
3
4use 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 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 } 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 } }
174 }
175}