fix(dav): handle Thunderbird accept-before-sync UID collisions#61207
Open
ndo84bw wants to merge 2 commits into
Open
fix(dav): handle Thunderbird accept-before-sync UID collisions#61207ndo84bw wants to merge 2 commits into
ndo84bw wants to merge 2 commits into
Conversation
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud> Signed-off-by: Nico Donath <ndo84bw@gmx.de>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Nico Donath <ndo84bw@gmx.de>
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.
Processing message failed. Status: 80004005.#17915Summary
This takes over and completes #54471 with agreement (#54471 (comment)).
The problem
When someone is invited to an event, the server's scheduling code immediately writes a copy of that event into the invitee's personal calendar (under a server-generated object name). If the invitee opens the invitation email in Thunderbird and clicks Accept/Decline before their calendar has synced, Thunderbird does not yet know that object name. It therefore sends the PUT to a name it guesses itself. Because the event UID already exists in the calendar, the server rejects the PUT and Thunderbird shows error
80004005; the acceptance is lost.What this PR does
Commit 1 (original work by @st3iny, rebased onto master, fixup commits squashed): a Sabre plugin that, for a Thunderbird PUT of a calendar object whose UID already exists in the same target calendar, rewrites the request URI to the existing object so the PUT updates it instead of failing on the collision.
Commit 2 (this take-over): completes and hardens that plugin.
Deliver the organizer's reply. Thunderbird's accept-before-sync body stamps the organizer with
SCHEDULE-AGENT=CLIENT. That parameter tells the server not to perform scheduling, so no iTip REPLY is generated and the organizer never learns of the acceptance (they stayNEEDS-ACTION, no email). An attendee is not allowed to change the ORGANIZER property (RFC 5546), so the plugin restoresSCHEDULE-AGENTto the value of the stored event. This is a no-op when the organizer was genuinely client-scheduled (the stored copy already carriesCLIENT) and re-enables the reply in the common server-scheduled case.Let the rewritten PUT through. Thunderbird sends
If-None-Match: *because it believes it is creating a new object. After the URI has been rewritten to an existing object that precondition would fail with 412, so the header is dropped on the rewrite path.Only act on a genuine accept-before-sync. The plugin now returns early when Thunderbird already targets the object's real URI (i.e. the calendar was already synced). This avoids touching the body on a normal update - in particular it never strips a
SCHEDULE-AGENTthat the organizer set on their own copy.Authorization for the rewrite target. The plugin now runs before the ACL plugin (event priority 19 vs. the ACL plugin's 20; the current user principal is provided by the auth plugin at priority 10 and is available either way). The ACL plugin only checks PUT privileges when the target node exists; the original (guessed) URI never exists, so without this change the privilege check was skipped and not repeated after the rewrite. Running first means the ACL plugin evaluates
{DAV:}write-contentagainst the rewritten, existing object.Scope the collision lookup. The lookup is restricted to the calendar the PUT targets (
calendars/{principal}/{calendar}/...) and excludes subscription/federated cache rows (calendartype) and trashed objects/calendars (deleted_at). This prevents rewriting onto an object in a different calendar of the same user, onto a read-only subscription cache, or onto a deleted object.Testing
Manually verified on a live Nextcloud 33.0.3 instance (MariaDB, Apache/mod_php) with this patch applied, using Thunderbird as the invitee/organizer client:
80004005), the PUT succeedsUnit tests (
apps/dav/tests/unit/Connector/Sabre/ThunderbirdPutInvitationQuirkPluginTest.php) cover the URI rewrite, theIf-None-Matchremoval, the early return when the object URI already matches, and theSCHEDULE-AGENTrestore (restored from the stored organizer, left untouched when already equal, dropped when the stored organizer has none, and skipped when the stored data is unparsable). 17 tests pass locally with the flags CI uses (--fail-on-warning --fail-on-risky).The authorization change (item 4) is not reproduced as a live exploit here; it is established from the Sabre ACL plugin source (the privilege check is bound to
beforeMethodand skipped for a non-existent node, with no second check on the update path) and covered by the unit test asserting the registration priority.Checklist
3. to review, feature component)stable32)AI (if applicable)