/* $Id: ChallengeInfo.java,v 1.9 2011/05/30 12:47:56 willuhn Exp $ This file is part of HBCI4Java Copyright (C) 2001-2008 Stefan Palme HBCI4Java is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. HBCI4Java is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.kapott.hbci.manager; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.kapott.hbci.GV.HBCIJob; import org.kapott.hbci.GV.HBCIJobImpl; import org.kapott.hbci.datatypes.SyntaxDE; import org.kapott.hbci.datatypes.factory.SyntaxDEFactory; import org.kapott.hbci.exceptions.HBCI_Exception; import org.kapott.hbci.exceptions.InvalidUserDataException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Diese Klasse ermittelt die noetigen HKTAN-Challenge-Parameter fuer einen * Geschaeftsvorfall */ public class ChallengeInfo { /** * Das Singleton. */ private static ChallengeInfo singleton = null; private Map<String,Job> data = null; // Die Parameter-Daten aus der XML-Datei. /** * Erzeugt ein neues Challenge-Info-Objekt. * @return das Challenge-Info-Objekt. */ public static synchronized ChallengeInfo getInstance() { if (singleton == null) singleton = new ChallengeInfo(); return singleton; } /** * ct. */ private ChallengeInfo() { HBCIUtils.log("initializing challenge info engine",HBCIUtils.LOG_DEBUG); //////////////////////////////////////////////////////////////////////////// // XML-Datei lesen String xmlpath = HBCIUtils.getParam("kernel.kernel.challengedatapath",""); InputStream dataStream = null; String filename = xmlpath+"challengedata.xml"; dataStream = ChallengeInfo.class.getClassLoader().getResourceAsStream(filename); if (dataStream == null) throw new InvalidUserDataException("*** can not load challenge information from "+filename); // mit den so gefundenen xml-daten ein xml-dokument bauen Document doc = null; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringComments(true); dbf.setValidating(true); DocumentBuilder db = dbf.newDocumentBuilder(); doc = db.parse(dataStream); dataStream.close(); } catch (Exception e) { throw new HBCI_Exception("*** can not load challengedata from file "+filename,e); } // //////////////////////////////////////////////////////////////////////////// data = new HashMap<String,Job>(); //////////////////////////////////////////////////////////////////////////// // Parsen NodeList jobs = doc.getElementsByTagName("job"); int size = jobs.getLength(); for (int i=0;i<size;++i) { Element job = (Element) jobs.item(i); String code = job.getAttribute("code"); data.put(code,new Job(job)); } // //////////////////////////////////////////////////////////////////////////// HBCIUtils.log("challenge information loaded",HBCIUtils.LOG_DEBUG); } /** * Liefert die Challenge-Daten fuer einen Geschaeftsvorfall. * @param code die Segmentkennung des Geschaeftsvorfalls. * @return die Challenge-Daten. */ public Job getData(String code) { return data.get(code); } /** * Uebernimmt die Challenge-Parameter in den HKTAN-Geschaeftsvorfall. * @param task der Job, zu dem die Challenge-Parameter ermittelt werden sollen. * @param hktan der HKTAN-Geschaeftsvorfall, in dem die Parameter gesetzt werden sollen. * @param secmech die BPD-Informationen zum TAN-Verfahren. */ public void applyParams(HBCIJobImpl task, HBCIJob hktan, Properties secmech) { String code = task.getHBCICode(); // Code des Geschaeftsvorfalls // Job-Parameter holen Job job = this.getData(code); // Den Geschaeftsvorfall kennen wir nicht. Dann brauchen wir // auch keine Challenge-Parameter setzen if (job == null) { HBCIUtils.log("have no challenge data for " + code + ", will not apply challenge params", HBCIUtils.LOG_INFO); return; } HHDVersion version = HHDVersion.find(secmech); HBCIUtils.log("using hhd version " + version, HBCIUtils.LOG_DEBUG2); // Parameter fuer die passende HHD-Version holen HhdVersion hhd = job.getVersion(version.getChallengeVersion()); // Wir haben keine Parameter fuer diese HHD-Version if (hhd == null) { HBCIUtils.log("have no challenge data for " + code + " in " + version + ", will not apply challenge params", HBCIUtils.LOG_INFO); return; } // Schritt 1: Challenge-Klasse uebernehmen String klass = hhd.getKlass(); HBCIUtils.log("using challenge klass " + klass, HBCIUtils.LOG_DEBUG2); hktan.setParam("challengeklass", klass); // Schritt 2: Challenge-Parameter uebernehmen List<Param> params = hhd.getParams(); for (int i=0;i<params.size();++i) { int num = i+1; // Die Job-Parameter beginnen bei 1 Param param = params.get(i); // Checken, ob der Parameter angewendet werden soll. if (!param.isComplied(secmech)) { HBCIUtils.log("skipping challenge parameter " + num + " (" + param.path + "), condition " + param.conditionName + "=" + param.conditionValue + " not complied",HBCIUtils.LOG_DEBUG2); continue; } // Parameter uebernehmen. Aber nur wenn er auch einen Wert hat. // Seit HHD 1.4 duerfen Parameter mittendrin optional sein, sie // werden dann freigelassen String value = param.getValue(task); if (value == null || value.length() == 0) { HBCIUtils.log("challenge parameter " + num + " (" + param.path + ") is empty",HBCIUtils.LOG_DEBUG2); continue; } HBCIUtils.log("adding challenge parameter " + num + " " + param.path + "=" + value, HBCIUtils.LOG_DEBUG2); hktan.setParam("ChallengeKlassParam" + num, value); } } /** * Eine Bean fuer die Parameter-Saetze eines Geschaeftsvorfalles fuer die HHD-Versionen. */ public static class Job { /** * Die Parameter fuer die jeweilige HHD-Version. */ private Map<String,HhdVersion> versions = new HashMap<String,HhdVersion>(); /** * ct. * @param job der XML-Knoten, in dem die Daten stehen. */ private Job(Element job) { NodeList specs = job.getElementsByTagName("challengeinfo"); int size = specs.getLength(); for (int i=0;i<size;++i) { Element spec = (Element) specs.item(i); String version = spec.getAttribute("spec"); this.versions.put(version,new HhdVersion(spec)); } } /** * Liefert die Challenge-Parameter fuer die angegeben HHD-Version. * @param version die HHD-Version. * @return die Challenge-Parameter fuer die HHD-Version. */ public HhdVersion getVersion(String version) { return this.versions.get(version); } } /** * Eine Bean fuer den Parameter-Satz eines Geschaeftvorfalles innerhalb einer HHD-Version. */ public static class HhdVersion { /** * Die Challenge-Klasse. */ private String klass = null; /** * Liste der Challenge-Parameter. */ private List<Param> params = new ArrayList<Param>(); /** * ct. * @param spec der XML-Knoten mit den Daten. */ private HhdVersion(Element spec) { this.klass = ((Element)spec.getElementsByTagName("klass").item(0)).getFirstChild().getNodeValue(); NodeList list = spec.getElementsByTagName("param"); int size = list.getLength(); for (int i=0;i<size;++i) { Element param = (Element) list.item(i); this.params.add(new Param(param)); } } /** * Liefert die Challenge-Klasse. * @return die Challenge-Klasse. */ public String getKlass() { return this.klass; } /** * Liefert die Challenge-Parameter fuer den Geschaeftsvorfall in dieser HHD-Version. * @return die Challenge-Parameter fuer den Geschaeftsvorfall in dieser HHD-Version. */ public List<Param> getParams() { return this.params; } } /** * Eine Bean fuer einen einzelnen Challenge-Parameter. */ public static class Param { /** * Der Typ des Parameters. */ private String type = null; /** * Der Pfad in den Geschaeftsvorfall-Parametern, unter dem der Wert steht. */ private String path = null; /** * Optional: Der Name einer Bedingung, die erfuellt sein muss, damit * der Parameter verwendet wird. Konkret ist hier der Name eines Property * aus secmechInfo gemeint. Also ein BPD-Parameter. */ private String conditionName = null; /** * Optional: Der Wert, den der BPD-Parameter haben muss, damit der Challenge-Parameter * verwendet wird. */ private String conditionValue = null; /** * ct. * @param param der XML-Knoten mit den Daten. */ private Param(Element param) { Node content = param.getFirstChild(); this.path = content != null ? content.getNodeValue() : null; this.type = param.getAttribute("type"); this.conditionName = param.getAttribute("condition-name"); this.conditionValue = param.getAttribute("condition-value"); } /** * Liefert true, wenn entweder keine Bedingung angegeben ist oder * die Bedingung erfuellt ist und der Parameter verwendet werden kann. * @param secmech die BPD-Informationen zum TAN-Verfahren. * @return true, wenn der Parameter verwendet werden kann. */ public boolean isComplied(Properties secmech) { if (this.conditionName == null || this.conditionName.length() == 0) return true; // Wir haben eine Bedingung. Mal schauen, ob sie erfuellt ist. String value = secmech.getProperty(this.conditionName,""); return value.equals(this.conditionValue); } /** * Liefert den Typ des Parameters. * @return der Typ des Parameters. */ public String getType() { return this.type; } /** * Liefert den Pfad zum Wert. * @return der Pfad zum Wert. */ public String getPath() { return this.path; } /** * Liefert den Wert des Parameters. * @param job der Geschaeftsvorfall. * @return der Wert des Parameters. */ private String getValue(HBCIJobImpl job) { // Leerer Parameter if (this.path == null || this.path.length() == 0) return null; String value = job.getChallengeParam(this.path); // Wert im passenden Format zurueckliefern return format(value); } /** * Formatiert den Text abhaengig vom Typ. * Wenn kein Typ angegeben ist, wird der Wert unformatiert zurueckgegeben. * @param value der zu formatierende Wert. * @return der formatierte Wert. */ public String format(String value) { // Bei leeren Werten lieferen wir generell NULL. // Die Parameter werden dann uebersprungen. if (value == null || value.trim().length() == 0) return null; // Wenn kein Typ angegeben ist, gibts auch nichts zu formatieren. // Nein, wir duerfen NICHT SyntaxAN verwenden. Denn die Parameter // in ChallengeKlassParams#param[1-9] sind ja bereits als Type AN // deklariert. Wuerden wir hier SyntaxAN verwenden, wuerden die // Werte dann doppelt codiert werden (das zweite Codieren macht ja // anschliessend HBCI4Java intern beim Zusammenbauen des Segments). // Was zum Beispiel dazu fuehren wuerde, dass ein Sonderzeichen wie // "+" oder "?" doppelt escaped werden wuerde. if (this.type == null || this.type.trim().length() == 0) return value; SyntaxDEFactory factory = SyntaxDEFactory.getInstance(); SyntaxDE syntax = null; try { syntax = factory.createSyntaxDE(this.type,this.path,value,0,0); return syntax.toString(0); } finally { // Objekt wieder freigeben if (syntax != null) factory.unuseObject(syntax,this.type); } } } }