1use bitwarden_generators::{
2 MAXIMUM_MIN_CHAR_COUNT, MAXIMUM_PASSPHRASE_NUM_WORDS, MAXIMUM_PASSWORD_LENGTH,
3 MINIMUM_MIN_CHAR_COUNT, MINIMUM_PASSPHRASE_NUM_WORDS, MINIMUM_PASSWORD_LENGTH,
4 PassphraseGeneratorRequest, PasswordGeneratorRequest,
5};
6use bitwarden_pm::PasswordManagerClient;
7use clap::{Args, Subcommand};
8
9use crate::render::CommandResult;
10
11#[derive(Args, Clone)]
12#[command(
13 about = "Generate a password/passphrase.",
14 after_help = r#"Notes:
15 Default options are `-uln --length 14`.
16 Minimum `length` is 5.
17 Minimum `words` is 3.
18
19Examples:
20 bw generate
21 bw generate -u -l --length 18
22 bw generate -ulns --length 25
23 bw generate -ul
24 bw generate -p --separator _
25 bw generate -p --words 5 --separator space
26 bw generate -p --words 5 --separator empty
27 "#
28)]
29pub struct GenerateArgs {
30 #[arg(short = 'u', long, action, help = "Include uppercase characters (A-Z)")]
32 pub uppercase: bool,
33
34 #[arg(short = 'l', long, action, help = "Include lowercase characters (a-z)")]
35 pub lowercase: bool,
36
37 #[arg(short = 'n', long, action, help = "Include numbers (0-9)")]
38 pub number: bool,
39
40 #[arg(
41 short = 's',
42 long,
43 action,
44 help = "Include special characters (!@#$%^&*)"
45 )]
46 pub special: bool,
47
48 #[arg(long, default_value = "14", help = "Length of generated password")]
49 pub length: u8,
50
51 #[arg(
55 long,
56 alias = "minNumber",
57 default_value = "0",
58 help = "Minimum number of numeric characters"
59 )]
60 pub min_number: u8,
61
62 #[arg(
63 long,
64 alias = "minSpecial",
65 default_value = "0",
66 help = "Minimum number of special characters"
67 )]
68 pub min_special: u8,
69
70 #[arg(long, action, help = "Avoid ambiguous characters")]
71 pub ambiguous: bool,
72
73 #[arg(short = 'p', long, action, help = "Generate a passphrase")]
75 pub passphrase: bool,
76
77 #[arg(long, default_value = "6", help = "Number of words in the passphrase")]
78 pub words: u8,
79
80 #[arg(long, default_value = "-", help = "Separator between words")]
81 pub separator: String,
82
83 #[arg(long, action, help = "Title case passphrase.")]
84 pub capitalize: bool,
85
86 #[arg(
87 long,
88 alias = "includeNumber",
89 action,
90 help = "Include a number in one of the words"
91 )]
92 pub include_number: bool,
93}
94
95impl GenerateArgs {
96 pub fn run(self, client: &PasswordManagerClient) -> CommandResult {
97 let result = if self.passphrase {
98 client.generator().passphrase(PassphraseGeneratorRequest {
99 num_words: self
102 .words
103 .clamp(MINIMUM_PASSPHRASE_NUM_WORDS, MAXIMUM_PASSPHRASE_NUM_WORDS),
104 word_separator: normalize_separator(self.separator),
105 capitalize: self.capitalize,
106 include_number: self.include_number,
107 })?
108 } else {
109 let any_explicit = self.lowercase || self.uppercase || self.number || self.special;
112 let lowercase = if any_explicit { self.lowercase } else { true };
113 let uppercase = if any_explicit { self.uppercase } else { true };
114 let number = if any_explicit {
117 self.number || self.min_number > 0
118 } else {
119 true
120 };
121 let special = self.special || self.min_special > 0;
122
123 client.generator().password(PasswordGeneratorRequest {
124 lowercase,
125 uppercase,
126 numbers: number,
127 special,
128 length: self
129 .length
130 .clamp(MINIMUM_PASSWORD_LENGTH, MAXIMUM_PASSWORD_LENGTH),
131 min_number: Some(
132 self.min_number
133 .clamp(MINIMUM_MIN_CHAR_COUNT, MAXIMUM_MIN_CHAR_COUNT),
134 ),
135 min_special: Some(
136 self.min_special
137 .clamp(MINIMUM_MIN_CHAR_COUNT, MAXIMUM_MIN_CHAR_COUNT),
138 ),
139 avoid_ambiguous: self.ambiguous,
140 ..Default::default()
141 })?
142 };
143
144 Ok(result.into())
145 }
146}
147
148fn normalize_separator(separator: String) -> String {
151 match separator.as_str() {
152 "space" => " ".to_string(),
153 "empty" => String::new(),
154 s if s.len() > 1 => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
155 _ => separator,
156 }
157}
158
159#[derive(Args, Clone)]
160pub struct GetSendArgs {
161 pub id: String,
162}
163
164#[derive(Args, Clone)]
165pub struct ImportArgs {
166 pub format: Option<String>,
168 pub input: Option<String>,
170
171 #[arg(long, help = "List formats")]
172 pub formats: bool,
173
174 #[arg(
175 long,
176 alias = "organizationid",
177 help = "ID of the organization to import to."
178 )]
179 pub organization_id: Option<String>,
180}
181
182#[derive(Args, Clone)]
183pub struct ExportArgs {
184 #[arg(long, help = "Output directory or filename.")]
185 pub output: Option<String>,
186
187 #[arg(long, help = "Export file format.")]
188 pub format: Option<String>,
189
190 #[arg(
191 long,
192 help = "Use password to encrypt instead of your Bitwarden account encryption key."
193 )]
194 pub password: Option<String>,
195
196 #[arg(
197 long,
198 alias = "organizationid",
199 help = "Organization id for an organization."
200 )]
201 pub organization_id: Option<String>,
202}
203
204#[derive(Args, Clone)]
205pub struct SendArgs {
206 pub data: Option<String>,
208
209 #[arg(short = 'f', long, help = "Specifies that <data> is a filepath.")]
210 pub file: bool,
211
212 #[arg(
213 short = 'd',
214 long = "deleteInDays",
215 help = "The number of days in the future to set deletion date.",
216 default_value = "7"
217 )]
218 pub delete_in_days: String,
219
220 #[arg(long, help = "Optional password to access this Send.")]
221 pub password: Option<String>,
222
223 #[arg(
224 short = 'a',
225 long = "maxAccessCount",
226 help = "The amount of max possible accesses."
227 )]
228 pub max_access_count: Option<u32>,
229
230 #[arg(long, help = "Hide <data> in web by default.")]
231 pub hidden: bool,
232
233 #[arg(short = 'n', long, help = "The name of the Send.")]
234 pub name: Option<String>,
235
236 #[arg(long, help = "Notes to add to the Send.")]
237 pub notes: Option<String>,
238
239 #[arg(
240 long = "fullObject",
241 help = "Specifies that the full Send object should be returned."
242 )]
243 pub full_object: bool,
244
245 #[command(subcommand)]
246 pub command: Option<SendCommands>,
247}
248
249#[derive(Subcommand, Clone, Debug)]
250pub enum SendCommands {
251 #[command(about = "List all the Sends owned by you.")]
252 List,
253
254 #[command(about = "Get json templates for send objects.")]
255 Template { object: String },
256
257 #[command(about = "Get Sends owned by you.")]
258 Get {
259 id: String,
260
261 #[arg(long, help = "Specify a file path to save a File-type Send to.")]
262 output: Option<String>,
263
264 #[arg(long, help = "Only return the access url.")]
265 text: bool,
266 },
267
268 #[command(about = "Access a Bitwarden Send from a url.")]
269 Receive {
270 url: String,
271
272 #[arg(long, help = "Optional password for the Send.")]
273 password: Option<String>,
274
275 #[arg(long, help = "Specify a file path to save a File-type Send to.")]
276 obj: Option<String>,
277 },
278
279 #[command(about = "Create a Send.")]
280 Create {
281 encoded_json: Option<String>,
282
283 #[arg(short = 'f', long, help = "Path to the file to Send.")]
284 file: Option<String>,
285
286 #[arg(long, help = "Text to Send.")]
287 text: Option<String>,
288
289 #[arg(
290 short = 'd',
291 long = "deleteInDays",
292 help = "The number of days in the future to set deletion date.",
293 default_value = "7"
294 )]
295 delete_in_days: String,
296
297 #[arg(
298 long = "maxAccessCount",
299 help = "The maximum number of times this Send can be accessed."
300 )]
301 max_access_count: Option<u32>,
302
303 #[arg(long, help = "Hide text.")]
304 hidden: bool,
305
306 #[arg(short = 'n', long, help = "The name of the Send.")]
307 name: Option<String>,
308
309 #[arg(long, help = "Notes to add to the Send.")]
310 notes: Option<String>,
311
312 #[arg(long, help = "Optional password to access this Send.")]
313 password: Option<String>,
314
315 #[arg(
316 long = "fullObject",
317 help = "Return full Send object instead of access url."
318 )]
319 full_object: bool,
320 },
321
322 #[command(about = "Edit a Send.")]
323 Edit {
324 encoded_json: Option<String>,
325
326 #[arg(long, help = "Overrides the itemId provided in encodedJson.")]
327 itemid: Option<String>,
328
329 #[arg(
330 short = 'd',
331 long = "deleteInDays",
332 help = "The number of days in the future to set deletion date."
333 )]
334 delete_in_days: Option<String>,
335
336 #[arg(
337 long = "maxAccessCount",
338 help = "The maximum number of times this Send can be accessed."
339 )]
340 max_access_count: Option<u32>,
341
342 #[arg(long, help = "Hide text.")]
343 hidden: bool,
344 },
345
346 #[command(about = "Removes the saved password from a Send.")]
347 RemovePassword { id: String },
348
349 #[command(about = "Delete a Send.")]
350 Delete { id: String },
351}
352
353#[derive(Args, Clone)]
354pub struct ReceiveArgs {
355 pub url: String,
357
358 #[arg(long, help = "Optional password for the Send.")]
359 pub password: Option<String>,
360
361 #[arg(long, help = "Specify a file path to save a File-type Send to.")]
362 pub obj: Option<String>,
363}