Filter certificate key selection to unused keys

This commit is contained in:
2026-10-27 23:34:25 +00:00
parent 356e3beccd
commit 3918a2ac71
2 changed files with 142 additions and 1 deletions
+58 -1
View File
@@ -31,6 +31,10 @@ struct ExtraSubjectRow {
value: Signal<String>,
}
fn key_is_selectable(key: &KeySummary, include_used: bool) -> bool {
key.has_private && (include_used || key.use_count == 0)
}
#[derive(Clone, Copy)]
struct CertFormSignals {
explicit_dn: Signal<Vec<String>>,
@@ -94,6 +98,7 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
let comment = create_signal(String::new());
let signer_id = create_signal("self".to_string());
let key_id = create_signal(String::new());
let include_used_keys = create_signal(false);
let new_key = create_signal(false);
let key_type = create_signal("rsa".to_string());
let key_size = create_signal("2048".to_string());
@@ -298,6 +303,9 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
name.set(format!("{} copy", value.summary.name));
comment.set(value.summary.comment.clone());
if let Some(key) = value.summary.key_id.clone() {
// XCA includes used keys when copying a certificate so
// the original key remains available and selected.
include_used_keys.set(true);
key_id.set(key);
}
if let Some(issuer) = value.summary.issuer_id.clone() {
@@ -594,9 +602,10 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
.collect::<Vec<View>>()
};
let key_options = move || {
let include_used = include_used_keys.get();
keys.get_clone()
.into_iter()
.filter(|key| key.has_private)
.filter(|key| key_is_selectable(key, include_used))
.map(|key| {
view! {
option(value=key.id.clone()) { (key.name.clone()) }
@@ -604,6 +613,22 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
})
.collect::<Vec<View>>()
};
create_effect(move || {
if include_used_keys.get() {
return;
}
let selected = key_id.get_clone();
if selected.is_empty() {
return;
}
let selected_is_used = keys
.get_clone()
.into_iter()
.any(|key| key.id == selected && key.use_count > 0);
if selected_is_used {
key_id.set(String::new());
}
});
let template_options = move || {
templates.get_clone().into_iter().map(|template| view! {
option(value=template.id.clone()) { (if template.builtin { format!("[built-in] {}", template.name) } else { template.name.clone() }) }
@@ -728,6 +753,10 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
option(value="") { "— select a key —" }
(key_options())
}
div(class="form-check mt-2") {
input(class="form-check-input", r#type="checkbox", id="include-used-keys", bind:checked=include_used_keys)
label(class="form-check-label", r#for="include-used-keys") { "Include used keys" }
}
}
})
}
@@ -1502,3 +1531,31 @@ fn query_param(name: &str) -> Option<String> {
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn key(has_private: bool, use_count: i64) -> KeySummary {
KeySummary {
has_private,
use_count,
..KeySummary::default()
}
}
#[test]
fn key_selection_hides_used_and_public_keys_by_default() {
assert!(key_is_selectable(&key(true, 0), false));
assert!(!key_is_selectable(&key(true, 1), false));
assert!(!key_is_selectable(&key(false, 0), false));
}
#[test]
fn key_selection_includes_used_private_keys_when_requested() {
assert!(key_is_selectable(&key(true, 0), true));
assert!(key_is_selectable(&key(true, 2), true));
assert!(!key_is_selectable(&key(false, 0), true));
assert!(!key_is_selectable(&key(false, 1), true));
}
}