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