| /* |
| */ |
| package org.taverna.server.master.identity; |
| |
| import static org.apache.commons.logging.LogFactory.getLog; |
| import static org.taverna.server.master.TavernaServer.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.defaults.Default.AUTHORITY_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.annotation.PreDestroy; |
| import javax.jdo.annotations.PersistenceAware; |
| |
| import org.apache.commons.logging.Log; |
| import org.springframework.beans.factory.annotation.Required; |
| 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.crypto.password.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.CallTimeLogger.PerfLogged; |
| 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, |
| UserStoreAPI { |
| /** The logger for the user store. */ |
| private transient Log log = getLog("Taverna.Server.UserDB"); |
| |
| public UserStore() { |
| super(User.class); |
| } |
| |
| @PreDestroy |
| void closeLog() { |
| log = null; |
| } |
| |
| private Map<String, BootstrapUserInfo> base = new HashMap<>(); |
| private String defLocalUser; |
| private PasswordEncoder encoder; |
| private volatile int epoch; |
| |
| /** |
| * 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.encode(password)); |
| } |
| |
| 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"); |
| else if (!getUsers().isEmpty()) |
| log.info("using existing users from database"); |
| else |
| 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; |
| epoch++; |
| } |
| |
| @Override |
| @PerfLogged |
| @WithinSingleTransaction |
| @ManagedAttribute(description = "The list of server accounts known about.", currencyTimeLimit = 30) |
| public List<String> getUserNames() { |
| return getUsers(); |
| } |
| |
| @Override |
| @PerfLogged |
| @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. |
| */ |
| @PerfLogged |
| @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<>(); |
| 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. |
| */ |
| @PerfLogged |
| @WithinSingleTransaction |
| public List<UserDetails> listUsers() { |
| ArrayList<UserDetails> result = new ArrayList<>(); |
| for (String id : getUsers()) |
| result.add(detach(getById(id))); |
| return result; |
| } |
| |
| @Override |
| @PerfLogged |
| @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"); |
| if (getById(username) != null) |
| throw new IllegalArgumentException("user name already exists"); |
| 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); |
| epoch++; |
| } |
| |
| @Override |
| @PerfLogged |
| @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); |
| epoch++; |
| } |
| } |
| |
| @Override |
| @PerfLogged |
| @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"); |
| epoch++; |
| } |
| } |
| |
| @Override |
| @PerfLogged |
| @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); |
| epoch++; |
| } |
| } |
| |
| @Override |
| @PerfLogged |
| @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); |
| epoch++; |
| } |
| } |
| |
| @Override |
| @PerfLogged |
| @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); |
| epoch++; |
| } |
| |
| @Override |
| @PerfLogged |
| @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?"); |
| } |
| |
| int getEpoch() { |
| return epoch; |
| } |
| |
| public static class CachedUserStore implements UserDetailsService { |
| private int epoch; |
| private Map<String, UserDetails> cache = new HashMap<>(); |
| private UserStore realStore; |
| |
| @Required |
| public void setRealStore(UserStore store) { |
| this.realStore = store; |
| } |
| |
| @Override |
| @PerfLogged |
| public UserDetails loadUserByUsername(String username) { |
| int epoch = realStore.getEpoch(); |
| UserDetails details; |
| synchronized (cache) { |
| if (epoch != this.epoch) { |
| cache.clear(); |
| this.epoch = epoch; |
| details = null; |
| } else |
| details = cache.get(username); |
| } |
| if (details == null) { |
| details = realStore.loadUserByUsername(username); |
| synchronized (cache) { |
| cache.put(username, details); |
| } |
| } |
| return details; |
| } |
| } |
| |
| 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(AUTHORITY_PREFIX)) |
| u.setLocalUsername(a.substring(AUTHORITY_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.encode(pass)); |
| u.setDisabled(false); |
| return u; |
| } |
| } |
| } |