All Downloads are FREE. Search and download functionalities are using the official Maven repository.

network.oxalis.as2.inbound.As2Servlet Maven / Gradle / Ivy

/*
 * Copyright 2010-2018 Norwegian Agency for Public Management and eGovernment (Difi)
 *
 * Licensed under the EUPL, Version 1.1 or – as soon they
 * will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 *
 * You may not use this work except in compliance with the Licence.
 *
 * You may obtain a copy of the Licence at:
 *
 * https://joinup.ec.europa.eu/community/eupl/og_page/eupl
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the Licence is
 * distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the Licence for the specific language governing
 * permissions and limitations under the Licence.
 */

package network.oxalis.as2.inbound;

import com.google.common.io.ByteStreams;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.contrib.web.servlet.filter.TracingFilter;
import lombok.extern.slf4j.Slf4j;
import network.oxalis.api.error.ErrorTracker;
import network.oxalis.api.model.Direction;
import network.oxalis.as2.code.As2Header;
import network.oxalis.as2.code.MdnHeader;
import network.oxalis.as2.lang.OxalisAs2InboundException;
import network.oxalis.as2.util.*;
import network.oxalis.commons.security.CertificateUtils;
import org.slf4j.MDC;

import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author steinar
 * @author thore
 * @author erlend
 */
@Slf4j
@Singleton
class As2Servlet extends HttpServlet {

    private final Provider inboundHandlerProvider;

    private final SMimeMessageFactory sMimeMessageFactory;

    private final ErrorTracker errorTracker;

    private final String toIdentifier;

    private final Tracer tracer;

    @Inject
    public As2Servlet(Provider inboundHandlerProvider, SMimeMessageFactory sMimeMessageFactory,
                      ErrorTracker errorTracker, X509Certificate certificate, Tracer tracer) {
        this.inboundHandlerProvider = inboundHandlerProvider;
        this.sMimeMessageFactory = sMimeMessageFactory;
        this.errorTracker = errorTracker;
        this.toIdentifier = CertificateUtils.extractCommonName(certificate);
        this.tracer = tracer;
    }

    /**
     * Receives the POST'ed AS2 message.
     * 

* Important to note that the HTTP headers contains the MIME headers for the payload. * Since the the request can only be read once, using getReader()/getInputStream() */ @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws IOException { // Fail fast when Message-Id header is not provided. if (request.getHeader("message-id") == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().println("Header field 'Message-ID' not found."); return; } // Fail fast when AS2-To is not provided or not addressed to me. if (request.getHeader("as2-to") == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().println("Header field 'AS2-To' not found."); return; } else if (!toIdentifier.equals(request.getHeader("as2-to").replace("\"", "").trim())) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().println("Invalid value in field 'AS2-To'."); return; } if (request.getHeader("as2-from") == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().println("Header field 'AS2-From' not found."); return; } SpanContext spanContext = (SpanContext) request.getAttribute(TracingFilter.SERVER_SPAN_CONTEXT); Span root = tracer.buildSpan("as2servlet.post").asChildOf(spanContext).start(); // Receives the data, validates the headers, signature etc., invokes the persistence handler // and finally returns the MdnData to be sent back to the caller try { root.setTag("message-id", request.getHeader("message-id")); MDC.put("message-id", request.getHeader("message-id")); // Read all headers InternetHeaders headers = new InternetHeaders(); Collections.list(request.getHeaderNames()) .forEach(name -> headers.addHeader(name, request.getHeader(name))); // Read MIME message MimeMessage mimeMessage = MimeMessageHelper.parse(request.getInputStream(), headers); try { // Performs the actual reception of the message by parsing the HTTP POST request // persisting the payload etc. Span span = tracer.buildSpan("as2message").asChildOf(root).start(); MimeMessage mdn = inboundHandlerProvider.get().receive(headers, mimeMessage, span); span.finish(); // Returns the MDN span = tracer.buildSpan("mdn").asChildOf(root).start(); writeMdn(response, mdn, HttpServletResponse.SC_OK); span.finish(); } catch (OxalisAs2InboundException e) { String identifier = errorTracker.track(Direction.IN, e, true); root.setTag("identifier", identifier); root.setTag("exception", String.valueOf(e.getMessage())); // Begin builder MdnBuilder mdnBuilder = MdnBuilder.newInstance(mimeMessage); // Original Message-Id mdnBuilder.addHeader(MdnHeader.ORIGINAL_MESSAGE_ID, headers.getHeader(As2Header.MESSAGE_ID)[0]); // Disposition from exception mdnBuilder.addHeader(MdnHeader.DISPOSITION, e.getDisposition()); mdnBuilder.addText(String.format("Error [%s]", identifier), e.getMessage()); // Build and add headers MimeMessage mdn = sMimeMessageFactory.createSignedMimeMessage(mdnBuilder.build(), SMimeDigestMethod.findByIdentifier(SignedMessage.extractMicalg(mimeMessage))); mdn.setHeader(As2Header.AS2_VERSION, As2Header.VERSION); mdn.setHeader(As2Header.AS2_FROM, toIdentifier); mdn.setHeader(As2Header.AS2_TO, headers.getHeader(As2Header.AS2_FROM)[0]); writeMdn(response, mdn, HttpServletResponse.SC_BAD_REQUEST); } } catch (Exception e) { String identifier = errorTracker.track(Direction.IN, e, false); root.setTag("identifier", identifier); root.setTag("exception", String.valueOf(e.getMessage())); // Unexpected internal error, cannot proceed, return HTTP 500 and partly MDN to indicating the problem writeFailureWithExplanation(request, response, e); } finally { MDC.remove("message-id"); root.finish(); } } protected void writeMdn(HttpServletResponse response, MimeMessage mdn, int status) throws MessagingException, IOException { // Set HTTP status. response.setStatus(status); // Add headers and collect header names. Map headers = Collections.list((Enumeration) mdn.getAllHeaders()).stream() .map(Header.class::cast) .collect(Collectors.toMap(Header::getName, Header::getValue)); // Move headers for (String name : headers.keySet()) { response.setHeader(name, headers.get(name)); mdn.removeHeader(name); } // Write MDN content to response. ByteStreams.copy(mdn.getInputStream(), response.getOutputStream()); } /** * If the AS2 message processing failed with an exception, we have an internal error and act accordingly */ void writeFailureWithExplanation(HttpServletRequest request, HttpServletResponse response, Exception e) throws IOException { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error("Request headers:"); Collections.list(request.getHeaderNames()) .forEach(name -> log.error("=> {}: {}", name, request.getHeader(name))); response.getWriter().write("INTERNAL ERROR!!"); // Being helpful to those who must read the error logs log.error("\n---------- REQUEST FAILURE INFORMATION ENDS HERE --------------"); } /** * Allows for simple http GET requests */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setStatus(HttpServletResponse.SC_OK); response.getOutputStream().println("Hello AS2 world\n"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy