org.nuxeo.ecm.webapp.security.UserManagementActions.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.webapp.security.UserManagementActions.java

Source

/*
 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 * 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.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.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

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.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.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.api.repository.RepositoryManager;
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.UserAdapter;
import org.nuxeo.ecm.platform.usermanager.UserAdapterImpl;
import org.nuxeo.ecm.platform.usermanager.exceptions.InvalidPasswordException;
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;

    protected boolean createAnotherUser = false;

    protected String defaultRepositoryName = null;

    protected String oldPassword;

    @Override
    protected String computeListingMode() {
        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) {
        setSelectedUser(refreshUser(userName));
    }

    /**
     * UserRegistrationService userRegistrationService = Framework.getLocalService(UserRegistrationService.class);
     *
     * @since 5.5
     */
    public void setSelectedUserName(String userName) {
        setSelectedUser(refreshUser(userName));
    }

    public String getSelectedUserName() {
        return selectedUser.getId();
    }

    // refresh to get references
    protected DocumentModel refreshUser(String userName) {
        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() {
        if (newUser == null) {
            newUser = userManager.getBareUserModel();
        }
        return newUser;
    }

    public boolean getAllowEditUser() {
        return selectedUser != null && getCanEditUsers(true) && !BaseSession.isReadOnlyEntry(selectedUser);
    }

    protected boolean getCanEditUsers(boolean allowCurrentUser) {
        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() {
        return selectedUser != null && getCanEditUsers(true) && !BaseSession.isReadOnlyEntry(selectedUser);
    }

    public boolean getAllowCreateUser() {
        return getCanEditUsers(false);
    }

    public boolean getAllowDeleteUser() {
        return selectedUser != null && getCanEditUsers(false) && !BaseSession.isReadOnlyEntry(selectedUser);
    }

    public void clearSearch() {
        searchString = null;
        fireSeamEvent(USERS_SEARCH_CHANGED);
    }

    public void createUser() {
        try {
            if (immediateCreation) {
                // Create the user with password
                setSelectedUser(userManager.createUser(newUser));
                // 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);
            } else {
                UserInvitationService userRegistrationService = Framework
                        .getLocalService(UserInvitationService.class);
                Map<String, Serializable> additionalInfos = new HashMap<String, Serializable>();
                // Wrap the form as an invitation to the user
                UserAdapter newUserAdapter = new UserAdapterImpl(newUser, userManager);
                DocumentModel userRegistrationDoc = wrapToUserRegistration(newUserAdapter);
                userRegistrationService.submitRegistrationRequest(userRegistrationDoc, additionalInfos, EMAIL,
                        true);

                facesMessages.add(StatusMessage.Severity.INFO,
                        resourcesAccessor.getMessages().get("info.userManager.userInvited"));
                if (createAnotherUser) {
                    showCreateForm = true;
                } else {
                    showCreateForm = false;
                    showUserOrGroup = false;
                    detailsMode = null;
                }

            }
            newUser = null;

        } catch (UserAlreadyExistsException e) {
            facesMessages.add(StatusMessage.Severity.ERROR,
                    resourcesAccessor.getMessages().get("error.userManager.userAlreadyExists"));
        } catch (InvalidPasswordException e) {
            facesMessages.add(StatusMessage.Severity.ERROR,
                    resourcesAccessor.getMessages().get("error.userManager.invalidPassword"));
        } catch (Exception e) {
            String message = e.getLocalizedMessage();
            if (e.getCause() != null) {
                message += e.getCause().getLocalizedMessage();
            }
            log.error(message, e);

            facesMessages.add(StatusMessage.Severity.ERROR, message);

        }
    }

    private String getDefaultRepositoryName() {
        if (defaultRepositoryName == null) {
            try {
                defaultRepositoryName = Framework.getService(RepositoryManager.class).getDefaultRepository()
                        .getName();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return defaultRepositoryName;
    }

    public void updateUser() {
        try {
            UpdateUserUnrestricted runner = new UpdateUserUnrestricted(getDefaultRepositoryName(), selectedUser);
            runner.runUnrestricted();
        } catch (InvalidPasswordException e) {
            facesMessages.add(StatusMessage.Severity.ERROR,
                    resourcesAccessor.getMessages().get("error.userManager.invalidPassword"));
        }

        detailsMode = DETAILS_VIEW_MODE;
        fireSeamEvent(USERS_LISTING_CHANGED);
    }

    public String changePassword() {
        try {
            updateUser();
        } catch (InvalidPasswordException e) {
            facesMessages.add(StatusMessage.Severity.ERROR,
                    resourcesAccessor.getMessages().get("error.userManager.invalidPassword"));
            return null;
        }
        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;
    }

    /**
     * @since 8.2
     */
    public String updateProfilePassword() {

        if (userManager.checkUsernamePassword(currentUser.getName(), oldPassword)) {

            try {
                doAsSystemUser(new Runnable() {
                    @Override
                    public void run() {
                        userManager.updateUser(selectedUser);
                    }

                });
            } catch (InvalidPasswordException e) {
                facesMessages.add(StatusMessage.Severity.ERROR,
                        resourcesAccessor.getMessages().get("error.userManager.invalidPassword"));
                return null;
            }
        } else {
            String message = resourcesAccessor.getMessages().get("label.userManager.old.password.error");
            facesMessages.add(FacesMessage.SEVERITY_ERROR, message);
            return null;
        }

        String message = resourcesAccessor.getMessages().get("label.userManager.password.changed");
        facesMessages.add(FacesMessage.SEVERITY_INFO, message);
        detailsMode = DETAILS_VIEW_MODE;
        fireSeamEvent(USERS_LISTING_CHANGED);

        return null;
    }

    protected void doAsSystemUser(Runnable runnable) {
        LoginContext loginContext;
        try {
            loginContext = Framework.login();
        } catch (LoginException e) {
            throw new NuxeoException(e);
        }

        try {
            runnable.run();
        } finally {
            try {
                // Login context may be null in tests
                if (loginContext != null) {
                    loginContext.logout();
                }
            } catch (LoginException e) {
                throw new NuxeoException("Cannot log out system user", e);
            }
        }
    }

    public void deleteUser() {
        userManager.deleteUser(selectedUser);
        selectedUser = null;
        showUserOrGroup = false;
        fireSeamEvent(USERS_LISTING_CHANGED);
    }

    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;
        }
        if (!isAllowedToAdminGroups(groups)) {
            throwValidationException(context, "label.userManager.invalidGroupSelected");
        }
    }

    /**
     * Checks if the current user is allowed to aministrate (meaning add/remove) the given groups.
     *
     * @param groups
     * @return
     * @since 5.9.2
     */
    boolean isAllowedToAdminGroups(List<String> groups) {
        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 attribute 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");
        }

    }

    private DocumentModel wrapToUserRegistration(UserAdapter newUserAdapter) {
        UserInvitationService userRegistrationService = Framework.getLocalService(UserInvitationService.class);
        DocumentModel newUserRegistration = userRegistrationService.getUserRegistrationModel(null);

        // Map the values from the object filled in the form
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoUsernameField(),
                newUserAdapter.getName());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoFirstnameField(),
                newUserAdapter.getFirstName());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoLastnameField(),
                newUserAdapter.getLastName());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoEmailField(),
                newUserAdapter.getEmail());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoGroupsField(),
                newUserAdapter.getGroups().toArray());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoTenantIdField(),
                newUserAdapter.getTenantId());
        newUserRegistration.setPropertyValue(userRegistrationService.getConfiguration().getUserInfoCompanyField(),
                newUserAdapter.getCompany());

        return newUserRegistration;
    }

    @Factory(value = "notReadOnly", scope = APPLICATION)
    public boolean isNotReadOnly() {
        return !Framework.isBooleanPropertyTrue("org.nuxeo.ecm.webapp.readonly.mode");
    }

    public List<String> getUserVirtualGroups(String userId) {
        NuxeoPrincipal principal = userManager.getPrincipal(userId);
        if (principal instanceof NuxeoPrincipalImpl) {
            NuxeoPrincipalImpl user = (NuxeoPrincipalImpl) principal;
            return user.getVirtualGroups();
        }
        return null;
    }

    public String viewUser(String userName) {
        webActions.setCurrentTabIds(MAIN_TAB_HOME + "," + USERS_TAB);
        setSelectedUser(userName);
        setShowUser(Boolean.TRUE.toString());
        return VIEW_HOME;
    }

    public String viewUser() {
        if (selectedUser != null) {
            return viewUser(selectedUser.getId());
        } else {
            return null;
        }
    }

    /**
     * @since 5.5
     */
    public void setShowUser(String showUser) {
        showUserOrGroup = Boolean.valueOf(showUser);
        // do not reset the state before actually viewing the user
        shouldResetStateOnTabChange = false;
    }

    protected void fireSeamEvent(String eventName) {
        Events evtManager = Events.instance();
        evtManager.raiseEvent(eventName);
    }

    @Factory(value = "anonymousUserDefined", scope = APPLICATION)
    public boolean anonymousUserDefined() {
        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;
    }

    public boolean isCreateAnotherUser() {
        return createAnotherUser;
    }

    public void setCreateAnotherUser(boolean createAnotherUser) {
        this.createAnotherUser = createAnotherUser;
    }

    public String getOldPassword() {
        return oldPassword;
    }

    public void setOldPassword(String oldPassword) {
        this.oldPassword = oldPassword;
    }
}