Skip to content

Commit

Permalink
Merge pull request #150 from brotskydotcom/persistence
Browse files Browse the repository at this point in the history
Fixes #147 as suggested by @benwr
  • Loading branch information
brotskydotcom authored Dec 4, 2023
2 parents f0ad8b9 + 0a56d58 commit 1732b79
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 13 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ keywords = ["password", "credential", "keychain", "keyring", "cross-platform"]
license = "MIT OR Apache-2.0"
name = "keyring"
repository = "https://github.com/hwchen/keyring-rs.git"
version = "2.0.5"
version = "2.1.0"
edition = "2021"
exclude = [".github/"]
readme = "README.md"
Expand Down Expand Up @@ -48,7 +48,7 @@ crate-type = ["staticlib"]

[dev-dependencies]
clap = { version = "4", features = ["derive", "wrap_help"] }
rpassword = "7.0"
rpassword = "7"
rand = "0.8"
doc-comment = "0.3"
whoami = "1.2"
whoami = "1"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Thanks to the following for helping make this library better,
whether through contributing code, discussion, or bug reports!

- @Alexei-Barnes
- @benwr
- @bhkaminski
- @brotskydotcom
- @complexspaces
Expand Down
27 changes: 25 additions & 2 deletions src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub trait CredentialApi {
/// so a second call to delete_password will return
/// a [NoEntry](crate::Error::NoEntry) error.
fn delete_password(&self) -> Result<()>;
/// Return the underlying concrete object cast to [Any](std::any::Any).
/// Return the underlying concrete object cast to [Any].
///
/// This allows clients
/// to downcast the credential to its concrete type so they
Expand All @@ -45,19 +45,42 @@ impl std::fmt::Debug for Credential {
/// A thread-safe implementation of the [Credential API](CredentialApi).
pub type Credential = dyn CredentialApi + Send + Sync;

/// A descriptor for the lifetime of stored credentials, returned from
/// a credential store's [persistence](CredentialBuilderApi::persistence) call.
#[non_exhaustive]
pub enum CredentialPersistence {
/// Credentials vanish when the entry vanishes (stored in the entry)
EntryOnly,
/// Credentials vanish when the process terminates (stored in process memory)
ProcessOnly,
/// Credentials persist until the machine reboots (stored in kernel memory)
UntilReboot,
/// Credentials persist until they are explicitly deleted (stored on disk)
UntilDelete,
}

/// The API that [credential builders](CredentialBuilder) implement.
pub trait CredentialBuilderApi {
/// Create a credential identified by the given target, service, and user.
///
/// This typically has no effect on the content of the underlying store.
/// A credential need not be persisted until its password is set.
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>>;
/// Return the underlying concrete object cast to [Any](std::any::Any).
/// Return the underlying concrete object cast to [Any].
///
/// Because credential builders need not have any internal structure,
/// this call is not so much for clients
/// as it is to allow automatic derivation of a Debug trait for builders.
fn as_any(&self) -> &dyn Any;

/// The lifetime of credentials produced by this builder.
///
/// A default implementation is provided for backward compatibility,
/// since this API was added in a minor release. The default assumes
/// that keystores use disk-based credential storage.
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilDelete
}
}

impl std::fmt::Debug for CredentialBuilder {
Expand Down
21 changes: 19 additions & 2 deletions src/keyutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ Alternatively, you can drop the secret-service credential store altogether
with `--no-default-features` and `--features linux-no-secret-service`.
*/
use super::credential::{Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi};
use super::credential::{
Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi, CredentialPersistence,
};
use super::error::{decode_password, Error as ErrorCode, Result};
use linux_keyutils::{KeyError, KeyRing, KeyRingIdentifier};

Expand Down Expand Up @@ -284,6 +286,12 @@ impl CredentialBuilderApi for KeyutilsCredentialBuilder {
fn as_any(&self) -> &dyn std::any::Any {
self
}

/// Since this keystore keeps credentials in kernel memory,
/// they vanish on reboot
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilReboot
}
}

/// Map an underlying keyutils error to a platform-independent error with annotation.
Expand Down Expand Up @@ -313,9 +321,18 @@ fn wrap(err: KeyError) -> Box<dyn std::error::Error + Send + Sync> {

#[cfg(test)]
mod tests {
use crate::credential::CredentialPersistence;
use crate::{tests::generate_random_string, Entry, Error};

use super::KeyutilsCredential;
use super::{default_credential_builder, KeyutilsCredential};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilReboot
))
}

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(KeyutilsCredential::new_with_target, service, user)
Expand Down
11 changes: 10 additions & 1 deletion src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,18 @@ pub fn decode_error(err: Error) -> ErrorCode {

#[cfg(test)]
mod tests {
use crate::credential::CredentialPersistence;
use crate::{tests::generate_random_string, Entry, Error};

use super::MacCredential;
use super::{default_credential_builder, MacCredential};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(MacCredential::new_with_target, service, user)
Expand Down
24 changes: 20 additions & 4 deletions src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ set_default_credential_builder(mock::default_credential_builder());
```
You can then create entries as you usually do, and call their usual methods
to set, get, and delete passwords. There is no peristence between
runs, so getting a credential before setting it will always result
to set, get, and delete passwords. There is no persistence other than
in the entry itself, so getting a password before setting it will always result
in a [NotFound](Error::NoEntry) error.
If you want a method call on an entry to fail in a specific way, you can
Expand All @@ -36,7 +36,9 @@ entry.set_password("test").expect("error has been cleared");
use std::cell::RefCell;
use std::sync::Mutex;

use super::credential::{Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi};
use super::credential::{
Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi, CredentialPersistence,
};
use super::error::{Error, Result};

/// The concrete mock credential
Expand Down Expand Up @@ -180,6 +182,11 @@ impl CredentialBuilderApi for MockCredentialBuilder {
fn as_any(&self) -> &dyn std::any::Any {
self
}

/// This keystore keeps the password in the entry!
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::EntryOnly
}
}

/// Return a mock credential builder for use by clients.
Expand All @@ -189,9 +196,18 @@ pub fn default_credential_builder() -> Box<CredentialBuilder> {

#[cfg(test)]
mod tests {
use super::MockCredential;
use super::{default_credential_builder, MockCredential};
use crate::credential::CredentialPersistence;
use crate::{tests::generate_random_string, Entry, Error};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::EntryOnly
))
}

fn entry_new(service: &str, user: &str) -> Entry {
let credential = MockCredential::new_with_target(None, service, user).unwrap();
Entry::new_with_credential(Box::new(credential))
Expand Down
11 changes: 10 additions & 1 deletion src/secret_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,18 @@ fn wrap(err: Error) -> Box<dyn std::error::Error + Send + Sync> {

#[cfg(test)]
mod tests {
use crate::credential::CredentialPersistence;
use crate::{tests::generate_random_string, Entry, Error};

use super::SsCredential;
use super::{default_credential_builder, SsCredential};

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(SsCredential::new_with_target, service, user)
Expand Down
9 changes: 9 additions & 0 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,18 @@ fn wrap(code: DWORD) -> Box<dyn std::error::Error + Send + Sync> {
mod tests {
use super::*;

use crate::credential::CredentialPersistence;
use crate::tests::{generate_random_string, generate_random_string_of_len};
use crate::Entry;

#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}

fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(WinCredential::new_with_target, service, user)
}
Expand Down

0 comments on commit 1732b79

Please sign in to comment.