nextcloud/server · error · Forbidden

Read-only sharees cannot permanently delete trashbin entries

Error message

Read-only sharees cannot permanently delete trashbin entries

What it means

Permanent deletion of a trashed calendar object (DELETE on calendars/<principal>/trashbin/objects/<id>.ics) is gated by DeletedCalendarObject::canModify(). The owner of a non-shared entry may always delete; for entries that came from a calendar shared to the acting principal, the row's shared_access must equal Backend::ACCESS_READ_WRITE. Read-only sharees (or rows without shared_access) get this Sabre Forbidden (HTTP 403) and deleteCalendarObject is never reached.

Source

Thrown at apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php:35

use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;

class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
	use ACLTrait;

	public function __construct(
		private string $name,
		/** @var mixed[] */
		private array $objectData,
		private string $principalUri,
		private CalDavBackend $calDavBackend,
	) {
	}

	#[\Override]
	public function delete() {
		if (!$this->canModify()) {
			throw new Forbidden('Read-only sharees cannot permanently delete trashbin entries');
		}
		$this->calDavBackend->deleteCalendarObject(
			$this->objectData['calendarid'],
			$this->objectData['uri'],
			CalDavBackend::CALENDAR_TYPE_CALENDAR,
			true
		);
	}

	private function isShared(): bool {
		$calendarOwner = $this->objectData['calendarprincipaluri'] ?? null;
		return $calendarOwner !== null && $calendarOwner !== $this->principalUri;
	}

	private function canModify(): bool {
		if (!$this->isShared()) {
			return true;
		}

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Perform the DELETE authenticated as the calendar owner (or via an owner calendar-proxy-write)
  2. Ask the owner to re-share the calendar with edit (read/write) permission so shared_access becomes ACCESS_READ_WRITE
  3. As a client, hide permanent-delete actions for trashbin entries whose source calendar (calendarprincipaluri/sourcecalendaruri metadata) is shared to the current user read-only
Defensive patterns

Strategy: try-catch

Validate before calling

// Only offer permanent delete when metadata allows it
const canModify = (entry) =>
  !entry.calendarprincipaluri ||                       // own trashbin entry
  entry.shared_access === 2 /* ACCESS_READ_WRITE */;
if (canModify(entry)) { await client.delete(entry.href); }

Try / catch

try {
    await client.delete(`/remote.php/dav/calendars/${me}/trashbin/objects/${id}.ics`);
} catch (e) {
    if (e.status === 403 && e.message.includes('Read-only sharees')) {
        // retry with owner credentials or prompt the owner to act
        return escalateToOwner(entry);
    }
    throw e;
}

Prevention

When it happens

Trigger: A DELETE request on a trashbin object whose calendarprincipaluri differs from the authenticated principal's trashbin (a shared calendar) while shared_access is ACCESS_READ or missing; e.g. a read-only sharee clicking 'delete permanently' in a custom trashbin UI.

Common situations: Custom CalDAV clients that list the trashbin for shared calendars without filtering by edit permission; scripts running with sharee credentials attempting cleanup; misunderstanding that the trashbin enforces the original share's permission level, not just authentication.

Related errors


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/1839fc242db5e1a2. Report an issue: GitHub.