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:
- : Gibt die Verwendungsanweisungen aus.
- : Generiert ein Benutzerzugriffstoken, das die App verwenden kann, um API-Anforderungen im Namen von Benutzer*innen zu senden.
- : 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 AUTOTITLE.
Es gibt zwei Möglichkeiten, ein Benutzerzugriffstoken für eine GitHub App zu generieren: Webanwendungsfluss und Gerätefluss. Wenn deine App kopflos ist oder keinen Zugriff auf eine Webschnittstelle 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 AUTOTITLE und AUTOTITLE.
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 AUTOTITLE.
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 AUTOTITLE.
Dieses Tutorial setzt Grundkenntnisse in Ruby voraus. Weitere Informationen findest du unter Ruby.
Client-ID abrufen
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 in der linken Randleiste auf Developer settings.
- Klicke auf der linken Randleiste auf GitHub Apps .
- Klicke neben der GitHub App, mit der du arbeiten möchtest, auf Bearbeiten.
- 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.
Befehle in der CLI ausführen
Diese Schritte führen dich durch das Erstellen einer CLI und die Verwendung des Geräteflusses, um ein Benutzerzugriffstoken zu erhalten. Um direkt zum endgültigen Code zu springen, sieh dir das vollständige Codebeispiel an.
Einrichtung
-
Erstelle eine Ruby-Datei, die den Code enthält, mit dem ein Benutzerzugriffstoken generiert wird. In diesem Tutorial wird die Datei benannt.
-
Führe in deinem Terminal in dem Verzeichnis, in dem gespeichert ist, den folgenden Befehl aus, um ausführbar zu machen:
Text chmod +x app_cli.rb
chmod +x app_cli.rb -
Füge oben in 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 die folgenden Abhängigkeiten am Anfang ein, nach :
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 -Funktion hinzu, die als Einstiegspunkt dient. Die Funktion enthält eine -Anweisung zum Ausführen unterschiedlicher Aktionen, abhängig vom angegebenen Befehl. Sie werden diese Aussage später erweitern.
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 enddef 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:
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 mainFühre im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Diese Ausgabe wird angezeigt:
`help` is not yet definedDu kannst dein Skript auch ohne einen Befehl oder mit einem nicht behandelten Befehl testen. sollte beispielsweise Folgendes ausgeben:
Unknown command `create-issue`
Hinzufügen eines -Befehls
-
Füge die folgende -Funktion hinzu. Derzeit gibt die -Funktion eine Zeile aus, um Benutzer*innen mitzuteilen, dass diese CLI einen Befehl („help“) akzeptiert. Du erweiterst diese -Funktion später.
Ruby def help puts "usage: app_cli <help>" end
def help puts "usage: app_cli <help>" end -
Aktualisiere die Funktion, um die Funktion aufzurufen, wenn 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 enddef 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:
sieht jetzt wie folgt aus. Die Reihenfolge der Funktionen spielt keine Rolle, solange sich der -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 mainFühre im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Diese Ausgabe wird angezeigt:
usage: app_cli <help>
Hinzufügen eines -Befehls
Der Befehl führt den Geräteauthentifizierungsprozess aus, um ein Benutzerzugriffstoken abzurufen. Weitere Informationen finden Sie unter AUTOTITLE.
-
Füge im oberen Bereich deiner Datei nach den Anweisungen die Client-ID deiner GitHub App als Konstante hinzu. Weitere Informationen zum Ermitteln der Client-ID deiner App findest du unter Abrufen der Client-ID. Ersetze durch die Client-ID deiner App:
Ruby CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_ID="YOUR_CLIENT_ID" -
Füge die folgende -Funktion hinzu. Diese Funktion analysiert eine Antwort der GitHub-REST-API. Wenn der Antwortstatus oder lautet, gibt die Funktion den analysierten Antworttext zurück. Andernfalls gibt die Funktion die Antwort und den Body 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 enddef 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 die folgende -Funktion hinzu. Diese Funktion sendet eine -Anforderung an und gibt die Antwort zurück.
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) enddef 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 -
Füge die folgende -Funktion hinzu. Diese Funktion sendet eine -Anforderung an und gibt die Antwort zurück.
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) enddef 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 -
Füge die folgende -Funktion hinzu. Diese Funktion ruft in dem angegebenen Intervall ab, bis GitHub mit einem bestimmten Parameter anstelle eines anderen Parameters 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 enddef 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 -Funktion hinzu.
Diese Funktion bewirkt Folgendes:
- Ruft die Funktion auf und erhält die Parameter , , , und aus der Antwort.
- Aufforderung der Benutzer*innen, die Informationen aus dem vorherigen Schritt einzugeben.
- Fordert GitHub auf, nach einem Zugriffstoken zu fragen.
- 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!" enddef 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 -
Aktualisiere die Funktion für das Aufrufen der Funktion, wenn der Befehl gegeben 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 enddef 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 -Funktion so, dass sie den -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:
sieht nun in etwa wie folgt aus, wobei die Client-ID deiner App ist. Die Reihenfolge der Funktionen spielt keine Rolle, solange sich der -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("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-
Führe im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Sie sollten eine Ausgabe sehen, die so aussieht. Der Code unterscheidet sich jedes Mal:
Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94 -
Navigiere in deinem Browser zu http(s)://HOSTNAME/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 -Befehls
Nachdem deine App nun ein Benutzerzugriffstoken generieren kann, kannst du API-Anforderungen im Namen von Benutzer*innen ausführen. Füge einen -Befehl hinzu, um den Benutzernamen des authentifizierten Benutzers oder der authentifizierten Benutzerin abzurufen.
-
Füge die folgende -Funktion hinzu. Diese Funktion ruft über den REST-API-Endpunkt Informationen zu den Benutzerinnen ab. Sie gibt den Benutzernamen aus, der dem Benutzerzugriffstoken entspricht. Wenn die Datei nicht gefunden wurde, werden die Benutzerinnen zum Ausführen der -Funktion aufgefordert.
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"]}" enddef 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 -
Aktualisiere die -Funktion für den Fall, dass das Token abgelaufen ist oder widerrufen wurde. Wenn Sie nun eine Antwort erhalten, fordert die CLI die Benutzer*innen auf, den 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 enddef 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 Funktion für das Aufrufen der Funktion, wenn der Befehl gegeben wird:
Ruby def main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end enddef main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end end -
Aktualisiere die -Funktion so, dass sie den -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 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("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
Testen
In diesem Tutorial wird davon ausgegangen, dass dein App-Code in einer Datei mit dem Namen gespeichert ist.
-
Führe im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Sie sollten eine Ausgabe sehen, die so aussieht.
usage: app_cli <login | whoami | help> -
Führe im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Sie sollten eine Ausgabe sehen, die so aussieht. Der Code unterscheidet sich jedes Mal:
Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94 -
Navigiere in deinem Browser zu http(s)://HOSTNAME/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 gespeichert ist, aus. Sie sollten eine Ausgabe sehen, die in etwa so aussieht, wobei
Ihr Benutzername ist. You are octocat -
Öffne die Datei in deinem Editor, und ändere das Token. Das Token ist jetzt ungültig.
-
Führe im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Du solltest eine Ausgabe sehen, die so aussieht:
You are not authorized. Run the `login` command. -
Lösche die Datei .
-
Führe im Terminal in dem Verzeichnis, in dem gespeichert ist, aus. Eine Ausgabe wie diese 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 Befehl hinzufügen, mit dem ein Issue geöffnet 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 AUTOTITLE.
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 AUTOTITLE.
Bewährte Methode befolgen
Du solltest Best Practices für deine GitHub App befolgen. Weitere Informationen finden Sie unter AUTOTITLE.