Skip to main content

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

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

Введение

В этом руководстве показано, как создать интерфейс командной строки (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.

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

Для создания маркера доступа пользователя с помощью потока устройства вам потребуется идентификатор клиента приложения.

  1. В правом верхнем углу любой страницы на GitHubщелкните фото профиля.
  2. Перейдите к настройкам учетной записи.
    • Для приложения, принадлежащих личная учетная запись, нажмите кнопку "Параметры".
    • Для приложения, принадлежащих организации:
      1. Щелкните Your organizations (Ваши организации).
      2. Справа от организации нажмите кнопку "Параметры".
  3. На левой боковой панели щелкните Параметры разработчика.
  4. На левой боковой панели щелкните GitHub Apps.
  5. Рядом с GitHub App, с которыми вы хотите работать, нажмите кнопку "Изменить".
  6. На странице параметров приложения найдите идентификатор клиента для приложения. Вы будете использовать его далее в этом руководстве. Обратите внимание, что идентификатор клиента отличается от идентификатора приложения.

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

Эти шаги поведут вас за счет создания интерфейса командной строки и использования потока устройств для получения маркера доступа пользователей. Сведения о переходе к окончательному коду см. в разделе "Полный пример кода".

Настройка

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

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

    Text
    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 что этот CLI принимает одну команду , "справка". Вы развернете эту 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 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
    
  3. Добавьте следующую request_device_code функцию app_cli.rbв . Эта функция отправляет POST запрос https://github.com/login/device/code и возвращает ответ.

    Ruby
    def request_device_code
      uri = URI("https://github.com/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 запрос https://github.com/login/oauth/access_token и возвращает ответ.

    Ruby
    def request_token(device_code)
      uri = URI("https://github.com/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в . Эта функция опрашивает https://github.com/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 Вызывает функцию и получает device_code``verification_uri``user_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("https://github.com/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("https://github.com/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: https://github.com/login/device
      and enter code: CA86-8D94
      
    2. Перейдите к https://github.com/login/device в браузере и введите код на предыдущем шаге, а затем нажмите кнопку "Продолжить".

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

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

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

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

  1. Добавьте следующую whoami функцию app_cli.rbв . Эта функция получает сведения о пользователе с конечной /user точкой REST API. Он выводит имя пользователя, соответствующее маркеру доступа пользователя. .token Если файл не найден, он предложит пользователю запустить функциюlogin.

    Ruby
    def whoami
      uri = URI("https://api.github.com/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("https://github.com/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("https://github.com/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("https://api.github.com/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: https://github.com/login/device
    and enter code: CA86-8D94
    
  3. Перейдите к https://github.com/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.

Применение рекомендаций

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