password.pwm.util.ServletHelper.java Source code

Java tutorial

Introduction

Here is the source code for password.pwm.util.ServletHelper.java

Source

/*
 * Password Management Servlets (PWM)
 * http://code.google.com/p/pwm/
 *
 * Copyright (c) 2006-2009 Novell, Inc.
 * Copyright (c) 2009-2015 The PWM Project
 *
 * This program 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.
 *
 * 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 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 password.pwm.util;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import password.pwm.AppProperty;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.Validator;
import password.pwm.bean.SessionStateBean;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.ContextManager;
import password.pwm.http.PwmRequest;
import password.pwm.http.PwmResponse;
import password.pwm.http.PwmSession;
import password.pwm.i18n.LocaleHelper;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.stats.Statistic;
import password.pwm.util.stats.StatisticsManager;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

public class ServletHelper {

    private static final PwmLogger LOGGER = PwmLogger.forClass(ServletHelper.class);

    public static String debugHttpHeaders(final HttpServletRequest req) {
        final StringBuilder sb = new StringBuilder();

        sb.append("http").append(req.isSecure() ? "s " : " non-").append("secure request headers: ");
        sb.append("\n");

        for (Enumeration enumeration = req.getHeaderNames(); enumeration.hasMoreElements();) {
            final String headerName = (enumeration.nextElement()).toString();
            sb.append("  ");
            sb.append(headerName);
            sb.append("=");
            if (headerName.contains("Authorization")) {
                sb.append(PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT);
            } else {
                sb.append(req.getHeader(headerName));
            }
            sb.append(enumeration.hasMoreElements() ? "\n" : "");
        }

        return sb.toString();
    }

    public static String readRequestBody(final HttpServletRequest request)
            throws IOException, PwmUnrecoverableException {
        final PwmApplication pwmApplication = ContextManager.getPwmApplication(request);
        final int maxChars = Integer
                .parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_BODY_MAXREAD_LENGTH));
        return readRequestBody(request, maxChars);
    }

    public static String readRequestBody(final HttpServletRequest request, final int maxChars) throws IOException {
        final StringBuilder inputData = new StringBuilder();
        String line;
        try {
            final BufferedReader reader = request.getReader();
            while (((line = reader.readLine()) != null) && inputData.length() < maxChars) {
                inputData.append(line);
            }
        } catch (Exception e) {
            LOGGER.error("error reading request body stream: " + e.getMessage());
        }
        return inputData.toString();
    }

    public static void addPwmResponseHeaders(final PwmRequest pwmRequest, boolean fromServlet) {

        if (pwmRequest == null) {
            return;
        }
        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
        final PwmSession pwmSession = pwmRequest.getPwmSession();
        final PwmResponse resp = pwmRequest.getPwmResponse();

        if (!resp.isCommitted()) {
            final boolean includeXAmb = Boolean
                    .parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XAMB));
            final boolean includeXInstance = Boolean.parseBoolean(
                    pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XINSTANCE));
            final boolean includeXSessionID = Boolean.parseBoolean(
                    pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XSESSIONID));
            final boolean includeXVersion = Boolean.parseBoolean(
                    pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XVERSION));
            final boolean includeXContentTypeOptions = Boolean.parseBoolean(
                    pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XCONTENTTYPEOPTIONS));
            final boolean includeXXSSProtection = Boolean.parseBoolean(
                    pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XXSSPROTECTION));

            final boolean includeXFrameDeny = pwmApplication.getConfig()
                    .readSettingAsBoolean(PwmSetting.SECURITY_PREVENT_FRAMING);
            final boolean sendNoise = Boolean
                    .parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XNOISE));

            if (sendNoise) {
                resp.setHeader(PwmConstants.HttpHeader.XNoise,
                        PwmRandom.getInstance().alphaNumericString(PwmRandom.getInstance().nextInt(100) + 10));
            }

            if (fromServlet && includeXAmb) {
                resp.setHeader(PwmConstants.HttpHeader.XAmb, PwmConstants.X_AMB_HEADER[PwmRandom.getInstance()
                        .nextInt(PwmConstants.X_AMB_HEADER.length)]);
            }

            if (includeXVersion) {
                resp.setHeader(PwmConstants.HttpHeader.XVersion, PwmConstants.SERVLET_VERSION);
            }

            if (includeXContentTypeOptions) {
                resp.setHeader(PwmConstants.HttpHeader.XContentTypeOptions, "nosniff");
            }

            if (includeXXSSProtection) {
                resp.setHeader(PwmConstants.HttpHeader.XXSSProtection, "1");
            }

            if (includeXInstance) {
                resp.setHeader(PwmConstants.HttpHeader.XInstance, String.valueOf(pwmApplication.getInstanceID()));
            }

            if (includeXSessionID && pwmSession != null) {
                resp.setHeader(PwmConstants.HttpHeader.XSessionID, pwmSession.getSessionStateBean().getSessionID());
            }

            if (includeXFrameDeny && fromServlet) {
                resp.setHeader(PwmConstants.HttpHeader.XFrameOptions, "DENY");
            }

            if (fromServlet) {
                resp.setHeader(PwmConstants.HttpHeader.Cache_Control,
                        "no-cache, no-store, must-revalidate, proxy-revalidate");
            }

            //resp.setHeader("Content-Security-Policy","default-src 'self' 'unsafe-inline' 'unsafe-eval'");
            if (fromServlet && pwmSession != null) {
                final String contentPolicy = pwmApplication.getConfig()
                        .readSettingAsString(PwmSetting.SECURITY_CSP_HEADER);
                if (contentPolicy != null && !contentPolicy.isEmpty()) {
                    final String nonce = pwmRequest.getCspNonce();
                    final String expandedPolicy = contentPolicy.replace("%NONCE%", nonce);
                    resp.setHeader(PwmConstants.HttpHeader.ContentSecurityPolicy, expandedPolicy);
                }
            }

            resp.setHeader(PwmConstants.HttpHeader.Server, null);
        }
    }

    public static String readCookie(final HttpServletRequest req, final String cookieName) {
        if (req == null || cookieName == null) {
            return null;
        }
        final Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (final Cookie cookie : cookies) {
                if (cookie != null) {
                    final String loopName = cookie.getName();
                    if (cookieName.equals(loopName)) {
                        return StringUtil.urlDecode(cookie.getValue());
                    }
                }
            }
        }
        return null;
    }

    public static boolean cookieEquals(final HttpServletRequest req, final String cookieName,
            final String cookieValue) {
        final String value = readCookie(req, cookieName);
        if (value == null) {
            return cookieValue == null;
        }
        return value.equals(cookieValue);
    }

    public static String readUserHostname(final HttpServletRequest req, final PwmSession pwmSession)
            throws PwmUnrecoverableException {
        final Configuration config = ContextManager.getPwmApplication(req).getConfig();
        if (config != null && !config.readSettingAsBoolean(PwmSetting.REVERSE_DNS_ENABLE)) {
            return "";
        }

        final String userIPAddress = readUserIPAddress(req, pwmSession);
        try {
            return InetAddress.getByName(userIPAddress).getCanonicalHostName();
        } catch (UnknownHostException e) {
            LOGGER.trace(pwmSession,
                    "unknown host while trying to compute hostname for src request: " + e.getMessage());
        }
        return "";
    }

    /**
     * Returns the IP address of the user.  If there is an X-Forwarded-For header in the request, that address will
     * be used.  Otherwise, the source address of the request is used.
     *
     * @param req        A valid HttpServletRequest.
     * @param pwmSession pwmSession used for config lookup
     * @return String containing the textual representation of the source IP address, or null if the request is invalid.
     */
    public static String readUserIPAddress(final HttpServletRequest req, final PwmSession pwmSession)
            throws PwmUnrecoverableException {
        final Configuration config = ContextManager.getPwmApplication(req).getConfig();
        final boolean useXForwardedFor = config != null
                && config.readSettingAsBoolean(PwmSetting.USE_X_FORWARDED_FOR_HEADER);

        String userIP = "";

        if (useXForwardedFor) {
            try {
                userIP = req.getHeader(PwmConstants.HTTP_HEADER_X_FORWARDED_FOR);
            } catch (Exception e) {
                //ip address not in header (no X-Forwarded-For)
            }
        }

        if (userIP == null || userIP.length() < 1) {
            userIP = req.getRemoteAddr();
        }

        return userIP == null ? "" : userIP;
    }

    public static String readFileUpload(final HttpServletRequest req, final String filePartName, int maxFileChars)
            throws IOException, ServletException, PwmUnrecoverableException {
        try {
            if (ServletFileUpload.isMultipartContent(req)) {

                // Create a new file upload handler
                final ServletFileUpload upload = new ServletFileUpload();

                String uploadFile = null;

                // Parse the request
                for (final FileItemIterator iter = upload.getItemIterator(req); iter.hasNext();) {
                    final FileItemStream item = iter.next();

                    if (filePartName.equals(item.getFieldName())) {
                        uploadFile = streamToString(item.openStream(), maxFileChars);
                    }
                }

                return uploadFile;
            }
        } catch (Exception e) {
            LOGGER.error("error reading file upload: " + e.getMessage());
        }
        return null;
    }

    public static InputStream readFileUpload(final HttpServletRequest req, final String filePartName)
            throws IOException, ServletException, PwmUnrecoverableException {
        try {
            if (ServletFileUpload.isMultipartContent(req)) {

                // Create a new file upload handler
                final ServletFileUpload upload = new ServletFileUpload();

                // Parse the request
                for (final FileItemIterator iter = upload.getItemIterator(req); iter.hasNext();) {
                    final FileItemStream item = iter.next();

                    if (filePartName.equals(item.getFieldName())) {
                        return item.openStream();
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("error reading file upload: " + e.getMessage());
        }
        return null;
    }

    private static String streamToString(final InputStream stream, final int maxFileChars)
            throws IOException, PwmUnrecoverableException {
        final BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(stream, PwmConstants.DEFAULT_CHARSET));
        final StringBuilder sb = new StringBuilder();
        int charCounter = 0;
        int nextChar = bufferedReader.read();
        while (nextChar != -1) {
            charCounter++;
            sb.append((char) nextChar);
            nextChar = bufferedReader.read();
            if (charCounter > maxFileChars) {
                stream.close();
                throw new PwmUnrecoverableException(
                        new ErrorInformation(PwmError.CONFIG_UPLOAD_FAILURE, "file too large"));
            }
        }
        return sb.toString();
    }

    public static void handleRequestInitialization(final PwmRequest pwmRequest, final PwmApplication pwmApplication,
            final PwmSession pwmSession) throws PwmUnrecoverableException {
        final SessionStateBean ssBean = pwmSession.getSessionStateBean();

        // mark if first request
        if (ssBean.getSessionCreationTime() == null) {
            ssBean.setSessionCreationTime(new Date());
            ssBean.setSessionLastAccessedTime(new Date());
        }

        // mark session ip address
        if (ssBean.getSrcAddress() == null) {
            ssBean.setSrcAddress(readUserIPAddress(pwmRequest.getHttpServletRequest(), pwmSession));
        }

        // mark the user's hostname in the session bean
        if (ssBean.getSrcHostname() == null) {
            ssBean.setSrcHostname(readUserHostname(pwmRequest.getHttpServletRequest(), pwmSession));
        }

        // update the privateUrlAccessed flag
        if (pwmRequest.getURL().isPrivateUrl()) {
            ssBean.setPrivateUrlAccessed(true);
        }

        // initialize the session's locale
        if (ssBean.getLocale() == null) {
            initializeLocaleAndTheme(pwmRequest.getHttpServletRequest(), pwmApplication, pwmSession);
        }

        // set idle timeout (may get overridden by module-specific values elsewhere
        {
            final int sessionIdleSeconds = (int) pwmApplication.getConfig()
                    .readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
            pwmSession.setSessionTimeout(pwmRequest.getHttpServletRequest().getSession(), sessionIdleSeconds);
        }
    }

    private static void initializeLocaleAndTheme(final HttpServletRequest req, final PwmApplication pwmApplication,
            final PwmSession pwmSession) throws PwmUnrecoverableException {
        final String localeCookieName = pwmApplication.getConfig()
                .readAppProperty(AppProperty.HTTP_COOKIE_LOCALE_NAME);
        final String localeCookie = ServletHelper.readCookie(req, localeCookieName);
        if (localeCookieName.length() > 0 && localeCookie != null) {
            LOGGER.debug(pwmSession, "detected locale cookie in request, setting locale to " + localeCookie);
            pwmSession.setLocale(pwmApplication, localeCookie);
        } else {
            final List<Locale> knownLocales = pwmApplication.getConfig().getKnownLocales();
            final Locale userLocale = LocaleHelper.localeResolver(req.getLocale(), knownLocales);
            pwmSession.getSessionStateBean()
                    .setLocale(userLocale == null ? PwmConstants.DEFAULT_LOCALE : userLocale);
            LOGGER.trace(pwmSession, "user locale set to '" + pwmSession.getSessionStateBean().getLocale() + "'");
        }

        final String themeCookieName = pwmApplication.getConfig()
                .readAppProperty(AppProperty.HTTP_COOKIE_THEME_NAME);
        final String themeCookie = ServletHelper.readCookie(req, themeCookieName);
        if (localeCookieName.length() > 0 && themeCookie != null && themeCookie.length() > 0) {
            LOGGER.debug(pwmSession, "detected theme cookie in request, setting theme to " + themeCookie);
            pwmSession.getSessionStateBean().setTheme(themeCookie);
        }
    }

    public static void handleRequestSecurityChecks(final HttpServletRequest req,
            final PwmApplication pwmApplication, final PwmSession pwmSession) throws PwmUnrecoverableException {
        final SessionStateBean ssBean = pwmSession.getSessionStateBean();

        // check the user's IP address
        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.MULTI_IP_SESSION_ALLOWED)) {
            final String remoteAddress = readUserIPAddress(req, pwmSession);
            if (!ssBean.getSrcAddress().equals(remoteAddress)) {
                final String errorMsg = "current network address '" + remoteAddress
                        + "' has changed from original network address '" + ssBean.getSrcAddress() + "'";
                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,
                        errorMsg);
                throw new PwmUnrecoverableException(errorInformation);
            }
        }

        // check total time.
        {
            if (ssBean.getSessionCreationTime() != null) {
                final Long maxSessionSeconds = pwmApplication.getConfig()
                        .readSettingAsLong(PwmSetting.SESSION_MAX_SECONDS);
                final TimeDuration sessionAge = TimeDuration.fromCurrent(ssBean.getSessionCreationTime());
                if (sessionAge.getTotalSeconds() > maxSessionSeconds) {
                    final String errorMsg = "session age (" + sessionAge.asCompactString()
                            + ") is longer than maximum permitted age";
                    final ErrorInformation errorInformation = new ErrorInformation(
                            PwmError.ERROR_SECURITY_VIOLATION, errorMsg);
                    throw new PwmUnrecoverableException(errorInformation);
                }
            }
        }

        // check headers
        {
            final List<String> requiredHeaders = pwmApplication.getConfig()
                    .readSettingAsStringArray(PwmSetting.REQUIRED_HEADERS);
            if (requiredHeaders != null && !requiredHeaders.isEmpty()) {
                final Map<String, String> configuredValues = StringUtil
                        .convertStringListToNameValuePair(requiredHeaders, "=");
                for (final String key : configuredValues.keySet()) {
                    if (key != null && key.length() > 0) {
                        final String requiredValue = configuredValues.get(key);
                        if (requiredValue != null && requiredValue.length() > 0) {
                            final String value = Validator.sanitizeInputValue(pwmApplication.getConfig(),
                                    req.getHeader(key), 1024);
                            if (value == null || value.length() < 1) {
                                final String errorMsg = "request is missing required value for header '" + key
                                        + "'";
                                final ErrorInformation errorInformation = new ErrorInformation(
                                        PwmError.ERROR_SECURITY_VIOLATION, errorMsg);
                                throw new PwmUnrecoverableException(errorInformation);
                            } else {
                                if (!requiredValue.equals(value)) {
                                    final String errorMsg = "request has incorrect required value for header '"
                                            + key + "'";
                                    final ErrorInformation errorInformation = new ErrorInformation(
                                            PwmError.ERROR_SECURITY_VIOLATION, errorMsg);
                                    throw new PwmUnrecoverableException(errorInformation);
                                }
                            }
                        }
                    }
                }
            }
        }

        // check permitted source IP address
        {
            final List<String> requiredHeaders = pwmApplication.getConfig()
                    .readSettingAsStringArray(PwmSetting.IP_PERMITTED_RANGE);
            if (requiredHeaders != null && !requiredHeaders.isEmpty()) {
                boolean match = false;
                final String requestAddress = req.getRemoteAddr();
                for (int i = 0; i < requiredHeaders.size() && !match; i++) {
                    String ipMatchString = requiredHeaders.get(i);
                    try {
                        final IPMatcher ipMatcher = new IPMatcher(ipMatchString);
                        try {
                            if (ipMatcher.match(requestAddress)) {
                                match = true;
                            }
                        } catch (IPMatcher.IPMatcherException e) {
                            LOGGER.error("error while attempting to match permitted address range '" + ipMatchString
                                    + "', error: " + e);
                        }
                    } catch (IPMatcher.IPMatcherException e) {
                        LOGGER.error("error parsing permitted address range '" + ipMatchString + "', error: " + e);
                    }
                }
                if (!match) {
                    final String errorMsg = "request network address '" + req.getRemoteAddr()
                            + "' does not match any configured permitted source address";
                    final ErrorInformation errorInformation = new ErrorInformation(
                            PwmError.ERROR_SECURITY_VIOLATION, errorMsg);
                    throw new PwmUnrecoverableException(errorInformation);
                }
            }
        }

        // check trial
        if (PwmConstants.TRIAL_MODE) {
            final String currentAuthString = pwmApplication.getStatisticsManager()
                    .getStatBundleForKey(StatisticsManager.KEY_CURRENT).getStatistic(Statistic.AUTHENTICATIONS);
            if (new BigInteger(currentAuthString)
                    .compareTo(BigInteger.valueOf(PwmConstants.TRIAL_MAX_AUTHENTICATIONS)) > 0) {
                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TRIAL_VIOLATION,
                        "maximum usage per server startup exceeded"));
            }

            final String totalAuthString = pwmApplication.getStatisticsManager()
                    .getStatBundleForKey(StatisticsManager.KEY_CUMULATIVE).getStatistic(Statistic.AUTHENTICATIONS);
            if (new BigInteger(totalAuthString)
                    .compareTo(BigInteger.valueOf(PwmConstants.TRIAL_MAX_TOTAL_AUTH)) > 0) {
                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TRIAL_VIOLATION,
                        "maximum usage for this server has been exceeded"));
            }
        }

        // check intruder
        pwmApplication.getIntruderManager().convenience().checkAddressAndSession(pwmSession);
    }

    public static String appendAndEncodeUrlParameters(final String inputUrl, final Map<String, String> parameters) {
        final StringBuilder output = new StringBuilder();
        output.append(inputUrl == null ? "" : inputUrl);

        if (parameters != null) {
            for (final String key : parameters.keySet()) {
                final String value = parameters.get(key);
                final String encodedValue = StringUtil.urlEncode(value);

                output.append(output.toString().contains("?") ? "&" : "?");
                output.append(key);
                output.append("=");
                output.append(encodedValue);
            }
        }

        if (output.charAt(0) == '?' || output.charAt(0) == '&') {
            output.deleteCharAt(0);
        }

        return output.toString();
    }

}