@@ -11,37 +11,30 @@ const CARGO_TAG: &str = "cargo-registry";
11
11
struct OnePasswordKeychain {
12
12
account : Option < String > ,
13
13
vault : Option < String > ,
14
- sign_in_address : Option < String > ,
15
- email : Option < String > ,
16
14
}
17
15
18
- /// 1password Login item type, used for the JSON output of `op get item`.
16
+ /// 1password Login item type, used for the JSON output of `op item get `.
19
17
#[ derive( Deserialize ) ]
20
18
struct Login {
21
- details : Details ,
22
- }
23
-
24
- #[ derive( Deserialize ) ]
25
- struct Details {
26
19
fields : Vec < Field > ,
27
20
}
28
21
29
22
#[ derive( Deserialize ) ]
30
23
struct Field {
31
- designation : String ,
32
- value : String ,
24
+ id : String ,
25
+ value : Option < String > ,
33
26
}
34
27
35
- /// 1password item from `op list items`.
28
+ /// 1password item from `op items list `.
36
29
#[ derive( Deserialize ) ]
37
30
struct ListItem {
38
- uuid : String ,
39
- overview : Overview ,
31
+ id : String ,
32
+ urls : Vec < Url > ,
40
33
}
41
34
42
35
#[ derive( Deserialize ) ]
43
- struct Overview {
44
- url : String ,
36
+ struct Url {
37
+ href : String ,
45
38
}
46
39
47
40
impl OnePasswordKeychain {
@@ -50,8 +43,6 @@ impl OnePasswordKeychain {
50
43
let mut action = false ;
51
44
let mut account = None ;
52
45
let mut vault = None ;
53
- let mut sign_in_address = None ;
54
- let mut email = None ;
55
46
while let Some ( arg) = args. next ( ) {
56
47
match arg. as_str ( ) {
57
48
"--account" => {
@@ -60,12 +51,6 @@ impl OnePasswordKeychain {
60
51
"--vault" => {
61
52
vault = Some ( args. next ( ) . ok_or ( "--vault needs an arg" ) ?) ;
62
53
}
63
- "--sign-in-address" => {
64
- sign_in_address = Some ( args. next ( ) . ok_or ( "--sign-in-address needs an arg" ) ?) ;
65
- }
66
- "--email" => {
67
- email = Some ( args. next ( ) . ok_or ( "--email needs an arg" ) ?) ;
68
- }
69
54
s if s. starts_with ( '-' ) => {
70
55
return Err ( format ! ( "unknown option {}" , s) . into ( ) ) ;
71
56
}
@@ -78,15 +63,7 @@ impl OnePasswordKeychain {
78
63
}
79
64
}
80
65
}
81
- if sign_in_address. is_none ( ) && email. is_some ( ) {
82
- return Err ( "--email requires --sign-in-address" . into ( ) ) ;
83
- }
84
- Ok ( OnePasswordKeychain {
85
- account,
86
- vault,
87
- sign_in_address,
88
- email,
89
- } )
66
+ Ok ( OnePasswordKeychain { account, vault } )
90
67
}
91
68
92
69
fn signin ( & self ) -> Result < Option < String > , Error > {
@@ -96,24 +73,9 @@ impl OnePasswordKeychain {
96
73
return Ok ( None ) ;
97
74
}
98
75
let mut cmd = Command :: new ( "op" ) ;
99
- cmd. arg ( "signin" ) ;
100
- if let Some ( addr) = & self . sign_in_address {
101
- cmd. arg ( addr) ;
102
- if let Some ( email) = & self . email {
103
- cmd. arg ( email) ;
104
- }
105
- }
106
- cmd. arg ( "--raw" ) ;
76
+ cmd. args ( & [ "signin" , "--raw" ] ) ;
107
77
cmd. stdout ( Stdio :: piped ( ) ) ;
108
- #[ cfg( unix) ]
109
- const IN_DEVICE : & str = "/dev/tty" ;
110
- #[ cfg( windows) ]
111
- const IN_DEVICE : & str = "CONIN$" ;
112
- let stdin = std:: fs:: OpenOptions :: new ( )
113
- . read ( true )
114
- . write ( true )
115
- . open ( IN_DEVICE ) ?;
116
- cmd. stdin ( stdin) ;
78
+ self . with_tty ( & mut cmd) ?;
117
79
let mut child = cmd
118
80
. spawn ( )
119
81
. map_err ( |e| format ! ( "failed to spawn `op`: {}" , e) ) ?;
@@ -133,6 +95,11 @@ impl OnePasswordKeychain {
133
95
if !status. success ( ) {
134
96
return Err ( format ! ( "failed to run `op signin`: {}" , status) . into ( ) ) ;
135
97
}
98
+ if buffer. is_empty ( ) {
99
+ // When using CLI integration, `op signin` returns no output,
100
+ // so there is no need to set the session.
101
+ return Ok ( None ) ;
102
+ }
136
103
Ok ( Some ( buffer) )
137
104
}
138
105
@@ -154,6 +121,19 @@ impl OnePasswordKeychain {
154
121
cmd
155
122
}
156
123
124
+ fn with_tty ( & self , cmd : & mut Command ) -> Result < ( ) , Error > {
125
+ #[ cfg( unix) ]
126
+ const IN_DEVICE : & str = "/dev/tty" ;
127
+ #[ cfg( windows) ]
128
+ const IN_DEVICE : & str = "CONIN$" ;
129
+ let stdin = std:: fs:: OpenOptions :: new ( )
130
+ . read ( true )
131
+ . write ( true )
132
+ . open ( IN_DEVICE ) ?;
133
+ cmd. stdin ( stdin) ;
134
+ Ok ( ( ) )
135
+ }
136
+
157
137
fn run_cmd ( & self , mut cmd : Command ) -> Result < String , Error > {
158
138
cmd. stdout ( Stdio :: piped ( ) ) ;
159
139
let mut child = cmd
@@ -179,20 +159,22 @@ impl OnePasswordKeychain {
179
159
let cmd = self . make_cmd (
180
160
session,
181
161
& [
182
- "list" ,
183
162
"items" ,
163
+ "list" ,
184
164
"--categories" ,
185
165
"Login" ,
186
166
"--tags" ,
187
167
CARGO_TAG ,
168
+ "--format" ,
169
+ "json" ,
188
170
] ,
189
171
) ;
190
172
let buffer = self . run_cmd ( cmd) ?;
191
173
let items: Vec < ListItem > = serde_json:: from_str ( & buffer)
192
174
. map_err ( |e| format ! ( "failed to deserialize JSON from 1password list: {}" , e) ) ?;
193
175
let mut matches = items
194
176
. into_iter ( )
195
- . filter ( |item| item. overview . url == index_url) ;
177
+ . filter ( |item| item. urls . iter ( ) . any ( | url| url . href == index_url) ) ;
196
178
match matches. next ( ) {
197
179
Some ( login) => {
198
180
// Should this maybe just sort on `updatedAt` and return the newest one?
@@ -204,7 +186,7 @@ impl OnePasswordKeychain {
204
186
)
205
187
. into ( ) ) ;
206
188
}
207
- Ok ( Some ( login. uuid ) )
189
+ Ok ( Some ( login. id ) )
208
190
}
209
191
None => Ok ( None ) ,
210
192
}
@@ -213,13 +195,13 @@ impl OnePasswordKeychain {
213
195
fn modify (
214
196
& self ,
215
197
session : & Option < String > ,
216
- uuid : & str ,
198
+ id : & str ,
217
199
token : & str ,
218
200
_name : Option < & str > ,
219
201
) -> Result < ( ) , Error > {
220
202
let cmd = self . make_cmd (
221
203
session,
222
- & [ "edit " , "item " , uuid , & format ! ( "password={}" , token) ] ,
204
+ & [ "item " , "edit " , id , & format ! ( "password={}" , token) ] ,
223
205
) ;
224
206
self . run_cmd ( cmd) ?;
225
207
Ok ( ( ) )
@@ -236,11 +218,12 @@ impl OnePasswordKeychain {
236
218
Some ( name) => format ! ( "Cargo registry token for {}" , name) ,
237
219
None => "Cargo registry token" . to_string ( ) ,
238
220
} ;
239
- let cmd = self . make_cmd (
221
+ let mut cmd = self . make_cmd (
240
222
session,
241
223
& [
242
- "create" ,
243
224
"item" ,
225
+ "create" ,
226
+ "--category" ,
244
227
"Login" ,
245
228
& format ! ( "password={}" , token) ,
246
229
& format ! ( "url={}" , index_url) ,
@@ -250,28 +233,30 @@ impl OnePasswordKeychain {
250
233
CARGO_TAG ,
251
234
] ,
252
235
) ;
236
+ // For unknown reasons, `op item create` seems to not be happy if
237
+ // stdin is not a tty. Otherwise it returns with a 0 exit code without
238
+ // doing anything.
239
+ self . with_tty ( & mut cmd) ?;
253
240
self . run_cmd ( cmd) ?;
254
241
Ok ( ( ) )
255
242
}
256
243
257
- fn get_token ( & self , session : & Option < String > , uuid : & str ) -> Result < String , Error > {
258
- let cmd = self . make_cmd ( session, & [ "get" , "item " , uuid ] ) ;
244
+ fn get_token ( & self , session : & Option < String > , id : & str ) -> Result < String , Error > {
245
+ let cmd = self . make_cmd ( session, & [ "item" , " get", "--format=json " , id ] ) ;
259
246
let buffer = self . run_cmd ( cmd) ?;
260
247
let item: Login = serde_json:: from_str ( & buffer)
261
248
. map_err ( |e| format ! ( "failed to deserialize JSON from 1password get: {}" , e) ) ?;
262
- let password = item
263
- . details
264
- . fields
265
- . into_iter ( )
266
- . find ( |item| item. designation == "password" ) ;
249
+ let password = item. fields . into_iter ( ) . find ( |item| item. id == "password" ) ;
267
250
match password {
268
- Some ( password) => Ok ( password. value ) ,
251
+ Some ( password) => password
252
+ . value
253
+ . ok_or_else ( || format ! ( "missing password value for entry" ) . into ( ) ) ,
269
254
None => Err ( "could not find password field" . into ( ) ) ,
270
255
}
271
256
}
272
257
273
- fn delete ( & self , session : & Option < String > , uuid : & str ) -> Result < ( ) , Error > {
274
- let cmd = self . make_cmd ( session, & [ "delete " , "item " , uuid ] ) ;
258
+ fn delete ( & self , session : & Option < String > , id : & str ) -> Result < ( ) , Error > {
259
+ let cmd = self . make_cmd ( session, & [ "item " , "delete " , id ] ) ;
275
260
self . run_cmd ( cmd) ?;
276
261
Ok ( ( ) )
277
262
}
@@ -284,8 +269,8 @@ impl Credential for OnePasswordKeychain {
284
269
285
270
fn get ( & self , index_url : & str ) -> Result < String , Error > {
286
271
let session = self . signin ( ) ?;
287
- if let Some ( uuid ) = self . search ( & session, index_url) ? {
288
- self . get_token ( & session, & uuid )
272
+ if let Some ( id ) = self . search ( & session, index_url) ? {
273
+ self . get_token ( & session, & id )
289
274
} else {
290
275
return Err ( format ! (
291
276
"no 1password entry found for registry `{}`, try `cargo login` to add a token" ,
@@ -298,8 +283,8 @@ impl Credential for OnePasswordKeychain {
298
283
fn store ( & self , index_url : & str , token : & str , name : Option < & str > ) -> Result < ( ) , Error > {
299
284
let session = self . signin ( ) ?;
300
285
// Check if an item already exists.
301
- if let Some ( uuid ) = self . search ( & session, index_url) ? {
302
- self . modify ( & session, & uuid , token, name)
286
+ if let Some ( id ) = self . search ( & session, index_url) ? {
287
+ self . modify ( & session, & id , token, name)
303
288
} else {
304
289
self . create ( & session, index_url, token, name)
305
290
}
@@ -308,8 +293,8 @@ impl Credential for OnePasswordKeychain {
308
293
fn erase ( & self , index_url : & str ) -> Result < ( ) , Error > {
309
294
let session = self . signin ( ) ?;
310
295
// Check if an item already exists.
311
- if let Some ( uuid ) = self . search ( & session, index_url) ? {
312
- self . delete ( & session, & uuid ) ?;
296
+ if let Some ( id ) = self . search ( & session, index_url) ? {
297
+ self . delete ( & session, & id ) ?;
313
298
} else {
314
299
eprintln ! ( "not currently logged in to `{}`" , index_url) ;
315
300
}
0 commit comments