Por casualidad me topé con Latch, una app que permite activar o desactivar la ejecución de un proceso. En un principio elevenpaths la creó para que un usuario active o desactive la identificación o login en una web, permitiendo bloquear el acceso aunque se introduzcan correctamente el nombre de usuario y contraseña, permitiendonos desactivar la cuenta si el usuario no la está usando, pero hace poco pasó al siguiente nivel, implantandose con éxito con infinidad de usos, desde iniciar sesión en windows hasta permitir modificar un archivo del sistema, ¿flipante no?.
El usuario gestiona este acceso con la app Latch que actualmente está disponible gratuitamente para android, ios y windows phone en sus respectivas stores.
El proceso para el usuario no puede ser más sencillo, la idea es simular la utilización de candados con posicion de bloqueado o desbloquedo, lo que permite con solo un toque bloquear el acceso a toda nuestra vida digital, el acceso a una web, a una sección, a nuestra sesión de windows, ect las posibilidades son infinitas. Cabe destacar que si se produce una solicitud del estado de un candado y este se encuentra bloqueado, llegará una notifiación al dispositivo alertando de un posible robo de contraseña.
La acción de sincronización que permite a un usuario dar de alta un candado se denomina pareado por ejemplo para habilitar el candado de acceso a la web de telefónica con nuestro latch debemos parear nuestro usuario con nuestra app. Esta acción se realiza en el sitio una vez estamos logueados introduciendo un código en la web que genera la app. Una vez estamos pareados no tendremos que introducir nunca más ningún código, esta es la gran diferencia con otras alternativas como goolge authenticator.
A los desarrolladores Latch también les permite crear operaciones que no son más que subcandados, asi con un solo pareado los usuarios podrán gestionar diferentes candados para diferentes acciones, por ejemplo, se podría tener un subcandado para el login, otro para enviar mensajes privados, otro para crear un artículo, otro para editar la cuenta de usuario, ect.
Dicho esto, pasamos a la instalación de Latch para phpost.
El proceso no es más que una comprobación del estado de un candado (on,off) con un if.
Tenemos que crearnos una cuenta de desarrollador en Latch
Una vez logueados vamos a Mis aplicaciones y damos en crear nueva aplicación.
Le ponemos un nombre, el nombre que le pongamos será el nombre visible por lo usuarios que lo utilicen pero se puede cambiar en cualquier momento.
Una vez creada pasaremos a la página de edición de la aplicación, podremos cambiar la imagen por el logo de nuestra web entre otras opciones. También tendremos visibles los códigos «ID de aplicación» y «Secreto» que nos harán falta para configurar la api de Latch en nuestra página web.
Los códigos son ligeramente diferentes si tenemos operaciones (subcandados) o no, pero lo explicaré luego porque solo es cambiar dos lineas. Para la instalación utilizaré operaciones de login y crear post, asi que en la misma ventana en la parte de abajo en «operaciones» damos en «añadir», ponemos un nombre y damos otra vez en «añadir». Se creará la operación con su id de operación que también nos hace falta. El resto de opciones como 2º factor OTP aun no lo probé. El email y teléfono no es necesario ponerlo para utilizar la aplicación.
Ok, tenemos nuestra aplicación de latch creada, pero antes de salir de la web vamos a descargar los archivos necesarios.
En el menú de la izquierda vamos a «plugins y SDKs» , en la parte inferior de la página en SDKs buscamos y descargamos los SDKs de php.
Descomprimimos y renombramos la carpeta que contiene los archivos a «latch» (sin comillas) subimos los archivos a la raiz de la web de forma que queden latch/archivos.php
Ahora creamos una tabla para guardar las ids de los usuario en la base de datos:
ALTER TABLE `u_miembros` ADD `user_latch` TEXT NOT NULL
Luego nos vamos a header.php y buscamos:
/* * ------------------------------------------------------------------- * Agregamos los archivos globales * ------------------------------------------------------------------- */ // Contiene las variables de configuración principal include 'config.inc.php';
Debajo agregamos:
//Archivos api Latch include_once("latch/Latch.php"); include_once("latch/LatchResponse.php"); include_once("latch/Error.php");
Luego buscamos:
// Mensajes $tsMP = new tsMensajes();
y debajo agregamos:
//Credenciales API Latch $latchid = array( 'appId' => 'TU APP ID', 'secret' => 'TU CÓDIGO SECRETO', );
Si tenemos operaciones creadas también las añadimos, por ejemplo en este caso quedaría así:
//Credenciales API Latch $latchid = array( 'appId' => 'TU APP ID', 'secret' => 'TU CÓDIGO SECRETO', 'opLogin' => 'ID OPERACIÓN', 'opCrearpost' => 'ID OPERACIÓN', );
El nombre de la id (columna izquierda) podeis poner el que querais por ejemplo en vez de opLogin podeis poner solo login pero debeis fijaros para poner el mismo nombre cuando necesitemes los ids.
Ahora nos vamos a la funcion en la que queremos incluir la comprobación de estado. En el caso del login es diferente porque al no estar logueado tenemos que hacer la consulta con el nombre de usuario del formulario de login, una vez el usuario está logueado no la necesitaremos porque usaremos variables nativas de Risus (phpost).
Nos vamos a c.user.php y buscamos:
function loginUser($username, $password, $remember = FALSE, $redirectTo = NULL){ global $tsCore;
Reemplazamos por:
function loginUser($username, $password, $remember = FALSE, $redirectTo = NULL){ global $tsCore, $latchid;
En la misma función buscamos:
// CHECAMOS if($data['user_password'] != $pp_password){ return '0: Tu contraseña es incorrecta.'; } else { if($data['user_activo'] == 1){ // Actualizamos la session $this->session->update($data['user_id'], $remember, TRUE); // Cargamos la información del usuario $this->loadUser(true); // COMPROBAMOS SI TENEMOS QUE ASIGNAR MEDALLAS $this->DarMedalla(); /* REDERIGIR */ if($redirectTo != NULL) $tsCore->redirectTo($redirectTo); // REDIRIGIR else return TRUE; } else return '0: Debes activar tu cuenta'; } }
Reemplazamos por:
// CHECAMOS if($data['user_password'] != $pp_password){ return '0: Tu contraseña es incorrecta.'; } else { // LATCH -- OBTENEMOS LATCH ID DE LA BASE DE DATOS POR EL USER NAME DEL FORMULARIO LOGIN $query2 = db_exec(array(__FILE__, __LINE__), 'query', 'SELECT user_latch FROM u_miembros WHERE user_name = \''.$username.'\' LIMIT 1'); $data2 = db_exec('fetch_assoc', $query2); $accountId = $data2['user_latch']; //LATCH -- SI EXISTE LATCH ID CONSULTAMOS EL ESTADO DEL CANDADO if (($accountId != -1) && ($accountId != '')){ $latchapi = new Latch($latchid['appId'], $latchid['secret']); $latchStatusResponse = $latchapi->operationStatus($accountId, $latchid['opLogin']); $statusData = $latchStatusResponse->getData()->operations; $operation = $statusData->{$latchid['opLogin']}; } //LATCH -- SI TENEMOS EL ESTADO DEL CANDADO Y SU ESTADO ES OFF PARAMOS EL LOGIN, SI NO LOGUEMAOS. if (isset($operation) && $operation->status == 'off'){ return '0: Latch bloqueó el acceso'; }else{ if($data['user_activo'] == 1){ // Actualizamos la session $this->session->update($data['user_id'], $remember, TRUE); // Cargamos la información del usuario $this->loadUser(true); // COMPROBAMOS SI TENEMOS QUE ASIGNAR MEDALLAS $this->DarMedalla(); /* REDERIGIR */ if($redirectTo != NULL) $tsCore->redirectTo($redirectTo); // REDIRIGIR else return TRUE; } else return '0: Debes activar tu cuenta'; } } }
Si no tuvieramos operaciones habría que reemplazar en el código anterior:
$latchStatusResponse = $latchapi->status($accountId, $latchid['opLogin']);
por:
$latchStatusResponse = $latchapi->status($accountId);
y:
$operation = $statusData->{$latchid['opLogin']};
por:
$operation = $statusData->{$latchid['appId']};
Esto solo sería válido si el login se realiza con el nombre de usuario, si estuviera el mod de login con correo electrónico activado habria que cambiar la consulta, pero no lo miré.
Ahora vamos a poner poner otra comprobación al crear un tema nuevo como ejemplo para que podais añadir una operación en donde querais.
En c.post.php buscamos:
function newPost(){ global $tsCore, $tsUser, $tsMonitor, $tsActividad;
Reemplazamos por:
function newPost(){ global $tsCore, $tsUser, $tsMonitor, $tsActividad, $latchid;
más abajo en la misma función buscamos:
// INSERTAMOS $_SERVER['REMOTE_ADDR'] = $_SERVER['X_FORWARDED_FOR'] ? $_SERVER['X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; if(!filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) { die('0: Su ip no se pudo validar.'); } if(db_exec(array(__FILE__, __LINE__), 'query', 'INSERT INTO `p_posts` (post_user, post_category, post_title, post_body, post_date, post_tags, post_ip, post_private, post_block_comments, post_sponsored, post_sticky, post_smileys, post_visitantes, post_status) VALUES (\''.$tsUser->uid.'\', \''.(int)$postData['category'].'\', \''.$postData['title'].'\', \''.$postData['body'].'\', \''.$postData['date'].'\', \''.$postData['tags'].'\', \''.$_SERVER['REMOTE_ADDR'].'\', \''.(int)$postData['private'].'\', \''.(int)$postData['block_comments'].'\', \''.(int)$postData['sponsored'].'\', \''.(int)$postData['sticky'].'\', \''.(int)$postData['smileys'].'\', \''.(int)$postData['visitantes'].'\', '.(!$tsUser->is_admod && ($tsCore->settings['c_desapprove_post'] == 1 || $tsUser->permisos['gorpap'] == true) ? '\'3\'' : '\'0\'').')')) { $postID = db_exec('insert_id'); // Si está oculto, lo creamos en el historial e.e if(!$tsUser->is_admod && ($tsCore->settings['c_desapprove_post'] == 1 || $tsUser->permisos['gorpap'] == true)) db_exec(array(__FILE__, __LINE__), 'query', 'INSERT INTO `w_historial` (`pofid`, `action`, `type`, `mod`, `reason`, `date`, `mod_ip`) VALUES (\''.(int)$postID.'\', \'3\', \'1\', \''.$tsUser->uid.'\', \'Revisión al publicar\', \''.time().'\', \''.$_SERVER['REMOTE_ADDR'].'\')'); $time = time(); // ESTADÍSTICAS db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE `w_stats` SET `stats_posts` = stats_posts + \'1\' WHERE `stats_no` = \'1\''); // ULTIMO POST db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE u_miembros SET user_lastpost = \''.$time.'\' WHERE user_id = \''.$tsUser->uid.'\''); // AGREGAR AL MONITOR DE LOS USUARIOS QUE ME SIGUEN $tsMonitor->setFollowNotificacion(5, 1, $tsUser->uid, $postID); // REGISTRAR MI ACTIVIDAD $tsActividad->setActividad(1, $postID); // SUBIR DE RANGO? $this->subirRango($tsUser->uid); // return $postID; } else return show_error('Error al ejecutar la consulta de la línea '.__LINE__.' de '.__FILE__.'.', 'db');
y reemplazamos por:
// INSERTAMOS $_SERVER['REMOTE_ADDR'] = $_SERVER['X_FORWARDED_FOR'] ? $_SERVER['X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; if(!filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) { die('0: Su ip no se pudo validar.'); } // LATCH -- OBTENEMOS ID USUARIO $accountId = $tsUser->info['user_latch']; //LATCH -- SI EXISTE LATCH ID CONSULTAMOS EL ESTADO DEL CANDADO if (($accountId != -1) && ($accountId != '')){ $latchapi = new Latch($latchid['appId'], $latchid['secret']); $latchStatusResponse = $latchapi->operationStatus($accountId, $latchid['opCrearpost']); $statusData = $latchStatusResponse->getData()->operations; $operation = $statusData->{$latchid['opCrearpost']}; } //LATCH -- SI TENEMOS EL ESTADO DEL CANDADO Y SU ESTADO ES OFF DEVOLVEMOS MENSAJE DE ERROR, SI NO CREAMOS POST. if (isset($operation) && $operation->status == 'off'){ return 'Latch bloqueó la creación del post'; }else{ if(db_exec(array(__FILE__, __LINE__), 'query', 'INSERT INTO `p_posts` (post_user, post_category, post_title, post_body, post_date, post_tags, post_ip, post_private, post_block_comments, post_sponsored, post_sticky, post_smileys, post_visitantes, post_status) VALUES (\''.$tsUser->uid.'\', \''.(int)$postData['category'].'\', \''.$postData['title'].'\', \''.$postData['body'].'\', \''.$postData['date'].'\', \''.$postData['tags'].'\', \''.$_SERVER['REMOTE_ADDR'].'\', \''.(int)$postData['private'].'\', \''.(int)$postData['block_comments'].'\', \''.(int)$postData['sponsored'].'\', \''.(int)$postData['sticky'].'\', \''.(int)$postData['smileys'].'\', \''.(int)$postData['visitantes'].'\', '.(!$tsUser->is_admod && ($tsCore->settings['c_desapprove_post'] == 1 || $tsUser->permisos['gorpap'] == true) ? '\'3\'' : '\'0\'').')')) { $postID = db_exec('insert_id'); // Si está oculto, lo creamos en el historial e.e if(!$tsUser->is_admod && ($tsCore->settings['c_desapprove_post'] == 1 || $tsUser->permisos['gorpap'] == true)) db_exec(array(__FILE__, __LINE__), 'query', 'INSERT INTO `w_historial` (`pofid`, `action`, `type`, `mod`, `reason`, `date`, `mod_ip`) VALUES (\''.(int)$postID.'\', \'3\', \'1\', \''.$tsUser->uid.'\', \'Revisión al publicar\', \''.time().'\', \''.$_SERVER['REMOTE_ADDR'].'\')'); $time = time(); // ESTADÍSTICAS db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE `w_stats` SET `stats_posts` = stats_posts + \'1\' WHERE `stats_no` = \'1\''); // ULTIMO POST db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE u_miembros SET user_lastpost = \''.$time.'\' WHERE user_id = \''.$tsUser->uid.'\''); // AGREGAR AL MONITOR DE LOS USUARIOS QUE ME SIGUEN $tsMonitor->setFollowNotificacion(5, 1, $tsUser->uid, $postID); // REGISTRAR MI ACTIVIDAD $tsActividad->setActividad(1, $postID); // SUBIR DE RANGO? $this->subirRango($tsUser->uid); // return $postID; } else return show_error('Error al ejecutar la consulta de la línea '.__LINE__.' de '.__FILE__.'.', 'db');
Ahora vamos a crear la opción de pareado y despareado. Decidí ponerla en privacidad por comodidad y hacerla por ajax para retornar con un bonito efecto si el pareado o despareado se realizo correctamente, esto implica modificar un par de archivos más pero vale la pena.
En cuenta.php buscamos:
} elseif($action == 'save'){ echo $tsCore->setJSON($tsCuenta->savePerfil());
debajo agregamos:
} elseif($action == 'parear_latch'){ echo $tsCuenta->parear_latch(); } elseif($action == 'desparear_latch'){ echo $tsCuenta->desparear_latch();
En c.cuenta.php buscamos la funcion yFollow:
/* yFollow() */ function yFollow($user_id){ global $tsUser; // YO LE SIGO? $query = db_exec(array(__FILE__, __LINE__), 'query', 'SELECT follow_id FROM u_follows WHERE f_id = \''.(int)$tsUser->uid.'\' AND f_user = \''.(int)$user_id.'\' AND f_type = \'1\' LIMIT 1'); $data = db_exec('num_rows', $query); // return ($data > 0) ? true : false; }
y despues agregamos:
/* parear latch */ function parear_latch(){ global $tsCore, $tsUser, $latchid; $dato = array( 'codigo' => $tsCore->setSecure($_POST['codigo']), ); // Obligatorio foreach($dato as $key => $val){ $val = trim(preg_replace('/[^ A-Za-z0-9]/', '', $val)); $val = str_replace(' ', '', $val); if(empty($val)) return '2: Completa los campos obligatorios.'; } //get pairing code from user $latchPairingCode = isset($dato['codigo']) ? $dato['codigo'] : ''; //New api instance and pairing $latchapi = new Latch($latchid['appId'], $latchid['secret']); $latchPairResponse = $latchapi->pair($latchPairingCode); $latchResponseData = $latchPairResponse->getData(); $accountId = isset($latchResponseData->accountId) ? $latchResponseData->accountId : ''; //Comprobamos y guardamos id if ($accountId != '' && (db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE u_miembros SET user_latch = \''.$accountId.'\' WHERE user_id = \''.$tsUser->uid.'\''))){ return '1: Pareado correctamente'; }else return '0: Ocurrio un error al parear la cuenta'; } /* Desparear latch */ function desparear_latch(){ global $tsUser, $latchid; $accountId = $tsUser->info['user_latch']; $latchapi = new Latch($latchid['appId'], $latchid['secret']); if (($accountId != -1) && ($accountId != '')){ $unpairResponse = $latchapi->unpair($accountId); $unpairResponseData = $unpairResponse->getData(); if (db_exec(array(__FILE__, __LINE__), 'query', 'UPDATE u_miembros SET user_latch = "" WHERE user_id = \''.$tsUser->uid.'\'')){ return '1: Despareado correctamente';} }else return '0: Ocurrio un problema al desparear la cuenta'; }
Ahora vamos a m.cuenta_config.tlp y buscamos:
</fieldset>
y antes agregamos:
<div class="field"> <label>Latch</label> <div class="input-fake"> {if $tsUser->info.user_latch} <div style=" width:310px;"><p>Tu cuenta ahora está pareada con Latch. Si quieres dejar de usar Latch con tu cuenta, haz clic en el botón Desparear Latch.</p></div> <a href="#" class="mBtn btnOk" onclick="latch.desparear(); return false;" title="Desparear latch">Desparear Latch</a> {else} <div style=" width:300px;"><p>Latch es un servicio que te permite agregar un nivel extra de seguridad a tus cuentas y servicios online. Con un solo toque puedes bloquear tus cuentas cuando no las estés utilizando. Para saber más, entra en <a href="https://latch.elevenpaths.com" target="_blank">la web de Latch</a>.</p> <p>Para parear tu cuenta con Latch, descárgate la app de Latch en tu smartphone y sigue las instrucciones. Cuando termines, clica en Parear con latch e introduce el código de pareado.</p> </div> <a href="#" class="mBtn btnOk" onclick="latch.form(); return false;" title="Parear con latch">Parear con latch</a> {/if} </div> </div>
Por último en cuenta.js buscamos:
/*
isImageFile(filename)
*/
y antes agregamos:
var latch = { form: function() { var html = '<input type="text" placeholder="Código de pareado" id="codigo" size="60" class="require" /></br>'; mydialog.class_aux = 'RLatch'; mydialog.mask_close = false; mydialog.close_button = true; mydialog.show(true); mydialog.title('Parear con Latch'); mydialog.body(html); mydialog.buttons(true, true, 'Aceptar', 'latch.verify()', true, true, true, 'Cancelar', 'close', true, false); mydialog.center(); }, verify: function() { var cont = false; $('.require').each(function(){ if($(this).val() == '' || $(this).val() == 0 || $(this).length == 0) { $(this).focus(); cont = false; return false; } else { cont = true; } }); if(cont) latch.parear(); else return false; }, parear: function() { mydialog.procesando_inicio(); var codigo = $('.RLatch #codigo').val(); $.ajax({ type: 'POST', url: global_data.url + '/cuenta.php?action=parear_latch&ajax=true', data: 'codigo='+codigo, success: function(h){ if(h.charAt(0) == '0') { mydialog.alert('Error', h.substring(3)); } else if(h.charAt(0) == '1') { mydialog.alert('Exito', h.substring(3)); } else if(h.charAt(0) == '2') { alert('Completa los datos obligatorios.'); } else { mydialog.alert('Error', h); } mydialog.procesando_fin(); } }); }, desparear: function() { mydialog.procesando_inicio(); var codigo = $('.RLatch #codigo').val(); $.ajax({ type: 'POST', url: global_data.url + '/cuenta.php?action=desparear_latch&ajax=true', data: 'codigo='+codigo, success: function(h){ if(h.charAt(0) == '0') { mydialog.alert('Error', h.substring(3)); } else if(h.charAt(0) == '1') { mydialog.alert('Exito', h.substring(3)); } else { mydialog.alert('Error', h); } mydialog.procesando_fin(); } }); }, }
Eso sería todo, debo decir que las cuentas gratuitas de latch community solo permiten hasta un máximo de 50 pareados simultaneos por api, lo que en este tipo de webs es muy poco, si quereis más hay de pagar, sin embargo si eres una pyme española podrás tener Latch de forma totalmente gratuita y con la consola de acceso LST.
Aunque no seas pyme y no quieras pagar el uso de Latch podría ser interesante para las cuentas de staff que son las que tienen un control avanzado de la web.
Si quereis ponerlo solo para el staff debeis configurar los if para que compruebe que sois admon (administradores o moderadores) en los php y en el tpl para ocultar el formulario de pareado.
No hay demo porque las cuentas gratuitas solo permiten 50 usuarios pero hice un video de demostración para que veais el funcionamiento del mod.
Eso es todo. Si veis algún error comentarlo y lo solucionaré lo antes posible o si veis alguna forma mejor de implementarlo también sería interesante estudiarla.
Espero que os sirva de ayuda. Un saludo.