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}
endWICHTIG:
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