Upgrade accounts saved as http:// to https on launch
Benchmarks / CPU and I/O (per commit) (push) Successful in 1m53s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Successful in 45m7s
Build and test / Layer separation (push) Successful in 41s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 2s
Traceability / Requirement traces (push) Successful in 40s
Build and test / Android (aarch64) (push) Successful in 28m49s
Build and test / Windows (x86_64, cross) (push) Successful in 17m9s
Build and test / Publish the release (push) Skipped

Before the previous commit, browser sign-in could store an account as
http://, and the client now refuses to send to one. Left alone, such a
library would fail to open with a configuration error, so its stored
endpoint is rewritten before anything reads it.

The endpoint is half of two keys, and both are handled:

- The keyring entry is filed under it. Rewriting only the record would
  strand the app password under the old key and sign the user out, so
  AccountStore::move_endpoint copies the secret across first, rewrites
  the record in place (the last record is the one resumed), and deletes
  the old entry only once nothing refers to it.
- namespace() is built from it and names the catalog directory. For an
  http to https rewrite it does not change, because the namespace strips
  either scheme. A move that would change it is refused, not performed,
  so no later rewrite can abandon a catalog either.

The rewrite is a new BackendProvider::upgrade_endpoint hook, which does
nothing by default, and not a second call to normalise_endpoint. The
folder connector's normalise_endpoint canonicalises the path and needs
it to exist, so running it on every launch would fail a library on an
unplugged disk, or rename one whose path now resolves differently. Only
Nextcloud implements the hook.

If the move fails (for example, a locked keyring), it is logged, the
account is left as it was, and the move is tried again on the next
launch.

Closes #65.
This commit is contained in:
2026-09-24 20:44:19 -04:00
parent ea31791388
commit 5569a066ff
5 changed files with 269 additions and 5 deletions
+24
View File
@@ -96,6 +96,18 @@ impl BackendProvider for NextcloudProvider {
}
}
/// TRACES: NFR-SEC-3
/// An `http://` account written before sign-in kept the address the user
/// typed: it was stored as the server reported itself, and behind a proxy
/// without `overwriteprotocol` that is `http`. The client refuses to send
/// to it now, so it is upgraded here rather than left to fail. The
/// namespace ignores the scheme, so the catalog stays where it is.
fn upgrade_endpoint(&self, stored: &str) -> Option<String> {
stored
.strip_prefix("http://")
.map(|rest| format!("https://{rest}"))
}
fn connect(&self, conn: &Connection) -> Result<Box<dyn RemoteBackend>, RemoteError> {
let creds = Self::credentials(conn)?;
Ok(Box::new(NextcloudBackend::new(
@@ -132,6 +144,18 @@ mod tests {
assert!(p.normalise_endpoint(" ").is_err());
}
/// TRACES: NFR-SEC-3
#[test]
fn a_stored_http_endpoint_is_upgraded_and_nothing_else_is_touched() {
let p = NextcloudProvider;
assert_eq!(
p.upgrade_endpoint("http://cloud.example/nextcloud")
.as_deref(),
Some("https://cloud.example/nextcloud")
);
assert_eq!(p.upgrade_endpoint("https://cloud.example"), None);
}
#[test]
fn the_account_keeps_the_dav_user_id_apart_from_the_login() {
// A login can be an email address while the user id is something