Așa cum promiteam într-un articol precedent vom încerca realizarea unei aplicații pe baza API-ului oferit de rețeaua de micro-blogging Twitter și folosind protocolul OAuth.

Twitter

Pentru cei ce nu sunt familiarizaţi cu Twitter, acest serviciu oferă posibilitatea utilizatorilor de a trimite mesaje de maxim 140 caractere (gen SMS) şi în care, teoretic, s-ar transmite mesaje scurte, informative, care ar răspunde la întrebarea "What’s happening?". Prietenii acestuia, sau cei interesati de ce ar avea acea persoană de spus (cazul unei persoane publice) îi pot urmări twitt-urile fiind astfel la curent cu ce face sau cu ce are de zis. Astfel un utilizator poate urmări anumiţi utiliztatori şi poate fi urmarit de către alţii, creându-se astfel conexiunile pe care se bazează orice reţea socială.

Pe langă aplicaţia web de utilizare a sistemului pe care Twitter o ofera pe site-ul twitter.com, aceştia mai pun la dispoziţie dezvoltatorilor de software posibilitatea de a crea aplicaţii diverse folosindu-se de un API, un set de comenzi prin care cei autorizaţi să îl foloseasca pot citi şi scrie date direct în sistemul Twitter. Transmiterea de date se face prin formate standard, asigurându-se astfel că orice aplicaţie-client twitter s-ar folosi, datele transmise pot fi citite din orice altă aplicaţie-client.

Lista funcţiilor oferite de API-ul Twitter cuprinde toate funcţionalităţile oferite şi de site-ul twitter.com astfel că practic vorbind putem crea o aplicaţie identică cu site-ul actual, o clonă sau chiar o versiune mai bună, folosindu-se chiar de sistemul original Twitter, serviciu foarte popular cu un numar necunoscut de utilizatori nici până în ziua de azi.

Twitter, cei care ofera interfata de comunicare a aplicatiilor externe cu sistemul propriu-zis trebuie sa facă diferențierea între actiunile venite din partea site-ului și cele venite de la o aplicație, oricare ar fi aceasta. Mai mult ei trebuie să asigure suportul pentru o comunicatie sigură și să se știe tot timpul despre o acțiune de către ce aplicație este făcută și în numele cărui utilizator.

OAuth

Aici este locul în 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) și nu în ultimul rând avem mai multe aplicații externe care pot comunica cu Twitter în numele unor utilizatori. Deci putem asocia conceptele OAuth după cum urmează:

  • Resursele: lista de mesaje transmise, mesajele prietenilor, lista de prieteni
  • Furnizorul de servicii: Twitter, cel care implementeaza protocolul OAuth și care ofera acces la serviciile sale.
  • Utilizatorul: orice persoană care și-a creat un cont folosind site-ul twitter.com
  • Consumatorul: aplicația care va utiliza twitter-ul in numele clientului.

Așadar OAuth este folosit pentru autentificarea aplicațiilor twitter, autentificarea utilizatorilor si autorizarea aplicației să efectueze operații asupra Twitter-ului în numele lor. O astfel de autorizare trebuie făcută explicit și pentru fiecare aplicație în parte. Fiind vorba despre o aplicație vom încerca și noi crearea uneia care va permite folosirea sistemului Twitter.

Inscrierea

Inregistrarea unei aplicatii Twitter

Inregistrarea unei aplicatii Twitter

Pentru ca o aplicație să poată funcționa corect și pentru a oferi siguranță și încredere utilizatorilor ea trebuie înregistrată cu furnizorul de servicii. Aceasta se face dintr-un cont existent de twitter la adresa http://twitter.com/apps unde se vor cere informații despre aplicație cum ar fi: nume, descriere, cine o dezvoltă, site-ul de unde se poate procura, tipul aplicatiei (desktop sau web) daca aplicația are nevoie doar de a citi date sau și de a scrie și nu în ultimul rând URL-ul de întoarcere, despre care vom vorbi mai târziu în detalierea aplicației.

După înregistrarea completă a aplicației, Twitter va genera un Consumer Key si un Consumer Secret, ambele reprezentând câte un sir de caractere din care primul este folosit pentru identificarea aplicației iar cel de-al doilea este folosit pentru a coda comunicarea dintre aplicatie si Twitter. Fiind un sir de caractere secret, el trebuie pastrat în siguranță și trebuie cunoscut doar de către dezvoltatorii aplicației.

Aplicația

Aplicația în sine poate fi de orice tip (web sau desktop) și poate fi scrisă în aproape orice limbaj de programare . Pentru demonstrare am ales limbajul PHP cu care vom crea o aplicație simplă de citire a datelor și de actualizare a statusului Twitter. Pentru aceasta am creat o clasă numită TwitterConnector și pentru care am definit câțiva membri privați și câteva 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 și $_consumerSecret sunt valorile primite de la Twitter cu identificatorul aplicației și cu cheia secretă de semnare a mesajelor. $_token și $_tokenSecret au aceeași însemnătate ca și consumer key și respectiv consumer secret, însă ele sunt atribuite pentru utilizator. Așadar cu un cod de identificare al aplicației și un token din partea clientului putem detemina ce utilizator a folosit ce aplicație, iar prin codul secret al consumatorului și cel al utilizatorului se poate comunica intr-un mod sigur cu sistemul Twitter. $_version reprezintă versiunea OAuth conform căror specificații realizăm aplicația.

URL_REQUEST_TOKEN, URL_ACCESS_TOKEN sunt URL-urile furnizate de către Twitter și sunt folosite pentru a obține un request token și un access token (pe care le vom detalia mai jos), URL_AUTHORIZE este folosit în cazul în care aplicația folosește sistemul de autentificare al lui Twitter, URL_AUTHENTICATE este folosit pentru a redirecționa utilizatorul pentru a autoriza aplicația iar URL_DATA este URL-ul de la care se vor obține date sau unde vor fi trimise comenzile de actualizare sau ștergere.

Vom crea în continuare constructorul clasei ce va primi ca parametrii consumer key si consumer secret pentru ușurință în dezvoltarea ulterioară. Deasemeni am definit și 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 primește drept parametrii adresa URL unde va face cererea, metoda HTTP prin care se va accesa sistemul Twitter (GET, POST, DELETE etc) precum și parametrii care vor fi trimiși. Pe lângă parametrii specifici fiecărei funcții descrise în documentația API-ului Twitter, sistemul are nevoie de a primi niște parametrii specifici OAuth pentru ca furnizorul de servicii să fie sigur că aplicația care acționeză în numele unui utilizator are acest drept și nu se încearcă accesarea neautorizată a sistemului. Acești parametrii sunt returnați de metoda getDefaultParameters și sunt adaugați la lista de parametrii specifici apelului pe care incercăm sa-l facem, folosind funcția array_merge.

Având acești parametrii putem semna cererea, prin metoda generateSignature (detaliată mai jos), iar apoi folosind funcțiile cURL vom contacta Twitter-ul pentru a transmite sau pentru a obține date. Parametrii pe care ii primim sunt in format array deci vor trebui transformați într-un format ce poate fi trimis atat prin metoda GET (deci adaugat la URL) cât și prin metoda POST. cURL suporta pentru ambele variante transmiterea parametrilor folosind o structură 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);
}

Observați că am tratat separat cazul în care parametrii se trimit prin POST fată de cel în care se trimit prin GET, cazul în care se doresc apelarea altor metode fiind ușor de adaugat, putând constitui o temă pentru cei care doresc continuarea exemplului pe care il dezvoltăm.

Semnarea cererilor

Orice cerere către furnizorul de servicii, fie pentru a obtine un token fie pentru a obtine accesul la resursele protejate trebuie să fie însoțită de o semnătură, pentru a dovedi autenticitatea celui care o cere. Semnătura este compusă din parametrii cu care se face cererea și din cheia secretă și (dacă există) tokenul secret. Modul de semnare a cererii este definită în secțiunea 9 a documentației OAuth și este la rândul ei realizată în mai multi pași.

Desi specificațiile OAuth permit folosirea mai multor metode de criptarea a semnaturii, de fapt recomandă folosirea câtorva metode, dar nu ingrădește definirea și documentarea altora, Twitter nu sportă criptarea semnăturii decât prin metoda HMAC-SHA1. Avem nevoie așadar de un sir de caractere de bază și de o cheie de criptare. Sirul de caractere este format din concatenarea metodei HTTP folosite, a URL-ului si a listei de parametrii normalizați conform NVP. Acest sir de caractere de bază trebuie codat folosind o cheie, formată din concatenarea consumer secret si (dacă există) 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 funcției hash_hamc fiind unul binar, el trebuie transformat într-unul ASCII folosind funcția base64_encode. Metodele toNVP și fromNVP sunt folosite pentru a obține o codare NVP dintr-un array și un array dintr-un string codat NVP. Metoda toNVP are totuși particularitatea ca va ordona valorile din vector în ordine alfabetică a cheilor fiecărui element (aceasta fiind o cerintă din specificațile OAuth). O altă cerință OAuth este ca orice parametru să fie codat UTF-8 și trebuie să fie codat URL, lucru care se asigură 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, ceruți la fiecare cerere către Twitter sunt:

  • oauth_consumer_key – parametru consumer key cu care am instanțiat clasa
  • oauth_signature_method – metoda folosită de a semna cererea, in cazul nostru va fi tot timpul HMAC-SHA1
  • oauth_timestamp – reprezintă UNIX TIMESTAMP, sau numărul de secunde trecute din 01.01.1970 și este util pentru a verifica daca o nouă cerere este făcută după precedenta și de a preveni posibile atacuri
  • oauth_nonce – este un string unic, generat aleator și pentru care nu se impune nici o restricție de lungime. Pentru generarea lui am folosit funcția generateNonce care folosește o combinație de funcții de generarea de coduri unice (uniqid) si de codare (md5)
  • oauth_version – versiunea OAuth care se utilizează (poate fi opțional)
  • oauth_token – daca există definit, tokenul va fi inclus.

Parametrul oauth_signature este cerut la fiecare cerere, dar el trebuie omis în construirea semnăturii.

 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 și autorizarea

În secțiunea 6 a specificațiilor OAuth se definește procesul de autentificare și autorizare ca fiind unul în 3 pași:

  1. Consumatorul obține de la Twitter un token neautorizat (numit request token)
  2. Utilizatorul autorizează tokenul neautorizat spre folosire de către consumator (de regulă utilizatorul este direcționat la Twitter pentru autorizare și posibil autentificare)
  3. Consumatorul schimbă token-ul neautorizat cu unul autorizat (numit access token)

Așa vom proceda și noi: inițial vom instanția clasa și vom cere un request token, cu care vom redirecționa utilizatorul către site-ul twitter.com pentru a-l autoriza. După ce acesta se autentifică și autorizează aplicația noastră să opereze sistemul twitter în numele lui, twitter.com va redirecționa utilizatorul către URL-ul de întoarcere pe care l-am definit în momentul în care am declarat intenția de a crea o aplicație pe site-ul twitter.com. În acest fel, chiar daca s-ar petrece o scurgere de informații și consumer secret ar ajunge la persoane cu intenții necurate, sistemul twitter va redirecționa clientul înapoi către URL-ul definit la inregistrarea aplicției, eliminând astfel posibila fraudă.

$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']) <= 140 )
        {
            $tw->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 autorizată, aplicația poate accesa resursele protejate ale utilizatorului și poate acționa în numele lui pentru a urmări alți utilizatori, a trimite mesaje noi sau orice alte acțiuni permise prin API-ul Twitter. Evident ca într-o situație reală aceste acțiuni vor fi dictate de către însuși utilizator și aplicația doar le va intermedia și nu să trimită mesaje în neștirea utilizatorului. Deși token-ul de acces este emis pentru fiecare aplicație pentru un termen nelimitat utilizatorul are posibilitatea sa revoce acest acces prin intermediul twitter.com.

Doar pentru scop demostrativ am implementat funcționalitatea pentru a prelua "time-line"-ul utilizatorului (adică ultimele mesaje trimise de către cei pe care utilizatorul îi urmărește) și funcția de actualizare a status-ului. Pentru oricare din funcționalitățile API-ului Twitter oferă răspunsul în 2 variante: JSON și XML. Pentru aceasta am creat funcția returnFormatedReply care va întoarce rezultatul formatat în funcție de cererea făcută

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 funcțional pentru a fi folosit într-o aplicație Twitter gata de a fi lansată în producție; de exemplu nu tratează în 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 în contextul PHP. Totusi reprezintă un bun început pentru a se ajunge la o aplicație twitter de sine stătătoare si care sa funcționeze corect în toate cazurile. O modalitate de a face un cache al mesajelor și adăugarea unor funcționalități JavaScript pentru o experientă a utilizatorului mai placută, ar putea fi un început pentru o posibilă aplicație funcționala 100%. Provocăm pe oricine are timpul și este dispus la o completare și o continuare a codului început aici pentru a realiza o aplicație corectă si completă, și supunem la dezbateore orice idee în acest sens ați avea.

Puteți testa aplicația la adresa http://sandbox.inphpwetrust.com/twitter/, puteți obține codul sursă sau ne puteți urmări pe Twitter

Trackback

2 comments until now

  1. Very good article. Well documented. :)>-

  2. Ar fi interesant si un articol si despre partea de provider si cum permite accesul la API.

Add your comment now