feat: CardDAV support (#1284)
This commit is contained in:
committed by
Alexis Saettler
parent
3ee534ae06
commit
c7eea2b8c5
@@ -48,7 +48,7 @@ APP_TRUSTED_PROXIES=
|
||||
|
||||
# Frequency of creation of new log files. Logs are written when an error occurs.
|
||||
# Refer to config/logging.php for the possible values.
|
||||
LOG_CHANNEL=syslog
|
||||
LOG_CHANNEL=single
|
||||
|
||||
SENTRY_SUPPORT=false
|
||||
SENTRY_LARAVEL_DSN=
|
||||
|
||||
@@ -116,6 +116,9 @@ AWS_SERVER=
|
||||
# Allow Two Factor Authentication feature on your instance
|
||||
MFA_ENABLED=false
|
||||
|
||||
# Enable CardDAV support (beta feature)
|
||||
CARDDAV_ENABLED=false
|
||||
|
||||
# CLIENT ID and SECRET used for the official mobile application
|
||||
# This is to make sure that only the mobile application that you approve can
|
||||
# access the route to let your users sign in with their credentials
|
||||
|
||||
@@ -18,6 +18,7 @@ UNRELEASED CHANGES:
|
||||
* API breaking change: Remove 'PUT /contacts/:contact_id/pets/:id' in favor of 'PUT /pets/:id' with a 'contact_id'
|
||||
* API breaking change: Every validator fails now send a HTTP 400 code (was 200) with the error 32
|
||||
* API breaking change: Every Invald Parameters errors now send a HTTP 400 (was 500 or 200) code with the error 41
|
||||
* Add CardDAV support — disabled by default. To enable it, toggle the `CARDDAV_ENABLED` env variable.
|
||||
* Use Laravel email verification, and remove the old module used for that
|
||||
|
||||
RELEASED VERSIONS:
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Barryvdh\Debugbar\Facade as Debugbar;
|
||||
use App\Models\CardDAV\MonicaAddressBookRoot;
|
||||
use App\Models\CardDAV\Backends\MonicaSabreBackend;
|
||||
use App\Models\CardDAV\Backends\MonicaCardDAVBackend;
|
||||
use App\Models\CardDAV\Backends\MonicaPrincipleBackend;
|
||||
|
||||
class CardDAVController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function init(Request $request)
|
||||
{
|
||||
// Disable debugger for caldav output
|
||||
Debugbar::disable();
|
||||
|
||||
// Initiate custom backends for link between Sabre and Monica
|
||||
$authBackend = new MonicaSabreBackend(); // Authentication
|
||||
$principalBackend = new MonicaPrincipleBackend(); // User rights
|
||||
$carddavBackend = new MonicaCardDAVBackend(); // Contacts
|
||||
|
||||
$nodes = [
|
||||
new \Sabre\DAVACL\PrincipalCollection($principalBackend),
|
||||
new MonicaAddressBookRoot($principalBackend, $carddavBackend),
|
||||
];
|
||||
|
||||
// Initiate Sabre server
|
||||
$server = new \Sabre\DAV\Server($nodes);
|
||||
$server->setBaseUri('/carddav');
|
||||
$server->debugExceptions = true;
|
||||
|
||||
// Modify Laravel request to include trailing slash. Laravel removes it by default, Sabre needs it.
|
||||
$server->httpRequest->setUrl(str_replace('/carddav', '/carddav/', $request->getRequestUri()));
|
||||
$server->httpRequest->setBaseUrl('/carddav/');
|
||||
|
||||
// Testing needs method verb to be set manually
|
||||
if (App::environment('testing')) {
|
||||
$server->httpRequest->setMethod($request->method());
|
||||
}
|
||||
|
||||
// Add required plugins
|
||||
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'SabreDAV'));
|
||||
|
||||
// CardDAV plugin
|
||||
$server->addPlugin(new \Sabre\CardDAV\Plugin());
|
||||
|
||||
// ACL plugnin
|
||||
$aclPlugin = new \Sabre\DAVACL\Plugin();
|
||||
$aclPlugin->allowUnauthenticatedAccess = false;
|
||||
$server->addPlugin($aclPlugin);
|
||||
|
||||
// In debug mode add browser plugin
|
||||
if (App::environment('local')) {
|
||||
$server->addPlugin(new \Sabre\DAV\Browser\Plugin());
|
||||
}
|
||||
|
||||
// Execute requests and catch output
|
||||
// We do this because laravel always sends a 200 back, but we need to use the StatusCode and of Sabre
|
||||
ob_start();
|
||||
$server->exec();
|
||||
$status = $server->httpResponse->getStatus();
|
||||
$content = ob_get_contents();
|
||||
$headers = $server->httpResponse->getHeaders();
|
||||
ob_end_clean();
|
||||
|
||||
// Return response through laravel
|
||||
Log::debug(__CLASS__.' init', [
|
||||
'status' => $status,
|
||||
'content' => $content,
|
||||
]);
|
||||
|
||||
return response($content, $status)
|
||||
->withHeaders($headers);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ class Kernel extends HttpKernel
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.tokenonbasic' => \App\Http\Middleware\AuthenticateWithTokenOnBasicAuth::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'throttle' => \App\Http\Middleware\ThrottleRequestsMiddleware::class,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
|
||||
/**
|
||||
* Authenticate user with Basic Authentication, with two methods:
|
||||
* - Basic auth: login + password
|
||||
* - Bearer on basic: login + api token.
|
||||
*/
|
||||
class AuthenticateWithTokenOnBasicAuth
|
||||
{
|
||||
/**
|
||||
* The guard factory instance.
|
||||
*
|
||||
* @var AuthManager
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Create a new middleware instance.
|
||||
*
|
||||
* @param AuthManager $auth
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(AuthManager $auth)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$this->authenticate($request);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authentication.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
private function authenticate($request)
|
||||
{
|
||||
if ($this->auth->check()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try Bearer authentication, with token in 'password' field on basic auth
|
||||
if (! $request->bearerToken()) {
|
||||
$password = $request->getPassword();
|
||||
$request->headers->set('Authorization', 'Bearer '.$password);
|
||||
}
|
||||
|
||||
$user = $this->auth->guard('api')->user($request);
|
||||
|
||||
if ($user && (! $request->getUser() || $request->getUser() === $user->email)) {
|
||||
$this->auth->setUser($user);
|
||||
} else {
|
||||
// Basic authentication
|
||||
$this->auth->onceBasic();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV\Backends;
|
||||
|
||||
use Sabre\DAV;
|
||||
use App\Models\Contact\Contact;
|
||||
use Sabre\VObject\Component\VCard;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class MonicaCardDAVBackend implements \Sabre\CardDAV\Backend\BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns the list of addressbooks for a specific user.
|
||||
*
|
||||
* Every addressbook should have the following properties:
|
||||
* id - an arbitrary unique id
|
||||
* uri - the 'basename' part of the url
|
||||
* principaluri - Same as the passed parameter
|
||||
*
|
||||
* Any additional clark-notation property may be passed besides this. Some
|
||||
* common ones are :
|
||||
* {DAV:}displayname
|
||||
* {urn:ietf:params:xml:ns:carddav}addressbook-description
|
||||
* {http://calendarserver.org/ns/}getctag
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
public function getAddressBooksForUser($principalUri)
|
||||
{
|
||||
Log::debug(__CLASS__.' getAddressBooksForUser', func_get_args());
|
||||
|
||||
return [
|
||||
[
|
||||
'id' => '0',
|
||||
'uri' => 'Contacts',
|
||||
'principaluri' => 'principals/'.Auth::user()->email,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties for an address book.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
* @param \Sabre\DAV\PropPatch $propPatch
|
||||
* @return void|bool
|
||||
*/
|
||||
public function updateAddressBook($addressBookId, DAV\PropPatch $propPatch)
|
||||
{
|
||||
Log::debug(__CLASS__.' updateAddressBook', func_get_args());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new address book.
|
||||
*
|
||||
* This method should return the id of the new address book. The id can be
|
||||
* in any format, including ints, strings, arrays or objects.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url Just the 'basename' of the url.
|
||||
* @param array $properties
|
||||
* @return int|bool
|
||||
*/
|
||||
public function createAddressBook($principalUri, $url, array $properties)
|
||||
{
|
||||
Log::debug(__CLASS__.' createAddressBook', func_get_args());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @return void|bool
|
||||
*/
|
||||
public function deleteAddressBook($addressBookId)
|
||||
{
|
||||
Log::debug(__CLASS__.' deleteAddressBook', func_get_args());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function prepareCard($contact)
|
||||
{
|
||||
// The standard for most of these fields can be found on https://tools.ietf.org/html/rfc6350
|
||||
|
||||
// Basic information
|
||||
$vcard = new VCard([
|
||||
'FN' => $contact->name,
|
||||
'N' => [$contact->first_name, $contact->last_name],
|
||||
'UID' => $contact->hashid(),
|
||||
]);
|
||||
|
||||
// Picture
|
||||
$picture = $contact->getAvatarURL();
|
||||
if (! is_null($picture)) {
|
||||
$vcard->add('PHOTO', $picture);
|
||||
}
|
||||
|
||||
// Gender
|
||||
switch ($contact->gender->name) {
|
||||
case 'Man':
|
||||
$gender = 'M';
|
||||
break;
|
||||
case 'Woman':
|
||||
$gender = 'F';
|
||||
break;
|
||||
default:
|
||||
$gender = 'O';
|
||||
break;
|
||||
}
|
||||
$vcard->add('GENDER', $gender.';'.$contact->gender->name);
|
||||
|
||||
// Birthday
|
||||
if (! is_null($contact->birthdate)) {
|
||||
$date = $contact->birthdate->date->format('Ymd');
|
||||
$vcard->add('BDAY', $date);
|
||||
}
|
||||
|
||||
// Contactfields
|
||||
foreach ($contact->contactFields as $contactField) {
|
||||
switch ($contactField->contactFieldType->name) {
|
||||
case 'Phone':
|
||||
$vcard->add('TEL', $contactField->data);
|
||||
break;
|
||||
case 'Email':
|
||||
$vcard->add('EMAIL', $contactField->data);
|
||||
break;
|
||||
case 'Facebook':
|
||||
$vcard->add('socialProfile', $contactField->data, ['type' => 'facebook']);
|
||||
break;
|
||||
// ... Twitter, Whatsapp, Telegram, other
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $contact->hashid(),
|
||||
'etag' => '"'.md5($vcard->serialize()).'"',
|
||||
'uri' => $contact->id,
|
||||
'lastmodified' => $contact->updated_at->timestamp,
|
||||
'carddata' => $vcard->serialize(),
|
||||
];
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* This method should return the following properties for each card:
|
||||
* * carddata - raw vcard data
|
||||
* * uri - Some unique url
|
||||
* * lastmodified - A unix timestamp
|
||||
*
|
||||
* It's recommended to also return the following properties:
|
||||
* * etag - A unique etag. This must change every time the card changes.
|
||||
* * size - The size of the card in bytes.
|
||||
*
|
||||
* If these last two properties are provided, less time will be spent
|
||||
* calculating them. If they are specified, you can also ommit carddata.
|
||||
* This may speed up certain requests, especially with large cards.
|
||||
*
|
||||
* @param mixed $addressbookId
|
||||
* @return array
|
||||
*/
|
||||
public function getCards($addressbookId)
|
||||
{
|
||||
Log::debug(__CLASS__.' getCards', func_get_args());
|
||||
|
||||
$contacts = Auth::user()->account->contacts()->real()->get();
|
||||
|
||||
return $this->prepareCards($contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specfic card.
|
||||
*
|
||||
* The same set of prope
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return array
|
||||
*/
|
||||
public function getCard($addressBookId, $cardUri)
|
||||
{
|
||||
Log::debug(__CLASS__.' getCard', func_get_args());
|
||||
|
||||
$contact = Contact::where('account_id', Auth::user()->account_id)
|
||||
->findOrFail($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 = Contact::where('account_id', Auth::user()->account_id)
|
||||
->whereIn('id', $uris)
|
||||
->get();
|
||||
|
||||
return $this->prepareCards($contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressBooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag is for the
|
||||
* newly created resource, and must be enclosed with double quotes (that
|
||||
* is, the string itself must contain the double quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createCard($addressBookId, $cardUri, $cardData)
|
||||
{
|
||||
Log::debug(__CLASS__.' createCard', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressBooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag should
|
||||
* match that of the updated resource, and must be enclosed with double
|
||||
* quotes (that is: the string itself must contain the actual quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function updateCard($addressBookId, $cardUri, $cardData)
|
||||
{
|
||||
Log::debug(__CLASS__.' updateCard', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a card.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteCard($addressBookId, $cardUri)
|
||||
{
|
||||
Log::debug(__CLASS__.' deleteCard', func_get_args());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV\Backends;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\CalDAV;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class MonicaPrincipleBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns a list of principals based on a prefix.
|
||||
*
|
||||
* This prefix will often contain something like 'principals'. You are only
|
||||
* expected to return principals that are in this base path.
|
||||
*
|
||||
* You are expected to return at least a 'uri' for every user, you can
|
||||
* return any additional properties if you wish so. Common properties are:
|
||||
* {DAV:}displayname
|
||||
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
|
||||
* field that's actually injected in a number of other properties. If
|
||||
* you have an email address, use this property.
|
||||
*
|
||||
* @param string $prefixPath
|
||||
* @return array
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific principal, specified by its path.
|
||||
* The returned structure should be the exact same as from
|
||||
* getPrincipalsByPrefix.
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public function getPrincipalByPath($path)
|
||||
{
|
||||
Log::debug(__CLASS__.' getPrincipalByPath', func_get_args());
|
||||
|
||||
foreach ($this->getPrincipalsByPrefix('principals') as $principal) {
|
||||
if ($principal['uri'] === $path) {
|
||||
return $principal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one ore more webdav properties on a principal.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \Sabre\DAV\PropPatch $propPatch
|
||||
* @return void
|
||||
*/
|
||||
public function updatePrincipal($path, DAV\PropPatch $propPatch)
|
||||
{
|
||||
Log::debug(__CLASS__.' updatePrincipal', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to search for principals matching a set of
|
||||
* properties.
|
||||
*
|
||||
* This search is specifically used by RFC3744's principal-property-search
|
||||
* REPORT.
|
||||
*
|
||||
* The actual search should be a unicode-non-case-sensitive search. The
|
||||
* keys in searchProperties are the WebDAV property names, while the values
|
||||
* are the property values to search on.
|
||||
*
|
||||
* By default, if multiple properties are submitted to this method, the
|
||||
* various properties should be combined with 'AND'. If $test is set to
|
||||
* 'anyof', it should be combined using 'OR'.
|
||||
*
|
||||
* This method should simply return an array with full principal uri's.
|
||||
*
|
||||
* If somebody attempted to search on a property the backend does not
|
||||
* support, you should simply return 0 results.
|
||||
*
|
||||
* You can also just return 0 results if you choose to not support
|
||||
* searching at all, but keep in mind that this may stop certain features
|
||||
* from working.
|
||||
*
|
||||
* @param string $prefixPath
|
||||
* @param array $searchProperties
|
||||
* @param string $test
|
||||
* @return array
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of members for a group-principal.
|
||||
*
|
||||
* @param string $principal
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMemberSet($principal)
|
||||
{
|
||||
debug('getGroupMemberSet');
|
||||
|
||||
return [
|
||||
'principals/'.Auth::user()->email,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups a principal is a member of.
|
||||
*
|
||||
* @param string $principal
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMembership($principal)
|
||||
{
|
||||
debug('getGroupMembership');
|
||||
|
||||
return [
|
||||
'principals/'.Auth::user()->email,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of group members for a group principal.
|
||||
*
|
||||
* The principals should be passed as a list of uri's.
|
||||
*
|
||||
* @param string $principal
|
||||
* @param array $members
|
||||
* @return void
|
||||
*/
|
||||
public function setGroupMemberSet($principal, array $members)
|
||||
{
|
||||
dd('setGroupMemberSet');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV\Backends;
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Authentication Realm.
|
||||
*
|
||||
* The realm is often displayed by browser clients when showing the
|
||||
* authentication dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param string $realm
|
||||
* @return void
|
||||
*/
|
||||
public function setRealm($realm)
|
||||
{
|
||||
$this->realm = $realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Laravel authentication.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if (! Auth::check()) {
|
||||
return [false, 'User is not authenticated'];
|
||||
}
|
||||
|
||||
Log::debug(__CLASS__.' validateUserPass', [Auth::user()->name]);
|
||||
|
||||
return [true, $this->principalPrefix.Auth::user()->email];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Bearer Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return void
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new \Sabre\HTTP\Auth\Bearer(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
$auth->requireLogin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV;
|
||||
|
||||
use Sabre\CardDAV\AddressBook;
|
||||
|
||||
class MonicaAddressBook extends AddressBook
|
||||
{
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ACL's for card nodes in this address book.
|
||||
* The result of this method automatically gets passed to the
|
||||
* card nodes in this address book.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildACL()
|
||||
{
|
||||
return $this->getACL();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV;
|
||||
|
||||
use Sabre\CardDAV\AddressBookHome;
|
||||
|
||||
class MonicaAddressBookHome extends AddressBookHome
|
||||
{
|
||||
/**
|
||||
* Returns a list of addressbooks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
$addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
|
||||
$objs = [];
|
||||
foreach ($addressbooks as $addressbook) {
|
||||
$objs[] = new MonicaAddressBook($this->carddavBackend, $addressbook);
|
||||
}
|
||||
|
||||
return $objs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\CardDAV;
|
||||
|
||||
use Sabre\CardDAV\AddressBookRoot;
|
||||
|
||||
class MonicaAddressBookRoot extends AddressBookRoot
|
||||
{
|
||||
/**
|
||||
* This method returns a node for a principal.
|
||||
*
|
||||
* The passed array contains principal information, and is guaranteed to
|
||||
* at least contain a uri item. Other properties may or may not be
|
||||
* supplied by the authentication backend.
|
||||
*
|
||||
* @param array $principal
|
||||
* @return \Sabre\DAV\INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal)
|
||||
{
|
||||
return new MonicaAddressBookHome($this->carddavBackend, $principal['uri']);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,10 @@ class RouteServiceProvider extends ServiceProvider
|
||||
|
||||
$this->mapOAuthRoutes($router);
|
||||
|
||||
if (config('carddav.enabled')) {
|
||||
$this->mapCardDAVRoutes($router);
|
||||
}
|
||||
|
||||
$this->mapSpecialRoutes($router);
|
||||
}
|
||||
|
||||
@@ -131,11 +135,29 @@ class RouteServiceProvider extends ServiceProvider
|
||||
'prefix' => 'api',
|
||||
'middleware' => 'api',
|
||||
'namespace' => $this->namespace,
|
||||
], function ($router) {
|
||||
], function () {
|
||||
require base_path('routes/api.php');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "carddav" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapCardDAVRoutes(Router $router)
|
||||
{
|
||||
$router->group([
|
||||
'prefix' => 'carddav',
|
||||
'middleware' => 'api',
|
||||
'namespace' => $this->namespace,
|
||||
], function () {
|
||||
require base_path('routes/carddav.php');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "special" routes for the application.
|
||||
*
|
||||
@@ -148,7 +170,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
$router->group([
|
||||
'middleware' => 'web',
|
||||
'namespace' => $this->namespace,
|
||||
], function ($router) {
|
||||
], function () {
|
||||
require base_path('routes/special.php');
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@
|
||||
"pragmarx/recovery": "^0.1",
|
||||
"predis/predis": "^1.1",
|
||||
"ralouphie/mimey": "^2.0",
|
||||
"sabre/vobject": "^4.1",
|
||||
"sabre/dav": "^3.2",
|
||||
"sentry/sentry-laravel": "^0.10",
|
||||
"stevebauman/location": "^3.0",
|
||||
"symfony/translation": "^4.0",
|
||||
@@ -61,7 +61,7 @@
|
||||
"roave/security-advisories": "dev-master",
|
||||
"symfony/css-selector": "~4.0",
|
||||
"symfony/dom-crawler": "~4.0",
|
||||
"vimeo/psalm": "^2.0"
|
||||
"vimeo/psalm": "~2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-apcu": "*"
|
||||
|
||||
Generated
+222
-93
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "251e221e6b645534037348feff30e0d1",
|
||||
"content-hash": "652aed71d76e7dd519a8e64001933836",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
@@ -5158,24 +5158,220 @@
|
||||
"time": "2016-07-19T19:14:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/uri",
|
||||
"version": "2.1.1",
|
||||
"name": "sabre/dav",
|
||||
"version": "3.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/uri.git",
|
||||
"reference": "a42126042c7dcb53e2978dadb6d22574d1359b4c"
|
||||
"url": "https://github.com/sabre-io/dav.git",
|
||||
"reference": "a9780ce4f35560ecbd0af524ad32d9d2c8954b80"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/uri/zipball/a42126042c7dcb53e2978dadb6d22574d1359b4c",
|
||||
"reference": "a42126042c7dcb53e2978dadb6d22574d1359b4c",
|
||||
"url": "https://api.github.com/repos/sabre-io/dav/zipball/a9780ce4f35560ecbd0af524ad32d9d2c8954b80",
|
||||
"reference": "a9780ce4f35560ecbd0af524ad32d9d2c8954b80",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7"
|
||||
"ext-ctype": "*",
|
||||
"ext-date": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-spl": "*",
|
||||
"lib-libxml": ">=2.7.0",
|
||||
"php": ">=5.5.0",
|
||||
"psr/log": "^1.0",
|
||||
"sabre/event": ">=2.0.0, <4.0.0",
|
||||
"sabre/http": "^4.2.1",
|
||||
"sabre/uri": "^1.0.1",
|
||||
"sabre/vobject": "^4.1.0",
|
||||
"sabre/xml": "^1.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0",
|
||||
"evert/phpdoc-md": "~0.1.0",
|
||||
"monolog/monolog": "^1.18",
|
||||
"phpunit/phpunit": "> 4.8, <6.0.0",
|
||||
"sabre/cs": "^1.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*"
|
||||
},
|
||||
"bin": [
|
||||
"bin/sabredav",
|
||||
"bin/naturalselection"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabre\\DAV\\": "lib/DAV/",
|
||||
"Sabre\\DAVACL\\": "lib/DAVACL/",
|
||||
"Sabre\\CalDAV\\": "lib/CalDAV/",
|
||||
"Sabre\\CardDAV\\": "lib/CardDAV/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "WebDAV Framework for PHP",
|
||||
"homepage": "http://sabre.io/",
|
||||
"keywords": [
|
||||
"CalDAV",
|
||||
"CardDAV",
|
||||
"WebDAV",
|
||||
"framework",
|
||||
"iCalendar"
|
||||
],
|
||||
"time": "2018-10-19T09:58:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/event",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/event.git",
|
||||
"reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534",
|
||||
"reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*",
|
||||
"sabre/cs": "~0.0.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabre\\Event\\": "lib/"
|
||||
},
|
||||
"files": [
|
||||
"lib/coroutine.php",
|
||||
"lib/Loop/functions.php",
|
||||
"lib/Promise/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "sabre/event is a library for lightweight event-based programming",
|
||||
"homepage": "http://sabre.io/event/",
|
||||
"keywords": [
|
||||
"EventEmitter",
|
||||
"async",
|
||||
"events",
|
||||
"hooks",
|
||||
"plugin",
|
||||
"promise",
|
||||
"signal"
|
||||
],
|
||||
"time": "2015-11-05T20:14:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/http",
|
||||
"version": "v4.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/http.git",
|
||||
"reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/http/zipball/acccec4ba863959b2d10c1fa0fb902736c5c8956",
|
||||
"reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.4",
|
||||
"sabre/event": ">=1.0.0,<4.0.0",
|
||||
"sabre/uri": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.3",
|
||||
"sabre/cs": "~0.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": " to make http requests with the Client class"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Sabre\\HTTP\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
|
||||
"homepage": "https://github.com/fruux/sabre-http",
|
||||
"keywords": [
|
||||
"http"
|
||||
],
|
||||
"time": "2018-02-23T11:10:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/uri",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/uri.git",
|
||||
"reference": "ada354d83579565949d80b2e15593c2371225e61"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/uri/zipball/ada354d83579565949d80b2e15593c2371225e61",
|
||||
"reference": "ada354d83579565949d80b2e15593c2371225e61",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=4.0,<6.0",
|
||||
"sabre/cs": "~1.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -5206,7 +5402,7 @@
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2017-02-20T20:02:35+00:00"
|
||||
"time": "2017-02-20T19:59:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/vobject",
|
||||
@@ -5307,16 +5503,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sabre/xml",
|
||||
"version": "2.1.1",
|
||||
"version": "1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/xml.git",
|
||||
"reference": "c5f510f8d0fa8135de9495145e2758f56037aa1e"
|
||||
"reference": "59b20e5bbace9912607481634f97d05a776ffca7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/xml/zipball/c5f510f8d0fa8135de9495145e2758f56037aa1e",
|
||||
"reference": "c5f510f8d0fa8135de9495145e2758f56037aa1e",
|
||||
"url": "https://api.github.com/repos/sabre-io/xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7",
|
||||
"reference": "59b20e5bbace9912607481634f97d05a776ffca7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5324,11 +5520,12 @@
|
||||
"ext-xmlreader": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"lib-libxml": ">=2.6.20",
|
||||
"php": ">=7.0",
|
||||
"php": ">=5.5.5",
|
||||
"sabre/uri": ">=1.0,<3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
"phpunit/phpunit": "*",
|
||||
"sabre/cs": "~1.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -5365,7 +5562,7 @@
|
||||
"dom",
|
||||
"xml"
|
||||
],
|
||||
"time": "2018-10-09T11:41:10+00:00"
|
||||
"time": "2016-10-09T22:57:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry",
|
||||
@@ -9067,66 +9264,6 @@
|
||||
"description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
|
||||
"time": "2018-10-02T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/event",
|
||||
"version": "5.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/event.git",
|
||||
"reference": "f5cf802d240df1257866d8813282b98aee3bc548"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/event/zipball/f5cf802d240df1257866d8813282b98aee3bc548",
|
||||
"reference": "f5cf802d240df1257866d8813282b98aee3bc548",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=6",
|
||||
"sabre/cs": "~1.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabre\\Event\\": "lib/"
|
||||
},
|
||||
"files": [
|
||||
"lib/coroutine.php",
|
||||
"lib/Loop/functions.php",
|
||||
"lib/Promise/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "sabre/event is a library for lightweight event-based programming",
|
||||
"homepage": "http://sabre.io/event/",
|
||||
"keywords": [
|
||||
"EventEmitter",
|
||||
"async",
|
||||
"coroutine",
|
||||
"eventloop",
|
||||
"events",
|
||||
"hooks",
|
||||
"plugin",
|
||||
"promise",
|
||||
"reactor",
|
||||
"signal"
|
||||
],
|
||||
"time": "2018-03-05T13:55:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/code-unit-reverse-lookup",
|
||||
"version": "1.0.1",
|
||||
@@ -9868,32 +10005,25 @@
|
||||
},
|
||||
{
|
||||
"name": "vimeo/psalm",
|
||||
"version": "2.0.15",
|
||||
"version": "2.0.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vimeo/psalm.git",
|
||||
"reference": "8b525a066b107b9936f338ec7c2c5618cf9a1764"
|
||||
"reference": "792144def40d678b7fa7c8a52da6cdbd34dd415a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vimeo/psalm/zipball/8b525a066b107b9936f338ec7c2c5618cf9a1764",
|
||||
"reference": "8b525a066b107b9936f338ec7c2c5618cf9a1764",
|
||||
"url": "https://api.github.com/repos/vimeo/psalm/zipball/792144def40d678b7fa7c8a52da6cdbd34dd415a",
|
||||
"reference": "792144def40d678b7fa7c8a52da6cdbd34dd415a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/xdebug-handler": "^1.1",
|
||||
"felixfbecker/advanced-json-rpc": "^3.0.3",
|
||||
"felixfbecker/language-server-protocol": "^1.2",
|
||||
"muglug/package-versions-56": "1.2.4",
|
||||
"netresearch/jsonmapper": "^1.0",
|
||||
"nikic/php-parser": "^4.0.3 || ^4.1",
|
||||
"nikic/php-parser": "^4.0",
|
||||
"openlss/lib-array2xml": "^0.0.10||^0.5.1",
|
||||
"php": "^7.0",
|
||||
"php-cs-fixer/diff": "^1.2",
|
||||
"sabre/event": "^5.0.1",
|
||||
"sabre/uri": "^2.0",
|
||||
"webmozart/glob": "^4.1",
|
||||
"webmozart/path-util": "^2.3"
|
||||
"php-cs-fixer/diff": "^1.2"
|
||||
},
|
||||
"provide": {
|
||||
"psalm/psalm": "self.version"
|
||||
@@ -9909,8 +10039,7 @@
|
||||
},
|
||||
"bin": [
|
||||
"psalm",
|
||||
"psalter",
|
||||
"psalm-language-server"
|
||||
"psalter"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -9939,7 +10068,7 @@
|
||||
"inspection",
|
||||
"php"
|
||||
],
|
||||
"time": "2018-10-19T18:04:22+00:00"
|
||||
"time": "2018-10-10T18:22:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* Carddav enabled
|
||||
*/
|
||||
|
||||
'enabled' => (bool) env('CARDDAV_ENABLED', false),
|
||||
|
||||
];
|
||||
@@ -27,10 +27,10 @@ return [
|
||||
*/
|
||||
'languages' => parse_langs_to_array(
|
||||
env('LANG_DETECTOR_LANGUAGES', [
|
||||
'en',
|
||||
'ar',
|
||||
'cs',
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'he',
|
||||
|
||||
@@ -76,5 +76,9 @@ return [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
'testing' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'info',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
links:
|
||||
depends_on:
|
||||
- mysql
|
||||
ports:
|
||||
- 8080:80
|
||||
@@ -30,7 +30,7 @@ services:
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
links:
|
||||
depends_on:
|
||||
- mysql
|
||||
environment:
|
||||
PMA_HOST: mysql
|
||||
|
||||
@@ -41,5 +41,6 @@
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="QUEUE_DRIVER" value="sync"/>
|
||||
<env name="APP_DEFAULT_LOCALE" value="en"/>
|
||||
<env name="LOG_CHANNEL" value="testing"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
$verbs = [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'POST',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'DELETE',
|
||||
'PROPFIND',
|
||||
'PROPPATCH',
|
||||
'MKCOL',
|
||||
'COPY',
|
||||
'MOVE',
|
||||
'LOCK',
|
||||
'UNLOCK',
|
||||
'OPTIONS',
|
||||
'REPORT',
|
||||
];
|
||||
|
||||
Illuminate\Routing\Router::$verbs = $verbs;
|
||||
|
||||
Route::group(['middleware' => ['auth.tokenonbasic']], function () use ($verbs) {
|
||||
Route::match($verbs, '{path?}', 'CardDAVController@init')->where('path', '(.)*');
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\Carddav;
|
||||
|
||||
use Tests\ApiTestCase;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class CarddavContact extends ApiTestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
if (! (bool) env('CARDDAV_ENABLED', false)) {
|
||||
$this->markTestSkipped('carddav disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_get_one_contact()
|
||||
{
|
||||
$user = $this->signin();
|
||||
$contact = factory(Contact::class)->create([
|
||||
'account_id' => $user->account->id,
|
||||
]);
|
||||
|
||||
$response = $this->get("/carddav/addressbooks/{$user->email}/Contacts/{$contact->id}");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee('PRODID:-//Sabre//Sabre VObject');
|
||||
$response->assertSee('FN:'.$contact->name);
|
||||
$response->assertSee('N:'.$contact->first_name.';'.$contact->last_name);
|
||||
$response->assertSee('GENDER:O\;');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\Carddav;
|
||||
|
||||
use Tests\ApiTestCase;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class CarddavServer extends ApiTestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
if (! (bool) env('CARDDAV_ENABLED', false)) {
|
||||
$this->markTestSkipped('carddav disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_base()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('PROPFIND', '/carddav');
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee('<d:response><d:href>/carddav/</d:href>');
|
||||
$response->assertSee('<d:response><d:href>/carddav/principals/</d:href>');
|
||||
$response->assertSee('<d:response><d:href>/carddav/addressbooks/</d:href>');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_principals()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('PROPFIND', '/carddav/principals');
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee('<d:response><d:href>/carddav/principals/</d:href>');
|
||||
$response->assertSee("<d:response><d:href>/carddav/principals/{$user->email}/</d:href>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_principals_user()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('PROPFIND', "/carddav/principals/{$user->email}");
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee("<d:response><d:href>/carddav/principals/{$user->email}/</d:href>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_ensure_browser_plugin_not_enabled()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('GET', '/carddav');
|
||||
|
||||
$response->assertStatus(501);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee('There was no plugin in the system that was willing to handle this GET method. Enable the Browser plugin to get a better result here.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_addressbooks()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('PROPFIND', '/carddav/addressbooks');
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee('<d:response><d:href>/carddav/addressbooks/</d:href>');
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/</d:href>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_addressbooks_user()
|
||||
{
|
||||
$user = $this->signin();
|
||||
|
||||
$response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}");
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/</d:href>");
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/Contacts/</d:href>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_contacts()
|
||||
{
|
||||
$user = $this->signin();
|
||||
$contact = factory(Contact::class)->create([
|
||||
'account_id' => $user->account->id,
|
||||
]);
|
||||
|
||||
$response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}/Contacts");
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/Contacts/</d:href>");
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/Contacts/{$contact->id}</d:href>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @group carddav
|
||||
*/
|
||||
public function test_carddav_propfind_one_contact()
|
||||
{
|
||||
$user = $this->signin();
|
||||
$contact = factory(Contact::class)->create([
|
||||
'account_id' => $user->account->id,
|
||||
]);
|
||||
|
||||
$response = $this->call('PROPFIND', "/carddav/addressbooks/{$user->email}/Contacts/{$contact->id}");
|
||||
|
||||
$response->assertStatus(207);
|
||||
$response->assertHeader('X-Sabre-Version');
|
||||
|
||||
$response->assertSee("<d:response><d:href>/carddav/addressbooks/{$user->email}/Contacts/{$contact->id}</d:href>");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user