Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>tags/v14.0.0beta1
@@ -0,0 +1,63 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2017 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @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\Core\Controller; | |||
use OC\Security\CSRF\CsrfTokenManager; | |||
use OCP\AppFramework\Controller; | |||
use OCP\AppFramework\Http\JSONResponse; | |||
use OCP\IRequest; | |||
class CSRFTokenController extends Controller { | |||
/** @var CsrfTokenManager */ | |||
private $tokenManager; | |||
/** | |||
* @param string $appName | |||
* @param IRequest $request | |||
* @param CsrfTokenManager $tokenManager | |||
*/ | |||
public function __construct(string $appName, IRequest $request, | |||
CsrfTokenManager $tokenManager) { | |||
parent::__construct($appName, $request); | |||
$this->tokenManager = $tokenManager; | |||
} | |||
/** | |||
* @NoAdminRequired | |||
* @NoCSRFRequired | |||
* @PublicPage | |||
* @return JSONResponse | |||
*/ | |||
public function index(): JSONResponse { | |||
$requestToken = $this->tokenManager->getToken(); | |||
return new JSONResponse([ | |||
'token' => $requestToken->getEncryptedValue(), | |||
]); | |||
} | |||
} |
@@ -1366,34 +1366,29 @@ function initCore() { | |||
}); | |||
/** | |||
* Calls the server periodically to ensure that session doesn't | |||
* time out | |||
* Calls the server periodically to ensure that session and CSRF | |||
* token doesn't expire | |||
*/ | |||
function initSessionHeartBeat(){ | |||
// max interval in seconds set to 24 hours | |||
var maxInterval = 24 * 3600; | |||
function initSessionHeartBeat() { | |||
// interval in seconds | |||
var interval = 900; | |||
if (oc_config.session_lifetime) { | |||
interval = Math.floor(oc_config.session_lifetime / 2); | |||
} | |||
// minimum one minute | |||
if (interval < 60) { | |||
interval = 60; | |||
} | |||
if (interval > maxInterval) { | |||
interval = maxInterval; | |||
} | |||
var url = OC.generateUrl('/heartbeat'); | |||
var heartBeatTimeout = null; | |||
var heartBeat = function() { | |||
clearInterval(heartBeatTimeout); | |||
heartBeatTimeout = setInterval(function() { | |||
$.post(url); | |||
}, interval * 1000); | |||
}; | |||
$(document).ajaxComplete(heartBeat); | |||
heartBeat(); | |||
interval = Math.max(60, interval); | |||
// max interval in seconds set to 24 hours | |||
interval = Math.min(24 * 3600, interval); | |||
var url = OC.generateUrl('/csrftoken'); | |||
setInterval(function() { | |||
$.ajax(url).then(function(resp) { | |||
oc_requesttoken = resp.token; | |||
OC.requestToken = resp.token; | |||
}).fail(function(e) { | |||
console.error('session heartbeat failed', e); | |||
}); | |||
}, interval * 1000); | |||
} | |||
// session heartbeat (defaults to enabled) |
@@ -351,14 +351,14 @@ describe('Core base tests', function() { | |||
beforeEach(function() { | |||
clock = sinon.useFakeTimers(); | |||
oldConfig = window.oc_config; | |||
routeStub = sinon.stub(OC, 'generateUrl').returns('/heartbeat'); | |||
routeStub = sinon.stub(OC, 'generateUrl').returns('/csrftoken'); | |||
counter = 0; | |||
fakeServer.autoRespond = true; | |||
fakeServer.autoRespondAfter = 0; | |||
fakeServer.respondWith(/\/heartbeat/, function(xhr) { | |||
fakeServer.respondWith(/\/csrftoken/, function(xhr) { | |||
counter++; | |||
xhr.respond(200, {'Content-Type': 'application/json'}, '{}'); | |||
xhr.respond(200, {'Content-Type': 'application/json'}, '{"token": "pgBEsb3MzTb1ZPd2mfDZbQ6/0j3OrXHMEZrghHcOkg8=:3khw5PSa+wKQVo4f26exFD3nplud9ECjJ8/Y5zk5/k4="}'); | |||
}); | |||
$(document).off('ajaxComplete'); // ignore previously registered heartbeats | |||
}); | |||
@@ -377,7 +377,7 @@ describe('Core base tests', function() { | |||
session_lifetime: 300 | |||
}; | |||
window.initCore(); | |||
expect(routeStub.calledWith('/heartbeat')).toEqual(true); | |||
expect(routeStub.calledWith('/csrftoken')).toEqual(true); | |||
expect(counter).toEqual(0); | |||
@@ -502,8 +502,8 @@ describe('Core base tests', function() { | |||
}); | |||
describe('Generate Url', function() { | |||
it('returns absolute urls', function() { | |||
expect(OC.generateUrl('heartbeat')).toEqual(OC.webroot + '/index.php/heartbeat'); | |||
expect(OC.generateUrl('/heartbeat')).toEqual(OC.webroot + '/index.php/heartbeat'); | |||
expect(OC.generateUrl('csrftoken')).toEqual(OC.webroot + '/index.php/csrftoken'); | |||
expect(OC.generateUrl('/csrftoken')).toEqual(OC.webroot + '/index.php/csrftoken'); | |||
}); | |||
it('substitutes parameters which are escaped by default', function() { | |||
expect(OC.generateUrl('apps/files/download/{file}', {file: '<">ImAnUnescapedString/!'})).toEqual(OC.webroot + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!'); |
@@ -46,6 +46,7 @@ $application->registerRoutes($this, [ | |||
['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], | |||
['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'], | |||
['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'], | |||
['name' => 'CSRFToken#index', 'url' => '/csrftoken', 'verb' => 'GET'], | |||
['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'], | |||
['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'], | |||
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'], | |||
@@ -148,8 +149,3 @@ $this->create('files_sharing.publicpreview.directLink', '/s/{token}/preview')->g | |||
throw new \OC\HintException('App file sharing is not enabled'); | |||
} | |||
}); | |||
// used for heartbeat | |||
$this->create('heartbeat', '/heartbeat')->action(function(){ | |||
// do nothing | |||
}); |
@@ -0,0 +1,71 @@ | |||
<?php | |||
/** | |||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2017 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @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 Tests\Core\Controller; | |||
use OC\Core\Controller\CSRFTokenController; | |||
use OC\Security\CSRF\CsrfToken; | |||
use OC\Security\CSRF\CsrfTokenManager; | |||
use OCP\AppFramework\Http; | |||
use OCP\AppFramework\Http\JSONResponse; | |||
use OCP\IRequest; | |||
use PHPUnit_Framework_MockObject_MockObject; | |||
use Test\TestCase; | |||
class CSRFTokenControllerTest extends TestCase { | |||
/** @var CSRFTokenController */ | |||
private $controller; | |||
/** @var IRequest|PHPUnit_Framework_MockObject_MockObject */ | |||
private $request; | |||
/** @var CsrfTokenManager|PHPUnit_Framework_MockObject_MockObject */ | |||
private $tokenManager; | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->request = $this->createMock(IRequest::class); | |||
$this->tokenManager = $this->createMock(CsrfTokenManager::class); | |||
$this->controller = new CSRFTokenController('core', $this->request, | |||
$this->tokenManager); | |||
} | |||
public function testGetToken() { | |||
$token = $this->createMock(CsrfToken::class); | |||
$this->tokenManager->method('getToken')->willReturn($token); | |||
$token->method('getEncryptedValue')->willReturn('toktok123'); | |||
$response = $this->controller->index(); | |||
$this->assertInstanceOf(JSONResponse::class, $response); | |||
$this->assertSame(Http::STATUS_OK, $response->getStatus()); | |||
$this->assertEquals([ | |||
'token' => 'toktok123' | |||
], $response->getData()); | |||
} | |||
} |