eionet.webq.web.interceptor.CdrAuthorizationInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for eionet.webq.web.interceptor.CdrAuthorizationInterceptor.java

Source

/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Web Questionnaires 2
 *
 * The Initial Owner of the Original Code is European Environment
 * Agency. Portions created by TripleDev are Copyright
 * (C) European Environment Agency.  All Rights Reserved.
 *
 * Contributor(s):
 *        Anton Dmitrijev
 */
package eionet.webq.web.interceptor;

import eionet.webq.converter.CookiesToStringBidirectionalConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;

import static org.apache.commons.lang3.StringUtils.defaultString;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.web.client.RestOperations;

/**
 * Intercepts calls to pages for CDR integration.
 */
@Component
public class CdrAuthorizationInterceptor extends HandlerInterceptorAdapter {
    /**
     * Authorization failed attribute.
     */
    public static final String AUTHORIZATION_FAILED_ATTRIBUTE = CdrAuthorizationInterceptor.class.getName();
    /**
     * Forward parsed cookies to controller via request attribute.
     */
    public static final String PARSED_COOKIES_ATTRIBUTE = "request.parsed.cookies";
    /**
     * Session attribute name for counting unsuccessful authorization counts.
     */
    static final String AUTHORIZATION_TRY_COUNT = "authorization-try-count";
    /**
     * Session attribute name for counting unsuccessful authorization counts.
     */
    static final Integer ALLOWED_AUTHORIZATION_FAILURES_COUNT = 3;
    /**
     * Logger for this class.
     */
    private static final Logger LOGGER = Logger.getLogger(CdrAuthorizationInterceptor.class);
    /**
     * Flag signals that request handling could proceed.
     */
    private static final boolean PROCEED = true;
    /**
     * Flag signals that response already written and no more handling required.
     */
    private static final boolean STOP_REQUEST_PROPAGATION = false;
    /**
     * Authorization header name.
     */
    private static final String AUTHORIZATION_HEADER = "Authorization";
    /**
     * Convert cookie objects to string and vice versa.
     */
    @Autowired
    CookiesToStringBidirectionalConverter cookiesConverter;
    /**
     * Rest operations.
     */
    @Autowired
    private RestOperations restOperations;
    /**
     * CDR login method name.
     */
    @Value("${cdr.login}")
    private String cdrLoginMethod;
    /**
     * CDR envelope properties page name.
     */
    @Value("${cdr.envelope.properties}")
    private String cdrEnvelopePropertiesMethod;
    /**
     * Current session.
     */
    @Autowired
    private HttpSession session;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String authorization = request.getHeader(AUTHORIZATION_HEADER);
        //        if (true) return PROCEED;
        if (StringUtils.isNotEmpty(authorization) || request.getParameter("auth") != null) {
            // if Basic auth is present in the request, then try to log in to CDR to test if it is valid token for given domain.
            // "auth" parameter is just meant for testing the CDR API in development environment - WebQ asks to authenticate.
            HttpHeaders headers = new HttpHeaders();
            headers.add(AUTHORIZATION_HEADER, authorization);
            //            return PROCEED;
            try {
                ResponseEntity<String> loginResponse = restOperations.postForEntity(
                        extractCdrUrl(request) + "/" + cdrLoginMethod, new HttpEntity<Object>(headers),
                        String.class);
                LOGGER.info("Response code received from CDR basic authorization request "
                        + loginResponse.getStatusCode());
                return PROCEED;
            } catch (HttpStatusCodeException e) {
                if (e.getStatusCode() != HttpStatus.UNAUTHORIZED) {
                    LOGGER.warn("Authorization against CDR failed with unexpected HTTP status code", e);
                }
            }
        } else {
            // if Basic auth is not present, then test if user is already authorised in this domain
            // by using provided cookies to fetch CDR envelope properties page.
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                HttpHeaders headers = new HttpHeaders();
                for (Cookie cookie : cookies) {
                    // put ZopeId parameter to request header. It works only when the value is surrounded with quotes.
                    headers.add("Cookie", cookiesConverter.convertCookieToString(cookie));
                }
                String urlToFetch = extractCdrEnvelopeUrl(request) + "/" + cdrEnvelopePropertiesMethod;
                //ResponseEntity<String> loginResponse = restOperations.exchange(urlToFetch, HttpMethod.GET,
                //        new HttpEntity<Object>(headers), String.class);

                HttpResponse responseFromCdr = fetchUrlWithoutRedirection(urlToFetch, headers);
                try {
                    int statusCode = responseFromCdr.getStatusLine().getStatusCode();

                    LOGGER.info("Response code received from CDR envelope request using cookies " + statusCode);
                    if (statusCode == HttpStatus.OK.value()) {
                        request.setAttribute(PARSED_COOKIES_ATTRIBUTE,
                                cookiesConverter.convertCookiesToString(cookies));
                        return PROCEED;
                    } else if ((statusCode == HttpStatus.MOVED_PERMANENTLY.value()
                            || statusCode == HttpStatus.MOVED_TEMPORARILY.value())
                            && responseFromCdr.getFirstHeader("Location") != null) {
                        // redirect to CDR login page
                        String redirectUrl = extractCdrUrl(request)
                                + responseFromCdr.getFirstHeader("Location").getValue();
                        LOGGER.info("Redirect to " + redirectUrl);
                        response.sendRedirect(redirectUrl);
                    }
                } catch (HttpStatusCodeException e) {
                    if (e.getStatusCode() != HttpStatus.UNAUTHORIZED) {
                        LOGGER.warn("Fetching CDR envelope page failed with unexpected HTTP status code", e);
                    }
                }
            }
        }

        if (isFailureCountsEqualsToAllowedFailuresCount()) {
            request.setAttribute(AUTHORIZATION_FAILED_ATTRIBUTE, AUTHORIZATION_FAILED_ATTRIBUTE);
            session.removeAttribute(AUTHORIZATION_TRY_COUNT);
            return PROCEED;
        }

        increaseFailedAuthorizationsCount();
        response.addHeader("WWW-Authenticate", "Basic realm=\"Please login to use webforms.\"");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        return STOP_REQUEST_PROPAGATION;
    }

    public void setCdrLoginMethod(String cdrLoginMethod) {
        this.cdrLoginMethod = cdrLoginMethod;
    }

    private boolean isFailureCountsEqualsToAllowedFailuresCount() {
        return ALLOWED_AUTHORIZATION_FAILURES_COUNT.equals(session.getAttribute(AUTHORIZATION_TRY_COUNT));
    }

    /**
     * Extracts CDR host URL from request.
     *
     * @param request http request
     * @return host URL
     */
    private String extractCdrUrl(HttpServletRequest request) {
        return defaultString(getUrlHost(request.getParameter("base_uri")), defaultString(
                getUrlHost(request.getParameter("envelope")), getUrlHost(request.getParameter("instance"))));
    }

    /**
     * Extracts CDR envelope URL from request. If envelope parameter is not defined, then extract envelope from instance parameter.
     *
     * @param request http request
     * @return envelope URL
     */
    private String extractCdrEnvelopeUrl(HttpServletRequest request) {

        if (StringUtils.isNotEmpty(request.getParameter("envelope"))) {
            return request.getParameter("envelope");
        } else if (StringUtils.isNotEmpty(request.getParameter("instance"))) {
            int fileNameSeparatorIndex = request.getParameter("instance").lastIndexOf("/");
            return request.getParameter("instance").substring(0, fileNameSeparatorIndex);
        } else {
            return null;
        }
    }

    /**
     * Gets host from url string.
     * Result will be in format protocol://host:port.
     *
     * @param url url to be checked
     * @return host or null if provided string is malformed URL
     */
    private String getUrlHost(String url) {
        try {
            URL parsedUrl = new URL(url);
            return parsedUrl.getProtocol() + "://" + parsedUrl.getAuthority();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * Increase failed authorizations count.
     */
    private void increaseFailedAuthorizationsCount() {
        Integer failedAttempts = (Integer) session.getAttribute(AUTHORIZATION_TRY_COUNT);
        session.setAttribute(AUTHORIZATION_TRY_COUNT, failedAttempts != null ? failedAttempts + 1 : 1);
    }

    /**
     * Calls a resource in CDR with redirect disabled. Then it is possible to catch if the user is redirected to login page.
     *
     * @param url CDR url to fetch.
     * @param headers HTTP headers to send.
     * @return HTTP response object
     * @throws IOException if network error occurs
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.KeyManagementException
     */

    protected CloseableHttpResponse fetchUrlWithoutRedirection(String url, HttpHeaders headers)
            throws IOException, NoSuchAlgorithmException, KeyManagementException {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setSSLContext(SSLContexts.custom().useProtocol("TLSv1.2").build())
                .setRedirectStrategy(new RedirectStrategy() {
                    @Override
                    public boolean isRedirected(HttpRequest httpRequest, HttpResponse httpResponse,
                            HttpContext httpContext) throws ProtocolException {
                        return false;
                    }

                    @Override
                    public HttpUriRequest getRedirect(HttpRequest httpRequest, HttpResponse httpResponse,
                            HttpContext httpContext) throws ProtocolException {
                        return null;
                    }
                });
        HttpGet httpget = new HttpGet(url);

        for (Map.Entry<String, List<String>> header : headers.entrySet()) {
            for (String value : header.getValue()) {
                httpget.addHeader(header.getKey(), value);
            }
        }
        CloseableHttpClient client = httpClientBuilder.build();
        CloseableHttpResponse httpResponse = client.execute(httpget);
        return httpResponse;
    }
}