diff --git a/.gitignore b/.gitignore index 3b4f66b7..c00051a2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ pom.xml.versionsBackup **/.settings **/*.class .metadata/* +"Server/OPENAS2_LOG_DIR_IS_UNDEFINED/*.txt" diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiResource.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiResource.java index a5bf91a2..33b2abc1 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiResource.java +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiResource.java @@ -5,48 +5,76 @@ */ package org.openas2.cmd.processor.restapi; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import org.openas2.cert.AliasedCertificateFactory; -import org.openas2.cert.CertificateFactory; -import org.openas2.cmd.CommandResult; -import org.openas2.cmd.processor.RestCommandProcessor; import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.Consumes; - import jakarta.ws.rs.DefaultValue; - - +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.HEAD; import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.POST; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.PUT; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.HEAD; - - -import jakarta.ws.rs.PathParam; - +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; - -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.core.Request; -import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.StringWriter; import java.security.cert.Certificate; import java.security.cert.X509Certificate; + import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; + + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + + + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; + +import org.glassfish.grizzly.http.server.Request; + +import org.openas2.cert.AliasedCertificateFactory; +import org.openas2.cert.CertificateFactory; +import org.openas2.cmd.CommandResult; +import org.openas2.cmd.processor.RestCommandProcessor; +import org.openas2.Session; +import org.openas2.util.Properties; + import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + + /** * @author javier */ @@ -70,12 +98,11 @@ public static void setProcessor(RestCommandProcessor aProcessor) { private static RestCommandProcessor processor; @Context UriInfo ui; - @Context Request request; private final ObjectMapper mapper; - + public ApiResource() { - + mapper = new ObjectMapper(); // enable pretty printing mapper.enable(SerializationFeature.INDENT_OUTPUT); @@ -220,6 +247,139 @@ public Response headCommand(@PathParam("param") String command) { return Response.status(200).build(); } + @GET + @RolesAllowed({"ADMIN"}) + @Path("/getPropertyList") + @Produces(MediaType.APPLICATION_JSON) + public Response getPropertyList(@Context Request request) { + if (!request.isSecure() && !isLocalhost(request)) { + return Response.status(Response.Status.FORBIDDEN) + .entity("{\"error\":\"SSL/TLS required\"}") + .type(MediaType.APPLICATION_JSON) + .build(); + } + Map result = new HashMap<>(); + try { + result = (Map) Properties.getProperties(); + }catch(Exception ex) { + LoggerFactory.getLogger(ApiResource.class.getName()).error(ex.getMessage(), ex); + throw ex; + } + ObjectMapper om = new ObjectMapper(); + try { + String js = om.writeValueAsString(result); + return Response.ok(js, MediaType.APPLICATION_JSON).build(); + } catch (JsonProcessingException e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error").type(MediaType.APPLICATION_JSON).build(); + } + } + + @GET + @RolesAllowed({"ADMIN"}) + @Path("/getXml") + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + public Response getXml(@Context Request request, + @QueryParam("filename") String filename, + @QueryParam("xpath") String xpathExpression) { + + if (!request.isSecure() && !isLocalhost(request)) { + return Response.status(Response.Status.FORBIDDEN) + .entity("{\"error\":\"SSL/TLS required\"}") + .type(MediaType.APPLICATION_JSON) + .build(); + } + + + Set allowedXPaths = Set.of( + "/partnerships/partner", + "/partnerships/partner/name", + "/partnerships/*", + "*" + ); + + if (xpathExpression == null || !allowedXPaths.contains(xpathExpression)) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("{\"error\":\"Invalid or disallowed XPath expression\"}") + .type(MediaType.APPLICATION_JSON) + .build(); + } + + Session session = getProcessor().getSession(); + String filePath = session.getBaseDirectory() + "/" + filename; + + try { + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // prevent XXE + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new File(filePath)); + + + XPath xpath = XPathFactory.newInstance().newXPath(); + XPathExpression expr = xpath.compile(xpathExpression); + NodeList nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET); + + + Document resultDocument = builder.newDocument(); + Element root = resultDocument.createElement("results"); + resultDocument.appendChild(root); + + for (int i = 0; i < nodeList.getLength(); i++) { + Node importedNode = resultDocument.importNode(nodeList.item(i), true); + root.appendChild(importedNode); + } + + // Convert to string + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringWriter stringWriter = new StringWriter(); + transformer.transform(new DOMSource(resultDocument), new StreamResult(stringWriter)); + + return Response.ok(stringWriter.toString(), MediaType.APPLICATION_XML).build(); + + } catch (Exception exception) { + LoggerFactory.getLogger(ApiResource.class.getName()).error( + "Error processing XML file: " + filePath, exception); + return Response.serverError() + .entity("{\"error\":\"Internal server error\"}") + .type(MediaType.APPLICATION_JSON) + .build(); + } + } + + + + private static boolean isLocalhost(Request request) { + boolean isLocalhost = request.getRemoteAddr().equals("127.0.0.1") || request.getRemoteAddr().equals("::1"); + return isLocalhost; + } + + private NodeList getNodes(String xmlFileName, String xpathExpression) { + NodeList nodeList = null; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + // === XXE Protection === + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + DocumentBuilder db = dbf.newDocumentBuilder(); + File file = new File(xmlFileName); + Document document = db.parse(file); + + XPathExpression xPathExpr = XPathFactory.newInstance().newXPath().compile(xpathExpression); + nodeList = (NodeList) xPathExpr.evaluate(document, XPathConstants.NODESET); + + } catch (Exception ex) { + LoggerFactory.getLogger(ApiResource.class.getName()).error("Error parsing XML file: " + xmlFileName, ex); + // return null on error + } + return nodeList; + } + private CommandResult importCertificateByStream(String itemId, MultivaluedMap formParams) throws Exception { try { List params = new ArrayList(); @@ -245,7 +405,7 @@ private CommandResult importCertificateByStream(String itemId, MultivaluedMap