com.thoughtworks.go.server.service.UserService.java Source code

Java tutorial

Introduction

Here is the source code for com.thoughtworks.go.server.service.UserService.java

Source

/*
 * Copyright 2019 ThoughtWorks, Inc.
 *
 * 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.
 */

package com.thoughtworks.go.server.service;

import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.exceptions.EntityType;
import com.thoughtworks.go.config.exceptions.RecordNotFoundException;
import com.thoughtworks.go.domain.Users;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.exception.ValidationException;
import com.thoughtworks.go.presentation.TriStateSelection;
import com.thoughtworks.go.presentation.UserModel;
import com.thoughtworks.go.presentation.UserSearchModel;
import com.thoughtworks.go.server.dao.UserDao;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.exceptions.UserEnabledException;
import com.thoughtworks.go.server.security.OnlyKnownUsersAllowedException;
import com.thoughtworks.go.server.service.result.BulkUpdateUsersOperationResult;
import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.util.TriState;
import com.thoughtworks.go.util.comparator.AlphaAsciiCollectionComparator;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;

import java.util.*;

import static com.thoughtworks.go.serverhealth.HealthStateScope.GLOBAL;
import static com.thoughtworks.go.serverhealth.HealthStateType.general;
import static org.apache.commons.lang3.StringUtils.join;

@Service
public class UserService {
    private final UserDao userDao;
    private final SecurityService securityService;
    private final GoConfigService goConfigService;
    private final TransactionTemplate transactionTemplate;

    private final Object disableUserMutex = new Object();
    private final Object enableUserMutex = new Object();

    @Autowired
    public UserService(UserDao userDao, SecurityService securityService, GoConfigService goConfigService,
            TransactionTemplate transactionTemplate) {
        this.userDao = userDao;
        this.securityService = securityService;
        this.goConfigService = goConfigService;
        this.transactionTemplate = transactionTemplate;
    }

    public void disable(final List<String> usersToBeDisabled, LocalizedOperationResult result) {
        synchronized (disableUserMutex) {
            if (willDisableAllAdmins(usersToBeDisabled)) {
                result.badRequest("There must be atleast one admin user enabled!");
                return;
            }
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    userDao.disableUsers(usersToBeDisabled);
                }
            });
        }
    }

    public boolean canUserTurnOffAutoLogin() {
        return !willDisableAllAdmins(new ArrayList<>());
    }

    private boolean willDisableAllAdmins(List<String> usersToBeDisabled) {
        List<String> enabledUserNames = toUserNames(userDao.enabledUsers());
        enabledUserNames.removeAll(usersToBeDisabled);
        return !userNameListContainsAdmin(enabledUserNames);
    }

    private List<String> toUserNames(List<User> enabledUsers) {
        List<String> enabledUserNames = new ArrayList<>();
        for (User enabledUser : enabledUsers) {
            enabledUserNames.add(enabledUser.getName());
        }
        return enabledUserNames;
    }

    private boolean userNameListContainsAdmin(List<String> enabledUserNames) {
        for (String enabledUserName : enabledUserNames) {
            if (securityService.isUserAdmin(new Username(new CaseInsensitiveString(enabledUserName)))) {
                return true;
            }
        }
        return false;
    }

    public User save(final User user, TriState enabled, TriState emailMe, String email, String checkinAliases,
            LocalizedOperationResult result) {
        if (enabled.isTrue()) {
            user.enable();
        }

        if (enabled.isFalse()) {
            user.disable();
        }

        if (email != null) {
            user.setEmail(email);
        }

        if (checkinAliases != null) {
            user.setMatcher(checkinAliases);
        }

        if (emailMe.isTrue()) {
            user.setEmailMe(true);
        }

        if (emailMe.isFalse()) {
            user.setEmailMe(false);
        }

        if (validate(result, user)) {
            return user;
        }

        try {
            saveOrUpdate(user);
        } catch (ValidationException e) {
            result.badRequest("Failed to add user. Validations failed. " + e.getMessage());
        }

        return user;
    }

    public void enable(List<String> usernames, LocalizedOperationResult result) {
        synchronized (enableUserMutex) {
            Set<String> potentialEnabledUsers = new HashSet<>(toUserNames(userDao.enabledUsers()));
            potentialEnabledUsers.addAll(usernames);
            userDao.enableUsers(usernames);
        }
    }

    public long enabledUserCount() {
        return userDao.enabledUserCount();
    }

    public long disabledUserCount() {
        return allUsersForDisplay().size() - enabledUserCount();
    }

    public void modifyRolesAndUserAdminPrivileges(final List<String> users, final TriStateSelection adminPrivilege,
            final List<TriStateSelection> roleSelections, LocalizedOperationResult result) {
        Users allUsers = userDao.allUsers();
        for (String user : users) {
            if (!allUsers.containsUserNamed(user)) {
                result.badRequest("User '" + user + "' does not exist in the database.");
                return;
            }
        }
        try {
            final GoConfigDao.CompositeConfigCommand command = new GoConfigDao.CompositeConfigCommand();
            command.addCommand(goConfigService.modifyRolesCommand(users, roleSelections));
            command.addCommand(goConfigService.modifyAdminPrivilegesCommand(users, adminPrivilege));
            goConfigService.updateConfig(command);
        } catch (Exception e) {
            result.badRequest("Failed to add role. Reason - '" + e.getMessage() + "'");
        }
    }

    public Set<String> allUsernames() {
        List<UserModel> userModels = allUsersForDisplay();
        Set<String> users = new HashSet<>();
        for (UserModel model : userModels) {
            users.add(model.getUser().getName());
        }
        return users;
    }

    public Collection<String> allRoleNames(CruiseConfig cruiseConfig) {
        List<String> roles = new ArrayList<>();
        for (Role role : allRoles(cruiseConfig)) {
            roles.add(CaseInsensitiveString.str(role.getName()));
        }
        return roles;
    }

    public Collection<String> allRoleNames() {
        return allRoleNames(goConfigService.cruiseConfig());
    }

    public Collection<Role> allRoles(CruiseConfig cruiseConfig) {
        return cruiseConfig.server().security().getRoles();
    }

    public Set<String> usersThatCanOperateOnStage(CruiseConfig cruiseConfig, PipelineConfig pipelineConfig) {
        SortedSet<String> users = new TreeSet<>();
        PipelineConfigs group = cruiseConfig.findGroupOfPipeline(pipelineConfig);
        if (group.hasAuthorizationDefined()) {
            if (group.hasOperationPermissionDefined()) {
                users.addAll(group.getOperateUserNames());
                List<String> roles = group.getOperateRoleNames();
                for (Role role : cruiseConfig.server().security().getRoles()) {
                    if (roles.contains(CaseInsensitiveString.str(role.getName()))) {
                        users.addAll(role.usersOfRole());
                    }
                }
            }
        } else {
            users.addAll(allUsernames());
        }
        return users;
    }

    public Set<String> rolesThatCanOperateOnStage(CruiseConfig cruiseConfig, PipelineConfig pipelineConfig) {
        PipelineConfigs group = cruiseConfig.findGroupOfPipeline(pipelineConfig);
        SortedSet<String> roles = new TreeSet<>();
        if (group.hasAuthorizationDefined()) {
            if (group.hasOperationPermissionDefined()) {
                roles.addAll(group.getOperateRoleNames());
            }
        } else {
            roles.addAll(allRoleNames(cruiseConfig));
        }
        return roles;
    }

    public User load(long id) {
        return userDao.load(id);
    }

    public void deleteUser(String username, String byWhom, HttpLocalizedOperationResult result) {
        try {
            userDao.deleteUser(username, byWhom);
            result.setMessage(EntityType.User.deleteSuccessful(username));
        } catch (RecordNotFoundException e) {
            result.notFound(EntityType.User.notFoundMessage(username), general(GLOBAL));
        } catch (UserEnabledException e) {
            result.badRequest("User '" + username + "' is not disabled.");
        }
    }

    public void deleteUsers(List<String> userNames, String byWhom, BulkUpdateUsersOperationResult result) {
        synchronized (enableUserMutex) {
            boolean isValid = performUserDeletionValidation(userNames, result);
            if (isValid) {
                userDao.deleteUsers(userNames, byWhom);
                result.setMessage(EntityType.User.deleteSuccessful(userNames));
            }
        }
    }

    public void bulkEnableDisableUsers(List<String> userNames, boolean shouldEnable,
            BulkUpdateUsersOperationResult result) {
        synchronized (enableUserMutex) {
            boolean isValid = performUserUpdateValidation(userNames, result);
            if (isValid) {
                if (shouldEnable) {
                    enable(userNames, result);
                } else {
                    disable(userNames, result);
                }

                if (result.isSuccessful()) {
                    String enabledOrDisabled = shouldEnable ? "enabled" : "disabled";
                    result.setMessage(String.format("Users '%s' were %s successfully.", join(userNames, ", "),
                            enabledOrDisabled));
                }
            }
        }
    }

    private boolean performUserDeletionValidation(List<String> userNames, BulkUpdateUsersOperationResult result) {
        boolean isValid = validateUserSelection(userNames, result);
        if (!isValid) {
            return false;
        }

        for (String userName : userNames) {
            User user = userDao.findUser(userName);
            if (user instanceof NullUser) {
                result.addNonExistentUserName(userName);
                result.unprocessableEntity("Deletion failed because some users do not exist.");
                continue;
            }
            if (user.isEnabled()) {
                result.addEnabledUserName(userName);
                result.unprocessableEntity("Deletion failed because some users were enabled.");
            }
        }

        return result.isSuccessful();
    }

    private boolean performUserUpdateValidation(List<String> userNames, BulkUpdateUsersOperationResult result) {
        boolean isValid = validateUserSelection(userNames, result);
        if (!isValid) {
            return false;
        }

        for (String userName : userNames) {
            User user = userDao.findUser(userName);
            if (user instanceof NullUser) {
                result.addNonExistentUserName(userName);
                result.unprocessableEntity("Update failed because some users do not exist.");
            }
        }

        return result.isSuccessful();
    }

    private boolean validateUserSelection(List<String> userNames, BulkUpdateUsersOperationResult result) {
        if (CollectionUtils.isEmpty(userNames)) {
            result.badRequest("No users selected.");
            return false;
        }
        return true;
    }

    public enum SortableColumn {
        EMAIL {
            protected String get(UserModel model) {
                return model.getUser().getEmail();
            }

        },
        USERNAME {
            protected String get(UserModel model) {
                return model.getUser().getName();
            }
        },
        ROLES {
            @Override
            public Comparator<UserModel> sorter() {
                return (one, other) -> STRING_COMPARATOR.compare(one.getRoles(), other.getRoles());
            }
        },
        MATCHERS {
            @Override
            public Comparator<UserModel> sorter() {
                return (one, other) -> STRING_COMPARATOR.compare(one.getUser().getMatchers(),
                        other.getUser().getMatchers());
            }
        },
        IS_ADMIN {
            @Override
            public Comparator<UserModel> sorter() {
                return (one, other) -> Boolean.compare(one.isAdmin(), other.isAdmin());
            }
        },
        ENABLED {
            @Override
            public Comparator<UserModel> sorter() {
                return (one, other) -> Boolean.compare(one.isEnabled(), other.isEnabled());
            }
        };

        private static final AlphaAsciiCollectionComparator<String> STRING_COMPARATOR = new AlphaAsciiCollectionComparator<>();

        public Comparator<UserModel> sorter() {
            return Comparator.comparing(this::get);
        }

        protected String get(UserModel model) {
            return null;
        }
    }

    public enum SortDirection {
        ASC {
            @Override
            public Comparator<UserModel> forColumn(final SortableColumn column) {
                return column.sorter();
            }
        },
        DESC {
            @Override
            public Comparator<UserModel> forColumn(final SortableColumn column) {
                return (one, other) -> column.sorter().compare(other, one);
            }
        };

        public abstract Comparator<UserModel> forColumn(SortableColumn column);
    }

    public void addUserIfDoesNotExist(User user) {
        synchronized (enableUserMutex) {
            if (!(user.isAnonymous() || userExists(user))) {
                assertUnknownUsersAreAllowedToLogin(user.getUsername());

                userDao.saveOrUpdate(user);
            }
        }
    }

    public void withEnableUserMutex(Runnable runnable) {
        synchronized (enableUserMutex) {
            runnable.run();
        }
    }

    private void assertUnknownUsersAreAllowedToLogin(Username username) {
        if (goConfigService.isOnlyKnownUserAllowedToLogin()) {
            throw new OnlyKnownUsersAllowedException(username.getUsername().toString(),
                    "Please ask the administrator to add you to GoCD.");
        }
    }

    public void saveOrUpdate(User user) throws ValidationException {
        validate(user);
        synchronized (enableUserMutex) {
            userDao.saveOrUpdate(user);
        }
    }

    private boolean userExists(User user) {
        User foundUser = userDao.findUser(user.getName());
        return !(foundUser instanceof NullUser);
    }

    public User findUserByName(String username) {
        return userDao.findUser(username);
    }

    public void addNotificationFilter(final long userId, final NotificationFilter filter) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                User user = userDao.load(userId);
                user.addNotificationFilter(filter);
                synchronized (enableUserMutex) {
                    userDao.saveOrUpdate(user);
                }
            }
        });
    }

    public void removeNotificationFilter(final long userId, final long filterId) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                User user = userDao.load(userId);
                user.removeNotificationFilter(filterId);
                userDao.saveOrUpdate(user);
            }
        });

    }

    public Users findValidSubscribers(final StageConfigIdentifier identifier) {
        Users users = userDao.findNotificationSubscribingUsers();
        return users.filter(user -> user.hasSubscribedFor(identifier.getPipelineName(), identifier.getStageName())
                && securityService.hasViewPermissionForPipeline(user.getUsername(), identifier.getPipelineName()));
    }

    public void validate(User user) throws ValidationException {
        user.validateLoginName();
        user.validateMatcher();
        user.validateEmail();
    }

    public List<UserModel> allUsersForDisplay(SortableColumn column, SortDirection direction) {
        List<UserModel> userModels = allUsersForDisplay();
        Comparator<UserModel> userModelComparator = direction.forColumn(column);

        userModels.sort(userModelComparator);
        return userModels;
    }

    private List<UserModel> allUsersForDisplay() {
        Collection<User> users = allUsers();
        ArrayList<UserModel> userModels = new ArrayList<>();
        for (User user : users) {
            String userName = user.getName();

            ArrayList<String> roles = new ArrayList<>();
            for (Role role : goConfigService.rolesForUser(new CaseInsensitiveString(userName))) {
                roles.add(CaseInsensitiveString.str(role.getName()));
            }

            userModels.add(new UserModel(user, roles,
                    securityService.isUserAdmin(new Username(new CaseInsensitiveString(userName)))));
        }
        return userModels;
    }

    public Collection<User> allUsers() {
        Set<User> result = new HashSet<>();
        result.addAll(userDao.allUsers());
        return result;
    }

    public void create(List<UserSearchModel> userSearchModels, HttpLocalizedOperationResult result) {
        if (userSearchModels.isEmpty()) {
            result.badRequest("No users selected.");
            return;
        }
        synchronized (enableUserMutex) {
            for (UserSearchModel userSearchModel : userSearchModels) {
                User user = userSearchModel.getUser();

                if (userExists(user)) {
                    result.conflict(EntityType.User.alreadyExists(user.getName()));
                    return;
                }

                if (user.isAnonymous()) {
                    result.badRequest("Failed to add user. Username '" + user.getName() + "' is not permitted.");
                    return;
                }

                if (validate(result, user)) {
                    return;
                }
                userDao.saveOrUpdate(user);
                result.setMessage("User '" + user.getName() + "' successfully added.");
            }
        }
    }

    public static class AdminAndRoleSelections {
        private final TriStateSelection adminSelection;
        private final List<TriStateSelection> roleSelections;

        public AdminAndRoleSelections(TriStateSelection adminSelection, List<TriStateSelection> roleSelections) {
            this.adminSelection = adminSelection;
            this.roleSelections = roleSelections;
        }

        public TriStateSelection getAdminSelection() {
            return adminSelection;
        }

        public List<TriStateSelection> getRoleSelections() {
            return roleSelections;
        }
    }

    public AdminAndRoleSelections getAdminAndRoleSelections(List<String> users) {
        final SecurityConfig securityConfig = goConfigService.security();
        Set<Role> roles = new HashSet<>(securityConfig.getRoles().getRoleConfigs());
        final List<TriStateSelection> roleSelections = TriStateSelection.forRoles(roles, users);
        final TriStateSelection adminSelection = TriStateSelection.forSystemAdmin(securityConfig.adminsConfig(),
                roles, new SecurityService.UserRoleMatcherImpl(securityConfig), users);
        return new AdminAndRoleSelections(adminSelection, roleSelections);
    }

    private boolean validate(LocalizedOperationResult result, User user) {
        try {
            validate(user);
        } catch (ValidationException e) {
            result.badRequest("Failed to add user. Validations failed. " + e.getMessage());
            return true;
        }
        return false;
    }

}