20.02.2020 09:11 - bearbeitet 20.02.2020 12:58
Dank der REST-Schnittstelle habe ich eine saubere Lösung zum Postbox-Sammeldownload!
Der untenstehende Code (vorgeschlagener Name: repobosado = REstPOstBOxSAmmelDOwnload) mag als Anregung für weitere Experimente dienen.
Kurzer Überblick:
Benötigt wird ein aktuelles ruby und das rest-client gem (falls es auf dem eigenen PC fehlt: gem install rest-client).
Die Datei enthält den REST-Clienten und ein kurzes Beispielprogramm (ab Zeile 133)
Die Klasse ComdirectClient kapselt die Kommunikation mit der Bank, die Schnittstellen sind:
login_and_get_phototan_challenge(client_id:, client_secret:, access_number:, pin: )
Führt Login aus und liefert die Binärdaten für ein PNG-Bild (die Photo-TAN-Challenge) zurück. Parameter: OAUTH Client und Secret, Online-Zugangsnummer, Online-Passwort
finalize_login(tan: )
Beendet den login-Vorgang. Parameter: Die Photo-TAN aus dem challenge-Bild
get_postbox_document_list
Liefert eine Liste der Dokumente in der Postbox
get_document_pdf_from_id(id: )
Holt das pdf-Dokument mit der gewünschten ID aus der Postbox
BEISPIEL
Client initialisieren, login-Vorgang starten und die PNG-Bilddaten in die Datei "tan_challenge.png" schreiben:
client = ComdirectClient.new(ClientRequestId.new) image = client.login_and_get_phototan_challenge(client_id: OAUTH_client_id, client_secret: OAUTH_client_secret, access_number: Zugangsnummer, pin: Pin) File.open("tan_challenge.png","w") {|f| f.write image}
PhotoTAN einlesen und TAN eingeben:
puts "Bild geschrieben, bitte TAN eingeben" tan = gets.chomp client.finalize_login(tan: tan)
Liste der Dokumente in der Postbox holen (und Anzahl ausgeben):
postbox_documents = client.get_postbox_document_list puts "Anzahl Dokumente in Postbox: #{postbox_documents.length}"
Alle PDF-Dokumente downloaden, dabei den Dateinamen anpassen (Leerzeichen entfernen, Dokument-Id zum Namen hinzufügen (sonst sind die Dateinamen nicht eindeutig)
postbox_documents.each do |doc| next unless doc["mimeType"] == "application/pdf" filename = doc["name"] + doc["documentId"][-4..-1] + ".pdf" # Leerzeichen und Schrägstrich entfernen filename.tr!(' /','_') puts "hole #{filename}" pdf = client.get_document_pdf_from_id(id: doc["documentId"]) File.open(filename,"w") {|f| f.write pdf} end
WICHTIG:
Der Code:
require 'rest-client' require 'json' require 'securerandom' require 'base64' # Erzeugt ClientRequests mit monoton steigender requestId class ClientRequestId def initialize @session = SecureRandom.hex(12) @cnt = 0 end def value @cnt += 1 '{"clientRequestId":{"sessionId":"' + @session +'","requestId":"' + ("%09d" % @cnt) +'"}}' end end # REST-Client class ComdirectClient AUTH_URL = "https://api.comdirect.de/oauth/token" URL = "https://api.comdirect.de/api/" def initialize(clientrequest) @client_request = clientrequest end # 'getter' für mehrfach benutzte Daten def authorisation_header {'Authorization' => "Bearer #{@access_token}", 'x-http-request-info' => @client_request.value } end def json_header authorisation_header.merge({'accept' => 'application/json'}) end def session_object '{"identifier" : "' + @sessionUUID + '", "sessionTanActive": true, "activated2FA": true}' end def oauth_flow(extra_payload: {}) payload = {client_id: @client_id, client_secret: @client_secret}.merge(extra_payload) result = RestClient.post AUTH_URL, payload raise "fehler bei oauth-login " unless result.code == 200 @access_token = JSON.parse(result.body)["access_token"] end def get_session_object result = begin RestClient.get URL + 'session/clients/user/v1/sessions', json_header rescue RestClient::ExceptionWithResponse => e puts e.response end @sessionUUID = JSON.parse(result.body)[0]["identifier"] end def get_phototan_challenge_image result = begin RestClient.post URL + "session/clients/user/v1/sessions/#{@sessionUUID}/validate", session_object, json_header.merge({'Content-Type' => 'application/json'}) rescue RestClient::ExceptionWithResponse => e puts "prepare_tan_challenge: rescue" puts e.response end raise "prepare_tan_challenge: ungülter response code " unless result.code == 201 @authentication_info = JSON.parse(result.headers[:x_once_authentication_info]) raise "Nur P_TAN unterstützt" unless @authentication_info["typ"] == "P_TAN" Base64.decode64(@authentication_info["challenge"]) end def send_tan(tan: ) begin RestClient.patch URL + "session/clients/user/v1/sessions/#{@sessionUUID}", session_object, json_header.merge( {'Content-Type' => 'application/json', 'x-once-authentication-info' => "{\"id\": #{@authentication_info["id"]}}", 'x-once-authentication' => tan}) rescue RestClient::ExceptionWithResponse => e puts "prepare_tan_challenge: rescue" puts e.response end end def login_and_get_phototan_challenge(client_id:, client_secret:, access_number:, pin:) @client_id = client_id @client_secret = client_secret oauth_flow(extra_payload: {grant_type: 'password', username: access_number, password: pin}) get_session_object get_phototan_challenge_image end def finalize_login(tan: ) send_tan(tan: tan) oauth_flow(extra_payload: {grant_type: 'cd_secondary', token: @access_token}) end def get_postbox_document_list result = begin RestClient.get URL + "messages/clients/user/v2/documents", json_header rescue RestClient::ExceptionWithResponse => e puts "prepare_tan_challenge: rescue" puts e.response end raise "get_postbox_document_list: ungueltiger code" unless result.code == 200 JSON.parse(result.body)["values"] end def get_document_pdf_from_id(id:) result = RestClient.get URL + "messages/v2/documents/" + id, authorisation_header.merge({'accept' => 'application/pdf'}) raise "get_document_pdf_from_id: ungueltiger code" unless result.code == 200 result.body end end # MAIN client = ComdirectClient.new(ClientRequestId.new) image = client.login_and_get_phototan_challenge(client_id: OAUTH_client_id, client_secret: OAUTH_client_secret, access_number: Zugangsnummer, pin: Pin) File.open("tan_challenge.png","w") {|f| f.write image} puts "Bild geschrieben, bitte TAN eingeben" tan = gets.chomp client.finalize_login(tan: tan) postbox_documents = client.get_postbox_document_list puts "Anzahl Dokumente in Postbox: #{postbox_documents.length}" postbox_documents.each do |doc| next unless doc["mimeType"] == "application/pdf" filename = doc["name"] + doc["documentId"][-4..-1] + ".pdf" # Leerzeichen und Schrägstrich entfernen filename.tr!(' /','_') puts "hole #{filename}" pdf = client.get_document_pdf_from_id(id: doc["documentId"]) File.open(filename,"w") {|f| f.write pdf} end