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
- Perform the DELETE authenticated as the calendar owner (or via an owner calendar-proxy-write)
- Ask the owner to re-share the calendar with edit (read/write) permission so shared_access becomes ACCESS_READ_WRITE
- 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
- Read shared_access/calendarprincipaluri from the trashbin listing before enabling destructive actions
- Run trashbin purge jobs with owner credentials, never sharee credentials
- Treat 403 from trashbin DELETE as a permission signal, not a transient error
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
- Read-only sharees cannot restore trashbin entries
- Permission denied to create a directory in the trashbin
- Permission denied to delete the trashbin
- Permission denied to rename the trashbin
- not implemented
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/1839fc242db5e1a2.
Report an issue: GitHub.