Add laravel sabre
This commit is contained in:
parent
ddfcb04989
commit
5ed486559a
|
@ -0,0 +1,256 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dav;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\User;
|
||||||
|
use Sabre\CardDAV\Backend\AbstractBackend;
|
||||||
|
use Sabre\DAV\PropPatch;
|
||||||
|
use Sabre\VObject\Component\VCard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template M as array{lastmodified: int, etag: string, uri: string, id: int, size: int}
|
||||||
|
*/
|
||||||
|
class AddressBookBackend extends AbstractBackend
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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<int, array<string, string>>
|
||||||
|
*/
|
||||||
|
public function getAddressBooksForUser($principalUri)
|
||||||
|
{
|
||||||
|
if (1 !== preg_match('/^\/principals\/(.*)$/', $principalUri, $matches)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::where('email', $matches[1])->firstOrFail();
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'id' => 'contacts',
|
||||||
|
'principaluri' => $principalUri,
|
||||||
|
'uri' => 'contacts',
|
||||||
|
'{DAV:}displayname' => 'Kontakte',
|
||||||
|
'{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Alle Adressen',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updateAddressBook($addressBookId, PropPatch $propPatch)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, string> $properties
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function createAddressBook($principalUri, $url, array $properties)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an entire addressbook and all its contents.
|
||||||
|
*
|
||||||
|
* @param mixed $addressBookId
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteAddressBook($addressBookId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<int, M>
|
||||||
|
*/
|
||||||
|
public function getCards($addressbookId): array
|
||||||
|
{
|
||||||
|
return Member::get()->map(fn ($member) => $this->cardMeta($member))->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a specfic card.
|
||||||
|
*
|
||||||
|
* The same set of properties must be returned as with getCards. The only
|
||||||
|
* exception is that 'carddata' is absolutely required.
|
||||||
|
*
|
||||||
|
* If the card does not exist, you must return false.
|
||||||
|
*
|
||||||
|
* @param mixed $addressBookId
|
||||||
|
* @param string $cardUri
|
||||||
|
*
|
||||||
|
* @return M
|
||||||
|
*/
|
||||||
|
public function getCard($addressBookId, $cardUri)
|
||||||
|
{
|
||||||
|
$member = Member::where('slug', $cardUri)->firstOrFail();
|
||||||
|
|
||||||
|
return [
|
||||||
|
...$this->cardMeta($member),
|
||||||
|
'carddata' => $member->toVcard()->serialize(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMultipleCards($addressBookId, array $uris)
|
||||||
|
{
|
||||||
|
return Member::whereIn('slug', $uris)->get()->map(fn ($member) => [
|
||||||
|
...$this->cardMeta($member),
|
||||||
|
'carddata' => $member->toVcard()->serialize(),
|
||||||
|
])->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a card.
|
||||||
|
*
|
||||||
|
* @param mixed $addressBookId
|
||||||
|
* @param string $cardUri
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function deleteCard($addressBookId, $cardUri)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return M
|
||||||
|
*/
|
||||||
|
private function cardMeta(Member $member): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'lastmodified' => $member->updated_at->timestamp,
|
||||||
|
'etag' => '"'.$member->etag.'"',
|
||||||
|
'uri' => $member->slug,
|
||||||
|
'id' => $member->id,
|
||||||
|
'size' => strlen($member->toVcard()->serialize()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dav;
|
||||||
|
|
||||||
|
use App\User;
|
||||||
|
use Sabre\DAV\PropPatch;
|
||||||
|
use Sabre\DAVACL\PrincipalBackend\BackendInterface as PrincipalBackendInterface;
|
||||||
|
|
||||||
|
class Principal implements PrincipalBackendInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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<int, array<string, string>>
|
||||||
|
*/
|
||||||
|
public function getPrincipalsByPrefix($prefixPath)
|
||||||
|
{
|
||||||
|
if ('principals' !== $prefixPath) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return User::get()->map(fn ($user) => $this->userToPrincipal($user))->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a specific principal, specified by it's path.
|
||||||
|
* The returned structure should be the exact same as from
|
||||||
|
* getPrincipalsByPrefix.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function getPrincipalByPath($path)
|
||||||
|
{
|
||||||
|
if (1 !== preg_match('/^principals\/(.*)$/', $path, $matches)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::where('email', $matches[1])->firstOrFail();
|
||||||
|
|
||||||
|
return $this->userToPrincipal($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updatePrincipal($path, PropPatch $propPatch)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, string> $searchProperties
|
||||||
|
* @param string $test
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof')
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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|null
|
||||||
|
*/
|
||||||
|
public function findByUri($uri, $principalPrefix)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of members for a group-principal.
|
||||||
|
*
|
||||||
|
* @param string $principal
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getGroupMemberSet($principal)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of groups a principal is a member of.
|
||||||
|
*
|
||||||
|
* @param string $principal
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getGroupMembership($principal)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the list of group members for a group principal.
|
||||||
|
*
|
||||||
|
* The principals should be passed as a list of uri's.
|
||||||
|
*
|
||||||
|
* @param string $principal
|
||||||
|
*/
|
||||||
|
public function setGroupMemberSet($principal, array $members)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
private function userToPrincipal(User $user): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'{DAV:}displayname' => $user->name,
|
||||||
|
'uri' => '/principals/'.$user->email,
|
||||||
|
'{http://sabredav.org/ns}email-address' => $user->email,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dav;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
|
||||||
|
use LaravelSabre\Http\Auth\AuthBackend;
|
||||||
|
use LaravelSabre\LaravelSabre;
|
||||||
|
use Sabre\CardDAV\AddressBookRoot;
|
||||||
|
use Sabre\CardDAV\Plugin as CardDAVPlugin;
|
||||||
|
use Sabre\DAV\Auth\Plugin as AuthPlugin;
|
||||||
|
use Sabre\DAV\Browser\Plugin as BrowserPlugin;
|
||||||
|
use Sabre\DAVACL\AbstractPrincipalCollection;
|
||||||
|
use Sabre\DAVACL\PrincipalCollection;
|
||||||
|
|
||||||
|
class ServiceProvider extends BaseServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
LaravelSabre::nodes(function () {
|
||||||
|
return $this->nodes();
|
||||||
|
});
|
||||||
|
LaravelSabre::plugins(fn () => $this->plugins());
|
||||||
|
LaravelSabre::auth(function () {
|
||||||
|
auth()->onceBasic();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of nodes for DAV Collection.
|
||||||
|
*
|
||||||
|
* @return array<int, AbstractPrincipalCollection>
|
||||||
|
*/
|
||||||
|
private function nodes(): array
|
||||||
|
{
|
||||||
|
$principalBackend = new Principal();
|
||||||
|
$addressBookBackend = new AddressBookBackend();
|
||||||
|
|
||||||
|
// Directory tree
|
||||||
|
return [
|
||||||
|
new PrincipalCollection($principalBackend),
|
||||||
|
new AddressBookRoot($principalBackend, $addressBookBackend),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function plugins(): array
|
||||||
|
{
|
||||||
|
$authBackend = new AuthBackend();
|
||||||
|
|
||||||
|
return [
|
||||||
|
new BrowserPlugin(),
|
||||||
|
new AuthPlugin($authBackend),
|
||||||
|
new CardDAVPlugin(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use LaravelSabre\Http\Middleware\Authorize;
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| LaravelSabre Domain
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the subdomain where LaravelSabre will be accessible from. If the
|
||||||
|
| setting is null, LaravelSabre will reside under the same domain as the
|
||||||
|
| application. Otherwise, this value will be used as the subdomain.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'domain' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| LaravelSabre Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the URI path where LaravelSabre will be accessible from. Feel free
|
||||||
|
| to change this path to anything you like.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'path' => 'dav',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| LaravelSabre Master Switch
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option may be used to disable LaravelSabre.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enabled' => env('LARAVELSABRE_ENABLED', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| LaravelSabre Route Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These middleware will be assigned to every LaravelSabre route, giving you
|
||||||
|
| the chance to add your own middleware to this list or change any of
|
||||||
|
| the existing middleware. Or, you can simply stick with this list.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'api',
|
||||||
|
Authorize::class,
|
||||||
|
],
|
||||||
|
];
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class() extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('members', function (Blueprint $table) {
|
||||||
|
$table->string('slug', 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue