647 lines
18 KiB
PHP
647 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Nextcloud - Gallery
|
|
*
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later. See the COPYING file.
|
|
*
|
|
* @author Olivier Paroz <galleryapps@oparoz.com>
|
|
*
|
|
* @copyright Olivier Paroz 2017
|
|
*/
|
|
|
|
namespace OCA\Gallery\Tests\Middleware;
|
|
|
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
|
|
|
use OCP\Constants;
|
|
use OCP\IRequest;
|
|
use OCP\Security\IHasher;
|
|
use OCP\ISession;
|
|
use OCP\ILogger;
|
|
use OCP\IURLGenerator;
|
|
use OCP\Share;
|
|
use OCP\Share\IManager;
|
|
|
|
use OCP\AppFramework\Controller;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Utility\IControllerMethodReflector;
|
|
use OCP\AppFramework\Http\JSONResponse;
|
|
use OCP\AppFramework\Http\RedirectResponse;
|
|
use OCP\AppFramework\Http\TemplateResponse;
|
|
|
|
use OCA\Gallery\Environment\EnvironmentException;
|
|
use OCA\Gallery\Middleware\SharingCheckMiddleware;
|
|
use OCA\Gallery\Middleware\EnvCheckMiddleware;
|
|
use OCA\Gallery\Middleware\CheckException;
|
|
|
|
/**
|
|
* Class EnvCheckMiddlewareTest
|
|
*
|
|
* @package OCA\Gallery\Tests\Middleware
|
|
*/
|
|
class EnvCheckMiddlewareTest extends \OCA\Gallery\Tests\GalleryUnitTest {
|
|
|
|
/** @var IRequest */
|
|
private $request;
|
|
/** @var IHasher */
|
|
private $hasher;
|
|
/** @var ISession */
|
|
private $session;
|
|
/** @var IControllerMethodReflector */
|
|
protected $reflector;
|
|
/** @var IURLGenerator */
|
|
private $urlGenerator;
|
|
/** @var ILogger */
|
|
protected $logger;
|
|
/** @var Controller */
|
|
private $controller;
|
|
/** @var SharingCheckMiddleware */
|
|
private $middleware;
|
|
/** @var IManager */
|
|
private $shareManager;
|
|
|
|
/**
|
|
* Test set up
|
|
*/
|
|
protected function setUp() {
|
|
parent::setUp();
|
|
|
|
$this->request = $this->getMockBuilder('\OCP\IRequest')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->hasher = $this->getMockBuilder('\OCP\Security\IHasher')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->session = $this->getMockBuilder('\OCP\ISession')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->environment = $this->getMockBuilder('\OCA\Gallery\Environment\Environment')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
// We need to use a real reflector to be able to test our custom notation
|
|
$this->reflector = new ControllerMethodReflector();
|
|
$this->urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->logger = $this->getMockBuilder('\OCP\ILogger')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->controller = $this->getMockBuilder('OCP\AppFramework\Controller')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
$this->shareManager = $this->getMockBuilder('\OCP\Share\IManager')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
|
|
$this->middleware = new EnvCheckMiddleware(
|
|
$this->appName,
|
|
$this->request,
|
|
$this->hasher,
|
|
$this->session,
|
|
$this->environment,
|
|
$this->reflector,
|
|
$this->urlGenerator,
|
|
$this->shareManager,
|
|
$this->logger
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @todo Mock an environment response
|
|
*/
|
|
public function testBeforeControllerWithoutNotation() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testBeforeControllerWithPublicNotationAndInvalidToken() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
$this->mockGetTokenParam($token);
|
|
|
|
$this->mockShareManagerGetShareByTokenThrowsException($token);
|
|
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
*/
|
|
public function testBeforeControllerWithPublicNotationAndToken() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
$this->mockGetTokenAndPasswordParams($token, null);
|
|
|
|
$share = $this->mockShare(
|
|
'folder', 'tester', 'shared1', Share::SHARE_TYPE_LINK, null,
|
|
Constants::PERMISSION_READ, null
|
|
);
|
|
|
|
$this->mockShareManagerGetShareByToken($token, $share);
|
|
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
*/
|
|
public function testBeforeControllerWithPublicNotationAndTokenAndValidPassword() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
$password = 'validpassword';
|
|
$this->mockGetTokenAndPasswordParams($token, $password);
|
|
|
|
$share = $this->mockShare(
|
|
'folder', 'tester', 'shared1', Share::SHARE_TYPE_LINK, null,
|
|
Constants::PERMISSION_READ, 'validpassword'
|
|
);
|
|
|
|
$this->mockShareManagerGetShareByToken($token, $share);
|
|
$this->mockShareManagerCheckPassword($share, $password, true);
|
|
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testBeforeControllerWithPublicNotationAndTokenAndInvalidPassword() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
$password = 'NOTvalidpassword';
|
|
$this->mockGetTokenAndPasswordParams($token, $password);
|
|
|
|
$share = $this->mockShare(
|
|
'folder', 'tester', 'shared1', Share::SHARE_TYPE_LINK, null,
|
|
Constants::PERMISSION_READ, 'validpassword'
|
|
);
|
|
|
|
$this->mockShareManagerGetShareByToken($token, $share);
|
|
$this->mockShareManagerCheckPassword($share, 'NOTvalidpassword', false);
|
|
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testBeforeControllerWithPublicNotationAndNoToken() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$token = null;
|
|
$this->mockGetTokenParam($token);
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
/**
|
|
* @@Guest
|
|
*/
|
|
public function testBeforeControllerWithGuestNotation() {
|
|
$this->reflector->reflect(__CLASS__, __FUNCTION__);
|
|
|
|
$this->middleware->beforeController(__CLASS__, __FUNCTION__);
|
|
}
|
|
|
|
public function testCheckSessionAfterPasswordEntry() {
|
|
$share = $this->mockShare('file', 'tester', 'image.png');
|
|
|
|
$this->mockSessionExists((string)$share->getId());
|
|
$this->mockSessionWithShareId((string)$share->getId());
|
|
|
|
parent::invokePrivate($this->middleware, 'checkSession', [$share]);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckSessionBeforePasswordEntry() {
|
|
$share = $this->mockShare('file', 'tester', 'image.png');
|
|
|
|
$this->mockSessionExists(false);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkSession', [$share]);
|
|
}
|
|
|
|
/**
|
|
* Ids of shares do not match
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckSessionWithWrongSession() {
|
|
$share = $this->mockShare('file', 'tester', 'image.png');
|
|
|
|
$this->mockSessionExists(true);
|
|
$this->mockSessionWithShareId(99999);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkSession', [$share]);
|
|
}
|
|
|
|
public function testCheckPasswordAfterValidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_USER, 'externaluser',
|
|
Constants::PERMISSION_READ, $password
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, true);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkPassword', [$share, $password]);
|
|
}
|
|
|
|
/**
|
|
* Given password and token password don't match
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckPasswordAfterInvalidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$wrongPassword = 'Empyrion Galactic Survival';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_USER, 'externaluser',
|
|
Constants::PERMISSION_READ,
|
|
$wrongPassword
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, false);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkPassword', [$share, $password]);
|
|
}
|
|
|
|
public function testAuthenticateAfterValidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, null, Constants::PERMISSION_READ,
|
|
$password
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, true);
|
|
|
|
$this->assertTrue(
|
|
parent::invokePrivate($this->middleware, 'authenticate', [$share, $password])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given password and token password don't match
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testAuthenticateAfterInvalidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$wrongPassword = 'Empyrion Galactic Survival';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, null, Constants::PERMISSION_READ,
|
|
$wrongPassword
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, false);
|
|
|
|
parent::invokePrivate($this->middleware, 'authenticate', [$share, $password]);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testAuthenticateWithWrongLinkType() {
|
|
$password = 'Je suis une pipe';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, 'tester',
|
|
Constants::PERMISSION_READ, $password
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, false);
|
|
|
|
parent::invokePrivate($this->middleware, 'authenticate', [$share, $password]);
|
|
}
|
|
|
|
public function testCheckAuthorisationAfterValidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, 'tester',
|
|
Constants::PERMISSION_READ, $password
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, true);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkAuthorisation', [$share, $password]);
|
|
}
|
|
|
|
/**
|
|
* Given password and token password don't match
|
|
*
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckAuthorisationAfterInvalidPasswordEntry() {
|
|
$password = 'Je suis une pipe';
|
|
$wrongPassword = 'Empyrion Galactic Survival';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, 'tester',
|
|
Constants::PERMISSION_READ, $wrongPassword
|
|
);
|
|
|
|
$this->mockShareManagerCheckPassword($share, $password, false);
|
|
|
|
parent::invokePrivate($this->middleware, 'checkAuthorisation', [$share, $password]);
|
|
}
|
|
|
|
/**
|
|
* It will use the session, wich is a valid one in this case
|
|
* Other cases are in the checkSession tests
|
|
*/
|
|
public function testCheckAuthorisationWithNoPassword() {
|
|
$password = null;
|
|
$wrongPassword = 'Empyrion Galactic Survival';
|
|
$share = $this->mockShare(
|
|
'file', 'tester', 'image.png', Share::SHARE_TYPE_LINK, 'tester',
|
|
Constants::PERMISSION_READ, $wrongPassword
|
|
);
|
|
|
|
$this->mockSessionExists((string)$share->getId());
|
|
$this->mockSessionWithShareId((string)$share->getId());
|
|
parent::invokePrivate($this->middleware, 'checkAuthorisation', [$share, $password]);
|
|
}
|
|
|
|
public function testCheckItemTypeWithItemTypeSet() {
|
|
$share = $this->mockShare('folder', 'tester', 'folder1');
|
|
|
|
parent::invokePrivate($this->middleware, 'checkItemType', [$share]);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckItemTypeWithItemTypeNotSet() {
|
|
$share = $this->mockShare(null, 'tester', 'folder1');
|
|
|
|
parent::invokePrivate($this->middleware, 'checkItemType', [$share]);
|
|
}
|
|
|
|
public function testCheckShareIsValidWithValidShare() {
|
|
$share = $this->mockShare('file', 'tester', 'image.png');
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
|
|
parent::invokePrivate($this->middleware, 'checkShareIsValid', [$share, $token]);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckShareIsValidWithMissingOwner() {
|
|
$share = $this->mockShare('file', null, 'image.png');
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
|
|
parent::invokePrivate($this->middleware, 'checkShareIsValid', [$share, $token]);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Middleware\CheckException
|
|
*/
|
|
public function testCheckShareIsValidWithMissingSource() {
|
|
$share = $this->mockShare('file', 'tester', null);
|
|
|
|
$token = 'aaaabbbbccccdddd';
|
|
|
|
parent::invokePrivate($this->middleware, 'checkShareIsValid', [$share, $token]);
|
|
}
|
|
|
|
public function testAfterExceptionWithCheckExceptionAndHtmlAcceptAnd401Code() {
|
|
$message = 'fail';
|
|
$code = Http::STATUS_UNAUTHORIZED;
|
|
$exception = new CheckException($message, $code);
|
|
|
|
$template = $this->mockHtml401Response();
|
|
|
|
$response =
|
|
$this->middleware->afterException($this->controller, 'checkSession', $exception);
|
|
|
|
$this->assertEquals($template, $response);
|
|
}
|
|
|
|
public function testAfterExceptionWithCheckExceptionAndHtmlAcceptAnd404Code() {
|
|
$message = 'fail';
|
|
$code = Http::STATUS_NOT_FOUND;
|
|
$exception = new CheckException($message, $code);
|
|
|
|
$redirectUrl = '/app/error/route';
|
|
$this->mockHtml404Response($redirectUrl, $code);
|
|
|
|
$response =
|
|
$this->middleware->afterException($this->controller, 'authenticate', $exception);
|
|
|
|
$this->assertEquals($redirectUrl, $response->getRedirectURL());
|
|
$this->assertEquals(Http::STATUS_SEE_OTHER, $response->getStatus());
|
|
$this->assertEquals($message, $response->getCookies()['galleryErrorMessage']['value']);
|
|
}
|
|
|
|
public function testAfterExceptionWithCheckExceptionAndJsonAccept() {
|
|
$message = 'fail';
|
|
$code = Http::STATUS_NOT_FOUND;
|
|
$exception = new CheckException($message, $code);
|
|
|
|
$template = $this->mockJsonResponse($message, $code);
|
|
|
|
$response =
|
|
$this->middleware->afterException(
|
|
$this->controller, 'checkShareIsValid', $exception
|
|
);
|
|
|
|
$this->assertEquals($template, $response);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \OCA\Gallery\Environment\EnvironmentException
|
|
*/
|
|
public function testAfterExceptionWithNonCheckException() {
|
|
$message = 'fail';
|
|
$code = Http::STATUS_NOT_FOUND;
|
|
$exception = new EnvironmentException($message, $code);
|
|
|
|
$this->middleware->afterException($this->controller, 'checkShareIsValid', $exception);
|
|
}
|
|
|
|
/**
|
|
* Mocks ISession->exists('public_link_authenticated')
|
|
*
|
|
* @param int $shareId
|
|
*/
|
|
private function mockSessionExists($shareId) {
|
|
$this->session->expects($this->once())
|
|
->method('exists')
|
|
->with('public_link_authenticated')
|
|
->willReturn($shareId);
|
|
}
|
|
|
|
/**
|
|
* Mocks ISession->get('public_link_authenticated')
|
|
*
|
|
* @param int $shareId
|
|
*/
|
|
private function mockSessionWithShareId($shareId) {
|
|
$this->session->expects($this->once())
|
|
->method('get')
|
|
->with('public_link_authenticated')
|
|
->willReturn($shareId);
|
|
}
|
|
|
|
private function mockHtml401Response() {
|
|
$this->mockAcceptHeader('html');
|
|
$this->mockGetParams();
|
|
|
|
return new TemplateResponse($this->appName, 'authenticate', [], 'guest');
|
|
}
|
|
|
|
private function mockHtml404Response($redirectUrl, $code) {
|
|
$this->mockAcceptHeader('html');
|
|
$this->mockUrlToErrorPage($code, $redirectUrl);
|
|
}
|
|
|
|
private function mockJsonResponse($message, $code) {
|
|
$this->mockAcceptHeader('json');
|
|
$jsonData = [
|
|
'message' => $message,
|
|
'success' => false
|
|
];
|
|
|
|
return new JSONResponse($jsonData, $code);
|
|
}
|
|
|
|
/**
|
|
* Mocks IRequest->getHeader('Accept')
|
|
*
|
|
* @param string $type
|
|
*/
|
|
private function mockAcceptHeader($type) {
|
|
$this->request->expects($this->once())
|
|
->method('getHeader')
|
|
->with('Accept')
|
|
->willReturn($type);
|
|
}
|
|
|
|
/**
|
|
* Mocks IRequest->getParams()
|
|
*/
|
|
private function mockGetParams() {
|
|
$this->request->expects($this->once())
|
|
->method('getParams')
|
|
->willReturn([]);
|
|
}
|
|
|
|
/**
|
|
* Mocks IURLGenerator->linkToRoute()
|
|
*
|
|
* @param int $code
|
|
* @param string $url
|
|
*/
|
|
private function mockUrlToErrorPage($code, $url) {
|
|
$this->urlGenerator->expects($this->once())
|
|
->method('linkToRoute')
|
|
->with($this->appName . '.page.error_page', ['code' => $code])
|
|
->willReturn($url);
|
|
}
|
|
|
|
/**
|
|
* Mocks IRequest->getParam()
|
|
*
|
|
* @param string $token
|
|
* @param string $password
|
|
*/
|
|
private function mockGetTokenAndPasswordParams($token, $password = null) {
|
|
$this->request->expects($this->at(0))
|
|
->method('getParam')
|
|
->with('token')
|
|
->willReturn($token);
|
|
$this->request->expects($this->at(1))
|
|
->method('getParam')
|
|
->with('password')
|
|
->willReturn($password);
|
|
}
|
|
|
|
/**
|
|
* Mocks IRequest->getParam('token')
|
|
*
|
|
* @param string $token
|
|
*/
|
|
private function mockGetTokenParam($token) {
|
|
$this->request->expects($this->once())
|
|
->method('getParam')
|
|
->with('token')
|
|
->willReturn($token);
|
|
}
|
|
|
|
private function mockShare(
|
|
$nodeType,
|
|
$shareOwner,
|
|
$target,
|
|
$shareType = Share::SHARE_TYPE_USER,
|
|
$sharedWith = 'externaluser',
|
|
$permission = Constants::PERMISSION_READ,
|
|
$password = 'securePassword'
|
|
) {
|
|
$share = $this->getMockBuilder('OCP\Share\IShare')
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
|
|
$share->method('getId')
|
|
->willReturn(12345);
|
|
$share->method('getNodeType')
|
|
->willReturn($nodeType);
|
|
$share->method('getShareOwner')
|
|
->willReturn($shareOwner);
|
|
$share->method('getTarget')
|
|
->willReturn($target);
|
|
$share->method('getShareType')
|
|
->willReturn($shareType);
|
|
$share->method('getSharedWith')
|
|
->willReturn($sharedWith);
|
|
$share->method('getPermissions')
|
|
->willReturn($permission);
|
|
$share->method('getPassword')
|
|
->willReturn($password);
|
|
|
|
return $share;
|
|
}
|
|
|
|
private function mockShareManagerGetShareByToken($token, $share) {
|
|
$this->shareManager->expects($this->once())
|
|
->method('getShareByToken')
|
|
->with($token)
|
|
->willReturn($share);
|
|
}
|
|
|
|
private function mockShareManagerGetShareByTokenThrowsException($token) {
|
|
$this->shareManager->expects($this->once())
|
|
->method('getShareByToken')
|
|
->with($token)
|
|
->willThrowException(
|
|
new \OCP\Share\Exceptions\ShareNotFound(
|
|
"Can't find a share using that token"
|
|
)
|
|
);
|
|
}
|
|
|
|
private function mockShareManagerCheckPassword($share, $password, $result) {
|
|
$this->shareManager->expects($this->once())
|
|
->method('checkPassword')
|
|
->with($share, $password)
|
|
->willReturn($result);
|
|
}
|
|
|
|
}
|