26/01/2015

Accés (login) transparent mitjançant l'API v3 de Google

Login, API, Google

Josep Sanz, Jordi Company

Des del 17 de novembre del 2014, les API v1 i v2 per accedir als serveis de Google han deixat de funcionar forçant a tots els desenvolupadors a migrar els seus codis a la v3 de la nova API (https://developers.google.com/api-client-library/php/). A SaltOS, com la resta d'aplicacions, hem hagut de fer aquest canvi i ja torna tot a funcionar.

Antigament, l'accés al servei es podia obtenir utilitzant l'usuari+password del compte del servei. Actualment això ja no funciona així, sinó que s'ha d'obtenir un token que ens permetrà l'accés futur al servei fins que l'usuari revoqui el token. Per a això, és necessari crear una key des de la consola de desenvolupadors de google developers. Hi ha 2 tipus: per a aplicació online i per tant, estarà associada a un host, o per aplicació instal·lada, la qual no estarà associada a un host. Aquest segon cas és el que nosaltres hem emprat per crear la key d'accés al servei ja que la primera no ens val.

Actualment, quan és necessari que l'usuari s'autentiqui, el que aquesta API proposa és que redirigim la nostra pàgina cap a una pàgina de Google on s'hauran de seguir 3 passos:

1) Si no està autenticat a Google, se li demana a l'usuari el seu email i password de Google.
2) Se li pregunta si vol donar permís perquè l'aplicació accedeixi a les seves dades de l'aplicació (en el cas de SaltOS, és Google Calendar).
3) Apareixerà una pantalla amb una caixa de text que contindrà el token i se li indica a l'usuari que faci copy-paste d'aquest codi a l'aplicació.

Si la key creada té associada un host, el punt 3 és automàtic, ja que es redirigeix al host passant per paràmetre el token obtingut. Com en el nostre cas hem creat una key per aplicació instal·lada, no té host i per tant cal fer el copy-paste del token cap SaltOS.

Com podeu veure, és un procés tediós per a l'usuari, i des SaltOS, hem programat una solució per estalviar tot aquest procés extra causat per la falta d'un mecanisme d'autenticació directe des de la API v3 de Google. El "truc" consisteix en fer els passos que vol Google per obtenir el token (token1 en endavant) mitjançant programació.

Aquest token, ha de ser passat al mètode authenticate del client de google i això farà que obtinguem el segon token (token2 en endavant) que serà el que podrem usar en les futures peticions per accedir al servei. Com a resum:

1) Si no tenim el token2, obtenir el token1 amb l'usuari+password i ens autentifiquem mitjançant authenticate. El token2 que farem servir en les futures autenticacions l'obtindrem mitjançant getAccessToken.
2) si ja tenim el token2, el fem servir per autenticar mitjançant setAccessToken

Per a això, SaltOS defineix les següents funcions:

function __gcalendar_getattr($html,$attr)

Aquesta funció retorna el valor del primer atribut que troba a $html. Per exemple: si es passa a $html un node <form> i es passa a $attr la cadena "method", retornarà GET o POST.

function __gcalendar_getnode($html, $name)

Aquesta funció retornarà la porció de codi $html on el tag sigui $name. Per exemple: si es passa un formulari html i es demana el node <form>, aquesta funció retornarà el codi html des de <form> fins </form> amb tot el que contingui aquest formulari.

function __gcalendar_parse($html)

Aquesta funció retorna en un array aquests 3 dades del primer form que hi ha a $html:

1) El method.
2) El action.
3) Un array amb una llista de clau=>valor on clau és el name dels inputs i valor són els valors dels inputs.

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

Aquesta funció fa servir un tipus usada en SaltOS (http://www.phpclasses.org/package/3-PHP-HTTP-client-to-access-Web-site-pages.html) que permet fer peticions http de forma ràpida.

Aquesta funció fa una petició a la $url, usant el $method, enviant les variables del array $values, usant les cookies del array $cookies i usant el referer de $referer. Retorna un array amb 3 elements:

1) El body.
2) Un array amb els headers.
3) Un array amb les 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;
}

Aquesta funció és la més interessant de tota l'explicació, perquè usant les funcions anteriors, aconsegueix obtenir el primer token per accedir al servei de Google Calendar.

Per a això el que fa és:

1) Obtenir la url d'autenticació de l'objecte $client
2) Fer la primera petició. Aquesta primera petició obtindrà el codi HTML de la pàgina que sol·licita l'email i password per accedir a Google.
3) Del body de resultat de la petició anterior, s'obté el action, method i la llista de variables del formulari.
4) Es posa valor als inputs del formulari Email i Passwd
5) Es fa la segona petició amb el action, method i la llista de variables del formulari anterior modificat. Aquesta segona petició obtindrà el codi HTML de la pàgina que sol·licita a l'usuari permís per accedir a Google Calendar.
6) De l'body de resultat de la petició anterior, s'obté el action, method i la llista de variables del formulari.
7) Es posa "true" a l'input "submit_access"
8) Es fa la tercera petició amb el action, method i la llista de variables del formulari anterior modificat. Això ens retornarà una pàgina html amb un input que contindrà el token que necessitem.
9) De l'body de resultat, només cal obtenir el node <input> i d'aquest node, obtenir el valor de l'atribut "value". Amb això ja tenim el token.

Notes:

- El punt 1 és directe mitjançant l'API v3 de Google.
- En els punts 2, 5 i 8 es fan les 3 peticions a les pàgines que necessitem (accés a google, permís a l'aplicació i obtenció del token).
- En els punts 3 i 6 s'obté el formulari amb el action, method i llista de variables.
- En els punts 4 i 7 es modifica el formulari (emulant la interacció de l'usuari).
- El punt 9 és processar la pàgina de resultats.

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;
}

Aquesta funció és la que ens retornarà un objecte amb accés al servei de Google Calendar.

El que fa és crear un objecte Google_Client, estableix el fitxer que conté la key creada per al projecte, estableix on volem accedir, el tipus d'accés i després:

1) Si tenim ja un token2 a la base de dades, el fem servir per autentificarnos mitjançant setAccessToken. Si aquest és valid mitjançant getAccessToken, retornarem el $client.
2) Si token2 no és vàlid o no existeix, cridarem a la funció anterior (__gcalendar_token1) que mitjançant el truc anterior ens retornarà l'token1 d'accés per obtenir el token2.
3) Amb el token1 anterior, cridarem a la funció authenticate i obtindrem el token2 mitjançant getAccessToken.
4) Si tot ha anat bé i tenim ja el token2, el guardarem a la base de dades per poder reutilitzar el token2 a les futures peticions de SaltOS.

Més info:

Per a més detalls sobre el que he explicat aquí, podeu mirar el codi font de SaltOS i en concret, el fitxer php/action/gcalendar.php

Línies de XML
63,511
Línies de PHP
15,001
Línies de JS
14,448
Línies de T2T
3,499
Línies de XSLT
2,640
Línies de SQL
1,681