Refactored Csrf form widget. + Added unit tests for Csrt.
This commit is contained in:
parent
d3a390d3f8
commit
7abd3febc1
|
@ -81,6 +81,7 @@
|
|||
"phploc/phploc": "~4.0.0",
|
||||
"jakub-onderka/php-parallel-lint": "~0.9.0",
|
||||
"sensiolabs/security-checker": "~4.0.0",
|
||||
"paragonie/random_compat": "~2.0.0",
|
||||
|
||||
"symfony/debug": "~3.4.0",
|
||||
"doctrine/instantiator": "~1.0.0",
|
||||
|
|
104
composer.lock
generated
104
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "88f485bcc21591dbaf905423dc70d835",
|
||||
"content-hash": "8c2289ac491f1f1019f88be4f375b8a9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "behat/gherkin",
|
||||
|
@ -1144,6 +1144,54 @@
|
|||
"homepage": "http://www.oomphinc.com/",
|
||||
"time": "2017-03-31T16:57:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8",
|
||||
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2017-09-27T21:40:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pda/pheanstalk",
|
||||
"version": "v3.1.0",
|
||||
|
@ -2091,16 +2139,16 @@
|
|||
},
|
||||
{
|
||||
"name": "psr/simple-cache",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/simple-cache.git",
|
||||
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
|
||||
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
|
||||
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
|
||||
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
|
||||
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2135,7 +2183,7 @@
|
|||
"psr-16",
|
||||
"simple-cache"
|
||||
],
|
||||
"time": "2017-01-02T13:31:39+00:00"
|
||||
"time": "2017-10-23T01:57:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "robmorgan/phinx",
|
||||
|
@ -3029,7 +3077,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/browser-kit.git",
|
||||
|
@ -3086,7 +3134,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/cache",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/cache.git",
|
||||
|
@ -3156,7 +3204,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
|
@ -3219,7 +3267,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
|
@ -3288,7 +3336,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
|
@ -3341,7 +3389,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
|
@ -3397,16 +3445,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dependency-injection.git",
|
||||
"reference": "752c45dc831dc42a472f0ab8ae0450b63b840656"
|
||||
"reference": "12e901abc1cb0d637a0e5abe9923471361d96b07"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/752c45dc831dc42a472f0ab8ae0450b63b840656",
|
||||
"reference": "752c45dc831dc42a472f0ab8ae0450b63b840656",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/12e901abc1cb0d637a0e5abe9923471361d96b07",
|
||||
"reference": "12e901abc1cb0d637a0e5abe9923471361d96b07",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3464,11 +3512,11 @@
|
|||
],
|
||||
"description": "Symfony DependencyInjection Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-02-26T14:27:04+00:00"
|
||||
"time": "2018-03-04T03:54:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
|
@ -3524,7 +3572,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
|
@ -3587,7 +3635,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
|
@ -3636,16 +3684,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "6a615613745cef820d807443f32076bb9f5d0a38"
|
||||
"reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/6a615613745cef820d807443f32076bb9f5d0a38",
|
||||
"reference": "6a615613745cef820d807443f32076bb9f5d0a38",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/a479817ce0a9e4adfd7d39c6407c95d97c254625",
|
||||
"reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3681,7 +3729,7 @@
|
|||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-02-11T17:15:12+00:00"
|
||||
"time": "2018-03-05T18:28:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-apcu",
|
||||
|
@ -3800,7 +3848,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
|
@ -3849,7 +3897,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
|
@ -4004,7 +4052,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.4.5",
|
||||
"version": "v3.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
|
|
|
@ -92,9 +92,12 @@ class GroupController extends Controller
|
|||
}
|
||||
|
||||
$form = new Form();
|
||||
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(APP_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : ''));
|
||||
|
||||
$form->addField(new Form\Element\Csrf('group_form'));
|
||||
|
||||
$title = new Form\Element\Text('title');
|
||||
$title->setContainerClass('form-group');
|
||||
$title->setClass('form-control');
|
||||
|
|
|
@ -421,9 +421,9 @@ class ProjectController extends PHPCensor\Controller
|
|||
$form = new Form();
|
||||
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(APP_URL.'project/' . $type);
|
||||
$form->setAction(APP_URL . 'project/' . $type);
|
||||
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
$form->addField(new Form\Element\Csrf('project_form'));
|
||||
$form->addField(new Form\Element\Hidden('pubkey'));
|
||||
|
||||
$options = [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace PHPCensor\Controller;
|
||||
|
||||
use PHPCensor\Form\Element\Csrf;
|
||||
use PHPCensor\Helper\Email;
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Controller;
|
||||
|
@ -38,6 +39,44 @@ class SessionController extends Controller
|
|||
$this->authentication = Service::getInstance();
|
||||
}
|
||||
|
||||
protected function loginForm($values)
|
||||
{
|
||||
$form = new \PHPCensor\Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(APP_URL . 'session/login');
|
||||
|
||||
$form->addField(new Csrf('login_form'));
|
||||
|
||||
$email = new \PHPCensor\Form\Element\Text('email');
|
||||
$email->setLabel(Lang::get('login'));
|
||||
$email->setRequired(true);
|
||||
$email->setContainerClass('form-group');
|
||||
$email->setClass('form-control');
|
||||
$form->addField($email);
|
||||
|
||||
$pwd = new \PHPCensor\Form\Element\Password('password');
|
||||
$pwd->setLabel(Lang::get('password'));
|
||||
$pwd->setRequired(true);
|
||||
$pwd->setContainerClass('form-group');
|
||||
$pwd->setClass('form-control');
|
||||
$form->addField($pwd);
|
||||
|
||||
$remember = \PHPCensor\Form\Element\Checkbox::create('remember_me', Lang::get('remember_me'), false);
|
||||
$remember->setContainerClass('form-group');
|
||||
$remember->setCheckedValue(1);
|
||||
$remember->setValue(0);
|
||||
$form->addField($remember);
|
||||
|
||||
$pwd = new \PHPCensor\Form\Element\Submit();
|
||||
$pwd->setValue(Lang::get('log_in'));
|
||||
$pwd->setClass('btn-success');
|
||||
$form->addField($pwd);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user login (form and processing)
|
||||
*/
|
||||
|
@ -55,15 +94,22 @@ class SessionController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
if ($method === 'POST') {
|
||||
$values = $this->getParams();
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
|
||||
$form = $this->loginForm($values);
|
||||
|
||||
$isLoginFailure = false;
|
||||
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$token = $this->getParam('token');
|
||||
if (!isset($token, $_SESSION['login_token']) || $token !== $_SESSION['login_token']) {
|
||||
if ($this->request->getMethod() === 'POST') {
|
||||
if (!$form->getChild('login_form')->validate()) {
|
||||
$isLoginFailure = true;
|
||||
} else {
|
||||
unset($_SESSION['login_token']);
|
||||
|
||||
$email = $this->getParam('email');
|
||||
$password = $this->getParam('password', '');
|
||||
$rememberMe = (bool)$this->getParam('remember_me', 0);
|
||||
|
@ -116,41 +162,6 @@ class SessionController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$form = new \PHPCensor\Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(APP_URL . 'session/login');
|
||||
|
||||
$email = new \PHPCensor\Form\Element\Text('email');
|
||||
$email->setLabel(Lang::get('login'));
|
||||
$email->setRequired(true);
|
||||
$email->setContainerClass('form-group');
|
||||
$email->setClass('form-control');
|
||||
$form->addField($email);
|
||||
|
||||
$pwd = new \PHPCensor\Form\Element\Password('password');
|
||||
$pwd->setLabel(Lang::get('password'));
|
||||
$pwd->setRequired(true);
|
||||
$pwd->setContainerClass('form-group');
|
||||
$pwd->setClass('form-control');
|
||||
$form->addField($pwd);
|
||||
|
||||
$remember = \PHPCensor\Form\Element\Checkbox::create('remember_me', Lang::get('remember_me'), false);
|
||||
$remember->setContainerClass('form-group');
|
||||
$remember->setCheckedValue(1);
|
||||
$remember->setValue(0);
|
||||
$form->addField($remember);
|
||||
|
||||
$pwd = new \PHPCensor\Form\Element\Submit();
|
||||
$pwd->setValue(Lang::get('log_in'));
|
||||
$pwd->setClass('btn-success');
|
||||
$form->addField($pwd);
|
||||
|
||||
$tokenValue = $this->generateToken();
|
||||
$_SESSION['login_token'] = $tokenValue;
|
||||
$token = new \PHPCensor\Form\Element\Hidden('token');
|
||||
$token->setValue($tokenValue);
|
||||
$form->addField($token);
|
||||
|
||||
$this->view->form = $form->render();
|
||||
$this->view->failed = $isLoginFailure;
|
||||
|
||||
|
@ -261,20 +272,4 @@ class SessionController extends Controller
|
|||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/** Generate a random token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateToken()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(openssl_random_pseudo_bytes(16));
|
||||
}
|
||||
|
||||
return sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,9 +84,12 @@ class UserController extends Controller
|
|||
$this->layout->subtitle = Lang::get('edit_profile');
|
||||
|
||||
$form = new Form();
|
||||
$form->setAction(APP_URL.'user/profile');
|
||||
|
||||
$form->setAction(APP_URL . 'user/profile');
|
||||
$form->setMethod('POST');
|
||||
|
||||
$form->addField(new Form\Element\Csrf('profile_form'));
|
||||
|
||||
$name = new Form\Element\Text('name');
|
||||
$name->setClass('form-control');
|
||||
$name->setContainerClass('form-group');
|
||||
|
@ -159,15 +162,15 @@ class UserController extends Controller
|
|||
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
if ($method == 'POST') {
|
||||
if ($method === 'POST') {
|
||||
$values = $this->getParams();
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
|
||||
$form = $this->userForm($values);
|
||||
$form = $this->userForm($values);
|
||||
|
||||
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
||||
if ($method !== 'POST' || ($method == 'POST' && !$form->validate())) {
|
||||
$view = new View('User/edit');
|
||||
$view->type = 'add';
|
||||
$view->user = null;
|
||||
|
@ -236,9 +239,11 @@ class UserController extends Controller
|
|||
protected function userForm($values, $type = 'add')
|
||||
{
|
||||
$form = new Form();
|
||||
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(APP_URL.'user/' . $type);
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
$form->setAction(APP_URL . 'user/' . $type);
|
||||
|
||||
$form->addField(new Form\Element\Csrf('user_form'));
|
||||
|
||||
$field = new Form\Element\Email('email');
|
||||
$field->setRequired(true);
|
||||
|
@ -281,6 +286,7 @@ class UserController extends Controller
|
|||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,17 +6,16 @@ use PHPCensor\View;
|
|||
|
||||
class Csrf extends Hidden
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $rows = 4;
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if ($this->value != $_COOKIE[$this->getName()]) {
|
||||
$sessionToken = isset($_SESSION['csrf_tokens'][$this->getName()])
|
||||
? $_SESSION['csrf_tokens'][$this->getName()]
|
||||
: null;
|
||||
|
||||
if ($this->value !== $sessionToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -30,9 +29,12 @@ class Csrf extends Hidden
|
|||
{
|
||||
parent::onPreRender($view);
|
||||
|
||||
$csrf = md5(microtime(true));
|
||||
$view->csrf = $csrf;
|
||||
$this->setValue(
|
||||
rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=')
|
||||
);
|
||||
|
||||
setcookie($this->getName(), $csrf);
|
||||
$view->value = $this->getValue();
|
||||
|
||||
$_SESSION['csrf_tokens'][$this->getName()] = $this->getValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<input type="hidden" id="<?= $id; ?>" name="<?= $name; ?>" value="<?= $csrf; ?>">
|
||||
<input type="hidden" id="<?= $id; ?>" name="<?= $name; ?>" value="<?= $value; ?>">
|
||||
|
|
|
@ -107,7 +107,7 @@ class FormTest extends \PHPUnit\Framework\TestCase
|
|||
|
||||
public function testFieldSetBasics()
|
||||
{
|
||||
$f = new Form\FieldSet();
|
||||
$f = new Form\FieldSet();
|
||||
$f2 = new Form\FieldSet('group');
|
||||
$f3 = new Form\FieldSet();
|
||||
|
||||
|
@ -141,6 +141,14 @@ class FormTest extends \PHPUnit\Framework\TestCase
|
|||
$html = $f->render();
|
||||
self::assertTrue(strpos($html, 'one') !== false);
|
||||
self::assertTrue(strpos($html, 'two') !== false);
|
||||
|
||||
$children = $f->getChildren();
|
||||
self::assertEquals(2, count($children));
|
||||
self::assertEquals($f2, $children[$f2->getName()]);
|
||||
self::assertEquals($f3, $children[$f3->getName()]);
|
||||
|
||||
$child = $f->getChild($f3->getName());
|
||||
self::assertEquals($f3, $child);
|
||||
}
|
||||
|
||||
public function testElements()
|
||||
|
@ -196,6 +204,15 @@ class FormTest extends \PHPUnit\Framework\TestCase
|
|||
$e = new Form\Element\Url();
|
||||
self::assertTrue(strpos($e->render(), 'url') !== false);
|
||||
|
||||
$_SESSION = [];
|
||||
|
||||
$e = new Form\Element\Csrf();
|
||||
self::assertTrue(strpos($e->render(), $e->getValue()) !== false);
|
||||
self::assertEquals($_SESSION['csrf_tokens'][$e->getName()], $e->getValue());
|
||||
self::assertTrue($e->validate());
|
||||
$e->setValue('111');
|
||||
self::assertFalse($e->validate());
|
||||
|
||||
$e = new Form\Element\Password();
|
||||
self::assertTrue(strpos($e->render(), 'password') !== false);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue