You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
5.4 KiB
167 lines
5.4 KiB
<?php
|
|
/**
|
|
* li₃: the most RAD framework for PHP (http://li3.me)
|
|
*
|
|
* Copyright 2011, Union of RAD. All rights reserved. This source
|
|
* code is distributed under the terms of the BSD 3-Clause License.
|
|
* The full license text can be found in the LICENSE.txt file.
|
|
*/
|
|
|
|
namespace lithium\template\helper;
|
|
|
|
use lithium\aop\Filters;
|
|
|
|
/**
|
|
* The `Security` helper is responsible for various tasks associated with verifying the authenticity
|
|
* of requests, including embedding secure tokens to protect against CSRF attacks, and signing forms
|
|
* to prevent adding or removing fields, or tampering with fields that are designated 'locked'.
|
|
*
|
|
* @see lithium\security\validation\RequestToken
|
|
*/
|
|
class Security extends \lithium\template\Helper {
|
|
|
|
protected $_classes = [
|
|
'requestToken' => 'lithium\security\validation\RequestToken',
|
|
'formSignature' => 'lithium\security\validation\FormSignature'
|
|
];
|
|
|
|
protected $_state = [];
|
|
|
|
/**
|
|
* Constructor. Configures the helper with the default settings for interacting with
|
|
* security tokens.
|
|
*
|
|
* @param array $config
|
|
* @return void
|
|
*/
|
|
public function __construct(array $config = []) {
|
|
$defaults = ['sessionKey' => 'security.token', 'salt' => null];
|
|
parent::__construct($config + $defaults);
|
|
}
|
|
|
|
/**
|
|
* Generates a request key used to protect your forms against CSRF attacks. See the
|
|
* `RequestToken` class for examples and proper usage.
|
|
*
|
|
* @see lithium\security\validation\RequestToken
|
|
* @param array $options Options used as HTML when generating the field.
|
|
* @return string Returns a hidden `<input />` field containing a request-specific CSRF token
|
|
* key.
|
|
*/
|
|
public function requestToken(array $options = []) {
|
|
$defaults = ['name' => 'security.token', 'id' => false];
|
|
$options += $defaults;
|
|
$requestToken = $this->_classes['requestToken'];
|
|
|
|
$flags = array_intersect_key($this->_config, ['sessionKey' => '', 'salt' => '']);
|
|
$value = $requestToken::key($flags);
|
|
|
|
$name = $options['name'];
|
|
unset($options['name']);
|
|
return $this->_context->form->hidden($name, compact('value') + $options);
|
|
}
|
|
|
|
/**
|
|
* Binds the `Security` helper to the `Form` helper to create a signature used to secure form
|
|
* fields against tampering.
|
|
*
|
|
* First `FormSignature` must be provided with a secret unique to your app. This is best
|
|
* done in the bootstrap process. The secret key should be a random lengthy string.
|
|
* ```php
|
|
* use lithium\security\validation\FormSignature;
|
|
* FormSignature::config(['secret' => 'a long secret key']);
|
|
* ```
|
|
*
|
|
* In the view call the `sign()` method before creating the form.
|
|
* ```php
|
|
* <?php $this->security->sign(); ?>
|
|
* <?=$this->form->create(...); ?>
|
|
* // Form fields...
|
|
* <?=$this->form->end(); ?>
|
|
* ```
|
|
*
|
|
* In the corresponding controller action verify the signature.
|
|
* ```php
|
|
* if ($this->request->is('post') && !FormSignature::check($this->request)) {
|
|
* // The key didn't match, meaning the request has been tampered with.
|
|
* }
|
|
* ```
|
|
*
|
|
* Calling this method before a form is created adds two additional options to the `$options`
|
|
* parameter in all form inputs:
|
|
*
|
|
* - `'locked'` _boolean_: If `true`, _locks_ the value specified in the field when the field
|
|
* is generated, such that tampering with the value will invalidate the signature. Defaults
|
|
* to `true` for hidden fields, and `false` for all other form inputs.
|
|
*
|
|
* - `'exclude'` _boolean_: If `true`, this field and all subfields of the same name will be
|
|
* excluded from the signature calculation. This is useful in situations where fields may be
|
|
* added dynamically on the client side. Defaults to `false`.
|
|
*
|
|
* @see lithium\template\helper\Form
|
|
* @see lithium\security\validation\FormSignature
|
|
* @param object $form Optional. Allows specifying an instance of the `Form` helper manually.
|
|
* @return void
|
|
*/
|
|
public function sign($form = null) {
|
|
$form = $form ?: $this->_context->form;
|
|
|
|
if (isset($state[spl_object_hash($form)])) {
|
|
return;
|
|
}
|
|
|
|
Filters::apply($form, 'create', function($params, $next) use ($form) {
|
|
$this->_state[spl_object_hash($form)] = [
|
|
'fields' => [],
|
|
'locked' => [],
|
|
'excluded' => []
|
|
];
|
|
return $next($params);
|
|
});
|
|
|
|
Filters::apply($form, 'end', function($params, $next) use ($form) {
|
|
$id = spl_object_hash($form);
|
|
|
|
if (!$this->_state[$id]) {
|
|
return $next($params);
|
|
}
|
|
$formSignature = $this->_classes['formSignature'];
|
|
|
|
$value = $formSignature::key($this->_state[$id]);
|
|
echo $form->hidden('security.signature', compact('value'));
|
|
|
|
$this->_state[$id] = [];
|
|
return $next($params);
|
|
});
|
|
|
|
Filters::apply($form, '_defaults', function($params, $next) use ($form) {
|
|
$defaults = [
|
|
'locked' => ($params['method'] === 'hidden' && $params['name'] !== '_method'),
|
|
'exclude' => $params['name'] === '_method'
|
|
];
|
|
$options = $params['options'];
|
|
|
|
$options += $defaults;
|
|
$params['options'] = array_diff_key($options, $defaults);
|
|
$result = $next($params);
|
|
|
|
if ($params['method'] === 'label') {
|
|
return $result;
|
|
}
|
|
$value = isset($params['options']['value']) ? $params['options']['value'] : "";
|
|
|
|
$type = [
|
|
$options['exclude'] => 'excluded',
|
|
!$options['exclude'] => 'fields',
|
|
$options['locked'] => 'locked'
|
|
];
|
|
if (!$name = preg_replace('/(\.\d+)+$/', '', $params['name'])) {
|
|
return $result;
|
|
}
|
|
$this->_state[spl_object_hash($form)][$type[true]][$name] = $value;
|
|
return $result;
|
|
});
|
|
}
|
|
}
|
|
|
|
?>
|