Merge pull request #9518 from nextcloud/feature/5986/public_share_controller_middleware
Public share middleware & controllerpull/9951/head
commit
8ebc3d90a0
@ -0,0 +1,7 @@ |
||||
<?php |
||||
|
||||
namespace OC\AppFramework\Middleware\PublicShare\Exceptions; |
||||
|
||||
class NeedAuthenticationException extends \Exception { |
||||
|
||||
} |
@ -0,0 +1,112 @@ |
||||
<?php |
||||
|
||||
namespace OC\AppFramework\Middleware\PublicShare; |
||||
|
||||
use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; |
||||
use OCP\AppFramework\AuthPublicShareController; |
||||
use OCP\AppFramework\Http\NotFoundResponse; |
||||
use OCP\AppFramework\Http\Response; |
||||
use OCP\AppFramework\Middleware; |
||||
use OCP\AppFramework\PublicShareController; |
||||
use OCP\Files\NotFoundException; |
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use OCP\ISession; |
||||
|
||||
class PublicShareMiddleware extends Middleware { |
||||
/** @var IRequest */ |
||||
private $request; |
||||
|
||||
/** @var ISession */ |
||||
private $session; |
||||
|
||||
/** @var IConfig */ |
||||
private $config; |
||||
|
||||
public function __construct(IRequest $request, ISession $session, IConfig $config) { |
||||
$this->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; |
||||
} |
||||
} |
@ -0,0 +1,192 @@ |
||||
<?php |
||||
/** |
||||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
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)); |
||||
} |
||||
} |
@ -0,0 +1,138 @@ |
||||
<?php |
||||
/** |
||||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
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() { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
<?php |
||||
/** |
||||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
namespace 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); |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
<?php |
||||
/** |
||||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
namespace 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()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,287 @@ |
||||
<?php |
||||
/** |
||||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
namespace 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); |
||||
} |
||||
|
||||
|
||||
} |
Loading…
Reference in new issue