Skip to content

Commit 96f86a3

Browse files
Merge pull request #245 from brotskydotcom/v4
Get to v4.0.0-beta.1
2 parents 253465f + 15f81c8 commit 96f86a3

File tree

6 files changed

+219
-38
lines changed

6 files changed

+219
-38
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ keywords = ["password", "credential", "keychain", "secret-service", "cross-platf
66
license = "MIT OR Apache-2.0"
77
name = "keyring"
88
repository = "https://github.com/hwchen/keyring-rs.git"
9-
version = "4.0.0-alpha.1"
9+
version = "4.0.0-beta.1"
1010
rust-version = "1.85"
1111
edition = "2024"
1212
exclude = [".github/"]

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ keyring = "4"
1717

1818
This will give you access to the `keyring` crate in your code. Now you can use the `Entry::new` function to create a new keyring entry. The `new` function takes a service name and a user's name which together identify the entry.
1919

20-
Passwords (strings) or secrets (binary data) can be added to an entry using its `set_password` or `set_secret` methods, respectively. (These methods create an entry in the underlying platform's persistent credential store.) The password or secret can then be read back using the `get_password` or `get_secret` methods. The underlying credential (with its password/secret data) can then be removed using the `delete_credential` method.
20+
Passwords (strings) or secrets (binary data) can be added to an entry using its `set_password` or `set_secret` methods, respectively. (These methods create or update an entry in the underlying platform's persistent credential store.) The password or secret can then be read back using the `get_password` or `get_secret` methods. The underlying credential (with its password/secret data) can then be removed using the `delete_credential` method.
2121

2222
```rust
2323
use keyring::{Entry, Result};
@@ -46,11 +46,11 @@ The `ios` library is a full exercise of all the iOS functionality; it's meant to
4646

4747
## Client Testing
4848

49-
This crate comes with a mock credential store that can be used by clients who want to test without accessing the native platform store. The mock store is cross-platform and allows mocking errors as well as successes.
49+
This crate comes with a mock credential store that can be used by clients who want to test without accessing the native platform store. The mock store is cross-platform and allows mocking errors as well as successes. See the [developer docs](https://docs.rs/keyring/) for details.
5050

5151
## Extensibility
5252

53-
This crate allows clients to "bring their own credential store" by providing traits that clients can implement. See the [developer docs](https://docs.rs/keyring/) for details.
53+
This crate allows clients to bring their own credential store by providing traits that clients can implement. See the [developer docs](https://docs.rs/keyring/) for details.
5454

5555
## Platforms
5656

@@ -60,13 +60,15 @@ This crate provides built-in implementations of the following platform-specific
6060
* _macOS_, _iOS_: Keychain Services.
6161
* _Windows_: The Windows Credential Manager.
6262

63+
It can be built and used on other platforms, but will not provide a built-in credential store implementation; you will have to bring your own.
64+
6365
### Platform-specific issues
6466

6567
Since neither the maintainers nor GitHub do testing on BSD variants, we rely on contributors to support these platforms. Thanks for your help!
6668

6769
If you use the *Secret Service* as your credential store, be aware of the following:
6870

69-
* This implementation requires that `libdbus` be installed on user machines. If you have users whose machines might not have `libdbus` installed, you can specify the `vendored` when building this crate to statically link the dbus library with your app.
71+
* The default build of this crate expects that `libdbus` will be installed on users' machines. If you have users whose machines might not have `libdbus` installed, you can specify the `vendored` feature when building this crate to statically link the dbus library with your app.
7072
* Every call to the Secret Service is done via an inter-process call, which takes time (typically tens if not hundreds of milliseconds).
7173
* By default, this implementation does not encrypt secrets when sending them to or fetching them from the Dbus. If you want them encrypted, you can specify the `encrypted` feature when building this crate.
7274

@@ -76,11 +78,11 @@ The *macOS and iOS credential stores* do not allow service names or usernames to
7678

7779
## Upgrading from v3
7880

79-
There are no functional API changes between v4 and v3; all the changes are in the keystores and how they are specified.
80-
81-
Version 4 of this crate removes a number of the built-in credential stores that were available in version 3, namely the async secret service and linux keyutils.
81+
There are no functional API changes between v4 and v3. All the changes are in the keystore implementations and how features are used to select keystores:
8282

83-
Version 4 of this crate also dispenses with the need to explicitly specify which credential store you want to use on each platform. Instead, the default feature set provides a single credential store on each platform. If you would rather bring your own store, and not build this crate's built-in ones, you can simply suppress the default feature set.
83+
* Version 4 of this crate removes a number of the built-in credential stores that were available in version 3, namely the async secret service and linux keyutils. These keystores are being contributed directly to the existing [secret-service](https://crates.io/crates/secret-service) and [linux-keyutils](https://crates.io/crates/linux-keyutils) crates, respectively.
84+
* Version 4 of this crate dispenses with the need to explicitly specify which credential store you want to use on each platform. Instead, the default feature set provides a single credential store on each platform. If you would rather bring your own store, and not build this crate's built-in ones, you can simply suppress the default feature set.
85+
* The built-in macOS keystore now supports use of the Data Protection keychain, which is the same keychain used by iOS. You can specify a target of "Data Protection" (or simply "Protected") to write and read credentials in that keychain.
8486

8587
All v2/v3 data is fully forward-compatible with v4 data; there have been no changes at all in that respect.
8688

src/ios.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ impl IosCredential {
136136
));
137137
}
138138
if let Some(target) = target {
139-
if target.to_ascii_lowercase() != "default" {
139+
if !target.eq_ignore_ascii_case("default") {
140140
return Err(ErrorCode::Invalid(
141141
"target".to_string(),
142142
"only 'default' is allowed".to_string(),
@@ -188,3 +188,98 @@ fn decode_error(err: Error) -> ErrorCode {
188188
_ => ErrorCode::PlatformFailure(Box::new(err)),
189189
}
190190
}
191+
192+
#[cfg(test)]
193+
mod tests {
194+
use super::{IosCredential, default_credential_builder};
195+
use crate::credential::CredentialPersistence;
196+
use crate::{Entry, Error, tests::generate_random_string};
197+
198+
#[test]
199+
fn test_persistence() {
200+
assert!(matches!(
201+
default_credential_builder().persistence(),
202+
CredentialPersistence::UntilDelete
203+
))
204+
}
205+
206+
fn entry_new(service: &str, user: &str) -> Entry {
207+
crate::tests::entry_from_constructor(IosCredential::new_with_target, service, user)
208+
}
209+
210+
#[test]
211+
fn test_invalid_parameter() {
212+
let credential = IosCredential::new_with_target(None, "", "user");
213+
assert!(
214+
matches!(credential, Err(Error::Invalid(_, _))),
215+
"Created credential with empty service"
216+
);
217+
let credential = IosCredential::new_with_target(None, "service", "");
218+
assert!(
219+
matches!(credential, Err(Error::Invalid(_, _))),
220+
"Created entry with empty user"
221+
);
222+
let credential = IosCredential::new_with_target(Some(""), "service", "user");
223+
assert!(
224+
matches!(credential, Err(Error::Invalid(_, _))),
225+
"Created entry with empty target"
226+
);
227+
}
228+
229+
#[test]
230+
fn test_missing_entry() {
231+
crate::tests::test_missing_entry(entry_new);
232+
}
233+
234+
#[test]
235+
fn test_empty_password() {
236+
crate::tests::test_empty_password(entry_new);
237+
}
238+
239+
#[test]
240+
fn test_round_trip_ascii_password() {
241+
crate::tests::test_round_trip_ascii_password(entry_new);
242+
}
243+
244+
#[test]
245+
fn test_round_trip_non_ascii_password() {
246+
crate::tests::test_round_trip_non_ascii_password(entry_new);
247+
}
248+
249+
#[test]
250+
fn test_round_trip_random_secret() {
251+
crate::tests::test_round_trip_random_secret(entry_new);
252+
}
253+
254+
#[test]
255+
fn test_update() {
256+
crate::tests::test_update(entry_new);
257+
}
258+
259+
#[test]
260+
fn test_get_credential() {
261+
let name = generate_random_string();
262+
let entry = entry_new(&name, &name);
263+
let credential: &IosCredential = entry
264+
.get_credential()
265+
.downcast_ref()
266+
.expect("Not a mac credential");
267+
assert!(
268+
credential.get_credential().is_err(),
269+
"Platform credential shouldn't exist yet!"
270+
);
271+
entry
272+
.set_password("test get_credential")
273+
.expect("Can't set password for get_credential");
274+
assert!(credential.get_credential().is_ok());
275+
entry
276+
.delete_credential()
277+
.expect("Couldn't delete after get_credential");
278+
assert!(matches!(entry.get_password(), Err(Error::NoEntry)));
279+
}
280+
281+
#[test]
282+
fn test_get_update_attributes() {
283+
crate::tests::test_noop_get_update_attributes(entry_new);
284+
}
285+
}

src/lib.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ pub mod secret_service;
189189
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
190190
pub mod macos;
191191

192-
#[cfg(all(target_os = "ios", feature = "apple-native"))]
193-
#[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
192+
#[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "apple-native"))]
193+
#[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
194194
pub mod ios;
195195

196196
//
@@ -505,8 +505,7 @@ mod tests {
505505
}
506506
}
507507

508-
/// A basic round-trip unit test given an entry and a password.
509-
pub fn test_round_trip(case: &str, entry: &Entry, in_pass: &str) {
508+
fn test_round_trip_no_delete(case: &str, entry: &Entry, in_pass: &str) {
510509
entry
511510
.set_password(in_pass)
512511
.unwrap_or_else(|err| panic!("Can't set password for {case}: {err:?}"));
@@ -516,7 +515,12 @@ mod tests {
516515
assert_eq!(
517516
in_pass, out_pass,
518517
"Passwords don't match for {case}: set='{in_pass}', get='{out_pass}'",
519-
);
518+
)
519+
}
520+
521+
/// A basic round-trip unit test given an entry and a password.
522+
pub fn test_round_trip(case: &str, entry: &Entry, in_pass: &str) {
523+
test_round_trip_no_delete(case, entry, in_pass);
520524
entry
521525
.delete_credential()
522526
.unwrap_or_else(|err| panic!("Can't delete password for {case}: {err:?}"));
@@ -637,7 +641,7 @@ mod tests {
637641
{
638642
let name = generate_random_string();
639643
let entry = f(&name, &name);
640-
test_round_trip("initial ascii password", &entry, "test ascii password");
644+
test_round_trip_no_delete("initial ascii password", &entry, "test ascii password");
641645
test_round_trip(
642646
"updated non-ascii password",
643647
&entry,

0 commit comments

Comments
 (0)