/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.web.bean; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; 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.model.SelectItem; import javax.faces.validator.ValidatorException; import javax.portlet.PortletRequest; import javax.servlet.http.HttpServletRequest; import org.alfresco.config.ConfigService; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.webdav.WebDAVServlet; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; 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.repository.StoreRef; 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.config.ClientConfigElement; 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 { // ------------------------------------------------------------------------------ // Managed bean properties /** * @param authenticationService The AuthenticationService to set. */ public void setAuthenticationService(AuthenticationService authenticationService) { this.authenticationService = authenticationService; } /** * @param personService The personService to set. */ public void setPersonService(PersonService personService) { this.personService = personService; } /** * @param nodeService The nodeService to set. */ public void setNodeService(NodeService nodeService) { this.nodeService = 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 configService The ConfigService to set. */ public void setConfigService(ConfigService configService) { this.configService = configService; } /** * @param fileFolderService The FileFolderService to set. */ public void setFileFolderService(FileFolderService fileFolderService) { this.fileFolderService = fileFolderService; } /** * @param val Username from login dialog */ public void setUsername(String val) { 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 the available languages */ public SelectItem[] getLanguages() { ClientConfigElement config = (ClientConfigElement) this.configService.getGlobalConfig() .getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); List languages = config.getLanguages(); SelectItem[] items = new SelectItem[languages.size()]; int count = 0; for (String locale : languages) { // get label associated to the locale String label = config.getLabelForLanguage(locale); // set default selection if (count == 0 && this.language == null) { // first try to get the language that the current user is using Locale lastLocale = Application.getLanguage(FacesContext.getCurrentInstance()); if (lastLocale != null) { this.language = lastLocale.toString(); } // else we default to the first item in the list else { this.language = locale; } } items[count++] = new SelectItem(locale, label); } return items; } /** * @return Returns the language selection. */ public String getLanguage() { return this.language; } /** * @param language The language selection to set. */ public void setLanguage(String language) { this.language = language; Application.setLanguage(FacesContext.getCurrentInstance(), this.language); } // ------------------------------------------------------------------------------ // Validator methods /** * Validate password field data is acceptable */ public void validatePassword(FacesContext context, UIComponent component, Object value) throws ValidatorException { String pass = (String) value; if (pass.length() < 3 || pass.length() > 32) { String err = MessageFormat.format(Application.getMessage(context, MSG_PASSWORD_LENGTH), new Object[]{3, 32}); throw new ValidatorException(new FacesMessage(err)); } } /** * Validate Username field data is acceptable */ public void validateUsername(FacesContext context, UIComponent component, Object value) throws ValidatorException { String pass = (String) value; if (pass.length() < 3 || pass.length() > 32) { String err = MessageFormat.format(Application.getMessage(context, MSG_USERNAME_LENGTH), new Object[]{3, 32}); 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.password != null) { // 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 try { this.authenticationService.authenticate(this.username, this.password.toCharArray()); // setup User object and Home space ID User user = new User(this.authenticationService.getCurrentUserName(), this.authenticationService.getCurrentTicket(), personService.getPerson(this.username)); NodeRef homeSpaceRef = (NodeRef) this.nodeService.getProperty(personService.getPerson(this.username), ContentModel.PROP_HOMEFOLDER); // check that the home space node exists - else user cannot login if (this.nodeService.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 Map session = fc.getExternalContext().getSessionMap(); session.put(AuthenticationHelper.AUTHENTICATION_USER, user); // if an external outcome has been provided then use that, else use default String externalOutcome = (String)fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_KEY); if (externalOutcome != null) { // TODO: This is a quick solution. It would be better to specify the (identifier?) // of a handler class that would be responsible for processing specific outcome arguments. if (logger.isDebugEnabled()) logger.debug("External outcome found: " + externalOutcome); // setup is required for certain outcome requests if (OUTCOME_DOCDETAILS.equals(externalOutcome)) { NodeRef nodeRef = null; String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); if (args[0].equals(WebDAVServlet.WEBDAV_PREFIX)) { nodeRef = resolveWebDAVPath(fc, args); } else if (args.length == 3) { StoreRef storeRef = new StoreRef(args[0], args[1]); nodeRef = new NodeRef(storeRef, args[2]); } if (nodeRef != null) { // setup the Document on the browse bean // TODO: the browse bean should accept a full NodeRef - not just an ID this.browseBean.setupContentAction(nodeRef.getId(), true); } } else if (OUTCOME_SPACEDETAILS.equals(externalOutcome)) { NodeRef nodeRef = null; String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); if (args[0].equals(WebDAVServlet.WEBDAV_PREFIX)) { nodeRef = resolveWebDAVPath(fc, args); } else if (args.length == 3) { StoreRef storeRef = new StoreRef(args[0], args[1]); nodeRef = new NodeRef(storeRef, args[2]); } if (nodeRef != null) { // setup the Space on the browse bean // TODO: the browse bean should accept a full NodeRef - not just an ID this.browseBean.setupSpaceAction(nodeRef.getId(), true); } } else if (OUTCOME_BROWSE.equals(externalOutcome)) { String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); if (args != null) { NodeRef nodeRef = null; int offset = 0; if (args.length >= 3) { offset = args.length - 3; StoreRef storeRef = new StoreRef(args[0+offset], args[1+offset]); nodeRef = new NodeRef(storeRef, args[2+offset]); // setup the ref as current Id in the global navigation bean this.navigator.setCurrentNodeId(nodeRef.getId()); // check for view mode first argument if (args[0].equals(LOGIN_ARG_TEMPLATE)) { this.browseBean.setDashboardView(true); // the above call will auto-navigate to the correct outcome - so we don't! externalOutcome = null; } } } } fc.getExternalContext().getSessionMap().remove(LOGIN_OUTCOME_KEY); return externalOutcome; } else { // if a redirect URL has been provided then use that String redirectURL = (String)fc.getExternalContext().getSessionMap().get(LOGIN_REDIRECT_KEY); if (redirectURL != null) { if (logger.isDebugEnabled()) logger.debug("Redirect URL found: " + redirectURL); // remove URL from session fc.getExternalContext().getSessionMap().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 { return "success"; } } } catch (AuthenticationException aerr) { Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_UNKNOWN_USER)); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage(fc, Repository.ERROR_NOHOME), refErr.getNodeRef().getId())); } } else { Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_MISSING)); } return outcome; } /** * Invalidate ticket and logout user */ public String logout() { FacesContext context = FacesContext.getCurrentInstance(); Map session = context.getExternalContext().getSessionMap(); User user = (User) session.get(AuthenticationHelper.AUTHENTICATION_USER); boolean alfrescoAuth = (session.get(LOGIN_EXTERNAL_AUTH) == null); // invalidate Session for this user if (Application.inPortalServer() == false) { HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); request.getSession().invalidate(); } else { PortletRequest request = (PortletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); request.getPortletSession().invalidate(); } // invalidate User ticket if (user != null) { this.authenticationService.invalidateTicket(user.getTicket()); } // 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. session = context.getExternalContext().getSessionMap(); session.put(AuthenticationHelper.SESSION_INVALIDATED, true); // set language to last used if (this.language != null && this.language.length() != 0) { Application.setLanguage(context, this.language); } return alfrescoAuth ? "logout" : "relogin"; } // ------------------------------------------------------------------------------ // Private helpers /** * Resolves the given path elements to a NodeRef in the current repository * * @param context Faces context * @param args The elements of the path to lookup */ private NodeRef resolveWebDAVPath(FacesContext context, String[] args) { NodeRef nodeRef = null; List paths = new ArrayList(args.length-1); FileInfo file = null; try { // create a list of path elements (decode the URL as we go) for (int x = 1; x < args.length; x++) { paths.add(URLDecoder.decode(args[x], "UTF-8")); } if (logger.isDebugEnabled()) logger.debug("Attempting to resolve webdav path to NodeRef: " + paths); // get the company home node to start the search from NodeRef companyHome = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); file = this.fileFolderService.resolveNamePath(companyHome, paths); nodeRef = file.getNodeRef(); } catch (UnsupportedEncodingException uee) { if (logger.isWarnEnabled()) logger.warn("Failed to resolve webdav path", uee); nodeRef = null; } catch (FileNotFoundException fne) { if (logger.isWarnEnabled()) logger.debug("Failed to resolve webdav path", fne); nodeRef = null; } return nodeRef; } // ------------------------------------------------------------------------------ // 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_USERNAME_CHARS = "login_err_username_chars"; private static final String MSG_USERNAME_LENGTH = "login_err_username_length"; private static final String MSG_PASSWORD_CHARS = "login_err_password_chars"; private static final String MSG_PASSWORD_LENGTH = "login_err_password_length"; public static final String LOGIN_REDIRECT_KEY = "_alfRedirect"; public static final String LOGIN_OUTCOME_KEY = "_alfOutcome"; public static final String LOGIN_OUTCOME_ARGS = "_alfOutcomeArgs"; public static final String LOGIN_EXTERNAL_AUTH= "_alfExternalAuth"; public final static String OUTCOME_DOCDETAILS = "showDocDetails"; public final static String OUTCOME_SPACEDETAILS = "showSpaceDetails"; public final static String OUTCOME_BROWSE = "browse"; private static final String LOGIN_ARG_TEMPLATE = "template"; /** user name */ private String username = null; /** password */ private String password = null; /** language locale selection */ private String language = null; /** PersonService bean reference */ private PersonService personService; /** AuthenticationService bean reference */ private AuthenticationService authenticationService; /** NodeService bean reference */ private NodeService nodeService; /** The BrowseBean reference */ private BrowseBean browseBean; /** The NavigationBean bean reference */ private NavigationBean navigator; /** ConfigService bean reference */ private ConfigService configService; /** FileFolderService bean reference */ private FileFolderService fileFolderService; }