Skip to main content
Мы публикуем частые обновления нашей документации, и перевод этой страницы может все еще выполняться. Актуальные сведения см. в документации на английском языке.

Создание интерфейса командной строки с помощью Приложение GitHub

Следуйте инструкциям из этого руководства, чтобы написать интерфейс командной строки в Ruby, который создает маркер доступа пользователя для GitHub App через поток устройства.

Введение

В этом руководстве показано, как создать интерфейс командной строки (CLI) на основе GitHub App, а также как использовать поток устройства для создания маркера доступа пользователя для приложения.

Интерфейс командной строки содержит три команды:

  • help: выводит инструкции по использованию.
  • login: создает маркер доступа пользователя, который приложение может использовать для выполнения запросов API от имени пользователя.
  • whoami: возвращает сведения о вошедшего пользователя.

В этом руководстве используется Ruby, но вы можете написать интерфейс командной строки и использовать поток устройства для создания маркера доступа пользователя на любом языке программирования.

Сведения о потоке устройства и маркерах доступа пользователей

Cli будет использовать поток устройства для проверки подлинности пользователя и создания маркера доступа пользователя. Затем CLI может использовать маркер доступа пользователя для выполнения запросов API от имени пользователя, прошедшего проверку подлинности.

Приложение должно использовать маркер доступа пользователя, если вы хотите приказать действия приложения пользователю. Дополнительные сведения см. в разделе Проверка подлинности с помощью Приложение GitHub от имени пользователя.

Существует два способа создания маркера доступа пользователя для GitHub App: поток веб-приложения и поток устройства. Для создания маркера доступа пользователя следует использовать поток устройства, если приложение не имеет доступа к веб-интерфейсу. Например, средства CLI, простые Raspberry Pis и классические приложения должны использовать поток устройства. Если приложение имеет доступ к веб-интерфейсу, следует использовать поток веб-приложения. Дополнительные сведения см. в разделах Создание маркера доступа пользователя для Приложение GitHub и Создание кнопки "Вход с помощью GitHub" с Приложение GitHub.

Предварительные требования

В этом руководстве предполагается, что вы уже создали GitHub App. Дополнительные сведения о создании приложения см. в разделе Создание приложения GitHub.

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

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

Получение идентификатора клиента

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

  1. Перейдите к настройкам учетной записи.

    • Для приложения GitHub App, принадлежащего учетной записи пользователя, нажмите на фото своего профиля в правом верхнем углу любой страницы и выберите Настройки. Снимок экрана: раскрывающееся меню "ПрофильGitHub в GitHub.com. Параметр с меткой "Параметры" выделен темно-оранжевым цветом.

    • Для приложения GitHub App, принадлежащего организации, нажмите на фото своего профиля в правом верхнем углу любой страницы и выберите Ваши организации. Затем нажмите Настройки справа от организации.

      Снимок экрана: раскрывающееся меню под @octocatизображением профиля. "Ваши организации" выделены темно-оранжевым цветом.

  2. На левой боковой панели щелкните Параметры разработчика.

  3. На левой боковой панели щелкните GitHub Apps. Снимок экрана: страница "Параметры разработчика" в GitHub. Параметр с меткой "Приложения GitHub" выделен темно-оранжевым цветом.

  4. Рядом с GitHub App, с которым вы хотите работать, нажмите кнопку Изменить.

  5. На странице параметров приложения найдите идентификатор клиента для приложения. Вы будете использовать его далее в этом руководстве. Обратите внимание, что идентификатор клиента отличается от идентификатора приложения.

Запись интерфейса командной строки

Эти шаги поведут вас к созданию ИНТЕРФЕЙСА командной строки и использованию потока устройства для получения маркера доступа пользователя. Чтобы перейти к окончательному коду, см. раздел Полный пример кода.

Настройка

  1. Создайте файл Ruby для хранения кода, который создаст маркер доступа пользователя. В этом руководстве файлу app_cli.rbбудет присвоено имя .

  2. В окне терминала в каталоге, в котором app_cli.rb хранится файл, выполните следующую команду, чтобы сделать app_cli.rb исполняемый файл:

    Code
    chmod +x app_cli.rb
  3. Добавьте эту строку в начало, чтобы указать, что для выполнения скрипта app_cli.rb следует использовать интерпретатор Ruby:

    Ruby
    #!/usr/bin/env ruby
  4. Добавьте эти зависимости в начало , следующееapp_cli.rb``#!/usr/bin/env ruby:

    Ruby
    require "net/http"
    require "json"
    require "uri"
    require "fileutils"

    Все они являются частью стандартной библиотеки Ruby, поэтому вам не нужно устанавливать какие-либо драгоценные камни.

  5. Добавьте следующую 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
  6. В нижней части файла добавьте следующую строку, чтобы вызвать функцию точки входа. Этот вызов функции должен оставаться в нижней части файла, так как вы добавите дополнительные функции в этот файл далее в этом руководстве.

    Ruby
    main
  7. При необходимости проверка хода выполнения:

    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

    В терминале из каталога, в котором app_cli.rb хранится, выполните команду ./app_cli.rb help. Вы должны увидеть такой результат:

    `help` is not yet defined
    

    Вы также можете протестировать скрипт без команды или с помощью необработанных команд. Например, ./app_cli.rb create-issue должен выводить следующее:

    Unknown command `create-issue`
    

help Добавление команды

  1. Добавьте следующую help функцию в app_cli.rb. В настоящее время функция выводит строку, help чтобы сообщить пользователям, что этот интерфейс командной строки принимает одну команду help. Вы развернете эту help функцию позже.

    Ruby
    def help
      puts "usage: app_cli <help>"
    end
  2. Обновите функцию 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
  3. При необходимости проверка хода выполнения:

    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

    В терминале из каталога, в котором app_cli.rb хранится, выполните команду ./app_cli.rb help. Вы должны увидеть такой результат:

    usage: app_cli <help>
    

login Добавление команды

Команда login запустит поток устройства, чтобы получить маркер доступа пользователя. Дополнительные сведения см. в разделе Создание маркера доступа пользователя для Приложение GitHub.

  1. В верхней части файла после require операторов добавьте CLIENT_ID GitHub App в качестве константы в app_cli.rb. Дополнительные сведения о поиске идентификатора клиента приложения см. в разделе Получение идентификатора клиента. Замените YOUR_CLIENT_ID идентификатором клиента приложения:

    Ruby
    CLIENT_ID="YOUR_CLIENT_ID"
  2. Добавьте следующую parse_response функцию в app_cli.rb. Эта функция анализирует ответ из REST API GitHub. Если состояние ответа равно 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
  3. Добавьте следующую 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
  4. Добавьте следующую 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
  5. Добавьте следующую 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
  6. Добавьте следующую login функцию.

    Эта функция выполняет следующее:

    1. Вызывает функцию request_device_code и получает verification_uriпараметры , user_code, device_codeи interval из ответа.
    2. Предлагает пользователям ввести из user_code предыдущего шага.
    3. Вызывает для опроса маркера poll_for_token доступа GitHub.
    4. Сообщает пользователю, что проверка подлинности прошла успешно.
    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
  7. Обновите функцию 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
  8. Обновите функцию help , включив команду login :

    Ruby
    def help
      puts "usage: app_cli <login | help>"
    end
  9. При необходимости проверка хода выполнения:

    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
    1. В терминале из каталога, в котором app_cli.rb хранится, выполните команду ./app_cli.rb login. Вы должны увидеть выходные данные, которые выглядят следующим образом. Код будет отличаться каждый раз:

      Please visit: http(s)://HOSTNAME/login/device
      and enter code: CA86-8D94
      
    2. Перейдите в http(s)://HOSTNAME/login/device в браузере и введите код из предыдущего шага, а затем нажмите кнопку Продолжить.

    3. GitHub должна отображать страницу с предложением авторизовать приложение. Нажмите кнопку "Авторизовать".

    4. Теперь в терминале должно быть указано "Успешно выполнена проверка подлинности!".

whoami Добавление команды

Теперь, когда приложение может создать маркер доступа пользователя, можно выполнять запросы API от имени пользователя. whoami Добавьте команду, чтобы получить имя пользователя, прошедшего проверку подлинности.

  1. Добавьте следующую 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
  2. Обновите функцию 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
  3. Обновите функцию 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
  4. Обновите функцию, help включив команду whoami :

    Ruby
    def help
      puts "usage: app_cli <login | whoami | help>"
    end
  5. Проверьте код на соответствие полному примеру кода в следующем разделе. Вы можете протестировать код, выполнив действия, описанные в разделе "Тестирование" под полным примером кода.

Полный пример кода

Это полный пример кода, описанный в предыдущем разделе. Замените YOUR_CLIENT_ID идентификатором клиента приложения.

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 | 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.

  1. В терминале в каталоге, где app_cli.rb хранится , выполните команду ./app_cli.rb help. Должны отобразиться выходные данные, которые выглядят следующим образом.

    usage: app_cli <login | whoami | help>
    
  2. В терминале в каталоге, где app_cli.rb хранится , выполните команду ./app_cli.rb login. Должны отобразиться выходные данные, которые выглядят следующим образом. Код будет отличаться каждый раз:

    Please visit: http(s)://HOSTNAME/login/device
    and enter code: CA86-8D94
    
  3. Перейдите к http(s)://HOSTNAME/login/device в браузере и введите код из предыдущего шага, а затем нажмите кнопку Продолжить.

  4. В GitHub должна отображаться страница с запросом на авторизацию приложения. Нажмите кнопку "Авторизовать".

  5. В окне терминала должно появиться сообщение "Проверка подлинности успешно выполнена!".

  6. В терминале в каталоге, где app_cli.rb хранится , выполните команду ./app_cli.rb whoami. Вы увидите выходные данные, которые выглядят следующим образом, где octocat — ваше имя пользователя.

    You are octocat
    
  7. .token Откройте файл в редакторе и измените маркер. Теперь маркер недопустим.

  8. В терминале в каталоге, где app_cli.rb хранится , выполните команду ./app_cli.rb whoami. Вы должны получить следующий результат:

    You are not authorized. Run the `login` command.
    
  9. Удалите файл .token.

  10. В терминале в каталоге, где app_cli.rb хранится , выполните команду ./app_cli.rb whoami. Вы должны получить следующий результат:

    You are not authorized. Run the `login` command.
    

Дальнейшие действия

Настройка кода в соответствии с потребностями приложения

В этом руководстве показано, как написать интерфейс командной строки, который использует поток устройства для создания маркера доступа пользователя. Вы можете развернуть этот интерфейс командной строки, чтобы принимать дополнительные команды. Например, можно добавить create-issue команду, которая открывает проблему. Не забудьте обновить разрешения приложения, если приложению требуются дополнительные разрешения для запросов API, которые вы хотите выполнить. Дополнительные сведения см. в разделе Выбор разрешений для Приложение GitHub.

Безопасное хранение маркеров

В этом руководстве создается маркер доступа пользователя и он сохраняется в локальном файле. Никогда не следует фиксировать этот файл или делать его общедоступным.

В зависимости от устройства вы можете выбрать другой способ хранения маркера. Следует проверка рекомендации по хранению маркеров на устройстве.

Дополнительные сведения см. в разделе Рекомендации по созданию Приложение GitHub.