Asa cum promiteam intr-un articol precedent vom incerca realizarea unei aplicatii pe baza API-ului oferit de reteaua de micro-blogging Twitter si folosind protocolul OAuth
Pentru cei ce nu sunt familiarizati cu Twitter, acest serviciu ofera posibilitatea utilizatorilor de a trimite mesaje de maxim 140 caractere (gen SMS) si in care, teoretic, s-ar transmite mesaje scurte, informative, care ar raspunde la intrebarea “What’s happening?”. Prietenii acestuia, sau cei interesati de ce ar avea acea persoana de spus (cazul unei persoane publice) ii pot urmari twitt-urile fiind astfel la curent cu ce face sau cu ce are de zis. Astfel un utilizator poate urmari anumiti utiliztatori si poate fi urmarit de catre altii, creandu-se astfel conexiunile pe care se bazeaza orice retea sociala.
Pe langa aplicatia web de utilizare a sistemului pe care Twitter o ofera pe site-ul twitter.com, acestia mai pun la dispozitie dezvoltatorilor de software posibilitatea de a crea aplicatii diverse folosindu-se de un API, un set de comenzi prin care cei autorizati sa il foloseasca pot citi si scrie date direct in sistemul Twitter. Transmiterea de date se face prin formate standard, asigurandu-se astfel ca orice aplicatie-client twitter s-ar folosi, datele transmise pot fi citite din orice alta aplicatie-client.
Lista functiilor oferite de API-ul Twitter cuprinde toate functionalitatile oferite si de site-ul twitter.com astfel ca practic vorbind putem crea o aplicatie identica cu site-ul actual, o clona sau chiar o versiune mai buna, folosindu-se chiar de sistemul original Twitter, serviciu foarte popular cu un numar necunoscut de utilizatori nici pana in ziua de azi. Twitter, cei care ofera interfata de comunicare a aplicatiilor externe cu sistemul propriu-zis trebuie sa faca diferentierea intre actiunile venite din partea site-ului si cele venite de la o aplicatie, oricare ar fi aceasta. Mai mult ei trebuie sa asigure suportul pentru o comunicatie sigura si sa se stie tot timpul despre o actiune de catre ce aplicatie este facuta și in numele carui utilizator.
OAuth
Aici este locul in care intervine OAuth: avem un sistem, avem niste utilizatori, avem pentru fiecare utilizator resurse proprii (mesajele proprii, mesajele prietenilor si posibilitatea de a trimite noi mesaje) si nu in ultimul rand avem mai multe aplicatii externe care pot comunica cu Twitter in numele unor utilizatori. Deci putem asocia conceptele OAuth dupa cum urmeaza:
- Resursele: lista de mesaje transmise, mesajele prietenilor, lista de prieteni
- Furnizorul de servicii: Twitter, cel care implementeaza protocolul OAuth si care ofera acces la serviciile sale.
- Utilizatorul: orice persoana care si-a creat un cont folosind site-ul twitter.com
- Consumatorul: aplicatia care va utiliza twitter-ul in numele clientului.
Asadar OAuth este folosit pentru autentificarea aplicatiilor twitter, autentificarea utilizatorilor si autorizarea aplicatiei sa efectueze operatii asupra Twitter-ului in numele lor. O astfel de autorizare trebuie facuta explicit si pentru fiecare aplicatie in parte. Fiind vorba despre o aplicatie vom incerca si noi crearea uneia care va permite folosirea sistemului Twitter.
Inscrierea
Pentru ca o aplicatie sa poata functiona corect si pentru a oferi siguranta si incredere utilizatorilor ea trebuie inregistrata cu furnizorul de servicii. Aceasta se face dintr-un cont existent de twitter la adresa http://twitter.com/apps unde se vor cere informatii despre aplicatie cum ar fi: nume, descriere, cine o dezvolta, site-ul de unde se poate procura, tipul aplicatiei (desktop sau web) daca aplicatia are nevoie doar de a citi date sau si de a scrie si nu in ultimul rand URL-ul de intoarcere, despre care vom vorbi mai tarziu in detalierea aplicatiei. Dupa inregistrarea completa a aplicatiei, Twitter va genera un Consumer Key si un Consumer Secret, ambele reprezentand cate un sir de caractere din care primul este folosit pentru identificarea aplicatiei iar cel de-al doilea este folosit pentru a coda comunicarea dintre aplicatie si Twitter. Fiind un sir de caractere secret, el trebuie pastrat in siguranta și trebuie cunoscut doar de catre dezvoltatorii aplicatiei.
Aplicatia
Aplicatia in sine poate fi de orice tip (web sau desktop) si poate fi scrisa in aproape orice limbaj de programare . Pentru demonstrare am ales limbajul PHP cu care vom crea o aplicatie simpla de citire a datelor si de actualizare a statusului Twitter. Pentru aceasta am creat o clasa numita TwitterConnector si pentru care am definit cativa membri privati si cateva constante:
class TwitterConnector { private $_consumerKey = ''; private $_consumerSecret = ''; private $_tokenSecret = ''; private $_token = ''; private $_version = '1.0'; const URL_REQUEST_TOKEN = 'https://twitter.com/oauth/request_token'; const URL_ACCESS_TOKEN = 'https://twitter.com/oauth/access_token'; const URL_AUTHORIZE = 'https://twitter.com/oauth/authorize?oauth_token=%s'; const URL_AUTHENTICATE = 'https://twitter.com/oauth/authenticate?oauth_token=%s'; const URL_DATA = 'http://api.twitter.com/1/'; }
$_consumerKey si $_consumerSecret sunt valorile primite de la Twitter cu identificatorul aplicatiei si cu cheia secreta de semnare a mesajelor. $_token si $_tokenSecret au aceeasi insemnatate ca si consumer key si respectiv consumer secret, insa ele sunt atribuite pentru utilizator. Asadar cu un cod de identificare al aplicatiei si un token din partea clientului putem detemina ce utilizator a folosit ce aplicatie, iar prin codul secret al consumatorului si cel al utilizatorului se poate comunica intr-un mod sigur cu sistemul Twitter. $_version reprezinta versiunea OAuth conform caror specificatii realizam aplicatia.
URL_REQUEST_TOKEN, URL_ACCESS_TOKEN sunt URL-urile furnizate de catre Twitter si sunt folosite pentru a obtine un request token si un access token (pe care le vom detalia mai jos), URL_AUTHORIZE este folosit in cazul in care aplicatia foloseste sistemul de autentificare al lui Twitter, URL_AUTHENTICATE este folosit pentru a redirectiona utilizatorul pentru a autoriza aplicatia iar URL_DATA este URL-ul de la care se vor obtine date sau unde vor fi trimise comenzile de actualizare sau stergere.
Vom crea in continuare constructorul clasei ce va primi ca parametrii consumer key si consumer secret pentru usurinta in dezvoltarea ulterioara. De asemenea am definit si cate un setter si getter pentru a opera cu parametrii token si token secret.
public function __construct($consumerKey, $consumerSecret) { $this->_consumerKey = $consumerKey; $this->_consumerSecret = $consumerSecret; } public function setToken($token) { $this->_token = $token; } public function getToken() { return $this->_token; } public function getTokenSecret() { return $this->_tokenSecret; } public function setTokenSecret($secret) { $this->_tokenSecret = $secret; }
Comunicarea cu sistemul Twitter
Pentru a comunica unitar cu Twitter am creat metoda doRequest, care primeste drept parametrii adresa URL unde va face cererea, metoda HTTP prin care se va accesa sistemul Twitter (GET, POST, DELETE etc) precum si parametrii care vor fi trimisi. Pe langa parametrii specifici fiecarei functii descrise in documentatia API-ului Twitter, sistemul are nevoie de a primi niste parametrii specifici OAuth pentru ca furnizorul de servicii sa fie sigur ca aplicatia care actioneza in numele unui utilizator are acest drept si nu se incearca accesarea neautorizata a sistemului. Acesti parametrii sunt returnati de metoda getDefaultParameters si sunt adaugati la lista de parametrii specifici apelului pe care incercam sa-l facem, folosind functia array_merge.
Avand acesti parametrii putem semna cererea, prin metoda generateSignature (detaliata mai jos), iar apoi folosind functiile cURL vom contacta Twitter-ul pentru a transmite sau pentru a obtine date. Parametrii pe care ii primim sunt in format array deci vor trebui transformati intr-un format ce poate fi trimis atat prin metoda GET (deci adaugat la URL) cat si prin metoda POST. cURL suporta pentru ambele variante transmiterea parametrilor folosind o structura name-value-pair (NVP), adica perechi de tipul atribut1=valoare1&atribut2=valoare2
protected function doRequest($url, $method, $params) { $params = array_merge($this->getDefaultParameters(), $params); $params = $this->encodeParams($params); $params['oauth_signature'] = $this->generateSignature($url, $method, $params); $ch = curl_init(); if (strtolower($method) == 'post') { curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->toNVP($params, true)); curl_setopt($ch, CURLOPT_URL, $url); } else { curl_setopt($ch, CURLOPT_URL, $url . '?' . $this->toNVP($params, true)); } curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); return curl_exec($ch); }
Observati ca am tratat separat cazul in care parametrii se trimit prin POST fata de cel in care se trimit prin GET, cazul in care se doresc apelarea altor metode fiind usor de adaugat, putand constitui o tema pentru cei care doresc continuarea exemplului pe care il dezvoltam.
Semnarea cererilor
Orice cerere catre furnizorul de servicii, fie pentru a obtine un token fie pentru a obtine accesul la resursele protejate trebuie sa fie insotita de o semnatura, pentru a dovedi autenticitatea celui care o cere. Semnatura este compusa din parametrii cu care se face cererea si din cheia secreta si (daca exista) tokenul secret. Modul de semnare a cererii este definita in sectiunea 9 a documentației OAuth si este la randul ei realizata in mai multi pasi.
Desi specificatiile OAuth permit folosirea mai multor metode de criptarea a semnaturii, de fapt recomanda folosirea catorva metode, dar nu ingradeste definirea si documentarea altora, Twitter nu sporta criptarea semnaturii decat prin metoda HMAC-SHA1. Avem nevoie asadar de un sir de caractere de baza si de o cheie de criptare. Sirul de caractere este format din concatenarea metodei HTTP folosite, a URL-ului si a listei de parametrii normalizati conform NVP. Acest sir de caractere de baza trebuie codat folosind o cheie, formata din concatenarea consumer secret si (daca exista) token secret.
protected function generateSignature($url, $method , $params) { $nvp = $this->toNVP($params, true); $nvp = $this->encode($nvp); $url = $this->encode($url); $string = "{$method}&{$url}&{$nvp}"; $key = $this->encode($this->_consumerSecret) . '&' . $this->encode($this->_tokenSecret); return base64_encode(hash_hmac('sha1', $string, $key, true)); }
Rezultatul functiei hash_hamc fiind unul binar, el trebuie transformat intr-unul ASCII folosind functia base64_encode. Metodele toNVP si fromNVP sunt folosite pentru a obtine o codare NVP dintr-un array si un array dintr-un string codat NVP. Metoda toNVP are totusi particularitatea ca va ordona valorile din vector in ordine alfabetica a cheilor fiecarui element (aceasta fiind o cerinta din specificatiile OAuth). O alta cerinta OAuth este ca orice parametru sa fie codat UTF-8 si trebuie sa fie codat URL, lucru care se asigura prin metoda encode.
protected function toNVP(array $array) { ksort($array); $nvp = array(); foreach($array as $name => $value) { $value = $this->encode($value); $nvp[] = "{$name}={$value}"; } return implode('&', $nvp); } protected function fromNVP($string) { $list = explode('&', $string); $output = array(); foreach($list as $element) { $tmp = explode('=', $element); $output[$tmp[0]] = $tmp[1]; } return $output; } protected function encode($value) { return rawurlencode(utf8_encode($value)); }
Parametrii obligatorii, ceruti la fiecare cerere catre Twitter sunt:
- oauth_consumer_key - parametru consumer key cu care am instantiat clasa
- oauth_signature_method - metoda folosita de a semna cererea, in cazul nostru va fi tot timpul HMAC-SHA1
- oauth_timestamp - reprezintă UNIX TIMESTAMP, sau numarul de secunde trecute din 01.01.1970 1970 si este util pentru a verifica daca o noua cerere este facuta dupa precedenta si de a preveni posibile atacuri
- oauth_nonce - este un string unic, generat aleator si pentru care nu se impune nici o restrictie de lungime. Pentru generarea lui am folosit functia generateNonce care foloseste o combinatie de functii de generarea de coduri unice (uniqid) si de codare (md5)
- oauth_version - versiunea OAuth care se utilizeaza (poate fi optional)
- oauth_token - daca exista definit, tokenul va fi inclus.
Parametrul oauth_signature este cerut la fiecare cerere, dar el trebuie omis in construirea semnaturii.
protected function getDefaultParameters() { $params['oauth_consumer_key'] = $this->_consumerKey; $params['oauth_signature_method'] = 'HMAC-SHA1'; $params['oauth_timestamp'] = time(); $params['oauth_nonce'] = $this->generateNonce(); $params['oauth_version'] = $this->_version; if ($this->_token != '') { $params['oauth_token'] = $this->_token; } return $params; } protected function generateNonce() { return md5(uniqid(rand(), true)); }
Autentificarea si autorizarea
In sectiunea 6 a specificatiilor OAuth se defineste procesul de autentificare si autorizare ca fiind unul în 3 pasi:
1. Consumatorul obtine de la Twitter un token neautorizat (numit request token)
2. Utilizatorul autorizeaza tokenul neautorizat spre folosire de catre consumator (de regula utilizatorul este directionat la Twitter pentru autorizare si posibil autentificare)
3. Consumatorul schimba token-ul neautorizat cu unul autorizat (numit access token)
Asa vom proceda si noi: initial vom instantia clasa si vom cere un request token, cu care vom redirectiona utilizatorul catre site-ul twitter.com pentru a-l autoriza. Dupa ce acesta se autentifica si autorizeaza aplicatia noastra sa opereze sistemul twitter in numele lui, twitter.com va redirectiona utilizatorul catre URL-ul de intoarcere pe care l-am definit in momentul in care am declarat intentia de a crea o aplicatie pe site-ul twitter.com. In acest fel, chiar daca s-ar petrece o scurgere de informatii si consumer secret ar ajunge la persoane cu intentii rele, sistemul twitter va redirectiona clientul inapoi catre URL-ul definit la inregistrarea aplicatiei, eliminand astfel posibila frauda.
$tw = new TwitterConnector(CONSUMER_KEY, CONSUMER_SECRET); if (isset($_SESSION['oauth_token']) && isset($_SESSION['oauth_token_secret'])) { /** * in cazul in care avem stocate pe sesiune informatiile * despre token putem instantia clasa * si folosi toate functionalitatile twitter **/ $tw->setToken($_SESSION['oauth_token']); $tw->setTokenSecret($_SESSION['oauth_token_secret']); if (count($_POST) > 0) { if (isset($_POST['msg']) && strlen($_POST['msg']) updateStatus('json', $_POST['msg'], null); header('Location: index.php'); } } } else if (isset($_GET['oauth_token'])) { /** * in cazul in care primim pe GET informatiile despre un token * inseamna ca el este unul neautorizat * si vom incerca sa-l autorizam dupa care vom scrie pe sesiune * informatiile despre access token **/ $tw->setToken($_GET['oauth_token']); $tw->setTokenSecret(''); $tw->getAccessToken(); $_SESSION['oauth_token'] = $tw->getToken(); $_SESSION['oauth_token_secret'] = $tw->getTokenSecret(); } else { /** * in cazul in care nu suntem in nici un caz din cele de mai sus * inseamna ca utilizatorul a accesat pentru prima oara aplicatia noastra * deci vom obtine un reuest token si apoi vom redirectiona utilizatorul * catre twitter.com pentru autorizare **/ $tw->requestToken(); $tw->redirect(); }
Comunicarea
Odata autorizata, aplicatia poate accesa resursele protejate ale utilizatorului si poate actiona in numele lui pentru a urmari alti utilizatori, a trimite mesaje noi sau orice alte actiuni permise prin API-ul Twitter. Evident ca intr-o situatie reala aceste actiuni vor fi dictate de catre insusi utilizator si aplicatia doar le va intermedia, nu va trimite mesaje in fara sa stie utilizatorul. Desi token-ul de acces este emis pentru fiecare aplicatie pentru un termen nelimitat, utilizatorul are posibilitatea sa revoce acest acces prin intermediul twitter.com.
Doar pentru scop demostrativ am implementat functionalitatea pentru a prelua “time-line”-ul utilizatorului (adica ultimele mesaje trimise de catre cei pe care utilizatorul ii urmareste) si functia de actualizare a status-ului. Pentru oricare din functionalitatile API-ului Twitter ofera raspunsul in 2 variante: JSON si XML. Pentru aceasta am creat functia returnFormatedReply care va intoarce rezultatul formatat in functie de cererea facuta.
public function getHomeTimeline($format) { $reply = $this->doRequest( self::URL_DATA . 'statuses/home_timeline.' . $format, 'GET', array() ); return $this->returnFormatedReply($format, $reply); } public function updateStatus($format, $status, $inreply) { $reply = $this->doRequest( self::URL_DATA . 'statuses/update.' . $format, 'POST', array('status' => $status, 'in_reply_to_status_id' => $inreply) ); return $this->returnFormatedReply($format, $reply); } protected function returnFormatedReply($format, $reply) { switch ($format) { case 'json': return json_decode($reply); break; case 'xml': return new SimpleXMLElement($reply); break; default: return null; } }
Considerente finale
Exemplu folosit este pe departe de a fi un exemplu complet functional pentru a fi folosit intr-o aplicatie Twitter gata de a fi lansata in productie; de exemplu nu trateaza in nici un fel erorile posibile de la Twitter. Am creat acest exemplu pentru a demonstra posibilitatea de implementare a specificațiilor OAuth pe partea de consumator in contextul PHP. Totusi reprezinta un bun inceput pentru a se ajunge la o aplicatie twitter de sine statatoare si care sa functioneze corect in toate cazurile. O modalitate de a face un cache al mesajelor si adaugarea unor functionalitati JavaScript pentru o experienta a utilizatorului mai placuta, ar putea fi un inceput pentru o posibila aplicatie functionala 100%. Provocam pe oricine are timpul si este dispus la o completare si o continuare a codului inceput aici pentru a realiza o aplicatie corecta si completa, si supunem la dezbatere orice idee in acest sens ati avea.
Puteti testa aplicatia la adresa http://sandbox.inphpwetrust.com/twitter/, puteti obtine codul sursa sau ne puteti urmari pe Twitter



Comentarii