mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
Merge pull request #32400 from nextcloud/feat/public-dav-v2
This commit is contained in:
commit
dc2066bc26
25 changed files with 923 additions and 80 deletions
|
|
@ -86,8 +86,4 @@
|
|||
<provider>OCA\DAV\CardDAV\Activity\Provider\Card</provider>
|
||||
</providers>
|
||||
</activity>
|
||||
|
||||
<public>
|
||||
<webdav>appinfo/v1/publicwebdav.php</webdav>
|
||||
</public>
|
||||
</info>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ OC_Util::obEnd();
|
|||
\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(),
|
||||
|
|
|
|||
155
apps/dav/appinfo/v2/publicremote.php
Normal file
155
apps/dav/appinfo/v2/publicremote.php
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Bjoern Schiessle <bjoern@schiessle.org>
|
||||
* @author Björn Schießle <bjoern@schiessle.org>
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
|
||||
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();
|
||||
|
|
@ -149,7 +149,7 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 @@ use Sabre\DAV\Auth\Backend\AbstractBasic;
|
|||
*
|
||||
* @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 @@ class PublicAuth extends AbstractBasic {
|
|||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Sabre\DAV\Exception\NotAuthenticated
|
||||
*/
|
||||
|
|
@ -96,8 +99,8 @@ class PublicAuth extends AbstractBasic {
|
|||
|| $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')))) {
|
||||
239
apps/dav/lib/Connector/Sabre/PublicAuth.php
Normal file
239
apps/dav/lib/Connector/Sabre/PublicAuth.php
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Björn Schießle <bjoern@schiessle.org>
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Maxence Lange <maxence@artificial-owl.com>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @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 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,13 +34,13 @@ use OCP\Share\IManager;
|
|||
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 @@ class PublicAuthTest extends \Test\TestCase {
|
|||
->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 @@ class PublicAuthTest extends \Test\TestCase {
|
|||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSharePasswordLinkValidSession(): void {
|
||||
public function testInvalidSharePasswordLinkValidSession(): void {
|
||||
$share = $this->getMockBuilder(IShare::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
425
apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php
Normal file
425
apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Bjoern Schiessle <bjoern@schiessle.org>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
*
|
||||
* @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\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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 @@ class MountPublicLinkController extends Controller {
|
|||
|
||||
// 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(
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class PublicPreviewController extends PublicShareController {
|
|||
$this->previewManager = $previewManager;
|
||||
}
|
||||
|
||||
protected function getPasswordHash(): string {
|
||||
protected function getPasswordHash(): ?string {
|
||||
return $this->share->getPassword();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ namespace OCA\Files_Sharing\Controller;
|
|||
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 @@ class ShareController extends AuthPublicShareController {
|
|||
return $this->shareManager->checkPassword($this->share, $password);
|
||||
}
|
||||
|
||||
protected function getPasswordHash(): string {
|
||||
protected function getPasswordHash(): ?string {
|
||||
return $this->share->getPassword();
|
||||
}
|
||||
|
||||
|
|
@ -222,8 +223,12 @@ class ShareController extends AuthPublicShareController {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -50,18 +50,18 @@ namespace OCA\Files_Sharing;
|
|||
* 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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,9 +45,8 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
$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 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
$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'
|
||||
];
|
||||
|
|
|
|||
|
|
@ -187,8 +187,8 @@ trait Sharing {
|
|||
$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) {
|
||||
|
|
|
|||
|
|
@ -169,11 +169,10 @@ trait WebDav {
|
|||
*/
|
||||
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 @@ trait WebDav {
|
|||
*/
|
||||
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 @@ trait WebDav {
|
|||
'Range' => $range
|
||||
]
|
||||
];
|
||||
$options['auth'] = [$token, ""];
|
||||
|
||||
$this->response = $client->request("GET", $fullUrl, $options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ abstract class PublicShareController extends Controller {
|
|||
*
|
||||
* @since 14.0.0
|
||||
*/
|
||||
abstract protected function getPasswordHash(): string;
|
||||
abstract protected function getPasswordHash(): ?string;
|
||||
|
||||
/**
|
||||
* Is the provided token a valid token
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ interface IShare {
|
|||
* If this share is obtained via a shareprovider the password is
|
||||
* hashed.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function getPassword();
|
||||
|
|
|
|||
60
public.php
60
public.php
|
|
@ -32,34 +32,54 @@
|
|||
*/
|
||||
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 === false || $pathInfo === '') {
|
||||
throw new RemoteException('Path not found', 404);
|
||||
}
|
||||
if (!$pos = strpos($pathInfo, '/', 1)) {
|
||||
$pos = strlen($pathInfo);
|
||||
}
|
||||
$service = substr($pathInfo, 1, $pos - 1);
|
||||
|
||||
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);
|
||||
}
|
||||
$file = \OC::$server->getConfig()->getAppValue('core', 'public_' . strip_tags($service));
|
||||
if ($file === '') {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
$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 @@ try {
|
|||
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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue