Рабочий процесс SE OAuth в Emacs

Прогресс ускоряется в StackMode, клиент Emacs для StackExchange, и теперь мы должны иметь возможность сделать аутентифицированные запросы API для продолжения тестирования. (Предел 300-запросов начинает ограничивать количество тестов, которые я могу выполнить за один день.)

Отказ от ответственности: Я мало что знаю о веб-разработке; это одна из областей, над которыми я работаю профессионально. Пожалуйста, извините меня, если я злоупотребляю любыми условиями и не стесняюсь исправить меня в комментариях. Спасибо!

API StackExchange использует аутентификацию OAuth 2.0. Поскольку это локальное клиентское приложение с авторизацией клиента. У меня есть следующие сведения, предоставленные мне StackExchange:

  • Идентификатор клиента
  • Клиентский секрет (не должен делиться, поэтому в этом потоке не нужно)
  • Key
  • Описание (не связанное с OAuth)
  • Домен OAuth
  • Веб-сайт приложения (не связанный с OAuth)
  • Значок приложения (не связанный с OAuth)
  • Сообщение о публикациях в стеке (не связанное с OAuth)

со следующими дополнительными сведениями:

  • Поток клиентской стороны включен
  • Доступен OAuth Redirect Uri

Чтобы сохранить любой ответ как общий, так и явный, вы можете использовать my-client-id (и т.д.) для значений. Фактические значения - те, которые, я думаю, согласны, - доступные на GitHub.


Я изучаю это в течение половины дня, но я не очень-то ближе к решению, чем когда начал. Самое близкое, что я получил, это небольшой фрагмент кода:

(require 'oauth2) ; available via GNU ELPA
(defconst stack-auth-token
  (make-oauth2-token
   :client-id stack-auth--client-id
   :client-secret stack-auth--key))

;; this doesn't use the above, but it does open an auth page on SE
(oauth2-auth-and-store
 "https://stackexchange.com/oauth/dialog"
 nil nil
 stack-auth--client-id
 stack-auth--key
 "https://stackexchange.com/oauth/login_success")

Единственное, что я могу предложить для запроса OAuth2 (сверху), по-видимому,

  • Идентификатор клиента
  • Key
  • Домен OAuth

Как я могу реализовать этот поток в Elisp?


Текущий "поток"

  • Выполнить oauth2-auth-and-store с соответствующими наборами переменных.
  • Открытие

    auth

  • Нажмите "Утвердить"
  • Открытие

    page

    с этим URL

    url

  • Приложение успешно добавлено

    added

  • Но у меня нет кода для предоставления oauth2

    prompt

В дополнение к ответам, PR также приветствуются, конечно.

Ответ 1

Я постараюсь ответить как можно больше. Я абсолютно ничего не знаю о Lisp, но я очень хорошо знаком с API-интерфейсом Stack Exchange и потоками авторизации.

"Предел 300-запросов начинает ограничивать количество тестов, которые я могу выполнить за один день".

Вы можете обновить это ограничение до 10 000 запросов в день, добавив ключ API в строку запроса URL-адресов методов (&key=...).

"Фактические значения - те, которые, я думаю, я в порядке, чтобы поделиться, доступны на GitHub".

Yup, вы можете их разделить, поскольку любое приложение, отправляющее эти значения, может быть легко реконструировано или декомпилировано для извлечения значений в любом случае.

"4. Открывает [...] страницу [...] с этим URL-адресом"

Это предполагаемое поведение. На скриншоте авторизация прошла успешно, а хэш URL-адресов содержит токен доступа. Этот токен вам понадобится для доступа к определенным методам, таким как /inbox.

Что вы, вероятно, хотите сделать, выглядит примерно так:

  • Продолжайте, как вы делали, пока не достигнете конца шага №4 в вашем примере.
  • Запросить пользователя в Emacs для отображаемого URL-адреса. Они будут копировать и вставлять его как есть.
  • Извлеките хэш (все после самого правого '#') и проанализируйте его так же, как и строку запроса. Параметр access_token содержит нужное значение.
  • Используйте параметры access_token и key (API) при вызове защищенных методов.

Ответ 2

Вот краткий пример. Короче говоря, это откроет auth url в клиентском браузере, попросит пользователя разрешить приложение, а затем перенаправит URL-адрес /oauth/login_success, как описано в docs (неявный auth).

В этом коде предлагается пользователю вставить URL-адрес login_success, затем проанализировать и сохранить access_token, который затем можно будет использовать для последующих вызовов в api. Определены две интерактивные функции: so-authenticate, которая выполняет описанные выше шаги аутентификации, и so-read-inbox, которая извлекает данные api для входящих почтовых ящиков и отправляет их в буфер сообщений.


Предупреждение. В этом примере нет обработки ошибок!

По крайней мере, вы захотите добавить проверки на неудачу аутентификации, сбои запроса api и истечение срока действия токена. Вы можете увидеть пример ошибки api, попробовав вызвать so-read-inbox перед вызовом so-authenticate.


Чтобы запустить, вставьте следующее в буфер, установите переменные so--client-id и so--client-key, затем M-x eval-buffer.

Затем вы можете использовать M-x so-authenticate для аутентификации и M-x so-read-inbox, чтобы сбрасывать ответ входящих сообщений.

(require 'json)

(defvar so--client-id "")  ; SET THIS
(defvar so--client-key "") ; AND THIS

(defvar so--auth-url "https://stackexchange.com/oauth/dialog?")
(defvar so--redirect-url "https://stackexchange.com/oauth/login_success")
(defvar so--api-inbox-url "https://api.stackexchange.com/inbox?")

(defvar so--current-token nil) ; this will get set after authentication

(defun so-authenticate ()
  (interactive)
  (so--open-auth))

(defun so-read-inbox()
  (interactive)
  (so--retrieve-inbox))

;; Open auth url in browser and call so--get-save-token.
(defun so--open-auth ()
  (let ((auth-url
     (concat so--auth-url (url-build-query-string
               `((client_id ,so--client-id)
                 (scope "read_inbox")
                 (redirect_uri ,so--redirect-url))))))
(browse-url auth-url))
  (so--get-save-token))

;; Prompt user for callback URL, extract token and save in so--current-token
(defun so--get-save-token ()
  (let* ((post-auth-url-string (read-string "Enter URL from your browser: "))
     (token (nth 2 (split-string post-auth-url-string "[[#=&]"))))
(setq so--current-token token)
(message "Saved token: %S" token)))

;; Make a request for our inbox data
(defun so--retrieve-inbox()
  (let ((inbox-url (concat so--api-inbox-url
               (url-build-query-string
            `((access_token ,so--current-token) ; the token from auth
              (key ,so--client-key))))))        ; your client key
(url-retrieve inbox-url 'so--retrieve-inbox-cb)))

;; Parse json response for inbox request.
;; This simply dumps the parsed data to your messages buffer.
(defun so--retrieve-inbox-cb (status)
  (goto-char (point-min))
  (re-search-forward "^$")
  (let ((inbox-data (json-read)))
(message "inbox data: %S" inbox-data)))

Теперь получайте удовольствие от анализа ответа!:)