<?php class Form { /** * Sufijo para el nombre del parámetro de la sesión del usuario donde se almacena el token CSRF. */ const CSRF_PARAM = 'csrf'; /** * Cadena utilizada como valor del atributo "id" de la etiqueta <form> asociada al formulario y como parámetro a comprobar para verificar que el usuario ha enviado el formulario. */ private $formId; private $ajax; /** * URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el envío del formulario. */ private $action; /** * 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; /** * Valor del parámetro enctype del formulario. */ private $enctype; /** * Se encarga de orquestar todo el proceso de creación y procesamiento de un formulario web. * * @param string $formId Cadena utilizada como valor del atributo "id" de la etiqueta <form> asociada al formulario y como parámetro a comprobar para verificar que el usuario ha enviado el formulario. * * @param string $action (opcional) URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el envío del formulario. Por defecto la URL es $_SERVER['PHP_SELF'] * * @param string $class (opcional) 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. * * @param string enctype (opcional) Valor del parámetro enctype del formulario. */ public function __construct($formId, $opciones = array() ) { $this->formId = $formId; $opcionesPorDefecto = array( 'ajax' => false, 'action' => null, 'class' => null, 'enctype' => null ); $opciones = array_merge($opcionesPorDefecto, $opciones); $this->ajax = $opciones['ajax']; $this->action = $opciones['action']; $this->classAtt = $opciones['class']; $this->enctype = $opciones['enctype']; if ( !$this->action ) { $this->action = $_SERVER['PHP_SELF']; } } public function gestiona() { if ( ! $this->formularioEnviado($_POST) ) { echo $this->generaFormulario(); } else { // Valida el token CSRF si es necesario (hay un token en la sesión asociada al formulario) $tokenRecibido = isset($_POST['CSRFToken']) ? $_POST['CSRFToken'] : FALSE; if ( ($errores = $this->csrfguard_ValidateToken($this->formId, $tokenRecibido)) !== TRUE ) { if ( ! $this->ajax ) { echo $this->generaFormulario($errores, $_POST); } else { echo $this->generaHtmlErrores($errores); } } else { $result = $this->procesaFormulario($_POST); if ( is_array($result) ) { // Error al procesar el formulario, volvemos a mostrarlo if ( ! $this->ajax ) { echo $this->generaFormulario($result, $_POST); } else { echo $this->generaHtmlErrores($result); } } else { if ( ! $this->ajax ) { header('Location: '.$result); } else { echo $result; } } } } } /** * Devuelve un <code>string</code> con el HTML necesario para presentar los campos del formulario. Es necesario asegurarse que como parte del envío se envía un parámetro con nombre <code$formId</code> (i.e. utilizado como valor del atributo name del botón de envío del formulario). */ protected function generaCamposFormulario ($datos) { return ''; } /** * Procesa los datos del formulario. */ protected function procesaFormulario($datos) { } /** * Función que verifica si el usuario ha enviado el formulario. Comprueba si existe el parámetro <code>$formId</code> en <code>$params</code>. * * @param array $params Array que contiene los datos recibidos en el envío formulario. * * @return boolean Devuelve <code>TRUE</code> si <code>$formId</code> existe como clave en <code>$params</code> */ private function formularioEnviado(&$params) { return isset($params['action']) && $params['action'] == $this->formId; } /** * Función que genera el HTML necesario para el formulario. * * * @param array $errores (opcional) Array con los mensajes de error de validación y/o procesamiento del formulario. * * @param array $datos (opcional) Array con los valores por defecto de los campos del formulario. */ private function generaFormulario($errores = array(), &$datos = array()) { $html= $this->generaListaErrores($errores); $html .= '<form method="POST" action="'.$this->action.'" id="'.$this->formId.'"'; if ( $this->classAtt ) { $html .= ' class="'.$this->classAtt.'"'; } if ( $this->enctype ) { $html .= ' enctype="'.$this->enctype.'"'; } $html .=' >'; // Se genera el token CSRF si el usuario no solicita explícitamente lo contrario. if ( ! $this->classAtt || strpos($this->classAtt, 'nocsrf') === false ) { $tokenValue = $this->csrfguard_GenerateToken($this->formId); $html .= '<input type="hidden" name="CSRFToken" value="'.$tokenValue.'" />'; } $html .= '<input type="hidden" name="action" value="'.$this->formId.'" />'; $html .= $this->generaCamposFormulario($datos); $html .= '</form>'; return $html; } private function generaListaErrores($errores) { $html=''; $numErrores = count($errores); if ( $numErrores == 1 ) { $html .= "<ul><li>".$errores[0]."</li></ul>"; } else if ( $numErrores > 1 ) { $html .= "<ul><li>"; $html .= implode("</li><li>", $errores); $html .= "</li></ul>"; } return $html; } private function csrfguard_GenerateToken($formId) { if ( ! isset($_SESSION) ) { throw new Exception('La sesión del usuario no está definida.'); } 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[$formId.'_'.self::CSRF_PARAM]=$token; return $token; } private function csrfguard_ValidateToken($formId, $tokenRecibido) { if ( ! isset($_SESSION) ) { throw new Exception('La sesión del usuario no está definida.'); } $result = TRUE; if ( isset($_SESSION[$formId.'_'.self::CSRF_PARAM]) ) { if ( $_SESSION[$formId.'_'.self::CSRF_PARAM] !== $tokenRecibido ) { $result = array(); $result[] = 'Has enviado el formulario dos veces'; } $_SESSION[$formId.'_'.self::CSRF_PARAM] = ' '; unset($_SESSION[$formId.'_'.self::CSRF_PARAM]); } else { $result = array(); $result[] = 'Formulario no válido'; } return $result; } } /* class Formulario { private $formId; private $action; private $classAtt; private $enctype; public function __construct($formId, $opciones = array() ) { $this->formId = $formId; $opcionesPorDefecto = array( 'ajax' => false, 'action' => null, 'class' => null, 'enctype' => null ); $opciones = array_merge($opcionesPorDefecto, $opciones); $this->ajax = $opciones['ajax']; $this->action = $opciones['action']; $this->classAtt = $opciones['class']; $this->enctype = $opciones['enctype']; if (!$this->action) { $app = Aplicacion::getSingleton(); $this->action = htmlspecialchars($_SERVER['REQUEST_URI']); $this->action = $app->resuelve($this->action); } } } */ ?>