Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion lib/CalDAV/OrganizerValidationPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function initialize(Server $server) {
$this->server = $server;
$server->on('calendarObjectChange', [$this, 'calendarObjectChange'], Plugin::PRIORITY_BEFORE_SCHEDULING - 10);
$server->on('beforeMove', [$this, 'beforeMove'], 45);
$server->on('beforeCopy', [$this, 'beforeCopy'], 45);
}

function getPluginName() {
Expand Down Expand Up @@ -55,8 +56,15 @@ function calendarObjectChange(
}

function beforeMove($sourcePath, $destinationPath) {
// A team calendar lives in its own calendar home, so a legitimate MOVE into one always
// crosses homes. Exempt exactly that destination from the guard, because it is the one
// the ORGANIZER validation below covers instead.
list($calendarPath,) = Utils::splitEventPath('/' . ltrim($destinationPath, '/'));
if (!$calendarPath || !$this->isTeamCalendarPath($calendarPath)) return;
if (!$calendarPath || !$this->isTeamCalendarPath($calendarPath)) {
$this->assertSameCalendarHome($sourcePath, $destinationPath);

return;
}

try {
$source = $this->server->tree->getNodeForPath($sourcePath);
Expand All @@ -80,6 +88,38 @@ function beforeMove($sourcePath, $destinationPath) {
}
}

function beforeCopy($sourcePath, $destinationPath, $depth = null) {
$this->assertSameCalendarHome($sourcePath, $destinationPath);
}

/**
* SabreDAV runs COPY and MOVE through Tree::copy()/Tree::move(), which never emit
* calendarObjectChange and therefore skip the ORGANIZER validation a PUT goes through.
* Confining a transfer to a single calendar home keeps an event that a PUT would have
* rejected from being smuggled into someone else's calendar.
*/
private function assertSameCalendarHome($sourcePath, $destinationPath): void {
$sourceHome = $this->calendarHomeOf($sourcePath);
$destinationHome = $this->calendarHomeOf($destinationPath);

// Not a calendar object on either end: collection level transfers are left to the ACL plugin.
if ($sourceHome === null || $destinationHome === null) return;

if ($sourceHome !== $destinationHome) {
throw new Forbidden('COPY and MOVE are only allowed within the same calendar home.');
}
}

private function calendarHomeOf($eventPath): ?string {
list($calendarPath,) = Utils::splitEventPath('/' . ltrim($eventPath, '/'));
if (!$calendarPath) {
return null;
}

// $calendarPath is 'calendars/{baseId}/{calendarUri}'.
return explode('/', $calendarPath)[1];
}

private function validateCalendarOrganizer(VCalendar $calendar, $calendarPath, ?string $existingOrganizerUri = null): void {
$vevents = $calendar->select('VEVENT');
if (empty($vevents) || !($organizerUri = $this->extractOrganizerUri($vevents))) return;
Expand Down
1 change: 1 addition & 0 deletions run_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ trap cleanup EXIT # finally: sera exécuté *quoi qu'il arrive*
javatest() {
git clone https://github.com/linagora/twake-calendar-integration-tests.git it-tests || exit 1
cd it-tests || exit 1
git checkout test-sabre-450 || exit 1
bash pre-build.sh esn_sabre_test || exit 1
mvn clean install -Dapi.version=1.43 -Dtest=com.linagora.dav.sabrev4_7.** -Damqp.scheduling.enabled=true || exit 1
}
Expand Down