Update form.php
This commit is contained in:
parent
6c7555dcd7
commit
c012b3b617
@ -3,16 +3,35 @@
|
|||||||
/**
|
/**
|
||||||
* Clase base para la gestión de formularios.
|
* Clase base para la gestión de formularios.
|
||||||
*
|
*
|
||||||
* Además de la gestión básica de los formularios.
|
* Gestión de token CSRF está basada en: https://www.owasp.org/index.php/PHP_CSRF_Guard
|
||||||
*/
|
*/
|
||||||
abstract class Form {
|
abstract class Form {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string Cadena utilizada como valor del atributo "id" de la etiqueta <form> asociada al formulario y
|
* @var string Sufijo para el nombre del parámetro de la sesión del usuario donde se almacena el token CSRF.
|
||||||
* como parámetro a comprobar para verificar que el usuario ha enviado el formulario.
|
*/
|
||||||
|
const CSRF_PARAM = 'csrf';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Identificador utilizado para construir el atributo "id" de la etiqueta <form> como <code>$tipoFormulario.$formId</code>.
|
||||||
*/
|
*/
|
||||||
private $formId;
|
private $formId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Valor del parámetro enctype del formulario.
|
||||||
|
*/
|
||||||
|
private $enctype;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Valor del atributo "class" de la etiqueta <form> asociada al formulario. Si este parámetro incluye la cadena "nocsrf" no se generá el token CSRF para este formulario.
|
||||||
|
*/
|
||||||
|
private $classAtt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Parámetro de la petición utilizado para comprobar que el usuario ha enviado el formulario..
|
||||||
|
*/
|
||||||
|
private $tipoFormulario;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el
|
* @var string URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el
|
||||||
* envío del formulario.
|
* envío del formulario.
|
||||||
@ -37,22 +56,35 @@ abstract class Form {
|
|||||||
* <td><code>$_SERVER['PHP_SELF']</code></td>
|
* <td><code>$_SERVER['PHP_SELF']</code></td>
|
||||||
* <td>URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el envío del formulario.</td>
|
* <td>URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el envío del formulario.</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>class</td>
|
||||||
|
* <td>""</td>
|
||||||
|
* <td>Valor del atributo "class" de la etiqueta <form> asociada al formulario. Si este parámetro incluye la cadena
|
||||||
|
* "nocsrf" no se generá el token CSRF para este formulario.</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>enctype</td>
|
||||||
|
* <td>""</td>
|
||||||
|
* <td>Valor del parámetro enctype del formulario.</td>
|
||||||
|
* </tr>
|
||||||
* </tbody>
|
* </tbody>
|
||||||
* </table>
|
* </table>
|
||||||
|
* @param string $tipoFormulario Parámetro de la petición utilizado para comprobar que el usuario ha enviado el formulario.
|
||||||
* @param string $formId Identificador utilizado en el atributo "id" de la etiqueta <form> asociada al formulario y como parámetro
|
* @param string $formId (opcional) Identificador utilizado para construir el atributo "id" de la etiqueta <form> como <code>$tipoFormulario.$formId</code>.
|
||||||
* a comprobar para verificar que el usuario ha enviado el formulario.
|
|
||||||
*
|
*
|
||||||
* @param array $opciones (ver más arriba).
|
* @param array $opciones (ver más arriba).
|
||||||
*/
|
*/
|
||||||
public function __construct($formId, $opciones = array() )
|
public function __construct($tipoFormulario, $opciones = array(), $formId = 1)
|
||||||
{
|
{
|
||||||
$this->formId = $formId;
|
$this->tipoFormulario = $tipoFormulario;
|
||||||
|
$this->formId = $tipoFormulario.$formId;
|
||||||
|
|
||||||
$opcionesPorDefecto = array( 'action' => null, );
|
$opcionesPorDefecto = array( 'action' => null, 'class' => null, 'enctype' => null );
|
||||||
$opciones = array_merge($opcionesPorDefecto, $opciones);
|
$opciones = array_merge($opcionesPorDefecto, $opciones);
|
||||||
|
|
||||||
$this->action = $opciones['action'];
|
$this->action = $opciones['action'];
|
||||||
|
$this->classAtt = $opciones['class'];
|
||||||
|
$this->enctype = $opciones['enctype'];
|
||||||
|
|
||||||
if ( !$this->action ) {
|
if ( !$this->action ) {
|
||||||
$this->action = htmlentities($_SERVER['PHP_SELF']);
|
$this->action = htmlentities($_SERVER['PHP_SELF']);
|
||||||
@ -81,6 +113,13 @@ abstract class Form {
|
|||||||
if ( ! $this->formularioEnviado($_POST) ) {
|
if ( ! $this->formularioEnviado($_POST) ) {
|
||||||
return $this->generaFormulario();
|
return $this->generaFormulario();
|
||||||
} else {
|
} else {
|
||||||
|
// Valida el token CSRF si es necesario (hay un token en la sesión asociada al formulario)
|
||||||
|
$tokenRecibido = $_POST['CSRFToken'] ?? FALSE;
|
||||||
|
$errores = $this->csrfguard_ValidateToken($this->tipoFormulario, $tokenRecibido);
|
||||||
|
|
||||||
|
// limpia los tokens CSRF que no han sido utilizados en esta petición
|
||||||
|
self::limpiaCsrfTokens();
|
||||||
|
|
||||||
$result = $this->procesaFormulario($_POST);
|
$result = $this->procesaFormulario($_POST);
|
||||||
if ( is_array($result) ) {
|
if ( is_array($result) ) {
|
||||||
return $this->generaFormulario($_POST, $result);
|
return $this->generaFormulario($_POST, $result);
|
||||||
@ -133,7 +172,7 @@ abstract class Form {
|
|||||||
*/
|
*/
|
||||||
private function formularioEnviado(&$params)
|
private function formularioEnviado(&$params)
|
||||||
{
|
{
|
||||||
return isset($params['action']) && $params['action'] == $this->formId;
|
return isset($params['action']) && $params['action'] == $this->tipoFormulario;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,12 +188,29 @@ abstract class Form {
|
|||||||
{
|
{
|
||||||
$htmlCamposFormularios = $this->generaCamposFormulario($datos, $errores);
|
$htmlCamposFormularios = $this->generaCamposFormulario($datos, $errores);
|
||||||
|
|
||||||
|
$classAtt='';
|
||||||
|
if ( $this->classAtt ) {
|
||||||
|
$classAtt = " class=\"{$this->classAtt}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
$enctypeAtt='';
|
||||||
|
if ( $this->enctype ) {
|
||||||
|
$enctypeAtt = " enctype=\"{$this->enctype}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se genera el token CSRF si el usuario no solicita explícitamente lo contrario.
|
||||||
|
$tokenCSRF = '';
|
||||||
|
if ( ! $this->classAtt || strpos($this->classAtt, 'nocsrf') === false ) {
|
||||||
|
$tokenValue = $this->csrfguard_GenerateToken($this->tipoFormulario);
|
||||||
|
$tokenCSRF = "<input type='hidden' name='CSRFToken' value='$tokenValue' />";
|
||||||
|
}
|
||||||
|
|
||||||
/* <<< Permite definir cadena en múltiples líneas.
|
/* <<< Permite definir cadena en múltiples líneas.
|
||||||
* Revisa https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
|
* Revisa https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
|
||||||
*/
|
*/
|
||||||
$htmlForm = "<form method='POST' action='{$this->action}' id='{$this->formId}' >
|
$htmlForm = "<form method='POST' action='{$this->action}' id='{$this->formId}{$classAtt}{$enctypeAtt}' >
|
||||||
<input type='hidden' name='action' value='{$this->formId}' />
|
<input type='hidden' name='action' value='{$this->tipoFormulario}' />
|
||||||
".$htmlCamposFormularios."
|
".$tokenCSRF.$htmlCamposFormularios."
|
||||||
</form>";
|
</form>";
|
||||||
return $htmlForm;
|
return $htmlForm;
|
||||||
}
|
}
|
||||||
@ -213,4 +269,70 @@ abstract class Form {
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para eliminar los tokens CSRF almecenados en la petición anterior que no hayan sido utilizados en la actual.
|
||||||
|
*/
|
||||||
|
public static function limpiaCsrfTokens()
|
||||||
|
{
|
||||||
|
foreach(array_keys($_SESSION) as $key) {
|
||||||
|
if (strpos($key, self::CSRF_PARAM) === 0) {
|
||||||
|
unset($_SESSION[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function csrfguard_GenerateToken($formParameter)
|
||||||
|
{
|
||||||
|
if ( ! session_id() ) {
|
||||||
|
throw new \Exception('La sesión del usuario no está definida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$paramSession = self::CSRF_PARAM.'_'.$formParameter;
|
||||||
|
if (isset($_SESSION[$paramSession])) {
|
||||||
|
$token = $_SESSION[$paramSession];
|
||||||
|
} else {
|
||||||
|
if ( function_exists('hash_algos') && in_array('sha512', hash_algos()) ) {
|
||||||
|
$token = hash('sha512', mt_rand(0, mt_getrandmax()));
|
||||||
|
} else {
|
||||||
|
$token=' ';
|
||||||
|
for ($i=0;$i<128;++$i) {
|
||||||
|
$r=mt_rand(0,35);
|
||||||
|
if ($r<26){
|
||||||
|
$c=chr(ord('a')+$r);
|
||||||
|
} else{
|
||||||
|
$c=chr(ord('0')+$r-26);
|
||||||
|
}
|
||||||
|
$token.=$c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[$paramSession]=$token;
|
||||||
|
}
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function csrfguard_ValidateToken($formParameter, $tokenRecibido)
|
||||||
|
{
|
||||||
|
if ( ! session_id() ) {
|
||||||
|
throw new \Exception('La sesión del usuario no está definida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = TRUE;
|
||||||
|
|
||||||
|
$paramSession = self::CSRF_PARAM.'_'.$formParameter;
|
||||||
|
if ( isset($_SESSION[$paramSession]) ) {
|
||||||
|
if ( $_SESSION[$paramSession] !== $tokenRecibido ) {
|
||||||
|
$result = array();
|
||||||
|
$result[] = 'Has enviado el formulario dos veces';
|
||||||
|
}
|
||||||
|
$_SESSION[$paramSession] = ' ';
|
||||||
|
unset($_SESSION[$paramSession]);
|
||||||
|
} else {
|
||||||
|
$result = array();
|
||||||
|
$result[] = 'Formulario no válido';
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user