aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files_sharing/lib/Controller/ShareesAPIController.php70
-rw-r--r--apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php256
-rw-r--r--apps/theming/appinfo/app.php1
-rw-r--r--core/templates/layout.base.php2
-rw-r--r--core/templates/layout.guest.php2
-rw-r--r--core/templates/layout.user.php2
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php3
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php29
-rw-r--r--lib/private/Files/Storage/Local.php12
-rw-r--r--lib/private/LargeFileHelper.php18
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php54
-rw-r--r--lib/private/Security/CSRF/CsrfToken.php10
-rw-r--r--lib/private/Security/CSRF/CsrfTokenManager.php13
-rw-r--r--lib/private/Server.php13
-rw-r--r--lib/public/AppFramework/Http/ContentSecurityPolicy.php2
-rw-r--r--lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php24
-rw-r--r--tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php24
-rw-r--r--tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php53
-rw-r--r--tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php57
-rw-r--r--tests/lib/Security/CSRF/CsrfTokenManagerTest.php16
-rw-r--r--tests/lib/Security/CSRF/CsrfTokenTest.php7
23 files changed, 644 insertions, 26 deletions
diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php
index a2063803450..99c6b55240d 100644
--- a/apps/files_sharing/lib/Controller/ShareesAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php
@@ -83,10 +83,12 @@ class ShareesAPIController extends OCSController {
'users' => [],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
],
'users' => [],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
];
protected $reachedEndFor = [];
@@ -403,6 +405,68 @@ class ShareesAPIController extends OCSController {
}
/**
+ * @param string $search
+ */
+ protected function getEmails($search) {
+ $this->result['emails'] = [];
+ $this->result['exact']['emails'] = [];
+
+ $foundEmail = false;
+
+ // Search in contacts
+ //@todo Pagination missing
+ $addressBookContacts = $this->contactsManager->search($search, ['FN', 'EMAIL']);
+ foreach ($addressBookContacts as $contact) {
+ if (!isset($contact['EMAIL'])) {
+ continue;
+ }
+
+ $emails = $contact['EMAIL'];
+ if (!is_array($emails)) {
+ $emails = [$emails];
+ }
+
+ foreach ($emails as $email) {
+ if (strtolower($search) === strtolower($contact['FN']) ||
+ strtolower($search) === strtolower($email)
+ ) {
+ if (strtolower($search) === strtolower($email)) {
+ $foundEmail = true;
+ }
+
+ $this->result['exact']['emails'][] = [
+ 'label' => $contact['FN'],
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_EMAIL,
+ 'shareWith' => $email,
+ ],
+ ];
+ } else if ($this->shareeEnumeration) {
+ $this->result['emails'][] = [
+ 'label' => $contact['FN'],
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_EMAIL,
+ 'shareWith' => $email,
+ ],
+ ];
+ }
+ }
+ }
+
+ if (!$foundEmail && substr_count($search, '@') >= 1 && $this->offset === 0) {
+ $this->result['exact']['emails'][] = [
+ 'label' => $search,
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_EMAIL,
+ 'shareWith' => $search,
+ ],
+ ];
+ }
+
+ $this->reachedEndFor[] = 'emails';
+ }
+
+ /**
* @NoAdminRequired
*
* @param string $search
@@ -429,6 +493,7 @@ class ShareesAPIController extends OCSController {
$shareTypes[] = Share::SHARE_TYPE_GROUP;
}
+ $shareTypes[] = Share::SHARE_TYPE_EMAIL;
$shareTypes[] = Share::SHARE_TYPE_REMOTE;
if (is_array($shareType)) {
@@ -499,6 +564,11 @@ class ShareesAPIController extends OCSController {
$this->getRemote($search);
}
+ // Get email
+ if (in_array(Share::SHARE_TYPE_EMAIL, $shareTypes)) {
+ $this->getEmails($search);
+ }
+
$response = new Http\DataResponse($this->result);
if (sizeof($this->reachedEndFor) < 3) {
diff --git a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
index 161cc8a184b..6ee1ff596e4 100644
--- a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
@@ -39,7 +39,7 @@ use OCP\Share;
* @package OCA\Files_Sharing\Tests\API
*/
class ShareesAPIControllerTest extends TestCase {
- /** @var Sharees */
+ /** @var ShareesAPIController */
protected $sharees;
/** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */
@@ -1025,8 +1025,234 @@ class ShareesAPIControllerTest extends TestCase {
$this->assertCount((int) $reachedEnd, $this->invokePrivate($this->sharees, 'reachedEndFor'));
}
+ public function dataGetEmails() {
+ return [
+ ['test', [], true, [], [], true],
+ ['test', [], false, [], [], true],
+ [
+ 'test@remote.com',
+ [],
+ true,
+ [
+ ['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']],
+ ],
+ [],
+ true,
+ ],
+ [
+ 'test@remote.com',
+ [],
+ false,
+ [
+ ['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']],
+ ],
+ [],
+ true,
+ ],
+ [
+ 'test',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ true,
+ [],
+ [
+ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost.com']],
+ ],
+ true,
+ ],
+ [
+ 'test',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ false,
+ [],
+ [],
+ true,
+ ],
+ [
+ 'test@remote.com',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ true,
+ [
+ ['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']],
+ ],
+ [
+ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost.com']],
+ ],
+ true,
+ ],
+ [
+ 'test@remote.com',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ false,
+ [
+ ['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']],
+ ],
+ [],
+ true,
+ ],
+ [
+ 'username@localhost.com',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ true,
+ [
+ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost.com']],
+ ],
+ [],
+ true,
+ ],
+ [
+ 'username@localhost.com',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => [
+ 'username@localhost.com',
+ ],
+ ],
+ ],
+ false,
+ [
+ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost.com']],
+ ],
+ [],
+ true,
+ ],
+ // Test single email
+ [
+ 'username@localhost.com',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'EMAIL' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'EMAIL' => 'username@localhost.com',
+ ],
+ ],
+ false,
+ [
+ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost.com']],
+ ],
+ [],
+ true,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetEmails
+ *
+ * @param string $searchTerm
+ * @param array $contacts
+ * @param bool $shareeEnumeration
+ * @param array $exactExpected
+ * @param array $expected
+ * @param bool $reachedEnd
+ */
+ public function testGetEmails($searchTerm, $contacts, $shareeEnumeration, $exactExpected, $expected, $reachedEnd) {
+ $this->invokePrivate($this->sharees, 'shareeEnumeration', [$shareeEnumeration]);
+ $this->contactsManager->expects($this->any())
+ ->method('search')
+ ->with($searchTerm, ['FN', 'EMAIL'])
+ ->willReturn($contacts);
+
+ $this->invokePrivate($this->sharees, 'getEmails', [$searchTerm]);
+ $result = $this->invokePrivate($this->sharees, 'result');
+
+ $this->assertEquals($exactExpected, $result['exact']['emails']);
+ $this->assertEquals($expected, $result['emails']);
+ $this->assertCount((int) $reachedEnd, $this->invokePrivate($this->sharees, 'reachedEndFor'));
+ }
+
public function dataSearch() {
- $allTypes = [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE];
+ $allTypes = [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_EMAIL, Share::SHARE_TYPE_REMOTE];
return [
[[], '', 'yes', true, '', null, $allTypes, 1, 200, false, true, true],
@@ -1082,13 +1308,13 @@ class ShareesAPIControllerTest extends TestCase {
], '', 'yes', true, '', null, $allTypes, 1, 200, false, true, true],
[[
'shareType' => $allTypes,
- ], '', 'yes', false, '', null, [0, 1], 1, 200, false, true, true],
+ ], '', 'yes', false, '', null, [0, 1, 4], 1, 200, false, true, true],
[[
'shareType' => $allTypes,
- ], '', 'yes', true, '', null, [0, 6], 1, 200, false, true, false],
+ ], '', 'yes', true, '', null, [0, 4, 6], 1, 200, false, true, false],
[[
'shareType' => $allTypes,
- ], '', 'yes', false, '', null, [0], 1, 200, false, true, false],
+ ], '', 'yes', false, '', null, [0, 4], 1, 200, false, true, false],
// Test pagination
[[
@@ -1195,7 +1421,7 @@ class ShareesAPIControllerTest extends TestCase {
->with($itemType)
->willReturn($remoteSharingEnabled);
- $this->assertInstanceOf('\OCP\AppFramework\Http\DataResponse', $sharees->search($search, $itemType, $page, $perPage, $shareType));
+ $this->assertInstanceOf(Http\DataResponse::class, $sharees->search($search, $itemType, $page, $perPage, $shareType));
$this->assertSame($shareWithGroupOnly, $this->invokePrivate($sharees, 'shareWithGroupOnly'));
$this->assertSame($shareeEnumeration, $this->invokePrivate($sharees, 'shareeEnumeration'));
@@ -1295,17 +1521,19 @@ class ShareesAPIControllerTest extends TestCase {
return [
['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], [],
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
], false],
['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], [],
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
], false],
[
'test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [
@@ -1316,7 +1544,7 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
@@ -1326,6 +1554,7 @@ class ShareesAPIControllerTest extends TestCase {
'remotes' => [
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
+ 'emails' => [],
], true,
],
// No groups requested
@@ -1336,7 +1565,7 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
@@ -1344,6 +1573,7 @@ class ShareesAPIControllerTest extends TestCase {
'remotes' => [
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
+ 'emails' => [],
], false,
],
// Share type restricted to user - Only one user
@@ -1352,12 +1582,13 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
], null, null,
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
], false,
],
// Share type restricted to user - Multipage result
@@ -1367,13 +1598,14 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
], null, null,
[
- 'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
+ 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []],
'users' => [
['label' => 'test 1', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
'groups' => [],
'remotes' => [],
+ 'emails' => [],
], true,
],
];
diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php
index e67092b642f..152504c4179 100644
--- a/apps/theming/appinfo/app.php
+++ b/apps/theming/appinfo/app.php
@@ -47,6 +47,7 @@ $linkToJs = \OC::$server->getURLGenerator()->linkToRoute(
'script',
[
'src' => $linkToJs,
+ 'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce()
], ''
);
diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php
index 7301ae690cc..3f13523afcb 100644
--- a/core/templates/layout.base.php
+++ b/core/templates/layout.base.php
@@ -19,7 +19,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach ($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php
index 58506353158..6d46ac6cf2c 100644
--- a/core/templates/layout.guest.php
+++ b/core/templates/layout.guest.php
@@ -20,7 +20,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index 285eb3ab5f3..d258e3582d0 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -27,7 +27,7 @@
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
<?php endforeach; ?>
<?php foreach($_['jsfiles'] as $jsfile): ?>
- <script src="<?php print_unescaped($jsfile); ?>"></script>
+ <script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
<?php endforeach; ?>
<?php print_unescaped($_['headers']); ?>
</head>
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index c10f1ca7e5f..0d2a782a867 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -674,6 +674,7 @@ return array(
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 4b7020b59b5..43e3fc3de34 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -704,6 +704,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 21d5eaa9503..97faa0edf49 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -379,7 +379,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$c['AppName'],
$app->isLoggedIn(),
$app->isAdminUser(),
- $app->getServer()->getContentSecurityPolicyManager()
+ $app->getServer()->getContentSecurityPolicyManager(),
+ $app->getServer()->getCsrfTokenManager()
);
});
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index 5e253d0954a..6c33c0023ea 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\CSP\ContentSecurityPolicyManager;
+use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
@@ -77,6 +78,8 @@ class SecurityMiddleware extends Middleware {
private $isAdminUser;
/** @var ContentSecurityPolicyManager */
private $contentSecurityPolicyManager;
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
/**
* @param IRequest $request
@@ -88,6 +91,7 @@ class SecurityMiddleware extends Middleware {
* @param bool $isLoggedIn
* @param bool $isAdminUser
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
+ * @param CSRFTokenManager $csrfTokenManager
*/
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
@@ -97,7 +101,8 @@ class SecurityMiddleware extends Middleware {
$appName,
$isLoggedIn,
$isAdminUser,
- ContentSecurityPolicyManager $contentSecurityPolicyManager) {
+ ContentSecurityPolicyManager $contentSecurityPolicyManager,
+ CsrfTokenManager $csrfTokenManager) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
@@ -107,6 +112,7 @@ class SecurityMiddleware extends Middleware {
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
+ $this->csrfTokenManager = $csrfTokenManager;
}
@@ -171,6 +177,23 @@ class SecurityMiddleware extends Middleware {
}
+ private function browserSupportsCspV3() {
+ $browserWhitelist = [
+ // Chrome 40+
+ '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/',
+ // Firefox 45+
+ '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/',
+ // Safari 10+
+ '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/',
+ ];
+
+ if($this->request->isUserAgent($browserWhitelist)) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Performs the default CSP modifications that may be injected by other
* applications
@@ -190,6 +213,10 @@ class SecurityMiddleware extends Middleware {
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+ if($this->browserSupportsCspV3()) {
+ $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
+ }
+
$response->setContentSecurityPolicy($defaultPolicy);
return $response;
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index bda7f2ea903..4fe7dcafbbf 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -173,8 +173,16 @@ class Local extends \OC\Files\Storage\Common {
}
public function filemtime($path) {
- clearstatcache($this->getSourcePath($path));
- return $this->file_exists($path) ? filemtime($this->getSourcePath($path)) : false;
+ $fullPath = $this->getSourcePath($path);
+ clearstatcache($fullPath);
+ if (!$this->file_exists($path)) {
+ return false;
+ }
+ if (PHP_INT_SIZE === 4) {
+ $helper = new \OC\LargeFileHelper();
+ return $helper->getFileMtime($fullPath);
+ }
+ return filemtime($fullPath);
}
public function touch($path, $mtime = null) {
diff --git a/lib/private/LargeFileHelper.php b/lib/private/LargeFileHelper.php
index 4363fb42cc6..9f18a6acd6b 100644
--- a/lib/private/LargeFileHelper.php
+++ b/lib/private/LargeFileHelper.php
@@ -1,6 +1,7 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Lukas Reschke <lukas@statuscode.ch>
@@ -177,6 +178,23 @@ class LargeFileHelper {
return $result;
}
+ /**
+ * Returns the current mtime for $fullPath
+ *
+ * @param string $fullPath
+ * @return int
+ */
+ public function getFileMtime($fullPath) {
+ if (\OC_Helper::is_function_enabled('exec')) {
+ $os = strtolower(php_uname('s'));
+ if (strpos($os, 'linux') !== false) {
+ return $this->exec('stat -c %Y ' . escapeshellarg($fullPath));
+ }
+ }
+
+ return filemtime($fullPath);
+ }
+
protected function exec($cmd) {
$result = trim(exec($cmd));
return ctype_digit($result) ? 0 + $result : null;
diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
new file mode 100644
index 00000000000..0482ea49e5c
--- /dev/null
+++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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 OC\Security\CSP;
+
+use OC\Security\CSRF\CsrfTokenManager;
+
+/**
+ * @package OC\Security\CSP
+ */
+class ContentSecurityPolicyNonceManager {
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
+ /** @var string */
+ private $nonce = '';
+
+ /**
+ * @param CsrfTokenManager $csrfTokenManager
+ */
+ public function __construct(CsrfTokenManager $csrfTokenManager) {
+ $this->csrfTokenManager = $csrfTokenManager;
+ }
+
+ /**
+ * Returns the current CSP nounce
+ *
+ * @return string
+ */
+ public function getNonce() {
+ if($this->nonce === '') {
+ $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue());
+ }
+
+ return $this->nonce;
+ }
+}
diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php
index bf61e339f77..dce9a83b727 100644
--- a/lib/private/Security/CSRF/CsrfToken.php
+++ b/lib/private/Security/CSRF/CsrfToken.php
@@ -33,6 +33,8 @@ namespace OC\Security\CSRF;
class CsrfToken {
/** @var string */
private $value;
+ /** @var string */
+ private $encryptedValue = '';
/**
* @param string $value Value of the token. Can be encrypted or not encrypted.
@@ -48,8 +50,12 @@ class CsrfToken {
* @return string
*/
public function getEncryptedValue() {
- $sharedSecret = base64_encode(random_bytes(strlen($this->value)));
- return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret;
+ if($this->encryptedValue === '') {
+ $sharedSecret = base64_encode(random_bytes(strlen($this->value)));
+ $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret;
+ }
+
+ return $this->encryptedValue;
}
/**
diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php
index d621cc2c29f..b43ca3d3679 100644
--- a/lib/private/Security/CSRF/CsrfTokenManager.php
+++ b/lib/private/Security/CSRF/CsrfTokenManager.php
@@ -34,6 +34,8 @@ class CsrfTokenManager {
private $tokenGenerator;
/** @var SessionStorage */
private $sessionStorage;
+ /** @var CsrfToken|null */
+ private $csrfToken = null;
/**
* @param CsrfTokenGenerator $tokenGenerator
@@ -51,6 +53,10 @@ class CsrfTokenManager {
* @return CsrfToken
*/
public function getToken() {
+ if(!is_null($this->csrfToken)) {
+ return $this->csrfToken;
+ }
+
if($this->sessionStorage->hasToken()) {
$value = $this->sessionStorage->getToken();
} else {
@@ -58,7 +64,8 @@ class CsrfTokenManager {
$this->sessionStorage->setToken($value);
}
- return new CsrfToken($value);
+ $this->csrfToken = new CsrfToken($value);
+ return $this->csrfToken;
}
/**
@@ -69,13 +76,15 @@ class CsrfTokenManager {
public function refreshToken() {
$value = $this->tokenGenerator->generateToken();
$this->sessionStorage->setToken($value);
- return new CsrfToken($value);
+ $this->csrfToken = new CsrfToken($value);
+ return $this->csrfToken;
}
/**
* Remove the current token from the storage.
*/
public function removeToken() {
+ $this->csrfToken = null;
$this->sessionStorage->removeToken();
}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 11558118d52..1ccc27802d2 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler;
use OC\Security\CertificateManager;
use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\Crypto;
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\Security\CSRF\CsrfTokenGenerator;
use OC\Security\CSRF\CsrfTokenManager;
use OC\Security\CSRF\TokenStorage\SessionStorage;
@@ -708,6 +709,11 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService('ContentSecurityPolicyManager', function (Server $c) {
return new ContentSecurityPolicyManager();
});
+ $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) {
+ return new ContentSecurityPolicyNonceManager(
+ $c->getCsrfTokenManager()
+ );
+ });
$this->registerService('ShareManager', function(Server $c) {
$config = $c->getConfig();
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
@@ -1406,6 +1412,13 @@ class Server extends ServerContainer implements IServerContainer {
}
/**
+ * @return ContentSecurityPolicyNonceManager
+ */
+ public function getContentSecurityPolicyNonceManager() {
+ return $this->query('ContentSecurityPolicyNonceManager');
+ }
+
+ /**
* Not a public API as of 8.2, wait for 9.0
*
* @return \OCA\Files_External\Service\BackendService
diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
index 082aa0206c7..17844497f94 100644
--- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
@@ -24,8 +24,6 @@
namespace OCP\AppFramework\Http;
-use OCP\AppFramework\Http;
-
/**
* Class ContentSecurityPolicy is a simple helper which allows applications to
* modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript,
diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
index 4fca1588e7f..ae4ceef1923 100644
--- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
@@ -38,6 +38,8 @@ use OCP\AppFramework\Http;
class EmptyContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */
protected $inlineScriptAllowed = null;
+ /** @var string Whether JS nonces should be used */
+ protected $useJsNonce = null;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
@@ -74,6 +76,7 @@ class EmptyContentSecurityPolicy {
* @param bool $state
* @return $this
* @since 8.1.0
+ * @deprecated 10.0 CSP tokens are now used
*/
public function allowInlineScript($state = false) {
$this->inlineScriptAllowed = $state;
@@ -81,6 +84,18 @@ class EmptyContentSecurityPolicy {
}
/**
+ * Use the according JS nonce
+ *
+ * @param string $nonce
+ * @return $this
+ * @since 9.2.0
+ */
+ public function useJsNonce($nonce) {
+ $this->useJsNonce = $nonce;
+ return $this;
+ }
+
+ /**
* Whether eval in JavaScript is allowed or forbidden
* @param bool $state
* @return $this
@@ -323,6 +338,15 @@ class EmptyContentSecurityPolicy {
if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
$policy .= 'script-src ';
+ if(is_string($this->useJsNonce)) {
+ $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
+ $allowedScriptDomains = array_flip($this->allowedScriptDomains);
+ unset($allowedScriptDomains['\'self\'']);
+ $this->allowedScriptDomains = array_flip($allowedScriptDomains);
+ if(count($allowedScriptDomains) !== 0) {
+ $policy .= ' ';
+ }
+ }
if(is_array($this->allowedScriptDomains)) {
$policy .= implode(' ', $this->allowedScriptDomains);
}
diff --git a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
index 248c3d808d2..33e2315ed89 100644
--- a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
+++ b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php
@@ -427,4 +427,28 @@ class EmptyContentSecurityPolicyTest extends \Test\TestCase {
$this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
$this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
}
+
+ public function testGetPolicyWithJsNonceAndScriptDomains() {
+ $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl' www.nextcloud.com www.nextcloud.org";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.com');
+ $this->contentSecurityPolicy->useJsNonce('MyJsNonce');
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyWithJsNonceAndSelfScriptDomain() {
+ $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl'";
+
+ $this->contentSecurityPolicy->useJsNonce('MyJsNonce');
+ $this->contentSecurityPolicy->addAllowedScriptDomain("'self'");
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyWithoutJsNonceAndSelfScriptDomain() {
+ $expectedPolicy = "default-src 'none';script-src 'self'";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain("'self'");
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
}
diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
index 55bf3e46e07..b597317fca4 100644
--- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
@@ -36,6 +36,8 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\CSP\ContentSecurityPolicy;
use OC\Security\CSP\ContentSecurityPolicyManager;
+use OC\Security\CSRF\CsrfToken;
+use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
@@ -72,6 +74,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $urlGenerator;
/** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */
private $contentSecurityPolicyManager;
+ /** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */
+ private $csrfTokenManager;
protected function setUp() {
parent::setUp();
@@ -83,6 +87,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->request = $this->createMock(IRequest::class);
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
+ $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
$this->middleware = $this->getMiddleware(true, true);
$this->secException = new SecurityException('hey', false);
$this->secAjaxException = new SecurityException('hey', true);
@@ -103,7 +108,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
'files',
$isLoggedIn,
$isAdminUser,
- $this->contentSecurityPolicyManager
+ $this->contentSecurityPolicyManager,
+ $this->csrfTokenManager
);
}
@@ -553,6 +559,10 @@ class SecurityMiddlewareTest extends \Test\TestCase {
}
public function testAfterController() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->willReturn(false);
$response = $this->createMock(Response::class);
$defaultPolicy = new ContentSecurityPolicy();
$defaultPolicy->addAllowedImageDomain('defaultpolicy');
@@ -591,4 +601,45 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->middleware->afterController($this->controller, 'test', $response);
}
+
+ public function testAfterControllerWithContentSecurityPolicy3Support() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->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));
+ }
}
diff --git a/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php
new file mode 100644
index 00000000000..39d24807d5b
--- /dev/null
+++ b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\CSP;
+
+use OC\Security\CSP\ContentSecurityPolicyNonceManager;
+use OC\Security\CSRF\CsrfToken;
+use OC\Security\CSRF\CsrfTokenManager;
+use Test\TestCase;
+
+class ContentSecurityPolicyNonceManagerTest extends TestCase {
+ /** @var CsrfTokenManager */
+ private $csrfTokenManager;
+ /** @var ContentSecurityPolicyNonceManager */
+ private $nonceManager;
+
+ public function setUp() {
+ $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
+ $this->nonceManager = new ContentSecurityPolicyNonceManager(
+ $this->csrfTokenManager
+ );
+ }
+
+ public function testGetNonce() {
+ $token = $this->createMock(CsrfToken::class);
+ $token
+ ->expects($this->once())
+ ->method('getEncryptedValue')
+ ->willReturn('MyToken');
+
+ $this->csrfTokenManager
+ ->expects($this->once())
+ ->method('getToken')
+ ->willReturn($token);
+
+ $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce());
+ $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce());
+ }
+}
diff --git a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
index ab19a43e91e..6f7842fdfd9 100644
--- a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
+++ b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php
@@ -56,6 +56,22 @@ class CsrfTokenManagerTest extends \Test\TestCase {
$this->assertEquals($expected, $this->csrfTokenManager->getToken());
}
+ public function testGetTokenWithExistingTokenKeepsOnSecondRequest() {
+ $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');
+ $token = $this->csrfTokenManager->getToken();
+ $this->assertSame($token, $this->csrfTokenManager->getToken());
+ $this->assertSame($token, $this->csrfTokenManager->getToken());
+ }
+
public function testGetTokenWithoutExistingToken() {
$this->storageInterface
->expects($this->once())
diff --git a/tests/lib/Security/CSRF/CsrfTokenTest.php b/tests/lib/Security/CSRF/CsrfTokenTest.php
index da640ce5052..d19d1de916c 100644
--- a/tests/lib/Security/CSRF/CsrfTokenTest.php
+++ b/tests/lib/Security/CSRF/CsrfTokenTest.php
@@ -28,6 +28,13 @@ class CsrfTokenTest extends \Test\TestCase {
$this->assertSame(':', $csrfToken->getEncryptedValue()[16]);
}
+ public function testGetEncryptedValueStaysSameOnSecondRequest() {
+ $csrfToken = new \OC\Security\CSRF\CsrfToken('MyCsrfToken');
+ $tokenValue = $csrfToken->getEncryptedValue();
+ $this->assertSame($tokenValue, $csrfToken->getEncryptedValue());
+ $this->assertSame($tokenValue, $csrfToken->getEncryptedValue());
+ }
+
public function testGetDecryptedValue() {
$csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc=');
$this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue());