/* * (C) Copyright 2010 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library 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 * Lesser General Public License for more details. * Contributors: * Nuxeo - initial API and implementation */ package org.nuxeo.ecm.webapp.security; import static org.jboss.seam.ScopeType.APPLICATION; import static org.jboss.seam.ScopeType.CONVERSATION; import static org.jboss.seam.annotations.Install.FRAMEWORK; import static org.jboss.seam.international.StatusMessage.Severity.ERROR; import static org.nuxeo.ecm.platform.ui.web.api.WebActions.CURRENT_TAB_CHANGED_EVENT; import static org.nuxeo.ecm.platform.ui.web.api.WebActions.CURRENT_TAB_SELECTED_EVENT; import static org.nuxeo.ecm.user.invite.UserInvitationService.ValidationMethod.EMAIL; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.event.ValueChangeEvent; import javax.faces.validator.ValidatorException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.core.Events; import org.jboss.seam.international.StatusMessage; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.directory.BaseSession; import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl; import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException; import org.nuxeo.ecm.user.invite.UserInvitationService; import org.nuxeo.runtime.api.Framework; /** * Handles users management related web actions. * * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> * @since 5.4.2 */ @Name("userManagementActions") @Scope(CONVERSATION) @Install(precedence = FRAMEWORK) public class UserManagementActions extends AbstractUserGroupManagement implements Serializable { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(UserManagementActions.class); public static final String USERS_TAB = USER_CENTER_CATEGORY + ":" + USERS_GROUPS_HOME + ":" + "UsersHome"; public static final String USERS_LISTING_CHANGED = "usersListingChanged"; public static final String USERS_SEARCH_CHANGED = "usersSearchChanged"; public static final String USER_SELECTED_CHANGED = "selectedUserChanged"; public static final String SELECTED_LETTER_CHANGED = "selectedLetterChanged"; protected String selectedLetter = ""; protected DocumentModel selectedUser; protected DocumentModel newUser; protected boolean immediateCreation = false; @Override protected String computeListingMode() throws ClientException { return userManager.getUserListingMode(); } public DocumentModel getSelectedUser() { shouldResetStateOnTabChange = true; return selectedUser; } public void setSelectedUser(DocumentModel user) { fireSeamEvent(USER_SELECTED_CHANGED); selectedUser = user; } /** * @deprecated since version 5.5, use {@link #setSelectedUserName} instead. */ @Deprecated public void setSelectedUser(String userName) throws ClientException { setSelectedUser(refreshUser(userName)); } /** UserRegistrationService userRegistrationService = Framework.getLocalService(UserRegistrationService.class); * @since 5.5 */ public void setSelectedUserName(String userName) throws ClientException { setSelectedUser(refreshUser(userName)); } public String getSelectedUserName() throws ClientException { return selectedUser.getId(); } // refresh to get references protected DocumentModel refreshUser(String userName) throws ClientException { return userManager.getUserModel(userName); } public String getSelectedLetter() { return selectedLetter; } public void setSelectedLetter(String selectedLetter) { if (selectedLetter != null && !selectedLetter.equals(this.selectedLetter)) { this.selectedLetter = selectedLetter; fireSeamEvent(SELECTED_LETTER_CHANGED); } this.selectedLetter = selectedLetter; } public DocumentModel getNewUser() throws ClientException { if (newUser == null) { if (immediateCreation) { newUser = userManager.getBareUserModel(); } else { UserInvitationService userRegistrationService = Framework.getLocalService(UserInvitationService.class); newUser = userRegistrationService.getUserRegistrationModel(null); } } return newUser; } public boolean getAllowEditUser() throws ClientException { return getCanEditUsers(true) && !BaseSession.isReadOnlyEntry(selectedUser); } protected boolean getCanEditUsers(boolean allowCurrentUser) throws ClientException { if (userManager.areUsersReadOnly()) { return false; } // if the selected user is the anonymous user, do not display // edit/password tabs if (selectedUser != null && userManager.getAnonymousUserId() != null && userManager.getAnonymousUserId().equals(selectedUser.getId())) { return false; } if (selectedUser != null) { NuxeoPrincipal selectedPrincipal = userManager.getPrincipal(selectedUser.getId()); if (selectedPrincipal.isAdministrator() && !((NuxeoPrincipal) currentUser).isAdministrator()) { return false; } } if (currentUser instanceof NuxeoPrincipal) { NuxeoPrincipal pal = (NuxeoPrincipal) currentUser; if (webActions.checkFilter(USERS_GROUPS_MANAGEMENT_ACCESS_FILTER)) { return true; } if (allowCurrentUser && selectedUser != null) { if (pal.getName().equals(selectedUser.getId())) { return true; } } } return false; } public boolean getAllowChangePassword() throws ClientException { return getCanEditUsers(true) && !BaseSession.isReadOnlyEntry(selectedUser); } public boolean getAllowCreateUser() throws ClientException { return getCanEditUsers(false); } public boolean getAllowDeleteUser() throws ClientException { return getCanEditUsers(false) && !BaseSession.isReadOnlyEntry(selectedUser); } public void clearSearch() { searchString = null; fireSeamEvent(USERS_SEARCH_CHANGED); } public void createUser() throws ClientException { createUser(false); } public void createUser(boolean createAnotherUser) throws ClientException { try { setSelectedUser(userManager.createUser(newUser)); newUser = null; // Set the default value for the creation immediateCreation = false; facesMessages.add( StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get( "info.userManager.userCreated")); if (createAnotherUser) { showCreateForm = true; } else { showCreateForm = false; showUserOrGroup = true; detailsMode = null; } fireSeamEvent(USERS_LISTING_CHANGED); } catch (UserAlreadyExistsException e) { facesMessages.add( StatusMessage.Severity.ERROR, resourcesAccessor.getMessages().get( "error.userManager.userAlreadyExists")); } } public void updateUser() throws ClientException { try { userManager.updateUser(selectedUser); detailsMode = DETAILS_VIEW_MODE; fireSeamEvent(USERS_LISTING_CHANGED); } catch (Exception t) { throw ClientException.wrap(t); } } public String changePassword() throws ClientException { updateUser(); detailsMode = DETAILS_VIEW_MODE; String message = resourcesAccessor.getMessages().get( "label.userManager.password.changed"); facesMessages.add(FacesMessage.SEVERITY_INFO, message); fireSeamEvent(USERS_LISTING_CHANGED); return null; } public void deleteUser() throws ClientException { try { userManager.deleteUser(selectedUser); selectedUser = null; showUserOrGroup = false; fireSeamEvent(USERS_LISTING_CHANGED); } catch (Exception t) { throw ClientException.wrap(t); } } public void validateUserName(FacesContext context, UIComponent component, Object value) { if (!(value instanceof String) || !StringUtils.containsOnly((String) value, VALID_CHARS)) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, ComponentUtils.translate( context, "label.userManager.wrong.username"), null); // also add global message context.addMessage(null, message); throw new ValidatorException(message); } } /** * Verify that only administrators can add administrator groups. * * @param context * @param component * @param value * * @since 5.9.2 */ public void validateGroups(FacesContext context, UIComponent component, Object value) { UIInput groupsComponent = getReferencedComponent("groupsValueHolderId", component); @SuppressWarnings("unchecked") List<String> groups = groupsComponent == null ? null : (List<String>) groupsComponent.getLocalValue(); if (groups == null || groups.isEmpty()) { return; } try { if (!isAllowedToAdminGroups(groups)) { throwValidationException(context, "label.userManager.invalidGroupSelected"); } } catch (ClientException e) { throwValidationException(context, "label.userManager.unableToValidateGroups", e.getMessage()); } } /** * Checks if the current user is allowed to aministrate (meaning add/remove) * the given groups. * * @param groups * @return * @throws ClientException * * @since 5.9.2 */ boolean isAllowedToAdminGroups(List<String> groups) throws ClientException { NuxeoPrincipalImpl nuxeoPrincipal = (NuxeoPrincipalImpl) currentUser; if (!nuxeoPrincipal.isAdministrator()) { List<String> adminGroups = getAllAdminGroups(); for (String group : groups) { if (adminGroups.contains(group)) { return false; } } } return true; } /** * Throw a validation exception with a translated message that is show in * the UI. * * @param context the current faces context * @param message the error message * @param messageArgs the parameters for the message * * @since 5.9.2 */ private void throwValidationException(FacesContext context, String message, Object... messageArgs) { FacesMessage fmessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context, message, messageArgs), null); throw new ValidatorException(fmessage); } /** * Return the value of the JSF component who's id is references in an * attribute of the componet passed in parameter. * * @param string the attribute holding the target component id * @param component the component holding the attribute * @return the UIInput component, null otherwise * * @since 5.9.2 */ private UIInput getReferencedComponent(String attribute, UIComponent component) { Map<String, Object> attributes = component.getAttributes(); String targetComponentId = (String) attributes.get(attribute); if (targetComponentId == null) { log.error(String.format( "Target component id (%s) not found in attributes", attribute)); return null; } UIInput targetComponent = (UIInput) component.findComponent(targetComponentId); if (targetComponent == null) { return null; } return targetComponent; } public void validatePassword(FacesContext context, UIComponent component, Object value) { Object firstPassword = getReferencedComponent("firstPasswordInputId", component).getLocalValue(); Object secondPassword = getReferencedComponent("secondPasswordInputId", component).getLocalValue(); if (firstPassword == null || secondPassword == null) { log.error("Cannot validate passwords: value(s) not found"); return; } if (!firstPassword.equals(secondPassword)) { throwValidationException(context, "label.userManager.password.not.match"); } } /** * Create a new user using the "Invite" action. * * @param inviteAnotherUser Invite another user after executing the action. * @throws ClientException * * @since 5.9.3 */ public void inviteUser(boolean inviteAnotherUser) throws ClientException { try { Map<String, Serializable> additionnalInfo = new HashMap<String, Serializable>(); UserInvitationService userRegistrationService = Framework.getLocalService(UserInvitationService.class); userRegistrationService.submitRegistrationRequest(newUser, additionnalInfo, EMAIL, true); newUser = null; // Set the default value for the creation immediateCreation = false; facesMessages.add( StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get( "info.userManager.userInvited")); // Set the flags used for the display if (inviteAnotherUser) { showCreateForm = true; } else { showCreateForm = false; showUserOrGroup = false; detailsMode = null; } } catch (UserAlreadyExistsException e) { facesMessages.add( StatusMessage.Severity.ERROR, resourcesAccessor.getMessages().get( "error.userManager.userAlreadyExists")); } } @Factory(value = "notReadOnly", scope = APPLICATION) public boolean isNotReadOnly() { return !Framework.isBooleanPropertyTrue("org.nuxeo.ecm.webapp.readonly.mode"); } public List<String> getUserVirtualGroups(String userId) throws Exception { NuxeoPrincipal principal = userManager.getPrincipal(userId); if (principal instanceof NuxeoPrincipalImpl) { NuxeoPrincipalImpl user = (NuxeoPrincipalImpl) principal; return user.getVirtualGroups(); } return null; } public String viewUser(String userName) throws ClientException { webActions.setCurrentTabIds(MAIN_TAB_HOME + "," + USERS_TAB); setSelectedUser(userName); showUserOrGroup = true; // do not reset the state before actually viewing the user shouldResetStateOnTabChange = false; return VIEW_HOME; } public String viewUser() throws ClientException { if (selectedUser != null) { return viewUser(selectedUser.getId()); } else { return null; } } /** * @since 5.5 */ public void setShowUser(String showUser) { showUserOrGroup = Boolean.valueOf(showUser); } protected void fireSeamEvent(String eventName) { Events evtManager = Events.instance(); evtManager.raiseEvent(eventName); } @Factory(value = "anonymousUserDefined", scope = APPLICATION) public boolean anonymousUserDefined() throws ClientException { return userManager.getAnonymousUserId() != null; } @Observer(value = { USERS_LISTING_CHANGED }) public void onUsersListingChanged() { contentViewActions.refreshOnSeamEvent(USERS_LISTING_CHANGED); contentViewActions.resetPageProviderOnSeamEvent(USERS_LISTING_CHANGED); } @Observer(value = { USERS_SEARCH_CHANGED }) public void onUsersSearchChanged() { contentViewActions.refreshOnSeamEvent(USERS_SEARCH_CHANGED); contentViewActions.resetPageProviderOnSeamEvent(USERS_SEARCH_CHANGED); } @Observer(value = { SELECTED_LETTER_CHANGED }) public void onSelectedLetterChanged() { contentViewActions.refreshOnSeamEvent(SELECTED_LETTER_CHANGED); contentViewActions.resetPageProviderOnSeamEvent(SELECTED_LETTER_CHANGED); } @Observer(value = { CURRENT_TAB_CHANGED_EVENT + "_" + MAIN_TABS_CATEGORY, CURRENT_TAB_CHANGED_EVENT + "_" + NUXEO_ADMIN_CATEGORY, CURRENT_TAB_CHANGED_EVENT + "_" + USER_CENTER_CATEGORY, CURRENT_TAB_CHANGED_EVENT + "_" + USERS_GROUPS_MANAGER_SUB_TAB, CURRENT_TAB_CHANGED_EVENT + "_" + USERS_GROUPS_HOME_SUB_TAB, CURRENT_TAB_SELECTED_EVENT + "_" + MAIN_TABS_CATEGORY, CURRENT_TAB_SELECTED_EVENT + "_" + NUXEO_ADMIN_CATEGORY, CURRENT_TAB_SELECTED_EVENT + "_" + USER_CENTER_CATEGORY, CURRENT_TAB_SELECTED_EVENT + "_" + USERS_GROUPS_MANAGER_SUB_TAB, CURRENT_TAB_SELECTED_EVENT + "_" + USERS_GROUPS_HOME_SUB_TAB }) public void resetState() { if (shouldResetStateOnTabChange) { newUser = null; selectedUser = null; showUserOrGroup = false; showCreateForm = false; immediateCreation = false; detailsMode = DETAILS_VIEW_MODE; } } /** * * @return The type of creation for the user. * * @since 5.9.3 */ public boolean isImmediateCreation() { return immediateCreation; } /** * * @param immediateCreation * * @since 5.9.3 */ public void setImmediateCreation(boolean immediateCreation) { this.immediateCreation = immediateCreation; } /** * Changes the type of newUser depending of the value defined in the * checkbox "Set for immediate creation". Sets also the values previously * entered in the form into the new DocumentModel. * * @param event * * @since 5.9.3 */ public void changeTypeUserModel(ValueChangeEvent event) throws ClientException { Object newValue = event.getNewValue(); if (newValue instanceof Boolean) { if ((Boolean) newValue) { newUser = userManager.getBareUserModel(); } else { UserInvitationService userRegistrationService = Framework.getLocalService(UserInvitationService.class); newUser = userRegistrationService.getUserRegistrationModel(null); } } } }