org.taverna.server.master.identity.UserStore.java Source code

Java tutorial

Introduction

Here is the source code for org.taverna.server.master.identity.UserStore.java

Source

/*
 * Copyright (C) 2011-2012 The University of Manchester
 * 
 * See the file "LICENSE.txt" for license terms.
 */
package org.taverna.server.master.identity;

import static org.apache.commons.logging.LogFactory.getLog;
import static org.taverna.server.master.TavernaServerImpl.JMX_ROOT;
import static org.taverna.server.master.common.Roles.ADMIN;
import static org.taverna.server.master.common.Roles.USER;
import static org.taverna.server.master.identity.AuthorityDerivedIDMapper.DEFAULT_PREFIX;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.jdo.annotations.PersistenceAware;

import org.apache.commons.logging.Log;
import org.springframework.dao.DataAccessException;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;
import org.taverna.server.master.utils.JDOSupport;

/**
 * The bean class that is responsible for managing the users in the database.
 * 
 * @author Donal Fellows
 */
@PersistenceAware
@ManagedResource(objectName = JMX_ROOT + "Users", description = "The user database.")
public class UserStore extends JDOSupport<User> implements UserDetailsService {
    /** The logger for the user store. */
    private static final Log log = getLog("Taverna.Server.UserDB");

    public UserStore() {
        super(User.class);
    }

    private Map<String, BootstrapUserInfo> base = new HashMap<String, BootstrapUserInfo>();
    private String defLocalUser;
    private PasswordEncoder encoder;

    /**
     * Install the encoder that will be used to turn a plaintext password into
     * something that it is safe to store in the database.
     * 
     * @param encoder
     *            The password encoder bean to install.
     */
    public void setEncoder(PasswordEncoder encoder) {
        this.encoder = encoder;
    }

    public void setBaselineUserProperties(Properties props) {
        UserAttributeEditor parser = new UserAttributeEditor();

        for (Object name : props.keySet()) {
            String username = (String) name;
            String value = props.getProperty(username);

            // Convert value to a password, enabled setting, and list of granted
            // authorities
            parser.setAsText(value);

            UserAttribute attr = (UserAttribute) parser.getValue();
            if (attr != null && attr.isEnabled())
                base.put(username, new BootstrapUserInfo(username, attr));
        }
    }

    private void installPassword(User u, String password) {
        u.setEncodedPassword(encoder.encodePassword(password, u.getUsername()));
    }

    public void setDefaultLocalUser(String defLocalUser) {
        this.defLocalUser = defLocalUser;
    }

    @SuppressWarnings("unchecked")
    private List<String> getUsers() {
        return (List<String>) namedQuery("users").execute();
    }

    @WithinSingleTransaction
    @PostConstruct
    void initDB() {
        if (base == null || base.isEmpty()) {
            log.warn("no baseline user collection");
            return;
        }
        if (!getUsers().isEmpty()) {
            log.info("using existing users from database");
            return;
        }
        for (String username : base.keySet()) {
            BootstrapUserInfo ud = base.get(username);
            if (ud == null)
                continue;
            User u = ud.get(encoder);
            if (u == null)
                continue;
            log.info("bootstrapping user " + username + " in the database");
            persist(u);
        }
        base = null;
    }

    /**
     * List the currently-known account names.
     * 
     * @return A list of users in the database. Note that this is a snapshot.
     */
    @WithinSingleTransaction
    @ManagedAttribute(description = "The list of server accounts known about.", currencyTimeLimit = 30)
    public List<String> getUserNames() {
        return getUsers();
    }

    /**
     * Get a particular user's description.
     * 
     * @param userName
     *            The username to look up.
     * @return A <i>copy</i> of the user description.
     */
    @WithinSingleTransaction
    public User getUser(String userName) {
        return detach(getById(userName));
    }

    /**
     * Get information about a server account.
     * 
     * @param userName
     *            The username to look up.
     * @return A description map intended for use by a server admin over JMX.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Get information about a server account.")
    @ManagedOperationParameters(@ManagedOperationParameter(name = "userName", description = "The username to look up."))
    public Map<String, String> getUserInfo(String userName) {
        User u = getById(userName);
        Map<String, String> info = new HashMap<String, String>();
        info.put("name", u.getUsername());
        info.put("admin", u.isAdmin() ? "yes" : "no");
        info.put("enabled", u.isEnabled() ? "yes" : "no");
        info.put("localID", u.getLocalUsername());
        return info;
    }

    /**
     * Get a list of all the users in the database.
     * 
     * @return A list of user details, <i>copied</i> out of the database.
     */
    @WithinSingleTransaction
    public List<UserDetails> listUsers() {
        ArrayList<UserDetails> result = new ArrayList<UserDetails>();
        for (String id : getUsers())
            result.add(detach(getById(id)));
        return result;
    }

    /**
     * Create a new user account; the account will be disabled and
     * non-administrative by default. Does not create any underlying system
     * account.
     * 
     * @param username
     *            The username to create.
     * @param password
     *            The password to use.
     * @param coupleLocalUsername
     *            Whether to set the local user name to the 'main' one.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Create a new user account; the account will be disabled and non-administrative by default. Does not create any underlying system account.")
    @ManagedOperationParameters({
            @ManagedOperationParameter(name = "username", description = "The username to create."),
            @ManagedOperationParameter(name = "password", description = "The password to use."),
            @ManagedOperationParameter(name = "coupleLocalUsername", description = "Whether to set the local user name to the 'main' one.") })
    public void addUser(String username, String password, boolean coupleLocalUsername) {
        if (username.matches(".*[^a-zA-Z0-9].*"))
            throw new IllegalArgumentException("bad user name; must be pure alphanumeric");
        User u = new User();
        u.setDisabled(true);
        u.setAdmin(false);
        u.setUsername(username);
        installPassword(u, password);
        if (coupleLocalUsername)
            u.setLocalUsername(username);
        else
            u.setLocalUsername(defLocalUser);
        log.info("creating user for " + username);
        persist(u);
    }

    /**
     * Set or clear whether this account is enabled. Disabled accounts cannot be
     * used to log in.
     * 
     * @param username
     *            The username to adjust.
     * @param enabled
     *            Whether to enable the account.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Set or clear whether this account is enabled. Disabled accounts cannot be used to log in.")
    @ManagedOperationParameters({
            @ManagedOperationParameter(name = "username", description = "The username to adjust."),
            @ManagedOperationParameter(name = "enabled", description = "Whether to enable the account.") })
    public void setUserEnabled(String username, boolean enabled) {
        User u = getById(username);
        if (u != null) {
            u.setDisabled(!enabled);
            log.info((enabled ? "enabling" : "disabling") + " user " + username);
        }
    }

    /**
     * Set or clear the mark on an account that indicates that it has
     * administrative privileges.
     * 
     * @param username
     *            The username to adjust.
     * @param admin
     *            Whether the account has admin privileges.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Set or clear the mark on an account that indicates that it has administrative privileges.")
    @ManagedOperationParameters({
            @ManagedOperationParameter(name = "username", description = "The username to adjust."),
            @ManagedOperationParameter(name = "admin", description = "Whether the account has admin privileges.") })
    public void setUserAdmin(String username, boolean admin) {
        User u = getById(username);
        if (u != null) {
            u.setAdmin(admin);
            log.info((admin ? "enabling" : "disabling") + " user " + username + " admin status");
        }
    }

    /**
     * Change the password for an account.
     * 
     * @param username
     *            The username to adjust.
     * @param password
     *            The new password to use.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Change the password for an account.")
    @ManagedOperationParameters({
            @ManagedOperationParameter(name = "username", description = "The username to adjust."),
            @ManagedOperationParameter(name = "password", description = "The new password to use.") })
    public void setUserPassword(String username, String password) {
        User u = getById(username);
        if (u != null) {
            installPassword(u, password);
            log.info("changing password for user " + username);
        }
    }

    /**
     * Change what local system account to use for a server account.
     * 
     * @param username
     *            The username to adjust.
     * @param localUsername
     *            The new local user account use.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Change what local system account to use for a server account.")
    @ManagedOperationParameters({
            @ManagedOperationParameter(name = "username", description = "The username to adjust."),
            @ManagedOperationParameter(name = "password", description = "The new local user account use.") })
    public void setUserLocalUser(String username, String localUsername) {
        User u = getById(username);
        if (u != null) {
            u.setLocalUsername(localUsername);
            log.info("mapping user " + username + " to local account " + localUsername);
        }
    }

    /**
     * Delete a server account. The underlying system account is not modified.
     * 
     * @param username
     *            The username to delete.
     */
    @WithinSingleTransaction
    @ManagedOperation(description = "Delete a server account. The underlying system account is not modified.")
    @ManagedOperationParameters(@ManagedOperationParameter(name = "username", description = "The username to delete."))
    public void deleteUser(String username) {
        delete(getById(username));
        log.info("deleting user " + username);
    }

    @Override
    @WithinSingleTransaction
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        User u;
        if (base != null) {
            log.warn("bootstrap user store still installed!");
            BootstrapUserInfo ud = base.get(username);
            if (ud != null) {
                log.warn("retrieved production credentials for " + username + " from bootstrap store");
                u = ud.get(encoder);
                if (u != null)
                    return u;
            }
        }
        try {
            u = detach(getById(username));
        } catch (NullPointerException npe) {
            throw new UsernameNotFoundException("who are you?");
        } catch (Exception ex) {
            throw new UsernameNotFoundException("who are you?", ex);
        }
        if (u != null)
            return u;
        throw new UsernameNotFoundException("who are you?");
    }

    private static class BootstrapUserInfo {
        private String user;
        private String pass;
        private Collection<GrantedAuthority> auth;

        BootstrapUserInfo(String username, UserAttribute attr) {
            user = username;
            pass = attr.getPassword();
            auth = attr.getAuthorities();
        }

        User get(PasswordEncoder encoder) {
            User u = new User();
            boolean realUser = false;
            for (GrantedAuthority ga : auth) {
                String a = ga.getAuthority();
                if (a.startsWith(DEFAULT_PREFIX))
                    u.setLocalUsername(a.substring(DEFAULT_PREFIX.length()));
                else if (a.equals(USER))
                    realUser = true;
                else if (a.equals(ADMIN))
                    u.setAdmin(true);
            }
            if (!realUser)
                return null;
            u.setUsername(user);
            u.setEncodedPassword(encoder.encodePassword(pass, user));
            u.setDisabled(false);
            return u;
        }
    }
}