/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security.impl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Logger; import org.geoserver.platform.resource.Resource; import org.geoserver.security.GeoServerSecurityManager; import org.geoserver.security.GeoServerUserGroupService; import org.geoserver.security.GeoServerUserGroupStore; import org.geoserver.security.config.SecurityNamedServiceConfig; import org.geoserver.security.event.UserGroupLoadedListener; import org.geoserver.security.password.GeoServerPasswordEncoder; import org.geoserver.security.validation.PasswordPolicyException; import org.geoserver.security.validation.PasswordValidatorImpl; import org.springframework.dao.DataAccessException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * A base implementation for {@link GeoServerUserGroupStore} * * @author christian * */ public abstract class AbstractUserGroupStore implements GeoServerUserGroupStore{ /** logger */ static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.security"); private boolean modified=false; protected AbstractUserGroupService service; protected UserGroupStoreHelper helper; protected AbstractUserGroupStore() { helper=new UserGroupStoreHelper(); } public String getName() { return service.getName(); } public void setName(String name) { service.setName(name); } public GeoServerSecurityManager getSecurityManager() { return service.getSecurityManager(); } public void setSecurityManager(GeoServerSecurityManager securityManager) { service.setSecurityManager(securityManager); } public boolean canCreateStore() { return service.canCreateStore(); } public String getPasswordEncoderName() { return service.getPasswordEncoderName(); } public String getPasswordValidatorName() { return service.getPasswordValidatorName(); } public GeoServerUserGroupStore createStore() throws IOException { return service.createStore(); } public void registerUserGroupLoadedListener(UserGroupLoadedListener listener) { service.registerUserGroupLoadedListener(listener); } public void unregisterUserGroupLoadedListener(UserGroupLoadedListener listener) { service.unregisterUserGroupLoadedListener(listener); } public GeoServerUser getUserByUsername(String username) throws IOException { return helper.getUserByUsername(username); } public GeoServerUserGroup getGroupByGroupname(String groupname) throws IOException { return helper.getGroupByGroupname(groupname); } public SortedSet<GeoServerUser> getUsers() throws IOException { return helper.getUsers(); } public SortedSet<GeoServerUserGroup> getUserGroups() throws IOException { return helper.getUserGroups(); } public GeoServerUserGroup createGroupObject(String groupname, boolean isEnabled) throws IOException { return service.createGroupObject(groupname, isEnabled); } public SortedSet<GeoServerUserGroup> getGroupsForUser(GeoServerUser user) throws IOException { return helper.getGroupsForUser(user); } public SortedSet<GeoServerUser> getUsersForGroup(GeoServerUserGroup group) throws IOException { return helper.getUsersForGroup(group); } public void load() throws IOException { deserialize(); } public Resource getConfigRoot() throws IOException { return service.getConfigRoot(); } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { // this is only need at runtime return service.loadUserByUsername(username); } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#isModified() */ public boolean isModified() { return modified; } /** * Setter for modified flag * @param value */ public void setModified(Boolean value) { modified=value; } /** * validates and encodes the password. Do nothing * for a not changed password of an existing user * * @param user * @throws IOException */ protected void preparePassword(GeoServerUser user) throws IOException,PasswordPolicyException { char []passwordArray = user.getPassword() != null ? user.getPassword().toCharArray() : null; if (PasswordValidatorImpl.passwordStartsWithEncoderPrefix(passwordArray)!=null) return; // do nothing, password already encoded // we have a plain text password // validate it getSecurityManager().loadPasswordValidator(getPasswordValidatorName()). validatePassword(passwordArray); // validation ok, initializer encoder and set encoded password GeoServerPasswordEncoder enc = getSecurityManager().loadPasswordEncoder(getPasswordEncoderName()); enc.initializeFor(this); user.setPassword(enc.encodePassword(user.getPassword(), null)); } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#addUser(org.geoserver.security.impl.GeoserverUser) */ public void addUser(GeoServerUser user) throws IOException, PasswordPolicyException{ if(helper.userMap.containsKey(user.getUsername())) throw new IllegalArgumentException("The user " + user.getUsername() + " already exists"); preparePassword(user); helper.userMap.put(user.getUsername(), user); addUserToPropertyMap(user); setModified(true); } protected void addUserToPropertyMap(GeoServerUser user) { for (Object key : user.getProperties().keySet()) { SortedSet<GeoServerUser> users = helper.propertyMap.get(key); if (users==null) { users = new TreeSet<GeoServerUser>(); helper.propertyMap.put((String) key, users); } users.add(user); } } protected void removeUserFromPropertyMap(GeoServerUser user) { for (SortedSet<GeoServerUser> users : helper.propertyMap.values()) { users.remove(user); } } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#addGroup(org.geoserver.security.impl.GeoserverUserGroup) */ public void addGroup(GeoServerUserGroup group) throws IOException{ if(helper.groupMap.containsKey(group.getGroupname())) throw new IllegalArgumentException("The group " + group.getGroupname() + " already exists"); else { helper.groupMap.put(group.getGroupname(), group); setModified(true); } } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#updateUser(org.geoserver.security.impl.GeoserverUser) */ public void updateUser(GeoServerUser user) throws IOException, PasswordPolicyException{ if(helper.userMap.containsKey(user.getUsername())==false) { throw new IllegalArgumentException("The user " + user.getUsername() + " does not exist"); } preparePassword(user); helper.userMap.put(user.getUsername(), user); removeUserFromPropertyMap(user); addUserToPropertyMap(user); setModified(true); } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#updateGroup(org.geoserver.security.impl.GeoserverUserGroup) */ public void updateGroup(GeoServerUserGroup group) throws IOException{ if(helper.groupMap.containsKey(group.getGroupname())) { helper.groupMap.put(group.getGroupname(), group); setModified(true); } else throw new IllegalArgumentException("The group " + group.getGroupname() + " does not exist"); } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#removeUser(org.geoserver.security.impl.GeoserverUser) */ public boolean removeUser(GeoServerUser user) throws IOException{ Collection<GeoServerUserGroup> groups = helper.user_groupMap.get(user); if (groups!=null) { Collection<GeoServerUserGroup> toBeRemoved = new ArrayList<GeoServerUserGroup>(); toBeRemoved.addAll(groups); for (GeoServerUserGroup group : toBeRemoved) { disAssociateUserFromGroup(user, group); } } boolean retValue = helper.userMap.remove(user.getUsername()) != null; if (retValue) { setModified(true); removeUserFromPropertyMap(user); } return retValue; } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#removeGroup(org.geoserver.security.impl.GeoserverUserGroup) */ public boolean removeGroup(GeoServerUserGroup group) throws IOException{ Collection<GeoServerUser> users = helper.group_userMap.get(group);; if (users !=null) { Collection<GeoServerUser> toBeRemoved = new ArrayList<GeoServerUser>(); toBeRemoved.addAll(users); for (GeoServerUser user : toBeRemoved) { disAssociateUserFromGroup(user, group); } } boolean retval = helper.groupMap.remove(group.getGroupname()) != null; if (retval) { setModified(true); } return retval; } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#store() * */ public void store() throws IOException { if (isModified()) { LOGGER.info("Start storing user/groups for service named "+getName()); // prevent concurrent write from store and // read from service synchronized (service) { serialize(); } setModified(false); LOGGER.info("Storing user/groups successful for service named "+getName()); service.load(); // service must reload } else { LOGGER.info("Storing unnecessary, no change for user and groups"); } } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#associateUserToGroup(org.geoserver.security.impl.GeoserverUser, org.geoserver.security.impl.GeoserverUserGroup) */ public void associateUserToGroup(GeoServerUser user, GeoServerUserGroup group) throws IOException{ checkUser(user); checkGroup(group); boolean changed = false; SortedSet<GeoServerUser> users = helper.group_userMap.get(group); if (users == null) { users = new TreeSet<GeoServerUser>(); helper.group_userMap.put(group,users); } if (users.contains(user)==false) { users.add(user); changed=true; } SortedSet<GeoServerUserGroup> groups = helper.user_groupMap.get(user); if (groups == null) { groups = new TreeSet<GeoServerUserGroup>(); helper.user_groupMap.put(user,groups); } if (groups.contains(group)==false) { groups.add(group); changed=true; } if (changed) { setModified(true); } } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserDetailsService#disAssociateUserFromGroup(org.geoserver.security.impl.GeoserverUser, org.geoserver.security.UserGroup) */ public void disAssociateUserFromGroup(GeoServerUser user, GeoServerUserGroup group) throws IOException{ checkUser(user); checkGroup(group); boolean changed = false; SortedSet<GeoServerUser> users = helper.group_userMap.get(group); if (users!=null) { changed |=users.remove(user); if (users.isEmpty()) { helper.group_userMap.remove(group); } } SortedSet<GeoServerUserGroup> groups = helper.user_groupMap.get(user); if (groups!=null) { changed |= groups.remove(group); if (groups.isEmpty()) helper.user_groupMap.remove(user); } if (changed) { setModified(true); } } /** * Subclasses must implement this method * Save user/groups to backend */ protected abstract void serialize() throws IOException; /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupStore#clear() */ public void clear() throws IOException { clearMaps(); setModified(true); } @Override public void initializeFromService(GeoServerUserGroupService service) throws IOException { this.service=(AbstractUserGroupService)service; load(); } /** * internal use, clear the maps */ protected void clearMaps() { helper.clearMaps(); } /** * Make a deep copy (using serialization) from the * service to the store. */ @SuppressWarnings("unchecked") protected void deserialize() throws IOException { // deepcopy from service, using serialization ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oout = new ObjectOutputStream(out); oout.writeObject(service.helper.userMap); oout.writeObject(service.helper.groupMap); oout.writeObject(service.helper.user_groupMap); oout.writeObject(service.helper.group_userMap); oout.writeObject(service.helper.propertyMap); byte[] bytes =out.toByteArray(); oout.close(); clearMaps(); ByteArrayInputStream in = new ByteArrayInputStream(bytes); ObjectInputStream oin = new ObjectInputStream(in); try { helper.userMap = (TreeMap<String,GeoServerUser>) oin.readObject(); helper.groupMap =(TreeMap<String,GeoServerUserGroup>) oin.readObject(); helper.user_groupMap = (TreeMap<GeoServerUser,SortedSet<GeoServerUserGroup>>)oin.readObject(); helper.group_userMap = (TreeMap<GeoServerUserGroup,SortedSet<GeoServerUser>>)oin.readObject(); helper.propertyMap = (TreeMap<String,SortedSet<GeoServerUser>>)oin.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e); } setModified(false); } /* (non-Javadoc) * @see org.geoserver.security.GeoserverUserGroupService#initializeFromConfig(org.geoserver.security.config.SecurityNamedServiceConfig) */ @Override public void initializeFromConfig(SecurityNamedServiceConfig config) throws IOException { service.initializeFromConfig(config); } /** * Delegates to the {@link GeoServerUserGroupService} backend */ @Override public GeoServerUser createUserObject(String username,String password, boolean isEnabled) throws IOException{ return service.createUserObject(username, password, isEnabled); } protected void checkUser(GeoServerUser user) throws IOException{ if (helper.userMap.containsKey(user.getUsername())==false) throw new IOException("User: " + user.getUsername()+ " does not exist"); } protected void checkGroup(GeoServerUserGroup group) throws IOException{ if (helper.groupMap.containsKey(group.getGroupname())==false) throw new IOException("Group: " + group.getGroupname()+ " does not exist"); } public int getUserCount() throws IOException { return helper.getUserCount(); } public int getGroupCount() throws IOException { return helper.getGroupCount(); } @Override public SortedSet<GeoServerUser> getUsersHavingProperty(String propname) throws IOException { return helper.getUsersHavingProperty(propname); } @Override public int getUserCountHavingProperty(String propname) throws IOException { return helper.getUserCountHavingProperty(propname); } @Override public SortedSet<GeoServerUser> getUsersNotHavingProperty(String propname) throws IOException { return helper.getUsersNotHavingProperty(propname); } @Override public int getUserCountNotHavingProperty(String propname) throws IOException { return helper.getUserCountNotHavingProperty(propname); } @Override public SortedSet<GeoServerUser> getUsersHavingPropertyValue(String propname, String propvalue) throws IOException { return helper.getUsersHavingPropertyValue(propname, propvalue); } @Override public int getUserCountHavingPropertyValue(String propname, String propvalue) throws IOException { return helper.getUserCountHavingPropertyValue(propname, propvalue); } }