Skip to content

Commit 8484841

Browse files
server: Add a test case for ensuring re-trying to unlock works
1 parent 79143f1 commit 8484841

3 files changed

Lines changed: 123 additions & 33 deletions

File tree

server/src/collection.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,59 @@ mod tests {
12581258
Ok(())
12591259
}
12601260

1261+
#[tokio::test]
1262+
async fn unlock_retry() -> Result<(), Box<dyn std::error::Error>> {
1263+
let setup = TestServiceSetup::plain_session(true).await?;
1264+
let default_collection = setup.default_collection().await?;
1265+
1266+
let secret = oo7::Secret::text("test-secret-data");
1267+
let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret);
1268+
default_collection
1269+
.create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
1270+
.await?;
1271+
1272+
let collection = setup
1273+
.server
1274+
.collection_from_path(default_collection.inner().path())
1275+
.await
1276+
.expect("Collection should exist");
1277+
collection
1278+
.set_locked(true, setup.keyring_secret.clone())
1279+
.await?;
1280+
1281+
assert!(
1282+
default_collection.is_locked().await?,
1283+
"Collection should be locked"
1284+
);
1285+
1286+
setup
1287+
.mock_prompter
1288+
.set_password_queue(vec![
1289+
oo7::Secret::from("wrong-password"),
1290+
oo7::Secret::from("wrong-password2"),
1291+
oo7::Secret::from("test-password-long-enough"),
1292+
])
1293+
.await;
1294+
1295+
let unlocked = setup
1296+
.service_api
1297+
.unlock(&[default_collection.inner().path()], None)
1298+
.await?;
1299+
1300+
assert_eq!(unlocked.len(), 1, "Should have unlocked 1 collection");
1301+
assert_eq!(
1302+
unlocked[0].as_str(),
1303+
default_collection.inner().path().as_str(),
1304+
"Should return the collection path"
1305+
);
1306+
assert!(
1307+
!default_collection.is_locked().await?,
1308+
"Collection should be unlocked after retry with correct password"
1309+
);
1310+
1311+
Ok(())
1312+
}
1313+
12611314
#[tokio::test]
12621315
async fn locked_collection_operations() -> Result<(), Box<dyn std::error::Error>> {
12631316
let setup = TestServiceSetup::plain_session(true).await?;

server/src/gnome/prompter.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -393,17 +393,15 @@ impl PrompterCallback {
393393
async fn prompter_done(&self, prompt: &Prompt, exchange: &str) -> Result<(), ServiceError> {
394394
let prompter = PrompterProxy::new(self.service.connection()).await?;
395395

396-
// Get the action from the prompt
397-
let Some(action) = prompt.take_action().await else {
398-
return Err(custom_service_error(
399-
"Prompt action was already executed or not set",
400-
));
401-
};
402-
403396
// Handle each role differently based on what validation/preparation is needed
404397
match prompt.role() {
405398
PromptRole::Lock => {
406-
// Lock operation doesn't need secret, just execute the action
399+
let Some(action) = prompt.take_action().await else {
400+
return Err(custom_service_error(
401+
"Prompt action was already executed or not set",
402+
));
403+
};
404+
407405
let result_value = action.execute(None).await?;
408406

409407
let path = self.path.clone();
@@ -451,6 +449,13 @@ impl PrompterCallback {
451449

452450
if is_valid {
453451
tracing::debug!("Keyring secret matches for {label}.");
452+
453+
let Some(action) = prompt.take_action().await else {
454+
return Err(custom_service_error(
455+
"Prompt action was already executed or not set",
456+
));
457+
};
458+
454459
// Execute the unlock action after successful validation
455460
let result_value = action.execute(Some(secret)).await?;
456461

@@ -507,6 +512,12 @@ impl PrompterCallback {
507512
));
508513
};
509514

515+
let Some(action) = prompt.take_action().await else {
516+
return Err(custom_service_error(
517+
"Prompt action was already executed or not set",
518+
));
519+
};
520+
510521
// Execute the collection creation action with the secret
511522
match action.execute(Some(secret)).await {
512523
Ok(collection_path_value) => {

server/src/tests.rs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ pub(crate) struct MockPrompterService {
162162
unlock_password: Arc<tokio::sync::Mutex<Option<oo7::Secret>>>,
163163
/// Whether to accept (true) or dismiss (false) prompts
164164
should_accept: Arc<tokio::sync::Mutex<bool>>,
165+
/// Queue of passwords to use for for testing retry logic
166+
password_queue: Arc<tokio::sync::Mutex<Vec<oo7::Secret>>>,
165167
}
166168

167169
impl MockPrompterService {
@@ -171,13 +173,18 @@ impl MockPrompterService {
171173
"test-password-long-enough",
172174
)))),
173175
should_accept: Arc::new(tokio::sync::Mutex::new(true)),
176+
password_queue: Arc::new(tokio::sync::Mutex::new(Vec::new())),
174177
}
175178
}
176179

177180
/// Set whether prompts should be accepted or dismissed
178181
pub async fn set_accept(&self, accept: bool) {
179182
*self.should_accept.lock().await = accept;
180183
}
184+
185+
pub async fn set_password_queue(&self, passwords: Vec<oo7::Secret>) {
186+
*self.password_queue.lock().await = passwords;
187+
}
181188
}
182189

183190
#[zbus::interface(name = "org.gnome.keyring.internal.Prompter")]
@@ -240,6 +247,7 @@ impl MockPrompterService {
240247
let callback_path = callback.to_owned();
241248
let unlock_password = self.unlock_password.clone();
242249
let should_accept = self.should_accept.clone();
250+
let password_queue = self.password_queue.clone();
243251
let exchange = exchange.to_owned();
244252
let connection = connection.clone();
245253

@@ -266,24 +274,29 @@ impl MockPrompterService {
266274
.await?;
267275
tracing::debug!("MockPrompter: PromptReady(no) completed");
268276

269-
// Call PromptDone to clean up the callback
270-
tracing::debug!("MockPrompter: calling PromptDone");
271-
connection
272-
.call_method(
273-
None::<()>,
274-
&callback_path,
275-
Some("org.gnome.keyring.internal.Prompter.Callback"),
276-
"PromptDone",
277-
&(),
278-
)
279-
.await?;
280-
tracing::debug!("MockPrompter: PromptDone completed");
281-
282277
return Ok(());
283278
} else if type_ == PromptType::Password {
284279
tracing::debug!("MockPrompter: performing unlock (password prompt)");
285280
// Unlock prompt - perform secret exchange
286-
let password = unlock_password.lock().await.clone().unwrap();
281+
282+
let mut queue = password_queue.lock().await;
283+
let password = if !queue.is_empty() {
284+
let pwd = queue.remove(0);
285+
tracing::debug!(
286+
"MockPrompter: using password from queue (length: {}, queue remaining: {})",
287+
std::str::from_utf8(pwd.as_bytes()).unwrap_or("<binary>"),
288+
queue.len()
289+
);
290+
pwd
291+
} else {
292+
let pwd = unlock_password.lock().await.clone().unwrap();
293+
tracing::debug!(
294+
"MockPrompter: using default password (length: {})",
295+
std::str::from_utf8(pwd.as_bytes()).unwrap_or("<binary>")
296+
);
297+
pwd
298+
};
299+
drop(queue);
287300

288301
// Generate our own key pair
289302
let private_key = oo7::Key::generate_private_key().unwrap();
@@ -330,27 +343,40 @@ impl MockPrompterService {
330343
tracing::debug!("MockPrompter: PromptReady(yes) completed");
331344
}
332345

333-
// Call PromptDone to clean up the callback
334-
tracing::debug!("MockPrompter: calling PromptDone");
335-
connection
346+
Ok::<_, zbus::Error>(())
347+
});
348+
349+
Ok(())
350+
}
351+
352+
async fn stop_prompting(
353+
&self,
354+
callback: ObjectPath<'_>,
355+
#[zbus(connection)] connection: &zbus::Connection,
356+
) -> zbus::fdo::Result<()> {
357+
tracing::debug!("MockPrompter: stop_prompting called for {}", callback);
358+
let callback_path = callback.to_owned();
359+
let connection = connection.clone();
360+
361+
tokio::spawn(async move {
362+
tracing::debug!("MockPrompter: calling PromptDone for {}", callback_path);
363+
let result = connection
336364
.call_method(
337365
None::<()>,
338366
&callback_path,
339367
Some("org.gnome.keyring.internal.Prompter.Callback"),
340368
"PromptDone",
341369
&(),
342370
)
343-
.await?;
344-
tracing::debug!("MockPrompter: PromptDone completed");
371+
.await;
345372

346-
Ok::<_, zbus::Error>(())
373+
if let Err(err) = result {
374+
tracing::debug!("MockPrompter: PromptDone failed: {}", err);
375+
} else {
376+
tracing::debug!("MockPrompter: PromptDone completed for {}", callback_path);
377+
}
347378
});
348379

349380
Ok(())
350381
}
351-
352-
async fn stop_prompting(&self, _callback: ObjectPath<'_>) -> zbus::fdo::Result<()> {
353-
// Called when prompting is complete
354-
Ok(())
355-
}
356382
}

0 commit comments

Comments
 (0)