/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 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.http; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.IOUtils; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; import password.pwm.bean.LocalSessionStateBean; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.bean.UserInfoBean; import password.pwm.config.Configuration; import password.pwm.config.FormConfiguration; import password.pwm.config.PwmSetting; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.servlet.PwmServletDefinition; import password.pwm.http.servlet.command.CommandServlet; import password.pwm.util.Validator; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmRandom; import password.pwm.ws.server.RestResultBean; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; public class PwmRequest extends PwmHttpRequestWrapper implements Serializable { // ------------------------------ FIELDS ------------------------------ private static final PwmLogger LOGGER = PwmLogger.forClass(PwmRequest.class); private static final Set<String> HTTP_PARAM_DEBUG_STRIP_VALUES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new String[] { "password", PwmConstants.PARAM_TOKEN, PwmConstants.PARAM_RESPONSE_PREFIX, }))); private static final Set<String> HTTP_HEADER_DEBUG_STRIP_VALUES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new String[] { HttpHeader.Authorization.getHttpName(), }))); private final PwmResponse pwmResponse; private transient PwmApplication pwmApplication; private transient PwmSession pwmSession; private final Set<PwmRequestFlag> flags = new HashSet<>(); public static PwmRequest forRequest( final HttpServletRequest request, final HttpServletResponse response ) throws PwmUnrecoverableException { PwmRequest pwmRequest = (PwmRequest) request.getAttribute(PwmRequestAttribute.PwmRequest.toString()); if (pwmRequest == null) { final PwmSession pwmSession = PwmSessionWrapper.readPwmSession(request); final PwmApplication pwmApplication = ContextManager.getPwmApplication(request); pwmRequest = new PwmRequest(request, response, pwmApplication, pwmSession); request.setAttribute(PwmRequestAttribute.PwmRequest.toString(), pwmRequest); } return pwmRequest; } private PwmRequest( final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final PwmApplication pwmApplication, final PwmSession pwmSession ) throws PwmUnrecoverableException { super(httpServletRequest, pwmApplication.getConfig()); this.pwmResponse = new PwmResponse(httpServletResponse, this, pwmApplication.getConfig()); this.pwmSession = pwmSession; this.pwmApplication = pwmApplication; } public PwmApplication getPwmApplication() { return pwmApplication; } public PwmSession getPwmSession() { return pwmSession; } public SessionLabel getSessionLabel() { return pwmSession.getLabel(); } public PwmResponse getPwmResponse() { return pwmResponse; } public Locale getLocale() { if (isFlag(PwmRequestFlag.INCLUDE_CONFIG_CSS)) { return PwmConstants.DEFAULT_LOCALE; } return pwmSession.getSessionStateBean().getLocale(); } public Configuration getConfig() { return pwmApplication.getConfig(); } public void forwardToJsp(final JspUrl jspURL) throws ServletException, IOException, PwmUnrecoverableException { this.getPwmResponse().forwardToJsp(jspURL); } public void respondWithError(final ErrorInformation errorInformation) throws IOException, ServletException { respondWithError(errorInformation, true); } public void respondWithError( final ErrorInformation errorInformation, final boolean forceLogout ) throws IOException, ServletException { if (forceLogout) { getPwmResponse().respondWithError(errorInformation, PwmResponse.Flag.ForceLogout); } else { getPwmResponse().respondWithError(errorInformation); } } public void sendRedirect(final String redirectURL) throws PwmUnrecoverableException, IOException { getPwmResponse().sendRedirect(redirectURL); } public void sendRedirect(final PwmServletDefinition pwmServletDefinition) throws PwmUnrecoverableException, IOException { getPwmResponse().sendRedirect(this.getContextPath() + pwmServletDefinition.servletUrl()); } public void sendRedirectToContinue() throws PwmUnrecoverableException, IOException { String redirectURL = this.getContextPath() + PwmServletDefinition.PublicCommand.servletUrl(); redirectURL = PwmURL.appendAndEncodeUrlParameters(redirectURL, Collections.singletonMap(PwmConstants.PARAM_ACTION_REQUEST, CommandServlet.CommandAction.next.toString())); sendRedirect(redirectURL); } public void outputJsonResult(final RestResultBean restResultBean) throws IOException { this.getPwmResponse().outputJsonResult(restResultBean); } public ContextManager getContextManager() throws PwmUnrecoverableException { return ContextManager.getContextManager(this); } public InputStream readFileUploadStream(final String filePartName) throws IOException, ServletException, PwmUnrecoverableException { try { if (ServletFileUpload.isMultipartContent(this.getHttpServletRequest())) { // Create a new file upload handler final ServletFileUpload upload = new ServletFileUpload(); // Parse the request for (final FileItemIterator iter = upload.getItemIterator(this.getHttpServletRequest()); 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; } public Map<String,FileUploadItem> readFileUploads( final int maxFileSize, final int maxItems ) throws IOException, ServletException, PwmUnrecoverableException { final Map<String,FileUploadItem> returnObj = new LinkedHashMap<>(); try { if (ServletFileUpload.isMultipartContent(this.getHttpServletRequest())) { final ServletFileUpload upload = new ServletFileUpload(); final FileItemIterator iter = upload.getItemIterator(this.getHttpServletRequest()); while (iter.hasNext() && returnObj.size() < maxItems) { final FileItemStream item = iter.next(); final InputStream inputStream = item.openStream(); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final long length = IOUtils.copyLarge(inputStream, baos, 0, maxFileSize + 1); if (length > maxFileSize) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"upload file size limit exceeded"); LOGGER.error(this, errorInformation); respondWithError(errorInformation); return Collections.emptyMap(); } final byte[] outputFile = baos.toByteArray(); final FileUploadItem fileUploadItem = new FileUploadItem( item.getName(), item.getContentType(), outputFile ); returnObj.put(item.getFieldName(),fileUploadItem); } } } catch (Exception e) { LOGGER.error("error reading file upload: " + e.getMessage()); } return Collections.unmodifiableMap(returnObj); } public static class FileUploadItem { private final String name; private final String type; private final byte[] content; public FileUploadItem( final String name, final String type, final byte[] content ) { this.name = name; this.type = type; this.content = content; } public String getName() { return name; } public String getType() { return type; } public byte[] getContent() { return content; } } public UserIdentity getUserInfoIfLoggedIn() { return this.getPwmSession().isAuthenticated() ? this.getPwmSession().getUserInfoBean().getUserIdentity() : null; } public void validatePwmFormID() throws PwmUnrecoverableException { Validator.validatePwmFormID(this); } public boolean convertURLtokenCommand() throws IOException, PwmUnrecoverableException { final String uri = getURLwithoutQueryString(); if (uri == null || uri.length() < 1) { return false; } final String servletPath = this.getHttpServletRequest().getServletPath(); if (!uri.contains(servletPath)) { LOGGER.error("unexpected uri handler, uri '" + uri + "' does not contain servlet path '" + servletPath + "'"); return false; } String aftPath = uri.substring(uri.indexOf(servletPath) + servletPath.length(),uri.length()); if (aftPath.startsWith("/")) { aftPath = aftPath.substring(1,aftPath.length()); } if (aftPath.contains("?")) { aftPath = aftPath.substring(0,aftPath.indexOf("?")); } if (aftPath.contains("&")) { aftPath = aftPath.substring(0,aftPath.indexOf("?")); } if (aftPath.length() <= 1) { return false; } final String tokenValue = aftPath; // note this value is still urlencoded - the servlet container does not decode path values. final StringBuilder redirectURL = new StringBuilder(); redirectURL.append(this.getHttpServletRequest().getContextPath()); redirectURL.append(this.getHttpServletRequest().getServletPath()); redirectURL.append("?"); redirectURL.append(PwmConstants.PARAM_ACTION_REQUEST).append("=enterCode"); redirectURL.append("&"); redirectURL.append(PwmConstants.PARAM_TOKEN).append("=").append(tokenValue); LOGGER.debug(pwmSession, "detected long servlet url, redirecting user to " + redirectURL); sendRedirect(redirectURL.toString()); return true; } public void setAttribute(final PwmRequestAttribute name, final Serializable value) { this.getHttpServletRequest().setAttribute(name.toString(),value); } public Serializable getAttribute(final PwmRequestAttribute name) { return (Serializable)this.getHttpServletRequest().getAttribute(name.toString()); } public PwmURL getURL() { return new PwmURL(this.getHttpServletRequest()); } public void debugHttpRequestToLog() throws PwmUnrecoverableException { debugHttpRequestToLog(null); } public void debugHttpRequestToLog(final String extraText) throws PwmUnrecoverableException { final StringBuilder sb = new StringBuilder(); final HttpServletRequest req = this.getHttpServletRequest(); sb.append(req.getMethod()); sb.append(" request for: "); sb.append(getURLwithoutQueryString()); if (req.getParameterMap().isEmpty()) { sb.append(" (no params)"); if (extraText != null) { sb.append(" "); sb.append(extraText); } } else { if (extraText != null) { sb.append(" "); sb.append(extraText); } sb.append("\n"); sb.append(debugOutputMapToString(this.readMultiParametersAsMap(), HTTP_PARAM_DEBUG_STRIP_VALUES)); } LOGGER.trace(this.getSessionLabel(), sb.toString()); } public boolean isAuthenticated() { return pwmSession.isAuthenticated(); } public boolean isForcedPageView() { if (!isAuthenticated()) { return false; } final PwmURL pwmURL = getURL(); final UserInfoBean userInfoBean = pwmSession.getUserInfoBean(); if (userInfoBean.isRequiresNewPassword() && pwmURL.isChangePasswordURL()) { return true; } if (userInfoBean.isRequiresResponseConfig() && pwmURL.isSetupResponsesURL()) { return true; } if (userInfoBean.isRequiresOtpConfig() && pwmURL.isSetupOtpSecretURL()) { return true; } if (userInfoBean.isRequiresUpdateProfile() && pwmURL.isProfileUpdateURL()) { return true; } return false; } public void setFlag(final PwmRequestFlag flag, final boolean status) { if (status) { flags.add(flag); } else { flags.remove(flag); } } public boolean isFlag(final PwmRequestFlag flag) { return flags.contains(flag); } public boolean hasForwardUrl() { final LocalSessionStateBean ssBean = this.getPwmSession().getSessionStateBean(); final String redirectURL = ssBean.getForwardURL(); return !((redirectURL == null || redirectURL.isEmpty()) && this.getConfig().isDefaultValue(PwmSetting.URL_FORWARD)); } public String getForwardUrl() { final LocalSessionStateBean ssBean = this.getPwmSession().getSessionStateBean(); String redirectURL = ssBean.getForwardURL(); if (redirectURL == null || redirectURL.length() < 1) { redirectURL = this.getConfig().readSettingAsString(PwmSetting.URL_FORWARD); } if (redirectURL == null || redirectURL.length() < 1) { redirectURL = this.getContextPath(); } return redirectURL; } public String getLogoutURL( ) { final LocalSessionStateBean ssBean = this.getPwmSession().getSessionStateBean(); return ssBean.getLogoutURL() == null ? pwmApplication.getConfig().readSettingAsString(PwmSetting.URL_LOGOUT) : ssBean.getLogoutURL(); } public synchronized String getCspNonce() { if (getAttribute(PwmRequestAttribute.CspNonce) == null) { final int nonceLength = Integer.parseInt(getConfig().readAppProperty(AppProperty.HTTP_HEADER_CSP_NONCE_BYTES)); final byte[] cspNonce = PwmRandom.getInstance().newBytes(nonceLength); final String cspString = StringUtil.base64Encode(cspNonce); setAttribute(PwmRequestAttribute.CspNonce, cspString); } return (String)getAttribute(PwmRequestAttribute.CspNonce); } public <T extends Serializable> T readEncryptedCookie(final String cookieName, final Class<T> returnClass) throws PwmUnrecoverableException { final String strValue = this.readCookie(cookieName); if (strValue != null && !strValue.isEmpty()) { return pwmApplication.getSecureService().decryptObject(strValue, returnClass); } return null; } public String toString() { return this.getClass().getSimpleName() + " " + (this.getSessionLabel() == null ? "" : getSessionLabel().toString()) + " " + getURLwithoutQueryString(); } public void addFormInfoToRequestAttr( final PwmSetting formSetting, final boolean readOnly, final boolean showPasswordFields ) { final ArrayList<FormConfiguration> formConfiguration = new ArrayList<>(this.getConfig().readSettingAsForm(formSetting)); addFormInfoToRequestAttr(formConfiguration, null, readOnly, showPasswordFields); } public void addFormInfoToRequestAttr( final List<FormConfiguration> formConfiguration, final Map<FormConfiguration, String> formDataMap, final boolean readOnly, final boolean showPasswordFields ) { final LinkedHashMap<FormConfiguration,String> formDataMapValue = formDataMap == null ? new LinkedHashMap<FormConfiguration,String>() : new LinkedHashMap<>(formDataMap); this.setAttribute(PwmRequestAttribute.FormConfiguration, new ArrayList<>(formConfiguration)); this.setAttribute(PwmRequestAttribute.FormData, formDataMapValue); this.setAttribute(PwmRequestAttribute.FormReadOnly, readOnly); this.setAttribute(PwmRequestAttribute.FormShowPasswordFields, showPasswordFields); } public void invalidateSession() { this.getPwmSession().unauthenticateUser(this); this.getHttpServletRequest().getSession().invalidate(); } public String getURLwithQueryString() throws PwmUnrecoverableException { final HttpServletRequest req = this.getHttpServletRequest(); return PwmURL.appendAndEncodeUrlParameters(getURLwithoutQueryString(), readParametersAsMap()); } public String getURLwithoutQueryString() { final HttpServletRequest req = this.getHttpServletRequest(); final String requestUri = (String) req.getAttribute("javax.servlet.forward.request_uri"); return (requestUri == null) ? req.getRequestURI() : requestUri; } public String debugHttpHeaders() { final String LINE_SEPERATOR = "\n"; final StringBuilder sb = new StringBuilder(); sb.append("http").append(getHttpServletRequest().isSecure() ? "s " : " non-").append("secure request headers: "); sb.append(LINE_SEPERATOR); sb.append(debugOutputMapToString(readHeaderValuesMap(), HTTP_HEADER_DEBUG_STRIP_VALUES)); return sb.toString(); } public boolean endUserFunctionalityAvailable() { final PwmApplicationMode mode = pwmApplication.getApplicationMode(); if (mode == PwmApplicationMode.NEW) { return false; } if (PwmConstants.TRIAL_MODE) { return true; } if (mode == PwmApplicationMode.RUNNING) { return true; } return false; } private static String debugOutputMapToString( final Map<String,List<String>> input, final Collection<String> stripValues ) { final String LINE_SEPARATOR = "\n"; final StringBuilder sb = new StringBuilder(); for (final String paramName : input.keySet()) { for (final String paramValue : input.get(paramName)) { sb.append(" ").append(paramName).append("="); boolean strip = false; for (final String stripValue : stripValues) { if (paramName.toLowerCase().contains(stripValue.toLowerCase())) { strip = true; } } if (strip) { sb.append(PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT); } else { sb.append("'"); sb.append(paramValue); sb.append("'"); } sb.append(LINE_SEPARATOR); } } if (LINE_SEPARATOR.equals(sb.substring(sb.length() - LINE_SEPARATOR.length(), sb.length()))) { sb.delete(sb.length() - LINE_SEPARATOR.length(), sb.length()); } return sb.toString(); } }