diff --git a/modules/qrest/src/main/java/org/jpos/qrest/ValidateParams.java b/modules/qrest/src/main/java/org/jpos/qrest/ValidateParams.java new file mode 100644 index 0000000000..e588b61921 --- /dev/null +++ b/modules/qrest/src/main/java/org/jpos/qrest/ValidateParams.java @@ -0,0 +1,259 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2018 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.qrest; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jackson.JsonLoader; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.report.ProcessingReport; +import com.github.fge.jsonschema.main.JsonSchema; +import com.github.fge.jsonschema.main.JsonSchemaFactory; +import org.jdom2.Element; +import org.jpos.core.ConfigurationException; +import org.jpos.core.XmlConfigurable; +import org.jpos.transaction.Context; +import org.jpos.transaction.TransactionParticipant; +import org.jpos.util.Caller; +import org.jpos.util.Log; +import org.jpos.util.LogSource; +import org.jpos.util.Logger; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Validate mandatory and optional parameters. Support JsonSchema. + * + * @author jr@jpos.org + * + * Sample usage: + * + * <participant class="org.jpos.rest.ValidateParams" logger="Q2" realm="validate-params"> + * <mandatory> + * <param name="version">"^1\.0$"</param> + * <param name="consumer">"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$</param> + * </mandatory> + * </participant> + * + * + * <participant class="org.jpos.rest.ValidateParams" logger="Q2" realm="validate-params"> + * <mandatory> + * <param name="JSON_REQUEST" type="json-schema"> + * { + * "$schema": "http://json-schema.org/draft-04/schema#", + * "title": "SysConfig", + * "description": "A SysConfig to post", + * "type": "object", + * "properties": { + * "id": { + * "type": "string" + * }, + * "value": { + * "type": "string" + * }, + * "readPerm": { + * "type" : "string" + * }, + * "writePerm": { + * "type": "string" + * } + * }, + * "additionalProperties" : false, + * "required": [ "id","value","readPerm","writePerm"] + * } + * </param> + * </mandatory> + * </participant> + * + */ + +@SuppressWarnings("unused") +public class ValidateParams implements TransactionParticipant, XmlConfigurable, LogSource { + private Map mandatory; + private Map optional; + private Map mandatoryJson; + private Map optionalJson; + + private Log log; + + @Override + public int prepare(long id, Serializable context) { + Context ctx = (Context) context; + + checkMandatory(ctx); + checkOptional(ctx); + checkMandatoryJson(ctx); + checkOptionalJson(ctx); + return (ctx.getResult().hasFailures() ? ABORTED : PREPARED) | READONLY | NO_JOIN; + } + + + @SuppressWarnings("unchecked") + @Override + public void setConfiguration(Element cfg) throws ConfigurationException{ + mandatory = new HashMap<>(); + mandatoryJson = new HashMap<>(); + Element m = cfg.getChild("mandatory"); + if (m != null) { + for (Element e : m.getChildren("param")) { + if ("json-schema".equals(e.getAttributeValue("type"))) { + try { + String json = e.getValue(); + JsonNode nodeSchema = JsonLoader.fromString(json); + JsonSchema schema = JsonSchemaFactory.byDefault().getJsonSchema(nodeSchema); + mandatoryJson.put(e.getAttributeValue("name"), schema); + } catch (Exception ex) { + throw new ConfigurationException (ex); + } + } else { + mandatory.put(e.getAttributeValue("name"), Pattern.compile(e.getValue())); + } + } + } + optional = new HashMap<>(); + optionalJson = new HashMap<>(); + Element o = cfg.getChild("optional"); + if (o != null) { + for (Element e : o.getChildren("param")) { + if ("json-schema".equals(e.getAttributeValue("type"))) { + String json = e.getValue(); + JsonNode nodeSchema; + try { + nodeSchema = JsonLoader.fromString(json); + JsonSchema schema = JsonSchemaFactory.byDefault().getJsonSchema(nodeSchema); + optionalJson.put(e.getAttributeValue("name"), schema); + } catch (IOException | ProcessingException ex) { + throw new ConfigurationException(ex); + } + } else { + optional.put(e.getAttributeValue("name"), Pattern.compile(e.getValue())); + } + } + } + } + + @Override + public void setLogger(Logger logger, String realm) { + this.log = new Log(logger, realm); + } + + @Override + public String getRealm() { + return log.getRealm(); + } + + @Override + public Logger getLogger() { + return log.getLogger(); + } + + private void checkMandatory (Context ctx) { + for (Map.Entry entry : mandatory.entrySet()) { + Object v = ctx.get(entry.getKey()); + String value = v != null ? v.toString() : null; + if (value == null) { + ctx.getResult().fail( + ResultCode.MANDATORY_PARAM_NOT_PRESENT, Caller.info(), "Mandatory param '%s' not present", entry.getKey() + ); + return; + } + Pattern p = entry.getValue(); + Matcher m = p.matcher(value); + if (!m.matches()) { + ctx.getResult().fail( + ResultCode.INVALID_PARAM, Caller.info(), "Invalid param '%s'", entry.getKey() + ); + return; + } + } + } + + private void checkOptional (Context ctx) { + for (Map.Entry entry : optional.entrySet()) { + String value = ctx.getString(entry.getKey()); + if (value != null) { + Pattern p = entry.getValue(); + Matcher m = p.matcher(value); + if (!m.matches()) { + ctx.getResult().fail( + ResultCode.INVALID_OPTIONAL_PARAM, Caller.info(), "Invalid optional param '%s'", entry.getKey() + ); + return; + } + } + } + } + + private void checkMandatoryJson (Context ctx) { + int errors = 0; + for (Map.Entry entry : mandatoryJson.entrySet()) { + String value = ctx.getString(entry.getKey()); + ProcessingReport report; + if (value != null) { + try { + JsonSchema schema = entry.getValue(); + JsonNode node = JsonLoader.fromString(value); + report = schema.validate(node); + if (!report.isSuccess()) { + ctx.getResult().fail( + ResultCode.INVALID_MANDATORY_JSON, Caller.info(), "Invalid mandatory JSON '%s'", entry.getKey() + ); + ctx.log(report); + } + } catch(Exception ex) { + ctx.getResult().fail( + ResultCode.INVALID_MANDATORY_JSON, Caller.info(), "Invalid mandatory JSON param '%s' - %s", entry.getKey(), ex.getMessage() + ); + } + } else { + ctx.getResult().fail( + ResultCode.MANDATORY_JSON_NOT_PRESENT, Caller.info(), "Mandatory JSON '%s' not present", entry.getKey() + ); + + } + } + } + + private void checkOptionalJson (Context ctx) { + for (Map.Entry entry : optionalJson.entrySet()) { + String value = ctx.getString(entry.getKey()); + ProcessingReport report; + if (value != null) { + try { + JsonSchema schema = entry.getValue(); + JsonNode node = JsonLoader.fromString(value); + report = schema.validate(node); + if (!report.isSuccess()) { + ctx.getResult().fail( + ResultCode.INVALID_OPTIONAL_JSON, Caller.info(), "Invalid mandatory JSON '%s'", entry.getKey() + ); + } + } catch(Exception ex) { + ctx.getResult().fail( + ResultCode.INVALID_OPTIONAL_JSON, Caller.info(), "Mandatory JSON '%s' not present", entry.getKey() + ); + } + } + } + } +}