Интеграция Drupal c Dropbox: OAuth авторизация, использование API

Drupal и Dropbox: OAuth авторизация

Всем, доброго дня! Захотелось мне в один прекрасный день вынести часть файлов сайта на отдельный сервер. Поразмыслив еще немного, я понял, что мне подойдет больше не сервер, а сервис с раздачей доступа к файлу. Так и началось написание модуля интеграции с Dropbox.

Первым делом пришлось, конечно же, изучать кучу документации. Первым разочарованием было то, что авторизация на Dropbox возможна только через OAuth – это такой протокол авторизации, который позволяет предоставить третьей стороне ограниченный доступ к ресурсам пользователя без необходимости передавать ей логин и пароль. Вторым разочарованием было то, что для использования рекомендуемого OAuth 2.0 необходимо настраивать на сайте HTTPS – админка Dropbox просто не позволяет указать HTTP в качестве Callback URL’a. Поэтому я решил ограничиться OAuth 1.0.

По итогу разработки модуля у меня получилась следующая схема: администратор сайта авторизуется на Dropbox, сохраняя тем самым Access Token в переменной Drupal, а все остальные пользователи, сами того не подозревая, льют все файлы в указанный администратором аккаунт на Dropbox. Сразу отмечу, что Access Token хранится довольно продолжительное время, поэтому вам не придется каждый день обновлять авторизацию.

Если вас устраивает такая схема, то перейдем к деталям реализации. В рамках этого поста расскажу об общих принципах работы с OAuth протоколом, об использовании Dropbox API и о том, как создать административную страницу в Drupal для авторизации.

Регистрация приложения

Чтобы использовать Dropbox API, вам необходимо зарегистрировать новое приложение для получения App key и App secret. Так как мы будем использовать OAuth 1.0, то нет необходимости заполнять какие-то специфические поля, наподобие OAuth redirect URIs. Все, что от вас требуется в процессе регистрации – это правильно выбрать тип приложения, заполнить поле названия и в случае необходимости отредактировать название папки.

Регистрация приложения в Dropbox

Страница с настройками для Drupal

Так как целью этого поста стоит показать процесс написания ровного модуля интеграции со сторонним сервисом, необходимо создать административную страницу Drupal, где будут храниться настройки приложения. В нашем случае будет 2 страницы:

  • страница для указания App key и App secret;
  • страница статуса авторизации на Dropbox.

Модуль у меня будет называться "dropbox_file" – имейте это в виду при описании хуков. Надеюсь, создать файлы модуля .info и .module ни для кого не составит труда. Далее в .module имплементируем hook_menu() и описываем указанные страницы:

  1. // Определяем несколько констант для удобства.
  2. define('DROPBOX_FILE_APP_KEY_NAME', 'dropbox_file_settings_app_key');
  3. define('DROPBOX_FILE_APP_SECRET_NAME', 'dropbox_file_settings_app_secret');
  4. define('DROPBOX_FILE_OAUTH_TOKENS_NAME', 'dropbox_file_oauth_tokens');
  5.  
  6. /**
  7.  * Implements hook_menu().
  8.  */
  9. function dropbox_file_menu() {
  10. $items = array();
  11.  
  12. $items['admin/config/media/dropbox_file'] = array(
  13. 'title' => 'Dropbox File',
  14. 'description' => 'Dropbox app configuration.',
  15. 'page callback' => 'drupal_get_form',
  16. 'page arguments' => array('dropbox_file_administer_settings_form'),
  17. 'access arguments' => array('administer dropbox file'),
  18. 'type' => MENU_NORMAL_ITEM,
  19. 'file' => 'dropbox_file.admin.inc',
  20. );
  21.  
  22. // Псевдо-элемент, чтобы выводить страницы администрирования в виде табов.
  23. $items['admin/config/media/dropbox_file/list'] = array(
  24. 'title' => 'Dropbox File',
  25. 'type' => MENU_DEFAULT_LOCAL_TASK,
  26. 'weight' => 0,
  27. );
  28.  
  29. $items['admin/config/media/dropbox_file/account'] = array(
  30. 'title' => 'Account',
  31. 'description' => 'Dropbox account login and statistics',
  32. 'page callback' => 'drupal_get_form',
  33. 'page arguments' => array('dropbox_file_administer_account_form'),
  34. 'access arguments' => array('administer dropbox file'),
  35. 'type' => MENU_LOCAL_TASK,
  36. 'weight' => 2,
  37. 'file' => 'dropbox_file.admin.inc',
  38. );
  39.  
  40. return $items;
  41. }
  42.  
  43. /**
  44.  * Implements hook_permission().
  45.  */
  46. function dropbox_file_permission() {
  47. return array(
  48. 'administer dropbox file' => array(
  49. 'title' => t('Administer Dropbox File'),
  50. 'description' => t('Administer Dropbox File Settings.'),
  51. ),
  52. );
  53. }

Как вы уже поняли, необходимо создать еще один файл модуля – dropbox_file.admin.inc, где будут описаны страницы. Первая страница у меня использует стандартную схему создания форм в Drupal для сохранения каких-либо значений в переменные.

  1. /**
  2.  * Page callback for Settings page.
  3.  *
  4.  * @param $form
  5.  * @param $form_state
  6.  */
  7. function dropbox_file_administer_settings_form($form, &$form_state) {
  8. $form[DROPBOX_FILE_APP_KEY_NAME] = array(
  9. '#type' => 'textfield',
  10. '#title' => t('Dropbox App Key'),
  11. '#required' => TRUE,
  12. '#default_value' => variable_get(DROPBOX_FILE_APP_KEY_NAME, ''),
  13. );
  14.  
  15. $form[DROPBOX_FILE_APP_SECRET_NAME] = array(
  16. '#type' => 'textfield',
  17. '#title' => t('Dropbox App Secret'),
  18. '#required' => TRUE,
  19. '#default_value' => variable_get(DROPBOX_FILE_APP_SECRET_NAME, ''),
  20. );
  21.  
  22. return system_settings_form($form);
  23. }

Вторая страница – более сложная форма, которая отображает статус авторизации на Dropbox. Если вы авторизованы, то выводится информация об аккаунте. Если не авторизованы – выводится Submit кнопка для запуска процесса авторизации.

  1. /**
  2.  * Page callback for Account page.
  3.  *
  4.  * @param $form
  5.  * @param $form_state
  6.  */
  7. function dropbox_file_administer_account_form($form, &$form_state) {
  8. $form['info'] = array(
  9. '#markup' => '<p>Dropbox login processing, account information.</p>',
  10. );
  11.  
  12. // Получаем ключи зарегистрированного приложения.
  13. $dropbox_app_key = variable_get(DROPBOX_FILE_APP_KEY_NAME, '');
  14. $dropbox_app_secret = variable_get(DROPBOX_FILE_APP_SECRET_NAME, '');
  15.  
  16. // Проверяем установлены ли ключи приложения. Если нет - выводим уведомление.
  17. if (empty($dropbox_app_key) || empty($dropbox_app_secret)) {
  18. $link = l('Drupal file settings', 'admin/config/media/dropbox_file');
  19. drupal_set_message(t('Dropbox app keys isn\'t set. Configure !link.', array('!link' => $link)), 'error');
  20. return $form;
  21. }
  22.  
  23. // Создаем экземпляр класс для использования Dropbox API. Класс будет описан чуть позже.
  24. $Dropbox = new DropboxFileService();
  25.  
  26. // Получаем Access Token.
  27. // После успешной авториазции на Dropbox пользователь перенаправляется на страницу
  28. // этой формы с необходимым набором GET-параметров.
  29. if (!empty($_GET['uid']) && !empty($_GET['oauth_token'])) {
  30. $result = $Dropbox->getAccessToken();
  31. if (!empty($result['error'])) {
  32. drupal_set_message($result['error'], 'error');
  33. return $form;
  34. }
  35.  
  36. // Перенаправляем пользователя на эту же страницу, чтобы удалить
  37. // GET-параметры для предотвращения повтороного получения Access Token.
  38. drupal_goto('admin/config/media/dropbox_file/account');
  39. }
  40.  
  41. // Осуществляем запрос к Dropbox API для получения информации об подключенном аккаунте.
  42. // Если запрос завершен с ошибкой, значит Access Token истек и требуется обновить авторизацию.
  43. $result = $Dropbox->accountInfo();
  44. if (!empty($result['error'])) {
  45. drupal_set_message($result['error'], 'error');
  46.  
  47. $form['authorize'] = array(
  48. '#type' => 'submit',
  49. '#value' => t('Dropbox connect'),
  50. '#name' => 'dropbox_connect',
  51. '#submit' => array('dropbox_file_administer_account_authorize'),
  52. );
  53. return $form;
  54. }
  55.  
  56. // Вывод данных об подключенном Dropbox аккаунте в виде таблицы.
  57. $header = array();
  58. $row = array();
  59. foreach ($result as $key => $value) {
  60. $header[] = array('data' => $key);
  61. $row[] = $value;
  62. }
  63. $rows[] = array('data' => $row);
  64.  
  65. $table = theme('table', array('header' => $header, 'rows' => $rows));
  66. $form['dropbox'] = array(
  67. '#markup' => $table,
  68. );
  69.  
  70. drupal_set_message(t('Dropbox has been already connected!'));
  71.  
  72. return $form;
  73. }
  74.  
  75. /**
  76.  * Form submit callback.
  77.  *
  78.  * @param $from
  79.  * @param $form_state
  80.  */
  81. function dropbox_file_administer_account_authorize($from, &$form_state) {
  82. $Dropbox = new DropboxFileService();
  83.  
  84. // Запрос на получение Request Token.
  85. $result = $Dropbox->getRequestToken();
  86. if (!empty($result['error'])) {
  87. drupal_set_message($result['error'], 'error');
  88. return;
  89. }
  90.  
  91. // Запрос на авторизацию на Dropbox, в ходе которого пользователя перенаправляет
  92. // на форму авторизации. В качетсве аргумента передаем URL, на который пользователь
  93. // вернется после успешной авторизации.
  94. $Dropbox->authorizeRedirect('admin/config/media/dropbox_file/account');
  95. }

OAuth авторизация

Документация OAuth 1.0: http://oauth.net/core/1.0/

Документация Dropbox API: https://www.dropbox.com/developers/core/docs

Общие принципы OAuth авторизации у всех сервисов одинаковы: что VK, что Facebook, что Dropbox – все везде практически одинаково и описано в спецификациях OAuth протокола. Конечная цель авторизации получить Access Token, который в дальнейшем используется в каждом API-запросе. Авторизацию можно разделить на 3 этапа:

  1. получение Request Token;
  2. авторизация пользователя;
  3. получение Access Token.

Большинство запросов, использующих OAuth протокол, содержат следующие параметры:

  • oauth_consumer_key – как правило, App key, полученный при регистрации приложения;
  • oauth_signature_method – как я понял, выбор метода шифрования oauth_signature, в котором передается App secret и Access Token;
  • oauth_signature – параметр для передачи App secret и Access Token;
  • oauth_timestamp – временная метка запроса в формате Unix;
  • oauth_nonce – могу ошибаться, но сгенерированная уникальная строка для каждого запроса;
  • oauth_version – версия протокола OAuth (1.0, 2.0);
  • Additional parameters – любые другие параметры, которые определены сервисом.

На первый взгляд все понятно, но, когда начинаешь пытаться авторизоваться, то возникает достаточно много непонятных моментов. Может это от особенностей Dropbox API, может от того, что я до этого никогда с OAuth дело не имел, но времени на написание авторизации у меня ушло прилично. Поэтому постараюсь в деталях рассказать все этапы.

Для взаимодействия с Dropbox API я решил написать свой PHP-класс. Хоть в Drupal 7 мало где встретишь ООП, но в случае написания интеграции с каким-либо сервисом рекомендую все же использовать классы.

Создание класса

В модуле заводим папку "includes", в которой создаем файл класса "DropboxFileService.class.php". Не забываем в .info файле модуля подключить данный файл: files[] = includes/DropboxFileService.class.php.

В созданном файле начинаем описание нашего класса:

  1. class DropboxFileService {
  2. private $dropbox_app_key = '';
  3. private $dropbox_app_secret = '';
  4. private $oauth_token = '';
  5. private $oauth_token_secret = '';
  6. private $root = 'sandbox';
  7. private $timeout = 30;
  8.  
  9. /**
  10.   * Constructor.
  11.   */
  12. function __construct() {
  13. $this->dropbox_app_key = variable_get(DROPBOX_FILE_APP_KEY_NAME, '');
  14. $this->dropbox_app_secret = variable_get(DROPBOX_FILE_APP_SECRET_NAME, '');
  15. }

Все дальнейшие функции класса будем дописывать по мере чтения данного поста.

Получение Request Token

Request Token – это самый первый OAuth Token, который мы будем с вами получать. Он необходим для следующего запроса авторизации. Уже на этом этапе я столкнулся с трудностями, так как не сразу понял, что в 'oauth_signature' помимо App secret надо еще добавлять символ ‘&’, даже если второго параметра нет!

  1. /**
  2.   * Request token.
  3.   *
  4.   * @return array|mixed
  5.   */
  6. public function getRequestToken() {
  7. $params = array(
  8. 'oauth_consumer_key' => $this->dropbox_app_key,
  9. 'oauth_signature_method' => 'PLAINTEXT',
  10. 'oauth_version' => '1.0',
  11. 'oauth_signature' => $this->dropbox_app_secret . '&',
  12. );
  13.  
  14. $options = array(
  15. 'method' => 'POST',
  16. 'timeout' => $this->timeout,
  17. );
  18.  
  19. $response = drupal_http_request('https://api.dropbox.com/1/oauth/request_token?' . drupal_http_build_query($params), $options);
  20. $result = array('code' => $response->code);
  21.  
  22. if ($response->code == "200") {
  23. parse_str($response->data, $oauth_tokens);
  24. $this->oauth_token = $oauth_tokens['oauth_token'];
  25. $this->oauth_token_secret = $oauth_tokens['oauth_token_secret'];
  26. variable_set(DROPBOX_FILE_OAUTH_TOKENS_NAME, $oauth_tokens);
  27. $result += $oauth_tokens;
  28. }
  29. else {
  30. $data = drupal_json_decode($response->data);
  31. $result += $data;
  32. }
  33.  
  34. return $result;
  35. }

Авторизация на Dropbox

После получения Request Token, можно смело переходить к процессу авторизации. Для этого необходимо перенаправить пользователя на заданную страницу Dropbox и в качестве GET-параметров указать полученный 'oauth_token' и 'oauth_callback' – страница на нашем сайте, на которую будет перенаправлен пользователь в случае успешной авторизации.

  1. /**
  2.   * Authorize.
  3.   *
  4.   * @param $oauth_callback
  5.   * @param null $oauth_token
  6.   */
  7. public function authorizeRedirect($oauth_callback) {
  8. $params = array(
  9. 'oauth_token' => $this->oauth_token,
  10. 'oauth_callback' => url($oauth_callback, array('absolute' => TRUE)),
  11. );
  12.  
  13. $url = 'https://www.dropbox.com/1/oauth/authorize?' . drupal_http_build_query($params);
  14. drupal_goto($url);
  15. }

Получение Access Token

После успешной авторизации пользователь по указанному URL’у в 'oauth_callback' должен вернуться на страницу, которая готова принять входящие GET-параметры ‘uid’ и ‘oauth_token’. Именно это и реализовано в нашей второй форме, описанной в dropbox_file.admin.inc: если пользователь пришел на страницу с указанными GET-параметрами, то осуществляется запрос на получение Access Token и перенаправление на страницу формы уже без параметров, чтобы избежать повторных запросов.

  1. /**
  2.   * Access token.
  3.   *
  4.   * @return array|mixed
  5.   */
  6. public function getAccessToken() {
  7. $tokens = variable_get(DROPBOX_FILE_OAUTH_TOKENS_NAME, array());
  8. $oauth_token = !empty($tokens['oauth_token']) ? $tokens['oauth_token'] : '';
  9. $oauth_token_secret = !empty($tokens['oauth_token_secret']) ? $tokens['oauth_token_secret'] : '';
  10.  
  11. $params = array(
  12. 'oauth_consumer_key' => $this->dropbox_app_key,
  13. 'oauth_token' => $oauth_token,
  14. 'oauth_signature_method' => 'PLAINTEXT',
  15. 'oauth_version' => '1.0',
  16. 'oauth_signature' => $this->dropbox_app_secret . '&' . $oauth_token_secret,
  17. );
  18.  
  19. $options = array(
  20. 'method' => 'POST',
  21. 'timeout' => $this->timeout,
  22. );
  23.  
  24. $response = drupal_http_request('https://api.dropbox.com/1/oauth/access_token?' . drupal_http_build_query($params), $options);
  25. $result = array('code' => $response->code);
  26.  
  27. if ($response->code == "200") {
  28. parse_str($response->data, $oauth_tokens);
  29. $this->oauth_token = $oauth_tokens['oauth_token'];
  30. $this->oauth_token_secret = $oauth_tokens['oauth_token_secret'];
  31. variable_set(DROPBOX_FILE_OAUTH_TOKENS_NAME, $oauth_tokens);
  32. $result += $oauth_tokens;
  33. }
  34. else {
  35. $data = drupal_json_decode($response->data);
  36. $result += $data;
  37. }
  38.  
  39. return $result;
  40. }

Именно после данного этапа, когда Access Token сохранен в переменной вашего сайта, вы можете активно использовать Dropbox API: получать информацию об аккаунте, загружать файлы, удалять файлы и т.д.

Пример API запроса

В качестве примера, раз уже метод $Dropbox->accountInfo(); используется в коде описанной формы, покажу как делаются запросы к API Dropbox.

  1. /**
  2.   * Account info.
  3.   *
  4.   * @return array|mixed
  5.   */
  6. public function accountInfo() {
  7. $params = $this->requestParamsPrepare('api');
  8.  
  9. $options = array(
  10. 'method' => 'POST',
  11. 'timeout' => $this->timeout,
  12. );
  13.  
  14. $response = drupal_http_request('https://api.dropbox.com/1/account/info?' . drupal_http_build_query($params), $options);
  15. $result = $this->processResponse($response);
  16.  
  17. return $result;
  18. }

Ну, вот и все. После авторизации и получения Access Token вы можете незаметно для пользователей изменить файловую систему Drupal и заставить их заливать файлы прямо к вам на Dropbox аккаунт. О том, как организовать хранение данных о файлах Dropbox в Drupal я расскажу в следующем посте. Пост обещает быть очень интересным – подписывайтесь :)

Комментарии

Аватар пользователя xandeadx
xandeadx

почему не использовал модуль https://drupal.org/project/oauth‎ ?

Аватар пользователя angarsky
angarsky
потому что не знал о нем :) спасибо. гляну на досуге, что за он
Аватар пользователя Прохожий
Прохожий

Не работает. Просто выводит текстом содержимое .module над и под контентом. В чем причина может быть?

Аватар пользователя ali
ali

Д.Д. если у тебя проект получилось сделать как в описании помоги пожалуйста мне тоже разобраться в этом спасибо

Аватар пользователя Павел
Павел

Все сделал как в примере,когда перехожу по пути /admin/config/media/dropbox_file/account - ошибка.В чем может быть проблема?
Как я понимаю при переходе в account должны отображатся все мои файли с dropbox,верно?

Fatal error: Call to undefined method DropboxFileService::accountInfo() in /var/www/online-tutoring-system/ots50/sites/all/modules/dropbox_file/dropbox_file.admin.inc on line 58

Аватар пользователя Владимир
Владимир

У меня та же проблема!!!

Аватар пользователя Владимир
Владимир

Помогите плиз, а то Кельт ругаться будет если таск не сделаем.

Аватар пользователя angarsky
angarsky
Ребяты, а я скажу, что все работает!!11 Специально заходил и проверял на проекте, ради которого это все писалось. Я уже не помню, но возможно, что в посте указан листинг не всех функций. Так что включайте смекалку и все у вас получаются. Отвечаю!
Аватар пользователя Кельт
Кельт

Вы уж помогите им, иначе им поможет только скорая! Уже второю неделю не могут сделать элементарную задачу!!!

Аватар пользователя Дмитрий
Дмитрий

Я сейчас работаю с Кельтом и могу сказать, что он действительно не шутит по поводу скорой. Парням реально нужна Ваша помощь. Атвечаю! Анонимно плз!

Добавить комментарий

 888                 88888888888   .d8888b.  
888 888 d88P Y88b
888 888 Y88b.
88888b. .d88888 888 "Y888b.
888 "88b d88" 888 888 "Y88b.
888 888 888 888 888 "888
888 888 Y88b 888 888 Y88b d88P
888 888 "Y88888 888 "Y8888P"
888
888
888
Зарегистрируйтесь для добавления материалов без проверки.