2013-08-17 09:16:48 +00:00
|
|
|
<?php
|
|
|
|
/**
|
2015-11-28 10:06:46 +00:00
|
|
|
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
|
|
|
* @author Lukas Reschke <lukas@owncloud.com>
|
2013-08-17 09:16:48 +00:00
|
|
|
*
|
2015-11-28 10:06:46 +00:00
|
|
|
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
|
|
|
* @license AGPL-3.0
|
2013-08-17 09:16:48 +00:00
|
|
|
*
|
2015-11-28 10:06:46 +00:00
|
|
|
* This code is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
|
|
* as published by the Free Software Foundation.
|
2013-08-17 09:16:48 +00:00
|
|
|
*
|
2015-11-28 10:06:46 +00:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
2013-08-17 09:16:48 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-11-28 10:06:46 +00:00
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
2013-08-17 09:16:48 +00:00
|
|
|
*
|
2015-11-28 10:06:46 +00:00
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
2013-08-17 09:16:48 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2016-05-18 16:40:34 +00:00
|
|
|
namespace Test\AppFramework\Middleware\Security;
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2013-10-23 03:57:34 +00:00
|
|
|
use OC\AppFramework\Http;
|
2013-08-17 09:16:48 +00:00
|
|
|
use OC\AppFramework\Http\Request;
|
2016-04-22 13:28:48 +00:00
|
|
|
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
|
|
|
|
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
|
|
|
|
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
|
|
|
|
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
|
2015-11-28 10:06:46 +00:00
|
|
|
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
|
2016-07-20 15:37:30 +00:00
|
|
|
use OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException;
|
2016-05-18 16:40:34 +00:00
|
|
|
use OC\AppFramework\Middleware\Security\SecurityMiddleware;
|
2014-05-06 14:29:19 +00:00
|
|
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
2016-01-28 13:33:02 +00:00
|
|
|
use OC\Security\CSP\ContentSecurityPolicy;
|
2016-07-29 11:41:30 +00:00
|
|
|
use OC\Security\CSP\ContentSecurityPolicyManager;
|
2016-10-25 19:36:17 +00:00
|
|
|
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
|
2016-10-24 09:00:00 +00:00
|
|
|
use OC\Security\CSRF\CsrfToken;
|
|
|
|
use OC\Security\CSRF\CsrfTokenManager;
|
2017-10-23 21:40:17 +00:00
|
|
|
use OCP\App\IAppManager;
|
2016-07-29 11:41:30 +00:00
|
|
|
use OCP\AppFramework\Controller;
|
2016-09-15 10:12:30 +00:00
|
|
|
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
2014-04-20 14:12:46 +00:00
|
|
|
use OCP\AppFramework\Http\RedirectResponse;
|
2013-08-20 22:41:20 +00:00
|
|
|
use OCP\AppFramework\Http\JSONResponse;
|
2016-09-15 10:12:30 +00:00
|
|
|
use OCP\AppFramework\Http\Response;
|
2015-11-28 10:06:46 +00:00
|
|
|
use OCP\AppFramework\Http\TemplateResponse;
|
2016-09-15 10:12:30 +00:00
|
|
|
use OCP\IConfig;
|
2018-02-26 14:32:17 +00:00
|
|
|
use OCP\IL10N;
|
2016-07-29 11:41:30 +00:00
|
|
|
use OCP\ILogger;
|
|
|
|
use OCP\INavigationManager;
|
|
|
|
use OCP\IRequest;
|
|
|
|
use OCP\IURLGenerator;
|
2016-09-15 10:12:30 +00:00
|
|
|
use OCP\Security\ISecureRandom;
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2014-11-10 22:30:38 +00:00
|
|
|
class SecurityMiddlewareTest extends \Test\TestCase {
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var SecurityMiddleware|\PHPUnit_Framework_MockObject_MockObject */
|
2013-08-17 09:16:48 +00:00
|
|
|
private $middleware;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject */
|
2013-08-17 09:16:48 +00:00
|
|
|
private $controller;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var SecurityException */
|
2013-08-17 09:16:48 +00:00
|
|
|
private $secException;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var SecurityException */
|
2013-08-17 09:16:48 +00:00
|
|
|
private $secAjaxException;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
2013-08-17 09:16:48 +00:00
|
|
|
private $request;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var ControllerMethodReflector */
|
2014-05-06 14:29:19 +00:00
|
|
|
private $reader;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
|
2014-05-28 00:12:01 +00:00
|
|
|
private $logger;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var INavigationManager|\PHPUnit_Framework_MockObject_MockObject */
|
2014-05-28 00:12:01 +00:00
|
|
|
private $navigationManager;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
|
2014-05-28 00:12:01 +00:00
|
|
|
private $urlGenerator;
|
2016-07-29 11:41:30 +00:00
|
|
|
/** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */
|
2016-01-28 13:33:02 +00:00
|
|
|
private $contentSecurityPolicyManager;
|
2016-10-24 09:00:00 +00:00
|
|
|
/** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */
|
|
|
|
private $csrfTokenManager;
|
2016-10-25 19:36:17 +00:00
|
|
|
/** @var ContentSecurityPolicyNonceManager|\PHPUnit_Framework_MockObject_MockObject */
|
|
|
|
private $cspNonceManager;
|
2017-10-23 21:40:17 +00:00
|
|
|
/** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */
|
|
|
|
private $appManager;
|
2018-02-26 14:32:17 +00:00
|
|
|
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
|
|
|
|
private $l10n;
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2014-11-10 22:30:38 +00:00
|
|
|
protected function setUp() {
|
|
|
|
parent::setUp();
|
|
|
|
|
2016-09-15 10:12:30 +00:00
|
|
|
$this->controller = $this->createMock(Controller::class);
|
2014-05-06 14:29:19 +00:00
|
|
|
$this->reader = new ControllerMethodReflector();
|
2016-09-15 10:12:30 +00:00
|
|
|
$this->logger = $this->createMock(ILogger::class);
|
|
|
|
$this->navigationManager = $this->createMock(INavigationManager::class);
|
|
|
|
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
|
|
|
$this->request = $this->createMock(IRequest::class);
|
|
|
|
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
|
2016-10-24 09:00:00 +00:00
|
|
|
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
|
2016-10-25 19:36:17 +00:00
|
|
|
$this->cspNonceManager = $this->createMock(ContentSecurityPolicyNonceManager::class);
|
2017-10-23 21:40:17 +00:00
|
|
|
$this->appManager = $this->createMock(IAppManager::class);
|
2018-02-26 14:32:17 +00:00
|
|
|
$this->l10n = $this->createMock(IL10N::class);
|
2017-10-23 21:40:17 +00:00
|
|
|
$this->appManager->expects($this->any())
|
|
|
|
->method('isEnabledForUser')
|
|
|
|
->willReturn(true);
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->middleware = $this->getMiddleware(true, true);
|
2013-08-17 09:16:48 +00:00
|
|
|
$this->secException = new SecurityException('hey', false);
|
|
|
|
$this->secAjaxException = new SecurityException('hey', true);
|
|
|
|
}
|
|
|
|
|
2015-11-28 10:06:46 +00:00
|
|
|
/**
|
|
|
|
* @param bool $isLoggedIn
|
|
|
|
* @param bool $isAdminUser
|
|
|
|
* @return SecurityMiddleware
|
|
|
|
*/
|
|
|
|
private function getMiddleware($isLoggedIn, $isAdminUser) {
|
2014-05-28 00:12:01 +00:00
|
|
|
return new SecurityMiddleware(
|
|
|
|
$this->request,
|
|
|
|
$this->reader,
|
|
|
|
$this->navigationManager,
|
|
|
|
$this->urlGenerator,
|
|
|
|
$this->logger,
|
2014-11-14 16:20:51 +00:00
|
|
|
'files',
|
2014-05-28 00:12:01 +00:00
|
|
|
$isLoggedIn,
|
2016-01-28 13:33:02 +00:00
|
|
|
$isAdminUser,
|
2016-10-24 09:00:00 +00:00
|
|
|
$this->contentSecurityPolicyManager,
|
2016-10-25 19:36:17 +00:00
|
|
|
$this->csrfTokenManager,
|
2017-10-23 21:40:17 +00:00
|
|
|
$this->cspNonceManager,
|
2018-02-26 14:32:17 +00:00
|
|
|
$this->appManager,
|
|
|
|
$this->l10n
|
2014-05-28 00:12:01 +00:00
|
|
|
);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testSetNavigationEntry(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->navigationManager->expects($this->once())
|
|
|
|
->method('setActiveEntry')
|
2014-11-14 16:20:51 +00:00
|
|
|
->with($this->equalTo('files'));
|
2014-05-28 00:12:01 +00:00
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-19 08:31:54 +00:00
|
|
|
/**
|
|
|
|
* @param string $method
|
|
|
|
* @param string $test
|
|
|
|
*/
|
2013-08-17 09:16:48 +00:00
|
|
|
private function ajaxExceptionStatus($method, $test, $status) {
|
2014-05-28 00:12:01 +00:00
|
|
|
$isLoggedIn = false;
|
|
|
|
$isAdminUser = false;
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2013-08-20 19:21:21 +00:00
|
|
|
// isAdminUser requires isLoggedIn call to return true
|
|
|
|
if ($test === 'isAdminUser') {
|
2014-05-28 00:12:01 +00:00
|
|
|
$isLoggedIn = true;
|
2013-08-20 19:21:21 +00:00
|
|
|
}
|
|
|
|
|
2014-05-28 00:12:01 +00:00
|
|
|
$sec = $this->getMiddleware($isLoggedIn, $isAdminUser);
|
2013-08-17 09:16:48 +00:00
|
|
|
|
|
|
|
try {
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->reader->reflect(__CLASS__, $method);
|
2017-07-26 08:50:39 +00:00
|
|
|
$sec->beforeController($this->controller, $method);
|
2013-08-17 09:16:48 +00:00
|
|
|
} catch (SecurityException $ex){
|
|
|
|
$this->assertEquals($status, $ex->getCode());
|
|
|
|
}
|
2014-05-28 13:23:57 +00:00
|
|
|
|
|
|
|
// add assertion if everything should work fine otherwise phpunit will
|
|
|
|
// complain
|
|
|
|
if ($status === 0) {
|
2018-01-25 10:23:12 +00:00
|
|
|
$this->addToAssertionCount(1);
|
2014-05-28 13:23:57 +00:00
|
|
|
}
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testAjaxStatusLoggedInCheck() {
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'isLoggedIn',
|
|
|
|
Http::STATUS_UNAUTHORIZED
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testAjaxNotAdminCheck() {
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'isAdminUser',
|
|
|
|
Http::STATUS_FORBIDDEN
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testAjaxStatusCSRFCheck() {
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'passesCSRFCheck',
|
|
|
|
Http::STATUS_PRECONDITION_FAILED
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testAjaxStatusAllGood() {
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'isLoggedIn',
|
|
|
|
0
|
|
|
|
);
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'isAdminUser',
|
|
|
|
0
|
|
|
|
);
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'isSubAdminUser',
|
|
|
|
0
|
|
|
|
);
|
|
|
|
$this->ajaxExceptionStatus(
|
2014-05-28 13:23:57 +00:00
|
|
|
__FUNCTION__,
|
2013-08-17 09:16:48 +00:00
|
|
|
'passesCSRFCheck',
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-08-20 19:21:21 +00:00
|
|
|
|
2013-08-17 09:16:48 +00:00
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testNoChecks(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->request->expects($this->never())
|
2016-07-20 15:37:30 +00:00
|
|
|
->method('passesCSRFCheck')
|
|
|
|
->will($this->returnValue(false));
|
2014-05-28 00:12:01 +00:00
|
|
|
|
|
|
|
$sec = $this->getMiddleware(false, false);
|
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$sec->beforeController($this->controller, __FUNCTION__);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-19 08:31:54 +00:00
|
|
|
/**
|
|
|
|
* @param string $method
|
|
|
|
* @param string $expects
|
|
|
|
*/
|
2013-08-17 09:16:48 +00:00
|
|
|
private function securityCheck($method, $expects, $shouldFail=false){
|
2013-08-20 19:21:21 +00:00
|
|
|
// admin check requires login
|
|
|
|
if ($expects === 'isAdminUser') {
|
2014-05-28 00:12:01 +00:00
|
|
|
$isLoggedIn = true;
|
|
|
|
$isAdminUser = !$shouldFail;
|
|
|
|
} else {
|
|
|
|
$isLoggedIn = !$shouldFail;
|
|
|
|
$isAdminUser = false;
|
2013-08-20 19:21:21 +00:00
|
|
|
}
|
|
|
|
|
2014-05-28 00:12:01 +00:00
|
|
|
$sec = $this->getMiddleware($isLoggedIn, $isAdminUser);
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2015-11-28 10:06:46 +00:00
|
|
|
if($shouldFail) {
|
2018-01-24 17:10:16 +00:00
|
|
|
$this->expectException(SecurityException::class);
|
2013-08-20 19:21:21 +00:00
|
|
|
} else {
|
2018-01-25 10:23:12 +00:00
|
|
|
$this->addToAssertionCount(1);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->reader->reflect(__CLASS__, $method);
|
2017-07-26 08:50:39 +00:00
|
|
|
$sec->beforeController($this->controller, $method);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
2015-11-28 10:06:46 +00:00
|
|
|
* @expectedException \OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testCsrfCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->request->expects($this->once())
|
2013-10-07 09:25:50 +00:00
|
|
|
->method('passesCSRFCheck')
|
|
|
|
->will($this->returnValue(false));
|
2016-07-20 15:37:30 +00:00
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->will($this->returnValue(true));
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2013-10-07 09:25:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
|
|
|
*/
|
|
|
|
public function testNoCsrfCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->request->expects($this->never())
|
2013-10-07 09:25:50 +00:00
|
|
|
->method('passesCSRFCheck')
|
|
|
|
->will($this->returnValue(false));
|
|
|
|
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 15:37:30 +00:00
|
|
|
/**
|
|
|
|
* @PublicPage
|
|
|
|
*/
|
|
|
|
public function testPassesCsrfCheck(){
|
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesCSRFCheck')
|
|
|
|
->will($this->returnValue(true));
|
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->will($this->returnValue(true));
|
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2016-07-20 15:37:30 +00:00
|
|
|
}
|
2013-08-17 09:16:48 +00:00
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @PublicPage
|
2016-07-20 15:37:30 +00:00
|
|
|
* @expectedException \OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testFailCsrfCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->request->expects($this->once())
|
2013-10-07 09:25:50 +00:00
|
|
|
->method('passesCSRFCheck')
|
2016-07-20 15:37:30 +00:00
|
|
|
->will($this->returnValue(false));
|
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
2013-10-07 09:25:50 +00:00
|
|
|
->will($this->returnValue(true));
|
|
|
|
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 15:37:30 +00:00
|
|
|
/**
|
|
|
|
* @PublicPage
|
|
|
|
* @StrictCookieRequired
|
|
|
|
* @expectedException \OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException
|
|
|
|
*/
|
|
|
|
public function testStrictCookieRequiredCheck() {
|
|
|
|
$this->request->expects($this->never())
|
|
|
|
->method('passesCSRFCheck');
|
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->will($this->returnValue(false));
|
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2016-07-20 15:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
|
|
|
*/
|
|
|
|
public function testNoStrictCookieRequiredCheck() {
|
|
|
|
$this->request->expects($this->never())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->will($this->returnValue(false));
|
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2016-07-20 15:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @PublicPage
|
|
|
|
* @NoCSRFRequired
|
|
|
|
* @StrictCookieRequired
|
|
|
|
*/
|
|
|
|
public function testPassesStrictCookieRequiredCheck() {
|
|
|
|
$this->request
|
|
|
|
->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->willReturn(true);
|
|
|
|
|
|
|
|
$this->reader->reflect(__CLASS__, __FUNCTION__);
|
2017-07-26 08:50:39 +00:00
|
|
|
$this->middleware->beforeController($this->controller, __FUNCTION__);
|
2016-07-20 15:37:30 +00:00
|
|
|
}
|
2013-08-17 09:16:48 +00:00
|
|
|
|
2016-07-29 11:41:30 +00:00
|
|
|
public function dataCsrfOcsController() {
|
|
|
|
$controller = $this->getMockBuilder('OCP\AppFramework\Controller')
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
$ocsController = $this->getMockBuilder('OCP\AppFramework\OCSController')
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
return [
|
2018-01-25 15:10:38 +00:00
|
|
|
[$controller, false, false, true],
|
|
|
|
[$controller, false, true, true],
|
|
|
|
[$controller, true, false, true],
|
|
|
|
[$controller, true, true, true],
|
|
|
|
|
|
|
|
[$ocsController, false, false, true],
|
|
|
|
[$ocsController, false, true, false],
|
|
|
|
[$ocsController, true, false, false],
|
|
|
|
[$ocsController, true, true, false],
|
2016-07-29 11:41:30 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider dataCsrfOcsController
|
|
|
|
* @param Controller $controller
|
|
|
|
* @param bool $hasOcsApiHeader
|
2018-01-25 15:10:38 +00:00
|
|
|
* @param bool $hasBearerAuth
|
2016-07-29 11:41:30 +00:00
|
|
|
* @param bool $exception
|
|
|
|
*/
|
2018-01-25 15:10:38 +00:00
|
|
|
public function testCsrfOcsController(Controller $controller, bool $hasOcsApiHeader, bool $hasBearerAuth, bool $exception) {
|
2016-07-29 11:41:30 +00:00
|
|
|
$this->request
|
|
|
|
->method('getHeader')
|
2018-01-25 15:10:38 +00:00
|
|
|
->will(self::returnCallback(function ($header) use ($hasOcsApiHeader, $hasBearerAuth) {
|
|
|
|
if ($header === 'OCS-APIREQUEST' && $hasOcsApiHeader) {
|
|
|
|
return 'true';
|
|
|
|
}
|
|
|
|
if ($header === 'Authorization' && $hasBearerAuth) {
|
|
|
|
return 'Bearer TOKEN!';
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}));
|
2016-07-29 11:41:30 +00:00
|
|
|
$this->request->expects($this->once())
|
|
|
|
->method('passesStrictCookieCheck')
|
|
|
|
->willReturn(true);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$this->middleware->beforeController($controller, 'foo');
|
|
|
|
$this->assertFalse($exception);
|
|
|
|
} catch (CrossSiteRequestForgeryException $e) {
|
|
|
|
$this->assertTrue($exception);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-17 09:16:48 +00:00
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @NoCSRFRequired
|
|
|
|
* @NoAdminRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testLoggedInCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->securityCheck(__FUNCTION__, 'isLoggedIn');
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @NoCSRFRequired
|
|
|
|
* @NoAdminRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testFailLoggedInCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->securityCheck(__FUNCTION__, 'isLoggedIn', true);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testIsAdminCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->securityCheck(__FUNCTION__, 'isAdminUser');
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:21:21 +00:00
|
|
|
* @NoCSRFRequired
|
2013-08-17 09:16:48 +00:00
|
|
|
*/
|
|
|
|
public function testFailIsAdminCheck(){
|
2014-05-28 00:12:01 +00:00
|
|
|
$this->securityCheck(__FUNCTION__, 'isAdminUser', true);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testAfterExceptionNotCaughtThrowsItAgain(){
|
|
|
|
$ex = new \Exception();
|
2018-01-24 17:10:16 +00:00
|
|
|
$this->expectException(\Exception::class);
|
2013-08-17 09:16:48 +00:00
|
|
|
$this->middleware->afterException($this->controller, 'test', $ex);
|
|
|
|
}
|
|
|
|
|
2015-11-28 10:06:46 +00:00
|
|
|
public function testAfterExceptionReturnsRedirectForNotLoggedInUser() {
|
2013-08-17 09:16:48 +00:00
|
|
|
$this->request = new Request(
|
2016-07-20 15:37:30 +00:00
|
|
|
[
|
|
|
|
'server' =>
|
|
|
|
[
|
|
|
|
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
2016-11-09 15:14:46 +00:00
|
|
|
'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp'
|
2016-07-20 15:37:30 +00:00
|
|
|
]
|
|
|
|
],
|
2016-09-15 10:12:30 +00:00
|
|
|
$this->createMock(ISecureRandom::class),
|
|
|
|
$this->createMock(IConfig::class)
|
2015-11-28 10:06:46 +00:00
|
|
|
);
|
|
|
|
$this->middleware = $this->getMiddleware(false, false);
|
|
|
|
$this->urlGenerator
|
2016-07-20 15:37:30 +00:00
|
|
|
->expects($this->once())
|
|
|
|
->method('linkToRoute')
|
|
|
|
->with(
|
|
|
|
'core.login.showLoginForm',
|
|
|
|
[
|
2016-11-09 15:14:46 +00:00
|
|
|
'redirect_url' => 'nextcloud/index.php/apps/specialapp',
|
2016-07-20 15:37:30 +00:00
|
|
|
]
|
|
|
|
)
|
2016-11-09 15:14:46 +00:00
|
|
|
->will($this->returnValue('http://localhost/nextcloud/index.php/login?redirect_url=nextcloud/index.php/apps/specialapp'));
|
2015-11-28 10:06:46 +00:00
|
|
|
$this->logger
|
2016-07-20 15:37:30 +00:00
|
|
|
->expects($this->once())
|
2018-01-17 14:21:56 +00:00
|
|
|
->method('logException');
|
2015-11-28 10:06:46 +00:00
|
|
|
$response = $this->middleware->afterException(
|
2016-07-20 15:37:30 +00:00
|
|
|
$this->controller,
|
|
|
|
'test',
|
|
|
|
new NotLoggedInException()
|
2015-11-28 10:06:46 +00:00
|
|
|
);
|
2016-11-09 15:14:46 +00:00
|
|
|
$expected = new RedirectResponse('http://localhost/nextcloud/index.php/login?redirect_url=nextcloud/index.php/apps/specialapp');
|
2015-11-28 10:06:46 +00:00
|
|
|
$this->assertEquals($expected , $response);
|
|
|
|
}
|
|
|
|
|
2016-07-20 15:37:30 +00:00
|
|
|
public function testAfterExceptionRedirectsToWebRootAfterStrictCookieFail() {
|
|
|
|
$this->request = new Request(
|
|
|
|
[
|
|
|
|
'server' => [
|
|
|
|
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
2016-11-09 15:14:46 +00:00
|
|
|
'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp',
|
2016-07-20 15:37:30 +00:00
|
|
|
],
|
|
|
|
],
|
2016-09-15 10:12:30 +00:00
|
|
|
$this->createMock(ISecureRandom::class),
|
|
|
|
$this->createMock(IConfig::class)
|
2016-07-20 15:37:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->middleware = $this->getMiddleware(false, false);
|
|
|
|
$response = $this->middleware->afterException(
|
|
|
|
$this->controller,
|
|
|
|
'test',
|
|
|
|
new StrictCookieMissingException()
|
|
|
|
);
|
|
|
|
|
|
|
|
$expected = new RedirectResponse(\OC::$WEBROOT);
|
|
|
|
$this->assertEquals($expected , $response);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-11-28 10:06:46 +00:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function exceptionProvider() {
|
|
|
|
return [
|
|
|
|
[
|
|
|
|
new AppNotEnabledException(),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
new CrossSiteRequestForgeryException(),
|
2015-02-09 10:41:48 +00:00
|
|
|
],
|
2015-11-28 10:06:46 +00:00
|
|
|
[
|
2018-02-26 14:32:17 +00:00
|
|
|
new NotAdminException(''),
|
2015-11-28 10:06:46 +00:00
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider exceptionProvider
|
|
|
|
* @param SecurityException $exception
|
|
|
|
*/
|
|
|
|
public function testAfterExceptionReturnsTemplateResponse(SecurityException $exception) {
|
|
|
|
$this->request = new Request(
|
2016-07-20 15:37:30 +00:00
|
|
|
[
|
|
|
|
'server' =>
|
|
|
|
[
|
|
|
|
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
2016-11-09 15:14:46 +00:00
|
|
|
'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp'
|
2016-07-20 15:37:30 +00:00
|
|
|
]
|
|
|
|
],
|
2016-09-15 10:12:30 +00:00
|
|
|
$this->createMock(ISecureRandom::class),
|
|
|
|
$this->createMock(IConfig::class)
|
2015-11-28 10:06:46 +00:00
|
|
|
);
|
|
|
|
$this->middleware = $this->getMiddleware(false, false);
|
|
|
|
$this->logger
|
2016-07-20 15:37:30 +00:00
|
|
|
->expects($this->once())
|
2018-01-17 14:21:56 +00:00
|
|
|
->method('logException');
|
2015-11-28 10:06:46 +00:00
|
|
|
$response = $this->middleware->afterException(
|
2016-07-20 15:37:30 +00:00
|
|
|
$this->controller,
|
|
|
|
'test',
|
|
|
|
$exception
|
2014-05-28 00:12:01 +00:00
|
|
|
);
|
2015-11-28 10:06:46 +00:00
|
|
|
$expected = new TemplateResponse('core', '403', ['file' => $exception->getMessage()], 'guest');
|
|
|
|
$expected->setStatus($exception->getCode());
|
|
|
|
$this->assertEquals($expected , $response);
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testAfterAjaxExceptionReturnsJSONError(){
|
|
|
|
$response = $this->middleware->afterException($this->controller, 'test',
|
2016-07-20 15:37:30 +00:00
|
|
|
$this->secAjaxException);
|
2013-08-17 09:16:48 +00:00
|
|
|
|
|
|
|
$this->assertTrue($response instanceof JSONResponse);
|
|
|
|
}
|
|
|
|
|
2016-01-28 13:33:02 +00:00
|
|
|
public function testAfterController() {
|
2016-10-25 19:36:17 +00:00
|
|
|
$this->cspNonceManager
|
2016-10-24 09:00:00 +00:00
|
|
|
->expects($this->once())
|
2016-10-25 19:36:17 +00:00
|
|
|
->method('browserSupportsCspV3')
|
2016-10-24 09:00:00 +00:00
|
|
|
->willReturn(false);
|
2016-09-15 10:12:30 +00:00
|
|
|
$response = $this->createMock(Response::class);
|
2016-01-28 13:33:02 +00:00
|
|
|
$defaultPolicy = new ContentSecurityPolicy();
|
|
|
|
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
|
|
|
|
$currentPolicy = new ContentSecurityPolicy();
|
|
|
|
$currentPolicy->addAllowedConnectDomain('currentPolicy');
|
|
|
|
$mergedPolicy = new ContentSecurityPolicy();
|
|
|
|
$mergedPolicy->addAllowedMediaDomain('mergedPolicy');
|
|
|
|
$response
|
|
|
|
->expects($this->exactly(2))
|
|
|
|
->method('getContentSecurityPolicy')
|
|
|
|
->willReturn($currentPolicy);
|
|
|
|
$this->contentSecurityPolicyManager
|
|
|
|
->expects($this->once())
|
|
|
|
->method('getDefaultPolicy')
|
|
|
|
->willReturn($defaultPolicy);
|
|
|
|
$this->contentSecurityPolicyManager
|
2016-07-20 15:37:30 +00:00
|
|
|
->expects($this->once())
|
|
|
|
->method('mergePolicies')
|
|
|
|
->with($defaultPolicy, $currentPolicy)
|
|
|
|
->willReturn($mergedPolicy);
|
2016-01-28 13:33:02 +00:00
|
|
|
$response->expects($this->once())
|
|
|
|
->method('setContentSecurityPolicy')
|
|
|
|
->with($mergedPolicy);
|
|
|
|
|
|
|
|
$this->middleware->afterController($this->controller, 'test', $response);
|
|
|
|
}
|
2016-09-15 10:12:30 +00:00
|
|
|
|
|
|
|
public function testAfterControllerEmptyCSP() {
|
|
|
|
$response = $this->createMock(Response::class);
|
|
|
|
$emptyPolicy = new EmptyContentSecurityPolicy();
|
|
|
|
$response->expects($this->any())
|
|
|
|
->method('getContentSecurityPolicy')
|
|
|
|
->willReturn($emptyPolicy);
|
|
|
|
$response->expects($this->never())
|
|
|
|
->method('setContentSecurityPolicy');
|
|
|
|
|
|
|
|
$this->middleware->afterController($this->controller, 'test', $response);
|
|
|
|
}
|
2016-10-24 09:00:00 +00:00
|
|
|
|
|
|
|
public function testAfterControllerWithContentSecurityPolicy3Support() {
|
2016-10-25 19:36:17 +00:00
|
|
|
$this->cspNonceManager
|
2016-10-24 09:00:00 +00:00
|
|
|
->expects($this->once())
|
2016-10-25 19:36:17 +00:00
|
|
|
->method('browserSupportsCspV3')
|
2016-10-24 09:00:00 +00:00
|
|
|
->willReturn(true);
|
|
|
|
$token = $this->createMock(CsrfToken::class);
|
|
|
|
$token
|
|
|
|
->expects($this->once())
|
|
|
|
->method('getEncryptedValue')
|
|
|
|
->willReturn('MyEncryptedToken');
|
|
|
|
$this->csrfTokenManager
|
|
|
|
->expects($this->once())
|
|
|
|
->method('getToken')
|
|
|
|
->willReturn($token);
|
|
|
|
$response = $this->createMock(Response::class);
|
|
|
|
$defaultPolicy = new ContentSecurityPolicy();
|
|
|
|
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
|
|
|
|
$currentPolicy = new ContentSecurityPolicy();
|
|
|
|
$currentPolicy->addAllowedConnectDomain('currentPolicy');
|
|
|
|
$mergedPolicy = new ContentSecurityPolicy();
|
|
|
|
$mergedPolicy->addAllowedMediaDomain('mergedPolicy');
|
|
|
|
$response
|
|
|
|
->expects($this->exactly(2))
|
|
|
|
->method('getContentSecurityPolicy')
|
|
|
|
->willReturn($currentPolicy);
|
|
|
|
$this->contentSecurityPolicyManager
|
|
|
|
->expects($this->once())
|
|
|
|
->method('getDefaultPolicy')
|
|
|
|
->willReturn($defaultPolicy);
|
|
|
|
$this->contentSecurityPolicyManager
|
|
|
|
->expects($this->once())
|
|
|
|
->method('mergePolicies')
|
|
|
|
->with($defaultPolicy, $currentPolicy)
|
|
|
|
->willReturn($mergedPolicy);
|
|
|
|
$response->expects($this->once())
|
|
|
|
->method('setContentSecurityPolicy')
|
|
|
|
->with($mergedPolicy);
|
|
|
|
|
|
|
|
$this->assertEquals($response, $this->middleware->afterController($this->controller, 'test', $response));
|
|
|
|
}
|
2013-08-17 09:16:48 +00:00
|
|
|
}
|