ubic.gemma.core.security.authentication.UserManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.core.security.authentication.UserManagerImpl.java

Source

/*
 * The Gemma project
 *
 * Copyright (c) 2006 University of British Columbia
 *
 * 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 ubic.gemma.core.security.authentication;

import gemma.gsec.AuthorityConstants;
import gemma.gsec.authentication.UserDetailsImpl;
import gemma.gsec.authentication.UserExistsException;
import gemma.gsec.authentication.UserManager;
import gemma.gsec.authentication.UserService;
import gemma.gsec.model.GroupAuthority;
import gemma.gsec.model.User;
import gemma.gsec.model.UserGroup;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * Implementation for Spring Security, plus some other handy methods.
 *
 * @author pavlidis
 */
@SuppressWarnings("unused")
@Transactional
@Service
public class UserManagerImpl implements UserManager {

    private final Log logger = LogFactory.getLog(this.getClass());

    private boolean enableAuthorities = false;

    private boolean enableGroups = true;

    private String rolePrefix = "GROUP_";

    @Autowired(required = false)
    private UserCache userCache = new NullUserCache();

    @Autowired
    private UserService userService;

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    @Transactional
    public String changePasswordForUser(String email, String username, String newPassword)
            throws AuthenticationException {
        Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();

        if (currentAuthentication == null) {
            // This would indicate bad coding somewhere
            throw new AccessDeniedException(
                    "Can't change password as no Authentication object found in context " + "for current user.");
        }

        User u = userService.findByEmail(email);

        if (u == null) {
            throw new UsernameNotFoundException("No user found for that email address.");
        }

        String foundUsername = u.getUserName();

        if (!foundUsername.equals(username)) {
            throw new AccessDeniedException("Wrong user name was provided for the email address.");
        }

        logger.debug("Changing password for user '" + username + "'");

        u.setPassword(newPassword);
        u.setEnabled(false);
        u.setSignupToken(this.generateSignupToken(username));
        u.setSignupTokenDatestamp(new Date());
        userService.update(u);

        userCache.removeUserFromCache(username);

        return u.getSignupToken();
    }

    @Override
    public Collection<String> findAllUsers() {
        Collection<User> users = userService.loadAll();

        List<String> result = new ArrayList<>();
        for (User u : users) {
            result.add(u.getUserName());
        }
        return result;
    }

    @Override
    @Secured({ "GROUP_USER", "RUN_AS_ADMIN" })
    public User findbyEmail(String emailAddress) {
        return this.findByEmail(emailAddress);
    }

    @Override
    @Secured({ "GROUP_USER", "RUN_AS_ADMIN" })
    public User findByEmail(String emailAddress) {
        return userService.findByEmail(emailAddress);
    }

    @Override
    public User findByUserName(String userName) {
        return this.userService.findByUserName(userName);
    }

    @Override
    public UserGroup findGroupByName(String name) {
        return this.userService.findGroupByName(name);
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_USER" })
    public Collection<String> findGroupsForUser(String userName) {

        Collection<String> result = new HashSet<>();

        if (!this.loggedIn()) {
            return result;
        }

        User u = this.loadUser(userName);
        Collection<UserGroup> groups = userService.findGroupsForUser(u);

        for (UserGroup g : groups) {
            result.add(g.getName());
        }

        return result;
    }

    @Override
    public String generateSignupToken(String username) {
        return RandomStringUtils.randomAlphanumeric(32).toUpperCase();
    }

    @Override
    public User getCurrentUser() {
        return this.getUserForUserName(this.getCurrentUsername());
    }

    @Override
    public String getCurrentUsername() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (auth == null || !auth.isAuthenticated()) {
            throw new IllegalStateException("Not authenticated!");
        }

        if (auth.getPrincipal() instanceof UserDetails) {
            return ((UserDetails) auth.getPrincipal()).getUsername();
        }
        return auth.getPrincipal().toString();
    }

    @Override
    public String getRolePrefix() {
        return rolePrefix;
    }

    public void setRolePrefix(String rolePrefix) {
        this.rolePrefix = rolePrefix;
    }

    @Override
    public boolean groupExists(String groupName) {
        return userService.groupExists(groupName);
    }

    @SuppressWarnings("unchecked") // Weird construct in gsec
    @Override
    public Collection<gemma.gsec.model.User> loadAll() {
        return new ArrayList<>(this.userService.loadAll());
    }

    @Override
    public boolean loggedIn() {
        Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
        // TODO: use a AuthenticationTrustResolver instead.
        return !(currentUser instanceof AnonymousAuthenticationToken);
    }

    @Override
    public void reauthenticate(String username, String password) {
        logger.warn("Not implemented.");
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    public boolean userWithEmailExists(String emailAddress) {
        return userService.findByEmail(emailAddress) != null;
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    public boolean validateSignupToken(String username, String key) {

        UserDetailsImpl u = (UserDetailsImpl) this.loadUserByUsername(username);

        if (u.isEnabled()) {
            logger.warn("User is already enabled, skipping token validation");
            return true;
        }

        String storedTok = u.getSignupToken();
        Date storedDate = u.getSignupTokenDatestamp();

        if (storedTok == null || storedDate == null) {
            throw new IllegalArgumentException("User does not have a token");
        }

        Date oneWeekAgo = DateUtils.addWeeks(new Date(), -2);

        if (!storedTok.equals(key) || storedDate.before(oneWeekAgo)) {
            return false;
        }

        u.setEnabled(true);

        this.updateUser(u);

        return true;
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    @Transactional
    public void createUser(UserDetails user) {

        /*
         * UserDetails is not an entity, so this method is not directly managed by the Audit or ACL advice. However, it
         * runs in a transaction and calls two service methods which are intercepted. This means it is intercepted
         * before the transaction is flushed.
         */

        this.validateUserName(user.getUsername());

        User u = ubic.gemma.model.common.auditAndSecurity.User.Factory.newInstance();
        u.setUserName(user.getUsername());
        u.setPassword(user.getPassword());
        u.setEnabled(user.isEnabled());

        if (user instanceof UserDetailsImpl) {
            u.setSignupToken(((UserDetailsImpl) user).getSignupToken());
            u.setSignupTokenDatestamp(((UserDetailsImpl) user).getSignupTokenDatestamp());
        }

        if (user instanceof UserDetailsImpl) {
            u.setEmail(((UserDetailsImpl) user).getEmail());
        }

        try {
            u = userService.create(u);
        } catch (UserExistsException e) {
            throw new RuntimeException(e);
        }

        // Add the user to the default user group.
        UserGroup g = this.loadGroup(AuthorityConstants.USER_GROUP_NAME);
        userService.addUserToGroup(g, u);

        /*
         * We don't log the user in automatically, because we require that new users click a confirmation link in an
         * email.
         */
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    @Transactional
    public void updateUser(UserDetails user) {
        String username = user.getUsername();
        User u = userService.findByUserName(username);
        if (u == null)
            throw new IllegalArgumentException("No user could be loaded with name=" + user);

        u.setPassword(user.getPassword());
        u.setEnabled(user.isEnabled());
        if (user instanceof UserDetailsImpl) {
            u.setEmail(((UserDetailsImpl) user).getEmail());
        }

        userService.update(u);

        userCache.removeUserFromCache(user.getUsername());
    }

    @Override
    @Transactional
    public void deleteUser(String username) {
        User user = this.loadUser(username);
        userService.delete(user);
        userCache.removeUserFromCache(username);
    }

    @Override
    @Secured({ "GROUP_USER" })
    @Transactional
    public void changePassword(String oldPassword, String newPassword) throws AuthenticationException {
        Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();

        if (currentAuthentication == null) {
            // This would indicate bad coding somewhere
            throw new AccessDeniedException(
                    "Can't change password as no Authentication object found in context " + "for current user.");
        }

        String username = currentAuthentication.getName();

        logger.debug("Changing password for user '" + username + "'");

        User u = this.loadUser(username);
        u.setPassword(newPassword);
        userService.update(u);

        SecurityContextHolder.getContext()
                .setAuthentication(this.createNewAuthentication(currentAuthentication, newPassword));

        userCache.removeUserFromCache(username);
    }

    @Override
    @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "RUN_AS_ADMIN" })
    public boolean userExists(String username) {
        return userService.findByUserName(username) != null;
    }

    @Override
    public List<String> findAllGroups() {
        Collection<UserGroup> groups = userService.listAvailableGroups();

        List<String> result = new ArrayList<>();
        for (UserGroup group : groups) {
            result.add(group.getName());
        }
        return result;
    }

    @Override
    public List<String> findUsersInGroup(String groupName) {

        UserGroup group = this.loadGroup(groupName);

        Collection<gemma.gsec.model.User> groupMembers = group.getGroupMembers();

        List<String> result = new ArrayList<>();
        for (gemma.gsec.model.User u : groupMembers) {
            result.add(u.getUserName());
        }
        return result;

    }

    @Override
    public void createGroup(String groupName, List<GrantedAuthority> authorities) {

        UserGroup g = ubic.gemma.model.common.auditAndSecurity.UserGroup.Factory.newInstance();
        g.setName(groupName);
        for (GrantedAuthority ga : authorities) {
            g.getAuthorities().add(
                    ubic.gemma.model.common.auditAndSecurity.GroupAuthority.Factory.newInstance(ga.getAuthority()));
        }

        userService.create(g);
    }

    @Override
    public void deleteGroup(String groupName) {
        UserGroup group = this.loadGroup(groupName);
        userService.delete(group);
    }

    @Override
    public void renameGroup(String oldName, String newName) {

        UserGroup group = userService.findGroupByName(oldName);

        group.setName(newName);

        userService.update(group);
    }

    @Override
    public void addUserToGroup(String username, String groupName) {
        User u = this.loadUser(username);
        UserGroup g = this.loadGroup(groupName);
        userService.addUserToGroup(g, u);
    }

    @Override
    public void removeUserFromGroup(String username, String groupName) {

        User user = userService.findByUserName(username);
        UserGroup group = userService.findGroupByName(groupName);

        if (user == null || group == null) {
            throw new IllegalArgumentException("User or group could not be read");
        }

        userService.removeUserFromGroup(user, group);
    }

    @Override
    public List<GrantedAuthority> findGroupAuthorities(String groupName) {

        String groupToSearch = groupName;
        if (groupName.startsWith(rolePrefix)) {
            groupToSearch = groupToSearch.replaceFirst(rolePrefix, "");
        }

        UserGroup group = this.loadGroup(groupToSearch);

        List<GrantedAuthority> result = new ArrayList<>();
        for (gemma.gsec.model.GroupAuthority ga : group.getAuthorities()) {
            result.add(new SimpleGrantedAuthority(ga.getAuthority()));
        }

        return result;
    }

    @Override
    public void addGroupAuthority(String groupName, GrantedAuthority authority) {
        UserGroup g = this.loadGroup(groupName);

        for (gemma.gsec.model.GroupAuthority ga : g.getAuthorities()) {
            if (ga.getAuthority().equals(authority.getAuthority())) {
                logger.warn("Group already has authority" + authority.getAuthority());
                return;
            }
        }

        GroupAuthority auth = ubic.gemma.model.common.auditAndSecurity.GroupAuthority.Factory.newInstance();
        auth.setAuthority(authority.getAuthority());

        g.getAuthorities().add(auth);

        userService.update(g);
    }

    @Override
    public void removeGroupAuthority(String groupName, GrantedAuthority authority) {

        UserGroup group = this.loadGroup(groupName);

        userService.removeGroupAuthority(group, authority.getAuthority());
    }

    public boolean isEnableAuthorities() {
        return enableAuthorities;
    }

    public void setEnableAuthorities(boolean enableAuthorities) {
        this.enableAuthorities = enableAuthorities;
    }

    public boolean isEnableGroups() {
        return enableGroups;
    }

    public void setEnableGroups(boolean enableGroups) {
        this.enableGroups = enableGroups;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        User user = this.loadUser(username);

        Set<GrantedAuthority> dbAuthsSet = new HashSet<>();

        if (enableAuthorities) {
            dbAuthsSet.addAll(this.loadUserAuthorities(user.getUserName()));
        }

        if (enableGroups) {
            dbAuthsSet.addAll(this.loadGroupAuthorities(user));
        }

        if (dbAuthsSet.isEmpty()) {
            throw new UsernameNotFoundException("User " + username + " has no GrantedAuthority");
        }

        List<GrantedAuthority> dbAuths = new ArrayList<>(dbAuthsSet);

        // addCustomAuthorities( user.getUsername(), dbAuths );

        return this.createUserDetails(username, new UserDetailsImpl(user), dbAuths);
    }

    protected List<UserDetails> loadUsersByUsername(String username) {
        List<UserDetails> result = new ArrayList<>();
        User u = this.loadUser(username);

        UserDetails ud = new UserDetailsImpl(u);

        result.add(ud);
        return result;
    }

    private Authentication createNewAuthentication(Authentication currentAuth, String newPassword) {
        UserDetails user = this.loadUserByUsername(currentAuth.getName());

        UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(user,
                user.getPassword(), user.getAuthorities());
        newAuthentication.setDetails(currentAuth.getDetails());

        return newAuthentication;
    }

    private UserDetails createUserDetails(String username, UserDetailsImpl userFromUserQuery,
            List<GrantedAuthority> combinedAuthorities) {
        return new UserDetailsImpl(userFromUserQuery.getPassword(), username, userFromUserQuery.isEnabled(),
                combinedAuthorities, userFromUserQuery.getEmail(), userFromUserQuery.getSignupToken(),
                userFromUserQuery.getSignupTokenDatestamp());
    }

    private List<GrantedAuthority> loadGroupAuthorities(User user) {
        Collection<GroupAuthority> authorities = userService.loadGroupAuthorities(user);

        List<GrantedAuthority> result = new ArrayList<>();
        for (GroupAuthority ga : authorities) {
            String roleName = this.getRolePrefix() + ga.getAuthority();
            result.add(new SimpleGrantedAuthority(roleName));
        }

        return result;
    }

    private List<GrantedAuthority> loadUserAuthorities(@SuppressWarnings("unused") String username) {
        throw new UnsupportedOperationException("Use the group-based authorities instead");
    }

    /**
     * @param username username
     * @return user, or null if the user is anonymous.
     * @throws UsernameNotFoundException if the user does not exist in the system
     */
    private User getUserForUserName(String username) throws UsernameNotFoundException {

        if (AuthorityConstants.ANONYMOUS_USER_NAME.equals(username)) {
            return null;
        }

        User u = userService.findByUserName(username);

        if (u == null) {
            throw new UsernameNotFoundException(username + " not found");
        }

        return u;
    }

    private UserGroup loadGroup(String groupName) {
        UserGroup group = userService.findGroupByName(groupName);

        if (group == null) {
            throw new UsernameNotFoundException("Group could not be read");
        }

        return group;
    }

    private User loadUser(String username) {
        User user = userService.findByUserName(username);
        if (user == null) {
            throw new UsernameNotFoundException("User with name " + username + " could not be loaded");
        }
        return user;
    }

    private void validateUserName(String username) {

        boolean ok = StringUtils.isNotBlank(username);
        if (AuthorityConstants.ADMIN_GROUP_AUTHORITY.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.ADMIN_GROUP_NAME.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.AGENT_GROUP_AUTHORITY.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.AGENT_GROUP_NAME.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.IS_AUTHENTICATED_ANONYMOUSLY.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.RUN_AS_ADMIN_AUTHORITY.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.USER_GROUP_AUTHORITY.equals(username)) {
            ok = false;
        } else if (AuthorityConstants.USER_GROUP_NAME.equals(username)) {
            ok = false;
        } else if (username.toUpperCase().startsWith(this.getRolePrefix())) {
            ok = false;
        }

        if (!ok) {
            throw new IllegalArgumentException("Username=" + username + " is not allowed");
        }

    }

}