Add Amazon S3 / S3-compatible storage backend#3261
Open
Nicolas-GUILLAUME wants to merge 2 commits into
Open
Conversation
Add an s3:// IFileStorage implementation (S3FileStorage) backed by AWSSDK.S3, supporting Amazon S3, Wasabi, Backblaze B2, Cloudflare R2 and custom/MinIO endpoints. Connection data (provider, region/endpoint, bucket, access key, secret key) is encoded into the IOConnectionInfo path like the FTP/SMB backends, so the recent-files store works with no extra plumbing. The backend is gated behind #if !NoNet and the AWSSDK.S3 PackageReference is excluded for the NoNet (offline) flavor. Design: - The credentials dialog takes a fully qualified object key instead of browsing the bucket, so a least-privilege IAM user only needs s3:GetObject and s3:PutObject (no s3:ListBucket). It shows a live "Resulting URL" preview, opens the exact object directly, and warns when the object key repeats the bucket name. ListContents therefore throws NotSupportedException. - Saves are atomic via a conditional If-Match write (412 -> "changed on the server"; 501 -> retried once without the precondition for providers lacking conditional writes). The ambiguous 403 that S3 returns without s3:ListBucket (missing vs forbidden) is surfaced as a clear message, and the write path reports its own 403 mentioning s3:PutObject. The bug-prone path codec (URL-encoding + string surgery) lives in a separate platform-agnostic library, Kp2aS3PathCodec (net8.0, no Android deps), with unit tests (Kp2aS3PathCodec.Tests, xunit) covering the settings serialize/parse round-trip (including secrets containing + : # / =), ParsePath, BuildFullPath, BuildPreviewUrl and bucket-in-key detection. Note: the access key and secret key persist in the recent-files store together with the file location, exactly like the FTP/WebDav backends. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two pre-existing, storage-agnostic issues surfaced while testing cloud saves: - Several operations set database meta timestamps with local DateTime.Now instead of DateTime.UtcNow (EntryTemplatesGroupChanged in AddTemplateEntries and DeleteRunnable, MasterKeyChanged in SetPassword). KeePassLib stores times as UTC and asserts on the kind, which aborts Debug builds when creating a database or changing the master key. Use UtcNow (same instant, correct kind). - OnOperationFinishedHandler.DisplayMessage created toasts/dialogs directly on the background save/load thread, crashing on any failure with "Can't toast on a thread that has not called Looper.prepare()". Marshal the display to the main looper, and show create/save errors in a dismissible dialog instead of a short-lived toast (CreateDatabaseActivity). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Description
Adds an Amazon S3 / S3-compatible storage backend (
s3://) so a.kdbxcan live on object storage, plus two small pre-existing bug fixes found while testing cloud saves. Two commits.New storage backend (commit 1)
S3FileStorage(IFileStorage, modeled onNetFtpFileStorage) backed byAWSSDK.S3, supporting Amazon S3, Wasabi, Backblaze B2, Cloudflare R2 and custom/MinIO endpoints.IOConnectionInfopath like the FTP/SMB backends. Gated behind#if !NoNet; theAWSSDK.S3PackageReference is excluded for the NoNet (offline) flavor.s3:GetObject+s3:PutObject(nos3:ListBucket). It shows a live "Resulting URL" preview, opens the exact object directly, and warns when the object key repeats the bucket name.ListContentsthereforethrowsNotSupportedException(the backend never browses).If-Matchwrite (412→ "changed on the server";501→ retried once without the precondition for providers lacking conditional writes). The ambiguous403S3 returns withoutListBucket(missing vs forbidden) is surfaced with a clear message, and the write path reports its own403(s3:PutObject).Kp2aS3PathCodec(net8.0, no Android deps) with xunit tests (Kp2aS3PathCodec.Tests) covering the settings serialize/parse round-trip (incl. secrets containing+ : # / =),ParsePath, full-path and preview-URL building, and bucket-in-key detection.Pre-existing fixes (commit 2 — storage-agnostic, surfaced while testing cloud saves)
DateTime.Now→DateTime.UtcNowfor DB meta timestamps (EntryTemplatesGroupChangedinAddTemplateEntries/DeleteRunnable,MasterKeyChangedinSetPassword). KeePassLib stores times as UTC and asserts on the kind, which aborts Debug builds when creating a database or changing the master key.OnOperationFinishedHandler.DisplayMessagenow marshals to the main looper — it previously created toasts/dialogs on the background save/load thread, crashing on any save failure with "Can't toast on a thread that has not called Looper.prepare()". Create/save errors are also shown in a dismissible dialog instead of a short-lived toast.🔗 Related Issue
N/A — new feature.
🛠️ Type of Change
✅ Checklist
src/.editorconfig, GPLv3 headers on new files)Kp2aS3PathCodec.Tests(22 cases) for the path codec. (TheIFileStoragebackends themselves have no unit-test harness; this extracts the testable part.)dotnet test; the autofill test project is untouched.)🧪 Testing
0 errors. Release link mode is the defaultSdkOnly, which does not trim theAWSSDK.S3/AWSSDK.Coreassemblies (verified they're packaged in the Release APK), so noTrimmerRootAssemblyis needed.Kp2aS3PathCodec.Tests: 22/22 pass..kdbx, create a new database, edit + save, and the error paths (missing object, forbidden object, missings3:PutObject, bucket-name-in-key warning).📌 Reviewer notes
AWSSDK.S33.7.511.8(ships anetstandard2.0target → compatible withnet9.0-android), excluded from the NoNet flavor via aCondition.src/Kp2aS3PathCodecandsrc/Kp2aS3PathCodec.Tests(both net8.0) added toKeePass.sln; tests run withcd src/Kp2aS3PathCodec.Tests && dotnet test(mirrors the existing autofill test project, so they could slot into CI the same way).🤖 Generated with Claude Code