Files
alfresco-community-repo/source/java/org/alfresco/web/bean/LoginBean.java
Dave Ward 8ff98a72f5 Merged V3.2 to HEAD
16662: LDAP sync: improved group association filtering, referential integrity checking, deletion strategy and performance tuning of batch sizes
   16648: ETHREEOH-2752: Improved ticket validation fix
      - Invalidate user's tickets during person deletion rather than validation or it can mess up chained validation
   16647: ETHREEOH-2534: Fixed Sharepoint NTLM authentication
      - user details were never getting cached in the session
   16579: Small improvement to LDAP error reporting
      - Committed errors counted before successes in a logging interval
   16515: LDAP sync performance
      - Improved full sync strategy - run differential queries to work out required updates/additions and full queries to work out required deletions. Saves updating unchanged nodes.
      - Use a TreeSet rather than a HashSet to gather group associations in an attempt to avoid blowing the heap size
   16498: More LDAP performance improvements
      - Uses thread pool with 4 worker threads and blocking queue to process returned results. The number of worker threads can be controlled by the synchronization.workerThreads property.
      - Switched LDAP connection pooling back on again
      - Group Associations processsed individually so that errors are collated and we get a better idea of their throughput
      - Fixed potential bug. Group membership resolution done with isolated LDAP context to avoid cookies from paging creeping in.
   16424: Try switching off LDAP connection pooling to see if it works better with our flaky server.
   16414: Further LDAP fault tolerance
      - Log causes of group member resolution failures where possible
   16413: More fault tolerance for LDAP sync
      - Always commit last sync times before overall sync is complete to avoid the 'forgetting' of differential sync information
      - DN comparisons should be case insensitive to avoid issues resolving DNs to user and group IDs
   16398: Improved monitoring and fault tolerance for LDAP sync
      - When the batch is complete a summary of the number of errors and the last error stack trace will be logged at ERROR level
      - Each individual error is logged at WARN level and progress information (including % complete) is collated and logged at INFO level after a configurable interval
      - In the Enterprise Edition all metrics can be monitored in real time through JMX
      - Sanity testing to be performed by Mike!
   16319: Merged HEAD to V3.2
      16316: ALFCOM-3397: JBoss 5 compatibility fix
         - Relative paths used by LDAP subsystem configuration weren't being resolved correctly
         - See also https://jira.jboss.org/jira/browse/JBAS-6548 and https://jira.springsource.org/browse/SPR-5120
   16272: ETHREEOH-2752: Once more with feeling!
   16261: ETHREEOH-2752: Correct exception propagation.
   16260: ETHREEOH-2752: Fix ticket validation
      - Current ticket was getting forgotten by previous fix
      - Person validation in CHECK mode now done AFTER the current user is set, so that the current ticket is remembered
   16243: ETHREEOH-2752: Improve ticket validation used by all authentication filters
      - Now takes into account whether person actually exists or not
      - Tickets for non-nonexistent persons are now considered invalid and cached session information is invalidated
      - New BaseAuthenticationFilter superclass for all authentication filters
      - Improved fix to ETHREEOH-2839: WebDAV user is cached consistently using a different session attribute from the Web Client
   16233: ETHREEOH-2754: Correction to previous checkin.
      - relogin for SSO authentication, logout for normal login page
      - logout is default
   16232: ETHREEOH-2754: Log Out Action outcome passed as a parameter
      - relogin for SSO authentication, login for normal login page
      - Means the log out link always leads to the correct place, even when the session has expired
      - Also lowered ticket validation error logging to DEBUG level to avoid unnecessary noise in the logs from expired sessions
   16220: ETHREEOH-2839: Fixed potential ClassCastExceptions when Alfresco accessed via WebDAV and Web Client links in same browser
      - WebDAV side no longer directly casts session user to a WebDAVUser
      - ContextListener no longer casts session user to web client user
      - Web client side will 'promote' session user to a web client User if necessary via AuthenticationHelper
      - All authentication filters made to use appropriate AuthenticationHelper methods
   16211: ETHREEOH-2835: LDAP sync batches user and group deletions as well as creations
      - Also improved logging of sync failures
   16197: ETHREEOH-2782: LDAP subsystems now support search-based user DN resolution
      - When ldap.authentication.userNameFormat isn't set (now the default) converts a user ID to a DN by running ldap.synchronization.personQuery with an extra condition tacked on the end to find the user by ID
      - Structured directories and authentication by attributes not in the DN such as email address now supported
   16189: ALFCOM-3283: Prevent errors when user accepts an invite when not logged in
      - new isGuest attribute propagated to user object
      - header component (used by accept-invite page) needs to avoid calling prefs and site webscripts for guest user
      - Conditional stuff in header template changed to use user.isGuest


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@16896 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2009-10-14 09:24:13 +00:00

498 lines
18 KiB
Java

/*
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.web.bean;
import java.io.IOException;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.SessionUser;
import org.alfresco.repo.security.authentication.AuthenticationDisallowedException;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationMaxUsersException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.servlet.AuthenticationHelper;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.bean.users.UserPreferencesBean;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* JSF Managed Bean. Backs the "login.jsp" view to provide the form fields used
* to enter user data for login. Also contains bean methods to validate form
* fields and action event fired in response to the Login button being pressed.
*
* @author Kevin Roast
*/
public class LoginBean implements Serializable
{
/**
* The default outcome of the logout action.
*/
private static final String OUTCOME_LOGOUT = "logout";
/**
* The outcome of the logout action when the user has been signed on by SSO.
*/
private static final String OUTCOME_RELOGIN = "relogin";
/**
* The name of the form parameter carrying the outcome to the logout action.
*/
private static final String PARAM_OUTCOME = "outcome";
private static final long serialVersionUID = 7417882503323795282L;
/**
* @param authenticationService The AuthenticationService to set.
*/
public void setAuthenticationService(AuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
protected AuthenticationService getAuthenticationService()
{
if (authenticationService == null)
authenticationService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getAuthenticationService();
return authenticationService;
}
/**
* @param personService The personService to set.
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
protected PersonService getPersonService()
{
if (personService == null)
personService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getPersonService();
return personService;
}
/**
* @param nodeService The nodeService to set.
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
protected NodeService getNodeService()
{
if (nodeService == null)
nodeService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService();
return nodeService;
}
/**
* @param browseBean The BrowseBean to set.
*/
public void setBrowseBean(BrowseBean browseBean)
{
this.browseBean = browseBean;
}
/**
* @param navigator The NavigationBean to set.
*/
public void setNavigator(NavigationBean navigator)
{
this.navigator = navigator;
}
/**
* @param preferences The UserPreferencesBean to set
*/
public void setUserPreferencesBean(UserPreferencesBean preferences)
{
this.preferences = preferences;
}
public UserPreferencesBean getUserPreferencesBean()
{
return preferences;
}
/**
* @return "logout" if the default Alfresco authentication process is being used, else "relogin"
* if an external authorisation mechanism is present.
*/
public String getLogoutOutcome()
{
Map<?, ?> session = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
return session.get(LOGIN_EXTERNAL_AUTH) == null ? OUTCOME_LOGOUT : OUTCOME_RELOGIN;
}
/**
* @param val Username from login dialog
*/
public void setUsername(String val)
{
if ( val != null ) { val = val.trim(); }
this.username = val;
}
/**
* @return The username string from login dialog
*/
public String getUsername()
{
// this value may have been set by a servlet filter via a cookie
// check for this by detecting a special value in the session
FacesContext context = FacesContext.getCurrentInstance();
Map session = context.getExternalContext().getSessionMap();
String username = (String)session.get(AuthenticationHelper.SESSION_USERNAME);
if (username != null)
{
session.remove(AuthenticationHelper.SESSION_USERNAME);
this.username = username;
}
return this.username;
}
public String getUsernameInternal()
{
return this.username;
}
/**
* @param val Password from login dialog
*/
public void setPassword(String val)
{
this.password = val;
}
/**
* @return The password string from login dialog
*/
public String getPassword()
{
return this.password;
}
/**
* @return true to display language selection, false to
*/
public boolean isLanguageSelect()
{
return Application.getClientConfig(FacesContext.getCurrentInstance()).isLanguageSelect();
}
// ------------------------------------------------------------------------------
// Validator methods
/**
* Validate password field data is acceptable
*/
public void validatePassword(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
int minPasswordLength = Application.getClientConfig(context).getMinPasswordLength();
String pass = (String)value;
if (pass.length() < minPasswordLength || pass.length() > 256)
{
String err = MessageFormat.format(Application.getMessage(context, MSG_PASSWORD_LENGTH),
new Object[]{minPasswordLength, 256});
throw new ValidatorException(new FacesMessage(err));
}
}
/**
* Validate Username field data is acceptable
*/
public void validateUsername(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
int minUsernameLength = Application.getClientConfig(context).getMinUsernameLength();
String name = ((String)value).trim();
if (name.length() < minUsernameLength || name.length() > 256)
{
String err = MessageFormat.format(Application.getMessage(context, MSG_USERNAME_LENGTH),
new Object[]{minUsernameLength, 256});
throw new ValidatorException(new FacesMessage(err));
}
if (name.indexOf('"') != -1)
{
String err = MessageFormat.format(Application.getMessage(context, MSG_USER_ERR),
new Object[]{"\""});
throw new ValidatorException(new FacesMessage(err));
}
}
// ------------------------------------------------------------------------------
// Action event methods
/**
* Login action handler
*
* @return outcome view name
*/
public String login()
{
String outcome = null;
FacesContext fc = FacesContext.getCurrentInstance();
if (this.username != null && this.username.length() != 0 &&
this.password != null && this.password.length() != 0)
{
try
{
Map session = fc.getExternalContext().getSessionMap();
// Authenticate via the authentication service, then save the details of user in an object
// in the session - this is used by the servlet filter etc. on each page to check for login
this.getAuthenticationService().authenticate(this.username, this.password.toCharArray());
// Set the user name as stored by the back end
this.username = this.getAuthenticationService().getCurrentUserName();
// remove the session invalidated flag (used to remove last username cookie by AuthenticationFilter)
session.remove(AuthenticationHelper.SESSION_INVALIDATED);
// setup User object and Home space ID
User user = new User(
this.username,
this.getAuthenticationService().getCurrentTicket(),
getPersonService().getPerson(this.username));
NodeRef homeSpaceRef = (NodeRef) this.getNodeService().getProperty(getPersonService().getPerson(this.username), ContentModel.PROP_HOMEFOLDER);
// check that the home space node exists - else user cannot login
if (homeSpaceRef == null || this.getNodeService().exists(homeSpaceRef) == false)
{
throw new InvalidNodeRefException(homeSpaceRef);
}
user.setHomeSpaceId(homeSpaceRef.getId());
// put the User object in the Session - the authentication servlet will then allow
// the app to continue without redirecting to the login page
session.put(AuthenticationHelper.AUTHENTICATION_USER, user);
// if a redirect URL has been provided then use that
// this allows servlets etc. to provide a URL to return too after a successful login
String redirectURL = (String)session.get(LOGIN_REDIRECT_KEY);
if (redirectURL != null)
{
if (logger.isDebugEnabled())
logger.debug("Redirect URL found: " + redirectURL);
// remove redirect URL from session
session.remove(LOGIN_REDIRECT_KEY);
try
{
fc.getExternalContext().redirect(redirectURL);
fc.responseComplete();
return null;
}
catch (IOException ioErr)
{
logger.warn("Unable to redirect to url: " + redirectURL);
}
}
else
{
// special case to handle jump to My Alfresco page initially
// note: to enable MT runtime client config customization, need to re-init NavigationBean
// in context of tenant login page
this.navigator.initFromClientConfig();
if (NavigationBean.LOCATION_MYALFRESCO.equals(this.preferences.getStartLocation()))
{
return "myalfresco";
}
else
{
// generally this will navigate to the generic browse screen
return "success";
}
}
}
catch (AuthenticationDisallowedException aerr)
{
Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_LOGIN_DISALLOWED));
}
catch (AuthenticationMaxUsersException aerr)
{
Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_LOGIN_MAXUSERS));
}
catch (AuthenticationException aerr)
{
Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_UNKNOWN_USER));
}
catch (InvalidNodeRefException refErr)
{
String msg;
if (refErr.getNodeRef() != null)
{
msg = refErr.getNodeRef().toString();
}
else
{
msg = Application.getMessage(fc, MSG_NONE);
}
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(fc,
Repository.ERROR_NOHOME), msg));
}
}
else
{
Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_MISSING));
}
return outcome;
}
/**
* Invalidate ticket and logout user
*/
public String logout()
{
FacesContext context = FacesContext.getCurrentInstance();
// The outcome is decided in advance (before session expiry) and included as a parameter
Map<?, ?> params = context.getExternalContext().getRequestParameterMap();
String outcome = (String)params.get(PARAM_OUTCOME);
if (outcome == null)
{
outcome = OUTCOME_LOGOUT;
}
Locale language = Application.getLanguage(context);
// Invalidate Session for this user.
if (Application.inPortalServer() == false)
{
// This causes the sessionDestroyed() event to be processed by ContextListener
// which is responsible for invalidating the ticket and clearing the security context
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.getSession().invalidate();
}
else
{
Map session = context.getExternalContext().getSessionMap();
SessionUser user = (SessionUser)session.get(AuthenticationHelper.AUTHENTICATION_USER);
if (user != null)
{
// invalidate ticket and clear the Security context for this thread
getAuthenticationService().invalidateTicket(user.getTicket());
getAuthenticationService().clearCurrentSecurityContext();
}
// remove all objects from our session by hand
// we do this as invalidating the Portal session would invalidate all other portlets!
for (Object key : session.keySet())
{
session.remove(key);
}
}
// Request that the username cookie state is removed - this is not
// possible from JSF - so instead we setup a session variable
// which will be detected by the login.jsp/Portlet as appropriate.
Map session = context.getExternalContext().getSessionMap();
session.put(AuthenticationHelper.SESSION_INVALIDATED, true);
// set language to last used on the login page
Application.setLanguage(context, language.toString());
return outcome;
}
// ------------------------------------------------------------------------------
// Private data
private static final Log logger = LogFactory.getLog(LoginBean.class);
/** I18N messages */
private static final String MSG_ERROR_MISSING = "error_login_missing";
private static final String MSG_ERROR_UNKNOWN_USER = "error_login_user";
private static final String MSG_ERROR_LOGIN_DISALLOWED = "error_login_disallowed";
private static final String MSG_ERROR_LOGIN_MAXUSERS = "error_login_maxusers";
private static final String MSG_NONE = "none";
public static final String MSG_ERROR_LOGIN_NOPERMISSIONS = "login_err_permissions";
public static final String MSG_USERNAME_LENGTH = "login_err_username_length";
public static final String MSG_PASSWORD_LENGTH = "login_err_password_length";
public static final String MSG_USER_ERR = "user_err_user_name";
public static final String LOGIN_REDIRECT_KEY = "_alfRedirect";
public static final String LOGIN_EXTERNAL_AUTH = "_alfExternalAuth";
public static final String LOGIN_NOPERMISSIONS = "_alfNoPermissions";
/** user name */
private String username = null;
/** password */
private String password = null;
/** PersonService bean reference */
private transient PersonService personService;
/** AuthenticationService bean reference */
private transient AuthenticationService authenticationService;
/** NodeService bean reference */
private transient NodeService nodeService;
/** The BrowseBean reference */
protected BrowseBean browseBean;
/** The NavigationBean bean reference */
protected NavigationBean navigator;
/** The user preferences bean reference */
protected UserPreferencesBean preferences;
}