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;
8
9use crate::render::CommandResult;
10
11mod send;
12pub use send::SendArgs;
13
14#[derive(Args, Clone)]
15#[command(
16 about = "Generate a password/passphrase.",
17 after_help = r#"Notes:
18 Default options are `-uln --length 14`.
19 Minimum `length` is 5.
20 Minimum `words` is 3.
21
22Examples:
23 bw generate
24 bw generate -u -l --length 18
25 bw generate -ulns --length 25
26 bw generate -ul
27 bw generate -p --separator _
28 bw generate -p --words 5 --separator space
29 bw generate -p --words 5 --separator empty
30 "#
31)]
32pub struct GenerateArgs {
33 #[arg(short = 'u', long, action, help = "Include uppercase characters (A-Z)")]
35 pub uppercase: bool,
36
37 #[arg(short = 'l', long, action, help = "Include lowercase characters (a-z)")]
38 pub lowercase: bool,
39
40 #[arg(short = 'n', long, action, help = "Include numbers (0-9)")]
41 pub number: bool,
42
43 #[arg(
44 short = 's',
45 long,
46 action,
47 help = "Include special characters (!@#$%^&*)"
48 )]
49 pub special: bool,
50
51 #[arg(long, default_value = "14", help = "Length of generated password")]
52 pub length: u8,
53
54 #[arg(
58 long,
59 alias = "minNumber",
60 default_value = "0",
61 help = "Minimum number of numeric characters"
62 )]
63 pub min_number: u8,
64
65 #[arg(
66 long,
67 alias = "minSpecial",
68 default_value = "0",
69 help = "Minimum number of special characters"
70 )]
71 pub min_special: u8,
72
73 #[arg(long, action, help = "Avoid ambiguous characters")]
74 pub ambiguous: bool,
75
76 #[arg(short = 'p', long, action, help = "Generate a passphrase")]
78 pub passphrase: bool,
79
80 #[arg(long, default_value = "6", help = "Number of words in the passphrase")]
81 pub words: u8,
82
83 #[arg(long, default_value = "-", help = "Separator between words")]
84 pub separator: String,
85
86 #[arg(long, action, help = "Title case passphrase.")]
87 pub capitalize: bool,
88
89 #[arg(
90 long,
91 alias = "includeNumber",
92 action,
93 help = "Include a number in one of the words"
94 )]
95 pub include_number: bool,
96}
97
98impl GenerateArgs {
99 pub fn run(self, client: &PasswordManagerClient) -> CommandResult {
100 let result = if self.passphrase {
101 client.generator().passphrase(PassphraseGeneratorRequest {
102 num_words: self
105 .words
106 .clamp(MINIMUM_PASSPHRASE_NUM_WORDS, MAXIMUM_PASSPHRASE_NUM_WORDS),
107 word_separator: normalize_separator(self.separator),
108 capitalize: self.capitalize,
109 include_number: self.include_number,
110 })?
111 } else {
112 let any_explicit = self.lowercase || self.uppercase || self.number || self.special;
115 let lowercase = if any_explicit { self.lowercase } else { true };
116 let uppercase = if any_explicit { self.uppercase } else { true };
117 let number = if any_explicit {
120 self.number || self.min_number > 0
121 } else {
122 true
123 };
124 let special = self.special || self.min_special > 0;
125
126 client.generator().password(PasswordGeneratorRequest {
127 lowercase,
128 uppercase,
129 numbers: number,
130 special,
131 length: self
132 .length
133 .clamp(MINIMUM_PASSWORD_LENGTH, MAXIMUM_PASSWORD_LENGTH),
134 min_number: Some(
135 self.min_number
136 .clamp(MINIMUM_MIN_CHAR_COUNT, MAXIMUM_MIN_CHAR_COUNT),
137 ),
138 min_special: Some(
139 self.min_special
140 .clamp(MINIMUM_MIN_CHAR_COUNT, MAXIMUM_MIN_CHAR_COUNT),
141 ),
142 avoid_ambiguous: self.ambiguous,
143 ..Default::default()
144 })?
145 };
146
147 Ok(result.into())
148 }
149}
150
151fn normalize_separator(separator: String) -> String {
154 match separator.as_str() {
155 "space" => " ".to_string(),
156 "empty" => String::new(),
157 s if s.len() > 1 => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
158 _ => separator,
159 }
160}
161
162#[derive(Args, Clone)]
163pub struct GetSendArgs {
164 pub id: String,
165}
166
167#[derive(Args, Clone)]
168pub struct ImportArgs {
169 pub format: Option<String>,
171 pub input: Option<String>,
173
174 #[arg(long, help = "List formats")]
175 pub formats: bool,
176
177 #[arg(
178 long,
179 alias = "organizationid",
180 help = "ID of the organization to import to."
181 )]
182 pub organization_id: Option<String>,
183}
184
185#[derive(Args, Clone)]
186pub struct ExportArgs {
187 #[arg(long, help = "Output directory or filename.")]
188 pub output: Option<String>,
189
190 #[arg(long, help = "Export file format.")]
191 pub format: Option<String>,
192
193 #[arg(
194 long,
195 help = "Use password to encrypt instead of your Bitwarden account encryption key."
196 )]
197 pub password: Option<String>,
198
199 #[arg(
200 long,
201 alias = "organizationid",
202 help = "Organization id for an organization."
203 )]
204 pub organization_id: Option<String>,
205}
206
207#[derive(Args, Clone)]
208pub struct ReceiveArgs {
209 pub url: String,
211
212 #[arg(long, help = "Optional password for the Send.")]
213 pub password: Option<String>,
214
215 #[arg(long, help = "Specify a file path to save a File-type Send to.")]
216 pub obj: Option<String>,
217}