Integración de una autenticación externa con los foros de Simple Machines 1.1.11

Introducción.

La situación es la siguiente, se cuenta con un CMS desarrollado en PHP bajo el framework de CodeIgniter con su propio sistema de autenticación en el que se almacenan los usuarios de la siguiente manera.

CREATE TABLE IF NOT EXISTS `core_usuario` (
`id_usuario` int(11) unsigned NOT NULL auto_increment,
`estado` enum(‘activo’,’inactivo’) NOT NULL,
`_username` varbinary(16) NOT NULL,
`_password` varbinary(32) NOT NULL,
`nombres` varchar(50) NOT NULL,
`apellidos` varchar(50) NOT NULL,
`fecha_nacimiento` date default NULL,
`tipo_documento` enum(‘cc’,’ce’,’ti’,’nit’) NOT NULL,
`documento_identidad` varchar(12) NOT NULL,
`genero` enum(‘m’,’f’) NOT NULL,
`correo` varchar(255) NOT NULL,
`pagina` varchar(255) default NULL,
`observaciones` varchar(255) default NULL,
`fecha_creacion` datetime NOT NULL,
`fecha_actualizacion` datetime NOT NULL,
PRIMARY KEY  (`id_usuario`),
);

En el sitio se ha instalado un foro basado en la versión 1.1.11 de Simple Machines el cual comparte la base de datos con el CMS.  Se necesita encontrar la forma de unificar los nombres de los usuarios y las contraseñas para tener una única identificación para la autenticación en los dos sistemas.

Para hacer esto se utiliza el API de SSI de SMF de la siguiente manera.

Hooks de integración.

SMF provee una facilidad para alterar el ciclo de flujo del software mediante la manipulación de hooks que se agregan a etapas específicas del programa para modificar su comportamiento por defecto.  En este caso nos interesa crear un hook sobre el punto integrate_validate_login ya que ese es el punto exacto en que SMF va a realizar la validación de los usuarios durante su autenticación.

Para implementar esto se crea un archivo llamado jimhook.php (el nombre es arbitrario) con el siguiente contenido inicial.

<?php
define('SMF_INTEGRATION_SETTINGS', serialize(array(
    'integrate_validate_login' => 'user_validate',
)));

require_once('SSI.php');

function user_validate()
{
    // Implementación de la lógica de la nueva autenticación.
}
?>

Estos hooks deben asociarse al sistema de foros agregando la siguiente línea al inicio del archivo index.php.

include_once("jimhook.php");

Conexión a la base de datos.

Como se mencionó inicialmente el CMS fue desarrollado en CodeIngniter y en este caso, comparte la base de datos con los foros.  El primer paso es obtener la información de conexión a la base de datos desde los archivos de configuración del CMS.

define('BASEPATH', 1);
include('../pt/system/application/config/database.php');

Teniendo esta información se realiza una conexión global a la base de datos utilizando las funciones de PDO.

$pdo = null;

try
{
    $pdo = new PDO("mysql:host={$db['default']['hostname']};dbname={$db['default']['database']}",
                   $db['default']['username'],
                   $db['default']['password'],
                   array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
    );
}
catch (PDOException $e)
{
    print "Error!: " . $e -> getMessage() . "<br/>";
    die();
}

Funciones de apoyo del CMS.

Se crean algunas funciones básicas que servirán de soporte para el nuevo procedimiento de autenticación.

function existeUsuarioEnPlataforma($usuario)
{
    global $pdo;

    $stmt = $pdo -> prepare("SELECT id_usuario FROM `usuarios` WHERE user=?");

    $results = $stmt -> execute(array($usuario));

    return ($stmt -> rowCount() > 0);
}

Verifica si el usuario identificado por el nombre de usuario ($usuario) existe o no registrado en el CMS.  Retorna true o false según se encuentren o no efectivamente registrados.

function autenticarUsuarioEnPlataforma($usuario, $contrasena)
{
    global $pdo;

    $stmt = $pdo -> prepare("SELECT id_usuario FROM `usuarios` WHERE user=? AND pass=?");

    $results = $stmt -> execute(array($usuario, MD5($contrasena)));

    return ($stmt -> rowCount() > 0);
}

Verifica que el usuario identificado con el nombre de usuario ($usuario) tenga efectivamente a $contraseña como clave de usuario.  Retorna true en caso de coincidir, false de lo contrario.

Nótese como el CMS almacena sus contraseñas en MD5 motivo por el cual es necesario realizar esta conversión antes de realizar la comparación en la consulta SQL.

Funciones de apoyo de los foros.

De manera análoga, estas funciones facilitan la manipulación de la información en las tablas del sistema de foros, las cuales tienen configurado el prefijo foros_ en sus tablas.

function existeUsuarioEnForos($usuario)
{
    global $pdo;

    $stmt = $pdo -> prepare("SELECT ID_MEMBER FROM foros_members WHERE memberName = ?");

    $results = $stmt -> execute(array($usuario));

    if($stmt -> rowCount() == 0)
        return false;

    $fuente = $stmt -> fetch();

    return $fuente['ID_MEMBER'];
}

Verifica si el usuario identificado por el nombre de usuario ($usuario) existe o no registrado en el foro .  Retorna el identificador del usuario si existe o false de lo contrario.

function traerUsuario($usuario, $contrasena)
{
    global $pdo, $sourcedir, $modSettings;

    if(!existeUsuarioEnPlataforma($usuario))
        return false;

    $stmt = $pdo -> prepare("SELECT * FROM `usuarios` WHERE user=?");

    $results = $stmt -> execute(array($usuario));

    $fuente = $stmt -> fetch();

    require_once($sourcedir . '/Subs-Members.php');

    $regOptions = array('username' => $fuente['user'],
                        'password' => $contrasena,
                        'password_check' => $contrasena,
                        'email' => $fuente['email'],
                        'posts' => '0',
                        'ID_GROUP' => '0',
                        'ID_POST_GROUP' => '4');

    $memberID = registerMember($regOptions);

    return ($memberID > 0);
}

Obtiene la información del usuario de plataforma identificado por $usuario y $contrasena, y lo agrega en la base de datos del foro a través de su API interno.  A este nivel no se realiza ninguna verificación de autenticación.

function activarUsuarioForo($usuario, $estado='1')
{
    global $pdo;

    $stmt = $pdo -> prepare("UPDATE foros_members SET is_activated = ? WHERE memberName = ?");

    $control = $stmt -> execute(array($estado, $usuario));

    return $control;
}

Activa al usuario del foro recién creado, el cual por defecto siempre queda en espera de confirmación (is_activated = 3).

function actualizarContrasenaForo($usuario, $contrasena)
{
    global $pdo;

    $passwd       = sha1(strtolower($usuario) . $contrasena);
    $passwordSalt = substr(md5(mt_rand()), 0, 4);

    $stmt = $pdo -> prepare("UPDATE foros_members SET passwd = ?, passwordSalt = ? WHERE memberName = ?");

    $control = $stmt -> execute(array($passwd, $passwordSalt, $usuario));

    return $control;
}

Actualiza la $contrasena del $usuario en el sistema de foros.  Nótese como la $contrasena se recibe de manera plana y se almacena en la base de datos de SMF en SHA1.

Acerca del comportamiento del login de SMF.

Algo que se debe tener en cuenta acerca del comportamiento del login de SMF es que desde el cliente (navegador del usuario) se envía la contraseña ya en su propia representación SHA1 así que si la contraseña con que se va a comparar no está en la misma representación, como en este caso, el CMS la almacena en MD5, se tiene entonces un problema.

Para solventar esto, el sistema de foros permite solicitar nuevamente la contraseña al usuario (retry) y en este segundo intento la envía totalmente plana para su manipulación libre.  En este punto es donde la aprovechamos para verificar la autenticación del usuario contra la información del CMS, crear el usuario en el foro si no existe y actualizar la contraseña en el foro previniendo que haya sido actualizada antes en el CMS.

Implementación del nuevo proceso de autenticación.

function user_validate()
{
    $user = $_REQUEST['user'];
    $id   = 0;

    // Verificar un usuario proveniente de plataforma.

    if(existeUsuarioEnPlataforma($user))
    {
        $id = existeUsuarioEnForos($user);

        // Nueva lógica de autenticación.
    }

    // El usuario no existe en plataforma, verificar los nativos del foro.

    return false;
}

La base de la nueva autenticación es entonces la definición de la función user_validate que corresponde con la especificada inicialmente durante la configuración de los hooks.

En ella se verifica inicialmente si el usuario que intenta acceder al sistema (login) se encuentra presente o no en la base de datos de usuarios del CMS.  En caso de no estar presente el control de la autenticación se libera para que el sistema verifique sus usuarios propios, esto permite definir también usuarios internos del foro que no estén presentes en el CMS.

El paso siguiente consiste en determinar si el usuario existe previamente o no en el foro, es decir, no es su primer ingreso al mismo.  A partir de esta respuesta se toma uno de los siguientes caminos.

  • Si no está disponible la contraseña plana (primer login) se solicita nuevamente (segundo login).
  • Si el usuario no existe en el foro entonces …
    • Se verifica que el nombre de usuario y la contraseña coincidan.
    • Se crea el usuario en el foro.
    • Se activa al nuevo usuario.
  • Si el usuario ya existía previamente en el foro entonces …
    • Se verifica que el nombre de usuario y la contraseña coincidan.
    • Se actualiza la contraseña en el foro por si ha sido actualizada previamente en el CMS.

La implementación de estas acciones se detalla a continuación.  En el caso en que el usuario no exista previamente en el foro se ejecuta la siguiente lógica del negocio.

if($id === false)
{
    // Esta disponible la contrasena (2do. login).

    if(isset($_REQUEST['passwrd']) && !empty($_REQUEST['passwrd']))
    {
        $auth = autenticarUsuarioEnPlataforma($user, $_REQUEST['passwrd']);

        if($auth)                           // La informacion del usuario es valida.
        {
            $id = traerUsuario($user, $_REQUEST['passwrd']);

            activarUsuarioForo($user);

            return false;                   // La autenticacion tiene EXITO.
        }
        else                                // La informacion del usuario NO es valida.
            return true;                    // La autenticacion FALLA.
    }
    else                                    // No esta disponible la contrasena (1er. login).
        return "retry";                     // Vuelva a solicitar el login para confirmar.
}

En el caso en que el usuario si exista previamente en el foro, el procedimiento es mas simple y se ejecuta la siguiente lógica del negocio.

// El usuario existe en el foro.

if($id !== false)
{
    // Esta disponible la contrasena (2do. login) -> actualizar la contraseña en el foro.

    if(isset($_REQUEST['passwrd']) && !empty($_REQUEST['passwrd']))
    {
        $auth = autenticarUsuarioEnPlataforma($user, $_REQUEST['passwrd']);

        if($auth)                           // La informacion del usuario es valida.
        {
            actualizarContrasenaForo($user, $_REQUEST['passwrd']);

            return false;                   // La autenticacion tiene EXITO.
        }
        else                                // La informacion del usuario NO es valida.
            return true;                    // La autenticacion FALLA.
    }
    else                                    // No esta disponible la contrasena (1er. login).
        return "retry";                     // Vuelva a solicitar el login para confirmar.
}

Conclusiones.

Hasta ahora, con poca experiencia en su uso, SMF parece ser un sistema de foros útil y práctico.  El proceso de determinar esta unificación de la autenticación fue largo y doloroso debido a la poca documentación que pude encontrar acerca del API, la cual en su mayor parte se encuentra diseminada en los foros de su sitio web.

En términos de su código parece tener un nivel decente de flexibilidad, el concepto de hooks permite realizar modificaciones interesantes al flujo normal del software, sin embargo es difícil formalizarlo al no contar con información suficiente de su lógica de funcionamiento.  Algunas partes del software, en especial los temas, adolecen de separación MVC por lo que se hace necesario editar funciones, entre comillas, que retornan el código HTML que se desea modificar siendo esto molesto, difícil y propenso a errores, sobretodo si se compara con tener archivos PHP separados con la lógica y HTML con la presentación como sería una mejor opción.

Finalmente, después de varios días de luchas y de pruebas, la unificación de la autenticación está funcionando y se ve bien, de todas maneras es mi primer acercamiento a este software por lo que no puedo garantizar que sea la mejor aproximación.  Como siempre, estoy abierto a sugerencias constructivas.

Enlaces.

Presentación de Sismos recientes en Colombia

Sismos recientes en Colombia
Sismos recientes en Colombia

Sismos recientes en Colombia es una aplicación de agregación de contenido que permite visualizar de manera gráfica en la geografía Colombiana la ubicación de los sismos que han sucedido en el territorio nacional, tanto los [últimos diez] destacados por su intensidad como la actividad sísmica completa de la semana anterior.  Esta información se actualiza casi en tiempo real, con una diferencia de al rededor de 3o minutos después del suceso, gracias a los servicios de la Red Sismológica Nacional del Instituto Colombiano de Geología y Minería.

La base del proyecto fue desarrollado en una semana de descanso utilizando casi por completo Software Libre.  Se construyó utilizando PHP, HTML/CSS/Javascript/AJAX, jQuery, CodeIgniter, SwiftMailer, SQLiteEclipse PDT y Google Maps.  La aplicación aún se encuentra en progreso, alias beta, así que aún tengo pensando hacerle varias mejoras y complementos.

Actualmente el sitio web permite consultar la información de los sismos destacados y de los sismos diarios de la semana en curso, la información georreferenciada se despliega en el mapa gráficamente desde donde se puede acceder a la información disponible del sismo.  El último sismo destacado sucedido cuenta con un enlace a la RSNC donde se amplía su información.  Las fechas y horas hacen referencia al territorio colombiano, es decir, GMT-5.  Adicionalmente la aplicación cuenta con un sistema de caché que permite agilizar la presentación de información cuando esta se encuentra fresca en el sistema y de refrescarla en ciertos intervalos, disminuyendo la consulta a las fuentes de datos y el tiempo de generación del contenido.

He planeado realizar las siguientes mejoras a la aplicación sin ningún orden necesario.

  • Mejorar los colores de la presentación.  Los actuales son de prueba, estoy buscando a alguien que si sepa del tema para que me asesore.
  • Establecer tooltips informativos para facilitar la utilización del sitio.
  • Verificar la viabilidad de crear un mapa del sitio (sitemap) que facilite las búsquedas de contenido en el sitio.
  • Establecer un procedimiento cron que actualice la inforamción aún sin la consulta de visitantes.
  • Establecer la comunicación con otras aplicaciones como Twitter.
  • Establecer valores finales para la duración de los cachés de información.
  • Implementar un módulo de Contáctenos.
  • Crear una versión móvil que muestre la información resumida.
  • Implementar un indicador que le muestre al usuario en que sección del sistema se encuentra.
  • Verificar el cumplimiento [en lo posible] de XHTML.
  • Establecer la viabilidad de incluír información de sismos de otros lugares del mundo como valor agregado al sitio.

La aplicación, [con optimismo] al igual que las cosas buenas del mundo, se encuentra licenciado bajo CC NC-SA así que su código será liberado tan pronto como termine de implementar las principales mejoras y lo documente para dejarlo apto para el consumo humano.

Si alguien tiene alguna sugerencia acerca de como mejorar esta aplicación me gustaría mucho que lo compartiera conmigo.

Enlaces.

Remover el index.php de las rutas de CodeIgniter

Para hacer esto se hace uso de las capacidades de sobreescritura de URLs de Apache.  Se debe agregar un archivo .htaccess en la raíz del sitio web con el siguiente contenido.

RewriteEngine on
RewriteCond $1 !^(index.php|images|robots.txt)
RewriteRule ^(.*)$ index.php/$1 [L]

En servidores de Dreamhost he tenido un mejor éxito utilizando la siguiente versión.

RewriteEngine on
RewriteCond $1 !^(index.php|public|user_guide|media|robots.txt)
RewriteRule ^(.*)$ index.php?/$1 [L]

Adicionalmente es necesario indicarle a CodeIgniter que ya no agregue artificialmente el archivo index.php a los URL de la siguiente manera.  Edite el archivo config/config.php y modifique la siguiente llave.

$config['index_page'] = "index.php";

De forma que quede de la siguiente manera.

$config['index_page'] = "";

Enlaces.

Instalación de CodeIgniter 1.7.x en Linux

Introducción.

CodeIgniter es un framework muy interesante para el desarrollo de aplicaciones web.  Lo he estado utilizando por dos años y ha sido muy útil.  Su curva de aprendizaje no es muy pronunciada lo que ha facilitado que desarrolladores no muy experimentados lo aprendan a usar rapidamente.  Su misión es la de proveer una base para el desarrollo de aplicaciones web con PHP brindándonos una serie de herramientas y estructuras facilitadoras que, gracias a su arquitectura desacoplada, no son obligatorias ni restringen el uso de facilidades de terceros.  Es un framework general, es útil y flexible, y no es el mas complejo o restrictivo del mercado.

Ventajas tiene muchas, algunas de las cuales ya he mencionado: fácil aprendizaje, flexible, desacoplado, buen desempeño en términos de tiempo de ejecución y consumo de memoria, muy utilizado y bien documentado.  En pocas palabras, utilizar CodeIgniter le permitirá desarrollar PHP de la misma forma como lo realizaba anteriormente pero de una manera mas estructurada y con algunas herramientas que le permitirán agilizar su implementación.

Como desventajas se encuentra el hecho de que su desarrollo recaiga sobre un sólo hombre, Derek Allard, haciendo que su proceso de actualización fuera lento en comparación con los deseos de la comunidad; sin embargo he estado leyendo un poco acerca de que ahora es un grupo de personas quienes lideran el desarrollo del framawork motivo por el cual me imagino que esto ha cambiado.  Una desventaja muy mencionada es su empeño por seguir soportando PHP4 además de PHP5 impidiéndole tomar ventaja de las mejoras que trajo el lenguaje en su última versión.  Otro punto en contra de CodeIgniter es que podría mejorarse su orientación a objetos en varios aspectos (probablemente producido por lo mencionado anteriormente), por ejemplo, los ayudantes (helpers) son en realidad una librería de funciones.

Por estos motivos un subconjunto de sus seguidores decidió hacer un fork del proyecto para implementar su propias mejoras.  Este fue el nacimiento de Kohana.  Con este nuevo framework estoy desarrollando un proyecto desde hace unos seis meses y su implementación ha sido muy placentera, aunque no se si por las mejoras en el framework o por lo la calidad y lo interesante del proyecto.  Sobra decir que la migración de CodeIgniter a Kohana es un proceso indoloro.  Pero no todo es color de rosa, actualmente la principal ventaja de Kohana es su propio punto débil: su comunidad activa y ávida de mejoras realiza modificaciones al framework frecuentemente haciendo que, según leo en los foros, la próxima versión que saldrá en el presente año no será compatible con versiones anteriores, haciendo que en este momento no sea idónea para desarrollar proyectos a largo plazo.  CodeIgniter por su parte a probado ser bastante estable y a documentar los cambios necesarios para actualizar sus versiones, que de paso no sobra decirlo, siempre es conveniente mantenerse al día con la última versión disponible.

Obtención del framework.

El objetivo de este paso es el de obtener los archivos de la distribución mas reciente de CodeIgniter que se va a instalar.

$ cd /home/www

$ wget http://codeigniter.com/download.php

Instalación del framework en una ubicación privada.

Como se mencionó, el framework se instalará en una ubicación privada y podrá ser compartido por múltiples aplicaciones.  Se crea un enlace dinámico (current) para facilitar la actualización de versiones del framework.

$ unzip CodeIgniter_1.7.1.zip

$ rm CodeIgniter_1.7.1.zip

$ mkdir codeigniter

$ mv CodeIgniter_1.7.1 codeigniter/1.7.1

$ cd codeigniter

$ ln -s 1.7.1/ current

Creación de una aplicación de prueba.

Esta aplicación de prueba deberá mostrar al usuario el controlador (welcome) y la vista (welcome_message) que trae por defecto CodeIgniter.

$ cd /home/www

$ mkdir public/Demo1

$ cp -rf codeigniter/current/system/application/* public/Demo1

$ cp codeigniter/current/index.php public/Demo1

$ rm public/Demo1/index.html

Configuración del controlador frontal de la nueva aplicación.

Es necesario indicarle al controlador frontal de la aplicación donde encontrar a la distribución del framework (system) y donde encontrar los archivos de la aplicación (application).

$ vi public/Demo1/index.php

error_reporting(E_ALL);

$system_folder = “/home/www/codeigniter/current/system”;

$application_folder = “/home/www/public/Demo1”;

Verificación del funcionamiento inicial del sitio.

Si los pasos anteriores fueron exitosos, si se consulta el sitio web deberá obtenerse un página similar a la siguiente.  Téngase en cuenta que deberá reemplazarse el nombre del servidor (localhost) por el que sea necesario si este no se encuentra en su mismo equipo.

Visitar con un navegador web la siguiente dirección.

http://localhost/Demo1/

Enlaces.

Documentación de CodeIgniter en español

CodeIgniter es un framework MVC para el desarrollo de aplicaciones web.  Es una alternativa interesante ya que es útil, facilita el desarrollo pero a su vez es lo suficientemente desacoplado para no obligarle a utilizar nada que no se desee usar.  Entre sus ventajas se cuentan su velocidad, su buena documentación, tutoriales y el soporte de una comunidad de tamaño decente.

Si por alguna extraña razón, no es de su agrado la lectura de la documentación en inglés también puede elegir otros idiomas.  Encontré que hay dos proyectos que se encuentran traduciendo la documentación al español actualmente.

Para mas información consultar el sitio Wiki de CodeIgniter acerca del estado de los proyectos de traducción.

Introducción a Kohana Framework

KohanaPHP  es  un  framework  liviano  y  flexible  para  el desarrollo  de  aplicaciones  web  pequeñas  y  medianas con PHP.   Se basa  en CodeIgniter  al cual complementa con  una  mejor  apropiación  de  la  OO  y  PHP5,  así  como un mayor soporte brindado por toda una comunidad.

En  esta  sesión  se  expondrán  los  conceptos  del framework  necesarios  para  empezar  a  diseñar  las aplicaciones  web,  sin  embargo  no  se  profundizarán  en detalles  específicos,  se  recomienda complementar  este documento  junto  con  el  wiki  de  la  sección  de documentación.

 
  • Introducción.
  • Características.
  • Instalación.
  • Directorios.
  • Configuración.
  • URLs.
  • Controladores.
  • Librerías.
  • Ayudantes.
  • Vistas.
  • Modelos.
  • Eventos.
  • Hooks.
  • Manejo de errores.
  • Módulos.
  • Recursos del framework.