Skip to content

Commit 6df3d93

Browse files
committed
init doc + unit tests
1 parent 160ad9f commit 6df3d93

File tree

1 file changed

+123
-9
lines changed

1 file changed

+123
-9
lines changed

src/keyutils_persistent.rs

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
33
# keyutils-persistent credential store
44
5-
TODO
5+
This store is a combination of the [keyutils](crate::keyutils) store
6+
backed up with a persistent [secret-service](crate::secret_service)
7+
store.
68
79
*/
10+
811
use log::debug;
912

1013
use super::credential::{
@@ -14,29 +17,49 @@ use super::error::{Error, Result};
1417
use super::keyutils::KeyutilsCredential;
1518
use super::secret_service::SsCredential;
1619

20+
/// Representation of a keyutils-persistent credential.
21+
///
22+
/// The credential owns a [KeyutilsCredential] for in-memory usage and
23+
/// a [SsCredential] for persistence.
1724
#[derive(Debug, Clone)]
1825
pub struct KeyutilsPersistentCredential {
1926
keyutils: KeyutilsCredential,
2027
ss: SsCredential,
2128
}
2229

2330
impl CredentialApi for KeyutilsPersistentCredential {
31+
/// Set a password in the underlying store
2432
fn set_password(&self, password: &str) -> Result<()> {
2533
self.set_secret(password.as_bytes())
2634
}
2735

36+
/// Set a secret in the underlying store
37+
///
38+
/// It sets first the secret in keyutils, then in
39+
/// secret-service. If the late one fails, keyutils secret change
40+
/// is reverted.
2841
fn set_secret(&self, secret: &[u8]) -> Result<()> {
29-
let prev_secret = self.keyutils.get_secret()?;
42+
let prev_secret = self.keyutils.get_secret();
3043
self.keyutils.set_secret(secret)?;
3144

3245
if let Err(err) = self.ss.set_secret(secret) {
33-
self.keyutils.set_secret(&prev_secret)?;
46+
match prev_secret {
47+
Ok(ref secret) => self.keyutils.set_secret(secret),
48+
Err(Error::NoEntry) => self.keyutils.delete_credential(),
49+
Err(err) => Err(err),
50+
}?;
51+
3452
return Err(err);
3553
}
3654

3755
Ok(())
3856
}
3957

58+
/// Retrieve a password from the underlying store
59+
///
60+
/// The password is retrieved from keyutils. In case of error, the
61+
/// password is retrieved from secret-service instead (and
62+
/// keyutils is updated).
4063
fn get_password(&self) -> Result<String> {
4164
match self.keyutils.get_password() {
4265
Ok(password) => {
@@ -53,6 +76,11 @@ impl CredentialApi for KeyutilsPersistentCredential {
5376
Ok(password)
5477
}
5578

79+
/// Retrieve a secret from the underlying store
80+
///
81+
/// The secret is retrieved from keyutils. In case of error, the
82+
/// secret is retrieved from secret-service instead (and keyutils
83+
/// is updated).
5684
fn get_secret(&self) -> Result<Vec<u8>> {
5785
match self.keyutils.get_secret() {
5886
Ok(secret) => {
@@ -69,6 +97,10 @@ impl CredentialApi for KeyutilsPersistentCredential {
6997
Ok(secret)
7098
}
7199

100+
/// Delete a password from the underlying store.
101+
///
102+
/// The credential is deleted from both keyutils and
103+
/// secret-service.
72104
fn delete_credential(&self) -> Result<()> {
73105
if let Err(err) = self.keyutils.delete_credential() {
74106
debug!("cannot delete keyutils credential: {err}");
@@ -87,21 +119,26 @@ impl CredentialApi for KeyutilsPersistentCredential {
87119
}
88120

89121
impl KeyutilsPersistentCredential {
122+
/// Create the platform credential for a Keyutils entry.
123+
///
124+
/// An explicit target string is interpreted as the KeyRing to use for the entry.
125+
/// If none is provided, then we concatenate the user and service in the string
126+
/// `keyring-rs:user@service`.
90127
pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result<Self> {
91128
let ss = SsCredential::new_with_target(target, service, user)?;
92129
let keyutils = KeyutilsCredential::new_with_target(target, service, user)?;
93130
Ok(Self { keyutils, ss })
94131
}
95132
}
96133

97-
/// The builder for secret-service-with-keyutils credentials
134+
/// The builder for keyutils-persistent credentials
98135
#[derive(Debug, Default)]
99136
pub struct KeyutilsPersistentCredentialBuilder {}
100137

101-
/// Returns an instance of the secret-service-with-keyutils credential builder.
138+
/// Returns an instance of the keyutils-persistent credential builder.
102139
///
103-
/// If secret-service-with-keyutils is the default credential store,
104-
/// this is called once when an entry is first created.
140+
/// If keyutils-persistent is the default credential store, this is
141+
/// called once when an entry is first created.
105142
pub fn default_credential_builder() -> Box<CredentialBuilder> {
106143
Box::new(KeyutilsPersistentCredentialBuilder {})
107144
}
@@ -120,17 +157,94 @@ impl CredentialBuilderApi for KeyutilsPersistentCredentialBuilder {
120157
self
121158
}
122159

123-
/// Since this keystore keeps credentials in kernel memory,
124-
/// they vanish on reboot
160+
/// This keystore keeps credentials thanks to the inner secret-service store.
125161
fn persistence(&self) -> CredentialPersistence {
126162
CredentialPersistence::UntilDelete
127163
}
128164
}
129165

166+
/// Replace any Ambiguous error with a NoEntry one
130167
fn ambigous_to_no_entry(err: Error) -> Error {
131168
if let Error::Ambiguous(_) = err {
132169
return Error::NoEntry;
133170
};
134171

135172
err
136173
}
174+
175+
#[cfg(test)]
176+
mod tests {
177+
use crate::credential::CredentialPersistence;
178+
use crate::{Entry, Error};
179+
180+
use super::{default_credential_builder, KeyutilsPersistentCredential};
181+
182+
#[test]
183+
fn test_persistence() {
184+
assert!(matches!(
185+
default_credential_builder().persistence(),
186+
CredentialPersistence::UntilDelete
187+
))
188+
}
189+
190+
fn entry_new(service: &str, user: &str) -> Entry {
191+
crate::tests::entry_from_constructor(
192+
KeyutilsPersistentCredential::new_with_target,
193+
service,
194+
user,
195+
)
196+
}
197+
198+
#[test]
199+
fn test_invalid_parameter() {
200+
let credential = KeyutilsPersistentCredential::new_with_target(Some(""), "service", "user");
201+
assert!(
202+
matches!(credential, Err(Error::Invalid(_, _))),
203+
"Created entry with empty target"
204+
);
205+
}
206+
207+
#[test]
208+
fn test_empty_service_and_user() {
209+
crate::tests::test_empty_service_and_user(entry_new);
210+
}
211+
212+
#[test]
213+
fn test_missing_entry() {
214+
crate::tests::test_missing_entry(entry_new);
215+
}
216+
217+
#[test]
218+
fn test_empty_password() {
219+
let entry = entry_new("empty password service", "empty password user");
220+
assert!(
221+
matches!(entry.set_password(""), Err(Error::Invalid(_, _))),
222+
"Able to set empty password"
223+
);
224+
}
225+
226+
#[test]
227+
fn test_round_trip_ascii_password() {
228+
crate::tests::test_round_trip_ascii_password(entry_new);
229+
}
230+
231+
#[test]
232+
fn test_round_trip_non_ascii_password() {
233+
crate::tests::test_round_trip_non_ascii_password(entry_new);
234+
}
235+
236+
#[test]
237+
fn test_round_trip_random_secret() {
238+
crate::tests::test_round_trip_random_secret(entry_new);
239+
}
240+
241+
#[test]
242+
fn test_update() {
243+
crate::tests::test_update(entry_new);
244+
}
245+
246+
#[test]
247+
fn test_noop_get_update_attributes() {
248+
crate::tests::test_noop_get_update_attributes(entry_new);
249+
}
250+
}

0 commit comments

Comments
 (0)