26/01/2015

Acceso (login) transparente mediante la API v3 de Google

Login, API, Google

Josep Sanz, Jordi Company

Desde el 17 de noviembre del 2014, las API v1 y v2 para acceder a los servicios de Google han dejado de funcionar forzando a todos los desarrolladores a migrar sus códigos a la v3 de la nueva API (https://developers.google.com/api-client-library/php/). En SaltOS, como el resto de aplicaciones, hemos tenido que hacer este cambio y ya vuelve todo a funcionar.

Antiguamente, el acceso al servicio se podía obtener usando el usuario+password de la cuenta del servicio. Actualmente esto ya no funciona así, sino que se debe obtener un token que nos permitirá el acceso futuro al servicio hasta que el usuario revoque el token. Para ello, es necesario crear una key desde la consola de desarrolladores de google developers. Hay 2 tipos: para aplicación online y por lo tanto, estará asociada a un host, o para aplicación instalada, la cual no estará asociada a un host. Este segundo caso es el que nosotros hemos empleado para crear la key de acceso al servicio pues la primera no nos vale.

Actualmente, cuando es necesario que el usuario se autentifique, lo que esta API propone es que redirijamos nuestra página hacia una página de Google donde se deberán seguir 3 pasos:

1) Si no esta autentificado en Google, se le solicita al usuario su email y password de Google.
2) Se le pregunta si desea dar permiso para que la aplicación acceda a sus datos de la aplicación deseada (en el caso de SaltOS, es Google Calendar).
3) Aparecerá una pantalla con una caja de texto que contendrá el token y se le indica al usuario que haga copy-paste de ese código a la aplicación.

Si la key creada tiene asociada un host, el punto 3 es automático, pues se redirige al host pasando por parámetro el token obtenido. Como en nuestro caso hemos creado una key para aplicación instalada, no tiene host y por lo tanto hay que hacer el copy-paste del token hacia SaltOS.

Como podéis ver, es un proceso tedioso para el usuario, y desde SaltOS, hemos programado una solución para ahorrarle todo este proceso extra causado por la falta de un mecanismo de autentificación directo desde la API v3 de Google. El "truco" consiste en hacer los pasos que quiere Google para obtener el token (token1 en adelante) mediante programación.

Este token, debe ser pasado al método authenticate del cliente de google y esto hará que obtengamos el segundo token (token2 en adelante) que será el que podremos usar en las futuras peticiones para acceder al servicio. Como resumen:

1) Si no tenemos el token2, obtener el token1 con el usuario+password y nos autentificamos mediante authenticate. El token2 que usaremos en las futuras autentificaciones lo obtendremos mediante getAccessToken.
2) si ya tenemos el token2, lo usamos para autentificar mediante setAccessToken

Para ello, SaltOS define las siguientes funciones:

function __gcalendar_getattr($html,$attr)

Esta función retorna el valor del primer atributo que encuentra en $html. Por ejemplo: si se pasa en $html un nodo <form> y se pasa en $attr la cadena "method", retornará GET o POST.

function __gcalendar_getnode($html,$name)

Esta función retornará la porción de código $html cuyo tag sea $name. Por ejemplo: si se pasa un formulario html y se pide el nodo <form>, esta función retornará el código html desde <form> hasta </form> con todo lo que contenga este formulario.

function __gcalendar_parse($html)

Esta función retorna en un array estos 3 datos del primer form que hay en $html:

1) El method.
2) El action.
3) Un array con una lista de clave=>valor donde clave es el name de los inputs y valor son los valores de los inputs.

function __gcalendar_request($url,$method="",$values=array(),$cookies=array(),$referer="")

Esta función usa una clase usada en SaltOS (http://www.phpclasses.org/package/3-PHP-HTTP-client-to-access-Web-site-pages.html) que permite hacer peticiones http de forma rápida.

Esta función hace una petición a la $url, usando el $method, enviando las variables del array $values, usando las cookies del array $cookies y usando el referer de $referer. Retorna un array con 3 elementos:

1) El body.
2) Un array con los headers.
3) Un array con las cookies.

function __gcalendar_token1($client,$login,$password) {
    $url=$client->createAuthUrl();
    list($body,$header,$cookies)=__gcalendar_request($url);
    // PROCESS LOGIN PAGE
    $parsed=__gcalendar_parse($body);
    $parsed["inputs"]["Email"]=$login;
    $parsed["inputs"]["Passwd"]=$password;
    $parsed["inputs"]["continue"]=str_replace("&","&",$parsed["inputs"]["continue"]);
    list($body,$header,$cookies)=__gcalendar_request($parsed["action"],$parsed["method"],$parsed["inputs"],$cookies,$url);
    // PROCESS ACCEPT PAGE
    $url=$parsed["action"];
    $parsed=__gcalendar_parse($body);
    $parsed["action"]=str_replace("&","&",$parsed["action"]);
    $parsed["inputs"]["submit_access"]="true";
    list($body,$header,$cookies)=__gcalendar_request($parsed["action"],$parsed["method"],$parsed["inputs"],$cookies,$url);
    // PROCESS TOKEN PAGE
    $html=__gcalendar_getnode($body,"input");
    $token1=__gcalendar_getattr($html,"value");
    return $token1;
}

Esta función es la más interesante de toda la explicación, porque usando las funciones anteriores, consigue obtener el primer token para acceder al servicio de Google Calendar.

Para ello lo que hace es:

1) Obtener la url de autentificación del objeto $client
2) Hacer la primera petición. Esta primera petición obtendrá el código HTML de la página que solicita el email y password para acceder a Google.
3) Del body de resultado de la petición anterior, se obtiene el action, method y la lista de variables del formulario.
4) Se pone valor a los inputs del formulario Email y Passwd
5) Se hace la segunda petición con el action, method y la lista de variables del formulario anterior modificado. Esta segunda petición obtendrá el código HTML de la página que solicita al usuario permiso para acceder a Google Calendar.
6) Del body de resultado de la petición anterior, se obtiene el action, method y la lista de variables del formulario.
7) Se pone "true" al input "submit_access"
8) Se hace la tercera petición con el action, method y la lista de variables del formulario anterior modificado. Esto nos retornará una página html con un input que contendrá el token que necesitamos.
9) Del body de resultado, sólo hay que obtener el nodo <input> y de ese nodo, obtener el valor del atributo "value". Con esto ya tenemos el token.

Notas:

- El punto 1 es directo mediante la API v3 de Google.
- En los puntos 2, 5 y 8 se hacen las 3 peticiones a las páginas que necesitamos (acceso a google, permiso a la aplicación y obtención del token).
- En los puntos 3 y 6 se obtiene el formulario con el action, method y lista de variables.
- En los puntos 4 y 7 se modifica el formulario (emulando la interacción del usuario).
- El punto 9 es procesar la página de resultados.

function __gcalendar_connect($login,$password) {
    $client=new Google_Client();
    $client->setAuthConfigFile("lib/google/saltos.json");
    $client->setRedirectUri("urn:ietf:wg:oauth:2.0:oob");
    $client->addScope("https://www.googleapis.com/auth/calendar");
    $client->setAccessType("offline");
    $token2=execute_query(make_select_query("tbl_gcalendar","token2",make_where_query(array(
        "id_usuario"=>current_user()
    ))));
    if($token2!="") {
        $client->setAccessToken(base64_decode($token2));
        if($client->getAccessToken()) return $client;
    }
    $token1=__gcalendar_token1($client,$login,$password);
    if($token1=="") return null;
    $client->authenticate($token1);
    $token2=$client->getAccessToken();
    if(!$token2) return null;
    $query=make_update_query("tbl_gcalendar",array(
        "token2"=>base64_encode($token2)
    ),"id_usuario='".current_user()."'");
    db_query($query);
    return $client;
}

Esta función es la que nos retornará un objeto con acceso al servicio de Google Calendar.

Lo que hace es crear un objeto Google_Client, establece el fichero que contiene la key creada para el proyecto, establece donde queremos acceder, el tipo de acceso y luego:

1) Si tenemos ya un token2 en la base de datos, lo usamos para autentificarnos mediante setAccessToken. Si este es valido mediante getAccessToken, retornaremos el $client.
2) Si token2 no es valido o no existe, llamaremos a la función anterior (__gcalendar_token1) que mediante el truco anterior nos retornará el token1 de acceso para obtener el token2.
3) Con el token1 anterior, llamaremos a la función authenticate y obtendremos el token2 mediante getAccessToken.
4) Si todo ha ido bien y tenemos ya el token2, lo guardaremos en la base de datos para poder reutilizar el token2 en las futuras peticiones de SaltOS.

Más info:

Para más detalles acerca de lo que he explicado aquí, podéis mirar el código fuente de SaltOS y en concreto, el fichero php/action/gcalendar.php

Líneas de XML
60,895
Líneas de PHP
18,637
Líneas de JS
11,611
Líneas de XSLT
2,498
Líneas de CSV
1,919
Líneas de CSS
577