Java tutorial
/* * SSL-Explorer * * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.sslexplorer.security; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.util.MessageResources; import com.sslexplorer.boot.ContextHolder; import com.sslexplorer.boot.DefaultPropertyDefinition; import com.sslexplorer.boot.HostService; import com.sslexplorer.boot.HttpConstants; import com.sslexplorer.boot.PropertyClassManager; import com.sslexplorer.boot.RequestHandlerRequest; import com.sslexplorer.boot.RequestHandlerResponse; import com.sslexplorer.boot.SystemProperties; import com.sslexplorer.boot.Util; import com.sslexplorer.core.CoreAttributeConstants; import com.sslexplorer.core.CoreEvent; import com.sslexplorer.core.CoreEventConstants; import com.sslexplorer.core.CoreServlet; import com.sslexplorer.core.CoreUtil; import com.sslexplorer.core.GlobalWarning; import com.sslexplorer.core.PageInterceptException; import com.sslexplorer.core.PageInterceptListener; import com.sslexplorer.core.ServletRequestAdapter; import com.sslexplorer.core.ServletResponseAdapter; import com.sslexplorer.core.UserDatabaseManager; import com.sslexplorer.policyframework.PolicyDatabaseFactory; import com.sslexplorer.policyframework.PolicyUtil; import com.sslexplorer.policyframework.ResourceUtil; import com.sslexplorer.properties.ProfilesFactory; import com.sslexplorer.properties.ProfilesListDataSource; import com.sslexplorer.properties.Property; import com.sslexplorer.properties.PropertyProfile; import com.sslexplorer.properties.impl.realms.RealmKey; import com.sslexplorer.properties.impl.systemconfig.SystemConfigKey; import com.sslexplorer.properties.impl.systemconfig.SystemConfiguration; import com.sslexplorer.properties.impl.userattributes.UserAttributeKey; import com.sslexplorer.realms.Realm; import com.sslexplorer.security.actions.PromptForPrivateKeyPassphraseDispatchAction; import com.sslexplorer.security.actions.UpdatePrivateKeyPassphraseDispatchAction; import com.sslexplorer.setup.SystemInformationProvider; import com.sslexplorer.setup.SystemInformationRegistry; import com.sslexplorer.util.TicketGenerator; /** * This class is the default implementation of the * {@link com.sslexplorer.security.LogonController} and maintains and validates * all logons to SSL-Explorer whether the be through the web based user * interface or other sub-systems such as the <i>Embedded Client</i>. */ public class DefaultLogonController implements LogonController { protected static Log log = LogFactory.getLog(DefaultLogonController.class); private Map<String, SessionInfo> logons = new HashMap<String, SessionInfo>(); Map<String, SessionInfo> logonsBySessionId = new HashMap<String, SessionInfo>(); int sessionTimeoutBlockId; HashMap lockedUsers = new HashMap(); List authenticationModules; HashMap authorizedTickets = new HashMap(); /** * Constructor. */ public DefaultLogonController() { lockedUsers = new HashMap(); PropertyClassManager.getInstance().getPropertyClass(SystemConfiguration.NAME) .registerPropertyDefinition(new DefaultPropertyDefinition(DefaultPropertyDefinition.TYPE_INTEGER, "security.maxUserCount", "", 99999, "0", 2, true)); SystemInformationRegistry.getInstance().registerProvider(new MostUsersOnline()); SystemInformationRegistry.getInstance().registerProvider(new CurrentUsersOnline()); } /* * (non-Javadoc) * * @see com.sslexplorer.security.LogonController#init() */ public void init() { new HorribleHackReaperThread(); } /* * (non-Javadoc) * * @see com.sslexplorer.security.LogonController#isAdministrator(com.sslexplorer.policyframework.Principal) */ public boolean isAdministrator(User principal) { // In setup mode everyone is an administrator if (ContextHolder.getContext().isSetupMode()) { return true; } try { // Now check the default administrators if (principal == null) { log.error("NULL principal object passed to isAdministrator!"); return false; } if (principal.getPrincipalName() == null) { log.error("NULL principal name in principal object passed to isAdministrator!"); return false; } List administrators = Property .getPropertyList(new RealmKey("security.administrators", principal.getRealm().getRealmID())); for (Iterator it = administrators.iterator(); it.hasNext();) { if (principal.getPrincipalName().equals((String) it.next())) return true; } } catch (Exception e) { log.error("Failed to determine administrator status.", e); } return false; } /* * (non-Javadoc) * * @see com.sslexplorer.security.LogonController#addSessionTimeoutBlock(javax.servlet.http.HttpSession, * java.lang.String) */ public synchronized int addSessionTimeoutBlock(HttpSession session, String reason) { Map sessionTimeoutBlocks = (Map) session.getAttribute(Constants.SESSION_TIMEOUT_BLOCKS); if (sessionTimeoutBlocks == null) { sessionTimeoutBlocks = new HashMap(); session.setAttribute(Constants.SESSION_TIMEOUT_BLOCKS, sessionTimeoutBlocks); } sessionTimeoutBlocks.put(String.valueOf(++sessionTimeoutBlockId), reason); if (log.isDebugEnabled()) log.debug("Preventing session timeout on session " + session.getId() + " (id of " + sessionTimeoutBlockId + ") because '" + reason + "'. There are now " + sessionTimeoutBlocks.size() + " reasons not to timeout the session."); session.setMaxInactiveInterval(-1); return sessionTimeoutBlockId; } /* * (non-Javadoc) * * @see com.sslexplorer.security.LogonController#removeSessionTimeoutBlock(javax.servlet.http.HttpSession, * int) */ public synchronized void removeSessionTimeoutBlock(HttpSession session, int sessionTimeoutBlockId) { try { Map sessionTimeoutBlocks = (Map) session.getAttribute(Constants.SESSION_TIMEOUT_BLOCKS); if (sessionTimeoutBlocks != null) { String reason = (String) sessionTimeoutBlocks.get(String.valueOf(sessionTimeoutBlockId)); if (reason == null) { log.warn("No session timeout block with id of " + sessionTimeoutBlockId); } else { sessionTimeoutBlocks.remove(String.valueOf(sessionTimeoutBlockId)); if (log.isDebugEnabled()) log.debug("Removing session timeout block " + sessionTimeoutBlockId + " for session " + session.getId() + " ('" + reason + "'). There are now " + sessionTimeoutBlocks.size() + " reasons not to timeout the session."); } if (sessionTimeoutBlocks.size() == 0) { session.removeAttribute(Constants.SESSION_TIMEOUT_BLOCKS); User user = (User) session.getAttribute(Constants.USER); int minutes = CoreUtil.getUsersProfilePropertyIntOrDefault(session, "webServer.sessionInactivityTimeout", user); if (log.isDebugEnabled()) log.debug("Initialising timeout for session " + session.getId() + " to " + minutes + " minutes"); session.setMaxInactiveInterval(minutes == 0 ? -1 : minutes * 60); } } } catch (IllegalStateException ise) { log.warn("Couldnt remove session timeout block.", ise); } } public void logoffSession(HttpServletRequest request, HttpServletResponse response) throws SecurityErrorException { if (log.isInfoEnabled()) log.info("Logging off session " + request.getSession().getId()); if (request.getSession().getAttribute(Constants.LOGON_TICKET) == null) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, "The current session does not contain a logon ticket"); } else { String ticket = (String) request.getSession().getAttribute(Constants.LOGON_TICKET); SessionInfo session = getSessionInfo(ticket); logoff(ticket); if (request.getCookies() != null) { for (int i = 0; i < request.getCookies().length; i++) { Cookie cookie = request.getCookies()[i]; if (cookie.getName().equals(Constants.LOGON_TICKET) || cookie.getName().equals(Constants.DOMAIN_LOGON_TICKET)) { cookie.setMaxAge(0); response.addCookie(cookie); } } } request.getSession().removeAttribute(Constants.LOGON_TICKET); session.invalidate(); } } public List<SessionInfo> getSessionInfo(String username, int sessionType) { List<SessionInfo> info = null; for (Map.Entry<String, SessionInfo> entry : logons.entrySet()) { SessionInfo inf = (SessionInfo) entry.getValue(); if (inf.getUser().getPrincipalName().equals(username) && (sessionType == -1 || (sessionType != -1 && sessionType == inf.getType()))) { if (info == null) { info = new ArrayList<SessionInfo>(); } info.add(inf); } } return info; } public int getUserStatus(User user) throws Exception { if (user != null && lockedUsers.containsKey(user.getPrincipalName()) && ((AccountLock) lockedUsers.get(user.getPrincipalName())).getLocks() > 0) { return ACCOUNT_LOCKED; } else if (getSessionInfo(user.getPrincipalName(), -1) != null) { return ACCOUNT_ACTIVE; } else { if (user == null) { return ACCOUNT_UNKNOWN; } else { boolean disabled = !PolicyUtil.isEnabled(user); // if (!admin && disabled) { if (disabled) { return ACCOUNT_DISABLED; } else { // boolean admin = isAdministrator(user, true, false, true); boolean logonAuthorized = PolicyUtil.canLogin(user); // if (!admin && !logonAuthorized) { if (!logonAuthorized) { return ACCOUNT_REVOKED; } else { return ACCOUNT_GRANTED; } } } } } private void checkForMultipleSessions(User user, InetAddress address, int sessionType) throws UserDatabaseException { int type = Property .getPropertyInt(new RealmKey("security.multipleSessions", user.getRealm().getResourceId())); List activeSessions; switch (type) { case 0: break; // No restrction case 1: activeSessions = getSessionInfo(user.getPrincipalName(), sessionType); if (activeSessions != null) { throw new UserDatabaseException( "You are already logged on, and this systems policy is to only allow one session per user."); } break; case 2: activeSessions = getSessionInfo(user.getPrincipalName(), sessionType); if (activeSessions != null) { for (Iterator i = activeSessions.iterator(); i.hasNext();) { SessionInfo info = (SessionInfo) i.next(); if (!info.getAddress().equals(address)) { throw new UserDatabaseException( "You are already logged on at a different address, and this systems policy is to only allow one session per user / address."); } } } break; default: throw new UserDatabaseException("Unknown multiple session restrictions type " + type + "."); } } public void initialiseSession(HttpSession session, User user) throws UserDatabaseException { if (log.isInfoEnabled()) log.info("Initialising session " + session.getId() + " with user " + (user == null ? "[none]" : user.getPrincipalName())); PropertyProfile profile = (PropertyProfile) session.getAttribute(Constants.SELECTED_PROFILE); session.setAttribute(Constants.USER, user); String logonInfo = MessageResources.getMessageResources("com.sslexplorer.navigation.ApplicationResources") .getMessage("footer.info", user.getPrincipalName(), SimpleDateFormat.getDateTimeInstance().format(new Date())); session.setAttribute(Constants.LOGON_INFO, logonInfo); try { List profiles = ResourceUtil.filterResources(user, ProfilesFactory.getInstance() .getPropertyProfiles(user.getPrincipalName(), true, user.getRealm().getResourceId()), true); session.setAttribute(Constants.PROFILES, profiles); if (profiles.size() == 0) { throw new UserDatabaseException("You do not have permission to use any profiles."); } String startupProfile = Property.getProperty(new UserAttributeKey(user, User.USER_STARTUP_PROFILE)); if (profiles.size() < 2) { profile = (PropertyProfile) profiles.get(0); } else if (!startupProfile.equals(ProfilesListDataSource.SELECT_ON_LOGIN)) { int profileId = Integer.parseInt(startupProfile); profile = null; for (Iterator i = profiles.iterator(); i.hasNext();) { PropertyProfile p = (PropertyProfile) i.next(); if (profileId == p.getResourceId()) { profile = p; break; } } if (profile == null) { profile = ProfilesFactory.getInstance().getPropertyProfile(null, "Default", UserDatabaseManager.getInstance().getDefaultUserDatabase().getRealm().getResourceId()); } } if (profile != null) { if (log.isInfoEnabled()) log.info("Switching user " + user.getPrincipalName() + " to profile " + profile.getResourceName()); session.setAttribute(Constants.SELECTED_PROFILE, profile); } } catch (Exception e) { throw new UserDatabaseException("Failed to initialise profiles.", e); } final String logonTicket = (String) session.getAttribute(Constants.LOGON_TICKET); session.setAttribute(Constants.LOGOFF_HOOK, new HttpSessionBindingListener() { public void valueBound(HttpSessionBindingEvent evt) { } public void valueUnbound(HttpSessionBindingEvent evt) { if (log.isDebugEnabled()) log.debug("Session unbound"); // We should should only log off completely if no other // session has // the logon ticket SessionInfo currentTicketSessionInfo = ((SessionInfo) logons.get(logonTicket)); if (currentTicketSessionInfo == null || evt.getSession().getId().equals(currentTicketSessionInfo.getHttpSession().getId())) { if (log.isDebugEnabled()) log.debug("Session (" + evt.getSession().getId() + ") unbound is the current session for ticket " + logonTicket + " so a logoff will be performed."); logoff(logonTicket); } else { if (log.isDebugEnabled()) log.debug("Session unbound is NOT the current session, ignoring."); } } }); if (log.isDebugEnabled()) log.debug("Using profile: " + (profile == null ? "DEFAULT" : profile.getResourceName()) + ")"); session.removeAttribute(Constants.SESSION_LOCKED); resetSessionTimeout(user, profile, session); } public void resetSessionTimeout(User user, PropertyProfile profile, HttpSession session) { try { Map sessionTimeoutBlocks = (Map) session.getAttribute(Constants.SESSION_TIMEOUT_BLOCKS); int minutes = 0; if (sessionTimeoutBlocks == null || sessionTimeoutBlocks.size() == 0) { minutes = CoreUtil.getUsersProfilePropertyIntOrDefault(session, "webServer.sessionInactivityTimeout", user); } if (log.isDebugEnabled()) log.debug("Resetting timeout for session " + session.getId() + " to " + minutes + " minutes"); session.setMaxInactiveInterval(minutes == 0 ? -1 : minutes * 60); } catch (Exception e) { log.error("Failed to reset session timeout.", e); } } public AccountLock checkForAccountLock(String username, String realmName) throws SecurityErrorException, AccountLockedException { // Get the user lockout policy int maxLogonAttemptsBeforeLock = 0; int lockDuration = 0; Realm realm; try { realm = UserDatabaseManager.getInstance().getRealm(realmName); } catch (Exception e1) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, e1, "Failed to determine the realm name " + realmName + "."); } try { maxLogonAttemptsBeforeLock = Property .getPropertyInt(new RealmKey("security.maxLogonAttemptsBeforeLock", realm.getResourceId())); lockDuration = Property.getPropertyInt(new RealmKey("security.lockDuration", realm.getResourceId())); } catch (Exception e) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, e, "Failed to determine password lockout policy."); } // Get the current lock (if any) AccountLock lock = "true".equals(SystemProperties.get("sslexplorer.recoveryMode", "false")) ? null : (AccountLock) lockedUsers.get(username); // If the user is currently locked, check if the lock has expired yeet if (lock != null && maxLogonAttemptsBeforeLock > 0 && lockDuration > 0 && lock.getLockedTime() != -1) { long expires = lock.getLockedTime() + (1000 * lockDuration); long now = System.currentTimeMillis(); if (now < expires) { throw new AccountLockedException(username, "Account temporarily locked. Please try later.", false, expires - now); } // There was a lock, it is now expired lock.setAttempts(0); lock.setLockedTime(-1); } return lock; } // public User doClientLogon(String username, String password, String // realmName) throws UserDatabaseException, // InvalidLoginCredentialsException, // AccountLockedException { // // Get the user lockout policy // int maxLogonAttemptsBeforeLock = 0; // int maxLocksBeforeDisable = 0; // int lockDuration = 0; // try { // maxLogonAttemptsBeforeLock = Property.getPropertyInt(new // SystemConfigKey("security.maxLogonAttemptsBeforeLock")); // maxLocksBeforeDisable = Property.getPropertyInt(new // SystemConfigKey("security.maxLocksBeforeDisable")); // lockDuration = Property.getPropertyInt(new // SystemConfigKey("security.lockDuration")); // } catch (Exception e) { // throw new UserDatabaseException("Failed to determine password lockout // policy.", e); // } // // Get the current lock (if any) // AccountLock lock = // "true".equals(SystemProperties.get("sslexplorer.recoveryMode", "false")) ? // null // : (AccountLock) lockedUsers.get(username); // // If the user is currently locked, check if the lock has expired yeet // if (lock != null && maxLogonAttemptsBeforeLock > 0 && lockDuration > 0 && // lock.getLockedTime() != -1) { // long expires = lock.getLockedTime() + (1000 * lockDuration); // long now = System.currentTimeMillis(); // if (now < expires) { // throw new AccountLockedException("Account temporarily locked. Please try // later.", false, expires - now); // } // // There was a lock, it is now expired // lock.setAttempts(0); // lock.setLockedTime(-1); // } // try { // User user = // UserDatabaseManager.getInstance().getRealm(realmName).getUserDatabase().logon(username, // password); // // Sucessful login, remove any locks // unlockUser(username); // return user; // } catch (InvalidLoginCredentialsException ilce) { // if (lock == null && maxLogonAttemptsBeforeLock > 0 && lockDuration > 0) { // lock = createLock(username); // } // if (lock != null) { // lock.setAttempts(lock.getAttempts() + 1); // if (lock.getAttempts() >= maxLogonAttemptsBeforeLock) { // lock.setLocks(lock.getLocks() + 1); // if (lock.getLocks() >= maxLocksBeforeDisable) { // try { // // Disable the user // User user = // UserDatabaseManager.getInstance().getRealm(realmName).getUserDatabase().logon(username, // password); // if (PolicyUtil.isEnabled(user)) { // PolicyUtil.setEnabled(user, false, lock, null); // } // } catch (Exception e) { // log.error(e); // } // throw new AccountLockedException("Account disabled, please contact your // administrator.", true, 0); // } else { // lock.setLockedTime(System.currentTimeMillis()); // throw new AccountLockedException("Account temporarily locked. Please try // later.", false, // lockDuration * 1000); // } // } // } // throw ilce; // } catch (AccountLockedException ale) { // throw ale; // } catch (Exception e) { // throw new UserDatabaseException("Failed to logon. ", e); // } // } public AccountLock logonFailed(String username, String realmName, AccountLock lock) throws SecurityErrorException, AccountLockedException { // Get the user lockout policy int maxLogonAttemptsBeforeLock = 0; int maxLocksBeforeDisable = 0; int lockDuration = 0; UserDatabase udb = null; try { udb = UserDatabaseManager.getInstance().getUserDatabase(realmName); } catch (Exception e1) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, e1, "Failed to determine the realm name " + realmName + "."); } try { maxLogonAttemptsBeforeLock = Property.getPropertyInt( new RealmKey("security.maxLogonAttemptsBeforeLock", udb.getRealm().getResourceId())); maxLocksBeforeDisable = Property .getPropertyInt(new RealmKey("security.maxLocksBeforeDisable", udb.getRealm().getResourceId())); lockDuration = Property .getPropertyInt(new RealmKey("security.lockDuration", udb.getRealm().getResourceId())); } catch (Exception e) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, e, "Failed to determine password lockout policy."); } if (lock == null && maxLogonAttemptsBeforeLock > 0 && lockDuration > 0) { lock = createLock(username); } if (lock != null) { lock.setAttempts(lock.getAttempts() + 1); if (lock.getAttempts() >= maxLogonAttemptsBeforeLock) { lock.setLocks(lock.getLocks() + 1); if (lock.getLocks() >= maxLocksBeforeDisable) { try { // Disable the user User user = udb.getAccount(username); if (PolicyUtil.isEnabled(user)) { PolicyUtil.setEnabled(user, false, lock, null); } } catch (Exception e) { log.error(e); } throw new AccountLockedException(username, "Account disabled, please contact your administrator.", true, 0); } else { lock.setLockedTime(System.currentTimeMillis()); throw new AccountLockedException(username, "Account temporarily locked. Please try later.", false, lockDuration * 1000); } } } return lock; } public void logoff(String ticket) { SessionInfo session = (SessionInfo) logons.remove(ticket); /** * LDP - What happens if logoff is called twice? Previously we assumed this * would never happen */ if (session == null) return; if (log.isInfoEnabled()) log.info("Logging off " + ticket); List<String> ticketsToRemove = new ArrayList<String>(); synchronized (logonsBySessionId) { for (Map.Entry<String, SessionInfo> entry : logonsBySessionId.entrySet()) { if (entry.getValue().getLogonTicket().equals(ticket)) { ticketsToRemove.add(entry.getKey()); } } for (String key : ticketsToRemove) { logonsBySessionId.remove(key); } } session.release(); CoreServlet.getServlet().fireCoreEvent(new CoreEvent(this, CoreEventConstants.LOGOFF, null, session)); } public Map getActiveSessions() { return logons; } public User getUser(HttpSession session, String logonTicket) throws SecurityErrorException { if (logonTicket == null) { logonTicket = (String) session.getAttribute(Constants.LOGON_TICKET); } if (logonTicket == null) { throw new SecurityErrorException(SecurityErrorException.ERR_INVALID_TICKET, "No ticket was provided or found in the session object (" + session.getId() + ")"); } SessionInfo info = (SessionInfo) logons.get(logonTicket); if (info == null) { throw new SecurityErrorException(SecurityErrorException.ERR_INVALID_TICKET, "No session info. object could be found for the ticket (" + session.getId() + ")"); } User user = info.getUser(); return user; } public User getUser(HttpServletRequest request) throws SecurityErrorException { return getUser(request, null); } public User getUser(HttpServletRequest request, String logonTicket) throws SecurityErrorException { return getUser(request.getSession(), logonTicket); } public int hasClientLoggedOn(HttpServletRequest request, HttpServletResponse response) throws SecurityErrorException { // Get the logon cookie String logonCookie = null; if (request.getCookies() != null) { for (int i = 0; i < request.getCookies().length; i++) { Cookie cookie = request.getCookies()[i]; if (cookie.getName().equals(Constants.LOGON_TICKET) || cookie.getName().equals(Constants.DOMAIN_LOGON_TICKET)) { logonCookie = cookie.getValue(); } } } // If there is a logon ticket in the requests attributes then reassign // as we've just been issued a new ticket. if (request.getAttribute(Constants.LOGON_TICKET) != null) logonCookie = (String) request.getAttribute(Constants.LOGON_TICKET); // First check the users session for a logonticket String sessionLogonTicket = (String) request.getSession().getAttribute(Constants.LOGON_TICKET); if (sessionLogonTicket != null) { // Make sure we are still receiving the logon ticket /** * LDP - Users are having too many issues with this change. If we * still have a ticket in the session then the HTTP session must * still be alive and the the cookie has simply expired before the * HTTP session (or the browser has elected not to send it). We * should allow this to continue and refresh the cookie here. */ /* * if(logonCookie == null && * request.getAttribute(Constants.LOGON_TICKET) == null) { * * * log.warn("Lost logon ticket. It is likely that logon cookie has * expired. "); return INVALID_TICKET; } else */ if (logonCookie == null) { SessionInfo session = getSessionInfo(sessionLogonTicket); if (session == null) return NOT_LOGGED_ON; addCookies(new ServletRequestAdapter(request), new ServletResponseAdapter(response), sessionLogonTicket, session); } // Still check that the cookie is what we expect it to be if (logonCookie != null && !sessionLogonTicket.equals(logonCookie)) { log.warn("Expected a different logon ticket."); return NOT_LOGGED_ON; } if (checkRemoteAddress(sessionLogonTicket, request.getRemoteAddr())) { return LOGGED_ON; } } else { if (logonCookie != null && logons.containsKey(logonCookie)) { if (checkRemoteAddress(logonCookie, request.getRemoteAddr())) { refreshLogonTicket(request, response, logonCookie); return LOGGED_ON; } } } return NOT_LOGGED_ON; } private boolean checkRemoteAddress(String logonTicket, String remoteAddr) { try { SessionInfo session = getSessionInfo(logonTicket); if (Property.getPropertyBoolean(new RealmKey("security.checkRemoteAddress", session.getRealmId()))) { InetAddress addr = InetAddress.getByName(remoteAddr); if (log.isDebugEnabled()) log.debug("Verifying " + addr.getHostAddress() + " is original address " + session.getAddress().getHostAddress()); return session != null && session.getAddress().equals(addr); } else return true; } catch (UnknownHostException e) { log.error("Failed to determine remote address", e); return false; } } private void refreshLogonTicket(HttpServletRequest request, HttpServletResponse response, String logonTicket) throws SecurityErrorException { if (log.isInfoEnabled()) log.info("Refreshing logon ticket " + logonTicket); User user = getUser(request, logonTicket); request.getSession().setAttribute(Constants.USER, user); request.getSession().setAttribute(Constants.LOGON_TICKET, logonTicket); request.setAttribute(Constants.LOGON_TICKET, logonTicket); SessionInfo info = (SessionInfo) logons.get(logonTicket); if (info == null) { InetAddress address; try { address = InetAddress.getByName(request.getRemoteAddr()); } catch (UnknownHostException uhe) { throw new SecurityErrorException(SecurityErrorException.ERR_INVALID_TICKET, "Could not refresh logon ticket. " + uhe.getMessage()); } String userAgent = request.getHeader("User-Agent"); info = SessionInfo.nextSession(request.getSession(), logonTicket, user, address, SessionInfo.UI, userAgent); } else { moveSessionTimeoutBlocks(info.getHttpSession(), request.getSession()); info.setSession(request.getSession()); } request.getSession().setAttribute(Constants.SESSION_INFO, info); /** * LDP - Allow for the session info to be looked up using the session * id. */ try { String sessionIdentifier = SystemProperties.get("sslexplorer.cookie", "JSESSIONID"); String sessionId = null; Cookie[] cookies = request.getCookies(); for (int i = 0; i < cookies.length; i++) { if (cookies[i].getName().equalsIgnoreCase(sessionIdentifier)) { sessionId = cookies[i].getValue(); break; } } if (sessionId != null) { logonsBySessionId.put(sessionId, info); } else log.warn("Could not find session id using identifier " + sessionIdentifier + " in HTTP request"); } catch (Exception ex) { log.warn("Failed to determine HTTP session id", ex); } addSession(logonTicket, info, request, response); try { if (Property.getPropertyBoolean(new SystemConfigKey("security.session.lockSessionOnBrowserClose"))) { if (log.isInfoEnabled()) log.info("New session - will force the user to authenticate again"); request.getSession().setAttribute(Constants.SESSION_LOCKED, user); } else { ResourceUtil.setAvailableProfiles(info); } } catch (Exception e) { log.warn("Failed to set session lock.", e); } } public void addSession(String logonTicket, SessionInfo info, HttpServletRequest request, HttpServletResponse response) { logons.put(logonTicket, info); addCookies(new ServletRequestAdapter(request), new ServletResponseAdapter(response), logonTicket, info); } public void addCookies(RequestHandlerRequest request, RequestHandlerResponse response, String logonTicket, SessionInfo session) { if (request.getAttribute("sslx.logon.cookie") != null) return; /** * Set the normal logon ticket without a domain - this works in almost * all circumstances */ Cookie cookie = new Cookie(Constants.LOGON_TICKET, logonTicket); cookie.setMaxAge(Property.getPropertyInt(new SystemConfigKey("security.session.maxCookieAge"))); cookie.setPath("/"); cookie.setSecure(true); response.addCookie(cookie); /** * Set a logon ticket for the domain - this is require to make active * dns work. */ Cookie cookie2 = new Cookie(Constants.DOMAIN_LOGON_TICKET, logonTicket); cookie2.setMaxAge(Property.getPropertyInt(new SystemConfigKey("security.session.maxCookieAge"))); cookie2.setPath("/"); // We now set the domain on the cookie so the new Active DNS feature for // Reverse Proxy works correctly String host = request.getField("Host"); if (host != null) { HostService hostService = new HostService(host); cookie2.setDomain(hostService.getHost()); } cookie2.setSecure(true); response.addCookie(cookie2); request.setAttribute("sslx.logon.cookie", new Object()); /** * LDP - This code was not setting the domain on the ticket. I've * converted to the new format of having two seperate tickets to ensure * tickets are sent across domains */ /* * Cookie cookie = new Cookie(Constants.LOGON_TICKET, logonTicket); try { * cookie.setMaxAge(Integer.parseInt(CoreServlet.getServlet().getPropertyDatabase().getProperty(0, * null, "security.session.maxCookieAge"))); if * ("true".equals(CoreServlet.getServlet().getPropertyDatabase().getProperty(0, * null, "security.session.lockSessionOnBrowserClose"))) { if * (log.isInfoEnabled()) log.info("New session - will force the user to * authenticate again"); // initialiseSession(request.getSession(), * user); // List profiles = // * CoreServlet.getServlet().getPropertyDatabase().getPropertyProfiles(user.getUsername(), // * false); // request.getSession().setAttribute(Constants.PROFILES, // * profiles); * request.getSession().setAttribute(Constants.SESSION_LOCKED, user); } } * catch (Exception e) { log.error(e); cookie.setMaxAge(900); } * cookie.setPath("/"); cookie.setSecure(true); * response.addCookie(cookie); */ // } private SessionInfo addLogonTicket(HttpServletRequest request, HttpServletResponse response, User user, InetAddress address, int sessionType) { String logonTicket = TicketGenerator.getInstance().generateUniqueTicket("SLX"); if (log.isInfoEnabled()) log.info("Adding logon ticket to session " + request.getSession().getId()); request.getSession().setAttribute(Constants.LOGON_TICKET, logonTicket); request.setAttribute(Constants.LOGON_TICKET, logonTicket); String userAgent = request.getHeader("User-Agent"); SessionInfo info = SessionInfo.nextSession(request.getSession(), logonTicket, user, address, sessionType, userAgent); request.getSession().setAttribute(Constants.SESSION_INFO, info); try { String sessionIdentifier = SystemProperties.get("sslexplorer.cookie", "JSESSIONID"); String sessionId = null; Cookie[] cookies = request.getCookies(); for (int i = 0; cookies != null && i < cookies.length; i++) { if (cookies[i].getName().equalsIgnoreCase(sessionIdentifier)) { sessionId = cookies[i].getValue(); break; } } if (sessionId != null) { logonsBySessionId.put(sessionId, info); } else log.warn("Could not find session id using identifier " + sessionIdentifier + " in HTTP request"); } catch (Exception ex) { log.warn("Failed to determine HTTP session id", ex); } logons.put(logonTicket, info); /** * Set the normal logon ticket without a domain - this works in almost * all circumstances */ Cookie cookie = new Cookie(Constants.LOGON_TICKET, logonTicket); cookie.setMaxAge(Property.getPropertyInt(new SystemConfigKey("security.session.maxCookieAge"))); cookie.setPath("/"); cookie.setSecure(true); response.addCookie(cookie); /** * Set a logon ticket for the domain - this is require to make active * dns work. */ Cookie cookie2 = new Cookie(Constants.DOMAIN_LOGON_TICKET, logonTicket); cookie2.setMaxAge(Property.getPropertyInt(new SystemConfigKey("security.session.maxCookieAge"))); cookie2.setPath("/"); // We now set the domain on the cookie so the new Active DNS feature for // Reverse Proxy works correctly String host = request.getHeader("Host"); if (host != null) { HostService hostService = new HostService(host); cookie2.setDomain(hostService.getHost()); } cookie.setSecure(true); response.addCookie(cookie2); return info; } public void unlockUser(String username) { if (log.isInfoEnabled()) log.info("Unlocking user " + username); lockedUsers.remove(username); } /** * @param request * @param response * @param scheme */ public void logon(HttpServletRequest request, HttpServletResponse response, AuthenticationScheme scheme) throws Exception { User user = scheme.getUser(); // Check logon is currently allowed String logonNotAllowedReason = LogonControllerFactory.getInstance().checkLogonAllowed(user); if (logonNotAllowedReason != null) { log.warn("Logon not allowed because '" + logonNotAllowedReason + "'"); throw new Exception(logonNotAllowedReason); } if (log.isInfoEnabled()) { log.info("Session logon ticket is " + (String) request.getSession().getAttribute(Constants.LOGON_TICKET)); log.info("Logging on " + scheme.getUsername() + " for scheme " + scheme.getSchemeName()); } // Sucessful login, remove any locks unlockUser(scheme.getUsername()); String host = (request.isSecure() || SystemProperties.get("jetty.force.HTTPSRedirect", "false").equals("true") ? "https" : "http") + "://" + request.getHeader(HttpConstants.HDR_HOST); request.getSession().setAttribute(Constants.HOST, host); SessionInfo info = null; boolean fireEvent = false; if (request.getSession().getAttribute(Constants.SESSION_LOCKED) == null) { InetAddress address = InetAddress.getByName(request.getRemoteAddr()); int sessionType = SessionInfo.getSessionTypeForUserAgent(request.getHeader("User-Agent")); checkForMultipleSessions(user, address, sessionType); info = addLogonTicket(request, response, user, address, sessionType); try { info.getHttpSession().setAttribute(Constants.VPN_AUTOSTART, CoreUtil.getUsersProfileProperty(info.getHttpSession(), "client.autoStart", user)); } catch (Exception e) { throw new SecurityErrorException(SecurityErrorException.INTERNAL_ERROR, e.getMessage()); } fireEvent = true; } // Initialise the session initialiseSession(request.getSession(), user); // Build the menus CoreUtil.resetMainNavigation(request.getSession()); char[] pw = getPasswordFromCredentials(scheme); String mode = Property.getProperty(new SystemConfigKey("security.privateKeyMode")); if (!mode.equals("disabled")) { try { PublicKeyStore.getInstance().verifyPrivateKey(user.getPrincipalName(), pw); } catch (PromptForPasswordException e) { CoreUtil.addPageInterceptListener(request.getSession(), new PromptForPrivateKeyPassphraseInterceptListener()); } catch (UpdatePrivateKeyPassphraseException e) { if (mode.equals("prompt")) { CoreUtil.addPageInterceptListener(request.getSession(), new PromptForPrivateKeyPassphraseInterceptListener()); } else { CoreUtil.addPageInterceptListener(request.getSession(), new UpdatePrivateKeyPassphraseInterceptListener()); } } } /* * Make sure the logon event gets fired after the private key has * been initialised. This is repeated in the actions the page * intercept listeners redirect to */ if (fireEvent) { CoreServlet.getServlet() .fireCoreEvent(new CoreEvent(this, CoreEventConstants.LOGON, scheme, info) .addAttribute(CoreAttributeConstants.EVENT_ATTR_IP_ADDRESS, request.getRemoteAddr()) .addAttribute(CoreAttributeConstants.EVENT_ATTR_HOST, request.getRemoteHost()) .addAttribute(CoreAttributeConstants.EVENT_ATTR_SCHEME, scheme.getSchemeName())); } } /** * @param scheme * @return */ public char[] getPasswordFromCredentials(AuthenticationScheme scheme) { if (scheme != null) { for (Iterator i = scheme.credentials(); i.hasNext();) { Credentials cred = (Credentials) i.next(); if (cred instanceof PasswordCredentials) { if (((PasswordCredentials) cred).getPassword() != null) { return ((PasswordCredentials) cred).getPassword(); } } } } return null; } public SessionInfo getSessionInfo(HttpServletRequest request) { /** * LDP - This was only ever looking at the HTTP session. This causes * problems if the browser creates a new session but the logon ticket is * still valid. Look at the cookies if the ticket cannot be found. */ /** * BPS - This is wrong and should be solved another way. * getSessionInfo is *supposed* to return null if the HttpSession is * not attached to a SessionInfo. This code is only partially * reconfiguring the session and will prevent hasClientLoggedOn * from completing its job properly. This is a possible culprit for * the SessionInfos hanging around as the session * binding listeners will not be set up correctly. */ SessionInfo session = getSessionInfo(request.getSession()); if (session == null) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { if (cookies[i].getName().equals(Constants.LOGON_TICKET) || cookies[i].getName().equals(Constants.DOMAIN_LOGON_TICKET)) { session = getSessionInfo(cookies[i].getValue()); if (session != null) { log.error("----------------------------------------------------------"); log.error("A call has been made to getSessionInfo(HttpServletRequest)"); log.error("but the SessionInfo was not contained in the HttpSession"); log.error("However, there appears to valid cookies that DO point to a"); log.error("valid SessionInfo."); dumpSessionStuff(session); request.getSession().setAttribute(Constants.LOGON_TICKET, session.getLogonTicket()); request.getSession().setAttribute(Constants.SESSION_INFO, session); break; } } } } } return session; } public SessionInfo getSessionInfo(HttpSession session) { String logonTicket = (String) session.getAttribute(Constants.LOGON_TICKET); if (logonTicket != null) { return getSessionInfo(logonTicket); } return null; } public SessionInfo getSessionInfoBySessionId(String sessionId) { return (SessionInfo) logonsBySessionId.get(sessionId); } public SessionInfo getSessionInfo(String logonTicket) { return (SessionInfo) logons.get(logonTicket); } public void attachSession(String sessionId, SessionInfo session) { logonsBySessionId.put(sessionId, session); } public void registerAuthorizationTicket(String ticket, SessionInfo session) { authorizedTickets.put(ticket, session); } public SessionInfo removeAuthorizationTicket(String ticket) { return (SessionInfo) authorizedTickets.remove(ticket); } public SessionInfo getAuthorizationTicket(String ticket) { return (SessionInfo) authorizedTickets.get(ticket); } AccountLock createLock(String username) { AccountLock lock = new AccountLock(username); lockedUsers.put(username, lock); return lock; } public String checkLogonAllowed(User user) { updateMostUsersEverOnline(); return null; } private synchronized void moveSessionTimeoutBlocks(HttpSession oldSession, HttpSession newSession) { Map sessionTimeoutBlocks = (Map) oldSession.getAttribute(Constants.SESSION_TIMEOUT_BLOCKS); if (sessionTimeoutBlocks != null) { newSession.setAttribute(Constants.SESSION_TIMEOUT_BLOCKS, sessionTimeoutBlocks); } Integer vpnClientSessionTimeoutBlockId = (Integer) oldSession .getAttribute(Constants.AGENT_SESSION_TIMEOUT_BLOCK_ID); if (vpnClientSessionTimeoutBlockId != null) { newSession.setAttribute(Constants.AGENT_SESSION_TIMEOUT_BLOCK_ID, vpnClientSessionTimeoutBlockId); } newSession.setMaxInactiveInterval(sessionTimeoutBlocks == null || sessionTimeoutBlocks.size() == 0 ? oldSession.getMaxInactiveInterval() : -1); } public static class UpdatePrivateKeyPassphraseInterceptListener implements PageInterceptListener { public String getId() { return "updatePrivateKeyPassphrase"; } public ActionForward checkForForward(Action action, ActionMapping mapping, HttpServletRequest request, HttpServletResponse response) throws PageInterceptException { if (!(action instanceof UpdatePrivateKeyPassphraseDispatchAction)) { return new ActionForward("/updatePrivateKeyPassphrase.do", true); } return null; } public boolean isRedirect() { return false; } } class PromptForPrivateKeyPassphraseInterceptListener implements PageInterceptListener { public String getId() { return "promptForPrivateKeyPassphrase"; } public ActionForward checkForForward(Action action, ActionMapping mapping, HttpServletRequest request, HttpServletResponse response) throws PageInterceptException { if (!(action instanceof PromptForPrivateKeyPassphraseDispatchAction)) { try { if ("automatic".equals(Property.getProperty(new SystemConfigKey("security.privateKeyMode")))) { return new ActionForward("/promptForPrivateKeyPassphraseAuto.do", true); } else { return new ActionForward("/promptForPrivateKeyPassphrase.do", true); } } catch (Exception e) { log.error("Failed to determine private key mode.", e); } } return null; } public boolean isRedirect() { return false; } } protected int getActiveSessionCount() { return getActiveSessions().size(); } protected void updateMostUsersEverOnline() { try { int concurrentSessions = getActiveSessionCount() + 1; if (Property.getPropertyInt(new SystemConfigKey("security.maxUserCount")) < (concurrentSessions)) { Property.setProperty(new SystemConfigKey("security.maxUserCount"), concurrentSessions, null); } } catch (Exception ex) { log.error("Could not update most users online property", ex); } } //////// TEMPORARY CODE /////////// private void dumpSessionStuff(SessionInfo sesh) { log.error("User: " + sesh.getUser().getPrincipalName()); log.error("Type: " + sesh.getType()); log.error("User Agent: " + sesh.getUserAgent()); log.error("Address: " + sesh.getAddress()); log.error("Logon Time: " + SimpleDateFormat.getDateTimeInstance().format(new Date(sesh.getLogonTime().getTimeInMillis()))); log.error("------------------------------------------------------"); } class MostUsersOnline implements SystemInformationProvider { public String getBundle() { return "security"; } public String getName() { return "security.maxUserCount"; } public String getValue() { return String.valueOf(Property.getProperty(new SystemConfigKey("security.maxUserCount"))); } } class CurrentUsersOnline implements SystemInformationProvider { public String getBundle() { return "security"; } public String getName() { return "security.currentUserCount"; } public String getValue() { return String.valueOf(getActiveSessionCount()); } } /* * (non-Javadoc) * * @see com.sslexplorer.security.LogonController#applyMenuItemChanges(javax.servlet.http.HttpServletRequest) */ public void applyMenuItemChanges(HttpServletRequest request) { for (Iterator i = logons.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); SessionInfo sessionInfo = (SessionInfo) entry.getValue(); if (sessionInfo.getType() == SessionInfo.UI) { try { // remove the menu tree. sessionInfo.getHttpSession().removeAttribute(Constants.MENU_TREE); String username = sessionInfo.getUser().getPrincipalName(); // clean up any policy setups as they have changed now. PolicyDatabaseFactory.getInstance().cleanup(); Realm realm = sessionInfo.getUser().getRealm(); // update the sessions user, so any changes are known. sessionInfo .setUser(UserDatabaseManager.getInstance().getUserDatabase(realm).getAccount(username)); } catch (Exception e) { } } } } /** * This thread is a temporary fix until we can find where SessionInfo * objects are not getting removed from the list of active sessions when * an HttpSession gets invalidated */ class HorribleHackReaperThread extends Thread { HorribleHackReaperThread() { super("HorribleHackReaperThread"); setPriority(Thread.MIN_PRIORITY); setDaemon(true); start(); } public void run() { try { while (true) { Thread.sleep(60000); Map sessions = getActiveSessions(); synchronized (sessions) { List<SessionInfo> toRemove = new ArrayList<SessionInfo>(); for (Iterator i = sessions.values().iterator(); i.hasNext();) { SessionInfo sesh = (SessionInfo) i.next(); if (sesh.getHttpSession() != null) { try { sesh.getHttpSession().getAttribute(Constants.SESSION_INFO); } catch (IllegalStateException ise) { log.error("------------------------------------------------------"); log.error("An SSL-Explorer session that is attached to an"); log.error("invalid HttpSession has been discovered. This"); log.error("may cause other problems so it has been removed."); log.error("Please report this 3SP together with the information"); log.error("displayed below so that we determine the underlying"); log.error("cause of this problem."); dumpSessionStuff(sesh); toRemove.add(sesh); } } } for (SessionInfo sesh : toRemove) { logons.remove(sesh.getLogonTicket()); logonsBySessionId.remove(sesh.getHttpSession().getId()); } } } } catch (Exception e) { log.error("SessionInfo reaper thread has died.", e); } } } }