This adds a new CSRF manager for unit testing purposes, it's interface is based upon https://github.com/symfony/security-csrf. Due to some of our required custom changes it is however not possible to use the Symfony component directly.remotes/origin/comments-markallread-dav
parent
37f5f5077a
commit
a977465af5
@ -0,0 +1,69 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
namespace OC\Security\CSRF; |
||||
|
||||
/** |
||||
* Class CsrfToken represents the stored or provided CSRF token. To mitigate |
||||
* BREACH alike vulnerabilities the token is returned in an encrypted value as |
||||
* well in an unencrypted value. For display measures to the user always the |
||||
* unencrypted one should be chosen. |
||||
* |
||||
* @package OC\Security\CSRF |
||||
*/ |
||||
class CsrfToken { |
||||
/** @var string */ |
||||
private $value; |
||||
|
||||
/** |
||||
* @param string $value Value of the token. Can be encrypted or not encrypted. |
||||
*/ |
||||
public function __construct($value) { |
||||
$this->value = $value; |
||||
} |
||||
|
||||
/** |
||||
* Encrypted value of the token. This is used to mitigate BREACH alike |
||||
* vulnerabilities. For display measures do use this functionality. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getEncryptedValue() { |
||||
$sharedSecret = base64_encode(random_bytes(strlen($this->value))); |
||||
return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret; |
||||
} |
||||
|
||||
/** |
||||
* The unencrypted value of the token. Used for decrypting an already |
||||
* encrypted token. |
||||
* |
||||
* @return int |
||||
*/ |
||||
public function getDecryptedValue() { |
||||
$token = explode(':', $this->value); |
||||
if (count($token) !== 2) { |
||||
return ''; |
||||
} |
||||
$obfuscatedToken = $token[0]; |
||||
$secret = $token[1]; |
||||
return base64_decode($obfuscatedToken) ^ $secret; |
||||
} |
||||
} |
||||
@ -0,0 +1,52 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
namespace OC\Security\CSRF; |
||||
|
||||
use OCP\Security\ISecureRandom; |
||||
|
||||
/** |
||||
* Class CsrfTokenGenerator is used to generate a cryptographically secure |
||||
* pseudo-random number for the token. |
||||
* |
||||
* @package OC\Security\CSRF |
||||
*/ |
||||
class CsrfTokenGenerator { |
||||
/** @var ISecureRandom */ |
||||
private $random; |
||||
|
||||
/** |
||||
* @param ISecureRandom $random |
||||
*/ |
||||
public function __construct(ISecureRandom $random) { |
||||
$this->random = $random; |
||||
} |
||||
|
||||
/** |
||||
* Generate a new CSRF token. |
||||
* |
||||
* @param int $length Length of the token in characters. |
||||
* @return string |
||||
*/ |
||||
public function generateToken($length = 32) { |
||||
return $this->random->generate($length); |
||||
} |
||||
} |
||||
@ -0,0 +1,97 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
namespace OC\Security\CSRF; |
||||
|
||||
use OC\Security\CSRF\TokenStorage\SessionStorage; |
||||
|
||||
/** |
||||
* Class CsrfTokenManager is the manager for all CSRF token related activities. |
||||
* |
||||
* @package OC\Security\CSRF |
||||
*/ |
||||
class CsrfTokenManager { |
||||
/** @var CsrfTokenGenerator */ |
||||
private $tokenGenerator; |
||||
/** @var SessionStorage */ |
||||
private $sessionStorage; |
||||
|
||||
/** |
||||
* @param CsrfTokenGenerator $tokenGenerator |
||||
* @param SessionStorage $storageInterface |
||||
*/ |
||||
public function __construct(CsrfTokenGenerator $tokenGenerator, |
||||
SessionStorage $storageInterface) { |
||||
$this->tokenGenerator = $tokenGenerator; |
||||
$this->sessionStorage = $storageInterface; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current CSRF token, if none set it will create a new one. |
||||
* |
||||
* @return CsrfToken |
||||
*/ |
||||
public function getToken() { |
||||
if($this->sessionStorage->hasToken()) { |
||||
$value = $this->sessionStorage->getToken(); |
||||
} else { |
||||
$value = $this->tokenGenerator->generateToken(); |
||||
$this->sessionStorage->setToken($value); |
||||
} |
||||
|
||||
return new CsrfToken($value); |
||||
} |
||||
|
||||
/** |
||||
* Invalidates any current token and sets a new one. |
||||
* |
||||
* @return CsrfToken |
||||
*/ |
||||
public function refreshToken() { |
||||
$value = $this->tokenGenerator->generateToken(); |
||||
$this->sessionStorage->setToken($value); |
||||
return new CsrfToken($value); |
||||
} |
||||
|
||||
/** |
||||
* Remove the current token from the storage. |
||||
*/ |
||||
public function removeToken() { |
||||
$this->sessionStorage->removeToken(); |
||||
} |
||||
|
||||
/** |
||||
* Verifies whether the provided token is valid. |
||||
* |
||||
* @param CsrfToken $token |
||||
* @return bool |
||||
*/ |
||||
public function isTokenValid(CsrfToken $token) { |
||||
if(!$this->sessionStorage->hasToken()) { |
||||
return false; |
||||
} |
||||
|
||||
return hash_equals( |
||||
$this->sessionStorage->getToken(), |
||||
$token->getDecryptedValue() |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,80 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
namespace OC\Security\CSRF\TokenStorage; |
||||
|
||||
use OCP\ISession; |
||||
|
||||
/** |
||||
* Class SessionStorage provides the session storage |
||||
* |
||||
* @package OC\Security\CSRF\TokenStorage |
||||
*/ |
||||
class SessionStorage { |
||||
/** @var ISession */ |
||||
private $session; |
||||
|
||||
/** |
||||
* @param ISession $session |
||||
*/ |
||||
public function __construct(ISession $session) { |
||||
$this->session = $session; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current token or throws an exception if none is found. |
||||
* |
||||
* @return string |
||||
* @throws \Exception |
||||
*/ |
||||
public function getToken() { |
||||
$token = $this->session->get('requesttoken'); |
||||
if(empty($token)) { |
||||
throw new \Exception('Session does not contain a requesttoken'); |
||||
} |
||||
|
||||
return $token; |
||||
} |
||||
|
||||
/** |
||||
* Set the valid current token to $value. |
||||
* |
||||
* @param string $value |
||||
*/ |
||||
public function setToken($value) { |
||||
$this->session->set('requesttoken', $value); |
||||
} |
||||
|
||||
/** |
||||
* Removes the current token. |
||||
*/ |
||||
public function removeToken() { |
||||
$this->session->remove('requesttoken'); |
||||
} |
||||
/** |
||||
* Whether the storage has a storage. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function hasToken() { |
||||
return $this->session->exists('requesttoken'); |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
class CsrfTokenGeneratorTest extends \Test\TestCase { |
||||
/** @var \OCP\Security\ISecureRandom */ |
||||
private $random; |
||||
/** @var \OC\Security\CSRF\CsrfTokenGenerator */ |
||||
private $csrfTokenGenerator; |
||||
|
||||
public function setUp() { |
||||
parent::setUp(); |
||||
$this->random = $this->getMockBuilder('\OCP\Security\ISecureRandom') |
||||
->disableOriginalConstructor()->getMock(); |
||||
$this->csrfTokenGenerator = new \OC\Security\CSRF\CsrfTokenGenerator($this->random); |
||||
|
||||
} |
||||
|
||||
public function testGenerateTokenWithCustomNumber() { |
||||
$this->random |
||||
->expects($this->once()) |
||||
->method('generate') |
||||
->with(3) |
||||
->willReturn('abc'); |
||||
$this->assertSame('abc', $this->csrfTokenGenerator->generateToken(3)); |
||||
} |
||||
|
||||
public function testGenerateTokenWithDefault() { |
||||
$this->random |
||||
->expects($this->once()) |
||||
->method('generate') |
||||
->with(32) |
||||
->willReturn('12345678901234567890123456789012'); |
||||
$this->assertSame('12345678901234567890123456789012', $this->csrfTokenGenerator->generateToken(32)); |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,134 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
class CsrfTokenManagerTest extends \Test\TestCase { |
||||
/** @var \OC\Security\CSRF\CsrfTokenManager */ |
||||
private $csrfTokenManager; |
||||
/** @var \OC\Security\CSRF\CsrfTokenGenerator */ |
||||
private $tokenGenerator; |
||||
/** @var \OC\Security\CSRF\TokenStorage\SessionStorage */ |
||||
private $storageInterface; |
||||
|
||||
public function setUp() { |
||||
parent::setUp(); |
||||
$this->tokenGenerator = $this->getMockBuilder('\OC\Security\CSRF\CsrfTokenGenerator') |
||||
->disableOriginalConstructor()->getMock(); |
||||
$this->storageInterface = $this->getMockBuilder('\OC\Security\CSRF\TokenStorage\SessionStorage') |
||||
->disableOriginalConstructor()->getMock(); |
||||
|
||||
$this->csrfTokenManager = new \OC\Security\CSRF\CsrfTokenManager( |
||||
$this->tokenGenerator, |
||||
$this->storageInterface |
||||
); |
||||
} |
||||
|
||||
public function testGetTokenWithExistingToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('hasToken') |
||||
->willReturn(true); |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('getToken') |
||||
->willReturn('MyExistingToken'); |
||||
|
||||
$expected = new \OC\Security\CSRF\CsrfToken('MyExistingToken'); |
||||
$this->assertEquals($expected, $this->csrfTokenManager->getToken()); |
||||
} |
||||
|
||||
public function testGetTokenWithoutExistingToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('hasToken') |
||||
->willReturn(false); |
||||
$this->tokenGenerator |
||||
->expects($this->once()) |
||||
->method('generateToken') |
||||
->willReturn('MyNewToken'); |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('setToken') |
||||
->with('MyNewToken'); |
||||
|
||||
$expected = new \OC\Security\CSRF\CsrfToken('MyNewToken'); |
||||
$this->assertEquals($expected, $this->csrfTokenManager->getToken()); |
||||
} |
||||
|
||||
public function testRefreshToken() { |
||||
$this->tokenGenerator |
||||
->expects($this->once()) |
||||
->method('generateToken') |
||||
->willReturn('MyNewToken'); |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('setToken') |
||||
->with('MyNewToken'); |
||||
|
||||
$expected = new \OC\Security\CSRF\CsrfToken('MyNewToken'); |
||||
$this->assertEquals($expected, $this->csrfTokenManager->refreshToken()); |
||||
} |
||||
|
||||
public function testRemoveToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('removeToken'); |
||||
|
||||
$this->csrfTokenManager->removeToken(); |
||||
} |
||||
|
||||
public function testIsTokenValidWithoutToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('hasToken') |
||||
->willReturn(false); |
||||
$token = new \OC\Security\CSRF\CsrfToken('Token'); |
||||
|
||||
$this->assertSame(false, $this->csrfTokenManager->isTokenValid($token)); |
||||
} |
||||
|
||||
public function testIsTokenValidWithWrongToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('hasToken') |
||||
->willReturn(true); |
||||
$token = new \OC\Security\CSRF\CsrfToken('Token'); |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('getToken') |
||||
->willReturn('MyToken'); |
||||
|
||||
$this->assertSame(false, $this->csrfTokenManager->isTokenValid($token)); |
||||
} |
||||
|
||||
public function testIsTokenValidWithValidToken() { |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('hasToken') |
||||
->willReturn(true); |
||||
$token = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc='); |
||||
$this->storageInterface |
||||
->expects($this->once()) |
||||
->method('getToken') |
||||
->willReturn('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF'); |
||||
|
||||
$this->assertSame(true, $this->csrfTokenManager->isTokenValid($token)); |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
class CsrfTokenTest extends \Test\TestCase { |
||||
public function testGetEncryptedValue() { |
||||
$csrfToken = new \OC\Security\CSRF\CsrfToken('MyCsrfToken'); |
||||
$this->assertSame(33, strlen($csrfToken->getEncryptedValue())); |
||||
$this->assertSame(':', $csrfToken->getEncryptedValue()[16]); |
||||
} |
||||
|
||||
public function testGetDecryptedValue() { |
||||
$csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc='); |
||||
$this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue()); |
||||
} |
||||
} |
||||
@ -0,0 +1,107 @@ |
||||
<?php |
||||
/** |
||||
* @author Lukas Reschke <lukas@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
class SessionStorageTest extends \Test\TestCase { |
||||
/** @var \OCP\ISession */ |
||||
private $session; |
||||
/** @var \OC\Security\CSRF\TokenStorage\SessionStorage */ |
||||
private $sessionStorage; |
||||
|
||||
public function setUp() { |
||||
parent::setUp(); |
||||
$this->session = $this->getMockBuilder('\OCP\ISession') |
||||
->disableOriginalConstructor()->getMock(); |
||||
$this->sessionStorage = new \OC\Security\CSRF\TokenStorage\SessionStorage($this->session); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getTokenDataProvider() { |
||||
return [ |
||||
[ |
||||
'', |
||||
], |
||||
[ |
||||
null, |
||||
], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @param string $token |
||||
* @dataProvider getTokenDataProvider |
||||
* |
||||
* @expectedException \Exception |
||||
* @expectedExceptionMessage Session does not contain a requesttoken |
||||
*/ |
||||
public function testGetTokenWithEmptyToken($token) { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('get') |
||||
->with('requesttoken') |
||||
->willReturn($token); |
||||
$this->sessionStorage->getToken(); |
||||
} |
||||
|
||||
public function testGetTokenWithValidToken() { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('get') |
||||
->with('requesttoken') |
||||
->willReturn('MyFancyCsrfToken'); |
||||
$this->assertSame('MyFancyCsrfToken', $this->sessionStorage->getToken()); |
||||
} |
||||
|
||||
public function testSetToken() { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('set') |
||||
->with('requesttoken', 'TokenToSet'); |
||||
$this->sessionStorage->setToken('TokenToSet'); |
||||
} |
||||
|
||||
public function testRemoveToken() { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('remove') |
||||
->with('requesttoken'); |
||||
$this->sessionStorage->removeToken(); |
||||
} |
||||
|
||||
public function testHasTokenWithExistingToken() { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('exists') |
||||
->with('requesttoken') |
||||
->willReturn(true); |
||||
$this->assertSame(true, $this->sessionStorage->hasToken()); |
||||
} |
||||
|
||||
public function testHasTokenWithoutExistingToken() { |
||||
$this->session |
||||
->expects($this->once()) |
||||
->method('exists') |
||||
->with('requesttoken') |
||||
->willReturn(false); |
||||
$this->assertSame(false, $this->sessionStorage->hasToken()); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue