<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>Thema "gelöst: Postbox-Sammeldownload" in Website &amp; Apps</title>
    <link>https://community.comdirect.de/t5/website-apps/gel%C3%B6st-postbox-sammeldownload/m-p/108340#M10244</link>
    <description>&lt;P&gt;Dank der REST-Schnittstelle habe ich eine saubere Lösung zum Postbox-Sammeldownload!&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Der untenstehende Code (vorgeschlagener Name: repobosado = &lt;STRONG&gt;RE&lt;/STRONG&gt;st&lt;STRONG&gt;PO&lt;/STRONG&gt;st&lt;STRONG&gt;BO&lt;/STRONG&gt;x&lt;STRONG&gt;SA&lt;/STRONG&gt;mmel&lt;STRONG&gt;DO&lt;/STRONG&gt;wnload) mag als Anregung für weitere Experimente dienen.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Kurzer Überblick:&lt;/P&gt;&lt;P&gt;Benötigt wird ein aktuelles ruby und das rest-client gem (falls es auf dem eigenen PC fehlt: &lt;EM&gt;gem install rest-client&lt;/EM&gt;).&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Die Datei enthält den REST-Clienten und ein kurzes Beispielprogramm (ab Zeile 133)&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Die Klasse &lt;EM&gt;ComdirectClient&lt;/EM&gt; kapselt die Kommunikation mit der Bank,&amp;nbsp; die Schnittstellen sind:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;login_and_get_phototan_challenge(client_id:, client_secret:, access_number:, pin: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;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&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;finalize_login(tan: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Beendet den login-Vorgang. Parameter: Die Photo-TAN aus dem challenge-Bild&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;get_postbox_document_list&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Liefert eine Liste der Dokumente in der Postbox&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;get_document_pdf_from_id(id: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Holt das pdf-Dokument mit der gewünschten ID aus der Postbox&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;BEISPIEL&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Client initialisieren, login-Vorgang starten und die PNG-Bilddaten in die Datei "tan_challenge.png" schreiben:&lt;/P&gt;&lt;PRE&gt;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}&lt;/PRE&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;PhotoTAN einlesen und TAN eingeben:&lt;/P&gt;&lt;PRE&gt;puts "Bild geschrieben, bitte TAN eingeben"
tan = gets.chomp
client.finalize_login(tan: tan)&lt;/PRE&gt;&lt;P&gt;Liste der Dokumente in der Postbox holen (und Anzahl ausgeben):&lt;/P&gt;&lt;PRE&gt;postbox_documents = client.get_postbox_document_list
puts "Anzahl Dokumente in Postbox: #{postbox_documents.length}"&lt;/PRE&gt;&lt;P&gt;Alle PDF-Dokumente downloaden, dabei den Dateinamen anpassen (Leerzeichen entfernen, Dokument-Id zum Namen hinzufügen (sonst sind die Dateinamen nicht eindeutig)&lt;/P&gt;&lt;PRE&gt;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&lt;/PRE&gt;&lt;P&gt;&lt;STRONG&gt;WICHTIG:&lt;/STRONG&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;Momentan werden nur maximal 20 Dokumente heruntergeladen. ursache unbekannt.&lt;/LI&gt;&lt;LI&gt;Bei TAN-Fehleingabe wird der Online-Zugang gesperrt. Falls der Dokumentabruf nicht funktioniert, dann bitte TAN-Fehlerzähler so zurücksetzen: Online-Banking starten, einloggen und eine TAN-Aktion (z.B. Aufruf Postbox) durchführen.&lt;/LI&gt;&lt;LI&gt;&amp;nbsp;Momentan wird nur Photo-TAN unterstützt&lt;/LI&gt;&lt;/OL&gt;&lt;P&gt;Der Code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;PRE&gt;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' =&amp;gt; "Bearer #{@access_token}",
      'x-http-request-info' =&amp;gt; @client_request.value
     }
  end

  def json_header
    authorisation_header.merge({'accept' =&amp;gt; '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 =&amp;gt; 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' =&amp;gt; 'application/json'})
      rescue RestClient::ExceptionWithResponse =&amp;gt; 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' =&amp;gt; 'application/json',
           'x-once-authentication-info' =&amp;gt; "{\"id\": #{@authentication_info["id"]}}",
           'x-once-authentication' =&amp;gt; tan})
    rescue RestClient::ExceptionWithResponse =&amp;gt; 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 =&amp;gt; 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' =&amp;gt; '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&lt;/PRE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Thu, 20 Feb 2020 11:58:11 GMT</pubDate>
    <dc:creator>dg2210</dc:creator>
    <dc:date>2020-02-20T11:58:11Z</dc:date>
    <item>
      <title>gelöst: Postbox-Sammeldownload</title>
      <link>https://community.comdirect.de/t5/website-apps/gel%C3%B6st-postbox-sammeldownload/m-p/108340#M10244</link>
      <description>&lt;P&gt;Dank der REST-Schnittstelle habe ich eine saubere Lösung zum Postbox-Sammeldownload!&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Der untenstehende Code (vorgeschlagener Name: repobosado = &lt;STRONG&gt;RE&lt;/STRONG&gt;st&lt;STRONG&gt;PO&lt;/STRONG&gt;st&lt;STRONG&gt;BO&lt;/STRONG&gt;x&lt;STRONG&gt;SA&lt;/STRONG&gt;mmel&lt;STRONG&gt;DO&lt;/STRONG&gt;wnload) mag als Anregung für weitere Experimente dienen.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Kurzer Überblick:&lt;/P&gt;&lt;P&gt;Benötigt wird ein aktuelles ruby und das rest-client gem (falls es auf dem eigenen PC fehlt: &lt;EM&gt;gem install rest-client&lt;/EM&gt;).&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Die Datei enthält den REST-Clienten und ein kurzes Beispielprogramm (ab Zeile 133)&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Die Klasse &lt;EM&gt;ComdirectClient&lt;/EM&gt; kapselt die Kommunikation mit der Bank,&amp;nbsp; die Schnittstellen sind:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;login_and_get_phototan_challenge(client_id:, client_secret:, access_number:, pin: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;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&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;finalize_login(tan: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Beendet den login-Vorgang. Parameter: Die Photo-TAN aus dem challenge-Bild&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;get_postbox_document_list&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Liefert eine Liste der Dokumente in der Postbox&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;get_document_pdf_from_id(id: )&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;Holt das pdf-Dokument mit der gewünschten ID aus der Postbox&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;BEISPIEL&lt;/STRONG&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Client initialisieren, login-Vorgang starten und die PNG-Bilddaten in die Datei "tan_challenge.png" schreiben:&lt;/P&gt;&lt;PRE&gt;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}&lt;/PRE&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;PhotoTAN einlesen und TAN eingeben:&lt;/P&gt;&lt;PRE&gt;puts "Bild geschrieben, bitte TAN eingeben"
tan = gets.chomp
client.finalize_login(tan: tan)&lt;/PRE&gt;&lt;P&gt;Liste der Dokumente in der Postbox holen (und Anzahl ausgeben):&lt;/P&gt;&lt;PRE&gt;postbox_documents = client.get_postbox_document_list
puts "Anzahl Dokumente in Postbox: #{postbox_documents.length}"&lt;/PRE&gt;&lt;P&gt;Alle PDF-Dokumente downloaden, dabei den Dateinamen anpassen (Leerzeichen entfernen, Dokument-Id zum Namen hinzufügen (sonst sind die Dateinamen nicht eindeutig)&lt;/P&gt;&lt;PRE&gt;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&lt;/PRE&gt;&lt;P&gt;&lt;STRONG&gt;WICHTIG:&lt;/STRONG&gt;&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;Momentan werden nur maximal 20 Dokumente heruntergeladen. ursache unbekannt.&lt;/LI&gt;&lt;LI&gt;Bei TAN-Fehleingabe wird der Online-Zugang gesperrt. Falls der Dokumentabruf nicht funktioniert, dann bitte TAN-Fehlerzähler so zurücksetzen: Online-Banking starten, einloggen und eine TAN-Aktion (z.B. Aufruf Postbox) durchführen.&lt;/LI&gt;&lt;LI&gt;&amp;nbsp;Momentan wird nur Photo-TAN unterstützt&lt;/LI&gt;&lt;/OL&gt;&lt;P&gt;Der Code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;PRE&gt;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' =&amp;gt; "Bearer #{@access_token}",
      'x-http-request-info' =&amp;gt; @client_request.value
     }
  end

  def json_header
    authorisation_header.merge({'accept' =&amp;gt; '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 =&amp;gt; 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' =&amp;gt; 'application/json'})
      rescue RestClient::ExceptionWithResponse =&amp;gt; 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' =&amp;gt; 'application/json',
           'x-once-authentication-info' =&amp;gt; "{\"id\": #{@authentication_info["id"]}}",
           'x-once-authentication' =&amp;gt; tan})
    rescue RestClient::ExceptionWithResponse =&amp;gt; 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 =&amp;gt; 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' =&amp;gt; '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&lt;/PRE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 20 Feb 2020 11:58:11 GMT</pubDate>
      <guid>https://community.comdirect.de/t5/website-apps/gel%C3%B6st-postbox-sammeldownload/m-p/108340#M10244</guid>
      <dc:creator>dg2210</dc:creator>
      <dc:date>2020-02-20T11:58:11Z</dc:date>
    </item>
  </channel>
</rss>

