1
1
extern crate keyring;
2
2
3
- use clap:: Parser ;
3
+ use clap:: { Args , Parser } ;
4
4
use std:: collections:: HashMap ;
5
5
6
6
use keyring:: { Entry , Error , Result } ;
@@ -22,57 +22,52 @@ fn main() {
22
22
} ;
23
23
match & args. command {
24
24
Command :: Set { .. } => {
25
- let ( secret, password, attributes) = args. get_password_and_attributes ( ) ;
26
- if secret. is_none ( ) && password. is_none ( ) && attributes. is_none ( ) {
27
- eprintln ! ( "You must provide either a password or attributes to the set command" ) ;
28
- std:: process:: exit ( 1 ) ;
29
- }
30
- if let Some ( secret) = secret {
31
- match entry. set_secret ( & secret) {
32
- Ok ( ( ) ) => args. success_message_for ( Some ( & secret) , None , None ) ,
33
- Err ( err) => args. error_message_for ( err) ,
34
- }
35
- }
36
- if let Some ( password) = password {
37
- match entry. set_password ( & password) {
38
- Ok ( ( ) ) => args. success_message_for ( None , Some ( & password) , None ) ,
25
+ let value = args. get_password_and_attributes ( ) ;
26
+ match & value {
27
+ Value :: Secret ( secret) => match entry. set_secret ( secret) {
28
+ Ok ( ( ) ) => args. success_message_for ( & value) ,
39
29
Err ( err) => args. error_message_for ( err) ,
40
- }
41
- }
42
- if let Some ( attributes) = attributes {
43
- let attrs: HashMap < & str , & str > = attributes
44
- . iter ( )
45
- . map ( |( key, value) | ( key. as_str ( ) , value. as_str ( ) ) )
46
- . collect ( ) ;
47
- match entry. update_attributes ( & attrs) {
48
- Ok ( ( ) ) => args. success_message_for ( None , None , Some ( attributes) ) ,
30
+ } ,
31
+ Value :: Password ( password) => match entry. set_password ( password) {
32
+ Ok ( ( ) ) => args. success_message_for ( & value) ,
49
33
Err ( err) => args. error_message_for ( err) ,
34
+ } ,
35
+ Value :: Attributes ( attributes) => {
36
+ let attrs: HashMap < & str , & str > = attributes
37
+ . iter ( )
38
+ . map ( |( k, v) | ( k. as_str ( ) , v. as_str ( ) ) )
39
+ . collect ( ) ;
40
+ match entry. update_attributes ( & attrs) {
41
+ Ok ( ( ) ) => args. success_message_for ( & value) ,
42
+ Err ( err) => args. error_message_for ( err) ,
43
+ }
50
44
}
45
+ _ => panic ! ( "Can't set without a value" ) ,
51
46
}
52
47
}
53
48
Command :: Password => match entry. get_password ( ) {
54
49
Ok ( password) => {
55
50
println ! ( "{password}" ) ;
56
- args. success_message_for ( None , Some ( & password) , None ) ;
51
+ args. success_message_for ( & Value :: Password ( password) ) ;
57
52
}
58
53
Err ( err) => args. error_message_for ( err) ,
59
54
} ,
60
55
Command :: Secret => match entry. get_secret ( ) {
61
56
Ok ( secret) => {
62
57
println ! ( "{}" , secret_string( & secret) ) ;
63
- args. success_message_for ( Some ( & secret) , None , None ) ;
58
+ args. success_message_for ( & Value :: Secret ( secret) ) ;
64
59
}
65
60
Err ( err) => args. error_message_for ( err) ,
66
61
} ,
67
62
Command :: Attributes => match entry. get_attributes ( ) {
68
63
Ok ( attributes) => {
69
64
println ! ( "{}" , attributes_string( & attributes) ) ;
70
- args. success_message_for ( None , None , Some ( attributes) ) ;
65
+ args. success_message_for ( & Value :: Attributes ( attributes) ) ;
71
66
}
72
67
Err ( err) => args. error_message_for ( err) ,
73
68
} ,
74
69
Command :: Delete => match entry. delete_credential ( ) {
75
- Ok ( ( ) ) => args. success_message_for ( None , None , None ) ,
70
+ Ok ( ( ) ) => args. success_message_for ( & Value :: None ) ,
76
71
Err ( err) => args. error_message_for ( err) ,
77
72
} ,
78
73
}
@@ -105,24 +100,15 @@ pub struct Cli {
105
100
106
101
#[ derive( Debug , Parser ) ]
107
102
pub enum Command {
108
- /// Set the password and, optionally, attributes in the secure store
103
+ /// Set the password or update the attributes in the secure store
109
104
Set {
110
- #[ clap( short, long, action) ]
111
- /// The password is base64-encoded binary
112
- binary : bool ,
113
-
114
- #[ clap( short, long, value_parser, default_value = "" ) ]
115
- attributes : String ,
105
+ #[ command( flatten) ]
106
+ what : What ,
116
107
117
108
#[ clap( value_parser) ]
118
- /// The password to set into the secure store.
119
- /// If it's a valid base64 encoding (with padding),
120
- /// it will be decoded and used to set the binary secret.
121
- /// Otherwise, it will be interpreted as a string password.
122
- /// If no password is specified, it will be
123
- /// collected interactively (without echo)
124
- /// from the terminal.
125
- password : Option < String > ,
109
+ /// The input to parse. If not specified, it will be
110
+ /// read interactively (without echo) from the terminal.
111
+ input : Option < String > ,
126
112
} ,
127
113
/// Retrieve the (string) password from the secure store
128
114
/// and write it to the standard output.
@@ -132,9 +118,33 @@ pub enum Command {
132
118
Secret ,
133
119
/// Retrieve attributes available in the secure store.
134
120
Attributes ,
121
+ /// Delete the credential from the secure store.
135
122
Delete ,
136
123
}
137
124
125
+ #[ derive( Debug , Args ) ]
126
+ #[ group( multiple = false ) ]
127
+ pub struct What {
128
+ #[ clap( short, long, action) ]
129
+ // The input is a password.
130
+ password : bool ,
131
+
132
+ #[ clap( short, long, action) ]
133
+ // The input is a base64-encoded secret.
134
+ secret : bool ,
135
+
136
+ #[ clap( short, long, action) ]
137
+ // The input is comma-separated, key=val attribute pairs.
138
+ attributes : bool ,
139
+ }
140
+
141
+ enum Value {
142
+ Secret ( Vec < u8 > ) ,
143
+ Password ( String ) ,
144
+ Attributes ( HashMap < String , String > ) ,
145
+ None ,
146
+ }
147
+
138
148
impl Cli {
139
149
fn description ( & self ) -> String {
140
150
if let Some ( target) = & self . target {
@@ -184,78 +194,67 @@ impl Cli {
184
194
std:: process:: exit ( 1 )
185
195
}
186
196
187
- fn success_message_for (
188
- & self ,
189
- secret : Option < & [ u8 ] > ,
190
- password : Option < & str > ,
191
- attributes : Option < HashMap < String , String > > ,
192
- ) {
197
+ fn success_message_for ( & self , value : & Value ) {
193
198
if !self . verbose {
194
199
return ;
195
200
}
196
201
let description = self . description ( ) ;
197
202
match self . command {
198
- Command :: Set { .. } => {
199
- if let Some ( pw) = password {
200
- eprintln ! ( "Set password for '{description}' to '{pw}'" ) ;
201
- }
202
- if let Some ( secret) = secret {
203
+ Command :: Set { .. } => match value {
204
+ Value :: Secret ( secret) => {
203
205
let secret = secret_string ( secret) ;
204
206
eprintln ! ( "Set secret for '{description}' to decode of '{secret}'" ) ;
205
207
}
206
- if let Some ( attributes) = attributes {
208
+ Value :: Password ( password) => {
209
+ eprintln ! ( "Set password for '{description}' to '{password}'" ) ;
210
+ }
211
+ Value :: Attributes ( attributes) => {
207
212
eprintln ! ( "Set attributes for '{description}' to:" ) ;
208
213
eprint_attributes ( attributes) ;
209
214
}
210
- }
215
+ _ => panic ! ( "Can't set without a value" ) ,
216
+ } ,
211
217
Command :: Password => {
212
- let pw = password . unwrap ( ) ;
213
- eprintln ! ( "Password for '{description}' is '{pw}'" ) ;
214
- }
215
- Command :: Secret => {
216
- let secret = secret_string ( secret . unwrap ( ) ) ;
217
- eprintln ! ( "Secret for '{description}' encodes as {secret}" ) ;
218
+ match value {
219
+ Value :: Password ( password ) => {
220
+ eprintln ! ( "Password for '{description}' is '{password}'" ) ;
221
+ }
222
+ _ => panic ! ( "Wrong value type for command" ) ,
223
+ } ;
218
224
}
219
- Command :: Attributes => {
220
- let attributes = attributes. unwrap ( ) ;
221
- if attributes. is_empty ( ) {
222
- eprintln ! ( "No attributes found for '{description}'" ) ;
223
- } else {
224
- eprintln ! ( "Attributes for '{description}' are:" ) ;
225
- eprint_attributes ( attributes) ;
225
+ Command :: Secret => match value {
226
+ Value :: Secret ( secret) => {
227
+ let encoded = secret_string ( secret) ;
228
+ eprintln ! ( "Secret for '{description}' encodes as {encoded}" ) ;
226
229
}
227
- }
230
+ _ => panic ! ( "Wrong value type for command" ) ,
231
+ } ,
232
+ Command :: Attributes => match value {
233
+ Value :: Attributes ( attributes) => {
234
+ if attributes. is_empty ( ) {
235
+ eprintln ! ( "No attributes found for '{description}'" ) ;
236
+ } else {
237
+ eprintln ! ( "Attributes for '{description}' are:" ) ;
238
+ eprint_attributes ( attributes) ;
239
+ }
240
+ }
241
+ _ => panic ! ( "Wrong value type for command" ) ,
242
+ } ,
228
243
Command :: Delete => {
229
244
eprintln ! ( "Successfully deleted credential for '{description}'" ) ;
230
245
}
231
246
}
232
247
}
233
248
234
- fn get_password_and_attributes (
235
- & self ,
236
- ) -> (
237
- Option < Vec < u8 > > ,
238
- Option < String > ,
239
- Option < HashMap < String , String > > ,
240
- ) {
241
- if let Command :: Set {
242
- binary,
243
- attributes,
244
- password,
245
- } = & self . command
246
- {
247
- let secret = if * binary {
248
- Some ( decode_secret ( password) )
249
- } else {
250
- None
251
- } ;
252
- let password = if !* binary {
253
- Some ( read_password ( password) )
249
+ fn get_password_and_attributes ( & self ) -> Value {
250
+ if let Command :: Set { what, input } = & self . command {
251
+ if what. password || ( !what. secret && !what. attributes ) {
252
+ Value :: Password ( read_password ( input) )
253
+ } else if what. secret {
254
+ Value :: Secret ( decode_secret ( input) )
254
255
} else {
255
- None
256
- } ;
257
- let attributes = parse_attributes ( attributes) ;
258
- ( secret, password, attributes)
256
+ Value :: Attributes ( parse_attributes ( input) )
257
+ }
259
258
} else {
260
259
panic ! ( "Can't happen: asking for password and attributes on non-set command" )
261
260
}
@@ -268,7 +267,7 @@ fn secret_string(secret: &[u8]) -> String {
268
267
BASE64_STANDARD . encode ( secret)
269
268
}
270
269
271
- fn eprint_attributes ( attributes : HashMap < String , String > ) {
270
+ fn eprint_attributes ( attributes : & HashMap < String , String > ) {
272
271
for ( key, value) in attributes {
273
272
println ! ( " {key}: {value}" ) ;
274
273
}
@@ -295,12 +294,11 @@ fn decode_secret(input: &Option<String>) -> Vec<u8> {
295
294
}
296
295
297
296
fn read_password ( input : & Option < String > ) -> String {
298
- let password = if let Some ( input) = input {
297
+ if let Some ( input) = input {
299
298
input. clone ( )
300
299
} else {
301
300
rpassword:: prompt_password ( "Password: " ) . unwrap_or_else ( |_| String :: new ( ) )
302
- } ;
303
- password
301
+ }
304
302
}
305
303
306
304
fn attributes_string ( attributes : & HashMap < String , String > ) -> String {
@@ -311,9 +309,14 @@ fn attributes_string(attributes: &HashMap<String, String>) -> String {
311
309
strings. join ( "," )
312
310
}
313
311
314
- fn parse_attributes ( input : & String ) -> Option < HashMap < String , String > > {
312
+ fn parse_attributes ( input : & Option < String > ) -> HashMap < String , String > {
313
+ let input = if let Some ( input) = input {
314
+ input. clone ( )
315
+ } else {
316
+ rpassword:: prompt_password ( "Attributes: " ) . unwrap_or_else ( |_| String :: new ( ) )
317
+ } ;
315
318
if input. is_empty ( ) {
316
- return None ;
319
+ eprintln ! ( "You must specify at least one key=value attribute pair to set" )
317
320
}
318
321
let mut attributes = HashMap :: new ( ) ;
319
322
let parts = input. split ( ',' ) ;
@@ -325,5 +328,5 @@ fn parse_attributes(input: &String) -> Option<HashMap<String, String>> {
325
328
}
326
329
attributes. insert ( parts[ 0 ] . to_string ( ) , parts[ 1 ] . to_string ( ) ) ;
327
330
}
328
- Some ( attributes)
331
+ attributes
329
332
}
0 commit comments