diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index d436596ca5a9d..02dec49a54668 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -86,8 +86,4 @@
OCA\DAV\CardDAV\Activity\Provider\Card
-
-
- appinfo/v1/publicwebdav.php
-
diff --git a/apps/dav/appinfo/v1/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php
index 8ca9c6c577c9c..b5a88afb16ea3 100644
--- a/apps/dav/appinfo/v1/publicwebdav.php
+++ b/apps/dav/appinfo/v1/publicwebdav.php
@@ -43,7 +43,7 @@
\OC::$server->getSession()->close();
// Backends
-$authBackend = new OCA\DAV\Connector\PublicAuth(
+$authBackend = new OCA\DAV\Connector\LegacyPublicAuth(
\OC::$server->getRequest(),
\OC::$server->getShareManager(),
\OC::$server->getSession(),
diff --git a/apps/dav/appinfo/v2/publicremote.php b/apps/dav/appinfo/v2/publicremote.php
new file mode 100644
index 0000000000000..0ef9bda8ddabc
--- /dev/null
+++ b/apps/dav/appinfo/v2/publicremote.php
@@ -0,0 +1,155 @@
+
+ * @author Björn Schießle
+ * @author Christoph Wurst
+ * @author Joas Schilling
+ * @author Julius Härtl
+ * @author Lukas Reschke
+ * @author Morris Jobke
+ * @author Robin Appelman
+ * @author Roeland Jago Douma
+ * @author Thomas Müller
+ * @author Vincent Petry
+ *
+ * @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
+ *
+ */
+
+use OC\Files\Filesystem;
+use OC\Files\Storage\Wrapper\PermissionsMask;
+use OC\Files\View;
+use OCA\DAV\Storage\PublicOwnerWrapper;
+use OCA\FederatedFileSharing\FederatedShareProvider;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Mount\IMountManager;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IPreview;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\ITagManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+use OCP\Security\Bruteforce\IThrottler;
+use OCP\Share\IManager;
+use Psr\Log\LoggerInterface;
+use Sabre\DAV\Exception\NotAuthenticated;
+use Sabre\DAV\Exception\NotFound;
+
+// load needed apps
+$RUNTIME_APPTYPES = ['filesystem', 'authentication', 'logging'];
+OC_App::loadApps($RUNTIME_APPTYPES);
+OC_Util::obEnd();
+
+$session = \OCP\Server::get(ISession::class);
+$request = \OCP\Server::get(IRequest::class);
+
+$session->close();
+$requestUri = $request->getRequestUri();
+
+// Backends
+$authBackend = new OCA\DAV\Connector\Sabre\PublicAuth(
+ $request,
+ \OCP\Server::get(IManager::class),
+ $session,
+ \OCP\Server::get(IThrottler::class),
+ \OCP\Server::get(LoggerInterface::class)
+);
+$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
+
+$l10nFactory = \OCP\Server::get(IFactory::class);
+$serverFactory = new OCA\DAV\Connector\Sabre\ServerFactory(
+ \OCP\Server::get(IConfig::class),
+ \OCP\Server::get(LoggerInterface::class),
+ \OCP\Server::get(IDBConnection::class),
+ \OCP\Server::get(IUserSession::class),
+ \OCP\Server::get(IMountManager::class),
+ \OCP\Server::get(ITagManager::class),
+ $request,
+ \OCP\Server::get(IPreview::class),
+ \OCP\Server::get(IEventDispatcher::class),
+ $l10nFactory->get('dav'),
+);
+
+
+$linkCheckPlugin = new \OCA\DAV\Files\Sharing\PublicLinkCheckPlugin();
+$filesDropPlugin = new \OCA\DAV\Files\Sharing\FilesDropPlugin();
+
+// Define root url with /public.php/dav/files/TOKEN
+/** @var string $baseuri defined in public.php */
+preg_match('/(^files\/\w+)/i', substr($requestUri, strlen($baseuri)), $match);
+$baseuri = $baseuri . $match[0];
+
+$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
+ $isAjax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
+ $federatedShareProvider = \OCP\Server::get(FederatedShareProvider::class);
+ if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) {
+ // this is what is thrown when trying to access a non-existing share
+ throw new NotAuthenticated();
+ }
+
+ $share = $authBackend->getShare();
+ $owner = $share->getShareOwner();
+ $isReadable = $share->getPermissions() & \OCP\Constants::PERMISSION_READ;
+ $fileId = $share->getNodeId();
+
+ // FIXME: should not add storage wrappers outside of preSetup, need to find a better way
+ /** @psalm-suppress InternalMethod */
+ $previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
+
+ /** @psalm-suppress MissingClosureParamType */
+ Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
+ return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | \OCP\Constants::PERMISSION_SHARE]);
+ });
+
+ /** @psalm-suppress MissingClosureParamType */
+ Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
+ return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);
+ });
+
+ /** @psalm-suppress InternalMethod */
+ Filesystem::logWarningWhenAddingStorageWrapper($previousLog);
+
+ OC_Util::tearDownFS();
+ OC_Util::setupFS($owner);
+ $ownerView = new View('/'. $owner . '/files');
+ $path = $ownerView->getPath($fileId);
+ $fileInfo = $ownerView->getFileInfo($path);
+
+ if ($fileInfo === false) {
+ throw new NotFound();
+ }
+
+ $linkCheckPlugin->setFileInfo($fileInfo);
+
+ // If not readble (files_drop) enable the filesdrop plugin
+ if (!$isReadable) {
+ $filesDropPlugin->enable();
+ }
+
+ $view = new View($ownerView->getAbsolutePath($path));
+ $filesDropPlugin->setView($view);
+
+ return $view;
+});
+
+$server->addPlugin($linkCheckPlugin);
+$server->addPlugin($filesDropPlugin);
+
+// And off we go!
+$server->exec();
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index 72455e6bf6779..d4305195d461e 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -149,7 +149,7 @@
'OCA\\DAV\\Comments\\EntityTypeCollection' => $baseDir . '/../lib/Comments/EntityTypeCollection.php',
'OCA\\DAV\\Comments\\RootCollection' => $baseDir . '/../lib/Comments/RootCollection.php',
'OCA\\DAV\\Connector\\LegacyDAVACL' => $baseDir . '/../lib/Connector/LegacyDAVACL.php',
- 'OCA\\DAV\\Connector\\PublicAuth' => $baseDir . '/../lib/Connector/PublicAuth.php',
+ 'OCA\\DAV\\Connector\\LegacyPublicAuth' => $baseDir . '/../lib/Connector/LegacyPublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => $baseDir . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\AppleQuirksPlugin' => $baseDir . '/../lib/Connector/Sabre/AppleQuirksPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Auth' => $baseDir . '/../lib/Connector/Sabre/Auth.php',
@@ -183,6 +183,7 @@
'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => $baseDir . '/../lib/Connector/Sabre/ObjectTree.php',
'OCA\\DAV\\Connector\\Sabre\\Principal' => $baseDir . '/../lib/Connector/Sabre/Principal.php',
'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => $baseDir . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php',
+ 'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => $baseDir . '/../lib/Connector/Sabre/PublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => $baseDir . '/../lib/Connector/Sabre/QuotaPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\RequestIdHeaderPlugin' => $baseDir . '/../lib/Connector/Sabre/RequestIdHeaderPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Server' => $baseDir . '/../lib/Connector/Sabre/Server.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index 0d715f510f778..9afd73635ffd1 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -164,7 +164,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Comments\\EntityTypeCollection' => __DIR__ . '/..' . '/../lib/Comments/EntityTypeCollection.php',
'OCA\\DAV\\Comments\\RootCollection' => __DIR__ . '/..' . '/../lib/Comments/RootCollection.php',
'OCA\\DAV\\Connector\\LegacyDAVACL' => __DIR__ . '/..' . '/../lib/Connector/LegacyDAVACL.php',
- 'OCA\\DAV\\Connector\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/PublicAuth.php',
+ 'OCA\\DAV\\Connector\\LegacyPublicAuth' => __DIR__ . '/..' . '/../lib/Connector/LegacyPublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\AppleQuirksPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AppleQuirksPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Auth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Auth.php',
@@ -198,6 +198,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ObjectTree.php',
'OCA\\DAV\\Connector\\Sabre\\Principal' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Principal.php',
'OCA\\DAV\\Connector\\Sabre\\PropfindCompressionPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PropfindCompressionPlugin.php',
+ 'OCA\\DAV\\Connector\\Sabre\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/PublicAuth.php',
'OCA\\DAV\\Connector\\Sabre\\QuotaPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/QuotaPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\RequestIdHeaderPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/RequestIdHeaderPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\Server' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Server.php',
diff --git a/apps/dav/lib/Connector/PublicAuth.php b/apps/dav/lib/Connector/LegacyPublicAuth.php
similarity index 92%
rename from apps/dav/lib/Connector/PublicAuth.php
rename to apps/dav/lib/Connector/LegacyPublicAuth.php
index d613a5a188f1b..c2cc81103bd4f 100644
--- a/apps/dav/lib/Connector/PublicAuth.php
+++ b/apps/dav/lib/Connector/LegacyPublicAuth.php
@@ -29,6 +29,7 @@
*/
namespace OCA\DAV\Connector;
+use OCA\DAV\Connector\Sabre\PublicAuth;
use OCP\IRequest;
use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
@@ -42,8 +43,9 @@
*
* @package OCA\DAV\Connector
*/
-class PublicAuth extends AbstractBasic {
- private const BRUTEFORCE_ACTION = 'public_webdav_auth';
+class LegacyPublicAuth extends AbstractBasic {
+ private const BRUTEFORCE_ACTION = 'legacy_public_webdav_auth';
+
private ?IShare $share = null;
private IManager $shareManager;
private ISession $session;
@@ -72,6 +74,7 @@ public function __construct(IRequest $request,
*
* @param string $username
* @param string $password
+ *
* @return bool
* @throws \Sabre\DAV\Exception\NotAuthenticated
*/
@@ -96,8 +99,8 @@ protected function validateUserPass($username, $password) {
|| $share->getShareType() === IShare::TYPE_CIRCLE) {
if ($this->shareManager->checkPassword($share, $password)) {
return true;
- } elseif ($this->session->exists('public_link_authenticated')
- && $this->session->get('public_link_authenticated') === (string)$share->getId()) {
+ } elseif ($this->session->exists(PublicAuth::DAV_AUTHENTICATED)
+ && $this->session->get(PublicAuth::DAV_AUTHENTICATED) === $share->getId()) {
return true;
} else {
if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) {
diff --git a/apps/dav/lib/Connector/Sabre/PublicAuth.php b/apps/dav/lib/Connector/Sabre/PublicAuth.php
new file mode 100644
index 0000000000000..d5b3d41e1ef31
--- /dev/null
+++ b/apps/dav/lib/Connector/Sabre/PublicAuth.php
@@ -0,0 +1,239 @@
+
+ * @author Christoph Wurst
+ * @author Joas Schilling
+ * @author Lukas Reschke
+ * @author Maxence Lange
+ * @author Robin Appelman
+ * @author Roeland Jago Douma
+ * @author Thomas Müller
+ * @author Vincent Petry
+ *
+ * @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
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre;
+
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\Security\Bruteforce\IThrottler;
+use OCP\Share\Exceptions\ShareNotFound;
+use OCP\Share\IManager;
+use OCP\Share\IShare;
+use Psr\Log\LoggerInterface;
+use Sabre\DAV\Auth\Backend\AbstractBasic;
+use Sabre\DAV\Exception\NotAuthenticated;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\ServiceUnavailable;
+use Sabre\HTTP;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * Class PublicAuth
+ *
+ * @package OCA\DAV\Connector
+ */
+class PublicAuth extends AbstractBasic {
+ private const BRUTEFORCE_ACTION = 'public_dav_auth';
+ public const DAV_AUTHENTICATED = 'public_link_authenticated';
+
+ private ?IShare $share = null;
+ private IManager $shareManager;
+ private ISession $session;
+ private IRequest $request;
+ private IThrottler $throttler;
+ private LoggerInterface $logger;
+
+ public function __construct(IRequest $request,
+ IManager $shareManager,
+ ISession $session,
+ IThrottler $throttler,
+ LoggerInterface $logger) {
+ $this->request = $request;
+ $this->shareManager = $shareManager;
+ $this->session = $session;
+ $this->throttler = $throttler;
+ $this->logger = $logger;
+
+ // setup realm
+ $defaults = new \OCP\Defaults();
+ $this->realm = $defaults->getName();
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ *
+ * @return array
+ * @throws NotAuthenticated
+ * @throws ServiceUnavailable
+ */
+ public function check(RequestInterface $request, ResponseInterface $response): array {
+ try {
+ $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), self::BRUTEFORCE_ACTION);
+
+ $auth = new HTTP\Auth\Basic(
+ $this->realm,
+ $request,
+ $response
+ );
+
+ $userpass = $auth->getCredentials();
+ // If authentication provided, checking its validity
+ if ($userpass && !$this->validateUserPass($userpass[0], $userpass[1])) {
+ return [false, 'Username or password was incorrect'];
+ }
+
+ return $this->checkToken();
+ } catch (NotAuthenticated $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ $class = get_class($e);
+ $msg = $e->getMessage();
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
+ throw new ServiceUnavailable("$class: $msg");
+ }
+ }
+
+ /**
+ * Extract token from request url
+ * @return string
+ * @throws NotFound
+ */
+ private function getToken(): string {
+ $path = $this->request->getPathInfo() ?: '';
+ // ['', 'dav', 'files', 'token']
+ $splittedPath = explode('/', $path);
+
+ if (count($splittedPath) < 4 || $splittedPath[3] === '') {
+ throw new NotFound();
+ }
+
+ return $splittedPath[3];
+ }
+
+ /**
+ * Check token validity
+ * @return array
+ * @throws NotFound
+ * @throws NotAuthenticated
+ */
+ private function checkToken(): array {
+ $token = $this->getToken();
+
+ try {
+ /** @var IShare $share */
+ $share = $this->shareManager->getShareByToken($token);
+ } catch (ShareNotFound $e) {
+ $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
+ throw new NotFound();
+ }
+
+ $this->share = $share;
+ \OC_User::setIncognitoMode(true);
+
+ // If already authenticated
+ if ($this->session->exists(self::DAV_AUTHENTICATED)
+ && $this->session->get(self::DAV_AUTHENTICATED) === $share->getId()) {
+ return [true, $this->principalPrefix . $token];
+ }
+
+ // If the share is protected but user is not authenticated
+ if ($share->getPassword() !== null) {
+ $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
+ throw new NotAuthenticated();
+ }
+
+ return [true, $this->principalPrefix . $token];
+ }
+
+ /**
+ * Validates a username and password
+ *
+ * This method should return true or false depending on if login
+ * succeeded.
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return bool
+ * @throws NotAuthenticated
+ */
+ protected function validateUserPass($username, $password) {
+ $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), self::BRUTEFORCE_ACTION);
+
+ $token = $this->getToken();
+ try {
+ $share = $this->shareManager->getShareByToken($token);
+ } catch (ShareNotFound $e) {
+ $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
+ return false;
+ }
+
+ $this->share = $share;
+ \OC_User::setIncognitoMode(true);
+
+ // check if the share is password protected
+ if ($share->getPassword() !== null) {
+ if ($share->getShareType() === IShare::TYPE_LINK
+ || $share->getShareType() === IShare::TYPE_EMAIL
+ || $share->getShareType() === IShare::TYPE_CIRCLE) {
+ if ($this->shareManager->checkPassword($share, $password)) {
+ // If not set, set authenticated session cookie
+ if (!$this->session->exists(self::DAV_AUTHENTICATED)
+ || $this->session->get(self::DAV_AUTHENTICATED) !== $share->getId()) {
+ $this->session->set(self::DAV_AUTHENTICATED, $share->getId());
+ }
+ return true;
+ }
+
+ if ($this->session->exists(PublicAuth::DAV_AUTHENTICATED)
+ && $this->session->get(PublicAuth::DAV_AUTHENTICATED) === $share->getId()) {
+ return true;
+ }
+
+ if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) {
+ // do not re-authenticate over ajax, use dummy auth name to prevent browser popup
+ http_response_code(401);
+ header('WWW-Authenticate: DummyBasic realm="' . $this->realm . '"');
+ throw new NotAuthenticated('Cannot authenticate over ajax calls');
+ }
+
+ $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
+ return false;
+ } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
+ return true;
+ }
+
+ $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getShare(): IShare {
+ assert($this->share !== null);
+ return $this->share;
+ }
+}
diff --git a/apps/dav/tests/unit/Connector/PublicAuthTest.php b/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php
similarity index 96%
rename from apps/dav/tests/unit/Connector/PublicAuthTest.php
rename to apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php
index 4a2ebb496b0cb..1c83beb8389bf 100644
--- a/apps/dav/tests/unit/Connector/PublicAuthTest.php
+++ b/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php
@@ -34,13 +34,13 @@
use OCP\Share\IShare;
/**
- * Class PublicAuthTest
+ * Class LegacyPublicAuthTest
*
* @group DB
*
* @package OCA\DAV\Tests\unit\Connector
*/
-class PublicAuthTest extends \Test\TestCase {
+class LegacyPublicAuthTest extends \Test\TestCase {
/** @var ISession|\PHPUnit\Framework\MockObject\MockObject */
private $session;
@@ -48,7 +48,7 @@ class PublicAuthTest extends \Test\TestCase {
private $request;
/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
private $shareManager;
- /** @var \OCA\DAV\Connector\PublicAuth */
+ /** @var \OCA\DAV\Connector\LegacyPublicAuth */
private $auth;
/** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */
private $throttler;
@@ -72,7 +72,7 @@ protected function setUp(): void {
->disableOriginalConstructor()
->getMock();
- $this->auth = new \OCA\DAV\Connector\PublicAuth(
+ $this->auth = new \OCA\DAV\Connector\LegacyPublicAuth(
$this->request,
$this->shareManager,
$this->session,
@@ -195,7 +195,7 @@ public function testSharePasswordMailValidPassword(): void {
$this->assertTrue($result);
}
- public function testSharePasswordLinkValidSession(): void {
+ public function testInvalidSharePasswordLinkValidSession(): void {
$share = $this->getMockBuilder(IShare::class)
->disableOriginalConstructor()
->getMock();
diff --git a/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php b/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php
new file mode 100644
index 0000000000000..ce0ff72940323
--- /dev/null
+++ b/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php
@@ -0,0 +1,425 @@
+
+ * @author Joas Schilling
+ * @author Lukas Reschke
+ * @author Morris Jobke
+ * @author Roeland Jago Douma
+ * @author Thomas Müller
+ *
+ * @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
+ *
+ */
+namespace OCA\DAV\Tests\unit\Connector;
+
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\Security\Bruteforce\IThrottler;
+use OCP\Share\Exceptions\ShareNotFound;
+use OCP\Share\IManager;
+use OCP\Share\IShare;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class PublicAuthTest
+ *
+ * @group DB
+ *
+ * @package OCA\DAV\Tests\unit\Connector
+ */
+class PublicAuthTest extends \Test\TestCase {
+
+ /** @var ISession|MockObject */
+ private $session;
+ /** @var IRequest|MockObject */
+ private $request;
+ /** @var IManager|MockObject */
+ private $shareManager;
+ /** @var PublicAuth */
+ private $auth;
+ /** @var IThrottler|MockObject */
+ private $throttler;
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var string */
+ private $oldUser;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->session = $this->createMock(ISession::class);
+ $this->request = $this->createMock(IRequest::class);
+ $this->shareManager = $this->createMock(IManager::class);
+ $this->throttler = $this->createMock(IThrottler::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->auth = new \OCA\DAV\Connector\Sabre\PublicAuth(
+ $this->request,
+ $this->shareManager,
+ $this->session,
+ $this->throttler,
+ $this->logger,
+ );
+
+ // Store current user
+ $this->oldUser = \OC_User::getUser();
+ }
+
+ protected function tearDown(): void {
+ \OC_User::setIncognitoMode(false);
+
+ // Set old user
+ \OC_User::setUserId($this->oldUser);
+ \OC_Util::setupFS($this->oldUser);
+
+ parent::tearDown();
+ }
+
+ public function testGetToken(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $result = $this->invokePrivate($this->auth, 'getToken');
+
+ $this->assertSame('GX9HSGQrGE', $result);
+ }
+
+ public function testGetTokenInvalid(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files');
+
+ $this->expectException(\Sabre\DAV\Exception\NotFound::class);
+ $this->invokePrivate($this->auth, 'getToken');
+ }
+
+ public function testCheckTokenValidShare(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn(null);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $result = $this->invokePrivate($this->auth, 'checkToken');
+ $this->assertSame([true, 'principals/GX9HSGQrGE'], $result);
+ }
+
+ public function testCheckTokenInvalidShare(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $this->shareManager
+ ->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->will($this->throwException(new ShareNotFound()));
+
+ $this->expectException(\Sabre\DAV\Exception\NotFound::class);
+ $this->invokePrivate($this->auth, 'checkToken');
+ }
+
+ public function testCheckTokenAlreadyAuthenticated(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getShareType')->willReturn(42);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
+ $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
+
+ $result = $this->invokePrivate($this->auth, 'checkToken');
+ $this->assertSame([true, 'principals/GX9HSGQrGE'], $result);
+ }
+
+ public function testCheckTokenPasswordNotAuthenticated(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(42);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(false);
+
+ $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class);
+ $this->invokePrivate($this->auth, 'checkToken');
+ }
+
+ public function testCheckTokenPasswordAuthenticatedWrongShare(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(42);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(false);
+ $this->session->method('get')->with('public_link_authenticated')->willReturn('43');
+
+ $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class);
+ $this->invokePrivate($this->auth, 'checkToken');
+ }
+
+ public function testNoShare(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willThrowException(new ShareNotFound());
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertFalse($result);
+ }
+
+ public function testShareNoPassword(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn(null);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSharePasswordFancyShareType(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(42);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertFalse($result);
+ }
+
+
+ public function testSharePasswordRemote(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_REMOTE);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSharePasswordLinkValidPassword(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->shareManager->expects($this->once())
+ ->method('checkPassword')->with(
+ $this->equalTo($share),
+ $this->equalTo('password')
+ )->willReturn(true);
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSharePasswordMailValidPassword(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL);
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->shareManager->expects($this->once())
+ ->method('checkPassword')->with(
+ $this->equalTo($share),
+ $this->equalTo('password')
+ )->willReturn(true);
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertTrue($result);
+ }
+
+ public function testInvalidSharePasswordLinkValidSession(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
+ $share->method('getId')->willReturn('42');
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->shareManager->expects($this->once())
+ ->method('checkPassword')
+ ->with(
+ $this->equalTo($share),
+ $this->equalTo('password')
+ )->willReturn(false);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
+ $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSharePasswordLinkInvalidSession(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
+ $share->method('getId')->willReturn('42');
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->shareManager->expects($this->once())
+ ->method('checkPassword')
+ ->with(
+ $this->equalTo($share),
+ $this->equalTo('password')
+ )->willReturn(false);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
+ $this->session->method('get')->with('public_link_authenticated')->willReturn('43');
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertFalse($result);
+ }
+
+
+ public function testSharePasswordMailInvalidSession(): void {
+ $this->request->method('getPathInfo')
+ ->willReturn('/dav/files/GX9HSGQrGE');
+
+ $share = $this->getMockBuilder(IShare::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $share->method('getPassword')->willReturn('password');
+ $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL);
+ $share->method('getId')->willReturn('42');
+
+ $this->shareManager->expects($this->once())
+ ->method('getShareByToken')
+ ->with('GX9HSGQrGE')
+ ->willReturn($share);
+
+ $this->shareManager->expects($this->once())
+ ->method('checkPassword')
+ ->with(
+ $this->equalTo($share),
+ $this->equalTo('password')
+ )->willReturn(false);
+
+ $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
+ $this->session->method('get')->with('public_link_authenticated')->willReturn('43');
+
+ $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']);
+
+ $this->assertFalse($result);
+ }
+}
diff --git a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php
index f13a77d936858..7e73f89ad4146 100644
--- a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php
+++ b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php
@@ -31,6 +31,7 @@
*/
namespace OCA\FederatedFileSharing\Controller;
+use OCA\DAV\Connector\Sabre\PublicAuth;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\AppFramework\Controller;
@@ -108,7 +109,7 @@ public function createFederatedShare($shareWith, $token, $password = '') {
// make sure that user is authenticated in case of a password protected link
$storedPassword = $share->getPassword();
- $authenticated = $this->session->get('public_link_authenticated') === $share->getId() ||
+ $authenticated = $this->session->get(PublicAuth::DAV_AUTHENTICATED) === $share->getId() ||
$this->shareManager->checkPassword($share, $password);
if (!empty($storedPassword) && !$authenticated) {
$response = new JSONResponse(
diff --git a/apps/files_sharing/js/files_drop.js b/apps/files_sharing/js/files_drop.js
index ffbe828443312..5215cfc5b5415 100644
--- a/apps/files_sharing/js/files_drop.js
+++ b/apps/files_sharing/js/files_drop.js
@@ -24,10 +24,9 @@
var filesClient = new OC.Files.Client({
host: OC.getHost(),
port: OC.getPort(),
- userName: $('#sharingToken').val(),
// note: password not be required, the endpoint
// will recognize previous validation from the session
- root: OC.getRootPath() + '/public.php/webdav',
+ root: OC.getRootPath() + '/public.php/dav/files/' + $('#sharingToken').val() + '/',
useHTTPS: OC.getProtocol() === 'https'
});
@@ -45,7 +44,7 @@
return false;
}
var base = OC.getProtocol() + '://' + OC.getHost();
- data.url = base + OC.getRootPath() + '/public.php/webdav/' + encodeURI(name);
+ data.url = base + OC.getRootPath() + '/public.php/dav/files/' + $('#sharingToken').val() + '/' + encodeURI(name);
data.multipart = false;
@@ -53,14 +52,6 @@
data.headers = {};
}
- var userName = filesClient.getUserName();
- var password = filesClient.getPassword();
- if (userName) {
- // copy username/password from DAV client
- data.headers['Authorization'] =
- 'Basic ' + btoa(userName + ':' + (password || ''));
- }
-
$('#drop-upload-done-indicator').addClass('hidden');
$('#drop-upload-progress-indicator').removeClass('hidden');
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 154b970c5be33..d26cecd02ee17 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -69,10 +69,9 @@ OCA.Sharing.PublicApp = {
var filesClient = new OC.Files.Client({
host: OC.getHost(),
port: OC.getPort(),
- userName: token,
// note: password not be required, the endpoint
// will recognize previous validation from the session
- root: OC.getRootPath() + '/public.php/webdav',
+ root: OC.getRootPath() + '/public.php/dav/files/' + token + '/',
useHTTPS: OC.getProtocol() === 'https'
});
@@ -167,11 +166,10 @@ OCA.Sharing.PublicApp = {
return;
}
// Undocumented Url to public WebDAV endpoint
- var url = parent.location.protocol + '//' + location.host + OC.linkTo('', 'public.php/webdav');
+ var url = parent.location.protocol + '//' + location.host + OC.linkTo('', 'public.php/dav/files/'+ token);
$.ajax({
url: url,
headers: {
- Authorization: 'Basic ' + btoa(token + ':'),
Range: 'bytes=0-10000'
}
}).then(function (data) {
@@ -247,7 +245,9 @@ OCA.Sharing.PublicApp = {
// also add auth in URL due to POST workaround
base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : '');
}
- return base + OC.getRootPath() + '/public.php/webdav' + encodedPath;
+
+ // encodedPath starts with a leading slash
+ return base + OC.getRootPath() + '/public.php/dav/files/' + token + encodedPath;
};
this.fileList.getAjaxUrl = function (action, params) {
diff --git a/apps/files_sharing/lib/Controller/PublicPreviewController.php b/apps/files_sharing/lib/Controller/PublicPreviewController.php
index 54a1a32166561..c59baac73c41d 100644
--- a/apps/files_sharing/lib/Controller/PublicPreviewController.php
+++ b/apps/files_sharing/lib/Controller/PublicPreviewController.php
@@ -61,7 +61,7 @@ public function __construct(string $appName,
$this->previewManager = $previewManager;
}
- protected function getPasswordHash(): string {
+ protected function getPasswordHash(): ?string {
return $this->share->getPassword();
}
diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php
index 835d08c446ca9..eebf964c8f994 100644
--- a/apps/files_sharing/lib/Controller/ShareController.php
+++ b/apps/files_sharing/lib/Controller/ShareController.php
@@ -46,6 +46,7 @@
use OC\Security\CSP\ContentSecurityPolicy;
use OC_Files;
use OC_Util;
+use OCA\DAV\Connector\Sabre\PublicAuth;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Files_Sharing\Activity\Providers\Downloads;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
@@ -203,7 +204,7 @@ protected function verifyPassword(string $password): bool {
return $this->shareManager->checkPassword($this->share, $password);
}
- protected function getPasswordHash(): string {
+ protected function getPasswordHash(): ?string {
return $this->share->getPassword();
}
@@ -222,8 +223,12 @@ protected function isPasswordProtected(): bool {
}
protected function authSucceeded() {
+ if ($this->share === null) {
+ throw new NotFoundException();
+ }
+
// For share this was always set so it is still used in other apps
- $this->session->set('public_link_authenticated', (string)$this->share->getId());
+ $this->session->set(PublicAuth::DAV_AUTHENTICATED, $this->share->getId());
}
protected function authFailed() {
diff --git a/apps/files_sharing/lib/ResponseDefinitions.php b/apps/files_sharing/lib/ResponseDefinitions.php
index aa1ee004971a5..313cf82bd51ca 100644
--- a/apps/files_sharing/lib/ResponseDefinitions.php
+++ b/apps/files_sharing/lib/ResponseDefinitions.php
@@ -50,18 +50,18 @@
* mimetype: string,
* note: string,
* parent: null,
- * password?: string,
+ * password?: null|string,
* password_expiration_time?: ?string,
* path: ?string,
* permissions: int,
* send_password_by_talk?: bool,
* share_type: int,
- * share_with?: string,
+ * share_with?: null|string,
* share_with_avatar?: string,
* share_with_displayname?: string,
* share_with_displayname_unique?: ?string,
* share_with_link?: string,
- * status?: array{clearAt?: int|null, icon?: ?string, message?: ?string, status?: string},
+ * status?: array{clearAt: int|null, icon: ?string, message: ?string, status: string},
* stime: int,
* storage: int,
* storage_id: string,
diff --git a/apps/files_sharing/openapi.json b/apps/files_sharing/openapi.json
index bc8128df0d20c..abbc3d250a6ea 100644
--- a/apps/files_sharing/openapi.json
+++ b/apps/files_sharing/openapi.json
@@ -598,7 +598,8 @@
"nullable": true
},
"password": {
- "type": "string"
+ "type": "string",
+ "nullable": true
},
"password_expiration_time": {
"type": "string",
@@ -620,7 +621,8 @@
"format": "int64"
},
"share_with": {
- "type": "string"
+ "type": "string",
+ "nullable": true
},
"share_with_avatar": {
"type": "string"
@@ -637,6 +639,12 @@
},
"status": {
"type": "object",
+ "required": [
+ "clearAt",
+ "icon",
+ "message",
+ "status"
+ ],
"properties": {
"clearAt": {
"type": "integer",
diff --git a/apps/files_sharing/tests/js/fileDropSpec.js b/apps/files_sharing/tests/js/fileDropSpec.js
index efe87802d1cc0..18eee661e7fad 100644
--- a/apps/files_sharing/tests/js/fileDropSpec.js
+++ b/apps/files_sharing/tests/js/fileDropSpec.js
@@ -91,8 +91,7 @@ describe("files Drop tests", function() {
OCA.FilesSharingDrop.addFileToUpload('',data);
expect(data.submit.calledOnce).toEqual(true);
- expect(data.url).toContain("/public.php/webdav/" + encodeURI(testFile.name));
- expect(data.headers['Authorization']).toEqual('Basic ' + btoa(sharingToken+":"));
+ expect(data.url).toContain("/public.php/dav/files/" + sharingToken + '/' + encodeURI(testFile.name));
});
}
});
diff --git a/apps/files_sharing/tests/js/publicAppSpec.js b/apps/files_sharing/tests/js/publicAppSpec.js
index 59ac4bd7bbd04..229e57ac4ed1e 100644
--- a/apps/files_sharing/tests/js/publicAppSpec.js
+++ b/apps/files_sharing/tests/js/publicAppSpec.js
@@ -107,8 +107,7 @@ describe('OCA.Sharing.PublicApp tests', function() {
App.initialize($('#preview'));
expect(fakeServer.requests.length).toEqual(1);
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
- expect(fakeServer.requests[0].url).toEqual('https://example.com:9876/owncloud/public.php/webdav/subdir');
- expect(fakeServer.requests[0].requestHeaders.Authorization).toEqual('Basic c2g0dG9rOm51bGw=');
+ expect(fakeServer.requests[0].url).toEqual('https://example.com:9876/owncloud/public.php/dav/files/sh4tok/subdir');
uploaderDetectStub.restore();
});
@@ -149,11 +148,11 @@ describe('OCA.Sharing.PublicApp tests', function() {
});
it('returns correct upload URL', function() {
expect(fileList.getUploadUrl('some file.txt'))
- .toEqual('/owncloud/public.php/webdav/subdir/some%20file.txt');
+ .toEqual('/owncloud/public.php/dav/files/sh4tok/subdir/some%20file.txt');
});
it('returns correct upload URL with specified dir', function() {
expect(fileList.getUploadUrl('some file.txt', 'sub'))
- .toEqual('/owncloud/public.php/webdav/sub/some%20file.txt');
+ .toEqual('/owncloud/public.php/dav/files/sh4tok/sub/some%20file.txt');
});
});
});
diff --git a/build/integration/features/bootstrap/FilesDropContext.php b/build/integration/features/bootstrap/FilesDropContext.php
index a5d4dad14e3df..22eaa63903069 100644
--- a/build/integration/features/bootstrap/FilesDropContext.php
+++ b/build/integration/features/bootstrap/FilesDropContext.php
@@ -45,9 +45,8 @@ public function droppingFileWith($path, $content) {
}
$base = substr($this->baseUrl, 0, -4);
- $fullUrl = $base . '/public.php/webdav' . $path;
+ $fullUrl = $base . "/public.php/dav/files/$token/$path";
- $options['auth'] = [$token, ''];
$options['headers'] = [
'X-REQUESTED-WITH' => 'XMLHttpRequest'
];
@@ -73,9 +72,8 @@ public function creatingFolderInDrop($folder) {
}
$base = substr($this->baseUrl, 0, -4);
- $fullUrl = $base . '/public.php/webdav/' . $folder;
+ $fullUrl = $base . "/public.php/dav/files/$token/$folder";
- $options['auth'] = [$token, ''];
$options['headers'] = [
'X-REQUESTED-WITH' => 'XMLHttpRequest'
];
diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php
index a38ef392e61f0..7993d502707be 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -187,8 +187,8 @@ public function lastShareWithPasswordCanBeDownloaded($password) {
$token = $this->lastShareData->data->token;
}
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav";
- $this->checkDownload($fullUrl, [$token, $password], 'text/plain');
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/";
+ $this->checkDownload($fullUrl, ['', $password], 'text/plain');
}
private function checkDownload($url, $auth = null, $mimeType = null) {
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index c239461b78864..b919976e790d3 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -169,11 +169,10 @@ public function downloadFileWithRange($fileSource, $range) {
*/
public function downloadPublicFileWithRange($range) {
$token = $this->lastShareData->data->token;
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav";
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token";
$client = new GClient();
$options = [];
- $options['auth'] = [$token, ""];
$options['headers'] = [
'Range' => $range
];
@@ -187,7 +186,7 @@ public function downloadPublicFileWithRange($range) {
*/
public function downloadPublicFileInsideAFolderWithRange($path, $range) {
$token = $this->lastShareData->data->token;
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav" . "$path";
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/$path";
$client = new GClient();
$options = [
@@ -195,7 +194,6 @@ public function downloadPublicFileInsideAFolderWithRange($path, $range) {
'Range' => $range
]
];
- $options['auth'] = [$token, ""];
$this->response = $client->request("GET", $fullUrl, $options);
}
diff --git a/build/integration/features/maintenance-mode.feature b/build/integration/features/maintenance-mode.feature
index 56d3b9c0fb6a7..e6637a5edff1d 100644
--- a/build/integration/features/maintenance-mode.feature
+++ b/build/integration/features/maintenance-mode.feature
@@ -39,3 +39,9 @@ Feature: maintenance-mode
Then the HTTP status code should be "503"
Then Maintenance mode is disabled
And the command was successful
+
+ Scenario: Accessing /public.php/dav with maintenance mode enabled
+ When requesting "/public.php/dav" with "GET"
+ Then the HTTP status code should be "503"
+ Then Maintenance mode is disabled
+ And the command was successful
diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php
index 7c3725c9290a4..cbcb934319875 100644
--- a/lib/public/AppFramework/PublicShareController.php
+++ b/lib/public/AppFramework/PublicShareController.php
@@ -86,7 +86,7 @@ final public function getToken(): string {
*
* @since 14.0.0
*/
- abstract protected function getPasswordHash(): string;
+ abstract protected function getPasswordHash(): ?string;
/**
* Is the provided token a valid token
diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php
index e5a943b0bac89..74d404101cd03 100644
--- a/lib/public/Share/IShare.php
+++ b/lib/public/Share/IShare.php
@@ -474,7 +474,7 @@ public function setPassword($password);
* If this share is obtained via a shareprovider the password is
* hashed.
*
- * @return string
+ * @return string|null
* @since 9.0.0
*/
public function getPassword();
diff --git a/public.php b/public.php
index 2956d7f79dd81..9f3ffcec5e919 100644
--- a/public.php
+++ b/public.php
@@ -32,33 +32,53 @@
*/
require_once __DIR__ . '/lib/versioncheck.php';
+/**
+ * @param $service
+ * @return string
+ */
+function resolveService(string $service): string {
+ $services = [
+ 'webdav' => 'dav/appinfo/v1/publicwebdav.php',
+ 'dav' => 'dav/appinfo/v2/publicremote.php',
+ ];
+ if (isset($services[$service])) {
+ return $services[$service];
+ }
+
+ return \OC::$server->getConfig()->getAppValue('core', 'remote_' . $service);
+}
+
try {
require_once __DIR__ . '/lib/base.php';
+
+ // All resources served via the DAV endpoint should have the strictest possible
+ // policy. Exempted from this is the SabreDAV browser plugin which overwrites
+ // this policy with a softer one if debug mode is enabled.
+ header("Content-Security-Policy: default-src 'none';");
+
if (\OCP\Util::needUpgrade()) {
// since the behavior of apps or remotes are unpredictable during
// an upgrade, return a 503 directly
- OC_Template::printErrorPage('Service unavailable', '', 503);
- exit;
+ throw new RemoteException('Service unavailable', 503);
}
- OC::checkMaintenanceMode(\OC::$server->get(\OC\SystemConfig::class));
$request = \OC::$server->getRequest();
$pathInfo = $request->getPathInfo();
-
- if (!$pathInfo && $request->getParam('service', '') === '') {
- http_response_code(404);
- exit;
- } elseif ($request->getParam('service', '')) {
- $service = $request->getParam('service', '');
- } else {
- $pathInfo = trim($pathInfo, '/');
- [$service] = explode('/', $pathInfo);
+ if ($pathInfo === false || $pathInfo === '') {
+ throw new RemoteException('Path not found', 404);
}
- $file = \OC::$server->getConfig()->getAppValue('core', 'public_' . strip_tags($service));
- if ($file === '') {
- http_response_code(404);
- exit;
+ if (!$pos = strpos($pathInfo, '/', 1)) {
+ $pos = strlen($pathInfo);
}
+ $service = substr($pathInfo, 1, $pos - 1);
+
+ $file = resolveService($service);
+
+ if (!$file) {
+ throw new RemoteException('Path not found', 404);
+ }
+
+ $file = ltrim($file, '/');
$parts = explode('/', $file, 2);
$app = $parts[0];
@@ -70,15 +90,13 @@
OC_App::loadApps(['filesystem', 'logging']);
if (!\OC::$server->getAppManager()->isInstalled($app)) {
- http_response_code(404);
- exit;
+ throw new RemoteException('App not installed: ' . $app);
}
OC_App::loadApp($app);
OC_User::setIncognitoMode(true);
- $baseuri = OC::$WEBROOT . '/public.php/' . $service . '/';
-
- require_once OC_App::getAppPath($app) . '/' . $parts[1];
+ $baseuri = OC::$WEBROOT . '/public.php/'.$service.'/';
+ require_once $file;
} catch (Exception $ex) {
$status = 500;
if ($ex instanceof \OC\ServiceUnavailableException) {