Merge pull request from GHSA-3wjh-93gr-chh6
* Integration tests: Memoize request handler as well
This is useful to send HTTP requests (or their PSR-7 equivalents)
through the entire application's middleware stack (instead of
talking to specific controllers, which should be considered
implementation detail).
* Add tests for CSRF token check
* Integration tests: Configure vendor path
Now that this is possible, make the easy change...
* Implement middleware for CSRF token verification
This fixes a rather large oversight in Flarum's codebase, which was that
we had no explicit CSRF protection using the traditional token approach.
The JS frontend was actually sending these tokens, but the backend did
not require them.
* Accept CSRF token in request body as well
* Refactor tests to shorten HTTP requests
Multiple tests now provide JSON request bodies, and others copy cookies
from previous responses, so let's provide convenient helpers for these.
* Fixed issue with tmp/storage/views not existing, this caused tmpname to notice.
Fixed csrf test that assumed an access token allows application access, which is actually api token.
Improved return type hinting in the StartSession middleware
* Using a different setting key now, so that it won't break tests whenever you re-run them once smtp is set.
Fixed, badly, the test to create users etc caused by the prepareDatabase flushing all settings by default.
* added custom view, now needs translation
2019-06-24 15:14:39 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file is part of Flarum.
|
|
|
|
*
|
|
|
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
|
|
*
|
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Flarum\Tests\integration\api\csrf_protection;
|
|
|
|
|
|
|
|
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
|
|
|
use Flarum\Tests\integration\TestCase;
|
|
|
|
|
|
|
|
class RequireCsrfTokenTest extends TestCase
|
|
|
|
{
|
|
|
|
use RetrievesAuthorizedUsers;
|
|
|
|
|
|
|
|
public function setUp()
|
|
|
|
{
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
$this->prepareDatabase([
|
|
|
|
'users' => [
|
|
|
|
$this->adminUser(),
|
|
|
|
],
|
|
|
|
'groups' => [
|
|
|
|
$this->adminGroup(),
|
|
|
|
],
|
|
|
|
'group_user' => [
|
|
|
|
['user_id' => 1, 'group_id' => 1],
|
|
|
|
],
|
|
|
|
'group_permission' => [
|
|
|
|
['permission' => 'viewUserList', 'group_id' => 3],
|
|
|
|
],
|
|
|
|
'api_keys' => [
|
|
|
|
['user_id' => 1, 'key' => 'superadmin'],
|
|
|
|
],
|
|
|
|
'settings' => [
|
|
|
|
['key' => 'csrf_test', 'value' => 1],
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @test
|
|
|
|
*/
|
|
|
|
public function error_when_doing_cookie_auth_without_csrf_token()
|
|
|
|
{
|
|
|
|
$auth = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/login',
|
|
|
|
[
|
|
|
|
'json' => ['identification' => 'admin', 'password' => 'password'],
|
|
|
|
]
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$response = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/api/settings',
|
|
|
|
[
|
|
|
|
'cookiesFrom' => $auth,
|
|
|
|
'json' => ['csrf_test' => 2],
|
|
|
|
]
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Response should be "HTTP 400 Bad Request"
|
|
|
|
$this->assertEquals(400, $response->getStatusCode());
|
|
|
|
|
|
|
|
// The response body should contain proper error details
|
|
|
|
$body = (string) $response->getBody();
|
|
|
|
$this->assertJson($body);
|
|
|
|
$this->assertEquals([
|
|
|
|
'errors' => [
|
|
|
|
['status' => '400', 'code' => 'csrf_token_mismatch'],
|
|
|
|
],
|
|
|
|
], json_decode($body, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @test
|
|
|
|
*/
|
|
|
|
public function cookie_auth_succeeds_with_csrf_token_in_header()
|
|
|
|
{
|
|
|
|
$initial = $this->send(
|
|
|
|
$this->request('GET', '/')
|
|
|
|
);
|
|
|
|
|
|
|
|
$token = $initial->getHeaderLine('X-CSRF-Token');
|
|
|
|
|
|
|
|
$auth = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/login',
|
|
|
|
[
|
|
|
|
'cookiesFrom' => $initial,
|
|
|
|
'json' => ['identification' => 'admin', 'password' => 'password'],
|
|
|
|
]
|
|
|
|
)->withHeader('X-CSRF-Token', $token)
|
|
|
|
);
|
|
|
|
|
|
|
|
$token = $auth->getHeaderLine('X-CSRF-Token');
|
|
|
|
|
|
|
|
$response = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/api/settings',
|
|
|
|
[
|
|
|
|
'cookiesFrom' => $auth,
|
|
|
|
'json' => ['csrf_test' => 2],
|
|
|
|
]
|
|
|
|
)->withHeader('X-CSRF-Token', $token)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Successful response?
|
|
|
|
$this->assertEquals(204, $response->getStatusCode());
|
|
|
|
|
|
|
|
// Was the setting actually changed in the database?
|
|
|
|
$this->assertEquals(
|
|
|
|
2,
|
|
|
|
$this->database()->table('settings')->where('key', 'csrf_test')->first()->value
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @test
|
|
|
|
*/
|
|
|
|
public function cookie_auth_succeeds_with_csrf_token_in_body()
|
|
|
|
{
|
|
|
|
$initial = $this->send(
|
|
|
|
$this->request('GET', '/')
|
|
|
|
);
|
|
|
|
|
|
|
|
$token = $initial->getHeaderLine('X-CSRF-Token');
|
|
|
|
|
|
|
|
$auth = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/login',
|
|
|
|
[
|
|
|
|
'cookiesFrom' => $initial,
|
|
|
|
'json' => ['identification' => 'admin', 'password' => 'password', 'csrfToken' => $token],
|
|
|
|
]
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$token = $auth->getHeaderLine('X-CSRF-Token');
|
|
|
|
|
|
|
|
$response = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/api/settings',
|
|
|
|
[
|
|
|
|
'cookiesFrom' => $auth,
|
|
|
|
'json' => ['csrf_test' => 2, 'csrfToken' => $token],
|
|
|
|
]
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Successful response?
|
|
|
|
$this->assertEquals(204, $response->getStatusCode());
|
|
|
|
|
|
|
|
// Was the setting actually changed in the database?
|
|
|
|
$this->assertEquals(
|
|
|
|
2,
|
|
|
|
$this->database()->table('settings')->where('key', 'csrf_test')->first()->value
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @test
|
|
|
|
*/
|
|
|
|
public function master_api_token_does_not_need_csrf_token()
|
|
|
|
{
|
|
|
|
$response = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/api/settings',
|
|
|
|
[
|
|
|
|
'json' => ['csrf_test' => 2],
|
|
|
|
]
|
|
|
|
)->withHeader('Authorization', 'Token superadmin')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Successful response?
|
|
|
|
$this->assertEquals(204, $response->getStatusCode());
|
|
|
|
|
|
|
|
// Was the setting actually changed in the database?
|
|
|
|
$this->assertEquals(
|
|
|
|
2,
|
|
|
|
$this->database()->table('settings')->where('key', 'csrf_test')->first()->value
|
|
|
|
);
|
|
|
|
}
|
2019-08-02 04:53:31 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @test
|
|
|
|
*/
|
|
|
|
public function access_token_does_not_need_csrf_token()
|
|
|
|
{
|
|
|
|
$this->database()->table('access_tokens')->insert(
|
|
|
|
['token' => 'myaccesstoken', 'user_id' => 1]
|
|
|
|
);
|
|
|
|
|
|
|
|
$response = $this->send(
|
|
|
|
$this->request(
|
|
|
|
'POST', '/api/settings',
|
|
|
|
[
|
|
|
|
'json' => ['csrf_test' => 2],
|
|
|
|
]
|
|
|
|
)->withHeader('Authorization', 'Token myaccesstoken')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Successful response?
|
|
|
|
$this->assertEquals(204, $response->getStatusCode());
|
|
|
|
|
|
|
|
// Was the setting actually changed in the database?
|
|
|
|
$this->assertEquals(
|
|
|
|
2,
|
|
|
|
$this->database()->table('settings')->where('key', 'csrf_test')->first()->value
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->database()->table('access_tokens')->where('token', 'myaccesstoken')->delete();
|
|
|
|
}
|
Merge pull request from GHSA-3wjh-93gr-chh6
* Integration tests: Memoize request handler as well
This is useful to send HTTP requests (or their PSR-7 equivalents)
through the entire application's middleware stack (instead of
talking to specific controllers, which should be considered
implementation detail).
* Add tests for CSRF token check
* Integration tests: Configure vendor path
Now that this is possible, make the easy change...
* Implement middleware for CSRF token verification
This fixes a rather large oversight in Flarum's codebase, which was that
we had no explicit CSRF protection using the traditional token approach.
The JS frontend was actually sending these tokens, but the backend did
not require them.
* Accept CSRF token in request body as well
* Refactor tests to shorten HTTP requests
Multiple tests now provide JSON request bodies, and others copy cookies
from previous responses, so let's provide convenient helpers for these.
* Fixed issue with tmp/storage/views not existing, this caused tmpname to notice.
Fixed csrf test that assumed an access token allows application access, which is actually api token.
Improved return type hinting in the StartSession middleware
* Using a different setting key now, so that it won't break tests whenever you re-run them once smtp is set.
Fixed, badly, the test to create users etc caused by the prepareDatabase flushing all settings by default.
* added custom view, now needs translation
2019-06-24 15:14:39 +08:00
|
|
|
}
|