вівторок, 3 липня 2012 р.

ВКонтакте → Авторизація додатків з Ruby і Net::HTTP

На днях виникло бажання переписати код своєї бібліотеки для для авторизації ВКонтакте і здійснення запитів до API, використовуючи тільки стандартні модулі Ruby.

Для того, щоб прикинутися справжнім браузером, просто завантажувати потрібні сторінки недостатньо. Як мінімум, потрібно коректно обробляти куки і редіректи. Для цього у попередній статі я використовував чудову бібліотеку Mechanize.

Спочатку встановимо необхідні параметри.
require 'net/http'
email = 'user@example.com'
pass = 'secret'
client_id = '1234567'
scope = 'friends,audio'
redirect_uri = 'http://oauth.vk.com/blank.html'
display = 'wap'
response_type = 'code'
view raw 1_params.rb hosted with ❤ by GitHub

Детально про параметри звернення можна прочитати в документації.
client_id - ідентифікатор вашого додатку.
Тут слід зазначити, що ми будемо використовувати параметр display із значенням wap, так в цьому варіанті сторінки практично відсутній JavaScript.
scope являє собою список прав, до яких ми хочемо отримати доступ.

Звертаємося до сторінки авторизації


url = "http://oauth.vk.com/oauth/authorize?client_id=#{client_id}&scope=#{scope}&redirect_uri=#{redirect_uri}&display=#{display}&response_type=#{response_type}&_hash=0"
uri = URI(url)
request = Net::HTTP::Get.new(uri.request_uri)
response = Net::HTTP.start(uri.host, uri.port){ |http| http.request(request) }
view raw 2_auth_page.rb hosted with ❤ by GitHub


Парсимо відповідь


Подивимося на код сторінки авторизації. Найбільше нас буде цікавити наступна частина:
<form method="POST" action="https://login.vk.com/?act=login&soft=1&utf8=1">
<input type="hidden" name="q" value="1">
<input type="hidden" name="from_host" value="oauth.vk.com">
<input type="hidden" name="from_protocol" value="http">
<input type="hidden" name="ip_h" value="e1b62a2b2aaaaecec4" />
<input type="hidden" name="to" value="aHR0cDovL29hdXRoLnZrLmNvbS9vYXV0aC9hdXRob3JpemU/Y2xpZW50X2lkPTE5MTUxMDgmcmVkaXJlY3RfdXJpPWJsYW5rLmh0bWwmcmVzcG9uc2VfdHlwZT1jb2RlJnNjb3BlPTImc3RhdGU9JmRpc3BsYXk9d2Fw">
<span class="label">Телефон або e-mail:</span><br />
<input type="text" name="email"><br />
<span class="label">Пароль:</span><br />
<input type="password" name="pass">
<div style="padding: 8px 0px 5px 0px">
<div class="button_yes">
<input type="submit" value="Увійти" />
</div>
<a class="button_no" href="https://oauth.vk.com/grant_access?hash=c67933e7624f531360&client_id=1915108&settings=2&redirect_uri=blank.html&cancel=1&state=&token_type=0">
<div>
Скасувати
</div>
</a>
</form>


Для подальшої відправки форми необхідно розпарсити всі input'и (и тому числі і приховані), а також URL, на який сабмітиться форма.
params = {
:q => response.body[/name="q" value="(.+?)"/, 1],
:from_host => response.body[/name="from_host" value="(.+?)"/, 1],
:from_protocol => response.body[/name="from_protocol" value="(.+?)"/, 1],
:ip_h => response.body[/name="ip_h" value="(.+?)"/, 1],
:to => response.body[/name="to" value="(.+?)"/, 1]
}
url = response.body[/<form method="POST" action="(.+?)"/, 1]
view raw 4_parse_form.rb hosted with ❤ by GitHub


Авторизуємось


Підставляємо в параметри запиту електронну пошту та пароль користувача і відправляємо форму:
uri = URI(url)
params.merge!(email: email, pass: pass)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(params)
response = Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https') {|http|
http.request(request)
}


Отримуємо куку


view raw 6_get_cookie.rb hosted with ❤ by GitHub


Встановлюємо куку


view raw 7_set_cookie.rb hosted with ❤ by GitHub


Отримуємо код


Наступним етапом, якщо користувач цього ще не робив, треба дати додатку ті права, які ми запитували в параметрі scope. Для цього нам буде запропонована сторінка з формою.

В результаті отримуємо відповідь наступного вигляду:
http://oauth.vk.com/blank.html#code=xxxxxxxxxxxxxxxxxx

if response.code == '302'
url = response['location']
code = /code=(.+)$/.match(url)[1]
elsif response.code == '200'
url = /<form method="POST" action="(.+?)"/.match(response.body)[1]
uri = URI(url)
request = Net::HTTP::Post.new(uri.request_uri, header)
response = Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https') {|http|
http.request(request)
}
if response.code == '302'
url = response['location']
code = /code=(.+)$/.match(url)[1]
end
end
puts 'code=' + code
view raw 8_get_code.rb hosted with ❤ by GitHub


Даний метод авторизації не є офіційним і може змінитися.