Введение
В этом руководстве показано, как создать интерфейс командной строки (CLI), поддерживаемый GitHub App, и как использовать поток устройства для создания маркера доступа пользователя для приложения.
Интерфейс командной строки будет иметь три команды:
help
: выводит инструкции по использованию.login
: создает маркер доступа пользователя, который приложение может использовать для выполнения запросов API от имени пользователя.whoami
: возвращает сведения о пользователе, вошедшего в систему.
В этом руководстве используется Ruby, но вы можете написать интерфейс командной строки и использовать поток устройства для создания маркера доступа пользователей с любым языком программирования.
Сведения о потоках устройств и маркерах доступа пользователей
Интерфейс командной строки будет использовать поток устройства для проверки подлинности пользователя и создания маркера доступа пользователя. Затем ИНТЕРФЕЙС командной строки может использовать маркер доступа пользователя для выполнения запросов API от имени прошедшего проверку подлинности пользователя.
Приложение должно использовать маркер доступа пользователя, если вы хотите атрибутировать действия приложения пользователю. Дополнительные сведения см. в разделе Проверка подлинности с помощью приложения GitHub от имени пользователя.
Существует два способа создания маркера доступа пользователя для GitHub App: поток веб-приложения и поток устройств. Поток устройства следует использовать для создания маркера доступа пользователей, если ваше приложение не является головным или не имеет доступа к веб-интерфейсу. Например, средства CLI, простые Raspberry Pis и классические приложения должны использовать поток устройств. Если приложение имеет доступ к веб-интерфейсу, вместо этого следует использовать поток веб-приложения. Дополнительные сведения см. в разделе "[AUTOTITLE" и "Создание маркера доступа пользователя для приложения GitHub](/apps/creating-github-apps/guides/using-the-web-application-flow-to-generate-a-user-access-token-for-a-github-app)".
Необходимые компоненты
В этом руководстве предполагается, что вы уже зарегистрировали GitHub App. Дополнительные сведения о регистрации GitHub Appсм. в разделе "Регистрация приложения GitHub".
Перед выполнением этого руководства необходимо включить поток устройств для приложения. Дополнительные сведения о включении потока устройств для приложения см. в разделе "Изменение регистрации приложения GitHub".
В этом руководстве предполагается, что у вас есть базовое представление о Ruby. Дополнительные сведения см. в разделе Ruby.
Получение идентификатора клиента
Для создания маркера доступа пользователя с помощью потока устройства вам потребуется идентификатор клиента приложения.
- В правом верхнем углу любой страницы на GitHubщелкните фото профиля.
- Перейдите к настройкам учетной записи.
- Для приложения, принадлежащих личная учетная запись, нажмите кнопку "Параметры".
- Для приложения, принадлежащих организации:
- Щелкните Your organizations (Ваши организации).
- Справа от организации нажмите кнопку "Параметры".
- На левой боковой панели щелкните Параметры разработчика.
- На левой боковой панели щелкните GitHub Apps.
- Рядом с GitHub App, с которыми вы хотите работать, нажмите кнопку "Изменить".
- На странице параметров приложения найдите идентификатор клиента для приложения. Вы будете использовать его далее в этом руководстве. Обратите внимание, что идентификатор клиента отличается от идентификатора приложения.
Запись интерфейса командной строки
Эти шаги поведут вас за счет создания интерфейса командной строки и использования потока устройств для получения маркера доступа пользователей. Сведения о переходе к окончательному коду см. в разделе "Полный пример кода".
Настройка
-
Создайте файл Ruby для хранения кода, который создаст маркер доступа пользователя. В этом руководстве будет присвоено имя файла
app_cli.rb
. -
В терминале в каталоге, где
app_cli.rb
хранится, выполните следующую команду, чтобы сделатьapp_cli.rb
исполняемый файл:Text chmod +x app_cli.rb
chmod +x app_cli.rb
-
Добавьте эту строку в верхнюю часть
app_cli.rb
, чтобы указать, что интерпретатор Ruby должен использоваться для выполнения скрипта:Ruby #!/usr/bin/env ruby
#!/usr/bin/env ruby
-
Добавьте эти зависимости в верхнюю часть
app_cli.rb
:#!/usr/bin/env ruby
Ruby require "net/http" require "json" require "uri" require "fileutils"
require "net/http" require "json" require "uri" require "fileutils"
Это все часть стандартной библиотеки Ruby, поэтому вам не нужно устанавливать какие-либо драгоценные камни.
-
Добавьте следующую
main
функцию, которая будет служить точкой входа. Функция включает инструкциюcase
для выполнения различных действий в зависимости от указанной команды. Вы развернете этуcase
инструкцию позже.Ruby def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end
def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end
-
В нижней части файла добавьте следующую строку, чтобы вызвать функцию точки входа. Этот вызов функции должен оставаться в нижней части файла при добавлении дополнительных функций в этот файл далее в руководстве.
Ruby main
main
-
При необходимости проверьте ход выполнения:
app_cli.rb
Теперь выглядит следующим образом:Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end main
#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end main
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb help
. Вы должны увидеть следующий результат:`help` is not yet defined
Вы также можете протестировать скрипт без команды или с помощью необработанных команд. Например, должен выводить следующее
./app_cli.rb create-issue
:Unknown command `create-issue`
help
Добавление команды
-
Добавьте следующую
help
функциюapp_cli.rb
в . В настоящее время функция выводит строку, чтобы сообщить пользователям,help
что этот CLI принимает одну команду , "справка". Вы развернете этуhelp
функцию позже.Ruby def help puts "usage: app_cli <help>" end
def help puts "usage: app_cli <help>" end
-
main
Обновите функцию, чтобы вызватьhelp
функцию приhelp
выполнении команды:Ruby def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
-
При необходимости проверьте ход выполнения:
app_cli.rb
теперь выглядит следующим образом. Порядок функций не имеет значения до тех пор, покаmain
вызов функции находится в конце файла.Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def help puts "usage: app_cli <help>" end def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end main
#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def help puts "usage: app_cli <help>" end def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end main
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb help
. Вы должны увидеть следующий результат:usage: app_cli <help>
login
Добавление команды
Команда login
запустит поток устройства для получения маркера доступа пользователя. Дополнительные сведения см. в разделе Создание маркера доступа пользователя для приложения GitHub.
-
В верхней части файла после
require
инструкций добавьтеCLIENT_ID
GitHub App вapp_cli.rb
качестве константы. Дополнительные сведения о поиске идентификатора клиента приложения см. в разделе "Получение идентификатора клиента". ЗаменитеYOUR_CLIENT_ID
идентификатором клиента приложения:Ruby CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_ID="YOUR_CLIENT_ID"
-
Добавьте следующую
parse_response
функциюapp_cli.rb
в . Эта функция анализирует ответ от REST API GitHub REST API. Если состояние ответа равно200 OK
или201 Created
, функция возвращает текст синтаксического ответа. В противном случае функция выводит ответ и текст и выходит из программы.Ruby def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end
def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end
-
Добавьте следующую
request_device_code
функциюapp_cli.rb
в . Эта функция отправляетPOST
запросhttp(s)://HOSTNAME/login/device/code
и возвращает ответ.Ruby def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
-
Добавьте следующую
request_token
функциюapp_cli.rb
в . Эта функция отправляетPOST
запросhttp(s)://HOSTNAME/login/oauth/access_token
и возвращает ответ.Ruby def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
-
Добавьте следующую
poll_for_token
функциюapp_cli.rb
в . Эта функция опрашиваетhttp(s)://HOSTNAME/login/oauth/access_token
по указанному интервалу, пока GitHub не отвечаетaccess_token
параметру, а не параметруerror
. Затем он записывает маркер доступа пользователя в файл и ограничивает разрешения для файла.Ruby def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end
def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end
-
Добавьте следующую
login
функцию.Эта функция выполняет следующее:
request_device_code
Вызывает функцию и получаетdevice_code``verification_uri``user_code``interval
параметры из ответа.- Предложит пользователям ввести
user_code
предыдущий шаг. poll_for_token
Вызывает опрос GitHub для маркера доступа.- Позволяет пользователю знать, что проверка подлинности выполнена успешно.
Ruby def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end
def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end
-
main
Обновите функцию, чтобы вызватьlogin
функцию приlogin
выполнении команды:Ruby def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
-
help
Обновите функцию, чтобы включитьlogin
команду:Ruby def help puts "usage: app_cli <login | help>" end
def help puts "usage: app_cli <login | help>" end
-
При необходимости проверьте ход выполнения:
app_cli.rb
теперь выглядит примерно так, гдеYOUR_CLIENT_ID
находится идентификатор клиента приложения. Порядок функций не имеет значения до тех пор, покаmain
вызов функции находится в конце файла.Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" CLIENT_ID="YOUR_CLIENT_ID" def help puts "usage: app_cli <login | help>" end def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end main
#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" CLIENT_ID="YOUR_CLIENT_ID" def help puts "usage: app_cli <login | help>" end def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end main
-
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb login
. Вы увидите выходные данные, которые выглядят следующим образом. Код будет отличаться каждый раз:Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94
-
Перейдите к http(s)://HOSTNAME/login/device в браузере и введите код на предыдущем шаге, а затем нажмите кнопку "Продолжить".
-
GitHub должен отображать страницу, которая предлагает авторизовать приложение. Нажмите кнопку "Авторизовать".
-
Теперь терминал должен сказать:"Успешно выполнена проверка подлинности!".
-
whoami
Добавление команды
Теперь, когда приложение может создать маркер доступа пользователя, вы можете выполнять запросы API от имени пользователя. whoami
Добавьте команду, чтобы получить имя пользователя пользователя, прошедшего проверку подлинности.
-
Добавьте следующую
whoami
функциюapp_cli.rb
в . Эта функция получает сведения о пользователе с конечной/user
точкой REST API. Он выводит имя пользователя, соответствующее маркеру доступа пользователя..token
Если файл не найден, он предложит пользователю запустить функциюlogin
.Ruby def whoami uri = URI("http(s)://HOSTNAME/api/v3/user") begin token = File.read("./.token").strip rescue Errno::ENOENT => e puts "You are not authorized. Run the `login` command." exit 1 end response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| body = {"access_token" => token}.to_json headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"} http.send_request("GET", uri.path, body, headers) end parsed_response = parse_response(response) puts "You are #{parsed_response["login"]}" end
def whoami uri = URI("http(s)://HOSTNAME/api/v3/user") begin token = File.read("./.token").strip rescue Errno::ENOENT => e puts "You are not authorized. Run the `login` command." exit 1 end response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| body = {"access_token" => token}.to_json headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"} http.send_request("GET", uri.path, body, headers) end parsed_response = parse_response(response) puts "You are #{parsed_response["login"]}" end
-
parse_response
Обновите функцию, чтобы справиться с ситуацией, когда срок действия маркера истек или был отменен. Теперь, если вы получите ответ, интерфейс командной401 Unauthorized
строки предложит пользователю выполнитьlogin
команду.Ruby def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) when Net::HTTPUnauthorized puts "You are not authorized. Run the `login` command." exit 1 else puts response puts response.body exit 1 end end
def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) when Net::HTTPUnauthorized puts "You are not authorized. Run the `login` command." exit 1 else puts response puts response.body exit 1 end end
-
main
Обновите функцию, чтобы вызватьwhoami
функцию приwhoami
выполнении команды:Ruby def main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end end
def main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end end
-
help
Обновите функцию, чтобы включитьwhoami
команду:Ruby def help puts "usage: app_cli <login | whoami | help>" end
def help puts "usage: app_cli <login | whoami | help>" end
-
Проверьте код в полном примере кода в следующем разделе. Вы можете протестировать код, выполнив действия, описанные в разделе "Тестирование" ниже полного примера кода.
Полный пример кода
Это полный пример кода, описанный в предыдущем разделе. Замените YOUR_CLIENT_ID
идентификатором клиента приложения.
#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" CLIENT_ID="YOUR_CLIENT_ID" def help puts "usage: app_cli <login | whoami | help>" end def main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end end def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) when Net::HTTPUnauthorized puts "You are not authorized. Run the `login` command." exit 1 else puts response puts response.body exit 1 end end def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end def whoami uri = URI("http(s)://HOSTNAME/api/v3/user") begin token = File.read("./.token").strip rescue Errno::ENOENT => e puts "You are not authorized. Run the `login` command." exit 1 end response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| body = {"access_token" => token}.to_json headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"} http.send_request("GET", uri.path, body, headers) end parsed_response = parse_response(response) puts "You are #{parsed_response["login"]}" end main
#!/usr/bin/env ruby
require "net/http"
require "json"
require "uri"
require "fileutils"
CLIENT_ID="YOUR_CLIENT_ID"
def help
puts "usage: app_cli <login | whoami | help>"
end
def main
case ARGV[0]
when "help"
help
when "login"
login
when "whoami"
whoami
else
puts "Unknown command #{ARGV[0]}"
end
end
def parse_response(response)
case response
when Net::HTTPOK, Net::HTTPCreated
JSON.parse(response.body)
when Net::HTTPUnauthorized
puts "You are not authorized. Run the `login` command."
exit 1
else
puts response
puts response.body
exit 1
end
end
def request_device_code
uri = URI("http(s)://HOSTNAME/login/device/code")
parameters = URI.encode_www_form("client_id" => CLIENT_ID)
headers = {"Accept" => "application/json"}
response = Net::HTTP.post(uri, parameters, headers)
parse_response(response)
end
def request_token(device_code)
uri = URI("http(s)://HOSTNAME/login/oauth/access_token")
parameters = URI.encode_www_form({
"client_id" => CLIENT_ID,
"device_code" => device_code,
"grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
})
headers = {"Accept" => "application/json"}
response = Net::HTTP.post(uri, parameters, headers)
parse_response(response)
end
def poll_for_token(device_code, interval)
loop do
response = request_token(device_code)
error, access_token = response.values_at("error", "access_token")
if error
case error
when "authorization_pending"
# The user has not yet entered the code.
# Wait, then poll again.
sleep interval
next
when "slow_down"
# The app polled too fast.
# Wait for the interval plus 5 seconds, then poll again.
sleep interval + 5
next
when "expired_token"
# The `device_code` expired, and the process needs to restart.
puts "The device code has expired. Please run `login` again."
exit 1
when "access_denied"
# The user cancelled the process. Stop polling.
puts "Login cancelled by user."
exit 1
else
puts response
exit 1
end
end
File.write("./.token", access_token)
# Set the file permissions so that only the file owner can read or modify the file
FileUtils.chmod(0600, "./.token")
break
end
end
def login
verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")
puts "Please visit: #{verification_uri}"
puts "and enter code: #{user_code}"
poll_for_token(device_code, interval)
puts "Successfully authenticated!"
end
def whoami
uri = URI("http(s)://HOSTNAME/api/v3/user")
begin
token = File.read("./.token").strip
rescue Errno::ENOENT => e
puts "You are not authorized. Run the `login` command."
exit 1
end
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
body = {"access_token" => token}.to_json
headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"}
http.send_request("GET", uri.path, body, headers)
end
parsed_response = parse_response(response)
puts "You are #{parsed_response["login"]}"
end
main
Тестирование
В этом руководстве предполагается, что код приложения хранится в файле с именем app_cli.rb
.
-
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb help
. Вы увидите выходные данные, которые выглядят следующим образом.usage: app_cli <login | whoami | help>
-
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb login
. Вы увидите выходные данные, которые выглядят следующим образом. Код будет отличаться каждый раз:Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94
-
Перейдите к http(s)://HOSTNAME/login/device в браузере и введите код на предыдущем шаге, а затем нажмите кнопку "Продолжить".
-
GitHub должен отображать страницу, которая предлагает авторизовать приложение. Нажмите кнопку "Авторизовать".
-
Теперь терминал должен сказать:"Успешно выполнена проверка подлинности!".
-
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb whoami
. Вы увидите выходные данные, которые выглядят следующим образом, гдеoctocat
находится ваше имя пользователя.You are octocat
-
Откройте файл в редакторе
.token
и измените маркер. Теперь маркер недопустим. -
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb whoami
. Вы должны получить следующий результат:You are not authorized. Run the `login` command.
-
Удалите файл
.token
. -
В терминале в каталоге, где
app_cli.rb
хранится, выполните команду./app_cli.rb whoami
. Вы должны получить следующий результат:You are not authorized. Run the `login` command.
Следующие шаги
Настройка кода в соответствии с потребностями приложения
В этом руководстве показано, как написать интерфейс командной строки, использующий поток устройства для создания маркера доступа пользователей. Вы можете развернуть этот интерфейс командной строки, чтобы принять дополнительные команды. Например, можно добавить create-issue
команду, которая открывает проблему. Не забудьте обновить разрешения приложения, если приложению требуются дополнительные разрешения для запросов API, которые вы хотите сделать. Дополнительные сведения см. в разделе Выбор разрешений для приложения GitHub.
Безопасное хранение маркеров
В этом руководстве создается маркер доступа пользователя и сохраняется в локальном файле. Этот файл никогда не следует зафиксировать или публиковать маркер.
В зависимости от устройства можно выбрать различные способы хранения маркера. Рекомендуется проверить рекомендации по хранению маркеров на устройстве.
Дополнительные сведения см. в разделе Рекомендации по созданию приложения GitHub.
Применение рекомендаций
Вам следует следовать рекомендациям по использованию данных GitHub App. Дополнительные сведения см. в разделе Рекомендации по созданию приложения GitHub.