Einführung
In diesem Tutorial wird veranschaulicht, wie du eine Befehlszeilenschnittstelle (Command Line Interface, CLI) erstellst, die von einer GitHub App unterstützt wird, und wie du mithilfe des Geräteflusses ein Benutzerzugriffstoken für die App generierst.
Die CLI verfügt über drei Befehle:
help
: Gibt die Verwendungsanweisungen aus.login
: Generiert ein Benutzerzugriffstoken, das die App verwenden kann, um API-Anforderungen im Namen von Benutzer*innen zu senden.whoami
: Gibt Informationen zum angemeldeten Benutzer oder zur angemeldeten Benutzerin zurück.
In diesem Tutorial wird Ruby verwendet, aber du kannst mit einer beliebigen Programmiersprache eine CLI schreiben und den Gerätefluss verwenden, um ein Benutzerzugriffstoken zu generieren.
Informationen zu Gerätefluss und Benutzerzugriffstoken
Die CLI verwendet den Gerätefluss, um Benutzerinnen zu authentifizieren und ein Benutzerzugriffstoken zu generieren. Anschließend kann die CLI das Benutzerzugriffstoken verwenden, um API-Anforderungen im Namen der authentifizierten Benutzerinnen zu senden.
Deine App muss ein Benutzerzugriffstoken verwenden, wenn du die Aktionen der App einemr Benutzerin zuordnen möchtest. Weitere Informationen finden Sie unter Authentifizieren mit einer GitHub-App im Namen von Benutzer*innen.
Es gibt zwei Möglichkeiten, ein Benutzerzugriffstoken für eine GitHub App zu generieren: Webanwendungsfluss und Gerätefluss. Wenn deine App monitorlos ist oder keinen Zugriff auf eine Weboberfläche hat, solltest du den Gerätefluss verwenden, um ein Benutzerzugriffstoken zu generieren. Beispielsweise sollten CLI-Tools, einfache Raspberry Pi-Geräte und Desktopanwendungen den Gerätefluss verwenden. Wenn deine App Zugriff auf eine Weboberfläche hat, solltest du stattdessen den Webanwendungsfluss verwenden. Weitere Informationen findest du unter Generieren eines Benutzerzugriffstokens für eine GitHub-App und Erstellen der Schaltfläche „Mit GitHub anmelden“ mit einer GitHub-App.
Voraussetzungen
In diesem Tutorial wird davon ausgegangen, dass du bereits eine GitHub App registriert hast. Weitere Informationen zum Registrieren einer GitHub App findest du unter Registrieren einer GitHub-App.
Bevor du dieses Tutorial ausführst, musst du den Gerätefluss für deine App aktivieren. Weitere Informationen zum Aktivieren des Geräteflusses für deine App findest du unter Ändern einer GitHub-App-Registrierung.
Dieses Tutorial setzt Grundkenntnisse in Ruby voraus. Weitere Informationen findest du unter Ruby.
Abrufen der Client-ID
Du benötigst die Client-ID deiner App, um ein Benutzerzugriffstoken über den Gerätefluss zu generieren.
- Klicke auf GitHub in der oberen rechten Ecke einer beliebigen Seite auf dein Profilfoto.
- Navigieren Sie zu den Einstellungen für Ihr Konto.
- Klicken Sie bei einer App, die zu einem persönlichen Konto gehört, auf Einstellungen.
- Für eine App im Besitz einer Organisation:
- Klicke Sie auf Ihre Organisationen.
- Klicken Sie rechts neben der Organisation auf Einstellungen.
- Klicke auf der linken Seitenleiste auf Entwicklereinstellungen.
- Klicke auf der linken Randleiste auf GitHub Apps .
- Wähle neben der GitHub App, die du verwenden möchtest, Bearbeiten aus.
- Suche auf der Einstellungsseite der App nach der Client-ID für deine App. Du benötigst sie später in diesem Tutorial. Beachte, dass sich die Client-ID von der App-ID unterscheidet.
Schreiben der CLI
Diese Schritte führen dich durch das Erstellen einer CLI und die Verwendung des Geräteflusses, um ein Benutzerzugriffstoken zu erhalten. Den endgültigen Code findest du im vollständigen Codebeispiel.
Setup
-
Erstelle eine Ruby-Datei, die den Code enthält, mit dem ein Benutzerzugriffstoken generiert wird. In diesem Tutorial wird die Datei mit
app_cli.rb
benannt. -
Führe in deinem Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist, den folgenden Befehl aus, umapp_cli.rb
ausführbar zu machen:Text chmod +x app_cli.rb
chmod +x app_cli.rb
-
Füge oben in
app_cli.rb
die folgende Zeile hinzu, um anzugeben, dass der Ruby-Interpreter zum Ausführen des Skripts verwendet werden soll:Ruby #!/usr/bin/env ruby
#!/usr/bin/env ruby
-
Füge oben in
app_cli.rb
nach#!/usr/bin/env ruby
die folgenden Abhängigkeiten hinzu:Ruby require "net/http" require "json" require "uri" require "fileutils"
require "net/http" require "json" require "uri" require "fileutils"
Diese sind alle Teil der Ruby-Standardbibliothek, sodass du keine Gems installieren musst.
-
Füge die folgende
main
-Funktion hinzu, die als Einstiegspunkt dient. Die Funktion enthält einecase
-Anweisung zum Ausführen unterschiedlicher Aktionen, abhängig vom angegebenen Befehl. Du erweiterst diesecase
-Anweisung später.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
-
Füge unten in der Datei die folgende Zeile hinzu, um die Einstiegspunktfunktion aufzurufen. Dieser Funktionsaufruf sollte unten in der Datei verbleiben, wenn du dieser Datei später im Tutorial weitere Funktionen hinzufügst.
Ruby main
main
-
Überprüfe optional den Fortschritt:
app_cli.rb
sieht jetzt so aus: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
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb help
aus. Die folgende Ausgabe sollte angezeigt werden:`help` is not yet defined
Du kannst dein Skript auch ohne einen Befehl oder mit einem nicht behandelten Befehl testen.
./app_cli.rb create-issue
sollte beispielsweise Folgendes ausgeben:Unknown command `create-issue`
Hinzufügen eines help
-Befehls
-
Füge
app_cli.rb
die folgendehelp
-Funktion hinzu. Derzeit gibt diehelp
-Funktion eine Zeile aus, um Benutzer*innen mitzuteilen, dass diese CLI einen Befehl („help“) akzeptiert. Du erweiterst diesehelp
-Funktion später.Ruby def help puts "usage: app_cli <help>" end
def help puts "usage: app_cli <help>" end
-
Aktualisiere die
main
-Funktion, um diehelp
-Funktion aufzurufen, wenn derhelp
-Befehl ausgegeben wird: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
-
Überprüfe optional den Fortschritt:
app_cli.rb
sieht jetzt wie folgt aus. Die Reihenfolge der Funktionen spielt keine Rolle, solange sich dermain
-Funktionsaufruf am Ende der Datei befindet.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
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb help
aus. Die folgende Ausgabe sollte angezeigt werden:usage: app_cli <help>
Hinzufügen eines login
-Befehls
Der login
-Befehl führt den Gerätefluss aus, um ein Benutzerzugriffstoken abzurufen. Weitere Informationen finden Sie unter Generieren eines Benutzerzugriffstokens für eine GitHub-App.
-
Füge im oberen Bereich der Datei nach den
require
-Anweisungen die Client-ID (CLIENT_ID
) deiner GitHub App als Konstante inapp_cli.rb
hinzu. Weitere Informationen zum Ermitteln der Client-ID deiner App findest du unter Abrufen der Client-ID. ErsetzeYOUR_CLIENT_ID
durch die Client-ID deiner App:Ruby CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_ID="YOUR_CLIENT_ID"
-
Füge
app_cli.rb
die folgendeparse_response
-Funktion hinzu. Diese Funktion analysiert eine Antwort der GitHub-REST-API. Wenn der Antwortstatus200 OK
oder201 Created
lautet, gibt die Funktion den analysierten Antworttext zurück. Andernfalls gibt die Funktion die Antwort und den Text aus und beendet das Programm.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
-
Füge
app_cli.rb
die folgenderequest_device_code
-Funktion hinzu. Diese Funktion sendet einePOST
-Anforderung anhttps://github.com/login/device/code
und gibt die Antwort zurück.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
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
-
Füge
app_cli.rb
die folgenderequest_token
-Funktion hinzu. Diese Funktion sendet einePOST
-Anforderung anhttps://github.com/login/oauth/access_token
und gibt die Antwort zurück.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
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
-
Füge
app_cli.rb
die folgendepoll_for_token
-Funktion hinzu. Diese Funktion rufthttps://github.com/login/oauth/access_token
im angegebenen Intervall ab, bis GitHub mit dem Parameteraccess_token
anstelle des Parameterserror
antwortet. Anschließend schreibt sie das Benutzerzugriffstoken in eine Datei und schränkt die Berechtigungen für die Datei ein.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
-
Füge die folgende
login
-Funktion hinzu.Diese Funktion bewirkt Folgendes:
- Aufrufen der Funktion
request_device_code
und Abrufen der Parameterverification_uri
,user_code
,device_code
undinterval
aus der Antwort - Auffordern der Benutzer*innen,
user_code
aus dem vorherigen Schritt einzugeben - Aufrufen von
poll_for_token
, um ein Zugriffstoken von GitHub abzufragen - Informieren der Benutzer*innen, dass die Authentifizierung erfolgreich war
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
- Aufrufen der Funktion
-
Aktualisiere die
main
-Funktion, um dielogin
-Funktion aufzurufen, wenn derlogin
-Befehl ausgegeben wird: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
-
Aktualisiere die
help
-Funktion so, dass sie denlogin
-Befehl enthält:Ruby def help puts "usage: app_cli <login | help>" end
def help puts "usage: app_cli <login | help>" end
-
Überprüfe optional den Fortschritt:
app_cli.rb
sieht nun in etwa wie folgt aus, wobeiYOUR_CLIENT_ID
die Client-ID deiner App ist. Die Reihenfolge der Funktionen spielt keine Rolle, solange sich dermain
-Funktionsaufruf am Ende der Datei befindet.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
#!/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
-
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb login
aus. Eine ähnliche Ausgabe wie die folgende sollte angezeigt werden. Der Code unterscheidet sich jedes Mal:Please visit: https://github.com/login/device and enter code: CA86-8D94
-
Navigiere in deinem Browser zu https://github.com/login/device, gib den Code aus dem vorherigen Schritt ein, und klicke dann auf Weiter.
-
Auf GitHub sollte eine Seite mit der Aufforderung zum Autorisieren deiner App angezeigt werden. Klicke auf die Schaltfläche „Autorisieren“.
-
In deinem Terminal sollte jetzt „Erfolgreich authentifiziert!“ angezeigt werden.
-
Hinzufügen eines whoami
-Befehls
Nachdem deine App nun ein Benutzerzugriffstoken generieren kann, kannst du API-Anforderungen im Namen von Benutzer*innen ausführen. Füge einen whoami
-Befehl hinzu, um den Benutzernamen des authentifizierten Benutzers oder der authentifizierten Benutzerin abzurufen.
-
Füge
app_cli.rb
die folgendewhoami
-Funktion hinzu. Diese Funktion ruft über den REST-API-Endpunkt/user
Informationen zu den Benutzerinnen ab. Sie gibt den Benutzernamen aus, der dem Benutzerzugriffstoken entspricht. Wenn die Datei.token
nicht gefunden wurde, werden die Benutzerinnen zum Ausführen derlogin
-Funktion aufgefordert.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
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
-
Aktualisiere die
parse_response
-Funktion für den Fall, dass das Token abgelaufen ist oder widerrufen wurde. Wenn du nun eine Antwort vom Typ401 Unauthorized
erhältst, fordert die CLI Benutzer*innen auf, denlogin
-Befehl auszuführen.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
-
Aktualisiere die
main
-Funktion, um diewhoami
-Funktion aufzurufen, wenn derwhoami
-Befehl ausgegeben wird: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
-
Aktualisiere die
help
-Funktion so, dass sie denwhoami
-Befehl enthält:Ruby def help puts "usage: app_cli <login | whoami | help>" end
def help puts "usage: app_cli <login | whoami | help>" end
-
Überprüfe deinen Code anhand des vollständigen Codebeispiels im nächsten Abschnitt. Du kannst deinen Code testen, indem du die Schritte im Abschnitt Testen unterhalb des vollständigen Codebeispiels ausführst.
Vollständiges Codebeispiel
Dies ist das vollständige Codebeispiel, das im vorherigen Abschnitt beschrieben wurde. Ersetze YOUR_CLIENT_ID
durch die Client-ID deiner App.
#!/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
#!/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
Testen
In diesem Tutorial wird davon ausgegangen, dass dein App-Code in einer Datei mit dem Namen app_cli.rb
gespeichert ist.
-
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb help
aus. Eine ähnliche Ausgabe wie die folgende sollte angezeigt werden.usage: app_cli <login | whoami | help>
-
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb login
aus. Eine ähnliche Ausgabe wie die folgende sollte angezeigt werden. Der Code unterscheidet sich jedes Mal:Please visit: https://github.com/login/device and enter code: CA86-8D94
-
Navigiere in deinem Browser zu https://github.com/login/device, gib den Code aus dem vorherigen Schritt ein, und klicke dann auf Weiter.
-
Auf GitHub sollte eine Seite mit der Aufforderung zum Autorisieren deiner App angezeigt werden. Klicke auf die Schaltfläche „Autorisieren“.
-
In deinem Terminal sollte jetzt „Erfolgreich authentifiziert!“ angezeigt werden.
-
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb whoami
aus. Ungefähr folgende Ausgabe sollte angezeigt werden.octocat
ist dabei dein Benutzername:You are octocat
-
Öffne die Datei
.token
in deinem Editor, und ändere das Token. Das Token ist jetzt ungültig. -
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb whoami
aus. Eine ähnliche Ausgabe wie die folgende sollte angezeigt werden:You are not authorized. Run the `login` command.
-
Lösche die Datei
.token
. -
Führe im Terminal in dem Verzeichnis, in dem
app_cli.rb
gespeichert ist,./app_cli.rb whoami
aus. Eine ähnliche Ausgabe wie die folgende sollte angezeigt werden:You are not authorized. Run the `login` command.
Nächste Schritte
Anpassen des Codes an die Anforderungen deiner App
In diesem Tutorial wurde gezeigt, wie du eine CLI schreibst, die den Gerätefluss verwendet, um ein Benutzerzugriffstoken zu generieren. Du kannst diese CLI erweitern, um zusätzliche Befehle zu akzeptieren. Du kannst beispielsweise einen create-issue
-Befehl hinzufügen, mit dem ein Issue erstellt wird. Denke daran, die Berechtigungen deiner App zu aktualisieren, wenn deine App zusätzliche Berechtigungen für die API-Anforderungen benötigt, die du erstellen möchtest. Weitere Informationen finden Sie unter Auswählen von Berechtigungen für eine GitHub-App.
Sicheres Speichern von Token
In diesem Tutorial wird ein Benutzerzugriffstoken generiert und in einer lokalen Datei gespeichert. Du solltest diese Datei niemals committen und das Token niemals veröffentlichen.
Abhängig von deinem Gerät kannst du eine andere Methoden zum Speichern des Tokens auswählen. Überprüfe die bewährten Methoden zum Speichern von Token auf deinem Gerät.
Weitere Informationen finden Sie unter Best Practices beim Erstellen einer GitHub-App.
Bewährte Methode befolgen
Du solltest Best Practices für deine GitHub App befolgen. Weitere Informationen finden Sie unter Best Practices beim Erstellen einer GitHub-App.