1
1
/*!
2
2
3
- # Secret Service credential store
3
+ # secret-service credential store
4
4
5
- Items in tne secret service are identified by an arbitrary collection
5
+ Items in the secret- service are identified by an arbitrary collection
6
6
of attributes, and each has "label" for use in graphical editors. The keyring
7
7
client uses the following attributes:
8
8
@@ -18,29 +18,33 @@ will have their items found by our searches. (Items we write with
18
18
the same service and user attributes but different target attributes will also come
19
19
back in searches, but they are filtered out of the results automatically.)
20
20
21
- New credentials are always created with all four attributes, and if they have
21
+ New items are always created with all four attributes, and if they have
22
22
a non-default target then they are created in a collection whose label matches
23
23
the target (creating it if necessary).
24
24
25
- The [set_password] implementation always prefers to update the password on an
26
- existing credential, so it will only create a new item if it can't find one.
27
- This allows better compatibility with 3rd party clients, and reduces the chance
25
+ Setting the password on an entry will always update the password on an
26
+ existing item in preference to creating a new item.
27
+ This provides better compatibility with 3rd party clients that may already
28
+ have created items that match the entry, and reduces the chance
28
29
of ambiguity in later searches.
29
30
*/
30
31
use std:: collections:: HashMap ;
31
32
32
33
use secret_service:: blocking:: { Collection , Item , SecretService } ;
33
- pub use secret_service:: { EncryptionType , Error } ;
34
+ use secret_service:: { EncryptionType , Error } ;
34
35
35
36
use super :: credential:: { Credential , CredentialApi , CredentialBuilder , CredentialBuilderApi } ;
36
37
use super :: error:: { decode_password, Error as ErrorCode , Result } ;
37
38
39
+ /// The representation of an item in the secret-service.
40
+ ///
38
41
/// This structure has two roles. On the one hand, it captures all the
39
- /// information user specify for an [Entry] and so is the basis for our search
40
- /// (or creation) of items that match that entry. On the other hand, when
41
- /// a search is ambiguous, each item found is represented by a structure that
42
+ /// information a user specifies for an [Entry](crate::Entry)
43
+ /// and so is the basis for our search
44
+ /// (or creation) of an item for that entry. On the other hand, when
45
+ /// a search is ambiguous, each item found is represented by a credential that
42
46
/// has the same attributes and label as the item.
43
- #[ derive( Debug , Clone , PartialEq , Eq ) ]
47
+ #[ derive( Debug , Clone ) ]
44
48
pub struct SsCredential {
45
49
pub attributes : HashMap < String , String > ,
46
50
pub label : String ,
@@ -49,9 +53,13 @@ pub struct SsCredential {
49
53
50
54
impl CredentialApi for SsCredential {
51
55
/// Sets the password on a unique matching item, if it exists, or creates one if necessary.
52
- /// When creating, the item is put into a collection named by the credential's `target`,
53
- /// which must be explicitly specified. If there is more than one matching item,
54
- /// returns an [ErrorCode::Ambiguous] error with a credential for each one.
56
+ ///
57
+ /// If there are multiple matches,
58
+ /// returns an [Ambiguous](ErrorCode::Ambiguous) error with a credential for each
59
+ /// matching item.
60
+ ///
61
+ /// When creating, the item is put into a collection named by the credential's `target`
62
+ /// attribute.
55
63
fn set_password ( & self , password : & str ) -> Result < ( ) > {
56
64
let ss = SecretService :: connect ( EncryptionType :: Dh ) . map_err ( platform_failure) ?;
57
65
// first try to find a unique, existing, matching item and set its password
@@ -78,17 +86,25 @@ impl CredentialApi for SsCredential {
78
86
Ok ( ( ) )
79
87
}
80
88
81
- /// Gets the password on a unique matching item, if it exists. If there are no
82
- /// matching items, returns an [ErrorCode::NoEntry] error. If there is more than
83
- /// one matching item, returns an [ErrorCode::Ambiguous] error with a credential for each one.
89
+ /// Gets the password on a unique matching item, if it exists.
90
+ ///
91
+ /// If there are no
92
+ /// matching items, returns a [NoEntry](ErrorCode::NoEntry) error.
93
+ /// If there are multiple matches,
94
+ /// returns an [Ambiguous](ErrorCode::Ambiguous)
95
+ /// error with a credential for each matching item.
84
96
fn get_password ( & self ) -> Result < String > {
85
97
let passwords: Vec < String > = self . map_matching_items ( get_item_password, true ) ?;
86
98
Ok ( passwords[ 0 ] . clone ( ) )
87
99
}
88
100
89
- /// Deletes the unique matching item, if it exists. If there are no
90
- /// matching items, returns an [ErrorCode::NoEntry] error. If there is more than
91
- /// one matching item, returns an [ErrorCode::Ambiguous] error with a credential for each one.
101
+ /// Deletes the unique matching item, if it exists.
102
+ ///
103
+ /// If there are no
104
+ /// matching items, returns a [NoEntry](ErrorCode::NoEntry) error.
105
+ /// If there are multiple matches,
106
+ /// returns an [Ambiguous](ErrorCode::Ambiguous)
107
+ /// error with a credential for each matching item.
92
108
fn delete_password ( & self ) -> Result < ( ) > {
93
109
self . map_matching_items ( delete_item, true ) ?;
94
110
Ok ( ( ) )
@@ -104,9 +120,12 @@ impl CredentialApi for SsCredential {
104
120
impl SsCredential {
105
121
/// Create a credential for the given target, service, and user.
106
122
///
107
- /// Creating this credential does not create an Item in the secret service
108
- /// until this credential is wrapped in an [Entry] and [set_password] is
109
- /// called on that entry.
123
+ /// The target defaults to `default` (the default secret-service collection).
124
+ ///
125
+ /// Creating this credential does not create a matching item.
126
+ /// If there isn't already one there, it will be created only
127
+ /// when [set_password](SsCredential::set_password) is
128
+ /// called.
110
129
pub fn new_with_target ( target : Option < & str > , service : & str , user : & str ) -> Result < Self > {
111
130
if let Some ( "" ) = target {
112
131
return Err ( empty_target ( ) ) ;
@@ -128,12 +147,10 @@ impl SsCredential {
128
147
} )
129
148
}
130
149
131
- /// Create a credential from an underlying Item .
150
+ /// Create a credential from an underlying item .
132
151
///
133
152
/// The created credential will have all the attributes and label
134
- /// of the underlying item, so you can examine them. But it won't
135
- /// have a target (and so can't be used to create new a new item)
136
- /// unless the underlying item has a `target` attribute.
153
+ /// of the underlying item, so you can examine them.
137
154
pub fn new_from_item ( item : & Item ) -> Result < Self > {
138
155
let attributes = item. get_attributes ( ) . map_err ( decode_error) ?;
139
156
let target = attributes. get ( "target" ) . cloned ( ) ;
@@ -144,31 +161,39 @@ impl SsCredential {
144
161
} )
145
162
}
146
163
147
- /// Construct a credential from the underlying matching Item, if there is exactly one.
164
+ /// Construct a credential for this credential's underlying matching item,
165
+ /// if there is exactly one.
148
166
pub fn new_from_matching_item ( & self ) -> Result < Self > {
149
167
let credentials = self . map_matching_items ( Self :: new_from_item, true ) ?;
150
168
Ok ( credentials[ 0 ] . clone ( ) )
151
169
}
152
170
153
- /// If there are multiple matching Items, get all of their passwords.
154
- /// (This is useful if [get_password] returns an `Ambiguous` error.)
171
+ /// If there are multiple matching items for this credential, get all of their passwords.
172
+ ///
173
+ /// (This is useful if [get_password](SsCredential::get_password)
174
+ /// returns an [Ambiguous](ErrorCode::Ambiguous) error.)
155
175
pub fn get_all_passwords ( & self ) -> Result < Vec < String > > {
156
176
self . map_matching_items ( get_item_password, true )
157
177
}
158
178
159
- /// If there are multiple matching Items, delete all of them.
160
- /// (This is useful if [delete_password] returns an `Ambiguous` error.)
179
+ /// If there are multiple matching items for this credential, delete all of them.
180
+ ///
181
+ /// (This is useful if [delete_password](SsCredential::delete_password)
182
+ /// returns an [Ambiguous](ErrorCode::Ambiguous) error.)
161
183
pub fn delete_all_passwords ( & self ) -> Result < ( ) > {
162
184
self . map_matching_items ( delete_item, true ) ?;
163
185
Ok ( ( ) )
164
186
}
165
187
166
188
/// Map a function over all of the items matching this credential.
189
+ ///
167
190
/// Items are unlocked before the function is applied.
191
+ ///
168
192
/// If `require_unique` is true, and there are no matching items, then
169
- /// [ErrorCode::NoEntry] is returned.
170
- /// If `require_unique` is true, and there is more than one matching item,
171
- /// then[ErrorCode::Ambiguous] is returned with a vector containing one
193
+ /// a [NoEntry](ErrorCode::NoEntry) error is returned.
194
+ /// If `require_unique` is true, and there are multiple matches,
195
+ /// then an [Ambiguous](ErrorCode::Ambiguous) error is returned
196
+ /// with a vector containing one
172
197
/// credential for each of the matching items.
173
198
pub fn map_matching_items < F , T > ( & self , f : F , require_unique : bool ) -> Result < Vec < T > >
174
199
where
@@ -179,8 +204,8 @@ impl SsCredential {
179
204
let attributes: HashMap < & str , & str > = self . search_attributes ( ) . into_iter ( ) . collect ( ) ;
180
205
let search = ss. search_items ( attributes) . map_err ( decode_error) ?;
181
206
let target = self . target . as_ref ( ) . ok_or_else ( empty_target) ?;
182
- let unlocked = matching_items ( & search. unlocked , target) ?;
183
- let locked = matching_items ( & search. locked , target) ?;
207
+ let unlocked = matching_target_items ( & search. unlocked , target) ?;
208
+ let locked = matching_target_items ( & search. locked , target) ?;
184
209
if require_unique {
185
210
let count = locked. len ( ) + unlocked. len ( ) ;
186
211
if count == 0 {
@@ -216,7 +241,8 @@ impl SsCredential {
216
241
. collect ( )
217
242
}
218
243
219
- /// Similar to all_attributes, but this just selects the ones we search on
244
+ /// Similar to [all_attributes](SsCredential::all_attributes),
245
+ /// but this just selects the ones we search on
220
246
fn search_attributes ( & self ) -> HashMap < & str , & str > {
221
247
let mut result: HashMap < & str , & str > = HashMap :: new ( ) ;
222
248
result. insert ( "service" , self . attributes [ "service" ] . as_str ( ) ) ;
@@ -225,21 +251,28 @@ impl SsCredential {
225
251
}
226
252
}
227
253
254
+ /// The builder for secret-service credentials
228
255
#[ derive( Debug , Default ) ]
229
256
pub struct SsCredentialBuilder { }
230
257
231
- /// Called by the crate when Secret Service is the default credential store.
258
+ /// Returns an instance of the secret-service credential builder.
259
+ ///
260
+ /// If secret-service is the default credential store,
261
+ /// this is called once when an entry is first created.
232
262
pub fn default_credential_builder ( ) -> Box < CredentialBuilder > {
233
263
Box :: new ( SsCredentialBuilder { } )
234
264
}
235
265
236
266
impl CredentialBuilderApi for SsCredentialBuilder {
267
+ /// Build an [SsCredential] for the given target, service, and user.
237
268
fn build ( & self , target : Option < & str > , service : & str , user : & str ) -> Result < Box < Credential > > {
238
269
Ok ( Box :: new ( SsCredential :: new_with_target (
239
270
target, service, user,
240
271
) ?) )
241
272
}
242
273
274
+ /// Return the underlying builder object with an `Any` type so that it can
275
+ /// be downgraded to an [SsCredentialBuilder] for platform-specific processing.
243
276
fn as_any ( & self ) -> & dyn std:: any:: Any {
244
277
self
245
278
}
@@ -248,10 +281,14 @@ impl CredentialBuilderApi for SsCredentialBuilder {
248
281
//
249
282
// Secret Service utilities
250
283
//
251
- /// Find the secret service collection that should contain this item
284
+
285
+ /// Find the secret service collection whose label is the given name.
286
+ ///
287
+ /// The name `default` is treated specially and is interpreted as naming
288
+ /// the default collection regardless of its label (which might be different).
252
289
pub fn get_collection < ' a > ( ss : & ' a SecretService , name : & str ) -> Result < Collection < ' a > > {
253
290
let collection = if name. eq ( "default" ) {
254
- ss. get_collection_by_alias ( name ) . map_err ( decode_error) ?
291
+ ss. get_default_collection ( ) . map_err ( decode_error) ?
255
292
} else {
256
293
let all = ss. get_all_collections ( ) . map_err ( decode_error) ?;
257
294
let found = all
@@ -265,27 +302,46 @@ pub fn get_collection<'a>(ss: &'a SecretService, name: &str) -> Result<Collectio
265
302
Ok ( collection)
266
303
}
267
304
268
- /// Create the secret service collection that will contain this credential
305
+ /// Create a secret service collection labeled with the given name.
306
+ ///
307
+ /// If a collection with that name already exists, it is returned.
308
+ ///
309
+ /// The name `default` is specially interpreted to mean the default collection,
310
+ /// which always exists.
269
311
pub fn create_collection < ' a > ( ss : & ' a SecretService < ' a > , name : & str ) -> Result < Collection < ' a > > {
270
- let collection = ss. create_collection ( name, "" ) . map_err ( decode_error) ?;
312
+ let collection = if name. eq ( "default" ) {
313
+ ss. get_default_collection ( ) . map_err ( decode_error) ?
314
+ } else {
315
+ ss. create_collection ( name, "" ) . map_err ( decode_error) ?
316
+ } ;
271
317
Ok ( collection)
272
318
}
273
319
320
+ /// Given an existing item, set its password.
274
321
pub fn set_item_password ( item : & Item , password : & str ) -> Result < ( ) > {
275
322
item. set_secret ( password. as_bytes ( ) , "text/plain" )
276
323
. map_err ( decode_error)
277
324
}
278
325
326
+ /// Given an existing item, retrieve and decode its password.
279
327
pub fn get_item_password ( item : & Item ) -> Result < String > {
280
328
let bytes = item. get_secret ( ) . map_err ( decode_error) ?;
281
329
decode_password ( bytes)
282
330
}
283
331
332
+ /// Given an existing item, delete it.
284
333
pub fn delete_item ( item : & Item ) -> Result < ( ) > {
285
334
item. delete ( ) . map_err ( decode_error)
286
335
}
287
336
288
- pub fn matching_items < ' a > ( source : & ' a [ Item < ' a > ] , target : & str ) -> Result < Vec < & ' a Item < ' a > > > {
337
+ /// Given a slice of items, filter out the ones that have an explicit target
338
+ /// attribute that doesn't match the given target.
339
+ ///
340
+ /// References to the matching items are returned in a new vector.
341
+ pub fn matching_target_items < ' a > (
342
+ source : & ' a [ Item < ' a > ] ,
343
+ target : & str ,
344
+ ) -> Result < Vec < & ' a Item < ' a > > > {
289
345
let mut result: Vec < & ' a Item < ' a > > = vec ! [ ] ;
290
346
for i in source. iter ( ) {
291
347
match i. get_attributes ( ) . map_err ( decode_error) ?. get ( "target" ) {
@@ -300,6 +356,7 @@ pub fn matching_items<'a>(source: &'a [Item<'a>], target: &str) -> Result<Vec<&'
300
356
//
301
357
// Error utilities
302
358
//
359
+
303
360
/// Map underlying secret-service errors to crate errors with
304
361
/// appropriate annotation.
305
362
pub fn decode_error ( err : Error ) -> ErrorCode {
@@ -411,7 +468,7 @@ mod tests {
411
468
412
469
fn probe_collection ( name : & str ) -> bool {
413
470
use secret_service:: blocking:: SecretService ;
414
- pub use secret_service:: EncryptionType ;
471
+ use secret_service:: EncryptionType ;
415
472
416
473
let ss =
417
474
SecretService :: connect ( EncryptionType :: Dh ) . expect ( "Can't connect to secret service" ) ;
0 commit comments