середу, 28 квітня 2010 р.

ВКонтакте → Авторизація Desktop-додатків на прикладі Ruby і Mechanize

Увага! Дана версія документації є застарілою. Замість неї я рекомендую прочитати статтю ВКонтакте → Авторизація Standalone-додатків використовуючи OAuth 2.0 на прикладі Ruby і Mechanize

Нещодавно розробники соціальної мережі ВКонтакте повідомити про запуск можливості використання VK API для standalone-додатків. Я ж вирішив експериментально перевірити цю нову можливість у зв'язці з мовою Ruby.

Використані джерела:

Авторизація Desktop-додатків
Desktop-додатки на відміну від звичайних програм ВКонтакте запускаються у вигляді звичайних програм на пристрої користувача, яким може бути комп'ютер, комунікатор або смартфон. Наприклад, це може бути додаток на платформі Adobe AIR або додаток для iPhone, написане на Objective-C.

Почав писати обгортку до цього на Ruby. І відразу ж засмутився - авторизація на даний момент через одне місце. Потрібен компонент браузера з підтримкою JavaScript. Насправді я уявлення не маю як таке реалізувати на Ruby. Доведеться вручну вивчати що і куди посилати. Але чому б не зробити метод у який висилається логін-пароль? І просто отримувати ID сесії.
Маємо те, що маємо. Обійдемося поки цим. А в майбутньому, надіюся виправлять.
Для початку необхідно створити новий або взяти вже існуючий додаток.

Нам знадобиться ID додатку.

Вхід і отримання сесії
Для отримання сесії необхідно усередині додатку створити елемент управління, який буде містити усередині себе браузер(наприклад UIWebView при створенні iPhone додатки). Після цього для входу необхідно буде направити браузер в створеному елементі керування на наступний URL
http://vkontakte.ru/login.php

і передати описані нижче параметри в POST або GET запиті:
Назва параметру Обов'язковий Опис
app ID додатку
layout
Вказує тип дизайну сторінки авторизації. На даний момент підтримуються наступні типи:
  • popup - для додатків, що запускаються на звичайних комп'ютерах, ноутбуках і нетбуках
  • touch - для додатків, що запускаються на комунікаторах, смартфонах і iPhone
type
Вказує яким чином передається значення сесії. На даний момент підтримуються наступні типи:
  • browser - сесія передається в редіректі у вигляді хешу
success_url
Сторінка, на яку буде перенаправлено браузер в разі успішної авторизації. У рядку запиту даної сторінки буде передано інформацію про сесію. Коли цей параметр не заданий, то за замовчуванням перенаправлення відбувається на сторінку
http://vkontakte.ru/api/login_success.html
fail_url
Сторінка, на яку буде перенаправлено браузер в разі скасування авторизації користувачем. Коли цей параметр не заданий, то за замовчуванням перенаправлення відбувається на сторінку
http://vkontakte.ru/api/login_failure.html
settings
Бітова маска настройок доступу програми, які необхідно перевірити при авторизації користувача і запитати, у разі відсутності необхідних.

Наш додаток ми будемо писати на чистому Ruby. А в якості броузера у нас буде бібліотека Mechanize, у якій відсутня підтримка JavaScript. Забігаючи на перед скажу, що це викликає деякі незручності.
require 'mechanize'
API_ID = '1861808'

agent = Mechanize.new{|a|
  a.user_agent_alias = 'Linux Konqueror'
}

login_url = "http://vk.com/login.php?app=#{API_ID}&layout=popup&type=browser&settings=130"
login_page = agent.get(login_url)
Далі по сценарію ми повинні потрапити на сторінку авторизації користувача, де потрібно ввести логін і пароль.

Виконаємо цю пересічну дію силами Mechanize.
email = 'user@example.com'
pass = 'xxx'
login_form = login_page.form_with(:name => 'real_login')
login_form.email = email
login_form.pass = pass
verify_page = login_form.submit
Тут починається найцікавіше. У разі успішної авторизації користувач опиниться на сторінці http://login.vk.com.
<html>
  <head>
    <meta http-equiv='content-type' content='text/html; charset=windows-1251' />
  </head>
  <body onload="document.getElementById('l').submit();">
    <form id='l' method='post' action='http://vk.com/login.php'>
      <input type='hidden' name='s' id='s' value='5656ecbc545b7fcb9fcce1bfde0ad20d368dda058a5908925141c94d' />
            <input type="hidden" name="act" id="act" value="auth_result" />
      <input type="hidden" name="m" id="m" value="4" />
      <input type="hidden" name="permanent" id="permanent" value="1" />
      <input type="hidden" name="app" id="app" value="1861808" />
      <input type="hidden" name="app_hash" id="app_hash" value="2f90dbb41067e7b22d" />
    </form>
  </body>
</html>

Якби це був броузер з підтримкою JavaScript, то при успішнії авторизації вбудований в додатку браузер буде перенаправлено на наступну URL-адресу:
http://vkontakte.ru/login_success.html#session=(...)
де в session записані параметри сесії в форматі JSON.

В нашому випадку, доведеться виконувати ці дії власноруч.
params_page = verify_page.forms.first.submit

Опиняємося на сторінці http://vk.com/login.php, у джерелі якої містяться необхідні параметри сесії в форматі JSON:
<script type="text/javascript">parent.onDone({"mid":12227146,"sid":"09f3c88847be319f9e5a44710d1549c166f3848e557f1780623bc222","secret":"893daf7ef4","expire":0})</script>

Нижче описано значення кожного з параметрів сесії:
Параметр Значення
expire час закінчення сесії у форматі UNIX
mid ID користувача ВКонтакте
secret спеціально згенерований секрет сесії
sid ідентифікатор сесії

Ми успішно авторизувалися і отримати сесію. Зберігаємо її параметри для подальшого використання.
require 'json'
@session_params = JSON::parse(/\((.*)\)/.match(params_page.body)[1])

Взаємодія з API
Після отримання сесії взаємодія з ВКонтакте API проводиться шляхом створення HTTP-запиту (POST або GET) до адреси API-сервісу http://api.vkontakte.ru/api.php.

Які параметри необхідно передавати при запиті?
Назва параметру Обов'язковий параметр Опис
api_id ідентифікатор програми, присвоюється при створенні.
method назва методу API із загального списку функцій.
sig підпис, який створюється з метою безпеки. Порядок створення підпису описаний нижче.
v версія API, в даному випадку необхідно використовувати 3.0.
format формат відповіді - XML або JSON. За замовчуванням XML.
sid дентифікатор сесії, отриманий раніше при авторизації

Як створювати підпис запиту?
Параметр sig дорівнює md5 від конкатенації наступних рядків:
  • mid - id поточного користувача, отриманий раніше при авторизації
  • пар "parameter_name = parameter_value", розташованих у порядку зростання імені параметра (за алфавітом) за винятком параметра sid
  • секрету сесії secret, який був отриманий раніше при авторизації

Напишемо функцію, яка на вході буде приймати назва методу API із загального списку функцій та його параметри як об’єкт Hash.
require 'digest/md5'

def make_sig(method_name, method_params)
  sig = @session_params['mid'].to_s
  params = {'api_id' => API_ID, 'format' => 'JSON', 'v' => '3.0', 'method' => method_name}
  params.merge!(method_params)
  sig = params.sort.inject(sig) {|s, e| s += "#{e[0]}=#{e[1]}"}
  sig = sig + @session_params['secret']
  sig = Digest::MD5.hexdigest(sig)

  return sig
end

Приклад запиту:
method_name = 'getProfiles'
method_params = {'uids' => '15297587', 'fields' => 'photo,sex'}

sig = make_sig(method_name, method_params)

method_params_to_url = method_params.inject(""){|s, e| s += "&#{e[0]}=#{e[1]}"}

res_url = "http://api.vk.com/api.php?api_id=#{API_ID}&method=#{method_name}#{method_params_to_url}&sig=#{sig}&v=3.0&format=JSON&sid=#{@session_params['sid']}"
res_page = agent.get(res_url)
puts JSON::parse(res_page.body)

У даному випадку API отримує запит getProfiles з параметрами:
  • uids=15297587
  • fields=photo,sex

5 коментарів:

Блог пассивного спамера сказав...

Хороша стаття!
Буде вільний час - обов'язково попробую.
І коли вже Mechanize буде підтримувати JavaScript...?

Anton Maminov сказав...

мабуть ніколи :(
Кому до снаги писати самотужки процесор JavaScript?

Анонім сказав...

http://watir.com/

Волошин Руслан сказав...

После прочтения твоего поста, решаил тоже поразбираться с апями контакта и вариант запросов немного другой, не обязательно вызывать рендеринг обычной страницы через нокогири, можно все сделать json запросом.

Anton Maminov сказав...

2 Волошин Руслан
зараз так і роблю.
Авторизація з використанням Mechanize, а взаємодія з API через httparty.

http://github.com/mamantoha/vkontakte/blob/master/lib/vkontakte.rb