Review: Protect Sheet can't replace a sheet's password without unprotecting it

Re-protecting a password-protected sheet would drop its hash and let a
plain Unprotect succeed; it is now refused. The sheet tab menu offers
Unprotect Sheet on a protected sheet, like the ribbon.

Refs #346

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76 2026-10-11 21:36:05 +02:00
parent 22978a7e62
commit 8587c2ba14
2 changed files with 12 additions and 2 deletions

View File

@ -132,6 +132,12 @@ fn convert_notes(s: &mut Session, _: &Json) -> Result<Json> {
fn protect_sheet(s: &mut Session, p: &Json) -> Result<Json> {
let sheet = target_sheet(s, p)?;
// Re-protecting a password-protected sheet would replace the password without knowing it:
// unprotect it first, as in Excel. One without a password can take new options directly.
let has_password = |pr: &SheetProtection| pr.password.is_some() || pr.legacy_password_hash.is_some();
if s.doc()?.wb.sheet(sheet).and_then(|sh| sh.protection.as_ref()).is_some_and(has_password) {
return Err(EngineError::Other("The sheet is protected with a password. Unprotect it first (Review › Unprotect Sheet).".into()));
}
let b = |k: &str| bool_param(p, k).unwrap_or(false);
let prot = SheetProtection {
password: None,
@ -401,6 +407,9 @@ mod tests {
assert!(matches!(&prot.password, Some(PasswordHash::Iterated { algorithm, .. }) if algorithm == "SHA-512"));
assert!(s.execute("review.unprotectSheet", json!({})).is_err());
assert!(s.execute("review.unprotectSheet", json!({"password": "pass"})).is_err());
// Protecting again can't swap the password out from under it.
assert!(s.execute("review.protectSheet", json!({})).is_err());
assert_eq!(s.doc().unwrap().wb.active().unwrap().protection.as_ref(), Some(&prot));
s.execute("review.unprotectSheet", json!({"password": "pa55"})).unwrap();
assert!(!s.doc().unwrap().wb.active().unwrap().is_protected());
// A GridCraft JSON workbook saved before carried the internal hash under `password_hash`.

View File

@ -164,8 +164,9 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) {
app.run_or_alert("sheet.unhide", json!({}));
}
ui.separator();
if ui.button("Protect Sheet…").clicked() {
app.open_dialog("protectSheet", json!({}));
let protected = app.session.active().and_then(|d| d.wb.active().map(|s| s.is_protected())).unwrap_or(false);
if ui.button(if protected { "Unprotect Sheet…" } else { "Protect Sheet…" }).clicked() {
app.open_dialog(if protected { "unprotectSheet" } else { "protectSheet" }, json!({}));
}
if ui.button("Select All Sheets").clicked() {
ui.close();