diff --git a/apps/dav/lib/connector/legacydavacl.php b/apps/dav/lib/connector/legacydavacl.php
index 149bd85e4bebd06fd7d75dd864e3c6830f9646a5..5a6546064658c1865c3067f519fb55aca633f37e 100644
--- a/apps/dav/lib/connector/legacydavacl.php
+++ b/apps/dav/lib/connector/legacydavacl.php
@@ -21,10 +21,10 @@
 
 namespace OCA\DAV\Connector;
 
-
+use OCA\DAV\Connector\Sabre\DavAclPlugin;
 use Sabre\HTTP\URLUtil;
 
-class LegacyDAVACL extends \Sabre\DAVACL\Plugin {
+class LegacyDAVACL extends DavAclPlugin {
 
 	/**
 	 * Converts the v1 principal `principal/<username>` to the new v2
diff --git a/apps/dav/lib/connector/sabre/davaclplugin.php b/apps/dav/lib/connector/sabre/davaclplugin.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a9dd66161d9bcf7a2988262122b8f9eff18ad28
--- /dev/null
+++ b/apps/dav/lib/connector/sabre/davaclplugin.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre;
+
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\IFile;
+use Sabre\DAV\INode;
+use \Sabre\DAV\PropFind;
+use \Sabre\DAV\PropPatch;
+use Sabre\DAVACL\Exception\NeedPrivileges;
+use \Sabre\HTTP\RequestInterface;
+use \Sabre\HTTP\ResponseInterface;
+use Sabre\HTTP\URLUtil;
+
+/**
+ * Class DavAclPlugin is a wrapper around \Sabre\DAVACL\Plugin that returns 404
+ * responses in case the resource to a response has been forbidden instead of
+ * a 403. This is used to prevent enumeration of valid resources.
+ *
+ * @see https://github.com/owncloud/core/issues/22578
+ * @package OCA\DAV\Connector\Sabre
+ */
+class DavAclPlugin extends \Sabre\DAVACL\Plugin {
+	public function __construct() {
+		$this->hideNodesFromListings = true;
+	}
+
+	function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {
+		$access = parent::checkPrivileges($uri, $privileges, $recursion, false);
+		if($access === false) {
+			/** @var INode $node */
+			$node = $this->server->tree->getNodeForPath($uri);
+
+			switch(get_class($node)) {
+				case 'OCA\DAV\CardDAV\AddressBook':
+					$type = 'Addressbook';
+					break;
+				default:
+					$type = 'Node';
+					break;
+			}
+			throw new NotFound(
+				sprintf(
+					"%s with name '%s' could not be found",
+					$type,
+					$node->getName()
+				)
+			);
+		}
+
+		return $access;
+	}
+}
diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php
index 55ae6c62d317d1253b675744737ed3d72f80c96a..2aa720c9dc4082c0f96343e6c3854dbca59a106c 100644
--- a/apps/dav/lib/server.php
+++ b/apps/dav/lib/server.php
@@ -26,6 +26,7 @@ use OCA\DAV\CalDAV\Schedule\IMipPlugin;
 use OCA\DAV\Connector\FedAuth;
 use OCA\DAV\Connector\Sabre\Auth;
 use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
+use OCA\DAV\Connector\Sabre\DavAclPlugin;
 use OCA\DAV\Connector\Sabre\FilesPlugin;
 use OCA\DAV\Files\CustomPropertiesBackend;
 use OCP\IRequest;
@@ -72,7 +73,7 @@ class Server {
 		$this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
 
 		// acl
-		$acl = new \Sabre\DAVACL\Plugin();
+		$acl = new DavAclPlugin();
 		$acl->defaultUsernamePath = 'principals/users';
 		$this->server->addPlugin($acl);
 
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index a1f9d610c68433328e034cbc1dbdc21c32c1f749..d0c4586d285aa90195a04f1a1593376822b6ea1d 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -16,6 +16,10 @@ default:
             baseUrl: http://localhost:8080
         - TagsContext:
             baseUrl: http://localhost:8080
+        - CardDavContext:
+            baseUrl: http://localhost:8080
+        - CalDavContext:
+            baseUrl: http://localhost:8080
     federation:
       paths:
         - %paths.base%/../federation_features
diff --git a/build/integration/features/bootstrap/CalDavContext.php b/build/integration/features/bootstrap/CalDavContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..88711f3aa733d5338b2f5d853baae02a64281445
--- /dev/null
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Message\ResponseInterface;
+
+class CalDavContext implements \Behat\Behat\Context\Context {
+	/** @var string  */
+	private $baseUrl;
+	/** @var Client */
+	private $client;
+	/** @var ResponseInterface */
+	private $response;
+	/** @var string */
+	private $responseXml = '';
+
+	/**
+	 * @param string $baseUrl
+	 */
+	public function __construct($baseUrl) {
+		$this->baseUrl = $baseUrl;
+
+		// in case of ci deployment we take the server url from the environment
+		$testServerUrl = getenv('TEST_SERVER_URL');
+		if ($testServerUrl !== false) {
+			$this->baseUrl = substr($testServerUrl, 0, -5);
+		}
+	}
+
+	/** @BeforeScenario */
+	public function tearUpScenario() {
+		$this->client = new Client();
+		$this->responseXml = '';
+	}
+
+	/** @AfterScenario */
+	public function afterScenario() {
+		$davUrl = $this->baseUrl. '/remote.php/dav/calendars/admin/MyCalendar';
+		try {
+			$this->client->delete(
+				$davUrl,
+				[
+					'auth' => [
+						'admin',
+						'admin',
+					],
+				]
+			);
+		} catch (\GuzzleHttp\Exception\ClientException $e) {}
+	}
+
+	/**
+	 * @When :user requests calendar :calendar
+	 */
+	public function requestsCalendar($user, $calendar)  {
+		$davUrl = $this->baseUrl . '/remote.php/dav/calendars/'.$calendar;
+
+		$password = ($user === 'admin') ? 'admin' : '123456';
+		try {
+			$this->response = $this->client->get(
+				$davUrl,
+				[
+					'auth' => [
+						$user,
+						$password,
+					]
+				]
+			);
+		} catch (\GuzzleHttp\Exception\ClientException $e) {
+			$this->response = $e->getResponse();
+		}
+	}
+
+	/**
+	 * @Then The CalDAV HTTP status code should be :code
+	 */
+	public function theCaldavHttpStatusCodeShouldBe($code) {
+		if((int)$code !== $this->response->getStatusCode()) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					(int)$code,
+					$this->response->getStatusCode()
+				)
+			);
+		}
+
+		$body = $this->response->getBody()->getContents();
+		if($body && substr($body, 0, 1) === '<') {
+			$reader = new Sabre\Xml\Reader();
+			$reader->xml($body);
+			$this->responseXml = $reader->parse();
+		}
+	}
+
+	/**
+	 * @Then The exception is :message
+	 */
+	public function theExceptionIs($message) {
+		$result = $this->responseXml['value'][0]['value'];
+
+		if($message !== $result) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					$message,
+					$result
+				)
+			);
+		}
+	}
+
+	/**
+	 * @Then The error message is :message
+	 */
+	public function theErrorMessageIs($message) {
+		$result = $this->responseXml['value'][1]['value'];
+
+		if($message !== $result) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					$message,
+					$result
+				)
+			);
+		}
+	}
+
+	/**
+	 * @Given :user creates a calendar named :name
+	 */
+	public function createsACalendarNamed($user, $name) {
+		$davUrl = $this->baseUrl . '/remote.php/dav/calendars/'.$user.'/'.$name;
+		$password = ($user === 'admin') ? 'admin' : '123456';
+
+		$request = $this->client->createRequest(
+			'MKCALENDAR',
+			$davUrl,
+			[
+				'body' => '<c:mkcalendar xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:" xmlns:a="http://apple.com/ns/ical/" xmlns:o="http://owncloud.org/ns"><d:set><d:prop><d:displayname>test</d:displayname><o:calendar-enabled>1</o:calendar-enabled><a:calendar-color>#21213D</a:calendar-color><c:supported-calendar-component-set><c:comp name="VEVENT"/></c:supported-calendar-component-set></d:prop></d:set></c:mkcalendar>',
+				'auth' => [
+					$user,
+					$password,
+				],
+			]
+		);
+
+		$this->response = $this->client->send($request);
+	}
+
+}
diff --git a/build/integration/features/bootstrap/CardDavContext.php b/build/integration/features/bootstrap/CardDavContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..251d76d083367b799d5edb64231da79e8375d767
--- /dev/null
+++ b/build/integration/features/bootstrap/CardDavContext.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Message\ResponseInterface;
+
+class CardDavContext implements \Behat\Behat\Context\Context {
+	/** @var string  */
+	private $baseUrl;
+	/** @var Client */
+	private $client;
+	/** @var ResponseInterface */
+	private $response;
+	/** @var string */
+	private $responseXml = '';
+
+	/**
+	 * @param string $baseUrl
+	 */
+	public function __construct($baseUrl) {
+		$this->baseUrl = $baseUrl;
+
+		// in case of ci deployment we take the server url from the environment
+		$testServerUrl = getenv('TEST_SERVER_URL');
+		if ($testServerUrl !== false) {
+			$this->baseUrl = substr($testServerUrl, 0, -5);
+		}
+	}
+
+	/** @BeforeScenario */
+	public function tearUpScenario() {
+		$this->client = new Client();
+		$this->responseXml = '';
+	}
+
+
+	/** @AfterScenario */
+	public function afterScenario() {
+		$davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/admin/MyAddressbook';
+		try {
+			$this->client->delete(
+				$davUrl,
+				[
+					'auth' => [
+						'admin',
+						'admin',
+					],
+				]
+			);
+		} catch (\GuzzleHttp\Exception\ClientException $e) {}
+	}
+
+
+	/**
+	 * @When :user requests addressbook :addressBook with statuscode :statusCode
+	 */
+	public function requestsAddressbookWithStatuscode($user, $addressBook, $statusCode) {
+		$davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$addressBook;
+
+		$password = ($user === 'admin') ? 'admin' : '123456';
+		try {
+			$this->response = $this->client->get(
+				$davUrl,
+				[
+					'auth' => [
+						$user,
+						$password,
+					],
+				]
+			);
+		} catch (\GuzzleHttp\Exception\ClientException $e) {
+			$this->response = $e->getResponse();
+		}
+
+		if((int)$statusCode !== $this->response->getStatusCode()) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					(int)$statusCode,
+					$this->response->getStatusCode()
+				)
+			);
+		}
+
+		$body = $this->response->getBody()->getContents();
+		if(substr($body, 0, 1) === '<') {
+			$reader = new Sabre\Xml\Reader();
+			$reader->xml($body);
+			$this->responseXml = $reader->parse();
+		}
+	}
+
+	/**
+	 * @Given :user creates an addressbook named :addressBook with statuscode :statusCode
+	 */
+	public function createsAnAddressbookNamedWithStatuscode($user, $addressBook, $statusCode) {
+		$davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook;
+		$password = ($user === 'admin') ? 'admin' : '123456';
+
+		$request = $this->client->createRequest(
+			'MKCOL',
+			$davUrl,
+			[
+				'body' => '<d:mkcol xmlns:c="urn:ietf:params:xml:ns:caldav"
+              xmlns:card="urn:ietf:params:xml:ns:carddav"
+              xmlns:cs="http://calendarserver.org/ns/"
+              xmlns:d="DAV:">
+    <d:set>
+      <d:prop>
+        <d:resourcetype>
+            <d:collection />,<card:addressbook />
+          </d:resourcetype>,<d:displayname>'.$addressBook.'</d:displayname>
+      </d:prop>
+    </d:set>
+  </d:mkcol>',
+				'auth' => [
+					$user,
+					$password,
+				],
+				'headers' => [
+					'Content-Type' => 'application/xml;charset=UTF-8',
+				],
+			]
+		);
+
+		$this->response = $this->client->send($request);
+
+		if($this->response->getStatusCode() !== (int)$statusCode) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					(int)$statusCode,
+					$this->response->getStatusCode()
+				)
+			);
+		}
+	}
+
+	/**
+	 * @When The CardDAV exception is :message
+	 */
+	public function theCarddavExceptionIs($message) {
+		$result = $this->responseXml['value'][0]['value'];
+
+		if($message !== $result) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					$message,
+					$result
+				)
+			);
+		}
+	}
+
+	/**
+	 * @When The CardDAV error message is :arg1
+	 */
+	public function theCarddavErrorMessageIs($message) {
+		$result = $this->responseXml['value'][1]['value'];
+
+		if($message !== $result) {
+			throw new \Exception(
+				sprintf(
+					'Expected %s got %s',
+					$message,
+					$result
+				)
+			);
+		}
+	}
+
+}
diff --git a/build/integration/features/caldav.feature b/build/integration/features/caldav.feature
new file mode 100644
index 0000000000000000000000000000000000000000..948151485dbc97cee078893bcbddf87a5d12a9f2
--- /dev/null
+++ b/build/integration/features/caldav.feature
@@ -0,0 +1,31 @@
+Feature: caldav
+  Scenario: Accessing a not existing calendar of another user
+    Given user "user0" exists
+    When "admin" requests calendar "user0/MyCalendar"
+    Then The CalDAV HTTP status code should be "404"
+    And The exception is "Sabre\DAV\Exception\NotFound"
+    And The error message is "Node with name 'MyCalendar' could not be found"
+
+  # Blocked by https://github.com/php/php-src/pull/1417
+  #Scenario: Accessing a not shared calendar of another user
+  #  Given user "user0" exists
+  #  Given "admin" creates a calendar named "MyCalendar"
+  #  Given The CalDAV HTTP status code should be "201"
+  #  When "user0" requests calendar "admin/MyCalendar"
+  #  Then The CalDAV HTTP status code should be "404"
+  #  And The exception is "Sabre\DAV\Exception\NotFound"
+  #  And The error message is "Node with name 'MyCalendar' could not be found"
+
+  Scenario: Accessing a not existing calendar of myself
+    Given user "user0" exists
+    When "user0" requests calendar "admin/MyCalendar"
+    Then The CalDAV HTTP status code should be "404"
+    And The exception is "Sabre\DAV\Exception\NotFound"
+    And The error message is "Node with name 'MyCalendar' could not be found"
+
+  # Blocked by https://github.com/php/php-src/pull/1417
+  #Scenario: Creating a new calendar
+  #  When "admin" creates a calendar named "MyCalendar"
+  #  Then The CalDAV HTTP status code should be "201"
+  #  And "admin" requests calendar "admin/MyCalendar"
+  #  Then The CalDAV HTTP status code should be "200"
diff --git a/build/integration/features/carddav.feature b/build/integration/features/carddav.feature
new file mode 100644
index 0000000000000000000000000000000000000000..ee9d877085d9d4f3c8f98be68015111c62d1c5b6
--- /dev/null
+++ b/build/integration/features/carddav.feature
@@ -0,0 +1,23 @@
+Feature: carddav
+  Scenario: Accessing a not existing addressbook of another user
+    Given user "user0" exists
+    When "admin" requests addressbook "user0/MyAddressbook" with statuscode "404"
+    And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
+    And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
+
+  Scenario: Accessing a not shared addressbook of another user
+    Given user "user0" exists
+    Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
+    When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404"
+    And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
+    And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
+
+  Scenario: Accessing a not existing addressbook of myself
+    Given user "user0" exists
+    When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404"
+    And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
+    And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
+
+  Scenario: Creating a new addressbook
+    When "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
+    Then "admin" requests addressbook "admin/MyAddressbook" with statuscode "200"