diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index 71b3699b1ecef5b46f0e060f8e385c7e8c2d0937..dc90ac58188b84a41e6e019886ed950288ee1ae3 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
 	<name>WebDAV</name>
 	<summary>WebDAV endpoint</summary>
 	<description>WebDAV endpoint</description>
-	<version>1.11.0</version>
+	<version>1.11.1</version>
 	<licence>agpl</licence>
 	<author>owncloud.org</author>
 	<namespace>DAV</namespace>
diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
index c2634a595a3b063a5ac4a1f7b28ea1139ebf79b0..8116453b11d49a60e1f6dde4438019ade09b75c4 100644
--- a/apps/dav/appinfo/v1/caldav.php
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -46,8 +46,8 @@ $principalBackend = new Principal(
 	\OC::$server->getGroupManager(),
 	\OC::$server->getShareManager(),
 	\OC::$server->getUserSession(),
-	\OC::$server->getConfig(),
 	\OC::$server->getAppManager(),
+	\OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class),
 	'principals/'
 );
 $db = \OC::$server->getDatabaseConnection();
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php
index 60669df60bad8f9a51fd7de9f9762f29372c2fe3..40ee12f1944eab56bc194589d804be6d8e0386e5 100644
--- a/apps/dav/appinfo/v1/carddav.php
+++ b/apps/dav/appinfo/v1/carddav.php
@@ -47,8 +47,8 @@ $principalBackend = new Principal(
 	\OC::$server->getGroupManager(),
 	\OC::$server->getShareManager(),
 	\OC::$server->getUserSession(),
-	\OC::$server->getConfig(),
 	\OC::$server->getAppManager(),
+	\OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class),
 	'principals/'
 );
 $db = \OC::$server->getDatabaseConnection();
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index 2931a4b1da7d85100017cb90050b5e326efaa4f1..694231eebdafc836a564f90ab3a1413c3f8aaee2 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -44,6 +44,8 @@ return array(
     'OCA\\DAV\\CalDAV\\Plugin' => $baseDir . '/../lib/CalDAV/Plugin.php',
     'OCA\\DAV\\CalDAV\\Principal\\Collection' => $baseDir . '/../lib/CalDAV/Principal/Collection.php',
     'OCA\\DAV\\CalDAV\\Principal\\User' => $baseDir . '/../lib/CalDAV/Principal/User.php',
+    'OCA\\DAV\\CalDAV\\Proxy\\Proxy' => $baseDir . '/../lib/CalDAV/Proxy/Proxy.php',
+    'OCA\\DAV\\CalDAV\\Proxy\\ProxyMapper' => $baseDir . '/../lib/CalDAV/Proxy/ProxyMapper.php',
     'OCA\\DAV\\CalDAV\\PublicCalendar' => $baseDir . '/../lib/CalDAV/PublicCalendar.php',
     'OCA\\DAV\\CalDAV\\PublicCalendarObject' => $baseDir . '/../lib/CalDAV/PublicCalendarObject.php',
     'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => $baseDir . '/../lib/CalDAV/PublicCalendarRoot.php',
@@ -181,6 +183,7 @@ return array(
     'OCA\\DAV\\Migration\\Version1008Date20181105112049' => $baseDir . '/../lib/Migration/Version1008Date20181105112049.php',
     'OCA\\DAV\\Migration\\Version1008Date20181114084440' => $baseDir . '/../lib/Migration/Version1008Date20181114084440.php',
     'OCA\\DAV\\Migration\\Version1011Date20190725113607' => $baseDir . '/../lib/Migration/Version1011Date20190725113607.php',
+    'OCA\\DAV\\Migration\\Version1011Date20190806104428' => $baseDir . '/../lib/Migration/Version1011Date20190806104428.php',
     'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
     'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
     'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
@@ -193,6 +196,7 @@ return array(
     'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
     'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
     'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
+    'OCA\\DAV\\Traits\\PrincipalProxyTrait' => $baseDir . '/../lib/Traits/PrincipalProxyTrait.php',
     'OCA\\DAV\\Upload\\AssemblyStream' => $baseDir . '/../lib/Upload/AssemblyStream.php',
     'OCA\\DAV\\Upload\\ChunkingPlugin' => $baseDir . '/../lib/Upload/ChunkingPlugin.php',
     'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index dd759e423df5370663d7c4c9691fd1f4e5e17918..6f1049160770b678ca3a2fa313e975253140f0c1 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -59,6 +59,8 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\CalDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Plugin.php',
         'OCA\\DAV\\CalDAV\\Principal\\Collection' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/Collection.php',
         'OCA\\DAV\\CalDAV\\Principal\\User' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/User.php',
+        'OCA\\DAV\\CalDAV\\Proxy\\Proxy' => __DIR__ . '/..' . '/../lib/CalDAV/Proxy/Proxy.php',
+        'OCA\\DAV\\CalDAV\\Proxy\\ProxyMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Proxy/ProxyMapper.php',
         'OCA\\DAV\\CalDAV\\PublicCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendar.php',
         'OCA\\DAV\\CalDAV\\PublicCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarObject.php',
         'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarRoot.php',
@@ -196,6 +198,7 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\Migration\\Version1008Date20181105112049' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105112049.php',
         'OCA\\DAV\\Migration\\Version1008Date20181114084440' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181114084440.php',
         'OCA\\DAV\\Migration\\Version1011Date20190725113607' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190725113607.php',
+        'OCA\\DAV\\Migration\\Version1011Date20190806104428' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190806104428.php',
         'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
         'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
         'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
@@ -208,6 +211,7 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
         'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
         'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
+        'OCA\\DAV\\Traits\\PrincipalProxyTrait' => __DIR__ . '/..' . '/../lib/Traits/PrincipalProxyTrait.php',
         'OCA\\DAV\\Upload\\AssemblyStream' => __DIR__ . '/..' . '/../lib/Upload/AssemblyStream.php',
         'OCA\\DAV\\Upload\\ChunkingPlugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingPlugin.php',
         'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php',
diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php
index f26913d7ce19b2a343230a56512dfacfb0bf59f0..38def19af1da0f8d52f6d0eb074b53abffa88f88 100644
--- a/apps/dav/lib/CalDAV/Calendar.php
+++ b/apps/dav/lib/CalDAV/Calendar.php
@@ -26,6 +26,7 @@
  */
 namespace OCA\DAV\CalDAV;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\DAV\Sharing\IShareable;
 use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
 use OCP\IConfig;
@@ -46,6 +47,14 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
 	/** @var IConfig */
 	private $config;
 
+	/**
+	 * Calendar constructor.
+	 *
+	 * @param BackendInterface $caldavBackend
+	 * @param $calendarInfo
+	 * @param IL10N $l10n
+	 * @param IConfig $config
+	 */
 	public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config) {
 		parent::__construct($caldavBackend, $calendarInfo);
 
@@ -119,27 +128,58 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
 		return $this->calendarInfo['principaluri'];
 	}
 
+	/**
+	 * @return array
+	 */
 	public function getACL() {
 		$acl =  [
 			[
 				'privilege' => '{DAV:}read',
 				'principal' => $this->getOwner(),
 				'protected' => true,
-			]];
+			],
+			[
+				'privilege' => '{DAV:}read',
+				'principal' => $this->getOwner() . '/calendar-proxy-write',
+				'protected' => true,
+			],
+			[
+				'privilege' => '{DAV:}read',
+				'principal' => $this->getOwner() . '/calendar-proxy-read',
+				'protected' => true,
+			],
+		];
+
 		if ($this->getName() !== BirthdayService::BIRTHDAY_CALENDAR_URI) {
 			$acl[] = [
 				'privilege' => '{DAV:}write',
 				'principal' => $this->getOwner(),
 				'protected' => true,
 			];
+			$acl[] = [
+				'privilege' => '{DAV:}write',
+				'principal' => $this->getOwner() . '/calendar-proxy-write',
+				'protected' => true,
+			];
 		} else {
 			$acl[] = [
 				'privilege' => '{DAV:}write-properties',
 				'principal' => $this->getOwner(),
 				'protected' => true,
 			];
+			$acl[] = [
+				'privilege' => '{DAV:}write-properties',
+				'principal' => $this->getOwner() . '/calendar-proxy-write',
+				'protected' => true,
+			];
 		}
 
+		$acl[] = [
+			'privilege' => '{DAV:}write-properties',
+			'principal' => $this->getOwner() . '/calendar-proxy-read',
+			'protected' => true,
+		];
+
 		if (!$this->isShared()) {
 			return $acl;
 		}
@@ -173,7 +213,13 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
 		}
 
 		$acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl);
-		$allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/public'];
+		$allowedPrincipals = [
+			$this->getOwner(),
+			$this->getOwner(). '/calendar-proxy-read',
+			$this->getOwner(). '/calendar-proxy-write',
+			parent::getOwner(),
+			'principals/system/public'
+		];
 		return array_filter($acl, function($rule) use ($allowedPrincipals) {
 			return \in_array($rule['principal'], $allowedPrincipals, true);
 		});
diff --git a/apps/dav/lib/CalDAV/Proxy/Proxy.php b/apps/dav/lib/CalDAV/Proxy/Proxy.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb01470ae828c9b0b6496b8da0446b8cd03e3be6
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Proxy/Proxy.php
@@ -0,0 +1,51 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\CalDAV\Proxy;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method string getOwnerId()
+ * @method void setOwnerId(string $ownerId)
+ * @method string getProxyId()
+ * @method void setProxyId(string $proxyId)
+ * @method int getPermissions()
+ * @method void setPermissions(int $permissions)
+ */
+class Proxy extends Entity {
+
+	/** @var string */
+	protected $ownerId;
+	/** @var string */
+	protected $proxyId;
+	/** @var int */
+	protected $permissions;
+
+	public function __construct() {
+		$this->addType('ownerId', 'string');
+		$this->addType('proxyId', 'string');
+		$this->addType('permissions', 'int');
+	}
+}
diff --git a/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php b/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d8adb811b6203d0fb9354a4d19a0ce0bcb266fc
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Proxy/ProxyMapper.php
@@ -0,0 +1,78 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\CalDAV\Proxy;
+
+use OCP\AppFramework\Db\QBMapper;
+use OCP\IDBConnection;
+
+/**
+ * Class ProxyMapper
+ *
+ * @package OCA\DAV\CalDAV\Proxy
+ */
+class ProxyMapper extends QBMapper {
+
+	const PERMISSION_READ  = 1;
+	const PERMISSION_WRITE = 2;
+
+	/**
+	 * ProxyMapper constructor.
+	 *
+	 * @param IDBConnection $db
+	 */
+	public function __construct(IDBConnection $db) {
+		parent::__construct($db, 'dav_cal_proxy', Proxy::class);
+	}
+
+	/**
+	 * @param string $proxyId The principal uri that can act as a proxy for the resulting calendars
+	 *
+	 * @return Proxy[]
+	 */
+	public function getProxiesFor(string $proxyId): array {
+		$qb = $this->db->getQueryBuilder();
+
+		$qb->select('*')
+			->from($this->getTableName())
+			->where($qb->expr()->eq('proxy_id', $qb->createNamedParameter($proxyId)));
+
+		return $this->findEntities($qb);
+	}
+
+	/**
+	 * @param string $ownerId The principal uri that has the resulting proxies for their calendars
+	 *
+	 * @return Proxy[]
+	 */
+	public function getProxiesOf(string $ownerId): array {
+		$qb = $this->db->getQueryBuilder();
+
+		$qb->select('*')
+			->from($this->getTableName())
+			->where($qb->expr()->eq('owner_id', $qb->createNamedParameter($ownerId)));
+
+		return $this->findEntities($qb);
+	}
+}
diff --git a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php
index aab5fcab8ad862ed5687015b2a95eacb97383ee0..63ed3381d142d5747e14fd5a80bdb7a70b51f6a2 100644
--- a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php
+++ b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php
@@ -22,6 +22,8 @@
  */
 namespace OCA\DAV\CalDAV\ResourceBooking;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
+use OCA\DAV\Traits\PrincipalProxyTrait;
 use OCP\IDBConnection;
 use OCP\IGroupManager;
 use OCP\ILogger;
@@ -44,6 +46,9 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
 	/** @var ILogger */
 	private $logger;
 
+	/** @var ProxyMapper */
+	private $proxyMapper;
+
 	/** @var string */
 	private $principalPrefix;
 
@@ -72,6 +77,7 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
 								IUserSession $userSession,
 								IGroupManager $groupManager,
 								ILogger $logger,
+								ProxyMapper $proxyMapper,
 								string $principalPrefix,
 								string $dbPrefix,
 								string $cuType) {
@@ -79,6 +85,7 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
 		$this->userSession = $userSession;
 		$this->groupManager = $groupManager;
 		$this->logger = $logger;
+		$this->proxyMapper = $proxyMapper;
 		$this->principalPrefix = $principalPrefix;
 		$this->dbTableName = 'calendar_' . $dbPrefix . 's';
 		$this->dbMetaDataTableName = $this->dbTableName . '_md';
@@ -86,6 +93,8 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
 		$this->cuType = $cuType;
 	}
 
+	use PrincipalProxyTrait;
+
 	/**
 	 * Returns a list of principals based on a prefix.
 	 *
@@ -215,39 +224,6 @@ abstract class AbstractPrincipalBackend implements BackendInterface {
 		return $this->rowToPrincipal($row, $metadata);
 	}
 
-	/**
-	 * Returns the list of members for a group-principal
-	 *
-	 * @param string $principal
-	 * @return string[]
-	 */
-	public function getGroupMemberSet($principal) {
-		return [];
-	}
-
-	/**
-	 * Returns the list of groups a principal is a member of
-	 *
-	 * @param string $principal
-	 * @return array
-	 */
-	public function getGroupMembership($principal) {
-		return [];
-	}
-
-	/**
-	 * 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 string[] $members
-	 * @throws Exception
-	 */
-	public function setGroupMemberSet($principal, array $members) {
-		throw new Exception('Setting members of the group is not supported yet');
-	}
-
 	/**
 	 * @param string $path
 	 * @param PropPatch $propPatch
diff --git a/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php
index 0f6e6e7b4fd4ea2bc039dd2fc170f6a168456f72..128e6c21fad49b498256468c593917abdc657d38 100644
--- a/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php
+++ b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php
@@ -22,24 +22,34 @@
  */
 namespace OCA\DAV\CalDAV\ResourceBooking;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCP\IDBConnection;
 use OCP\IGroupManager;
 use OCP\ILogger;
 use OCP\IUserSession;
 
+/**
+ * Class ResourcePrincipalBackend
+ *
+ * @package OCA\DAV\CalDAV\ResourceBooking
+ */
 class ResourcePrincipalBackend extends AbstractPrincipalBackend {
 
 	/**
+	 * ResourcePrincipalBackend constructor.
+	 *
 	 * @param IDBConnection $dbConnection
 	 * @param IUserSession $userSession
 	 * @param IGroupManager $groupManager
 	 * @param ILogger $logger
+	 * @param ProxyMapper $proxyMapper
 	 */
 	public function __construct(IDBConnection $dbConnection,
 								IUserSession $userSession,
 								IGroupManager $groupManager,
-								ILogger $logger) {
+								ILogger $logger,
+								ProxyMapper $proxyMapper) {
 		parent::__construct($dbConnection, $userSession, $groupManager, $logger,
-			'principals/calendar-resources', 'resource', 'RESOURCE');
+			$proxyMapper, 'principals/calendar-resources', 'resource', 'RESOURCE');
 	}
 }
diff --git a/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php
index 68a344aa0cae72f14c1204545c4004fe2eef49a1..3e9e8f688526ce0f0f1e095eb803a57a438fc1b0 100644
--- a/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php
+++ b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php
@@ -22,24 +22,34 @@
  */
 namespace OCA\DAV\CalDAV\ResourceBooking;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCP\IDBConnection;
 use OCP\IGroupManager;
 use OCP\ILogger;
 use OCP\IUserSession;
 
+/**
+ * Class RoomPrincipalBackend
+ *
+ * @package OCA\DAV\CalDAV\ResourceBooking
+ */
 class RoomPrincipalBackend extends AbstractPrincipalBackend {
 
 	/**
+	 * RoomPrincipalBackend constructor.
+	 *
 	 * @param IDBConnection $dbConnection
 	 * @param IUserSession $userSession
 	 * @param IGroupManager $groupManager
 	 * @param ILogger $logger
+	 * @param ProxyMapper $proxyMapper
 	 */
 	public function __construct(IDBConnection $dbConnection,
 								IUserSession $userSession,
 								IGroupManager $groupManager,
-								ILogger $logger) {
+								ILogger $logger,
+								ProxyMapper $proxyMapper) {
 		parent::__construct($dbConnection, $userSession, $groupManager, $logger,
-			'principals/calendar-rooms', 'room', 'ROOM');
+			$proxyMapper, 'principals/calendar-rooms', 'room', 'ROOM');
 	}
 }
diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php
index 8e3a4abf8cf1f365ea6c9abaa91957aeb2871fd3..a40bf48cc8e2c4a0b4549f9c06e7ec95985ef2ac 100644
--- a/apps/dav/lib/Command/CreateCalendar.php
+++ b/apps/dav/lib/Command/CreateCalendar.php
@@ -24,6 +24,7 @@
 namespace OCA\DAV\Command;
 
 use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\Connector\Sabre\Principal;
 use OCP\IDBConnection;
 use OCP\IGroupManager;
@@ -78,8 +79,8 @@ class CreateCalendar extends Command {
 			$this->groupManager,
 			\OC::$server->getShareManager(),
 			\OC::$server->getUserSession(),
-			\OC::$server->getConfig(),
-			\OC::$server->getAppManager()
+			\OC::$server->getAppManager(),
+			\OC::$server->query(ProxyMapper::class)
 		);
 		$random = \OC::$server->getSecureRandom();
 		$logger = \OC::$server->getLogger();
diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php
index 902c70bdaff0b1add98c0c37f214537fdad3ee24..5c61b8371f2fe0afd28a73e6f84e9231a661ceb5 100644
--- a/apps/dav/lib/Connector/Sabre/Principal.php
+++ b/apps/dav/lib/Connector/Sabre/Principal.php
@@ -35,9 +35,11 @@
 namespace OCA\DAV\Connector\Sabre;
 
 use OCA\Circles\Exceptions\CircleDoesNotExistException;
+use OCA\DAV\CalDAV\Proxy\Proxy;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
+use OCA\DAV\Traits\PrincipalProxyTrait;
 use OCP\App\IAppManager;
 use OCP\AppFramework\QueryException;
-use OCP\IConfig;
 use OCP\IGroup;
 use OCP\IGroupManager;
 use OCP\IUser;
@@ -62,9 +64,6 @@ class Principal implements BackendInterface {
 	/** @var IUserSession */
 	private $userSession;
 
-	/** @var IConfig */
-	private $config;
-
 	/** @var IAppManager */
 	private $appManager;
 
@@ -77,29 +76,39 @@ class Principal implements BackendInterface {
 	/** @var bool */
 	private $hasCircles;
 
+	/** @var ProxyMapper */
+	private $proxyMapper;
+
 	/**
+	 * Principal constructor.
+	 *
 	 * @param IUserManager $userManager
 	 * @param IGroupManager $groupManager
 	 * @param IShareManager $shareManager
 	 * @param IUserSession $userSession
-	 * @param IConfig $config
+	 * @param IAppManager $appManager
+	 * @param ProxyMapper $proxyMapper
 	 * @param string $principalPrefix
 	 */
 	public function __construct(IUserManager $userManager,
 								IGroupManager $groupManager,
 								IShareManager $shareManager,
 								IUserSession $userSession,
-								IConfig $config,
 								IAppManager $appManager,
+								ProxyMapper $proxyMapper,
 								string $principalPrefix = 'principals/users/') {
 		$this->userManager = $userManager;
 		$this->groupManager = $groupManager;
 		$this->shareManager = $shareManager;
 		$this->userSession = $userSession;
-		$this->config = $config;
 		$this->appManager = $appManager;
 		$this->principalPrefix = trim($principalPrefix, '/');
 		$this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
+		$this->proxyMapper = $proxyMapper;
+	}
+
+	use PrincipalProxyTrait {
+		getGroupMembership as protected traitGetGroupMembership;
 	}
 
 	/**
@@ -138,6 +147,21 @@ class Principal implements BackendInterface {
 	public function getPrincipalByPath($path) {
 		list($prefix, $name) = \Sabre\Uri\split($path);
 
+		if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') {
+			list($prefix2, $name2) = \Sabre\Uri\split($prefix);
+
+			if ($prefix2 === $this->principalPrefix) {
+				$user = $this->userManager->get($name2);
+
+				if ($user !== null) {
+					return [
+						'uri' => 'principals/users/' . $user->getUID() . '/' . $name,
+					];
+				}
+				return null;
+			}
+		}
+
 		if ($prefix === $this->principalPrefix) {
 			$user = $this->userManager->get($name);
 
@@ -154,23 +178,6 @@ class Principal implements BackendInterface {
 		return null;
 	}
 
-	/**
-	 * Returns the list of members for a group-principal
-	 *
-	 * @param string $principal
-	 * @return string[]
-	 * @throws Exception
-	 */
-	public function getGroupMemberSet($principal) {
-		// TODO: for now the group principal has only one member, the user itself
-		$principal = $this->getPrincipalByPath($principal);
-		if (!$principal) {
-			throw new Exception('Principal not found');
-		}
-
-		return [$principal['uri']];
-	}
-
 	/**
 	 * Returns the list of groups a principal is a member of
 	 *
@@ -182,36 +189,30 @@ class Principal implements BackendInterface {
 	public function getGroupMembership($principal, $needGroups = false) {
 		list($prefix, $name) = \Sabre\Uri\split($principal);
 
-		if ($prefix === $this->principalPrefix) {
-			$user = $this->userManager->get($name);
-			if (!$user) {
-				throw new Exception('Principal not found');
-			}
+		if ($prefix !== $this->principalPrefix) {
+			return [];
+		}
+
+		$user = $this->userManager->get($name);
+		if (!$user) {
+			throw new Exception('Principal not found');
+		}
 
-			if ($this->hasGroups || $needGroups) {
-				$groups = $this->groupManager->getUserGroups($user);
-				$groups = array_map(function($group) {
-					/** @var IGroup $group */
-					return 'principals/groups/' . urlencode($group->getGID());
-				}, $groups);
+		$groups = [];
 
-				return $groups;
+		if ($this->hasGroups || $needGroups) {
+			$userGroups = $this->groupManager->getUserGroups($user);
+			foreach($userGroups as $userGroup) {
+				$groups[] = 'principals/groups/' . urlencode($userGroup->getGID());
 			}
 		}
-		return [];
-	}
 
-	/**
-	 * 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 string[] $members
-	 * @throws Exception
-	 */
-	public function setGroupMemberSet($principal, array $members) {
-		throw new Exception('Setting members of the group is not supported yet');
+		$groups = array_unique(array_merge(
+			$groups,
+			$this->traitGetGroupMembership($principal, $needGroups)
+		));
+
+		return $groups;
 	}
 
 	/**
@@ -482,5 +483,4 @@ class Principal implements BackendInterface {
 
 		return [];
 	}
-
 }
diff --git a/apps/dav/lib/Migration/Version1011Date20190806104428.php b/apps/dav/lib/Migration/Version1011Date20190806104428.php
new file mode 100644
index 0000000000000000000000000000000000000000..c144e62bcded3c8de3a25e793c80472f1239ec97
--- /dev/null
+++ b/apps/dav/lib/Migration/Version1011Date20190806104428.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DAV\Migration;
+
+use Closure;
+use Doctrine\DBAL\Types\Type;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Migration\IOutput;
+
+/**
+ * Auto-generated migration step: Please modify to your needs!
+ */
+class Version1011Date20190806104428 extends SimpleMigrationStep {
+	/**
+	 * @param IOutput $output
+	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+	 * @param array $options
+	 * @return null|ISchemaWrapper
+	 */
+	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+		/** @var ISchemaWrapper $schema */
+		$schema = $schemaClosure();
+
+		$table = $schema->createTable('dav_cal_proxy');
+		$table->addColumn('id', Type::BIGINT, [
+			'autoincrement' => true,
+			'notnull' => true,
+			'length' => 11,
+			'unsigned' => true,
+		]);
+		$table->addColumn('owner_id', Type::STRING, [
+			'notnull' => true,
+			'length' => 64,
+		]);
+		$table->addColumn('proxy_id', Type::STRING, [
+			'notnull' => true,
+			'length' => 64,
+		]);
+		$table->addColumn('permissions', Type::INTEGER, [
+			'notnull' => false,
+			'length' => 4,
+			'unsigned' => true,
+		]);
+
+		$table->setPrimaryKey(['id']);
+		$table->addUniqueIndex(['owner_id', 'proxy_id', 'permissions'], 'dav_cal_proxy_uidx');
+		$table->addIndex(['owner_id'], 'dav_cal_proxy_ioid');
+		$table->addIndex(['proxy_id'], 'dav_cal_proxy_ipid');
+
+		return $schema;
+	}
+}
diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php
index 38c8b2f6b47200b5ef4a504de15b84f6035cdc4b..ed8297783d793c0c88af1a9111f930bf8ee4149c 100644
--- a/apps/dav/lib/RootCollection.php
+++ b/apps/dav/lib/RootCollection.php
@@ -26,6 +26,7 @@ namespace OCA\DAV;
 
 use OCA\DAV\CalDAV\CalDavBackend;
 use OCA\DAV\CalDAV\CalendarRoot;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\CalDAV\PublicCalendarRoot;
 use OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend;
 use OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend;
@@ -53,17 +54,19 @@ class RootCollection extends SimpleCollection {
 		$shareManager = \OC::$server->getShareManager();
 		$db = \OC::$server->getDatabaseConnection();
 		$dispatcher = \OC::$server->getEventDispatcher();
+		$proxyMapper = \OC::$server->query(ProxyMapper::class);
+
 		$userPrincipalBackend = new Principal(
 			$userManager,
 			$groupManager,
 			$shareManager,
 			\OC::$server->getUserSession(),
-			$config,
-			\OC::$server->getAppManager()
+			\OC::$server->getAppManager(),
+			$proxyMapper
 		);
 		$groupPrincipalBackend = new GroupPrincipalBackend($groupManager, $userSession, $shareManager, $l10n);
-		$calendarResourcePrincipalBackend = new ResourcePrincipalBackend($db, $userSession, $groupManager, $logger);
-		$calendarRoomPrincipalBackend = new RoomPrincipalBackend($db, $userSession, $groupManager, $logger);
+		$calendarResourcePrincipalBackend = new ResourcePrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
+		$calendarRoomPrincipalBackend = new RoomPrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
 		// as soon as debug mode is enabled we allow listing of principals
 		$disableListing = !$config->getSystemValue('debug', false);
 
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 7eb68ce58748ecc8a62bbf5941338ccc8bf135ba..cd67b3995a47b48d0ad521b4c43168272847d705 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -137,7 +137,8 @@ class Server {
 		// acl
 		$acl = new DavAclPlugin();
 		$acl->principalCollectionSet = [
-			'principals/users', 'principals/groups',
+			'principals/users',
+			'principals/groups',
 			'principals/calendar-resources',
 			'principals/calendar-rooms',
 		];
diff --git a/apps/dav/lib/Traits/PrincipalProxyTrait.php b/apps/dav/lib/Traits/PrincipalProxyTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e16d3a2f839de62de9d62a81d17c043ea2f38bd
--- /dev/null
+++ b/apps/dav/lib/Traits/PrincipalProxyTrait.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * @copyright 2019, Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\DAV\Traits;
+
+use OCA\DAV\CalDAV\Proxy\Proxy;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
+use Sabre\DAV\Exception;
+
+/**
+ * Trait PrincipalTrait
+ *
+ * @package OCA\DAV\Traits
+ */
+trait PrincipalProxyTrait {
+
+	/**
+	 * Returns the list of members for a group-principal
+	 *
+	 * @param string $principal
+	 * @return string[]
+	 * @throws Exception
+	 */
+	public function getGroupMemberSet($principal) {
+		$members = [];
+
+		if ($this->isProxyPrincipal($principal)) {
+			$realPrincipal = $this->getPrincipalUriFromProxyPrincipal($principal);
+			$principalArray = $this->getPrincipalByPath($realPrincipal);
+			if (!$principalArray) {
+				throw new Exception('Principal not found');
+			}
+
+			$proxies = $this->proxyMapper->getProxiesOf($principalArray['uri']);
+			foreach ($proxies as $proxy) {
+				if ($this->isReadProxyPrincipal($principal) && $proxy->getPermissions() === ProxyMapper::PERMISSION_READ) {
+					$members[] = $proxy->getProxyId();
+				}
+
+				if ($this->isWriteProxyPrincipal($principal) && $proxy->getPermissions() === (ProxyMapper::PERMISSION_READ | ProxyMapper::PERMISSION_WRITE)) {
+					$members[] = $proxy->getProxyId();
+				}
+			}
+		}
+
+		return $members;
+	}
+
+	/**
+	 * Returns the list of groups a principal is a member of
+	 *
+	 * @param string $principal
+	 * @param bool $needGroups
+	 * @return array
+	 * @throws Exception
+	 */
+	public function getGroupMembership($principal, $needGroups = false) {
+		list($prefix, $name) = \Sabre\Uri\split($principal);
+
+		if ($prefix !== $this->principalPrefix) {
+			return [];
+		}
+
+		$principalArray = $this->getPrincipalByPath($principal);
+		if (!$principalArray) {
+			throw new Exception('Principal not found');
+		}
+
+		$groups = [];
+		$proxies = $this->proxyMapper->getProxiesFor($principal);
+		foreach ($proxies as $proxy) {
+			if ($proxy->getPermissions() === ProxyMapper::PERMISSION_READ) {
+				$groups[] = $proxy->getOwnerId() . '/calendar-proxy-read';
+			}
+
+			if ($proxy->getPermissions() === (ProxyMapper::PERMISSION_READ | ProxyMapper::PERMISSION_WRITE)) {
+				$groups[] = $proxy->getOwnerId() . '/calendar-proxy-write';
+			}
+		}
+
+		return $groups;
+	}
+
+	/**
+	 * 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 string[] $members
+	 * @throws Exception
+	 */
+	public function setGroupMemberSet($principal, array $members) {
+		list($principalUri, $target) = \Sabre\Uri\split($principal);
+
+		if ($target !== 'calendar-proxy-write' && $target !== 'calendar-proxy-read') {
+			throw new Exception('Setting members of the group is not supported yet');
+		}
+
+		$masterPrincipalArray = $this->getPrincipalByPath($principalUri);
+		if (!$masterPrincipalArray) {
+			throw new Exception('Principal not found');
+		}
+
+		$permission = ProxyMapper::PERMISSION_READ;
+		if ($target === 'calendar-proxy-write') {
+			$permission |= ProxyMapper::PERMISSION_WRITE;
+		}
+
+		list($prefix, $owner) = \Sabre\Uri\split($principalUri);
+		$proxies = $this->proxyMapper->getProxiesOf($principalUri);
+
+		foreach ($members as $member) {
+			list($prefix, $name) = \Sabre\Uri\split($member);
+
+			if ($prefix !== $this->principalPrefix) {
+				throw new Exception('Invalid member group prefix: ' . $prefix);
+			}
+
+			$principalArray = $this->getPrincipalByPath($member);
+			if (!$principalArray) {
+				throw new Exception('Principal not found');
+			}
+
+			$found = false;
+			foreach ($proxies as $proxy) {
+				if ($proxy->getProxyId() === $member) {
+					$found = true;
+					$proxy->setPermissions($proxy->getPermissions() | $permission);
+					$this->proxyMapper->update($proxy);
+
+					$proxies = array_filter($proxies, function(Proxy $p) use ($proxy) {
+						return $p->getId() !== $proxy->getId();
+					});
+					break;
+				}
+			}
+
+			if ($found === false) {
+				$proxy = new Proxy();
+				$proxy->setOwnerId($principalUri);
+				$proxy->setProxyId($member);
+				$proxy->setPermissions($permission);
+				$this->proxyMapper->insert($proxy);
+			}
+		}
+
+		// Delete all remaining proxies
+		foreach ($proxies as $proxy) {
+			// Write and Read Proxies have individual requests,
+			// so only delete proxies of this permission
+			if ($proxy->getPermissions() === $permission) {
+				$this->proxyMapper->delete($proxy);
+			}
+		}
+	}
+
+	/**
+	 * @param string $principalUri
+	 * @return bool
+	 */
+	private function isProxyPrincipal(string $principalUri):bool {
+		list($realPrincipalUri, $proxy) = \Sabre\Uri\split($principalUri);
+		list($prefix, $userId) = \Sabre\Uri\split($realPrincipalUri);
+
+		if (!isset($prefix) || !isset($userId)) {
+			return false;
+		}
+		if ($prefix !== $this->principalPrefix) {
+			return false;
+		}
+
+		return $proxy === 'calendar-proxy-read'
+			|| $proxy === 'calendar-proxy-write';
+
+	}
+
+	/**
+	 * @param string $principalUri
+	 * @return bool
+	 */
+	private function isReadProxyPrincipal(string $principalUri):bool {
+		list(, $proxy) = \Sabre\Uri\split($principalUri);
+		return $proxy === 'calendar-proxy-read';
+	}
+
+	/**
+	 * @param string $principalUri
+	 * @return bool
+	 */
+	private function isWriteProxyPrincipal(string $principalUri):bool {
+		list(, $proxy) = \Sabre\Uri\split($principalUri);
+		return $proxy === 'calendar-proxy-write';
+	}
+
+	/**
+	 * @param string $principalUri
+	 * @return string
+	 */
+	private function getPrincipalUriFromProxyPrincipal(string $principalUri):string {
+		list($realPrincipalUri, ) = \Sabre\Uri\split($principalUri);
+		return $realPrincipalUri;
+	}
+}
diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
index 795a04e2cbfc70403134c35c0e0d3912b5a0a8ec..9f9a7c01337c89a30888fbc7f716920e3353714f 100644
--- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
+++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
@@ -26,6 +26,7 @@
 namespace OCA\DAV\Tests\unit\CalDAV;
 
 use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\Connector\Sabre\Principal;
 use OCP\App\IAppManager;
 use OCP\IConfig;
@@ -83,8 +84,8 @@ abstract class AbstractCalDavBackend extends TestCase {
 				$this->groupManager,
 				$this->createMock(ShareManager::class),
 				$this->createMock(IUserSession::class),
-				$this->createMock(IConfig::class),
 				$this->createMock(IAppManager::class),
+				$this->createMock(ProxyMapper::class),
 			])
 			->setMethods(['getPrincipalByPath', 'getGroupMembership'])
 			->getMock();
diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php
index f467d46bf608be35382a8e2a2c3b12286319ac66..7ce43f40916f4d4742742772e7a99d6c8960adea 100644
--- a/apps/dav/tests/unit/CalDAV/CalendarTest.php
+++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php
@@ -213,21 +213,44 @@ class CalendarTest extends TestCase {
 			'principal' => $hasOwnerSet ? 'user1' : 'user2',
 			'protected' => true
 		], [
-			'privilege' => '{DAV:}write',
-			'principal' => $hasOwnerSet ? 'user1' : 'user2',
-			'protected' => true
+			'privilege' => '{DAV:}read',
+			'principal' => ($hasOwnerSet ? 'user1' : 'user2') . '/calendar-proxy-write',
+			'protected' => true,
+		], [
+			'privilege' => '{DAV:}read',
+			'principal' => ($hasOwnerSet ? 'user1' : 'user2') . '/calendar-proxy-read',
+			'protected' => true,
 		]];
 		if ($uri === BirthdayService::BIRTHDAY_CALENDAR_URI) {
-			$expectedAcl = [[
-				'privilege' => '{DAV:}read',
+			$expectedAcl[] = [
+				'privilege' => '{DAV:}write-properties',
 				'principal' => $hasOwnerSet ? 'user1' : 'user2',
 				'protected' => true
-			], [
+			];
+			$expectedAcl[] = [
 				'privilege' => '{DAV:}write-properties',
+				'principal' => ($hasOwnerSet ? 'user1' : 'user2') . '/calendar-proxy-write',
+				'protected' => true
+			];
+		} else {
+			$expectedAcl[] = [
+				'privilege' => '{DAV:}write',
 				'principal' => $hasOwnerSet ? 'user1' : 'user2',
 				'protected' => true
-			]];
+			];
+			$expectedAcl[] = [
+				'privilege' => '{DAV:}write',
+				'principal' => ($hasOwnerSet ? 'user1' : 'user2') . '/calendar-proxy-write',
+				'protected' => true
+			];
 		}
+
+		$expectedAcl[] = [
+			'privilege' => '{DAV:}write-properties',
+			'principal' => ($hasOwnerSet ? 'user1' : 'user2') . '/calendar-proxy-read',
+			'protected' => true
+		];
+
 		if ($hasOwnerSet) {
 			$expectedAcl[] = [
 				'privilege' => '{DAV:}read',
diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php
index f4019d86e2b6bb5d2794946d778aa125be21ff9d..c6e16e2c484a7a35fcbf829c9dc8338182a2effc 100644
--- a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php
+++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php
@@ -21,6 +21,8 @@
  */
 namespace OCA\DAV\Tests\unit\CalDAV\ResourceBooking;
 
+use OCA\DAV\CalDAV\Proxy\Proxy;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCP\DB\QueryBuilder\IQueryBuilder;
 use OCP\IGroupManager;
 use OCP\ILogger;
@@ -43,6 +45,9 @@ abstract class AbstractPrincipalBackendTest extends TestCase {
 	/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
 	protected $logger;
 
+	/** @var ProxyMapper|\PHPUnit_Framework_MockObject_MockObject */
+	protected $proxyMapper;
+
 	/** @var string */
 	protected $mainDbTable;
 
@@ -64,6 +69,7 @@ abstract class AbstractPrincipalBackendTest extends TestCase {
 		$this->userSession = $this->createMock(IUserSession::class);
 		$this->groupManager = $this->createMock(IGroupManager::class);
 		$this->logger = $this->createMock(ILogger::class);
+		$this->proxyMapper = $this->createMock(ProxyMapper::class);
 	}
 
 	protected function tearDown() {
@@ -152,21 +158,113 @@ abstract class AbstractPrincipalBackendTest extends TestCase {
 	}
 
 	public function testGetGroupMemberSet() {
-		$actual = $this->principalBackend->getGroupMemberSet($this->principalPrefix . '/foo-bar');
+		$actual = $this->principalBackend->getGroupMemberSet($this->principalPrefix . '/backend1-res1');
 		$this->assertEquals([], $actual);
 	}
 
+	public function testGetGroupMemberSetProxyRead() {
+		$proxy1 = new Proxy();
+		$proxy1->setProxyId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setProxyId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$proxy3 = new Proxy();
+		$proxy3->setProxyId('proxyId3');
+		$proxy3->setPermissions(3);
+
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesOf')
+			->with($this->principalPrefix . '/backend1-res1')
+			->willReturn([$proxy1, $proxy2, $proxy3]);
+
+		$actual = $this->principalBackend->getGroupMemberSet($this->principalPrefix . '/backend1-res1/calendar-proxy-read');
+		$this->assertEquals(['proxyId1'], $actual);
+	}
+
+	public function testGetGroupMemberSetProxyWrite() {
+		$proxy1 = new Proxy();
+		$proxy1->setProxyId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setProxyId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$proxy3 = new Proxy();
+		$proxy3->setProxyId('proxyId3');
+		$proxy3->setPermissions(3);
+
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesOf')
+			->with($this->principalPrefix . '/backend1-res1')
+			->willReturn([$proxy1, $proxy2, $proxy3]);
+
+		$actual = $this->principalBackend->getGroupMemberSet($this->principalPrefix . '/backend1-res1/calendar-proxy-write');
+		$this->assertEquals(['proxyId2', 'proxyId3'], $actual);
+	}
+
 	public function testGetGroupMembership() {
-		$actual = $this->principalBackend->getGroupMembership($this->principalPrefix . '/foo-bar');
-		$this->assertEquals([], $actual);
+		$proxy1 = new Proxy();
+		$proxy1->setOwnerId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setOwnerId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesFor')
+			->with($this->principalPrefix . '/backend1-res1')
+			->willReturn([$proxy1, $proxy2]);
+
+		$actual = $this->principalBackend->getGroupMembership($this->principalPrefix . '/backend1-res1');
+
+		$this->assertEquals(['proxyId1/calendar-proxy-read', 'proxyId2/calendar-proxy-write'], $actual);
 	}
 
-	/**
-	 * @expectedException        \Sabre\DAV\Exception
-	 * @expectedExceptionMessage Setting members of the group is not supported yet
-	 */
 	public function testSetGroupMemberSet() {
-		$this->principalBackend->setGroupMemberSet($this->principalPrefix . '/foo-bar', ['foo', 'bar']);
+		$this->proxyMapper->expects($this->at(0))
+			->method('getProxiesOf')
+			->with($this->principalPrefix . '/backend1-res1')
+			->willReturn([]);
+
+		$this->proxyMapper->expects($this->at(1))
+			->method('insert')
+			->with($this->callback(function($proxy) {
+				/** @var Proxy $proxy */
+				if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') {
+					return false;
+				}
+				if ($proxy->getProxyId() !== $this->principalPrefix . '/backend1-res2') {
+					return false;
+				}
+				if ($proxy->getPermissions() !== 3) {
+					return false;
+				}
+
+				return true;
+			}));
+		$this->proxyMapper->expects($this->at(2))
+			->method('insert')
+			->with($this->callback(function($proxy) {
+				/** @var Proxy $proxy */
+				if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') {
+					return false;
+				}
+				if ($proxy->getProxyId() !== $this->principalPrefix . '/backend2-res3') {
+					return false;
+				}
+				if ($proxy->getPermissions() !== 3) {
+					return false;
+				}
+
+				return true;
+			}));
+
+		$this->principalBackend->setGroupMemberSet($this->principalPrefix . '/backend1-res1/calendar-proxy-write', [$this->principalPrefix . '/backend1-res2', $this->principalPrefix . '/backend2-res3']);
 	}
 
 	public function testUpdatePrincipal() {
diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php
index 3787e4df951ebc21f8d7c1489d83c0f556bc722b..461246dd51da83dda67476bbd5e5fdd7c97e01f1 100644
--- a/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php
+++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php
@@ -28,7 +28,7 @@ Class ResourcePrincipalBackendTest extends AbstractPrincipalBackendTest {
 		parent::setUp();
 
 		$this->principalBackend = new ResourcePrincipalBackend(self::$realDatabase,
-			$this->userSession, $this->groupManager, $this->logger);
+			$this->userSession, $this->groupManager, $this->logger, $this->proxyMapper);
 
 		$this->mainDbTable = 'calendar_resources';
 		$this->metadataDbTable = 'calendar_resources_md';
diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php
index 9b259c30adf0d943d51b117b09641baa64d77e82..6b691400cd118b2608eba6a210e1d8f71589617f 100644
--- a/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php
+++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php
@@ -28,7 +28,7 @@ Class RoomPrincipalBackendTest extends AbstractPrincipalBackendTest {
 		parent::setUp();
 
 		$this->principalBackend = new RoomPrincipalBackend(self::$realDatabase,
-			$this->userSession, $this->groupManager, $this->logger);
+			$this->userSession, $this->groupManager, $this->logger, $this->proxyMapper);
 
 		$this->mainDbTable = 'calendar_rooms';
 		$this->metadataDbTable = 'calendar_rooms_md';
diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
index 531f50e96c2368104a9b4d83eb5a87d766b77c78..aa18ef63a5cda7e36087f89c5a3bdc05f2f7e46c 100644
--- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
+++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
@@ -32,6 +32,7 @@
 namespace OCA\DAV\Tests\unit\CardDAV;
 
 use InvalidArgumentException;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\CardDAV\AddressBook;
 use OCA\DAV\CardDAV\CardDavBackend;
 use OCA\DAV\Connector\Sabre\Principal;
@@ -131,8 +132,8 @@ class CardDavBackendTest extends TestCase {
 				$this->groupManager,
 				$this->createMock(ShareManager::class),
 				$this->createMock(IUserSession::class),
-				$this->createMock(IConfig::class),
 				$this->createMock(IAppManager::class),
+				$this->createMock(ProxyMapper::class),
 				])
 			->setMethods(['getPrincipalByPath', 'getGroupMembership'])
 			->getMock();
diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php
index 225189e7d01590ddf4039d432a8e6c8f39fdc587..2a5c122ce9ea7f7a91953d38af1c6894a7e2424f 100644
--- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php
@@ -29,6 +29,8 @@
 namespace OCA\DAV\Tests\unit\Connector\Sabre;
 
 use OC\User\User;
+use OCA\DAV\CalDAV\Proxy\Proxy;
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCP\App\IAppManager;
 use OCP\IConfig;
 use OCP\IGroup;
@@ -57,27 +59,27 @@ class PrincipalTest extends TestCase {
 	/** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */
 	private $userSession;
 
-	/** @var IConfig | \PHPUnit_Framework_MockObject_MockObject  */
-	private $config;
-
 	/** @var IAppManager | \PHPUnit_Framework_MockObject_MockObject  */
 	private $appManager;
 
+	/** @var ProxyMapper | \PHPUnit_Framework_MockObject_MockObject */
+	private $proxyMapper;
+
 	public function setUp() {
 		$this->userManager = $this->createMock(IUserManager::class);
 		$this->groupManager = $this->createMock(IGroupManager::class);
 		$this->shareManager = $this->createMock(IManager::class);
 		$this->userSession = $this->createMock(IUserSession::class);
-		$this->config = $this->createMock(IConfig::class);
 		$this->appManager = $this->createMock(IAppManager::class);
+		$this->proxyMapper = $this->createMock(ProxyMapper::class);
 
 		$this->connector = new \OCA\DAV\Connector\Sabre\Principal(
 			$this->userManager,
 			$this->groupManager,
 			$this->shareManager,
 			$this->userSession,
-			$this->config,
-			$this->appManager
+			$this->appManager,
+			$this->proxyMapper
 		);
 		parent::setUp();
 	}
@@ -203,6 +205,25 @@ class PrincipalTest extends TestCase {
 	}
 
 	public function testGetGroupMemberSet() {
+		$response = $this->connector->getGroupMemberSet('principals/users/foo');
+		$this->assertSame([], $response);
+	}
+
+	/**
+	 * @expectedException \Sabre\DAV\Exception
+	 * @expectedExceptionMessage Principal not found
+	 */
+	public function testGetGroupMemberSetEmpty() {
+		$this->userManager
+			->expects($this->once())
+			->method('get')
+			->with('foo')
+			->will($this->returnValue(null));
+
+		$this->connector->getGroupMemberSet('principals/users/foo/calendar-proxy-read');
+	}
+
+	public function testGetGroupMemberSetProxyRead() {
 		$fooUser = $this->createMock(User::class);
 		$fooUser
 			->expects($this->exactly(1))
@@ -214,22 +235,56 @@ class PrincipalTest extends TestCase {
 			->with('foo')
 			->will($this->returnValue($fooUser));
 
-		$response = $this->connector->getGroupMemberSet('principals/users/foo');
-		$this->assertSame(['principals/users/foo'], $response);
+		$proxy1 = new Proxy();
+		$proxy1->setProxyId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setProxyId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$proxy3 = new Proxy();
+		$proxy3->setProxyId('proxyId3');
+		$proxy3->setPermissions(3);
+
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesOf')
+			->with('principals/users/foo')
+			->willReturn([$proxy1, $proxy2, $proxy3]);
+
+		$this->assertEquals(['proxyId1'], $this->connector->getGroupMemberSet('principals/users/foo/calendar-proxy-read'));
 	}
 
-	/**
-	 * @expectedException \Sabre\DAV\Exception
-	 * @expectedExceptionMessage Principal not found
-	 */
-	public function testGetGroupMemberSetEmpty() {
+	public function testGetGroupMemberSetProxyWrite() {
+		$fooUser = $this->createMock(User::class);
+		$fooUser
+			->expects($this->exactly(1))
+			->method('getUID')
+			->will($this->returnValue('foo'));
 		$this->userManager
 			->expects($this->once())
 			->method('get')
 			->with('foo')
-			->will($this->returnValue(null));
+			->will($this->returnValue($fooUser));
+
+		$proxy1 = new Proxy();
+		$proxy1->setProxyId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setProxyId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$proxy3 = new Proxy();
+		$proxy3->setProxyId('proxyId3');
+		$proxy3->setPermissions(3);
 
-		$this->connector->getGroupMemberSet('principals/users/foo');
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesOf')
+			->with('principals/users/foo')
+			->willReturn([$proxy1, $proxy2, $proxy3]);
+
+		$this->assertEquals(['proxyId2', 'proxyId3'], $this->connector->getGroupMemberSet('principals/users/foo/calendar-proxy-write'));
 	}
 
 	public function testGetGroupMembership() {
@@ -243,7 +298,7 @@ class PrincipalTest extends TestCase {
 			->method('getGID')
 			->willReturn('foo/bar');
 		$this->userManager
-			->expects($this->once())
+			->expects($this->exactly(2))
 			->method('get')
 			->with('foo')
 			->willReturn($fooUser);
@@ -256,9 +311,24 @@ class PrincipalTest extends TestCase {
 				$group2,
 			]);
 
+		$proxy1 = new Proxy();
+		$proxy1->setOwnerId('proxyId1');
+		$proxy1->setPermissions(1);
+
+		$proxy2 = new Proxy();
+		$proxy2->setOwnerId('proxyId2');
+		$proxy2->setPermissions(3);
+
+		$this->proxyMapper->expects($this->once())
+			->method('getProxiesFor')
+			->with('principals/users/foo')
+			->willReturn([$proxy1, $proxy2]);
+
 		$expectedResponse = [
 			'principals/groups/group1',
 			'principals/groups/foo%2Fbar',
+			'proxyId1/calendar-proxy-read',
+    		'proxyId2/calendar-proxy-write',
 		];
 		$response = $this->connector->getGroupMembership('principals/users/foo');
 		$this->assertSame($expectedResponse, $response);
@@ -286,6 +356,58 @@ class PrincipalTest extends TestCase {
 		$this->connector->setGroupMemberSet('principals/users/foo', ['foo']);
 	}
 
+	public function testSetGroupMembershipProxy() {
+		$fooUser = $this->createMock(User::class);
+		$fooUser
+			->expects($this->exactly(1))
+			->method('getUID')
+			->will($this->returnValue('foo'));
+		$barUser = $this->createMock(User::class);
+		$barUser
+			->expects($this->exactly(1))
+			->method('getUID')
+			->will($this->returnValue('bar'));
+		$this->userManager
+			->expects($this->at(0))
+			->method('get')
+			->with('foo')
+			->will($this->returnValue($fooUser));
+		$this->userManager
+			->expects($this->at(1))
+			->method('get')
+			->with('bar')
+			->will($this->returnValue($barUser));
+
+		$this->proxyMapper->expects($this->at(0))
+			->method('getProxiesOf')
+			->with('principals/users/foo')
+			->willReturn([]);
+
+		$this->proxyMapper->expects($this->at(1))
+			->method('insert')
+			->with($this->callback(function($proxy) {
+				/** @var Proxy $proxy */
+				if ($proxy->getOwnerId() !== 'principals/users/foo') {
+					return false;
+				}
+				if ($proxy->getProxyId() !== 'principals/users/bar') {
+					return false;
+				}
+				if ($proxy->getPermissions() !== 3) {
+					return false;
+				}
+
+				return true;
+			}));
+
+		$this->connector->setGroupMemberSet('principals/users/foo/calendar-proxy-write', ['principals/users/bar']);
+	}
+
+
+
+
+
+
 	public function testUpdatePrincipal() {
 		$this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array())));
 	}
diff --git a/apps/files_trashbin/lib/AppInfo/Application.php b/apps/files_trashbin/lib/AppInfo/Application.php
index 761298673ac0d70a90859ffed7ac3dd12397ba46..4baa82a6b4b5554dd94c1fc824a126dd78aa8676 100644
--- a/apps/files_trashbin/lib/AppInfo/Application.php
+++ b/apps/files_trashbin/lib/AppInfo/Application.php
@@ -23,6 +23,7 @@
 
 namespace OCA\Files_Trashbin\AppInfo;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\Connector\Sabre\Principal;
 use OCA\Files_Trashbin\Trash\ITrashManager;
 use OCA\Files_Trashbin\Trash\TrashManager;
@@ -61,8 +62,8 @@ class Application extends App {
 				\OC::$server->getGroupManager(),
 				\OC::$server->getShareManager(),
 				\OC::$server->getUserSession(),
-				\OC::$server->getConfig(),
-				\OC::$server->getAppManager()
+				\OC::$server->getAppManager(),
+				\OC::$server->query(ProxyMapper::class)
 			);
 		});
 
diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php
index 6a8e50dba93052a6899b8109f12c07b38c43bd6c..919a9b42d7979bf0346065276db028d575972766 100644
--- a/apps/files_versions/lib/AppInfo/Application.php
+++ b/apps/files_versions/lib/AppInfo/Application.php
@@ -23,6 +23,7 @@
 
 namespace OCA\Files_Versions\AppInfo;
 
+use OCA\DAV\CalDAV\Proxy\ProxyMapper;
 use OCA\DAV\Connector\Sabre\Principal;
 use OCA\Files_Versions\Versions\IVersionManager;
 use OCA\Files_Versions\Versions\VersionManager;
@@ -51,8 +52,8 @@ class Application extends App {
 				$server->getGroupManager(),
 				$server->getShareManager(),
 				$server->getUserSession(),
-				$server->getConfig(),
-				$server->getAppManager()
+				$server->getAppManager(),
+				$server->query(ProxyMapper::class)
 			);
 		});