Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117749730
D4659.1775180753.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None
D4659.1775180753.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D4659: Init default DAV folders
Attached
Detach File
Event Timeline