From 139e900afd88c57c9471fe1ecd111ffe8a1e46cb Mon Sep 17 00:00:00 2001 From: Alexis Saettler Date: Fri, 30 Nov 2018 12:16:59 +0100 Subject: [PATCH] fix: fix vcard export/import support (#2084) --- .../Controllers/CardDAV/CardDAVController.php | 10 +- ...SabreBackend.php => MonicaAuthBackend.php} | 14 +- .../CardDAV/Backends/MonicaCardDAVBackend.php | 90 +++-------- ...Backend.php => MonicaPrincipalBackend.php} | 103 +++++-------- app/Models/CardDAV/MonicaAddressBook.php | 17 +++ app/Models/CardDAV/MonicaAddressBookHome.php | 8 +- app/Services/VCard/ExportVCard.php | 9 +- app/Services/VCard/ImportVCard.php | 13 ++ database/factories/ModelFactory.php | 3 + .../2018_11_21_212932_add_contacts_uuid.php | 32 ++++ tests/Api/Carddav/CarddavContactTest.php | 142 +++++++++++++++++- tests/Api/Carddav/CarddavServerTest.php | 40 ++++- tests/Unit/Services/VCard/ExportVCardTest.php | 24 +-- 13 files changed, 331 insertions(+), 174 deletions(-) rename app/Models/CardDAV/Backends/{MonicaSabreBackend.php => MonicaAuthBackend.php} (84%) rename app/Models/CardDAV/Backends/{MonicaPrincipleBackend.php => MonicaPrincipalBackend.php} (67%) create mode 100644 database/migrations/2018_11_21_212932_add_contacts_uuid.php diff --git a/app/Http/Controllers/CardDAV/CardDAVController.php b/app/Http/Controllers/CardDAV/CardDAVController.php index f46d120c6b6..366b7eea845 100644 --- a/app/Http/Controllers/CardDAV/CardDAVController.php +++ b/app/Http/Controllers/CardDAV/CardDAVController.php @@ -14,9 +14,9 @@ use Sabre\CardDAV\Plugin as CardDAVPlugin; use App\Models\CardDAV\MonicaAddressBookRoot; use Sabre\DAV\Browser\Plugin as BrowserPlugin; -use App\Models\CardDAV\Backends\MonicaSabreBackend; +use App\Models\CardDAV\Backends\MonicaAuthBackend; use App\Models\CardDAV\Backends\MonicaCardDAVBackend; -use App\Models\CardDAV\Backends\MonicaPrincipleBackend; +use App\Models\CardDAV\Backends\MonicaPrincipalBackend; class CardDAVController extends Controller { @@ -42,7 +42,7 @@ public function init(Request $request) private function getNodes() : array { // Initiate custom backends for link between Sabre and Monica - $principalBackend = new MonicaPrincipleBackend(); // User rights + $principalBackend = new MonicaPrincipalBackend(); // User rights $carddavBackend = new MonicaCardDAVBackend(); // Contacts return [ @@ -97,7 +97,7 @@ public function fullUrl(Request $request) private function addPlugins(SabreServer $server) { // Authentication backend - $authBackend = new MonicaSabreBackend(); + $authBackend = new MonicaAuthBackend(); $server->addPlugin(new AuthPlugin($authBackend, 'SabreDAV')); // CardDAV plugin @@ -112,7 +112,7 @@ private function addPlugins(SabreServer $server) // VCFExport $server->addPlugin(new VCFExportPlugin()); - // In debug mode add browser plugin + // In local environment add browser plugin if (App::environment('local')) { $server->addPlugin(new BrowserPlugin()); } diff --git a/app/Models/CardDAV/Backends/MonicaSabreBackend.php b/app/Models/CardDAV/Backends/MonicaAuthBackend.php similarity index 84% rename from app/Models/CardDAV/Backends/MonicaSabreBackend.php rename to app/Models/CardDAV/Backends/MonicaAuthBackend.php index c93a3295faa..486d32a28e6 100644 --- a/app/Models/CardDAV/Backends/MonicaSabreBackend.php +++ b/app/Models/CardDAV/Backends/MonicaAuthBackend.php @@ -4,11 +4,10 @@ use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; use Sabre\DAV\Auth\Backend\BackendInterface; -class MonicaSabreBackend implements BackendInterface +class MonicaAuthBackend implements BackendInterface { /** * Authentication Realm. @@ -20,13 +19,6 @@ class MonicaSabreBackend implements BackendInterface */ protected $realm = 'sabre/dav'; - /** - * This is the prefix that will be used to generate principal urls. - * - * @var string - */ - protected $principalPrefix = 'principals/'; - /** * Sets the authentication realm for this backend. * @@ -51,9 +43,7 @@ public function check(RequestInterface $request, ResponseInterface $response) return [false, 'User is not authenticated']; } - Log::debug(__CLASS__.' validateUserPass', [Auth::user()->name]); - - return [true, $this->principalPrefix.Auth::user()->email]; + return [true, MonicaPrincipalBackend::getPrincipalUser()]; } /** diff --git a/app/Models/CardDAV/Backends/MonicaCardDAVBackend.php b/app/Models/CardDAV/Backends/MonicaCardDAVBackend.php index f1f660463f1..9ec2873a171 100644 --- a/app/Models/CardDAV/Backends/MonicaCardDAVBackend.php +++ b/app/Models/CardDAV/Backends/MonicaCardDAVBackend.php @@ -9,9 +9,9 @@ use App\Services\VCard\ImportVCard; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; -use Sabre\CardDAV\Backend\BackendInterface as SabreBackendInterface; +use Sabre\CardDAV\Backend\AbstractBackend; -class MonicaCardDAVBackend implements SabreBackendInterface +class MonicaCardDAVBackend extends AbstractBackend { /** * Returns the list of addressbooks for a specific user. @@ -32,13 +32,12 @@ class MonicaCardDAVBackend implements SabreBackendInterface */ public function getAddressBooksForUser($principalUri) { - Log::debug(__CLASS__.' getAddressBooksForUser', func_get_args()); - return [ [ - 'id' => '0', - 'uri' => 'contacts', - 'principaluri' => 'principals/'.Auth::user()->email, + 'id' => '0', + 'uri' => 'contacts', + 'principaluri' => MonicaPrincipalBackend::getPrincipalUser(), + '{DAV:}displayname' => Auth::user()->name, ], ]; } @@ -61,8 +60,6 @@ public function getAddressBooksForUser($principalUri) */ public function updateAddressBook($addressBookId, DAV\PropPatch $propPatch) { - Log::debug(__CLASS__.' updateAddressBook', func_get_args()); - return false; } @@ -79,8 +76,6 @@ public function updateAddressBook($addressBookId, DAV\PropPatch $propPatch) */ public function createAddressBook($principalUri, $url, array $properties) { - Log::debug(__CLASS__.' createAddressBook', func_get_args()); - return false; } @@ -92,8 +87,6 @@ public function createAddressBook($principalUri, $url, array $properties) */ public function deleteAddressBook($addressBookId) { - Log::debug(__CLASS__.' deleteAddressBook', func_get_args()); - return false; } @@ -113,47 +106,39 @@ private function prepareCard($contact) Log::debug(__CLASS__.' prepareCard: '.(string) $e); } + $carddata = $vcard->serialize(); + return [ - 'id' => $contact->hashid(), - 'etag' => md5($vcard->serialize()), + 'id' => $contact->hashID(), 'uri' => $this->encodeUri($contact), - 'lastmodified' => $contact->updated_at->timestamp, - 'carddata' => $vcard->serialize(), + 'carddata' => $carddata, + 'etag' => '"'.md5($carddata).'"', + 'lastmodified' => $contact->updated_at, ]; } private function encodeUri($contact) { - return urlencode($contact->hashid().'.vcf'); + return urlencode($contact->uuid.'.vcf'); } private function decodeUri($uri) { - return str_replace('.vcf', '', urldecode($uri)); + return pathinfo(urldecode($uri), PATHINFO_FILENAME); } private function getContact($uri) { try { - return (new Contact)->resolveRouteBinding($this->decodeUri($uri)); + return Contact::where([ + 'account_id' => Auth::user()->account_id, + 'uuid' => $this->decodeUri($uri), + ])->first(); } catch (\Exception $e) { return; } } - private function prepareCards($contacts) - { - $results = []; - - foreach ($contacts as $contact) { - $results[] = $this->prepareCard($contact); - } - - Log::debug(__CLASS__.' prepareCards', ['count' => count($results)]); - - return $results; - } - /** * Returns all cards for a specific addressbook id. * @@ -175,19 +160,19 @@ private function prepareCards($contacts) */ public function getCards($addressbookId) { - Log::debug(__CLASS__.' getCards', func_get_args()); - $contacts = Auth::user()->account ->contacts() ->real() ->active() ->get(); - return $this->prepareCards($contacts); + return $contacts->map(function ($contact) { + return $this->prepareCard($contact); + }); } /** - * Returns a specfic card. + * Returns a specific card. * * The same set of prope * @param mixed $addressBookId @@ -196,36 +181,11 @@ public function getCards($addressbookId) */ public function getCard($addressBookId, $cardUri) { - Log::debug(__CLASS__.' getCard', func_get_args()); - $contact = $this->getContact($cardUri); return $this->prepareCard($contact); } - /** - * Returns a list of cards. - * - * This method should work identical to getCard, but instead return all the - * cards in the list as an array. - * - * If the backend supports this, it may allow for some speed-ups. - * - * @param mixed $addressBookId - * @param array $uris - * @return array - */ - public function getMultipleCards($addressBookId, array $uris) - { - Log::debug(__CLASS__.' getMultipleCards', func_get_args()); - - $contacts = array_map(function ($uri) { - return $this->getContact($uri); - }, $uris); - - return $this->prepareCards($contacts); - } - /** * Creates a new card. * @@ -253,8 +213,6 @@ public function getMultipleCards($addressBookId, array $uris) */ public function createCard($addressBookId, $cardUri, $cardData) { - Log::debug(__CLASS__.' createCard', func_get_args()); - return $this->importCard(null, $cardData); } @@ -285,8 +243,6 @@ public function createCard($addressBookId, $cardUri, $cardData) */ public function updateCard($addressBookId, $cardUri, $cardData) { - Log::debug(__CLASS__.' updateCard', func_get_args()); - return $this->importCard($cardUri, $cardData); } @@ -330,8 +286,6 @@ private function importCard($cardUri, $cardData) */ public function deleteCard($addressBookId, $cardUri) { - Log::debug(__CLASS__.' deleteCard', func_get_args()); - return false; } } diff --git a/app/Models/CardDAV/Backends/MonicaPrincipleBackend.php b/app/Models/CardDAV/Backends/MonicaPrincipalBackend.php similarity index 67% rename from app/Models/CardDAV/Backends/MonicaPrincipleBackend.php rename to app/Models/CardDAV/Backends/MonicaPrincipalBackend.php index bcfec368582..c4ec3c3ef20 100644 --- a/app/Models/CardDAV/Backends/MonicaPrincipleBackend.php +++ b/app/Models/CardDAV/Backends/MonicaPrincipalBackend.php @@ -3,12 +3,39 @@ namespace App\Models\CardDAV\Backends; use Sabre\DAV; -use Sabre\CalDAV; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; +use Sabre\DAVACL\PrincipalBackend\AbstractBackend; -class MonicaPrincipleBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterface +class MonicaPrincipalBackend extends AbstractBackend { + /** + * This is the prefix that will be used to generate principal urls. + * + * @var string + */ + public const PRINCIPAL_PREFIX = 'principals/'; + + /** + * Get the principal for current user. + * + * @return string + */ + public static function getPrincipalUser(): string + { + return static::PRINCIPAL_PREFIX.Auth::user()->email; + } + + protected function getPrincipals() + { + return [ + [ + 'uri' => static::getPrincipalUser(), + '{http://sabredav.org/ns}email-address' => Auth::user()->email, + '{DAV:}displayname' => Auth::user()->name, + ], + ]; + } + /** * Returns a list of principals based on a prefix. * @@ -27,29 +54,11 @@ class MonicaPrincipleBackend implements \Sabre\DAVACL\PrincipalBackend\BackendIn */ public function getPrincipalsByPrefix($prefixPath) { - Log::debug(__CLASS__.' getPrincipalsByPrefix', func_get_args()); - $principals = [ - [ - 'uri' => 'principals/'.Auth::user()->email, - '{http://sabredav.org/ns}email-address' => Auth::user()->email, - '{DAV:}displayname' => Auth::user()->name, - ], - ]; - - $prefixPath = trim($prefixPath, '/'); - if ($prefixPath) { - $prefixPath .= '/'; - } - - $return = []; - foreach ($principals as $principal) { - if ($prefixPath && strpos($principal['uri'], $prefixPath) !== 0) { - continue; - } - $return[] = $principal; - } + $prefixPath = str_finish($prefixPath, '/'); - return $return; + return array_filter($this->getPrincipals(), function ($principal) use ($prefixPath) { + return ! $prefixPath || strpos($principal['uri'], $prefixPath) == 0; + }); } /** @@ -62,9 +71,7 @@ public function getPrincipalsByPrefix($prefixPath) */ public function getPrincipalByPath($path) { - Log::debug(__CLASS__.' getPrincipalByPath', func_get_args()); - - foreach ($this->getPrincipalsByPrefix('principals') as $principal) { + foreach ($this->getPrincipalsByPrefix(static::PRINCIPAL_PREFIX) as $principal) { if ($principal['uri'] === $path) { return $principal; } @@ -89,7 +96,6 @@ public function getPrincipalByPath($path) */ public function updatePrincipal($path, DAV\PropPatch $propPatch) { - Log::debug(__CLASS__.' updatePrincipal', func_get_args()); } /** @@ -123,29 +129,6 @@ public function updatePrincipal($path, DAV\PropPatch $propPatch) */ public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { - Log::debug(__CLASS__.' searchPrincipals', func_get_args()); - } - - /** - * Finds a principal by its URI. - * - * This method may receive any type of uri, but mailto: addresses will be - * the most common. - * - * Implementation of this API is optional. It is currently used by the - * CalDAV system to find principals based on their email addresses. If this - * API is not implemented, some features may not work correctly. - * - * This method must return a relative principal path, or null, if the - * principal was not found or you refuse to find it. - * - * @param string $uri - * @param string $principalPrefix - * @return string - */ - public function findByUri($uri, $principalPrefix) - { - Log::debug(__CLASS__.' searchPrincipals', func_get_args()); } /** @@ -156,11 +139,12 @@ public function findByUri($uri, $principalPrefix) */ public function getGroupMemberSet($principal) { - Log::debug(__CLASS__.' getGroupMemberSet', func_get_args()); + $principal = $this->getPrincipalByPath($principal); + if (! $principal) { + throw new \Sabre\DAV\Exception('Principal not found'); + } - return [ - 'principals/'.Auth::user()->email, - ]; + return $principal['uri']; } /** @@ -171,11 +155,7 @@ public function getGroupMemberSet($principal) */ public function getGroupMembership($principal) { - Log::debug(__CLASS__.' getGroupMembership', func_get_args()); - - return [ - 'principals/'.Auth::user()->email, - ]; + return $this->getGroupMemberSet($principal); } /** @@ -189,6 +169,5 @@ public function getGroupMembership($principal) */ public function setGroupMemberSet($principal, array $members) { - Log::debug(__CLASS__.' setGroupMemberSet', func_get_args()); } } diff --git a/app/Models/CardDAV/MonicaAddressBook.php b/app/Models/CardDAV/MonicaAddressBook.php index af7e244d2ef..32f998b6db0 100644 --- a/app/Models/CardDAV/MonicaAddressBook.php +++ b/app/Models/CardDAV/MonicaAddressBook.php @@ -3,6 +3,7 @@ namespace App\Models\CardDAV; use Sabre\CardDAV\AddressBook; +use Illuminate\Support\Facades\Auth; class MonicaAddressBook extends AddressBook { @@ -45,4 +46,20 @@ public function getChildACL() { return $this->getACL(); } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return void + */ + public function getLastModified() + { + $contacts = Auth::user()->account + ->contacts() + ->real() + ->active() + ->get(); + + return $contacts->max('updated_at'); + } } diff --git a/app/Models/CardDAV/MonicaAddressBookHome.php b/app/Models/CardDAV/MonicaAddressBookHome.php index 50100b9133d..f2827f92d59 100644 --- a/app/Models/CardDAV/MonicaAddressBookHome.php +++ b/app/Models/CardDAV/MonicaAddressBookHome.php @@ -14,11 +14,9 @@ class MonicaAddressBookHome extends AddressBookHome public function getChildren() { $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); - $objs = []; - foreach ($addressbooks as $addressbook) { - $objs[] = new MonicaAddressBook($this->carddavBackend, $addressbook); - } - return $objs; + return collect($addressbooks)->map(function ($addressbook) { + return new MonicaAddressBook($this->carddavBackend, $addressbook); + }); } } diff --git a/app/Services/VCard/ExportVCard.php b/app/Services/VCard/ExportVCard.php index dba673c5f42..6b2a4257057 100644 --- a/app/Services/VCard/ExportVCard.php +++ b/app/Services/VCard/ExportVCard.php @@ -2,6 +2,7 @@ namespace App\Services\VCard; +use Illuminate\Support\Str; use App\Services\BaseService; use App\Models\Contact\Gender; use App\Models\Contact\Contact; @@ -50,11 +51,17 @@ private function escape($value) : string private function export(Contact $contact) : VCard { // The standard for most of these fields can be found on https://tools.ietf.org/html/rfc6350 + if (! $contact->uuid) { + $contact->forceFill([ + 'uuid' => Str::uuid(), + ])->save(); + } // Basic information $vcard = new VCard([ - 'UID' => $contact->hashid(), + 'UID' => $contact->uuid, 'SOURCE' => route('people.show', $contact), + 'VERSION' => '4.0', ]); $this->exportNames($contact, $vcard); diff --git a/app/Services/VCard/ImportVCard.php b/app/Services/VCard/ImportVCard.php index 6915cdcd3b5..c6c963364e3 100644 --- a/app/Services/VCard/ImportVCard.php +++ b/app/Services/VCard/ImportVCard.php @@ -372,6 +372,7 @@ private function importEntry($contact, VCard $entry): Contact } $this->importNames($contact, $entry); + $this->importUid($contact, $entry); $this->importGender($contact, $entry); $this->importPhoto($contact, $entry); $this->importWorkInformation($contact, $entry); @@ -479,6 +480,18 @@ private function importFromFN(Contact $contact, VCard $entry): void } } + /** + * Import uid of the contact. + * + * @param Contact $contact + * @param VCard $entry + * @return void + */ + private function importUid(Contact $contact, VCard $entry): void + { + $contact->uuid = (string) $entry->UID; + } + /** * Import gender of the contact. * diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 0d8f2b1b338..f6474302a5a 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -1,5 +1,7 @@ $data['account_id'], ])->id; }, + 'uuid' => Str::uuid(), ]; }); $factory->state(App\Models\Contact\Contact::class, 'partial', [ diff --git a/database/migrations/2018_11_21_212932_add_contacts_uuid.php b/database/migrations/2018_11_21_212932_add_contacts_uuid.php new file mode 100644 index 00000000000..efdcc70a6c8 --- /dev/null +++ b/database/migrations/2018_11_21_212932_add_contacts_uuid.php @@ -0,0 +1,32 @@ +uuid('uuid')->after('description')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('contacts', function (Blueprint $table) { + $table->dropColumn('uuid'); + }); + } +} diff --git a/tests/Api/Carddav/CarddavContactTest.php b/tests/Api/Carddav/CarddavContactTest.php index d97396eed6b..be5032e6681 100644 --- a/tests/Api/Carddav/CarddavContactTest.php +++ b/tests/Api/Carddav/CarddavContactTest.php @@ -20,7 +20,7 @@ public function test_carddav_get_one_contact() 'account_id' => $user->account->id, ]); - $response = $this->get("/carddav/addressbooks/{$user->email}/contacts/{$contact->hashid()}.vcf"); + $response = $this->get("/carddav/addressbooks/{$user->email}/contacts/{$contact->uuid}.vcf"); $response->assertStatus(200); $response->assertHeader('X-Sabre-Version'); @@ -39,8 +39,8 @@ public function test_carddav_put_one_contact() $user = $this->signin(); $response = $this->call('PUT', "/carddav/addressbooks/{$user->email}/contacts/single_vcard_stub.vcf", [], [], [], - ['content-type' => 'text/vcard; charset=utf-8'], - "BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nN:Doe;John;;;\nEND:VCARD" + ['content-type' => 'application/xml; charset=utf-8'], + "BEGIN:VCARD\nVERSION:4.0\nFN:John Doe\nN:Doe;John;;;\nEND:VCARD" ); $response->assertStatus(201); @@ -63,11 +63,11 @@ public function test_carddav_update_existing_contact() $contact = factory(Contact::class)->create([ 'account_id' => $user->account->id, ]); - $filename = urlencode($contact->hashid().'.vcf'); + $filename = urlencode($contact->uuid.'.vcf'); $response = $this->call('PUT', "/carddav/addressbooks/{$user->email}/contacts/{$filename}", [], [], [], - ['content-type' => 'text/vcard; charset=utf-8'], - "BEGIN:VCARD\nVERSION:3.0\nFN:John Doex\nN:Doex;John;;;\nEND:VCARD" + ['content-type' => 'application/xml; charset=utf-8'], + "BEGIN:VCARD\nVERSION:4.0\nFN:John Doex\nN:Doex;John;;;\nEND:VCARD" ); $response->assertStatus(204); @@ -90,13 +90,13 @@ public function test_carddav_update_existing_contact_no_modify() $contact = factory(Contact::class)->create([ 'account_id' => $user->account->id, ]); - $filename = urlencode($contact->hashid().'.vcf'); + $filename = urlencode($contact->uuid.'.vcf'); $response = $this->get("/carddav/addressbooks/{$user->email}/contacts/{$filename}"); $data = $response->getContent(); $response = $this->call('PUT', "/carddav/addressbooks/{$user->email}/contacts/{$filename}", [], [], [], - ['content-type' => 'text/vcard; charset=utf-8'], + ['content-type' => 'application/xml; charset=utf-8'], $data ); @@ -104,4 +104,130 @@ public function test_carddav_update_existing_contact_no_modify() $response->assertHeader('X-Sabre-Version'); $response->assertHeader('ETag'); } + + public function test_carddav_contacts_report() + { + $user = $this->signin(); + $contact = factory(Contact::class)->create([ + 'account_id' => $user->account->id, + ]); + + $response = $this->call('REPORT', "/carddav/addressbooks/{$user->email}/contacts/", [], [], [], + [ + 'HTTP_DEPTH' => '1', + 'content-type' => 'application/xml; charset=utf-8', + ], + ' + + + + + ' + ); + + $response->assertStatus(207); + $response->assertHeader('X-Sabre-Version'); + + $peopleurl = route('people.show', $contact); + $sabreversion = \Sabre\VObject\Version::VERSION; + + $response->assertSee(''. + ''. + "/carddav/addressbooks/{$user->email}/contacts/{$contact->uuid}.vcf". + ''. + ''. + '"'); + $response->assertSee('"'. + "BEGIN:VCARD \n". + "VERSION:4.0 \n". + "PRODID:-//Sabre//Sabre VObject {$sabreversion}//EN \n". + "UID:{$contact->uuid} \n". + "SOURCE:{$peopleurl} \n". + "FN:John Doe \n". + "N:Doe;John;;; \n". + "GENDER:O; \n". + "END:VCARD \n". + ''. + ''. + 'HTTP/1.1 200 OK'. + ''. + ''. + ''); + } + + public function test_carddav_contacts_report_multiget() + { + $user = $this->signin(); + $contact1 = factory(Contact::class)->create([ + 'account_id' => $user->account->id, + ]); + $contact2 = factory(Contact::class)->create([ + 'account_id' => $user->account->id, + ]); + + $response = $this->call('REPORT', "/carddav/addressbooks/{$user->email}/contacts/", [], [], [], + [ + 'HTTP_DEPTH' => '1', + ], + " + + + + + /carddav/addressbooks/{$user->email}/contacts/{$contact1->uuid}.vcf + /carddav/addressbooks/{$user->email}/contacts/{$contact2->uuid}.vcf + " + ); + + $response->assertStatus(207); + $response->assertHeader('X-Sabre-Version'); + + $people1url = route('people.show', $contact1); + $people2url = route('people.show', $contact2); + $sabreversion = \Sabre\VObject\Version::VERSION; + + $response->assertSee(''. + ''. + "/carddav/addressbooks/{$user->email}/contacts/{$contact1->uuid}.vcf". + ''. + ''. + '"'); + $response->assertSee('"'. + "BEGIN:VCARD \n". + "VERSION:4.0 \n". + "PRODID:-//Sabre//Sabre VObject {$sabreversion}//EN \n". + "UID:{$contact1->uuid} \n". + "SOURCE:{$people1url} \n". + "FN:John Doe \n". + "N:Doe;John;;; \n". + "GENDER:O; \n". + "END:VCARD \n". + ''. + ''. + 'HTTP/1.1 200 OK'. + ''. + ''); + $response->assertSee( + ''. + "/carddav/addressbooks/{$user->email}/contacts/{$contact2->uuid}.vcf". + ''. + ''. + '"'); + $response->assertSee('"'. + "BEGIN:VCARD \n". + "VERSION:4.0 \n". + "PRODID:-//Sabre//Sabre VObject {$sabreversion}//EN \n". + "UID:{$contact2->uuid} \n". + "SOURCE:{$people2url} \n". + "FN:John Doe \n". + "N:Doe;John;;; \n". + "GENDER:O; \n". + "END:VCARD \n". + ''. + ''. + 'HTTP/1.1 200 OK'. + ''. + ''. + ''); + } } diff --git a/tests/Api/Carddav/CarddavServerTest.php b/tests/Api/Carddav/CarddavServerTest.php index 2b0c6ff7151..bf9d8dc9545 100644 --- a/tests/Api/Carddav/CarddavServerTest.php +++ b/tests/Api/Carddav/CarddavServerTest.php @@ -121,10 +121,44 @@ public function test_carddav_propfind_contacts() $response->assertHeader('X-Sabre-Version'); $response->assertSee("/carddav/addressbooks/{$user->email}/contacts/"); - $contactId = urlencode(urlencode($contact->hashid())); + $contactId = urlencode($contact->uuid); $response->assertSee("/carddav/addressbooks/{$user->email}/contacts/{$contactId}.vcf"); } + public function test_carddav_propfind_contacts_with_props() + { + $user = $this->signin(); + $contact = factory(Contact::class)->create([ + 'account_id' => $user->account->id, + ]); + + $response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}/contacts/", [], [], [], + [ + 'HTTP_DEPTH' => 0, + ], + ' + + + + ' + ); + + $response->assertStatus(207); + $response->assertHeader('X-Sabre-Version'); + + $response->assertSee(''. + ''. + "/carddav/addressbooks/{$user->email}/contacts/". + ''. + ''. + "{$user->name}". + ''. + 'HTTP/1.1 200 OK'. + ''. + ''. + ' $user->account->id, ]); - $response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}/contacts/{$contact->hashid()}"); + $response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}/contacts/{$contact->uuid}"); $response->assertStatus(207); $response->assertHeader('X-Sabre-Version'); - $response->assertSee("/carddav/addressbooks/{$user->email}/contacts/{$contact->hashid()}"); + $response->assertSee("/carddav/addressbooks/{$user->email}/contacts/{$contact->uuid}"); } } diff --git a/tests/Unit/Services/VCard/ExportVCardTest.php b/tests/Unit/Services/VCard/ExportVCardTest.php index e028b1221aa..3f898a59928 100644 --- a/tests/Unit/Services/VCard/ExportVCardTest.php +++ b/tests/Unit/Services/VCard/ExportVCardTest.php @@ -274,13 +274,15 @@ public function test_vcard_prepares_an_almost_empty_vcard() self::defaultPropsCount + 4, $vCard->children() ); - $hash = $contact->hashid(); - $url = config('app.url'); + + $url = route('people.show', $contact); + $sabreversion = \Sabre\VObject\Version::VERSION; + $this->assertVObjectEqualsVObject("BEGIN:VCARD VERSION:4.0 -PRODID:-//Sabre//Sabre VObject 4.1.6//EN -UID:{$hash} -SOURCE:{$url}/people/{$hash} +PRODID:-//Sabre//Sabre VObject {$sabreversion}//EN +UID:{$contact->uuid} +SOURCE:{$url} FN:John Doe N:Doe;John;;; GENDER:O; @@ -324,13 +326,15 @@ public function test_vcard_prepares_a_complete_vcard() self::defaultPropsCount + 7, $vCard->children() ); - $hash = $contact->hashid(); - $url = config('app.url'); + + $url = route('people.show', $contact); + $sabreversion = \Sabre\VObject\Version::VERSION; + $this->assertVObjectEqualsVObject("BEGIN:VCARD VERSION:4.0 -PRODID:-//Sabre//Sabre VObject 4.1.6//EN -UID:{$hash} -SOURCE:{$url}/people/{$hash} +PRODID:-//Sabre//Sabre VObject {$sabreversion}//EN +UID:{$contact->uuid} +SOURCE:{$url} FN:John Doe N:Doe;John;;; GENDER:O;