/* * 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.filter; import eu.bitwalker.useragentutils.Browser; import eu.bitwalker.useragentutils.UserAgent; import password.pwm.AppProperty; import password.pwm.Permission; import password.pwm.PwmApplication; import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; import password.pwm.config.stored.StoredConfigurationImpl; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.ContextManager; import password.pwm.http.HttpHeader; import password.pwm.http.JspUrl; import password.pwm.http.ProcessStatus; import password.pwm.http.PwmRequest; import password.pwm.http.PwmRequestAttribute; import password.pwm.http.PwmSession; import password.pwm.http.PwmURL; import password.pwm.http.bean.ConfigManagerBean; import password.pwm.svc.intruder.RecordType; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmHashAlgorithm; import password.pwm.util.secure.PwmSecurityKey; import password.pwm.util.secure.SecureEngine; import javax.servlet.ServletException; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; public class ConfigAccessFilter extends AbstractPwmFilter { private static final PwmLogger LOGGER = PwmLogger.forClass(ConfigAccessFilter.class); @Override void processFilter(final PwmApplicationMode mode, final PwmRequest pwmRequest, final PwmFilterChain filterChain) throws PwmException, IOException, ServletException { final PwmApplicationMode appMode = pwmRequest.getPwmApplication().getApplicationMode(); if (appMode == PwmApplicationMode.NEW) { filterChain.doFilter(); return; } try { checkUserAgent(pwmRequest); } catch (PwmException e) { pwmRequest.respondWithError(e.getErrorInformation()); return; } final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, ConfigManagerBean.class); if (checkAuthentication(pwmRequest, configManagerBean) == ProcessStatus.Continue) { filterChain.doFilter(); } } @Override boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) { return pwmURL.isConfigManagerURL(); } static ProcessStatus checkAuthentication( final PwmRequest pwmRequest, final ConfigManagerBean configManagerBean ) throws IOException, PwmUnrecoverableException, ServletException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final ConfigurationReader runningConfigReader = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession()).getConfigReader(); final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration(); boolean authRequired = false; if (storedConfig.hasPassword()) { authRequired = true; } if (PwmApplicationMode.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) { if (!pwmRequest.isAuthenticated()) { throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED); } if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED); denyAndError(pwmRequest, errorInformation); return ProcessStatus.Halt; } } if (PwmApplicationMode.CONFIGURATION != pwmRequest.getPwmApplication().getApplicationMode()) { authRequired = true; } if (!authRequired) { return ProcessStatus.Continue; } if (!storedConfig.hasPassword()) { final String errorMsg = "config file does not have a configuration password"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg,new String[]{errorMsg}); return denyAndError(pwmRequest, errorInformation); } if (configManagerBean.isPasswordVerified()) { return ProcessStatus.Continue; } String persistentLoginValue = null; boolean persistentLoginAccepted = false; boolean persistentLoginEnabled = false; if (pwmRequest.getConfig().isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) { LOGGER.debug(pwmRequest, "security not available, persistent login not possible."); } else { persistentLoginEnabled = true; final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey(); if (PwmApplicationMode.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) { persistentLoginValue = SecureEngine.hash( storedConfig.readConfigProperty(ConfigurationProperty.PASSWORD_HASH) + pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey(), PwmHashAlgorithm.SHA512); } else { persistentLoginValue = SecureEngine.hash( storedConfig.readConfigProperty(ConfigurationProperty.PASSWORD_HASH), PwmHashAlgorithm.SHA512); } { final String cookieStr = pwmRequest.readCookie(PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN); if (securityKey != null && cookieStr != null && !cookieStr.isEmpty()) { try { final String jsonStr = pwmApplication.getSecureService().decryptStringValue(cookieStr); final PersistentLoginInfo persistentLoginInfo = JsonUtil.deserialize(jsonStr, PersistentLoginInfo.class); if (persistentLoginInfo != null && persistentLoginValue != null) { if (persistentLoginInfo.getExpireDate().after(new Date())) { if (persistentLoginValue.equals(persistentLoginInfo.getPassword())) { persistentLoginAccepted = true; LOGGER.debug(pwmRequest, "accepting persistent config login from cookie (expires " + JavaHelper.toIsoDate(persistentLoginInfo.getExpireDate()) + ")" ); } } } } catch (Exception e) { LOGGER.error(pwmRequest, "error examining persistent config login cookie: " + e.getMessage()); } if (!persistentLoginAccepted) { pwmRequest.getPwmResponse().removeCookie(PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, null); LOGGER.debug(pwmRequest, "removing non-working persistent config login cookie"); } } } } final String password = pwmRequest.readParameterAsString("password"); boolean passwordAccepted = false; if (!persistentLoginAccepted) { if (password != null && password.length() > 0) { if (storedConfig.verifyPassword(password, pwmRequest.getConfig())) { passwordAccepted = true; LOGGER.trace(pwmRequest, "valid configuration password accepted"); updateLoginHistory(pwmRequest,pwmRequest.getUserInfoIfLoggedIn(), true); } else{ LOGGER.trace(pwmRequest, "configuration password is not correct"); pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession); pwmApplication.getIntruderManager().mark(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel()); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_PASSWORD_ONLY_BAD); updateLoginHistory(pwmRequest,pwmRequest.getUserInfoIfLoggedIn(), false); return denyAndError(pwmRequest, errorInformation); } } } if ((persistentLoginAccepted || passwordAccepted)) { configManagerBean.setPasswordVerified(true); pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession); pwmApplication.getIntruderManager().clear(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME); if (persistentLoginEnabled && !persistentLoginAccepted && "on".equals(pwmRequest.readParameterAsString("remember"))) { final int persistentSeconds = figureMaxLoginSeconds(pwmRequest); if (persistentSeconds > 0) { final Date expirationDate = new Date(System.currentTimeMillis() + (persistentSeconds * 1000)); final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo(expirationDate, persistentLoginValue); final String jsonPersistentLoginInfo = JsonUtil.serialize(persistentLoginInfo); final String cookieValue = pwmApplication.getSecureService().encryptToString(jsonPersistentLoginInfo); pwmRequest.getPwmResponse().writeCookie( PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, cookieValue, persistentSeconds ); LOGGER.debug(pwmRequest, "set persistent config login cookie (expires " + JavaHelper.toIsoDate(expirationDate) + ")" ); } } if (configManagerBean.getPrePasswordEntryUrl() != null) { final String originalUrl = configManagerBean.getPrePasswordEntryUrl(); configManagerBean.setPrePasswordEntryUrl(null); pwmRequest.getPwmResponse().sendRedirect(originalUrl); return ProcessStatus.Halt; } return ProcessStatus.Continue; } if (configManagerBean.getPrePasswordEntryUrl() == null) { configManagerBean.setPrePasswordEntryUrl(pwmRequest.getHttpServletRequest().getRequestURL().toString()); } forwardToJsp(pwmRequest); return ProcessStatus.Halt; } private static void forwardToJsp(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException { final int persistentSeconds = figureMaxLoginSeconds(pwmRequest); final String time = new TimeDuration(persistentSeconds * 1000).asLongString(pwmRequest.getLocale()); final ConfigLoginHistory configLoginHistory = readConfigLoginHistory(pwmRequest); pwmRequest.setAttribute(PwmRequestAttribute.ConfigLoginHistory, configLoginHistory); pwmRequest.setAttribute(PwmRequestAttribute.ConfigPasswordRememberTime,time); pwmRequest.forwardToJsp(JspUrl.CONFIG_MANAGER_LOGIN); } private static ConfigLoginHistory readConfigLoginHistory(final PwmRequest pwmRequest) { final ConfigLoginHistory configLoginHistory = pwmRequest.getPwmApplication().readAppAttribute(PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class); return configLoginHistory == null ? new ConfigLoginHistory() : configLoginHistory; } private static void updateLoginHistory(final PwmRequest pwmRequest, final UserIdentity userIdentity, final boolean successful) { final ConfigLoginHistory configLoginHistory = readConfigLoginHistory(pwmRequest); final ConfigLoginEvent event = new ConfigLoginEvent( userIdentity == null ? "n/a" : userIdentity.toDisplayString(), new Date(), pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress() ); final int maxEvents = Integer.parseInt(pwmRequest.getPwmApplication().getConfig().readAppProperty(AppProperty.CONFIG_HISTORY_MAX_ITEMS)); configLoginHistory.addEvent(event, maxEvents, successful); pwmRequest.getPwmApplication().writeAppAttribute(PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, configLoginHistory); } private static class PersistentLoginInfo implements Serializable { private Date expireDate; private String password; private PersistentLoginInfo( final Date expireDate, final String password ) { this.expireDate = expireDate; this.password = password; } public Date getExpireDate() { return expireDate; } public String getPassword() { return password; } } public static class ConfigLoginHistory implements Serializable { private final List<ConfigLoginEvent> successEvents = new ArrayList<>(); private final List<ConfigLoginEvent> failedEvents = new ArrayList<>(); void addEvent(final ConfigLoginEvent event, final int maxEvents, final boolean successful) { final List<ConfigLoginEvent> events = successful ? successEvents : failedEvents; events.add(event); if (maxEvents > 0) { while (events.size() > maxEvents) { events.remove(0); } } } public List<ConfigLoginEvent> successEvents() { return Collections.unmodifiableList(successEvents); } public List<ConfigLoginEvent> failedEvents() { return Collections.unmodifiableList(failedEvents); } } public static class ConfigLoginEvent implements Serializable { private final String userIdentity; private final Date date; private final String networkAddress; public ConfigLoginEvent(final String userIdentity, final Date date, final String networkAddress) { this.userIdentity = userIdentity; this.date = date; this.networkAddress = networkAddress; } public String getUserIdentity() { return userIdentity; } public Date getDate() { return date; } public String getNetworkAddress() { return networkAddress; } } static int figureMaxLoginSeconds(final PwmRequest pwmRequest) { return Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS)); } private void checkUserAgent(final PwmRequest pwmRequest) throws PwmUnrecoverableException { final String userAgentString = pwmRequest.readHeaderValueAsString(HttpHeader.UserAgent); if (userAgentString == null || userAgentString.isEmpty()) { return; } boolean badBrowser = false; try { final UserAgent userAgent = new UserAgent(userAgentString); final Browser browser = userAgent.getBrowser(); switch (browser) { case IE5: case IE5_5: case IE6: case IE7: case IE8: case IE9: case IE10: badBrowser = true; break; default: //other browsers okay break; } } catch (Exception e) { LOGGER.error(pwmRequest, "error during browser user-agent detection: " + e.getMessage()); } if (badBrowser) { final String errorMsg = "Internet Explorer version is not supported for this function. Please use Internet Explorer 11 or higher or another web browser."; throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg)); } } private static ProcessStatus denyAndError(final PwmRequest pwmRequest, final ErrorInformation errorInformation) throws ServletException, PwmUnrecoverableException, IOException { pwmRequest.setAttribute(PwmRequestAttribute.PwmErrorInfo, errorInformation); forwardToJsp(pwmRequest); return ProcessStatus.Halt; } }