diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 863b27da277..8e5110c6a16 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -34,13 +34,7 @@ return [ ], [ 'name' => 'PublicPreview#getPreview', - 'url' => '/publicpreview', - 'verb' => 'GET', - ], - - [ - 'name' => 'PublicPreview#getPreview', - 'url' => '/ajax/publicpreview.php', + 'url' => '/publicpreview/{token}', 'verb' => 'GET', ], diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 1de7c6b4fcd..a2884468a6b 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -112,7 +112,6 @@ OCA.Sharing.PublicApp = { y: Math.ceil(previewHeight * window.devicePixelRatio), a: 'true', file: encodeURIComponent(this.initialDir + $('#filename').val()), - t: token, scalingup: 0 }; @@ -150,7 +149,7 @@ OCA.Sharing.PublicApp = { } else if ((previewSupported === 'true' && mimetype.substr(0, mimetype.indexOf('/')) !== 'video') || mimetype.substr(0, mimetype.indexOf('/')) === 'image' && mimetype !== 'image/svg+xml') { - img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); + img.attr('src', OC.linkTo('files_sharing', '/publicpreview/'+token) + '?' + OC.buildQueryString(params)); imgcontainer.appendTo('#imgframe'); } else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') { img.attr('src', OC.Util.replaceSVGIcon(mimetypeIcon)); @@ -158,7 +157,7 @@ OCA.Sharing.PublicApp = { imgcontainer.appendTo('#imgframe'); } else if (previewSupported === 'true') { - $('#imgframe > video').attr('poster', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); + $('#imgframe > video').attr('poster', OC.generateUrl(OC.linkTo('files_sharing', '/publicpreview/'+token)) + '?' + OC.buildQueryString(params)); } if (this.fileList) { @@ -223,8 +222,8 @@ OCA.Sharing.PublicApp = { urlSpec.y *= window.devicePixelRatio; urlSpec.x = Math.ceil(urlSpec.x); urlSpec.y = Math.ceil(urlSpec.y); - urlSpec.t = $('#dirToken').val(); - return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec); + var token = $('#dirToken').val(); + return OC.generateUrl(OC.linkTo('files_sharing', '/publicpreview/'+token) + '?' + OC.buildQueryString(urlSpec)); }; this.fileList.updateEmptyContent = function() { @@ -427,4 +426,4 @@ $(document).ready(function () { }; } -}); \ No newline at end of file +}); diff --git a/apps/files_sharing/lib/Controller/PublicPreviewController.php b/apps/files_sharing/lib/Controller/PublicPreviewController.php index 0870995fc7b..b13c0a64b0e 100644 --- a/apps/files_sharing/lib/Controller/PublicPreviewController.php +++ b/apps/files_sharing/lib/Controller/PublicPreviewController.php @@ -27,15 +27,18 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\PublicShareController; use OCP\Constants; use OCP\Files\Folder; use OCP\Files\NotFoundException; use OCP\IPreview; use OCP\IRequest; +use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager as ShareManager; +use OCP\Share\IShare; -class PublicPreviewController extends Controller { +class PublicPreviewController extends PublicShareController { /** @var ShareManager */ private $shareManager; @@ -43,16 +46,38 @@ class PublicPreviewController extends Controller { /** @var IPreview */ private $previewManager; - public function __construct($appName, + /** @var IShare */ + private $share; + + public function __construct(string $appName, IRequest $request, ShareManager $shareManger, + ISession $session, IPreview $previewManager) { - parent::__construct($appName, $request); + parent::__construct($appName, $request, $session); $this->shareManager = $shareManger; $this->previewManager = $previewManager; } + protected function getPasswordHash(): string { + return $this->share->getPassword(); + } + + public function isValidToken(): bool { + try { + $this->share = $this->shareManager->getShareByToken($this->getToken()); + return true; + } catch (ShareNotFound $e) { + return false; + } + } + + protected function isPasswordProtected(): bool { + return $this->share->getPassword() !== null; + } + + /** * @PublicPage * @NoCSRFRequired @@ -60,24 +85,23 @@ class PublicPreviewController extends Controller { * @param string $file * @param int $x * @param int $y - * @param string $t * @param bool $a * @return DataResponse|FileDisplayResponse */ public function getPreview( - $file = '', - $x = 32, - $y = 32, - $t = '', + string $token, + string $file = '', + int $x = 32, + int $y = 32, $a = false ) { - if ($t === '' || $x === 0 || $y === 0) { + if ($token === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } try { - $share = $this->shareManager->getShareByToken($t); + $share = $this->shareManager->getShareByToken($token); } catch (ShareNotFound $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); } diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 739031d4bc2..0b30a599c7f 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -38,6 +38,7 @@ namespace OCA\Files_Sharing\Controller; use OC_Files; use OC_Util; use OCA\FederatedFileSharing\FederatedShareProvider; +use OCP\AppFramework\AuthPublicShareController; use OCP\AppFramework\Http\Template\SimpleMenuAction; use OCP\AppFramework\Http\Template\ExternalShareMenuAction; use OCP\AppFramework\Http\Template\LinkMenuAction; @@ -46,10 +47,8 @@ use OCP\Defaults; use OCP\IL10N; use OCP\Template; use OCP\Share; -use OCP\AppFramework\Controller; use OCP\IRequest; use OCP\AppFramework\Http\TemplateResponse; -use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\NotFoundResponse; use OCP\IURLGenerator; use OCP\IConfig; @@ -58,32 +57,27 @@ use OCP\IUserManager; use OCP\ISession; use OCP\IPreview; use OCA\Files_Sharing\Activity\Providers\Downloads; -use \OCP\Files\NotFoundException; +use OCP\Files\NotFoundException; use OCP\Files\IRootFolder; use OCP\Share\Exceptions\ShareNotFound; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use OCP\Share\IManager as ShareManager; /** * Class ShareController * * @package OCA\Files_Sharing\Controllers */ -class ShareController extends Controller { +class ShareController extends AuthPublicShareController { /** @var IConfig */ protected $config; - /** @var IURLGenerator */ - protected $urlGenerator; /** @var IUserManager */ protected $userManager; /** @var ILogger */ protected $logger; /** @var \OCP\Activity\IManager */ protected $activityManager; - /** @var \OCP\Share\IManager */ - protected $shareManager; - /** @var ISession */ - protected $session; /** @var IPreview */ protected $previewManager; /** @var IRootFolder */ @@ -96,6 +90,11 @@ class ShareController extends Controller { protected $l10n; /** @var Defaults */ protected $defaults; + /** @var ShareManager */ + protected $shareManager; + + /** @var Share\IShare */ + protected $share; /** * @param string $appName @@ -114,14 +113,14 @@ class ShareController extends Controller { * @param IL10N $l10n * @param Defaults $defaults */ - public function __construct($appName, + public function __construct(string $appName, IRequest $request, IConfig $config, IURLGenerator $urlGenerator, IUserManager $userManager, ILogger $logger, \OCP\Activity\IManager $activityManager, - \OCP\Share\IManager $shareManager, + ShareManager $shareManager, ISession $session, IPreview $previewManager, IRootFolder $rootFolder, @@ -129,108 +128,50 @@ class ShareController extends Controller { EventDispatcherInterface $eventDispatcher, IL10N $l10n, Defaults $defaults) { - parent::__construct($appName, $request); + parent::__construct($appName, $request, $session, $urlGenerator); $this->config = $config; - $this->urlGenerator = $urlGenerator; $this->userManager = $userManager; $this->logger = $logger; $this->activityManager = $activityManager; - $this->shareManager = $shareManager; - $this->session = $session; $this->previewManager = $previewManager; $this->rootFolder = $rootFolder; $this->federatedShareProvider = $federatedShareProvider; $this->eventDispatcher = $eventDispatcher; $this->l10n = $l10n; $this->defaults = $defaults; + $this->shareManager = $shareManager; } - /** - * @PublicPage - * @NoCSRFRequired - * - * @param string $token - * @return TemplateResponse|RedirectResponse - */ - public function showAuthenticate($token) { - $share = $this->shareManager->getShareByToken($token); - - if($this->linkShareAuth($share)) { - return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.showShare', array('token' => $token))); - } - - return new TemplateResponse($this->appName, 'authenticate', array(), 'guest'); + protected function verifyPassword(string $password): bool { + return $this->shareManager->checkPassword($this->share, $password); } - /** - * @PublicPage - * @UseSession - * @BruteForceProtection(action=publicLinkAuth) - * - * Authenticates against password-protected shares - * @param string $token - * @param string $redirect - * @param string $password - * @return RedirectResponse|TemplateResponse|NotFoundResponse - */ - public function authenticate($token, $redirect, $password = '') { + protected function getPasswordHash(): string { + return $this->share->getPassword(); + } - // Check whether share exists + public function isValidToken(): bool { try { - $share = $this->shareManager->getShareByToken($token); + $this->share = $this->shareManager->getShareByToken($this->getToken()); } catch (ShareNotFound $e) { - return new NotFoundResponse(); + return false; } - $authenticate = $this->linkShareAuth($share, $password); - - // if download was requested before auth, redirect to download - if ($authenticate === true && $redirect === 'download') { - return new RedirectResponse($this->urlGenerator->linkToRoute( - 'files_sharing.sharecontroller.downloadShare', - array('token' => $token)) - ); - } else if ($authenticate === true) { - return new RedirectResponse($this->urlGenerator->linkToRoute( - 'files_sharing.sharecontroller.showShare', - array('token' => $token)) - ); - } + return true; + } - $response = new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest'); - $response->throttle(); - return $response; + protected function isPasswordProtected(): bool { + return $this->share->getPassword() !== null; } - /** - * Authenticate a link item with the given password. - * Or use the session if no password is provided. - * - * This is a modified version of Helper::authenticate - * TODO: Try to merge back eventually with Helper::authenticate - * - * @param \OCP\Share\IShare $share - * @param string|null $password - * @return bool - */ - private function linkShareAuth(\OCP\Share\IShare $share, $password = null) { - if ($password !== null) { - if ($this->shareManager->checkPassword($share, $password)) { - $this->session->regenerateId(true, true); - $this->session->set('public_link_authenticated', (string)$share->getId()); - } else { - $this->emitAccessShareHook($share, 403, 'Wrong password'); - return false; - } - } else { - // not authenticated ? - if ( ! $this->session->exists('public_link_authenticated') - || $this->session->get('public_link_authenticated') !== (string)$share->getId()) { - return false; - } - } - return true; + protected function authSucceeded() { + // For share this was always set so it is still used in other apps + $this->session->set('public_link_authenticated', (string)$this->share->getId()); + } + + protected function authFailed() { + $this->emitAccessShareHook($this->share, 403, 'Wrong password'); } /** @@ -285,27 +226,21 @@ class ShareController extends Controller { * @PublicPage * @NoCSRFRequired * - * @param string $token + * @param string $path - * @return TemplateResponse|RedirectResponse|NotFoundResponse + * @return TemplateResponse * @throws NotFoundException * @throws \Exception */ - public function showShare($token, $path = '') { + public function showShare($path = ''): TemplateResponse { \OC_User::setIncognitoMode(true); // Check whether share exists try { - $share = $this->shareManager->getShareByToken($token); + $share = $this->shareManager->getShareByToken($this->getToken()); } catch (ShareNotFound $e) { - $this->emitAccessShareHook($token, 404, 'Share not found'); - return new NotFoundResponse(); - } - - // Share is password protected - check whether the user is permitted to access the share - if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { - return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', - array('token' => $token, 'redirect' => 'preview'))); + $this->emitAccessShareHook($this->getToken(), 404, 'Share not found'); + throw new NotFoundException(); } if (!$this->validateShare($share)) { @@ -329,8 +264,8 @@ class ShareController extends Controller { $shareTmpl['directory_path'] = $share->getTarget(); $shareTmpl['mimetype'] = $share->getNode()->getMimetype(); $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($share->getNode()->getMimetype()); - $shareTmpl['dirToken'] = $token; - $shareTmpl['sharingToken'] = $token; + $shareTmpl['dirToken'] = $this->getToken(); + $shareTmpl['sharingToken'] = $this->getToken(); $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled(); $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false'; $shareTmpl['dir'] = ''; @@ -367,7 +302,7 @@ class ShareController extends Controller { $folder = new Template('files', 'list', ''); $folder->assign('dir', $rootFolder->getRelativePath($folderNode->getPath())); - $folder->assign('dirToken', $token); + $folder->assign('dirToken', $this->getToken()); $folder->assign('permissions', \OCP\Constants::PERMISSION_READ); $folder->assign('isPublic', true); $folder->assign('hideFileList', $hideFileList); @@ -382,8 +317,8 @@ class ShareController extends Controller { $shareTmpl['hideFileList'] = $hideFileList; $shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); - $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $token]); - $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]); + $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $this->getToken()]); + $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $this->getToken()]); $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true); $shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024); @@ -393,19 +328,19 @@ class ShareController extends Controller { $ogPreview = ''; if ($shareTmpl['previewSupported']) { $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview', - ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 't' => $shareTmpl['dirToken']]); + ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]); $ogPreview = $shareTmpl['previewImage']; // We just have direct previews for image files if ($share->getNode()->getMimePart() === 'image') { - $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]); + $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $this->getToken()]); $ogPreview = $shareTmpl['previewURL']; //Whatapp is kind of picky about their size requirements if ($this->request->isUserAgent(['/^WhatsApp/'])) { $ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [ - 't' => $token, + 'token' => $this->getToken(), 'x' => 256, 'y' => 256, 'a' => true, @@ -488,12 +423,6 @@ class ShareController extends Controller { return new \OCP\AppFramework\Http\DataResponse('Share is read-only'); } - // Share is password protected - check whether the user is permitted to access the share - if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { - return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', - ['token' => $token, 'redirect' => 'download'])); - } - $files_list = null; if (!is_null($files)) { // download selected files $files_list = json_decode($files); @@ -507,13 +436,15 @@ class ShareController extends Controller { } } - $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); - $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath()); if (!$this->validateShare($share)) { throw new NotFoundException(); } + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath()); + + // Single file share if ($share->getNode() instanceof \OCP\Files\File) { // Single file download diff --git a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php index 4b630d0a8da..b5f1178b7f0 100644 --- a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php +++ b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php @@ -101,13 +101,6 @@ class SharingCheckMiddleware extends Middleware { if ($controller instanceof ExternalSharesController && !$this->externalSharesChecks()) { throw new S2SException('Federated sharing not allowed'); - } else if ($controller instanceof ShareController) { - $token = $this->request->getParam('token'); - $share = $this->shareManager->getShareByToken($token); - if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK - && !$this->isLinkSharingEnabled()) { - throw new NotFoundException('Link sharing is disabled'); - } } } @@ -165,22 +158,6 @@ class SharingCheckMiddleware extends Middleware { return true; } - /** - * Check if link sharing is allowed - * @return bool - */ - private function isLinkSharingEnabled() { - // Check if the shareAPI is enabled - if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') { - return false; - } - // Check whether public sharing is enabled - if($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') { - return false; - } - - return true; - } } diff --git a/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php b/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php index 174abbb6f60..27e13bc8ced 100644 --- a/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php +++ b/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php @@ -33,6 +33,7 @@ use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\IPreview; use OCP\IRequest; +use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; @@ -60,26 +61,27 @@ class PublicPreviewControllerTest extends TestCase { 'files_sharing', $this->createMock(IRequest::class), $this->shareManager, + $this->createMock(ISession::class), $this->previewManager ); } public function testInvalidToken() { - $res = $this->controller->getPreview('file', 10, 10, ''); + $res = $this->controller->getPreview('', 'file', 10, 10, ''); $expected = new DataResponse([], Http::STATUS_BAD_REQUEST); $this->assertEquals($expected, $res); } public function testInvalidWidth() { - $res = $this->controller->getPreview('file', 0); + $res = $this->controller->getPreview('token', 'file', 0); $expected = new DataResponse([], Http::STATUS_BAD_REQUEST); $this->assertEquals($expected, $res); } public function testInvalidHeight() { - $res = $this->controller->getPreview('file', 10, 0); + $res = $this->controller->getPreview('token', 'file', 10, 0); $expected = new DataResponse([], Http::STATUS_BAD_REQUEST); $this->assertEquals($expected, $res); @@ -90,7 +92,7 @@ class PublicPreviewControllerTest extends TestCase { ->with($this->equalTo('token')) ->willThrowException(new ShareNotFound()); - $res = $this->controller->getPreview('file', 10, 10, 'token'); + $res = $this->controller->getPreview('token', 'file', 10, 10); $expected = new DataResponse([], Http::STATUS_NOT_FOUND); $this->assertEquals($expected, $res); @@ -105,7 +107,7 @@ class PublicPreviewControllerTest extends TestCase { $share->method('getPermissions') ->willReturn(0); - $res = $this->controller->getPreview('file', 10, 10, 'token'); + $res = $this->controller->getPreview('token', 'file', 10, 10); $expected = new DataResponse([], Http::STATUS_FORBIDDEN); $this->assertEquals($expected, $res); @@ -132,7 +134,7 @@ class PublicPreviewControllerTest extends TestCase { $preview->method('getMimeType') ->willReturn('myMime'); - $res = $this->controller->getPreview('file', 10, 10, 'token', true); + $res = $this->controller->getPreview('token', 'file', 10, 10, true); $expected = new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => 'myMime']); $this->assertEquals($expected, $res); } @@ -154,7 +156,7 @@ class PublicPreviewControllerTest extends TestCase { ->with($this->equalTo('file')) ->willThrowException(new NotFoundException()); - $res = $this->controller->getPreview('file', 10, 10, 'token', true); + $res = $this->controller->getPreview('token', 'file', 10, 10, true); $expected = new DataResponse([], Http::STATUS_NOT_FOUND); $this->assertEquals($expected, $res); } @@ -186,7 +188,7 @@ class PublicPreviewControllerTest extends TestCase { $preview->method('getMimeType') ->willReturn('myMime'); - $res = $this->controller->getPreview('file', 10, 10, 'token', true); + $res = $this->controller->getPreview('token', 'file', 10, 10, true); $expected = new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => 'myMime']); $this->assertEquals($expected, $res); } diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index be99c5ee194..fb417878647 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -39,6 +39,7 @@ use OCP\AppFramework\Http\Template\ExternalShareMenuAction; use OCP\AppFramework\Http\Template\LinkMenuAction; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\Template\SimpleMenuAction; +use OCP\Files\NotFoundException; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -156,193 +157,24 @@ class ShareControllerTest extends \Test\TestCase { parent::tearDown(); } - public function testShowAuthenticateNotAuthenticated() { - $share = \OC::$server->getShareManager()->newShare(); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $response = $this->shareController->showAuthenticate('token'); - $expectedResponse = new TemplateResponse($this->appName, 'authenticate', [], 'guest'); - $this->assertEquals($expectedResponse, $response); - } - - public function testShowAuthenticateAuthenticatedForDifferentShare() { - $share = \OC::$server->getShareManager()->newShare(); - $share->setId(1); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); - $this->session->method('get')->with('public_link_authenticated')->willReturn('2'); - - $response = $this->shareController->showAuthenticate('token'); - $expectedResponse = new TemplateResponse($this->appName, 'authenticate', [], 'guest'); - $this->assertEquals($expectedResponse, $response); - } - - public function testShowAuthenticateCorrectShare() { - $share = \OC::$server->getShareManager()->newShare(); - $share->setId(1); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); - $this->session->method('get')->with('public_link_authenticated')->willReturn('1'); - - $this->urlGenerator->expects($this->once()) - ->method('linkToRoute') - ->with('files_sharing.sharecontroller.showShare', ['token' => 'token']) - ->willReturn('redirect'); - - $response = $this->shareController->showAuthenticate('token'); - $expectedResponse = new RedirectResponse('redirect'); - $this->assertEquals($expectedResponse, $response); - } - - public function testAuthenticateInvalidToken() { - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->will($this->throwException(new \OCP\Share\Exceptions\ShareNotFound())); - - $response = $this->shareController->authenticate('token', 'preview'); - $expectedResponse = new NotFoundResponse(); - $this->assertEquals($expectedResponse, $response); - } - - public function testAuthenticateValidPassword() { - $share = \OC::$server->getShareManager()->newShare(); - $share->setId(42); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $this->shareManager - ->expects($this->once()) - ->method('checkPassword') - ->with($share, 'validpassword') - ->willReturn(true); - - $this->session - ->expects($this->once()) - ->method('set') - ->with('public_link_authenticated', '42'); - - $this->urlGenerator->expects($this->once()) - ->method('linkToRoute') - ->with('files_sharing.sharecontroller.showShare', ['token'=>'token']) - ->willReturn('redirect'); - - $response = $this->shareController->authenticate('token', 'preview', 'validpassword'); - $expectedResponse = new RedirectResponse('redirect'); - $this->assertEquals($expectedResponse, $response); - } - - public function testAuthenticateValidPasswordAndDownload() { - $share = \OC::$server->getShareManager()->newShare(); - $share->setId(42); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $this->shareManager - ->expects($this->once()) - ->method('checkPassword') - ->with($share, 'validpassword') - ->willReturn(true); - - $this->session - ->expects($this->once()) - ->method('set') - ->with('public_link_authenticated', '42'); - - $this->urlGenerator->expects($this->once()) - ->method('linkToRoute') - ->with('files_sharing.sharecontroller.downloadShare', ['token'=>'token']) - ->willReturn('redirect'); - - $response = $this->shareController->authenticate('token', 'download', 'validpassword'); - $expectedResponse = new RedirectResponse('redirect'); - $this->assertEquals($expectedResponse, $response); - } - - public function testAuthenticateInvalidPassword() { - $share = \OC::$server->getShareManager()->newShare(); - $share->setNodeId(100) - ->setNodeType('file') - ->setToken('token') - ->setSharedBy('initiator') - ->setId(42); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('token') - ->willReturn($share); - - $this->shareManager - ->expects($this->once()) - ->method('checkPassword') - ->with($share, 'invalidpassword') - ->willReturn(false); - - $this->session - ->expects($this->never()) - ->method('set'); - - $hookListner = $this->getMockBuilder('Dummy')->setMethods(['access'])->getMock(); - \OCP\Util::connectHook('OCP\Share', 'share_link_access', $hookListner, 'access'); - - $hookListner->expects($this->once()) - ->method('access') - ->with($this->callback(function(array $data) { - return $data['itemType'] === 'file' && - $data['itemSource'] === 100 && - $data['uidOwner'] === 'initiator' && - $data['token'] === 'token' && - $data['errorCode'] === 403 && - $data['errorMessage'] === 'Wrong password'; - })); - - $response = $this->shareController->authenticate('token', 'preview', 'invalidpassword'); - $expectedResponse = new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest'); - $expectedResponse->throttle(); - $this->assertEquals($expectedResponse, $response); - } - public function testShowShareInvalidToken() { + $this->shareController->setToken('invalidtoken'); + $this->shareManager ->expects($this->once()) ->method('getShareByToken') ->with('invalidtoken') ->will($this->throwException(new ShareNotFound())); + $this->expectException(NotFoundException::class); + // Test without a not existing token - $response = $this->shareController->showShare('invalidtoken'); - $expectedResponse = new NotFoundResponse(); - $this->assertEquals($expectedResponse, $response); + $this->shareController->showShare(); } public function testShowShareNotAuthenticated() { + $this->shareController->setToken('validtoken'); + $share = \OC::$server->getShareManager()->newShare(); $share->setPassword('password'); @@ -352,19 +184,16 @@ class ShareControllerTest extends \Test\TestCase { ->with('validtoken') ->willReturn($share); - $this->urlGenerator->expects($this->once()) - ->method('linkToRoute') - ->with('files_sharing.sharecontroller.authenticate', ['token' => 'validtoken', 'redirect' => 'preview']) - ->willReturn('redirect'); + $this->expectException(NotFoundException::class); // Test without a not existing token - $response = $this->shareController->showShare('validtoken'); - $expectedResponse = new RedirectResponse('redirect'); - $this->assertEquals($expectedResponse, $response); + $this->shareController->showShare(); } public function testShowShare() { + $this->shareController->setToken('token'); + $owner = $this->getMockBuilder(IUser::class)->getMock(); $owner->method('getDisplayName')->willReturn('ownerDisplay'); $owner->method('getUID')->willReturn('ownerUID'); @@ -428,7 +257,7 @@ class ShareControllerTest extends \Test\TestCase { return vsprintf($text, $parameters); })); - $response = $this->shareController->showShare('token'); + $response = $this->shareController->showShare(); $sharedTmplParams = array( 'displayName' => 'ownerDisplay', 'owner' => 'ownerUID', @@ -476,6 +305,8 @@ class ShareControllerTest extends \Test\TestCase { * @expectedException \OCP\Files\NotFoundException */ public function testShowShareInvalid() { + $this->shareController->setToken('token'); + $owner = $this->getMockBuilder(IUser::class)->getMock(); $owner->method('getDisplayName')->willReturn('ownerDisplay'); $owner->method('getUID')->willReturn('ownerUID'); @@ -517,32 +348,7 @@ class ShareControllerTest extends \Test\TestCase { $this->userManager->method('get')->with('ownerUID')->willReturn($owner); - $this->shareController->showShare('token'); - } - - public function testDownloadShare() { - $share = $this->getMockBuilder(IShare::class)->getMock(); - $share->method('getPassword')->willReturn('password'); - $share - ->expects($this->once()) - ->method('getPermissions') - ->willReturn(\OCP\Constants::PERMISSION_READ); - - $this->shareManager - ->expects($this->once()) - ->method('getShareByToken') - ->with('validtoken') - ->willReturn($share); - - $this->urlGenerator->expects($this->once()) - ->method('linkToRoute') - ->with('files_sharing.sharecontroller.authenticate', ['token' => 'validtoken', 'redirect' => 'download']) - ->willReturn('redirect'); - - // Test with a password protected share and no authentication - $response = $this->shareController->downloadShare('validtoken'); - $expectedResponse = new RedirectResponse('redirect'); - $this->assertEquals($expectedResponse, $response); + $this->shareController->showShare(); } public function testDownloadShareWithCreateOnlyShare() { diff --git a/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php b/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php index d8676547a76..1fea73e6b47 100644 --- a/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php +++ b/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php @@ -98,49 +98,6 @@ class SharingCheckMiddlewareTest extends \Test\TestCase { $this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isSharingEnabled')); } - public function testIsLinkSharingEnabledWithEverythinEnabled() { - $this->config - ->expects($this->at(0)) - ->method('getAppValue') - ->with('core', 'shareapi_enabled', 'yes') - ->will($this->returnValue('yes')); - - $this->config - ->expects($this->at(1)) - ->method('getAppValue') - ->with('core', 'shareapi_allow_links', 'yes') - ->will($this->returnValue('yes')); - - $this->assertTrue(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled')); - } - - - public function testIsLinkSharingEnabledWithLinkSharingDisabled() { - $this->config - ->expects($this->at(0)) - ->method('getAppValue') - ->with('core', 'shareapi_enabled', 'yes') - ->will($this->returnValue('yes')); - - $this->config - ->expects($this->at(1)) - ->method('getAppValue') - ->with('core', 'shareapi_allow_links', 'yes') - ->will($this->returnValue('no')); - - $this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled')); - } - - public function testIsLinkSharingEnabledWithSharingAPIDisabled() { - $this->config - ->expects($this->once()) - ->method('getAppValue') - ->with('core', 'shareapi_enabled', 'yes') - ->will($this->returnValue('no')); - - $this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled')); - } - public function externalSharesChecksDataProvider() { $data = []; @@ -236,57 +193,11 @@ class SharingCheckMiddlewareTest extends \Test\TestCase { ->with('files_sharing') ->will($this->returnValue(true)); - $this->config - ->expects($this->at(0)) - ->method('getAppValue') - ->with('core', 'shareapi_enabled', 'yes') - ->will($this->returnValue('yes')); - - $this->config - ->expects($this->at(1)) - ->method('getAppValue') - ->with('core', 'shareapi_allow_links', 'yes') - ->will($this->returnValue('yes')); - - $this->request->expects($this->once())->method('getParam')->with('token') - ->willReturn('token'); - $this->shareManager->expects($this->once())->method('getShareByToken') - ->with('token')->willReturn($share); - - $share->expects($this->once())->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK); - $controller = $this->createMock(ShareController::class); $this->sharingCheckMiddleware->beforeController($controller, 'myMethod'); } - /** - * @expectedException \OCP\Files\NotFoundException - * @expectedExceptionMessage Link sharing is disabled - */ - public function testBeforeControllerWithShareControllerWithSharingEnabledAPIDisabled() { - - $share = $this->createMock(IShare::class); - - $this->appManager - ->expects($this->once()) - ->method('isEnabledForUser') - ->with('files_sharing') - ->will($this->returnValue(true)); - - $controller = $this->createMock(ShareController::class); - - $this->request->expects($this->once())->method('getParam')->with('token') - ->willReturn('token'); - $this->shareManager->expects($this->once())->method('getShareByToken') - ->with('token')->willReturn($share); - - $share->expects($this->once())->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK); - - - $this->sharingCheckMiddleware->beforeController($controller, 'myMethod'); - } - /** * @expectedException \OCP\Files\NotFoundException * @expectedExceptionMessage Sharing is disabled. diff --git a/apps/files_sharing/css/authenticate.css b/core/css/publicshareauth.css similarity index 100% rename from apps/files_sharing/css/authenticate.css rename to core/css/publicshareauth.css diff --git a/apps/files_sharing/js/authenticate.js b/core/js/publicshareauth.js similarity index 100% rename from apps/files_sharing/js/authenticate.js rename to core/js/publicshareauth.js diff --git a/apps/files_sharing/templates/authenticate.php b/core/templates/publicshareauth.php similarity index 91% rename from apps/files_sharing/templates/authenticate.php rename to core/templates/publicshareauth.php index 6f270c2851a..adcc2853f87 100644 --- a/apps/files_sharing/templates/authenticate.php +++ b/core/templates/publicshareauth.php @@ -2,8 +2,8 @@ /** @var $_ array */ /** @var $l \OCP\IL10N */ style('core', 'guest'); - style('files_sharing', 'authenticate'); - script('files_sharing', 'authenticate'); + style('core', 'publicshareauth'); + script('core', 'publicshareauth'); ?>
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 77729886601..2426272886b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -18,6 +18,7 @@ return array( 'OCP\\App' => $baseDir . '/lib/public/App.php', 'OCP\\AppFramework\\ApiController' => $baseDir . '/lib/public/AppFramework/ApiController.php', 'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php', + 'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php', 'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php', 'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php', 'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php', @@ -56,6 +57,7 @@ return array( 'OCP\\AppFramework\\OCS\\OCSException' => $baseDir . '/lib/public/AppFramework/OCS/OCSException.php', 'OCP\\AppFramework\\OCS\\OCSForbiddenException' => $baseDir . '/lib/public/AppFramework/OCS/OCSForbiddenException.php', 'OCP\\AppFramework\\OCS\\OCSNotFoundException' => $baseDir . '/lib/public/AppFramework/OCS/OCSNotFoundException.php', + 'OCP\\AppFramework\\PublicShareController' => $baseDir . '/lib/public/AppFramework/PublicShareController.php', 'OCP\\AppFramework\\QueryException' => $baseDir . '/lib/public/AppFramework/QueryException.php', 'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => $baseDir . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php', 'OCP\\AppFramework\\Utility\\ITimeFactory' => $baseDir . '/lib/public/AppFramework/Utility/ITimeFactory.php', @@ -350,6 +352,8 @@ return array( 'OC\\AppFramework\\Http\\Request' => $baseDir . '/lib/private/AppFramework/Http/Request.php', 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => $baseDir . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', 'OC\\AppFramework\\Middleware\\OCSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index be9c71d8246..26a38a29984 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -48,6 +48,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\App' => __DIR__ . '/../../..' . '/lib/public/App.php', 'OCP\\AppFramework\\ApiController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/ApiController.php', 'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php', + 'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php', 'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php', 'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php', 'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php', @@ -86,6 +87,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\AppFramework\\OCS\\OCSException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSException.php', 'OCP\\AppFramework\\OCS\\OCSForbiddenException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSForbiddenException.php', 'OCP\\AppFramework\\OCS\\OCSNotFoundException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSNotFoundException.php', + 'OCP\\AppFramework\\PublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/PublicShareController.php', 'OCP\\AppFramework\\QueryException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/QueryException.php', 'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php', 'OCP\\AppFramework\\Utility\\ITimeFactory' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/ITimeFactory.php', @@ -380,6 +382,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\AppFramework\\Http\\Request' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Request.php', 'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php', 'OC\\AppFramework\\Middleware\\OCSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/OCSMiddleware.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php', + 'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php', 'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php', diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index c82ac5255dd..8803ef8c47d 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -62,6 +62,7 @@ use OCP\IL10N; use OCP\ILogger; use OCP\IRequest; use OCP\IServerContainer; +use OCP\ISession; use OCP\IUserSession; use OCP\RichObjectStrings\IValidator; use OCP\Encryption\IManager; @@ -304,7 +305,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { }); $middleWares = &$this->middleWares; - $this->registerService('MiddlewareDispatcher', function($c) use (&$middleWares) { + $this->registerService('MiddlewareDispatcher', function(SimpleContainer $c) use (&$middleWares) { $dispatcher = new MiddlewareDispatcher(); $dispatcher->registerMiddleware($c[OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware::class]); $dispatcher->registerMiddleware($c['CORSMiddleware']); @@ -314,6 +315,11 @@ class DIContainer extends SimpleContainer implements IAppContainer { $dispatcher->registerMiddleware($c['TwoFactorMiddleware']); $dispatcher->registerMiddleware($c['BruteForceMiddleware']); $dispatcher->registerMiddleware($c['RateLimitingMiddleware']); + $dispatcher->registerMiddleware(new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware( + $c['Request'], + $c->query(ISession::class), + $c->query(\OCP\IConfig::class) + )); foreach($middleWares as $middleWare) { $dispatcher->registerMiddleware($c[$middleWare]); diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php new file mode 100644 index 00000000000..27e57fe9505 --- /dev/null +++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php @@ -0,0 +1,7 @@ +request = $request; + $this->session = $session; + $this->config = $config; + } + + public function beforeController($controller, $methodName) { + if (!($controller instanceof PublicShareController)) { + return; + } + + if (!$this->isLinkSharingEnabled()) { + throw new NotFoundException('Link sharing is disabled'); + } + + // We require the token parameter to be set + $token = $this->request->getParam('token'); + if ($token === null) { + throw new NotFoundException(); + } + + // Set the token + $controller->setToken($token); + + if (!$controller->isValidToken()) { + $controller->shareNotFound(); + throw new NotFoundException(); + } + + // No need to check for authentication when we try to authenticate + if ($methodName === 'authenticate' || $methodName === 'showAuthenticate') { + return; + } + + // If authentication succeeds just continue + if ($controller->isAuthenticated()) { + return; + } + + // If we can authenticate to this controller do it else we throw a 404 to not leak any info + if ($controller instanceof AuthPublicShareController) { + $this->session->set('public_link_authenticate_redirect', json_encode($this->request->getParams())); + throw new NeedAuthenticationException(); + } + + throw new NotFoundException(); + + } + + public function afterException($controller, $methodName, \Exception $exception) { + if (!($controller instanceof PublicShareController)) { + throw $exception; + } + + if ($exception instanceof NotFoundException) { + return new NotFoundResponse(); + } + + if ($controller instanceof AuthPublicShareController && $exception instanceof NeedAuthenticationException) { + return $controller->getAuthenticationRedirect($this->getFunctionForRoute($this->request->getParam('_route'))); + } + + throw $exception; + } + + private function getFunctionForRoute(string $route): string { + $tmp = explode('.', $route); + return array_pop($tmp); + } + + /** + * Check if link sharing is allowed + */ + private function isLinkSharingEnabled(): bool { + // Check if the shareAPI is enabled + if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') { + return false; + } + + // Check whether public sharing is enabled + if($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') { + return false; + } + + return true; + } +} diff --git a/lib/private/legacy/template/functions.php b/lib/private/legacy/template/functions.php index 290ffe120a3..55d3a595689 100644 --- a/lib/private/legacy/template/functions.php +++ b/lib/private/legacy/template/functions.php @@ -262,7 +262,7 @@ function preview_icon( $path ) { * @return string */ function publicPreview_icon ( $path, $token ) { - return \OC::$server->getURLGenerator()->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 't' => $token]); + return \OC::$server->getURLGenerator()->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 'token' => $token]); } /** diff --git a/lib/public/AppFramework/AuthPublicShareController.php b/lib/public/AppFramework/AuthPublicShareController.php new file mode 100644 index 00000000000..ffd2bddd24b --- /dev/null +++ b/lib/public/AppFramework/AuthPublicShareController.php @@ -0,0 +1,192 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +declare(strict_types=1); + +namespace OCP\AppFramework; + +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; + +/** + * Base controller for interactive public shares + * + * It will verify if the user is properly authenticated to the share. If not the + * user will be redirected to an authentication page. + * + * Use this for a controller that is to be called directly by a user. So the + * normal public share page for files/calendars etc. + * + * @since 14.0.0 + */ +abstract class AuthPublicShareController extends PublicShareController { + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** + * @since 14.0.0 + */ + public function __construct(string $appName, + IRequest $request, + ISession $session, + IURLGenerator $urlGenerator) { + parent::__construct($appName, $request, $session); + + $this->urlGenerator = $urlGenerator; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * Show the authentication page + * The form has to submit to the authenticate method route + * + * @since 14.0.0 + */ + public function showAuthenticate(): TemplateResponse { + return new TemplateResponse('core', 'publicshareauth', [], 'guest'); + } + + /** + * The template to show when authentication failed + * + * @since 14.0.0 + */ + protected function showAuthFailed(): TemplateResponse { + return new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest'); + } + + /** + * Verify the password + * + * @since 14.0.0 + */ + abstract protected function verifyPassword(string $password): bool; + + /** + * Function called after failed authentication + * + * You can use this to do some logging for example + * + * @since 14.0.0 + */ + protected function authFailed() { + } + + /** + * Function called after successfull authentication + * + * You can use this to do some logging for example + * + * @since 14.0.0 + */ + protected function authSucceeded() { + } + + /** + * @UseSession + * @PublicPage + * @BruteForceProtection(action=publicLinkAuth) + * + * Authenticate the share + * + * @since 14.0.0 + */ + final public function authenticate(string $password = '') { + // Already authenticated + if ($this->isAuthenticated()) { + return $this->getRedirect(); + } + + if (!$this->verifyPassword($password)) { + $this->authFailed(); + $response = $this->showAuthFailed(); + $response->throttle(); + return $response; + } + + $this->session->regenerateId(true, true); + $response = $this->getRedirect(); + + $this->session->set('public_link_authenticated_token', $this->getToken()); + $this->session->set('public_link_authenticated_password_hash', $this->getPasswordHash()); + + $this->authSucceeded(); + + return $response; + } + + /** + * Default landing page + * + * @since 14.0.0 + */ + abstract public function showShare(): TemplateResponse; + + /** + * @since 14.0.0 + */ + final public function getAuthenticationRedirect(string $redirect): RedirectResponse { + return new RedirectResponse( + $this->urlGenerator->linkToRoute($this->getRoute('showAuthenticate'), ['token' => $this->getToken(), 'redirect' => $redirect]) + ); + } + + /** + * @since 14.0.0 + */ + private function getRoute(string $function): string { + $app = strtolower($this->appName); + $class = strtolower((new \ReflectionClass($this))->getShortName()); + + return $app . '.' . $class . '.' . $function; + } + + /** + * @since 14.0.0 + */ + private function getRedirect(): RedirectResponse { + //Get all the stored redirect parameters: + $params = $this->session->get('public_link_authenticate_redirect'); + + $route = $this->getRoute('showShare'); + + if ($params === null) { + $params = [ + 'token' => $this->getToken(), + ]; + } else { + $params = json_decode($params, true); + if (isset($params['_route'])) { + $route = $params['_route']; + unset($params['_route']); + } + } + + return new RedirectResponse($this->urlGenerator->linkToRoute($route, $params)); + } +} diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php new file mode 100644 index 00000000000..d0e54a0295b --- /dev/null +++ b/lib/public/AppFramework/PublicShareController.php @@ -0,0 +1,138 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +declare(strict_types=1); + +namespace OCP\AppFramework; + +use OCP\IRequest; +use OCP\ISession; + +/** + * Base controller for public shares + * + * It will verify if the user is properly authenticated to the share. If not a 404 + * is thrown by the PublicShareMiddleware. + * + * Use this for example for a controller that is not to be called via a webbrowser + * directly. For example a PublicPreviewController. As this is not meant to be + * called by a user direclty. + * + * To show an auth page extend the AuthPublicShareController + * + * @since 14.0.0 + */ +abstract class PublicShareController extends Controller { + + /** @var ISession */ + protected $session; + + /** @var string */ + private $token; + + /** + * @since 14.0.0 + */ + public function __construct(string $appName, + IRequest $request, + ISession $session) { + parent::__construct($appName, $request); + + $this->session = $session; + } + + /** + * Middleware set the token for the request + * + * @since 14.0.0 + */ + final public function setToken(string $token) { + $this->token = $token; + } + + /** + * Get the token for this request + * + * @since 14.0.0 + */ + final public function getToken(): string { + return $this->token; + } + + /** + * Get a hash of the password for this share + * + * To ensure access is blocked when the password to a share is changed we store + * a hash of the password for this token. + * + * @since 14.0.0 + */ + abstract protected function getPasswordHash(): string; + + /** + * Is the provided token a valid token + * + * This function is already called from the middleware directly after setting the token. + * + * @since 14.0.0 + */ + abstract public function isValidToken(): bool; + + /** + * Is a share with this token password protected + * + * @since 14.0.0 + */ + abstract protected function isPasswordProtected(): bool; + + /** + * Check if a share is authenticated or not + * + * @since 14.0.0 + */ + final public function isAuthenticated(): bool { + // Always authenticated against non password protected shares + if (!$this->isPasswordProtected()) { + return true; + } + + // If we are authenticated properly + if ($this->session->get('public_link_authenticated_token') === $this->getToken() && + $this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) { + return true; + } + + // Fail by default if nothing matches + return false; + } + + /** + * Function called if the share is not found. + * + * You can use this to do some logging for example + * + * @since 14.0.0 + */ + public function shareNotFound() { + + } +} diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php index 61357142ae4..1fe12d5f42d 100644 --- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php @@ -137,7 +137,7 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { */ public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheSharedLinkIWroteDown() { PHPUnit_Framework_Assert::assertEquals( - $this->actor->getSharedNotebook()["shared link"] . "/authenticate/preview", + $this->actor->getSharedNotebook()["shared link"] . "/authenticate/showShare", $this->actor->getSession()->getCurrentUrl()); } @@ -146,7 +146,7 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { */ public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheDirectDownloadSharedLinkIWroteDown() { PHPUnit_Framework_Assert::assertEquals( - $this->actor->getSharedNotebook()["shared link"] . "/authenticate/download", + $this->actor->getSharedNotebook()["shared link"] . "/authenticate/downloadShare", $this->actor->getSession()->getCurrentUrl()); } diff --git a/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php b/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php new file mode 100644 index 00000000000..169ec82ce6d --- /dev/null +++ b/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php @@ -0,0 +1,159 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Test\AppFramework\Controller; + +use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; +use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware; +use OCP\AppFramework\AuthPublicShareController; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\PublicShareController; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; + +class AuthPublicShareControllerTest extends \Test\TestCase { + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ + private $session; + /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + + /** @var AuthPublicShareController|\PHPUnit_Framework_MockObject_MockObject */ + private $controller; + + + protected function setUp() { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->session = $this->createMock(ISession::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + + $this->controller = $this->getMockBuilder(AuthPublicShareController::class) + ->setConstructorArgs([ + 'app', + $this->request, + $this->session, + $this->urlGenerator + ])->setMethods([ + 'authFailed', + 'getPasswordHash', + 'isAuthenticated', + 'isPasswordProtected', + 'isValidToken', + 'showShare', + 'verifyPassword' + ])->getMock(); + } + + public function testShowAuthenticate() { + $expects = new TemplateResponse('core', 'publicshareauth', [], 'guest'); + + $this->assertEquals($expects, $this->controller->showAuthenticate()); + } + + public function testAuthenticateAuthenticated() { + $this->controller->method('isAuthenticated') + ->willReturn(true); + + $this->controller->setToken('myToken'); + + $this->session->method('get') + ->willReturnMap(['public_link_authenticate_redirect', ['foo' => 'bar']]); + + $this->urlGenerator->method('linkToRoute') + ->willReturn('myLink!'); + + $result = $this->controller->authenticate('password'); + $this->assertInstanceOf(RedirectResponse::class, $result); + $this->assertSame('myLink!', $result->getRedirectURL()); + } + + public function testAuthenticateInvalidPassword() { + $this->controller->setToken('token'); + $this->controller->method('isPasswordProtected') + ->willReturn(true); + + $this->controller->method('verifyPassword') + ->with('password') + ->willReturn(false); + + $this->controller->expects($this->once()) + ->method('authFailed'); + + $expects = new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest'); + $expects->throttle(); + + $result = $this->controller->authenticate('password'); + + $this->assertEquals($expects, $result); + } + + public function testAuthenticateValidPassword() { + $this->controller->setToken('token'); + $this->controller->method('isPasswordProtected') + ->willReturn(true); + $this->controller->method('verifyPassword') + ->with('password') + ->willReturn(true); + $this->controller->method('getPasswordHash') + ->willReturn('hash'); + + $this->session->expects($this->once()) + ->method('regenerateId'); + $this->session->method('get') + ->willReturnMap(['public_link_authenticate_redirect', ['foo' => 'bar']]); + + $tokenSet = false; + $hashSet = false; + $this->session + ->method('set') + ->will($this->returnCallback(function($key, $value) use (&$tokenSet, &$hashSet) { + if ($key === 'public_link_authenticated_token' && $value === 'token') { + $tokenSet = true; + return true; + } + if ($key === 'public_link_authenticated_password_hash' && $value === 'hash') { + $hashSet = true; + return true; + } + return false; + })); + + $this->urlGenerator->method('linkToRoute') + ->willReturn('myLink!'); + + $result = $this->controller->authenticate('password'); + $this->assertInstanceOf(RedirectResponse::class, $result); + $this->assertSame('myLink!', $result->getRedirectURL()); + $this->assertTrue($tokenSet); + $this->assertTrue($hashSet); + } +} diff --git a/tests/lib/AppFramework/Controller/PublicShareControllerTest.php b/tests/lib/AppFramework/Controller/PublicShareControllerTest.php new file mode 100644 index 00000000000..eff7563cc4f --- /dev/null +++ b/tests/lib/AppFramework/Controller/PublicShareControllerTest.php @@ -0,0 +1,102 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Test\AppFramework\Controller; + +use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; +use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware; +use OCP\AppFramework\AuthPublicShareController; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\PublicShareController; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; + + +class PublicShareControllerTest extends \Test\TestCase { + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ + private $session; + + /** @var PublicShareController|\PHPUnit_Framework_MockObject_MockObject */ + private $controller; + + + protected function setUp() { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->session = $this->createMock(ISession::class); + + $this->controller = $this->getMockBuilder(PublicShareController::class) + ->setConstructorArgs([ + 'app', + $this->request, + $this->session + ])->getMock(); + } + + public function testGetToken() { + $this->controller->setToken('test'); + $this->assertEquals('test', $this->controller->getToken()); + } + + public function dataIsAuthenticated() { + return [ + [false, 'token1', 'token1', 'hash1', 'hash1', true], + [false, 'token1', 'token1', 'hash1', 'hash2', true], + [false, 'token1', 'token2', 'hash1', 'hash1', true], + [false, 'token1', 'token2', 'hash1', 'hash2', true], + [ true, 'token1', 'token1', 'hash1', 'hash1', true], + [ true, 'token1', 'token1', 'hash1', 'hash2', false], + [ true, 'token1', 'token2', 'hash1', 'hash1', false], + [ true, 'token1', 'token2', 'hash1', 'hash2', false], + ]; + } + + /** + * @dataProvider dataIsAuthenticated + */ + public function testIsAuthenticatedNotPasswordProtected(bool $protected, string $token1, string $token2, string $hash1, string $hash2, bool $expected) { + $this->controller->method('isPasswordProtected') + ->willReturn($protected); + + $this->session->method('get') + ->willReturnMap([ + ['public_link_authenticated_token', $token1], + ['public_link_authenticated_password_hash', $hash1], + ]); + + $this->controller->setToken($token2); + $this->controller->method('getPasswordHash') + ->willReturn($hash2); + + $this->assertEquals($expected, $this->controller->isAuthenticated()); + } + +} diff --git a/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php b/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php new file mode 100644 index 00000000000..de610100c2a --- /dev/null +++ b/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php @@ -0,0 +1,287 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Test\AppFramework\Middleware\PublicShare; + +use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; +use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware; +use OCP\AppFramework\AuthPublicShareController; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\PublicShareController; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; + + +class PublicShareMiddlewareTest extends \Test\TestCase { + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */ + private $session; + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var PublicShareMiddleware */ + private $middleware; + + + protected function setUp() { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->session = $this->createMock(ISession::class); + $this->config = $this->createMock(IConfig::class); + + $this->middleware = new PublicShareMiddleware( + $this->request, + $this->session, + $this->config + ); + } + + public function testBeforeControllerNoPublicShareController() { + $controller = $this->createMock(Controller::class); + + $this->middleware->beforeController($controller, 'method'); + $this->assertTrue(true); + } + + public function dataShareApi() { + return [ + ['no', 'no',], + ['no', 'yes',], + ['yes', 'no',], + ]; + } + + /** + * @dataProvider dataShareApi + */ + public function testBeforeControllerShareApiDisabled(string $shareApi, string $shareLinks) { + $controller = $this->createMock(PublicShareController::class); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', $shareApi], + ['core', 'shareapi_allow_links', 'yes', $shareLinks], + ]); + + $this->expectException(NotFoundException::class); + $this->middleware->beforeController($controller, 'mehod'); + } + + public function testBeforeControllerNoTokenParam() { + $controller = $this->createMock(PublicShareController::class); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->expectException(NotFoundException::class); + $this->middleware->beforeController($controller, 'mehod'); + } + + public function testBeforeControllerInvalidToken() { + $controller = $this->createMock(PublicShareController::class); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->request->method('getParam') + ->with('token', null) + ->willReturn('myToken'); + + $controller->method('isValidToken') + ->willReturn(false); + $controller->expects($this->once()) + ->method('shareNotFound'); + + $this->expectException(NotFoundException::class); + $this->middleware->beforeController($controller, 'mehod'); + } + + public function testBeforeControllerValidTokenNotAuthenticated() { + $controller = $this->getMockBuilder(PublicShareController::class) + ->setConstructorArgs(['app', $this->request, $this->session]) + ->getMock(); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->request->method('getParam') + ->with('token', null) + ->willReturn('myToken'); + + $controller->method('isValidToken') + ->willReturn(true); + + $controller->method('isPasswordProtected') + ->willReturn(true); + + $this->expectException(NotFoundException::class); + $this->middleware->beforeController($controller, 'mehod'); + } + + public function testBeforeControllerValidTokenAuthenticateMethod() { + $controller = $this->getMockBuilder(PublicShareController::class) + ->setConstructorArgs(['app', $this->request, $this->session]) + ->getMock(); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->request->method('getParam') + ->with('token', null) + ->willReturn('myToken'); + + $controller->method('isValidToken') + ->willReturn(true); + + $controller->method('isPasswordProtected') + ->willReturn(true); + + $this->middleware->beforeController($controller, 'authenticate'); + $this->assertTrue(true); + } + + public function testBeforeControllerValidTokenShowAuthenticateMethod() { + $controller = $this->getMockBuilder(PublicShareController::class) + ->setConstructorArgs(['app', $this->request, $this->session]) + ->getMock(); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->request->method('getParam') + ->with('token', null) + ->willReturn('myToken'); + + $controller->method('isValidToken') + ->willReturn(true); + + $controller->method('isPasswordProtected') + ->willReturn(true); + + $this->middleware->beforeController($controller, 'showAuthenticate'); + $this->assertTrue(true); + } + + public function testBeforeControllerAuthPublicShareController() { + $controller = $this->getMockBuilder(AuthPublicShareController::class) + ->setConstructorArgs(['app', $this->request, $this->session, $this->createMock(IURLGenerator::class)]) + ->getMock(); + + $this->config->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]); + + $this->request->method('getParam') + ->with('token', null) + ->willReturn('myToken'); + + $controller->method('isValidToken') + ->willReturn(true); + + $controller->method('isPasswordProtected') + ->willReturn(true); + + $this->session->expects($this->once()) + ->method('set') + ->with('public_link_authenticate_redirect', '[]'); + + $this->expectException(NeedAuthenticationException::class); + $this->middleware->beforeController($controller, 'method'); + } + + public function testAfterExceptionNoPublicShareController() { + $controller = $this->createMock(Controller::class); + $exception = new \Exception(); + + try { + $this->middleware->afterException($controller, 'method', $exception); + } catch (\Exception $e) { + $this->assertEquals($exception, $e); + } + } + + public function testAfterExceptionPublicShareControllerNotFoundException() { + $controller = $this->createMock(PublicShareController::class); + $exception = new NotFoundException(); + + $result = $this->middleware->afterException($controller, 'method', $exception); + $this->assertInstanceOf(NotFoundResponse::class, $result); + } + + public function testAfterExceptionPublicShareController() { + $controller = $this->createMock(PublicShareController::class); + $exception = new \Exception(); + + try { + $this->middleware->afterException($controller, 'method', $exception); + } catch (\Exception $e) { + $this->assertEquals($exception, $e); + } + } + + public function testAfterExceptionAuthPublicShareController() { + $controller = $this->getMockBuilder(AuthPublicShareController::class) + ->setConstructorArgs([ + 'app', + $this->request, + $this->session, + $this->createMock(IURLGenerator::class), + ])->getMock(); + $controller->setToken('token'); + + $exception = new NeedAuthenticationException(); + + $this->request->method('getParam') + ->with('_route') + ->willReturn('my.route'); + + $result = $this->middleware->afterException($controller, 'method', $exception); + $this->assertInstanceOf(RedirectResponse::class, $result); + } + + +}