Page MenuHomePhorge

D4659.1775180753.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D4659.1775180753.diff

diff --git a/src/app/Backends/DAV.php b/src/app/Backends/DAV.php
--- a/src/app/Backends/DAV.php
+++ b/src/app/Backends/DAV.php
@@ -2,6 +2,7 @@
namespace App\Backends;
+use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
class DAV
@@ -343,6 +344,57 @@
return $response !== false;
}
+ /**
+ * Initialize DAV folders (collections)
+ *
+ * @param \App\User $user User object
+ * @param array<array> $folders Folders list (path, displayname, type, components)
+ *
+ * @throws \Exception
+ */
+ public static function initDefaultFolders(\App\User $user, array $folders): void
+ {
+ // Cyrus DAV does not support proxy authorization via DAV. Even though it has
+ // the Authorize-As header, it is used only for cummunication with Murder backends.
+ // We use a one-time token instead. It's valid for 10 seconds, assume it's enough time.
+ $password = \App\Auth\Utils::tokenCreate((string) $user->id);
+
+ if ($password === null) {
+ throw new \Exception("Failed to create an authentication token for DAV");
+ }
+
+ $dav = new self($user->email, $password);
+
+ foreach ($folders as $props) {
+ $folder = new DAV\Folder();
+ $folder->href = $props['type'] . 's' . '/user/' . $user->email . '/' . $props['path'];
+ $folder->types = ['collection', $props['type']];
+ $folder->name = $props['displayname'] ?? '';
+ $folder->components = $props['components'] ?? [];
+
+ $existing = null;
+ try {
+ $existing = $dav->folderInfo($folder->href);
+ } catch (RequestException $e) {
+ // Cyrus DAV returns 503 Service Unavailable on a non-existing location (?)
+ if ($e->getCode() != 503 && $e->getCode() != 404) {
+ throw $e;
+ }
+ }
+
+ // folder already exists? check the properties and update if needed
+ if ($existing) {
+ if ($existing->name != $folder->name || $existing->components != $folder->components) {
+ if (!$dav->folderUpdate($folder)) {
+ throw new \Exception("Failed to update DAV folder {$folder->href}");
+ }
+ }
+ } elseif (!$dav->folderCreate($folder)) {
+ throw new \Exception("Failed to create DAV folder {$folder->href}");
+ }
+ }
+ }
+
/**
* Check server options (and authentication)
*
diff --git a/src/app/Backends/DAV/Folder.php b/src/app/Backends/DAV/Folder.php
--- a/src/app/Backends/DAV/Folder.php
+++ b/src/app/Backends/DAV/Folder.php
@@ -94,10 +94,15 @@
$type = 'calendar';
}
- $props .= '<d:resourcetype><d:collection/>' . ($type ? "<c:{$type}/>" : '') . '</d:resourcetype>';
+ // Cyrus DAV does not allow resourcetype property change
+ if ($tag != 'propertyupdate') {
+ $props .= '<d:resourcetype><d:collection/>' . ($type ? "<c:{$type}/>" : '') . '</d:resourcetype>';
+ }
if (!empty($this->components)) {
- $props .= '<c:supported-calendar-component-set>';
+ // Note: Normally Cyrus DAV does not allow supported-calendar-component-set property update,
+ // but I found in Cyrus code that the action can be forced with force=yes attribute.
+ $props .= '<c:supported-calendar-component-set force="yes">';
foreach ($this->components as $component) {
$props .= '<c:comp name="' . $component . '"/>';
}
diff --git a/src/app/Jobs/User/CreateJob.php b/src/app/Jobs/User/CreateJob.php
--- a/src/app/Jobs/User/CreateJob.php
+++ b/src/app/Jobs/User/CreateJob.php
@@ -106,6 +106,11 @@
$user->status |= \App\User::STATUS_IMAP_READY;
}
+ $folders = \config('services.dav.default_folders');
+ if (count($folders)) {
+ \App\Backends\DAV::initDefaultFolders($user, $folders);
+ }
+
// Make user active in non-mandate mode only
if (
!($wallet = $user->wallet())
diff --git a/src/config/services.php b/src/config/services.php
--- a/src/config/services.php
+++ b/src/config/services.php
@@ -1,5 +1,32 @@
<?php
+$dav_folders = [];
+if (env('DAV_WITH_DEFAULT_FOLDERS', false)) {
+ $dav_folders = [
+ [
+ // FIXME: Should we use something else than 'Default'? This is
+ // what Cyrus creates, and I didn't find a setting to change it,
+ // we can only disable creation of the folder.
+ 'path' => 'Default',
+ 'displayname' => 'Calendar',
+ 'components' => ['VEVENT'],
+ 'type' => 'calendar',
+ ],
+ [
+ 'path' => 'Tasks',
+ 'displayname' => 'Tasks',
+ 'components' => ['VTODO'],
+ 'type' => 'calendar',
+ ],
+ [
+ // FIXME: Same here, should we use 'Contacts'?
+ 'path' => 'Default',
+ 'displayname' => 'Contacts',
+ 'type' => 'addressbook',
+ ],
+ ];
+}
+
return [
/*
@@ -58,6 +85,7 @@
'dav' => [
'uri' => env('DAV_URI', 'https://proxy/'),
+ 'default_folders' => $dav_folders,
'verify' => (bool) env('DAV_VERIFY', true),
],
diff --git a/src/tests/Feature/Backends/DAVTest.php b/src/tests/Feature/Backends/DAVTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Backends/DAVTest.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Tests\Feature\Backends;
+
+use App\Backends\DAV;
+use App\Backends\IMAP;
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class DAVTest extends TestCase
+{
+ private $user;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ if (!\config('services.dav.uri')) {
+ $this->markTestSkipped();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ if ($this->user) {
+ $this->deleteTestUser($this->user->email);
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test initializing default folders for a user.
+ *
+ * @group imap
+ * @group dav
+ */
+ public function testInitDefaultFolders(): void
+ {
+ Queue::fake();
+
+ $props = ['password' => 'test-pass'];
+ $this->user = $user = $this->getTestUser('davtest-' . time() . '@' . \config('app.domain'), $props);
+
+ // Create the IMAP mailbox, it is required otherwise DAV requests will fail
+ \config(['imap.default_folders' => null]);
+ IMAP::createUser($user);
+
+ $dav_folders = [
+ [
+ 'path' => 'Default',
+ 'displayname' => 'Calendar-Test',
+ 'components' => ['VEVENT'],
+ 'type' => 'calendar',
+ ],
+ [
+ 'path' => 'Tasks',
+ 'displayname' => 'Tasks-Test',
+ 'components' => ['VTODO'],
+ 'type' => 'calendar',
+ ],
+ [
+ 'path' => 'Default',
+ 'displayname' => 'Contacts-Test',
+ 'type' => 'addressbook',
+ ],
+ ];
+
+ DAV::initDefaultFolders($user, $dav_folders);
+
+ $dav = new DAV($user->email, $props['password']);
+
+ $folders = $dav->listFolders(DAV::TYPE_VCARD);
+ $this->assertCount(1, $folders);
+ $this->assertSame('Contacts-Test', $folders[0]->name);
+
+ $folders = $dav->listFolders(DAV::TYPE_VEVENT);
+ $folders = array_filter($folders, function ($f) { return $f->name != 'Inbox' && $f->name != 'Outbox'; });
+ $folders = array_values($folders);
+ $this->assertCount(1, $folders);
+ $this->assertSame(['VEVENT'], $folders[0]->components);
+ $this->assertSame(['collection', 'calendar'], $folders[0]->types);
+ $this->assertSame('Calendar-Test', $folders[0]->name);
+
+ $folders = $dav->listFolders(DAV::TYPE_VTODO);
+ $folders = array_filter($folders, function ($f) { return $f->name != 'Inbox' && $f->name != 'Outbox'; });
+ $folders = array_values($folders);
+ $this->assertCount(1, $folders);
+ $this->assertSame(['VTODO'], $folders[0]->components);
+ $this->assertSame(['collection', 'calendar'], $folders[0]->types);
+ $this->assertSame('Tasks-Test', $folders[0]->name);
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 3, 1:45 AM (1 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822002
Default Alt Text
D4659.1775180753.diff (8 KB)

Event Timeline