/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security;
import static org.geoserver.data.util.IOUtils.xStreamPersist;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.rmi.server.UID;
import java.security.InvalidKeyException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.config.util.XStreamPersisterFactory;
import org.geoserver.platform.ContextLoadedEvent;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Files;
import org.geoserver.platform.resource.Paths;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.platform.resource.ResourceStore;
import org.geoserver.platform.resource.Resources;
import org.geoserver.security.GeoServerSecurityManager.FilterHelper;
import org.geoserver.security.auth.AuthenticationCache;
import org.geoserver.security.auth.GeoServerRootAuthenticationProvider;
import org.geoserver.security.auth.GuavaAuthenticationCacheImpl;
import org.geoserver.security.auth.UsernamePasswordAuthenticationProvider;
import org.geoserver.security.concurrent.LockingKeyStoreProvider;
import org.geoserver.security.concurrent.LockingRoleService;
import org.geoserver.security.concurrent.LockingUserGroupService;
import org.geoserver.security.config.AnonymousAuthenticationFilterConfig;
import org.geoserver.security.config.BasicAuthenticationFilterConfig;
import org.geoserver.security.config.ExceptionTranslationFilterConfig;
import org.geoserver.security.config.FileBasedSecurityServiceConfig;
import org.geoserver.security.config.J2eeAuthenticationBaseFilterConfig;
import org.geoserver.security.config.J2eeAuthenticationBaseFilterConfig.J2EERoleSource;
import org.geoserver.security.config.LogoutFilterConfig;
import org.geoserver.security.config.PasswordPolicyConfig;
import org.geoserver.security.config.PreAuthenticatedUserNameFilterConfig;
import org.geoserver.security.config.PreAuthenticatedUserNameFilterConfig.PreAuthenticatedUserNameRoleSource;
import org.geoserver.security.config.RememberMeAuthenticationFilterConfig;
import org.geoserver.security.config.RoleFilterConfig;
import org.geoserver.security.config.RoleSource;
import org.geoserver.security.config.SSLFilterConfig;
import org.geoserver.security.config.SecurityAuthProviderConfig;
import org.geoserver.security.config.SecurityConfig;
import org.geoserver.security.config.SecurityContextPersistenceFilterConfig;
import org.geoserver.security.config.SecurityFilterConfig;
import org.geoserver.security.config.SecurityInterceptorFilterConfig;
import org.geoserver.security.config.SecurityManagerConfig;
import org.geoserver.security.config.SecurityNamedServiceConfig;
import org.geoserver.security.config.SecurityRoleServiceConfig;
import org.geoserver.security.config.SecurityUserGroupServiceConfig;
import org.geoserver.security.config.UsernamePasswordAuthenticationFilterConfig;
import org.geoserver.security.config.UsernamePasswordAuthenticationProviderConfig;
import org.geoserver.security.file.FileWatcher;
import org.geoserver.security.file.RoleFileWatcher;
import org.geoserver.security.file.UserGroupFileWatcher;
import org.geoserver.security.filter.GeoServerAnonymousAuthenticationFilter;
import org.geoserver.security.filter.GeoServerBasicAuthenticationFilter;
import org.geoserver.security.filter.GeoServerExceptionTranslationFilter;
import org.geoserver.security.filter.GeoServerLogoutFilter;
import org.geoserver.security.filter.GeoServerRememberMeAuthenticationFilter;
import org.geoserver.security.filter.GeoServerRoleFilter;
import org.geoserver.security.filter.GeoServerSSLFilter;
import org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter;
import org.geoserver.security.filter.GeoServerSecurityFilter;
import org.geoserver.security.filter.GeoServerSecurityInterceptorFilter;
import org.geoserver.security.filter.GeoServerUserNamePasswordAuthenticationFilter;
import org.geoserver.security.impl.DataAccessRuleDAO;
import org.geoserver.security.impl.GeoServerRole;
import org.geoserver.security.impl.GeoServerUser;
import org.geoserver.security.impl.GeoServerUserGroup;
import org.geoserver.security.impl.GroupAdminProperty;
import org.geoserver.security.impl.ServiceAccessRuleDAO;
import org.geoserver.security.impl.Util;
import org.geoserver.security.password.ConfigurationPasswordEncryptionHelper;
import org.geoserver.security.password.GeoServerDigestPasswordEncoder;
import org.geoserver.security.password.GeoServerPBEPasswordEncoder;
import org.geoserver.security.password.GeoServerPasswordEncoder;
import org.geoserver.security.password.MasterPasswordChangeRequest;
import org.geoserver.security.password.MasterPasswordConfig;
import org.geoserver.security.password.MasterPasswordProviderConfig;
import org.geoserver.security.password.PasswordValidator;
import org.geoserver.security.password.RandomPasswordProvider;
import org.geoserver.security.password.URLMasterPasswordProvider;
import org.geoserver.security.password.URLMasterPasswordProviderConfig;
import org.geoserver.security.rememberme.GeoServerTokenBasedRememberMeServices;
import org.geoserver.security.rememberme.RememberMeServicesConfig;
import org.geoserver.security.validation.MasterPasswordChangeException;
import org.geoserver.security.validation.MasterPasswordChangeValidator;
import org.geoserver.security.validation.MasterPasswordConfigValidator;
import org.geoserver.security.validation.PasswordPolicyException;
import org.geoserver.security.validation.PasswordValidatorImpl;
import org.geoserver.security.validation.SecurityConfigException;
import org.geoserver.security.validation.SecurityConfigValidator;
import org.geoserver.security.xml.XMLConstants;
import org.geoserver.security.xml.XMLRoleService;
import org.geoserver.security.xml.XMLRoleServiceConfig;
import org.geoserver.security.xml.XMLUserGroupService;
import org.geoserver.security.xml.XMLUserGroupServiceConfig;
import org.geotools.util.logging.Logging;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.util.StringUtils;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* Top level singleton/facade/dao for the security authentication/authorization subsystem.
*
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class GeoServerSecurityManager extends ProviderManager implements ApplicationContextAware,
ApplicationListener, ResourceStore {
static Logger LOGGER = Logging.getLogger("org.geoserver.security");
/** default config file name */
public static final String CONFIG_FILENAME = "config.xml";
/** master password config file name */
public static final String MASTER_PASSWD_CONFIG_FILENAME = "masterpw.xml";
/** master password info file name */
public static final String MASTER_PASSWD_INFO_FILENAME = "masterpw.info";
/** master password digest file name */
public static final String MASTER_PASSWD_DIGEST_FILENAME = "masterpw.digest";
/** default master password */
public static final char[] MASTER_PASSWD_DEFAULT= "geoserver".toCharArray();
/** data directory file system access */
GeoServerDataDirectory dataDir;
/** app context for loading plugins */
ApplicationContext appContext;
/** the active role service */
GeoServerRoleService activeRoleService;
/** configured authentication providers */
List<GeoServerAuthenticationProvider> authProviders;
/** current security config */
SecurityManagerConfig securityConfig = new SecurityManagerConfig();
/** current master password config */
MasterPasswordConfig masterPasswordConfig = new MasterPasswordConfig();
/** digested master password */
volatile String masterPasswdDigest;
/** cached user groups */
ConcurrentHashMap<String, GeoServerUserGroupService> userGroupServices =
new ConcurrentHashMap<String, GeoServerUserGroupService>();
/** cached role services */
ConcurrentHashMap<String, GeoServerRoleService> roleServices =
new ConcurrentHashMap<String, GeoServerRoleService>();
/** cached password validators services */
ConcurrentHashMap<String, PasswordValidator> passwordValidators =
new ConcurrentHashMap<String, PasswordValidator>();
/** some helper instances for storing/loading service config */
RoleServiceHelper roleServiceHelper = new RoleServiceHelper();
UserGroupServiceHelper userGroupServiceHelper = new UserGroupServiceHelper();
AuthProviderHelper authProviderHelper = new AuthProviderHelper();
FilterHelper filterHelper = new FilterHelper();
PasswordValidatorHelper passwordValidatorHelper = new PasswordValidatorHelper();
MasterPasswordProviderHelper masterPasswordProviderHelper = new MasterPasswordProviderHelper();
/** helper for encrypting store configuration parameters */
ConfigurationPasswordEncryptionHelper configPasswordEncryptionHelper;
/**
* listeners
*/
List<SecurityManagerListener> listeners = new ArrayList<SecurityManagerListener>();
/** cached flag determining is strong cryptography is available */
Boolean strongEncryptionAvaialble;
/** flag set once the security manager has been fully initialized */
boolean initialized = false;
/** keystore provider, loaded lazily */
volatile KeyStoreProvider keyStoreProvider;
/** generator of random passwords */
RandomPasswordProvider randomPasswdProvider = new RandomPasswordProvider();
/** authentication cache */
volatile AuthenticationCache authCache;
/** rememmber me service */
volatile RememberMeServices rememberMeService;
public static final String REALM="GeoServer Realm";
public GeoServerSecurityManager(GeoServerDataDirectory dataDir) throws Exception {
this.dataDir = dataDir;
setEraseCredentialsAfterAuthentication(true);
/*
* JD we have to ensure that the master password is initialized first thing, before the
* catalog since we need to decrypt configuration the passwords, the rest of the security
* initializes occurs at the end of startup
*/
Resource masterpw = security().get( MASTER_PASSWD_CONFIG_FILENAME);
if (masterpw.getType() == Type.RESOURCE) {
init(loadMasterPasswordConfig());
}
else {
//if it doesn't exist this must be a migration startup... and this case should be
// handled during migration where all the datastore passwords are processed
// explicitly
}
configPasswordEncryptionHelper = new ConfigurationPasswordEncryptionHelper(this);
}
public Catalog getCatalog() {
//have to look this up dynamically on demand on avoid circular dependency on application
// context startup
return (Catalog) GeoServerExtensions.bean("catalog");
}
public ConfigurationPasswordEncryptionHelper getConfigPasswordEncryptionHelper() {
return configPasswordEncryptionHelper;
}
@Override
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
this.appContext = appContext;
}
public ApplicationContext getApplicationContext() {
return appContext;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextLoadedEvent) {
try {
File masterPasswordInfo = new File(getSecurityRoot(), MASTER_PASSWD_INFO_FILENAME);
if (masterPasswordInfo.exists()) {
LOGGER.warning(masterPasswordInfo.getCanonicalPath()+" is a security risk. Please read this file and remove it afterward");
}
} catch (Exception e1) {
throw new RuntimeException(e1);
}
// migrate from old security config
try {
boolean migratedFrom21 = migrateFrom21();
removeErroneousAccessDeniedPage();
migrateFrom22(migratedFrom21);
migrateFrom23();
migrateFrom24();
} catch (Exception e1) {
throw new RuntimeException(e1);
}
// read config and initialize... we do this now since we can be ensured that the spring
// context has been property initialized, and we can successfully look up security
// plugins
KeyStoreProvider keyStoreProvider = getKeyStoreProvider();
try {
// check for an outstanding masster password change
keyStoreProvider.commitMasterPasswordChange();
// check if there is an outstanding master password change in case of SPrin injection
init();
for (GeoServerSecurityProvider securityProvider
: GeoServerExtensions.extensions(GeoServerSecurityProvider.class))
{
securityProvider.init(this);
}
} catch (Exception e) {
throw new BeanCreationException("Error occured reading security configuration", e);
}
try {
afterPropertiesSetInternal();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (event instanceof ContextClosedEvent) {
try {
destroy();
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error destroying security manager", e);
}
}
}
void migrateFrom24() throws SecurityConfigException, IOException {
// allows migration of RoleSource from PreAuthenticatedUserNameFilterConfig
MigrationHelper mh = new MigrationHelper() {
@Override
public void migrationPersister(XStreamPersister xp) {
xp.getXStream().registerConverter(new Converter() {
@Override
public boolean canConvert(Class cls) {
return cls.isAssignableFrom(RoleSource.class);
}
@Override
public void marshal(Object rs, HierarchicalStreamWriter writer,
MarshallingContext ctx) {
if (rs != null) {
writer.setValue(rs.toString());
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext ctx) {
if (reader.getValue() != null) {
return J2EERoleSource.valueOf(reader.getValue());
}
return null;
}
});
}
};
for (String fName : listFilters()) {
SecurityFilterConfig fConfig = loadFilterConfig(fName, mh);
if (fConfig != null) {
if (fConfig instanceof J2eeAuthenticationBaseFilterConfig) {
J2eeAuthenticationBaseFilterConfig j2eeConfig = (J2eeAuthenticationBaseFilterConfig) fConfig;
// add default J2EE RoleSource that was the only one possible
// before 2.5
if (j2eeConfig.getRoleSource() == null) {
j2eeConfig.setRoleSource(J2EERoleSource.J2EE);
}
} else if (fConfig instanceof PreAuthenticatedUserNameFilterConfig) {
PreAuthenticatedUserNameFilterConfig userNameConfig = (PreAuthenticatedUserNameFilterConfig) fConfig;
RoleSource rs = userNameConfig.getRoleSource();
if (rs != null) {
// use the right RoleSource enum
userNameConfig
.setRoleSource(PreAuthenticatedUserNameRoleSource
.valueOf(rs.toString()));
}
}
saveFilter(fConfig, mh);
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
//this is a bit o a hack but override and do nothing for now, we will call the super
// method later, after the app context is loaded, see afterPropertiesSetInternal()
}
void afterPropertiesSetInternal() throws Exception {
super.afterPropertiesSet();
}
public void destroy() throws Exception {
for (GeoServerSecurityProvider securityProvider
: GeoServerExtensions.extensions(GeoServerSecurityProvider.class))
{
securityProvider.destroy(this);
}
userGroupServices.clear();
roleServices.clear();
userGroupServiceHelper.destroy();
roleServiceHelper.destroy();
rememberMeService = null;
keyStoreProvider = null;
listeners.clear();
appContext = null;
}
/**
* Adds a listener to the security manager.
*/
public void addListener(SecurityManagerListener listener) {
listeners.add(listener);
}
/**
* Removes a listener to the security manager.
*/
public void removeListener(SecurityManagerListener listener) {
listeners.remove(listener);
}
/**
* List of active/configured authentication providers
*/
public List<GeoServerAuthenticationProvider> getAuthenticationProviders() {
return authProviders;
}
/*
* loads configuration and initializes the security subsystem.
*/
void init() throws Exception {
init(loadMasterPasswordConfig());
init(loadSecurityConfig());
fireChanged();
}
void init(SecurityManagerConfig config) throws Exception {
// load the master password provider
// prepare the keystore providing needed key material
getKeyStoreProvider().reloadKeyStore();
//load the role authority and ensure it is properly configured
String roleServiceName = config.getRoleServiceName();
GeoServerRoleService roleService = null;
try {
roleService = loadRoleService(roleServiceName);
//TODO:
//if (!roleService.isConfigured()) {
// roleService = null;
//}
}
catch(Exception e) {
LOGGER.log(Level.WARNING, String.format("Error occured loading role service %s, "
+ "falling back to default role service", roleServiceName), e);
}
if (roleService == null) {
try {
roleService = loadRoleService("default");
}
catch(Exception e) {
throw new RuntimeException("Fatal error occurred loading default role service", e);
}
}
//configure the user details instance
setActiveRoleService(roleService);
//set up authentication providers
this.authProviders = new ArrayList<GeoServerAuthenticationProvider>();
// first provider is for the root user
GeoServerRootAuthenticationProvider rootAuthProvider
= new GeoServerRootAuthenticationProvider();
rootAuthProvider.setSecurityManager(this);
rootAuthProvider.initializeFromConfig(null);
this.authProviders.add(rootAuthProvider);
//add the custom/configured ones
if(!config.getAuthProviderNames().isEmpty()) {
for (String authProviderName : config.getAuthProviderNames()) {
//TODO: handle failure here... perhaps simply disabling when auth provider
// fails to load?
GeoServerAuthenticationProvider authProvider =
authProviderHelper.load(authProviderName);
authProviders.add(authProvider);
}
}
List<AuthenticationProvider> allAuthProviders = new ArrayList<AuthenticationProvider>();
allAuthProviders.addAll(authProviders);
//anonymous, not needed anymore
// if (config.isAnonymousAuth()) {
// AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
// aap.setKey("geoserver");
// aap.afterPropertiesSet();
// allAuthProviders.add(aap);
// }
//remember me
RememberMeAuthenticationProvider rap = new RememberMeAuthenticationProvider();
rap.setKey(config.getRememberMeService().getKey());
rap.afterPropertiesSet();
allAuthProviders.add(rap);
setProviders(allAuthProviders);
this.securityConfig = new SecurityManagerConfig(config);
this.initialized = true;
}
void init(MasterPasswordConfig config) {
this.masterPasswordConfig = new MasterPasswordConfig(config);
}
public KeyStoreProvider getKeyStoreProvider() {
if (keyStoreProvider == null) {
synchronized (this) {
if (keyStoreProvider == null) {
keyStoreProvider = lookupKeyStoreProvider();
}
}
}
return keyStoreProvider;
}
KeyStoreProvider lookupKeyStoreProvider() {
KeyStoreProvider ksp = GeoServerExtensions.bean(KeyStoreProvider.class);
if (ksp == null) {
// use default key store provider
ksp=new KeyStoreProviderImpl();
}
ksp.setSecurityManager(this);
return new LockingKeyStoreProvider(ksp);
}
public RandomPasswordProvider getRandomPassworddProvider() {
return randomPasswdProvider;
}
public AuthenticationCache getAuthenticationCache() {
if (authCache == null) {
synchronized (this) {
if (authCache == null) {
authCache = lookupAuthenticationCache();
}
}
}
return authCache;
}
AuthenticationCache lookupAuthenticationCache() {
AuthenticationCache authCache = GeoServerExtensions.bean(AuthenticationCache.class);
return authCache != null ? authCache : new GuavaAuthenticationCacheImpl(1000);
}
public RememberMeServices getRememberMeService() {
if (rememberMeService == null) {
synchronized (this) {
if (rememberMeService == null) {
rememberMeService = lookupRememberMeService();
}
}
}
return rememberMeService;
}
RememberMeServices lookupRememberMeService() {
return (RememberMeServices) GeoServerExtensions.bean("rememberMeServices");
}
public DataAccessRuleDAO getDataAccessRuleDAO() {
return DataAccessRuleDAO.get();
}
public ServiceAccessRuleDAO getServiceAccessRuleDAO() {
return ServiceAccessRuleDAO.get();
}
/**
* Determines if the security manager has been initialized yet.
* <p>
* TODO: this is a temporary hack, perhaps we should think about initializing the security
* subsystem as the very first thing on startup... but now we have dependencies on the catalog
* so we cant.
* </p>
*/
public boolean isInitialized() {
return initialized;
}
@Override
public Resource get(String path) {
return dataDir.get(path);
}
@Override
public boolean remove(String path) {
return dataDir.remove(path);
}
@Override
public boolean move(String path, String target) {
return dataDir.move(path, target);
}
/**
* Security configuration root directory.
*/
public Resource security() {
return get("security");
}
/**
* Security configuration root directory.
*
* @deprecated Use {@link #secuirtyRoot()}
*/
public File getSecurityRoot() throws IOException {
Resource directory = get("security");
return directory.dir();
}
/**
* Role configuration root directory.
*/
public Resource role() {
return get("security/role");
}
/**
* Role configuration root directory.
*
* @deprecated Use {@link #role()}
*/
public File getRoleRoot() throws IOException {
Resource directory = get("security/role");
return directory.dir();
}
/**
* Role configuration root directory.
*
* @deprecated Use {@link #role()}
*/
public File getRoleRoot(boolean create) throws IOException {
Resource directory = get("security/role");
if (create) {
return directory.dir();
} else {
return Resources.directory(directory);
}
}
/**
* Password policy configuration root directory
*/
public Resource passwordPolicy(){
return get("security/pwpolicy");
}
/**
* Password policy configuration root directory
* @deprecated Use {@link #passwordPolicy()}
*/
public File getPasswordPolicyRoot() throws IOException {
return get("security/pwpolicy").dir();
}
/**
* User/group configuration root directory.
*/
public Resource userGroup() throws IOException {
return get("security/usergroup");
}
/**
* User/group configuration root directory.
* @deprecated Use {@link #userGroup()}
*/
public File getUserGroupRoot() throws IOException {
Resource directory = get("security/usergroup");
return directory.dir();
}
/**
* Authentication configuration root directory.
*/
public Resource auth() throws IOException {
return get("security/auth");
}
/**
* Authentication configuration root directory.
* @deprecated use {@link #auth()}
*/
public File getAuthRoot() throws IOException {
Resource directory = get("security/auth");
return directory.dir();
}
/**
* Authentication filter root directory.
*/
public Resource filterRoot() throws IOException {
return get("security/filter");
}
/**
* Authentication filter root directory.
* @deprecated Use {@link #auth()}
*/
public File getFilterRoot() throws IOException {
Resource directory = get("security/filter");
return directory.dir();
}
/**
* Master password provider root
*/
public Resource masterPasswordProvider() throws IOException {
return get("security/masterpw");
}
/**
* Master password provider root
* @deprecated Use {@link #masterPasswordProvider()}
*/
public File getMasterPasswordProviderRoot() throws IOException {
Resource resource = get("security/masterpw");
return resource.dir();
}
/**
* Lists all available role service configurations.
*/
public SortedSet<String> listRoleServices() throws IOException {
return listFiles(getRoleRoot());
}
/**
* Loads a role service from a named configuration.
*
* @param name The name of the role service configuration.
*/
public GeoServerRoleService loadRoleService(String name)
throws IOException {
GeoServerRoleService roleService = roleServices.get(name);
if (roleService == null) {
synchronized (this) {
roleService = roleServices.get(name);
if (roleService == null) {
roleService = roleServiceHelper.load(name);
if (roleService != null) {
roleServices.put(name, roleService);
}
}
}
}
return wrapRoleService(roleService);
}
GeoServerRoleService wrapRoleService(GeoServerRoleService roleService) throws IOException {
if (!initialized) {
//starting up
return roleService;
}
//check for group administrator and wrap accordingly
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (checkAuthenticationForAdminRole(auth)) {
//admin, no need to wrap
return roleService;
}
//check for group admin
if (checkAuthenticationForRole(auth, GeoServerRole.GROUP_ADMIN_ROLE)) {
roleService = new GroupAdminRoleService(roleService,
calculateAdminGroups((UserDetails) auth.getPrincipal()));
}
return roleService;
}
List<String> calculateAdminGroups(UserDetails userDetails) throws IOException {
if (userDetails instanceof GeoServerUser) {
Properties props = ((GeoServerUser)userDetails).getProperties();
if (GroupAdminProperty.has(props)) {
return Arrays.asList(GroupAdminProperty.get(props));
}
}
//fall back on including every group the user is part of
List<String> groupNames = new ArrayList<String>();
for (GeoServerUserGroupService ugService : loadUserGroupServices()) {
GeoServerUser user = ugService.getUserByUsername(userDetails.getUsername());
if (user != null) {
for (GeoServerUserGroup group : ugService.getGroupsForUser(user)) {
groupNames.add(group.getGroupname());
}
}
}
return groupNames;
}
/**
* Loads a role {@link SecurityRoleServiceConfig} from a named configuration.
* <code>null</code> if not found
*
* @param name The name of the role service configuration.
*/
public SecurityRoleServiceConfig loadRoleServiceConfig(String name)
throws IOException {
return roleServiceHelper.loadConfig(name);
}
/**
* Loads a password validator from a named configuration.
*
* @param name The name of the password policy configuration.
*/
public PasswordValidator loadPasswordValidator(String name)
throws IOException {
PasswordValidator validator = passwordValidators.get(name);
if (validator == null) {
synchronized (this) {
validator = passwordValidators.get(name);
if (validator == null) {
validator = passwordValidatorHelper.load(name);
if (validator != null) {
passwordValidators.put(name, validator);
}
}
}
}
return validator;
}
/**
* Loads a password {@link PasswordPolicyConfig} from a named configuration.
* <code>null</a> if not found
*
* @param name The name of the password policy configuration.
*/
public PasswordPolicyConfig loadPasswordPolicyConfig(String name) throws IOException {
return passwordValidatorHelper.loadConfig(name);
}
/**
* Loads a password encoder with the specified name.
*
* @return The password encoder, or <code>null</code> if non found matching the name.
*/
public GeoServerPasswordEncoder loadPasswordEncoder(String name) {
GeoServerPasswordEncoder encoder = (GeoServerPasswordEncoder) GeoServerExtensions.bean(name);
if (encoder != null) {
try {
encoder.initialize(this);
} catch (IOException e) {
throw new RuntimeException("Error occurred initializing password encoder");
}
}
return encoder;
}
/**
* Loads the first password encoder that matches the specified class filter.
* <p>
* This method is shorthand for:
* <pre>
* loadPasswordEncoder(filter, null, null);
* </pre>
* </p>
*
*/
public <T extends GeoServerPasswordEncoder> T loadPasswordEncoder(Class<T> filter) {
return loadPasswordEncoder(filter, null, null);
}
/**
* Loads the first password encoder that matches the specified criteria.
*
* @param filter Class used to filter password encoders.
* @param config Flag indicating if a reversible encoder is required, true forces reversible,
* false forces irreversible, null means either.
* @param strong Flag indicating if an encoder that supports strong encryption is required, true
* forces strong encryption, false forces weak encryption, null means either.
*
* @return The first encoder matching, or null if none was found.
*/
public <T extends GeoServerPasswordEncoder> T loadPasswordEncoder(Class<T> filter,
Boolean reversible, Boolean strong) {
List<T> pw = loadPasswordEncoders(filter, reversible, strong);
return pw.isEmpty() ? null : pw.get(0);
}
/**
* Looks up all available password encoders.
*/
public List<GeoServerPasswordEncoder> loadPasswordEncoders() {
return loadPasswordEncoders(null);
}
/**
* Looks up all available password encoders filtering out only those that are instances of the
* specified class.
* <p>
* This method is convenience for:
* <pre>
* loadPasswordEncoders(filter, null, null)
* </pre>
* </p>
*/
public <T extends GeoServerPasswordEncoder> List<T> loadPasswordEncoders(Class<T> filter) {
return loadPasswordEncoders(filter, null, null);
}
/**
* Loads all the password encoders that match the specified criteria.
*
* @param filter Class used to filter password encoders.
* @param config Flag indicating if a reversible encoder is required, true forces reversible,
* false forces irreversible, null means either.
* @param strong Flag indicating if an encoder that supports strong encryption is required, true
* forces strong encryption, false forces weak encryption, null means either.
*
* @return All matching encoders, or an empty list.
*/
public <T extends GeoServerPasswordEncoder> List<T> loadPasswordEncoders(Class<T> filter,
Boolean reversible, Boolean strong) {
filter = (Class<T>) (filter != null ? filter : GeoServerPasswordEncoder.class);
List list = GeoServerExtensions.extensions(filter);
for (Iterator it = list.iterator(); it.hasNext(); ) {
boolean remove = false;
T pw = (T) it.next();
if (reversible != null && !reversible.equals(pw.isReversible())) {
remove = true;
}
if (!remove && strong != null && strong.equals(pw.isAvailableWithoutStrongCryptogaphy())) {
remove = true;
}
if (remove) {
it.remove();
}
else {
try {
pw.initialize(this);
} catch (IOException e) {
LOGGER.log(Level.WARNING,
"Error initializing password encoder " + pw.getName() + ", skipping", e);
it.remove();
}
}
}
return list;
}
/**
* Determines if strong encryption is available.
* <p>
* This method does the determination by trying to encrypt a value with AES 256 Bit encryption.
* </p>
*
* @return True if strong encryption avaialble, otherwise false.
*/
public boolean isStrongEncryptionAvailable() {
if (strongEncryptionAvaialble!=null)
return strongEncryptionAvaialble;
KeyGenerator kgen;
try {
kgen = KeyGenerator.getInstance("AES");
kgen.init(256);
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
cipher.doFinal("This is just an example".getBytes());
strongEncryptionAvaialble = true;
LOGGER.info("Strong cryptography is available");
} catch (InvalidKeyException e) {
strongEncryptionAvaialble = false;
LOGGER.warning("Strong cryptography is NOT available"+
"\nDownload and installation the of unlimted length policy files is recommended"
);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Strong cryptography is NOT available, unexpected error", ex);
strongEncryptionAvaialble =false; //should not happen
}
return strongEncryptionAvaialble;
}
/**
* Saves/persists a role service configuration.
*/
public void saveRoleService(SecurityRoleServiceConfig config)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerRoleService.class,
config.getClassName());
if (config.getId() == null) {
config.initBeforeSave();
validator.validateAddRoleService(config);
}
else {
validator.validateModifiedRoleService(config,
roleServiceHelper.loadConfig(config.getName()));
}
roleServiceHelper.saveConfig(config);
// remove from cache
roleServices.remove(config.getName());
//update active role service
if (activeRoleService != null && config.getName().equals(activeRoleService.getName())) {
synchronized (activeRoleService) {
activeRoleService.initializeFromConfig(config);
}
}
}
/**
* Saves/persists a password policy configuration.
*/
public void savePasswordPolicy(PasswordPolicyConfig config)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
PasswordValidator.class,
config.getClassName());
if (config.getId() == null) {
config.initBeforeSave();
validator.validateAddPasswordPolicy(config);
}
else {
validator.validateModifiedPasswordPolicy(config,
passwordValidatorHelper.loadConfig(config.getName()));
}
passwordValidatorHelper.saveConfig(config);
}
/**
* Removes a role service configuration.
*
* @param name The role service configuration.
*/
public void removeRoleService(SecurityRoleServiceConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerRoleService.class,
config.getClassName());
validator.validateRemoveRoleService(config);
roleServices.remove(config.getName());
roleServiceHelper.removeConfig(config.getName());
}
/**
* Removes a password validator configuration.
*
* @param The password validator configuration.
*/
public void removePasswordValidator(PasswordPolicyConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
PasswordValidator.class,
config.getClassName());
validator.validateRemovePasswordPolicy(config);
passwordValidators.remove(config.getName());
passwordValidatorHelper.removeConfig(config.getName());
}
/**
* Lists all available user group service configurations.
*/
public SortedSet<String> listUserGroupServices() throws IOException {
return listFiles(getUserGroupRoot());
}
/**
* Lists all available password Validators.
*/
public SortedSet<String> listPasswordValidators() throws IOException {
return listFiles(getPasswordPolicyRoot());
}
/**
* Loads all available user group services.
*/
public List<GeoServerUserGroupService> loadUserGroupServices() throws IOException {
List<GeoServerUserGroupService> ugServices = new ArrayList<GeoServerUserGroupService>();
for (String ugServiceName : listUserGroupServices()) {
try {
GeoServerUserGroupService ugService = userGroupServiceHelper.load(ugServiceName);
ugServices.add(ugService);
}
catch(IOException e) {
LOGGER.log(Level.WARNING, "Failed to load user group service " + ugServiceName, e);
}
}
return ugServices;
}
/**
* Loads a user group service from a named configuration.
*
* @param name The name of the user group service configuration.
*/
public GeoServerUserGroupService loadUserGroupService(String name) throws IOException {
GeoServerUserGroupService ugService = userGroupServices.get(name);
if (ugService == null) {
synchronized (this) {
ugService = userGroupServices.get(name);
if (ugService == null) {
ugService = userGroupServiceHelper.load(name);
if (ugService != null) {
userGroupServices.put(name, ugService);
}
}
}
}
return wrapUserGroupService(ugService);
}
GeoServerUserGroupService wrapUserGroupService(GeoServerUserGroupService ugService)
throws IOException {
if (!initialized) {
//starting up
return ugService;
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (checkAuthenticationForAdminRole(auth)) {
//full admin, no need to wrap
return ugService;
}
//check for group administrator and wrap accordingly
if (checkAuthenticationForRole(auth, GeoServerRole.GROUP_ADMIN_ROLE)) {
ugService = new GroupAdminUserGroupService(ugService,
calculateAdminGroups((UserDetails) auth.getPrincipal()));
}
return ugService;
}
/**
* Loads a user {@link SecurityUserGroupServiceConfig} from a named configuration.
* <code>null</code> if not foun
*
* @param name The name of the user group service configuration.
*/
public SecurityUserGroupServiceConfig loadUserGroupServiceConfig(String name) throws IOException {
return userGroupServiceHelper.loadConfig(name);
}
/**
* Saves/persists a user group service configuration.
*/
public void saveUserGroupService(SecurityUserGroupServiceConfig config)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerUserGroupService.class,
config.getClassName());
if (config.getId() == null) {
config.initBeforeSave();
validator.validateAddUserGroupService(config);
}
else {
validator.validateModifiedUserGroupService(config,
userGroupServiceHelper.loadConfig(config.getName()));
}
userGroupServiceHelper.saveConfig(config);
// remove from cache
userGroupServices.remove(config.getName());
}
/**
* Removes a user group service configuration.
*
* @param name The user group service configuration.
*/
public void removeUserGroupService(SecurityUserGroupServiceConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerUserGroupService.class,
config.getClassName());
validator.validateRemoveUserGroupService(config);
userGroupServices.remove(config.getName());
userGroupServiceHelper.removeConfig(config.getName());
}
/**
* Lists all available authentication provider configurations.
*/
public SortedSet<String> listAuthenticationProviders() throws IOException {
return listFiles(getAuthRoot());
}
/**
* Loads an authentication provider from a named configuration.
*
* @param name The name of the authentication provider service configuration.
*/
public GeoServerAuthenticationProvider loadAuthenticationProvider(String name) throws IOException {
return authProviderHelper.load(name);
}
/**
* Loads an authentication provider config from a named configuration.
* <code>null</code> if not found
*
* @param name The name of the authentication provider service configuration.
*/
public SecurityAuthProviderConfig loadAuthenticationProviderConfig(String name) throws IOException {
return authProviderHelper.loadConfig(name);
}
public void saveAuthenticationProvider(SecurityAuthProviderConfig config)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(GeoServerAuthenticationProvider.class,
config.getClassName());
if (config.getId() == null) {
config.initBeforeSave();
validator.validateAddAuthProvider(config);
}
else {
validator.validateModifiedAuthProvider(config,
authProviderHelper.loadConfig(config.getName()));
}
//update the running auth providers
if (authProviders != null) {
GeoServerAuthenticationProvider authProvider = null;
for (GeoServerAuthenticationProvider ap : authProviders) {
if (config.getName().equals(ap.getName())) {
authProvider = ap;
break;
}
}
if (authProvider != null) {
synchronized (authProvider) {
authProvider.initializeFromConfig(config);
}
}
}
authProviderHelper.saveConfig(config);
}
/**
* Checks if the currently authenticated user has the administrator role.
* <p>
* This method is shorthand for:
* <code>
* <pre>
* checkAuthenticationForAdminRole(SecurityContextHolder.getContext().getAuthentication())
* </pre>
* </code>
* </p>
*/
public boolean checkAuthenticationForAdminRole() {
if (SecurityContextHolder.getContext()==null)
return checkAuthenticationForAdminRole(null);
else
return checkAuthenticationForAdminRole(
SecurityContextHolder.getContext().getAuthentication());
}
/**
* Checks if the specified authentication has the administrator role.
* <p>
* This method is shorthand for:
* <code>
* <pre>
* checkAuthenticationForRole(auth, GeoServerRole.ADMIN_ROLE)
* </pre>
* </code>
* </p>
*
*/
public boolean checkAuthenticationForAdminRole(Authentication auth) {
return checkAuthenticationForRole(auth, GeoServerRole.ADMIN_ROLE);
}
/**
* Checks if the specified authentication contains the specified role.
*
* If the current {@link HttpServletRequest} has security disabled,
* this method always returns <code>true</code>.
*
* @return <code>true</code> if the authenticated contains the role, otherwise <code>false</false>
*/
public boolean checkAuthenticationForRole(Authentication auth, GeoServerRole role) {
if (GeoServerSecurityFilterChainProxy.isSecurityEnabledForCurrentRequest()==false)
return true; // No security means any role is granted
if (auth == null || !auth.isAuthenticated()) {
return false;
}
for (GrantedAuthority authority : auth.getAuthorities()) {
if (role.getAuthority().equals(authority.getAuthority())) {
return true;
}
}
return false;
}
/**
* Returns true if the default password for the {@Link GeoServerUser#ADMIN_USERNAME} has not
* been changed.
*/
public boolean checkForDefaultAdminPassword() {
Authentication token = new UsernamePasswordAuthenticationToken(GeoServerUser.ADMIN_USERNAME,
GeoServerUser.DEFAULT_ADMIN_PASSWD);
try {
token = authenticate(token);
}
catch(Exception e) {
//ok
}
return token.isAuthenticated();
}
/**
* Lists all available filter configurations.
*/
public SortedSet<String> listFilters() throws IOException {
return listFiles(getFilterRoot());
}
/**
* Lists all available pre authentication filter configurations whose implentation class
* is an instance of the specified class.
*/
public SortedSet<String> listFilters(Class<?> type) throws IOException {
SortedSet<String> configs = new TreeSet<String>();
for (String name : listFilters()) {
SecurityFilterConfig config = (SecurityFilterConfig) loadFilterConfig(name);
if (config.getClassName() == null) {
continue;
}
try {
if (type.isAssignableFrom(Class.forName(config.getClassName()))) {
configs.add(config.getName());
}
} catch (ClassNotFoundException e) {
//ignore and continue
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
}
return configs;
}
/**
* Loads an authentication provider from a named configuration.
*
* @param name The name of the authentication provider service configuration.
*/
public GeoServerSecurityFilter loadFilter(String name) throws IOException {
return filterHelper.load(name);
}
/**
* Loads an authentication provider config from a named configuration.
* <code>null</a> if not found
*
* @param name The name of the authentication provider service configuration.
* @param migrationHelper Optional helper used for migration purposes
*/
public SecurityFilterConfig loadFilterConfig(String name, MigrationHelper migrationHelper) throws IOException {
return filterHelper.loadConfig(name, migrationHelper);
}
/**
* Loads an authentication provider config from a named configuration.
* <code>null</a> if not found
*
* @param name The name of the authentication provider service configuration.
*/
public SecurityFilterConfig loadFilterConfig(String name) throws IOException {
return filterHelper.loadConfig(name);
}
public void saveFilter(SecurityNamedServiceConfig config)
throws IOException,SecurityConfigException {
saveFilter(config, null);
}
public void saveFilter(SecurityNamedServiceConfig config, MigrationHelper migrationHelper)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerSecurityFilter.class,
config.getClassName());
boolean fireChanged = false;
if (config.getId() == null) {
config.initBeforeSave();
validator.validateAddFilter(config);
}
else {
validator.validateModifiedFilter(config,
filterHelper.loadConfig(config.getName(), migrationHelper));
// remove all cached authentications for this filter
getAuthenticationCache().removeAll(config.getName());
if (!securityConfig.getFilterChain().patternsForFilter(config.getName(),true).isEmpty()) {
fireChanged=true;
}
}
filterHelper.saveConfig(config);
if (fireChanged) {
fireChanged();
}
}
/**
* Removes an authentication provider configuration.
*
* @param name The authentication provider configuration.
*/
public void removeAuthenticationProvider(SecurityAuthProviderConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(GeoServerAuthenticationProvider.class,
config.getClassName());
validator.validateRemoveAuthProvider(config);
authProviderHelper.removeConfig(config.getName());
}
public void removeFilter(SecurityNamedServiceConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator =
SecurityConfigValidator.getConfigurationValiator(
GeoServerSecurityFilter.class,
config.getClassName());
validator.validateRemoveFilter(config);
getAuthenticationCache().removeAll(config.getName());
filterHelper.removeConfig(config.getName());
}
/**
* Returns the current security configuration.
* <p>
* In order to make changes to the security configuration client code may make changes to this
* object directly, but must call {@link #saveSecurityConfig(SecurityManagerConfig)} in order
* to persist changes.
* </p>
*/
public SecurityManagerConfig getSecurityConfig() {
return new SecurityManagerConfig(this.securityConfig);
}
public boolean isEncryptingUrlParams() {
if (this.securityConfig==null) return false;
return this.securityConfig.isEncryptingUrlParams();
}
/*
* saves the global security config
* TODO: use read/write lock rather than full synchronied
*/
public synchronized void saveSecurityConfig(SecurityManagerConfig config) throws Exception {
SecurityManagerConfig oldConfig = new SecurityManagerConfig(this.securityConfig);
SecurityConfigValidator validator = new SecurityConfigValidator(this);
validator.validateManagerConfig(config,oldConfig);
//save the current config to fall back to
// The whole try block should run as a transaction, unfortunately
// this is not possible with files.
try {
//set the new configuration
init(config);
if (config.getConfigPasswordEncrypterName().equals(
oldConfig.getConfigPasswordEncrypterName())==false){
updateConfigurationFilesWithEncryptedFields();
}
//save out new configuration
xStreamPersist(new File(getSecurityRoot(), CONFIG_FILENAME), config, globalPersister());
}
catch(IOException e) {
//exception, revert back to known working config
LOGGER.log(Level.SEVERE, "Error saving security config, reverting back to previous", e);
init(oldConfig);
return;
}
fireChanged();
}
/**
* Returns the master password configuration.
*/
public MasterPasswordConfig getMasterPasswordConfig() {
return new MasterPasswordConfig(masterPasswordConfig);
}
/**
* Saves the master password configuration.
*
* @param config The new configuration.
* @param currPasswd The current master password.
* @param newPasswd The new password, may be null depending on strategy used.
* @param newPasswdConfirm The confirmation password
*
* @throws MasterPasswordChangeException If there is a validation error with the new config
* @throws PasswordPolicyException If the new password violates the master password policy
*/
public synchronized void saveMasterPasswordConfig(MasterPasswordConfig config,
char[] currPasswd, char[] newPasswd, char[] newPasswdConfirm) throws Exception {
//load the (possibly new) master password provider
MasterPasswordProviderConfig mpProviderConfig =
loadMasterPassswordProviderConfig(config.getProviderName());
MasterPasswordProvider mpProvider = loadMasterPasswordProvider(config.getProviderName());
if (mpProviderConfig.isReadOnly()) {
//new password comes from the provider
newPasswd = mpProvider.getMasterPassword();
}
//first validate the password change
MasterPasswordChangeRequest req = new MasterPasswordChangeRequest();
req.setCurrentPassword(currPasswd);
req.setNewPassword(newPasswd);
req.setConfirmPassword(newPasswdConfirm);
MasterPasswordChangeValidator val = new MasterPasswordChangeValidator(this);
val.validateChangeRequest(req);
//validate the new config
MasterPasswordConfigValidator validator = new MasterPasswordConfigValidator(this);
validator.validateMasterPasswordConfig(config);
//save the current config to fall back to
MasterPasswordConfig oldConfig = new MasterPasswordConfig(this.masterPasswordConfig);
String oldMasterPasswdDigest = masterPasswdDigest;
KeyStoreProvider ksProvider = getKeyStoreProvider();
synchronized (ksProvider) {
ksProvider.prepareForMasterPasswordChange(currPasswd, newPasswdConfirm);
try {
if (!mpProviderConfig.isReadOnly()) {
//write it back first
try {
mpProvider.setMasterPassword(newPasswd);
} catch (Exception e) {
throw new IOException(e);
}
}
//save out the master password config
saveMasterPasswordConfig(config);
//redigest
masterPasswdDigest = computeAndSaveMasterPasswordDigest(newPasswdConfirm);
//commit the password change to the keystore
ksProvider.commitMasterPasswordChange();
if (!config.getProviderName().equals(oldConfig.getProviderName())){
//TODO: reencrypt the keystore? restart the server?
//updateConfigurationFilesWithEncryptedFields();
}
}
catch(IOException e) {
//error occured, roll back
ksProvider.abortMasterPasswordChange();
//revert to old master password config
this.masterPasswordConfig = oldConfig;
this.masterPasswdDigest = oldMasterPasswdDigest;
saveMasterPasswordDigest(oldMasterPasswdDigest);
throw e;
}
}
}
/**
* Saves master password config out directly, not during a password change.
*/
public void saveMasterPasswordConfig(MasterPasswordConfig config) throws IOException {
xStreamPersist(new File(getSecurityRoot(), MASTER_PASSWD_CONFIG_FILENAME),
config, globalPersister());
this.masterPasswordConfig = new MasterPasswordConfig(config);
}
/**
* Checks the specified password against the master password.
*/
public boolean checkMasterPassword(String passwd) {
return checkMasterPassword(passwd.toCharArray());
}
/**
* Checks the specified password against the master password.
*/
public boolean checkMasterPassword(char[] passwd) {
GeoServerDigestPasswordEncoder pwEncoder =
loadPasswordEncoder(GeoServerDigestPasswordEncoder.class);
if (masterPasswdDigest == null) {
synchronized (this) {
if (masterPasswdDigest == null) {
try {
//look for file
masterPasswdDigest = loadMasterPasswordDigest();
}
catch(IOException e) {
throw new RuntimeException("Unable to create master password digest", e);
}
}
}
}
return pwEncoder.isPasswordValid(masterPasswdDigest, passwd, null);
}
String loadMasterPasswordDigest() throws IOException {
//look for file
File pwDigestFile = new File(getSecurityRoot(),MASTER_PASSWD_DIGEST_FILENAME);
if (pwDigestFile.exists()) {
FileInputStream fin = new FileInputStream(pwDigestFile);
try {
return IOUtils.toString(fin);
}
finally {
fin.close();
}
}
else {
//compute and store
char[] masterPasswd = getMasterPassword();
try {
return computeAndSaveMasterPasswordDigest(masterPasswd);
}
finally {
disposePassword(masterPasswd);
}
}
}
void saveMasterPasswordDigest(String masterPasswdDigest) throws IOException {
FileOutputStream fout =
new FileOutputStream(new File(getSecurityRoot(),MASTER_PASSWD_DIGEST_FILENAME));
try {
IOUtils.write(masterPasswdDigest, fout);
}
finally {
fout.close();
}
}
String computeAndSaveMasterPasswordDigest(char[] passwd) throws IOException {
GeoServerDigestPasswordEncoder pwEncoder =
loadPasswordEncoder(GeoServerDigestPasswordEncoder.class);
String masterPasswdDigest = pwEncoder.encodePassword(passwd, null);
saveMasterPasswordDigest(masterPasswdDigest);
return masterPasswdDigest;
}
/**
* Returns the master password in plain text.
* <p>
* This method is package protected and only allowed to be called by classes in this package.
* </p>
* <p>
* The password is returned as a char array rather than string to allow for the scrambling of
* the password after use. Since strings are immutable they can not be scrambled. All code that
* calls this method should follow the following guidelines:
* <ol>
* <li>Never turn the result into a String object</li>
* <li>Always call {@link #disposeMasterPassword(char[])} (ideally in a finally block)
* when done with the password.</li>
* </ol>
* </p>
* <p>
* For example:
* <code>
* <pre>
* char[] passwd = manager.getMasterPassword();
* try {
* //do something
* }
* finally {
* manager.disposeMasterPassword(passwd);
* }
* </pre>
* </code>
* </p>
*/
char[] getMasterPassword() {
try {
MasterPasswordProvider mpp =
loadMasterPasswordProvider(getMasterPasswordConfig().getProviderName());
return mpp.getMasterPassword();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Disposes the char array containing the plain text password.
*/
public void disposePassword(char[] passwd) {
SecurityUtils.scramble(passwd);
}
/**
* Disposes the byte array containing the plain text password.
*/
public void disposePassword(byte[] passwd) {
SecurityUtils.scramble(passwd);
}
/**
* Loads a user {@link MasterPasswordProviderConfig} from a named configuration.
* <p>
* This method returns <code>null</code> if the provider config is not found.
* </p>
*
* @param name The name of the master password provider configuration.
*/
public MasterPasswordProviderConfig loadMasterPassswordProviderConfig(String name)
throws IOException {
return masterPasswordProviderHelper.loadConfig(name);
}
/**
* Loads a user {@link MasterPasswordProvider} from a named configuration.
* <p>
* This method returns <code>null</code> if the provider config is not found.
* </p>
*
* @param name The name of the master password provider configuration.
*/
protected MasterPasswordProvider loadMasterPasswordProvider(String name) throws IOException {
return masterPasswordProviderHelper.load(name);
}
/**
* Saves/persists a master password provider configuration.
*/
public void saveMasterPasswordProviderConfig(MasterPasswordProviderConfig config)
throws IOException,SecurityConfigException {
saveMasterPasswordProviderConfig(config, true);
}
/**
* Saves master password provider configuration, optionally skipping validation.
* <p>
* Validation only skipped during migration.
* </p>
*/
void saveMasterPasswordProviderConfig(MasterPasswordProviderConfig config, boolean validate)
throws IOException,SecurityConfigException {
SecurityConfigValidator validator = SecurityConfigValidator
.getConfigurationValiator(MasterPasswordProvider.class, config.getClassName());
if (config.getId() == null) {
config.initBeforeSave();
if (validate) {
validator.validateAddMasterPasswordProvider(config);
}
}
else {
if (validate) {
validator.validateModifiedMasterPasswordProvider(config,
masterPasswordProviderHelper.loadConfig(config.getName()));
}
}
masterPasswordProviderHelper.saveConfig(config);
}
/**
* Removes a master password provider configuration.
*/
public void removeMasterPasswordProvder(MasterPasswordProviderConfig config) throws IOException,SecurityConfigException {
SecurityConfigValidator validator = SecurityConfigValidator
.getConfigurationValiator(MasterPasswordProvider.class, config.getClassName());
validator.validateRemoveMasterPasswordProvider(config);
masterPasswordProviderHelper.removeConfig(config.getName());
}
/**
* Lists all available master password provider configurations.
*/
public SortedSet<String> listMasterPasswordProviders() throws IOException {
return listFiles(getMasterPasswordProviderRoot());
}
void fireChanged() {
for (SecurityManagerListener l : listeners) {
l.handlePostChanged(this);
}
}
/**
* @return the master password used for the migration
* @throws Exception
*/
char[] extractMasterPasswordForMigration(Properties props) throws Exception {
Map<String,String> candidates = new HashMap<String,String>();
String defaultPasswordAsString = new String(MASTER_PASSWD_DEFAULT);
if (props!=null) {
//load user.properties populate the services
UserAttributeEditor configAttribEd = new UserAttributeEditor();
for (Iterator<Object> iter = props.keySet().iterator(); iter.hasNext();) {
String username = (String) iter.next();
configAttribEd.setAsText(props.getProperty(username));
UserAttribute attr = (UserAttribute) configAttribEd.getValue();
if (attr == null) continue;
// The master password policy is not yet available, the default is to
// have a minimum of 8 chars --> all passwords shorter than 8 chars
// are no candidates
if (attr.getPassword()==null || attr.getPassword().length() <8 )
continue;
// The default password is not allowed
if (defaultPasswordAsString.equals(attr.getPassword()))
continue;
// the user named "admin" having a non default password is the primary candiate
if (GeoServerUser.ADMIN_USERNAME.equals(username)) {
candidates.put(GeoServerUser.ADMIN_USERNAME,attr.getPassword());
continue;
}
// other users having the amin role are secondary candidates
if (attr.getAuthorities().contains(GeoServerRole.ADMIN_ROLE)) {
candidates.put(username,attr.getPassword());
}
}
}
String username = GeoServerUser.ADMIN_USERNAME;
String masterPW=candidates.get(username);
if (masterPW==null && candidates.size()>0) {
username = candidates.keySet().iterator().next();
masterPW=candidates.get(username);
}
String message = null;
File info = new File(getSecurityRoot(),MASTER_PASSWD_INFO_FILENAME);
char[] masterPasswordArray=null;
if (masterPW!=null) {
message="Master password is identical to the password of user: "+username;
masterPasswordArray=masterPW.toCharArray();
writeMasterPasswordInfo(info,message,null);
} else {
message="The generated master password is: ";
masterPasswordArray = getRandomPassworddProvider().getRandomPassword(8);
writeMasterPasswordInfo(info,message,masterPasswordArray);
}
LOGGER.info("Information regarding the master password is in: "+ info.getCanonicalPath());
return masterPasswordArray;
}
/**
* Writes a file containing info about the master password.
*
* @param file
* @param message
* @param masterPasswordArray
* @throws IOException
*/
void writeMasterPasswordInfo(File file,String message,char[] masterPasswordArray) throws IOException {
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
w.write("This file was created at "+dateFormat.format(new Date()));
w.newLine();
w.newLine();
w.write(message);
if (masterPasswordArray!=null)
w.write(masterPasswordArray);
w.newLine();
w.newLine();
w.write("Test the master password by logging in as user \"root\"");
w.newLine();
w.newLine();
w.write("This file should be removed after reading !!!.");
w.newLine();
w.close();
}
/**
* Method to dump master password to a file
*
* The file name is the shared secret between the administrator and GeoServer.
*
* The method inspects the stack trace to check for an authorized calling method.
* The authenticated principal has to be an administrator
*
* If authorization fails, a warning is written in the log and the return
* code is <code>false</code>. On success, the return code is <code>true</code>.
*
* @param file
* @return
* @throws IOException
*/
public boolean dumpMasterPassword(File file) throws IOException {
if (checkAuthenticationForAdminRole()==false) {
LOGGER.warning("Unautorized user tries to dump master password");
return false;
}
String[][] allowedMethods = new String [][]{
{"org.geoserver.security.GeoServerSecurityManagerTest","testMasterPasswordDump"},
{"org.geoserver.security.web.passwd.MasterPasswordInfoPage","dumpMasterPassword"}
};
String result = checkStackTrace(10, allowedMethods);
if (result!=null) {
LOGGER.warning("Dump master password is called by an unautorized method\n"+result);
return false;
}
String message = "The current master password is: ";
writeMasterPasswordInfo(file, message, getMasterPassword());
return true;
}
/**
* Get master password for REST configuraton
*
* The method inspects the stack trace to check for an authorized calling method.
* The authenticated principal has to be an administrator
*
* If authorization fails, an IOException is thrown
*
* @return
* @throws IOException
*/
public char[] getMasterPasswordForREST() throws IOException {
if (checkAuthenticationForAdminRole()==false) {
throw new IOException("Unauthorized user tries to read master password");
}
String[][] allowedMethods = new String [][]{
{"org.geoserver.security.rest.MasterPasswordResource","getMap"}
};
String result = checkStackTrace(10, allowedMethods);
if (result!=null) {
throw new IOException ("Unauthorized method wants to read master password\n"+result);
}
return getMasterPassword();
}
/**
* Checks if the stack trace contains allowed methods.
* It it contains allowed methods, return <code>null</code>,
* if not return a String listing the methods.
*
* @param countMethodsToCheck
* @param allowedMethods
* @return
*/
String checkStackTrace(int countMethodsToCheck,String[][] allowedMethods) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
boolean isAllowed=false;
for (int i = 0; i< countMethodsToCheck;i++) {
StackTraceElement element = stackTraceElements[i];
for (String[] methodEntry : allowedMethods) {
if (methodEntry[0].equals(element.getClassName())&&
methodEntry[1].equals(element.getMethodName())) {
isAllowed=true;
break;
}
}
}
if (isAllowed) {
return null;
}
else {
StringBuffer buff = new StringBuffer();
for (int i = 0; i< countMethodsToCheck;i++) {
StackTraceElement element = stackTraceElements[i];
buff.append(element.getClassName()).append(" : ").
append(element.getMethodName()).append("\n");
}
return buff.toString();
}
}
/**
* converts an 2.1.x security configuration to 2.2.x
*
* @return <code>true</code> if migration has taken place
* @throws Exception
*/
boolean migrateFrom21() throws Exception{
if (getRoleRoot(false) != null) {
File oldUserFile = new File(getSecurityRoot(), "users.properties.old");
if (oldUserFile.exists()) {
LOGGER.warning(oldUserFile.getCanonicalPath()+" could be removed manually");
}
return false; // already migrated
}
LOGGER.info("Start security migration");
//create required directories
getRoleRoot();
getUserGroupRoot();
getAuthRoot();
getPasswordPolicyRoot();
getFilterRoot();
getMasterPasswordProviderRoot();
//master password configuration
MasterPasswordProviderConfig mpProviderConfig = loadMasterPassswordProviderConfig("default");
if (mpProviderConfig == null) {
mpProviderConfig = new URLMasterPasswordProviderConfig();
mpProviderConfig.setName("default");
mpProviderConfig.setClassName(URLMasterPasswordProvider.class.getCanonicalName());
mpProviderConfig.setReadOnly(false);
((URLMasterPasswordProviderConfig)mpProviderConfig).setURL(new URL("file:passwd"));
((URLMasterPasswordProviderConfig)mpProviderConfig).setEncrypting(true);
saveMasterPasswordProviderConfig(mpProviderConfig, false);
//save out the default master password
MasterPasswordProvider mpProvider =
loadMasterPasswordProvider(mpProviderConfig.getName());
File propFile = new File(getSecurityRoot(), "users.properties");
Properties userprops=null;
if (propFile.exists())
userprops = Util.loadPropertyFile(propFile);
mpProvider.setMasterPassword(extractMasterPasswordForMigration(userprops));
}
MasterPasswordConfig mpConfig = new MasterPasswordConfig();
mpConfig.setProviderName(mpProviderConfig.getName());
saveMasterPasswordConfig(mpConfig);
// check for services.properties, create if necessary
File serviceFile = new File(getSecurityRoot(), "services.properties");
if (serviceFile.exists()==false) {
FileUtils.copyURLToFile(Util.class.getResource("serviceTemplate.properties"),
serviceFile);
}
long checkInterval = 10000; // 10 secs
//check for the default user group service, create if necessary
GeoServerUserGroupService userGroupService =
loadUserGroupService(XMLUserGroupService.DEFAULT_NAME);
KeyStoreProvider keyStoreProvider = getKeyStoreProvider();
keyStoreProvider.reloadKeyStore();
keyStoreProvider.setUserGroupKey(
XMLUserGroupService.DEFAULT_NAME, randomPasswdProvider.getRandomPassword(32));
keyStoreProvider.storeKeyStore();
PasswordValidator validator =
loadPasswordValidator(PasswordValidator.DEFAULT_NAME);
if (validator==null) {
// Policy allows any password except null, this is the default
// at before migration
PasswordPolicyConfig pwpconfig = new PasswordPolicyConfig();
pwpconfig.setName(PasswordValidator.DEFAULT_NAME);
pwpconfig.setClassName(PasswordValidatorImpl.class.getName());
pwpconfig.setMinLength(0);
savePasswordPolicy(pwpconfig);
validator = loadPasswordValidator(PasswordValidator.DEFAULT_NAME);
}
validator = loadPasswordValidator(PasswordValidator.MASTERPASSWORD_NAME);
if (validator==null) {
// Policy requires a minimum of 8 chars for the master password
PasswordPolicyConfig pwpconfig = new PasswordPolicyConfig();
pwpconfig.setName(PasswordValidator.MASTERPASSWORD_NAME);
pwpconfig.setClassName(PasswordValidatorImpl.class.getName());
pwpconfig.setMinLength(8);
savePasswordPolicy(pwpconfig);
validator = loadPasswordValidator(PasswordValidator.MASTERPASSWORD_NAME);
}
if (userGroupService == null) {
XMLUserGroupServiceConfig ugConfig = new XMLUserGroupServiceConfig();
ugConfig.setName(XMLUserGroupService.DEFAULT_NAME);
ugConfig.setClassName(XMLUserGroupService.class.getName());
ugConfig.setCheckInterval(checkInterval);
ugConfig.setFileName(XMLConstants.FILE_UR);
ugConfig.setValidating(true);
// start with weak encryption, plain passwords can be restored
ugConfig.setPasswordEncoderName(
loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, null, false).getName());
ugConfig.setPasswordPolicyName(PasswordValidator.DEFAULT_NAME);
saveUserGroupService(ugConfig);
userGroupService = loadUserGroupService(XMLUserGroupService.DEFAULT_NAME);
}
//check for the default role service, create if necessary
GeoServerRoleService roleService =
loadRoleService(XMLRoleService.DEFAULT_NAME);
if (roleService == null) {
XMLRoleServiceConfig gaConfig = new XMLRoleServiceConfig();
gaConfig.setName(XMLRoleService.DEFAULT_NAME);
gaConfig.setClassName(XMLRoleService.class.getName());
gaConfig.setCheckInterval(checkInterval);
gaConfig.setFileName(XMLConstants.FILE_RR);
gaConfig.setValidating(true);
gaConfig.setAdminRoleName(XMLRoleService.DEFAULT_LOCAL_ADMIN_ROLE);
gaConfig.setGroupAdminRoleName(XMLRoleService.DEFAULT_LOCAL_GROUP_ADMIN_ROLE);
saveRoleService(gaConfig);
roleService = loadRoleService(XMLRoleService.DEFAULT_NAME);
}
String filterName = GeoServerSecurityFilterChain.BASIC_AUTH_FILTER;
GeoServerSecurityFilter filter = loadFilter(filterName);
if (filter==null) {
BasicAuthenticationFilterConfig bfConfig = new BasicAuthenticationFilterConfig();
bfConfig.setName(filterName);
bfConfig.setClassName(GeoServerBasicAuthenticationFilter.class.getName());
bfConfig.setUseRememberMe(true);
saveFilter(bfConfig);
}
/*filterName = GeoServerSecurityFilterChain.BASIC_AUTH_NO_REMEMBER_ME_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
BasicAuthenticationFilterConfig bfConfig = new BasicAuthenticationFilterConfig();
bfConfig.setClassName(GeoServerBasicAuthenticationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setUseRememberMe(false);
saveFilter(bfConfig);
}*/
filterName =GeoServerSecurityFilterChain.FORM_LOGIN_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
UsernamePasswordAuthenticationFilterConfig upConfig= new UsernamePasswordAuthenticationFilterConfig();
upConfig.setClassName(GeoServerUserNamePasswordAuthenticationFilter.class.getName());
upConfig.setName(filterName);
upConfig.setUsernameParameterName(UsernamePasswordAuthenticationFilterConfig.DEFAULT_USERNAME_PARAM);
upConfig.setPasswordParameterName(UsernamePasswordAuthenticationFilterConfig.DEFAULT_PASSWORD_PARAM);
saveFilter(upConfig);
}
filterName =GeoServerSecurityFilterChain.SECURITY_CONTEXT_ASC_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
SecurityContextPersistenceFilterConfig pConfig= new SecurityContextPersistenceFilterConfig();
pConfig.setClassName(GeoServerSecurityContextPersistenceFilter.class.getName());
pConfig.setName(filterName);
pConfig.setAllowSessionCreation(true);
saveFilter(pConfig);
}
filterName =GeoServerSecurityFilterChain.SECURITY_CONTEXT_NO_ASC_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
SecurityContextPersistenceFilterConfig pConfig= new SecurityContextPersistenceFilterConfig();
pConfig.setClassName(GeoServerSecurityContextPersistenceFilter.class.getName());
pConfig.setName(filterName);
pConfig.setAllowSessionCreation(false);
saveFilter(pConfig);
}
filterName =GeoServerSecurityFilterChain.ANONYMOUS_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
AnonymousAuthenticationFilterConfig aConfig= new AnonymousAuthenticationFilterConfig();
aConfig.setClassName(GeoServerAnonymousAuthenticationFilter.class.getName());
aConfig.setName(filterName);
saveFilter(aConfig);
}
filterName =GeoServerSecurityFilterChain.REMEMBER_ME_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
RememberMeAuthenticationFilterConfig rConfig= new RememberMeAuthenticationFilterConfig();
rConfig.setClassName(GeoServerRememberMeAuthenticationFilter.class.getName());
rConfig.setName(filterName);
saveFilter(rConfig);
}
filterName =GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
filter = loadFilter(filterName);
if (filter==null) {
SecurityInterceptorFilterConfig siConfig= new SecurityInterceptorFilterConfig();
siConfig.setClassName(GeoServerSecurityInterceptorFilter.class.getName());
siConfig.setName(filterName);
siConfig.setAllowIfAllAbstainDecisions(false);
siConfig.setSecurityMetadataSource("geoserverMetadataSource");
saveFilter(siConfig);
}
filterName =GeoServerSecurityFilterChain.FILTER_SECURITY_REST_INTERCEPTOR;
filter = loadFilter(filterName);
if (filter==null) {
SecurityInterceptorFilterConfig siConfig= new SecurityInterceptorFilterConfig();
siConfig.setClassName(GeoServerSecurityInterceptorFilter.class.getName());
siConfig.setName(filterName);
siConfig.setAllowIfAllAbstainDecisions(false);
siConfig.setSecurityMetadataSource("restFilterDefinitionMap");
saveFilter(siConfig);
}
filterName =GeoServerSecurityFilterChain.FORM_LOGOUT_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
LogoutFilterConfig loConfig= new LogoutFilterConfig();
loConfig.setClassName(GeoServerLogoutFilter.class.getName());
loConfig.setName(filterName);
saveFilter(loConfig);
}
filterName = GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
ExceptionTranslationFilterConfig bfConfig= new ExceptionTranslationFilterConfig();
bfConfig.setClassName(GeoServerExceptionTranslationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setAuthenticationFilterName(null);
bfConfig.setAccessDeniedErrorPage("/accessDenied.jsp");
saveFilter(bfConfig);
}
filterName = GeoServerSecurityFilterChain.GUI_EXCEPTION_TRANSLATION_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
ExceptionTranslationFilterConfig bfConfig= new ExceptionTranslationFilterConfig();
bfConfig.setClassName(GeoServerExceptionTranslationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setAuthenticationFilterName(GeoServerSecurityFilterChain.FORM_LOGIN_FILTER);
bfConfig.setAccessDeniedErrorPage("/accessDenied.jsp");
saveFilter(bfConfig);
}
//check for the default auth provider, create if necessary
GeoServerAuthenticationProvider authProvider = (GeoServerAuthenticationProvider)
loadAuthenticationProvider(GeoServerAuthenticationProvider.DEFAULT_NAME);
if (authProvider == null) {
UsernamePasswordAuthenticationProviderConfig upAuthConfig =
new UsernamePasswordAuthenticationProviderConfig();
upAuthConfig.setName(GeoServerAuthenticationProvider.DEFAULT_NAME);
upAuthConfig.setClassName(UsernamePasswordAuthenticationProvider.class.getName());
upAuthConfig.setUserGroupServiceName(userGroupService.getName());
saveAuthenticationProvider(upAuthConfig);
authProvider = loadAuthenticationProvider(GeoServerAuthenticationProvider.DEFAULT_NAME);
}
//save the top level config
SecurityManagerConfig config = new SecurityManagerConfig();
config.setRoleServiceName(roleService.getName());
config.getAuthProviderNames().add(authProvider.getName());
config.setEncryptingUrlParams(false);
// start with weak encryption
config.setConfigPasswordEncrypterName(
loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, true, false).getName());
// setup the default remember me service
RememberMeServicesConfig rememberMeConfig = new RememberMeServicesConfig();
rememberMeConfig.setClassName(GeoServerTokenBasedRememberMeServices.class.getName());
config.setRememberMeService(rememberMeConfig);
config.setFilterChain(GeoServerSecurityFilterChain.createInitialChain());
saveSecurityConfig(config);
//TODO: just call initializeFrom
userGroupService.setSecurityManager(this);
roleService.setSecurityManager(this);
//populate the user group and role service
GeoServerUserGroupStore userGroupStore = userGroupService.createStore();
GeoServerRoleStore roleStore = roleService.createStore();
//migrate from users.properties
File usersFile = new File(getSecurityRoot(), "users.properties");
if (usersFile.exists()) {
//load user.properties populate the services
Properties props = Util.loadPropertyFile(usersFile);
UserAttributeEditor configAttribEd = new UserAttributeEditor();
for (Iterator<Object> iter = props.keySet().iterator(); iter.hasNext();) {
// the attribute editors parses the list of strings into password, username and enabled
// flag
String username = (String) iter.next();
configAttribEd.setAsText(props.getProperty(username));
// if the parsing succeeded turn that into a user object
UserAttribute attr = (UserAttribute) configAttribEd.getValue();
if (attr != null) {
GeoServerUser user =
userGroupStore.createUserObject(username, attr.getPassword(), attr.isEnabled());
userGroupStore.addUser(user);
for (GrantedAuthority auth : attr.getAuthorities()) {
String roleName = GeoServerRole.ADMIN_ROLE.getAuthority().equals(auth.getAuthority()) ?
XMLRoleService.DEFAULT_LOCAL_ADMIN_ROLE : auth.getAuthority();
GeoServerRole role =
roleStore.getRoleByName(roleName);
if (role==null) {
role = roleStore.createRoleObject(roleName);
roleStore.addRole(role);
}
roleStore.associateRoleToUser(role, username);
}
}
}
} else {
// no user.properties, populate with default user and roles
if (userGroupService.getUserByUsername(GeoServerUser.ADMIN_USERNAME) == null) {
userGroupStore.addUser(GeoServerUser.createDefaultAdmin());
GeoServerRole localAdminRole = roleStore.createRoleObject(XMLRoleService.DEFAULT_LOCAL_ADMIN_ROLE);
roleStore.addRole(localAdminRole);
roleStore.associateRoleToUser(localAdminRole, GeoServerUser.ADMIN_USERNAME);
}
}
//add the local group administrator role
if (roleStore.getRoleByName(XMLRoleService.DEFAULT_LOCAL_GROUP_ADMIN_ROLE) == null) {
roleStore.addRole(roleStore.createRoleObject(XMLRoleService.DEFAULT_LOCAL_GROUP_ADMIN_ROLE));
}
// replace all occurrences of ROLE_ADMINISTRATOR in the property files
// TODO Justin, a little bit brute force, is this ok ?
for (String filename : new String[]{"services.properties","layers.properties","rest.properties"}) {
File file = new File(getSecurityRoot(), filename);
if (file.exists()==false) continue;
List<String> lines = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null)
lines.add(line.replace(GeoServerRole.ADMIN_ROLE.getAuthority(), XMLRoleService.DEFAULT_LOCAL_ADMIN_ROLE));
reader.close();
PrintWriter writer = new PrintWriter(new FileWriter(file));
for (String s : lines) {
writer.println(s);
}
writer.close();
}
// check for roles in services.properties but not in user.properties
serviceFile = new File(getSecurityRoot(), "services.properties");
if (serviceFile.exists()) {
Properties props = Util.loadPropertyFile(serviceFile);
for (Entry<Object,Object> entry: props.entrySet()) {
StringTokenizer tokenizer = new StringTokenizer((String)entry.getValue(),",");
while (tokenizer.hasMoreTokens()) {
String roleName = tokenizer.nextToken().trim();
if (roleName.length()>0) {
if (roleStore.getRoleByName(roleName)==null)
roleStore.addRole(roleStore.createRoleObject(roleName));
}
}
}
}
// check for roles in data.properties but not in user.properties
File dataFile = new File(getSecurityRoot(), "layers.properties");
if (dataFile.exists()) {
Properties props = Util.loadPropertyFile(dataFile);
for (Entry<Object,Object> entry: props.entrySet()) {
if ("mode".equals(entry.getKey().toString()))
continue; // skip mode directive
StringTokenizer tokenizer = new StringTokenizer((String)entry.getValue(),",");
while (tokenizer.hasMoreTokens()) {
String roleName = tokenizer.nextToken().trim();
if (roleName.length()>0 && roleName.equals("*")==false) {
if (roleStore.getRoleByName(roleName)==null)
roleStore.addRole(roleStore.createRoleObject(roleName));
}
}
}
}
//persist the changes
roleStore.store();
userGroupStore.store();
// first part of migration finished, rename old file
if (usersFile.exists()) {
File oldUserFile = new File(usersFile.getCanonicalPath()+".old");
usersFile.renameTo(oldUserFile);
LOGGER.info("Renamed "+usersFile.getCanonicalPath() + " to " +
oldUserFile.getCanonicalPath());
}
LOGGER.info("End security migration");
return true;
}
/**
* migration from 2.2.x to 2.3.x
* return <code>true</code> if migration has taken place
*
* @return
* @throws Exception
*/
boolean migrateFrom22(boolean migratedFrom21) throws Exception{
String filterName =GeoServerSecurityFilterChain.ROLE_FILTER;
GeoServerSecurityFilter filter = loadFilter(filterName);
File logoutFilterDir = new File(getFilterRoot(),GeoServerSecurityFilterChain.FORM_LOGOUT_FILTER);
File oldLogoutFilterConfig = new File(logoutFilterDir,"config.xml.2.2.x");
File oldSecManagerConfig = new File(getSecurityRoot(), "config.xml.2.2.x");
if (filter!=null) {
if (oldLogoutFilterConfig.exists())
LOGGER.warning(oldLogoutFilterConfig.getCanonicalPath()+" could be removed manually");
if (oldSecManagerConfig.exists())
LOGGER.warning(oldSecManagerConfig.getCanonicalPath()+" could be removed manually");
return false; // already migrated
}
// add role filter
RoleFilterConfig rfConfig= new RoleFilterConfig();
rfConfig.setClassName(GeoServerRoleFilter.class.getName());
rfConfig.setName(filterName);
rfConfig.setHttpResponseHeaderAttrForIncludedRoles(GeoServerRoleFilter.DEFAULT_HEADER_ATTRIBUTE);
rfConfig.setRoleConverterName(GeoServerRoleFilter.DEFAULT_ROLE_CONVERTER);
saveFilter(rfConfig);
// add ssl filter
SSLFilterConfig sslConfig= new SSLFilterConfig();
sslConfig.setClassName(GeoServerSSLFilter.class.getName());
sslConfig.setName(GeoServerSecurityFilterChain.SSL_FILTER);
sslConfig.setSslPort(443);
saveFilter(sslConfig);
// set redirect url after successful logout
if (migratedFrom21== false)
FileUtils.copyFile(new File(logoutFilterDir,"config.xml"), oldLogoutFilterConfig);
LogoutFilterConfig loConfig = (LogoutFilterConfig) loadFilterConfig(GeoServerSecurityFilterChain.FORM_LOGOUT_FILTER);
loConfig.setRedirectURL(GeoServerLogoutFilter.URL_AFTER_LOGOUT);
saveFilter(loConfig);
if (migratedFrom21== false)
FileUtils.copyFile(new File(getSecurityRoot(), "config.xml"), oldSecManagerConfig);
SecurityManagerConfig config = loadSecurityConfig();
for (RequestFilterChain chain : config.getFilterChain().getRequestChains()) {
if (chain.getFilterNames().contains(GeoServerSecurityFilterChain.SECURITY_CONTEXT_ASC_FILTER)) {
chain.setAllowSessionCreation(true);
chain.getFilterNames().remove(GeoServerSecurityFilterChain.SECURITY_CONTEXT_ASC_FILTER);
}
if (chain.getFilterNames().contains(GeoServerSecurityFilterChain.SECURITY_CONTEXT_NO_ASC_FILTER)) {
chain.setAllowSessionCreation(false);
chain.getFilterNames().remove(GeoServerSecurityFilterChain.SECURITY_CONTEXT_NO_ASC_FILTER);
}
// prepare web chain
if (GeoServerSecurityFilterChain.WEB_CHAIN_NAME.equals(chain.getName())) {
// replace exception translation filter
int index = chain.getFilterNames().indexOf(GeoServerSecurityFilterChain.GUI_EXCEPTION_TRANSLATION_FILTER);
if (index!=-1)
chain.getFilterNames().set(index, GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER);
// inject form login filter if necessary
if (chain.getFilterNames().indexOf(GeoServerSecurityFilterChain.FORM_LOGIN_FILTER)== -1) {
index=chain.getFilterNames().indexOf(GeoServerSecurityFilterChain.ANONYMOUS_FILTER);
if (index==-1)
index=chain.getFilterNames().indexOf(GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR);
if (index!=-1)
chain.getFilterNames().add(index, GeoServerSecurityFilterChain.FORM_LOGIN_FILTER);
}
}
// remove dynamic translation filter
chain.getFilterNames().remove(GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER);
chain.getFilterNames().remove(GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR);
chain.getFilterNames().remove(GeoServerSecurityFilterChain.FILTER_SECURITY_REST_INTERCEPTOR);
}
// gui filter not needed any more
removeFilter(loadFilterConfig(GeoServerSecurityFilterChain.GUI_EXCEPTION_TRANSLATION_FILTER));
saveSecurityConfig(config);
// load and store all filter configuration
// some filter configurations may have their class name as top level xml element in config.xml,
// the alias should be used instead, this was bug fixed during GSIP 82
if (migratedFrom21== false) {
for (String fName : listFilters()) {
SecurityFilterConfig fConfig = loadFilterConfig(fName );
if (fConfig!=null)
saveFilter(fConfig);
}
}
return true;
}
/**
* converts an 2.3.x security configuration to 2.4.x
*
* @return <code>true</code> if migration has taken place
* @throws Exception
*/
boolean migrateFrom23() throws Exception{
SecurityManagerConfig config = loadSecurityConfig();
RequestFilterChain webChain =
config.getFilterChain().getRequestChainByName(GeoServerSecurityFilterChain.WEB_CHAIN_NAME);
boolean migrated=false;
List<String>patterns = webChain.getPatterns();
if (patterns.contains("/")==false) {
patterns.add("/");
saveSecurityConfig(config);
migrated |= true;
}
return migrated;
}
/**
* Remove erroneous access denied page (HTTP) 403 (see GEOS-4943)
* The page /accessDeniedPage does not exist and would not work
* if it exists.
*
* @throws Exception
*/
void removeErroneousAccessDeniedPage() throws Exception {
ExceptionTranslationFilterConfig config =
(ExceptionTranslationFilterConfig) loadFilterConfig(GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER);
if (config!=null && "/accessDenied.jsp".equals(config.getAccessDeniedErrorPage())) {
config.setAccessDeniedErrorPage(null);
saveFilter(config);
}
config =
(ExceptionTranslationFilterConfig) loadFilterConfig(GeoServerSecurityFilterChain.GUI_EXCEPTION_TRANSLATION_FILTER);
if (config!=null && "/accessDenied.jsp".equals(config.getAccessDeniedErrorPage())) {
config.setAccessDeniedErrorPage(null);
saveFilter(config);
}
}
/*
* looks up security plugins
*/
public List<GeoServerSecurityProvider> lookupSecurityProviders() {
List<GeoServerSecurityProvider> list = new ArrayList<GeoServerSecurityProvider>();
for (GeoServerSecurityProvider provider :
GeoServerExtensions.extensions(GeoServerSecurityProvider.class, appContext)) {
if (!provider.isAvailable()) {
continue;
}
list.add(provider);
}
return list;
}
/*
* list files in a directory.
*/
SortedSet<String> listFiles(File dir) {
SortedSet<String> result = new TreeSet<String>();
File[] dirs = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() && new File(pathname, CONFIG_FILENAME).exists();
}
});
for (File d : dirs) {
result.add(d.getName());
}
return result;
}
XStreamPersister globalPersister() throws IOException {
XStreamPersister xp = persister();
xp.getXStream().alias("security", SecurityManagerConfig.class);
xp.getXStream().alias("masterPassword", MasterPasswordConfig.class);
xp.getXStream().registerLocalConverter( SecurityManagerConfig.class, "filterChain",
new FilterChainConverter(xp.getXStream().getMapper()));
// The field anonymousAuth is deprecated
xp.getXStream().omitField(SecurityManagerConfig.class, "anonymousAuth");
return xp;
}
/*
* creates the persister for security plugin configuration.
*/
XStreamPersister persister() throws IOException{
List<GeoServerSecurityProvider> all = lookupSecurityProviders();
//create and configure an xstream persister to load the configuration files
XStreamPersister xp = new XStreamPersisterFactory().createXMLPersister();
xp.getXStream().alias("security", SecurityManagerConfig.class);
for (GeoServerSecurityProvider roleService : all) {
roleService.configure(xp);
}
return xp;
}
/*
* loads the global security config
*/
public SecurityManagerConfig loadSecurityConfig() throws IOException {
return (SecurityManagerConfig) loadConfigFile(getSecurityRoot(), globalPersister());
}
/*
* loads the master password config
*/
public MasterPasswordConfig loadMasterPasswordConfig() throws IOException {
Resource resource = security().get(MASTER_PASSWD_CONFIG_FILENAME);
return loadConfig( MasterPasswordConfig.class, resource, globalPersister() );
}
/**
* reads a config file from the specified directly using the specified xstream persister
*/
<T extends SecurityConfig> T loadConfig( Class<T> config, Resource resource, XStreamPersister xp ) throws IOException {
InputStream in = resource.in();
try {
Object loaded = xp.load(in, SecurityConfig.class);
return config.cast( loaded );
}
finally {
in.close();
}
}
/**
* reads a config file from the specified directly using the specified xstream persister
*/
SecurityConfig loadConfigFile(File directory, String filename, XStreamPersister xp)
throws IOException {
FileInputStream fin = new FileInputStream(new File(directory, filename));
try {
return xp.load(fin, SecurityConfig.class);
}
finally {
fin.close();
}
}
/**
* reads a file named {@value #CONFIG_FILE_NAME} from the specified directly using the specified
* xstream persister
*/
SecurityConfig loadConfigFile(File directory, XStreamPersister xp) throws IOException {
return loadConfigFile(directory, CONFIG_FILENAME, xp);
}
/**
* saves a config file to the specified directly using the specified xstream persister
*/
void saveConfigFile(SecurityConfig config, File directory, String filename, XStreamPersister xp)
throws IOException {
xStreamPersist(new File(directory, filename), config, xp);
}
/**
* saves a file named {@value #CONFIG_FILE_NAME} from the specified directly using the specified xstream
* persister
*/
void saveConfigFile(SecurityConfig config, File directory, XStreamPersister xp)
throws IOException {
saveConfigFile(config, directory, CONFIG_FILENAME, xp);
}
abstract class HelperBase<T, C extends SecurityNamedServiceConfig> {
/*
* list of file watchers
* TODO: we should probably manage these better rather than just throwing them in a
* list, repeated loads will cause this list to fill up with threads
*/
protected List<FileWatcher> fileWatchers = new ArrayList<FileWatcher>();
public abstract T load(String name) throws IOException;
/**
* loads the named entity config from persistence
*/
public C loadConfig(String name, MigrationHelper migrationHelper) throws IOException {
File dir = new File(getRoot(), name);
if (!dir.exists()) {
return null;
}
XStreamPersister xp = persister();
if(migrationHelper != null) {
migrationHelper.migrationPersister(xp);
}
return (C) loadConfigFile(dir, xp);
}
/**
* loads the named entity config from persistence
*/
public C loadConfig(String name) throws IOException {
return loadConfig(name, null);
}
/**
* saves the user group service config to persistence
*/
public void saveConfig(SecurityNamedServiceConfig config) throws IOException {
File dir = new File(getRoot(), config.getName());
dir.mkdir();
boolean isNew = config.getId() == null;
if (isNew) {
config.setId(newId());
}
try {
saveConfigFile(config, dir, persister());
}
catch(Exception e) {
//catch exception, if the config was new, clear out the id since it was not added
if (isNew) {
config.setId(null);
}
if (e instanceof IOException) {
throw (IOException)e;
}
throw new IOException(e);
}
}
String newId() {
return new UID().toString();
}
/**
* removes the user group service config from persistence
*/
public void removeConfig(String name) throws IOException {
FileUtils.deleteDirectory(new File(getRoot(), name));
}
public void destroy() {
for (FileWatcher fw : fileWatchers) {
fw.setTerminate(true);
}
}
/**
* config root
*/
protected abstract File getRoot() throws IOException;
}
class UserGroupServiceHelper extends HelperBase<GeoServerUserGroupService,SecurityUserGroupServiceConfig> {
public GeoServerUserGroupService load(String name) throws IOException {
SecurityNamedServiceConfig config = loadConfig(name);
if (config == null) {
//no such config
return null;
}
//look up the service for this config
GeoServerUserGroupService service = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getUserGroupServiceClass() == null) {
continue;
}
if (p.getUserGroupServiceClass().getName().equals(config.getClassName())) {
service = p.createUserGroupService(config);
break;
}
}
if (service == null) {
throw new IOException("No user group service matching config: " + config);
}
service.setSecurityManager(GeoServerSecurityManager.this);
if (config instanceof SecurityUserGroupServiceConfig){
boolean needsLockProtection =
GeoServerSecurityProvider.getProvider(GeoServerUserGroupService.class,
config.getClassName()).roleServiceNeedsLockProtection();
if (needsLockProtection)
service = new LockingUserGroupService(service);
}
service.setName(name);
service.initializeFromConfig(config);
if (config instanceof FileBasedSecurityServiceConfig) {
FileBasedSecurityServiceConfig fileConfig =
(FileBasedSecurityServiceConfig) config;
if (fileConfig.getCheckInterval()>0) {
Resource resource = getConfigFile( fileConfig.getFileName());
if( resource == null ){
String path = Paths.path("security/usergroup", name, fileConfig.getFileName());
resource = get(path);
}
UserGroupFileWatcher watcher = new UserGroupFileWatcher(resource,service);
service.registerUserGroupLoadedListener(watcher);
watcher.start();
//register the watcher so we can kill it later on disposale
fileWatchers.add(watcher);
}
}
return service;
}
@Override
protected File getRoot() throws IOException {
return getUserGroupRoot();
}
}
class RoleServiceHelper extends HelperBase<GeoServerRoleService,SecurityRoleServiceConfig>{
/**
* Loads the role service for the named config from persistence.
*/
public GeoServerRoleService load(String name) throws IOException {
SecurityNamedServiceConfig config = loadConfig(name);
if (config == null) {
//no such config
return null;
}
//look up the service for this config
GeoServerRoleService service = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getRoleServiceClass() == null) {
continue;
}
if (p.getRoleServiceClass().getName().equals(config.getClassName())) {
service = p.createRoleService(config);
break;
}
}
if (service == null) {
throw new IOException("No authority service matching config: " + config);
}
service.setSecurityManager(GeoServerSecurityManager.this);
if (config instanceof SecurityRoleServiceConfig){
boolean needsLockProtection =
GeoServerSecurityProvider.getProvider(GeoServerRoleService.class,
config.getClassName()).roleServiceNeedsLockProtection();
if (needsLockProtection) {
service = new LockingRoleService(service);
}
}
service.setName(name);
//TODO: do we need this anymore?
service.initializeFromConfig(config);
if (config instanceof FileBasedSecurityServiceConfig) {
FileBasedSecurityServiceConfig fileConfig =
(FileBasedSecurityServiceConfig) config;
if (fileConfig.getCheckInterval()>0) {
Resource resource = getConfigFile( fileConfig.getFileName());
if( resource == null ){
String path = Paths.path("security/role", name, fileConfig.getFileName());
resource = get(path);
}
RoleFileWatcher watcher = new RoleFileWatcher(resource, service, resource.lastmodified());
service.registerRoleLoadedListener(watcher);
watcher.start();
//register the watcher so we can kill it later
fileWatchers.add(watcher);
}
}
return service;
}
@Override
protected File getRoot() throws IOException {
return getRoleRoot();
}
}
/**
* Alternative to {@link GeoServerResourceLoader#find(String)} that supports absolute paths
* for use in test cases.
* <p>
* If an absolute path is used the Resource implementation is provided by {@link Files#asResource(File)}.
*
* @param fileLocation
* @return resource
*/
Resource getConfigFile(String configFileLocation) throws IOException {
File file = new File(configFileLocation);
if (file.isAbsolute()) {
if (file.canRead()) {
return Files.asResource(file); // used by test cases
} else {
throw new IOException("Cannot read file: " + file.getCanonicalPath());
}
}
return null;
}
class PasswordValidatorHelper extends HelperBase<PasswordValidator,PasswordPolicyConfig> {
/**
* Loads the password policy for the named config from persistence.
*/
public PasswordValidator load(String name) throws IOException {
PasswordPolicyConfig config = loadConfig(name);
if (config == null) {
//no such config
return null;
}
//look up the validator for this config
PasswordValidator validator = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getPasswordValidatorClass() == null) {
continue;
}
if (p.getPasswordValidatorClass().getName().equals(config.getClassName())) {
validator = p.createPasswordValidator(config, GeoServerSecurityManager.this);
break;
}
}
if (validator == null) {
throw new IOException("No password policy matching config: " + config);
}
validator.setConfig(config);
return validator;
}
@Override
protected File getRoot() throws IOException {
return getPasswordPolicyRoot();
}
}
class MasterPasswordProviderHelper extends
HelperBase<MasterPasswordProvider, MasterPasswordProviderConfig> {
@Override
public MasterPasswordProvider load(String name) throws IOException {
MasterPasswordProviderConfig config = loadConfig(name);
if (config == null) {
return null;
}
//look up the provider for this config
MasterPasswordProvider provider = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getMasterPasswordProviderClass() == null) {
continue;
}
if (p.getMasterPasswordProviderClass().getName().equals(config.getClassName())) {
provider = p.createMasterPasswordProvider(config);
break;
}
}
if (provider == null) {
throw new IOException("No master password provider matching config: " + config);
}
//ensure that the provider is a final class
if (!Modifier.isFinal(provider.getClass().getModifiers())) {
throw new RuntimeException("Master password provider class: " +
provider.getClass().getCanonicalName() + " is not final");
}
provider.setName(config.getName());
provider.setSecurityManager(GeoServerSecurityManager.this);
provider.initializeFromConfig(config);
return provider;
}
@Override
protected File getRoot() throws IOException {
return getMasterPasswordProviderRoot();
}
}
/**
*
* @return the active {@link GeoServerRoleService}
*/
public GeoServerRoleService getActiveRoleService() {
try {
return wrapRoleService(activeRoleService);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* set the active {@link GeoServerRoleService}
* @param activeRoleService
*/
public void setActiveRoleService(GeoServerRoleService activeRoleService) {
this.activeRoleService = activeRoleService;
}
/**
* rewrites configuration files with encrypted fields.
* Candidates:
* {@link StoreInfo} from the {@link Catalog}
* {@link SecurityNamedServiceConfig} objects from the security directory
* @param catalog
*/
public void updateConfigurationFilesWithEncryptedFields() throws IOException{
// rewrite stores in catalog
LOGGER.info("Start encrypting configuration passwords using " +
getSecurityConfig().getConfigPasswordEncrypterName());
Catalog catalog = getCatalog();
List<StoreInfo> stores = catalog.getStores(StoreInfo.class);
for (StoreInfo info : stores) {
if (!configPasswordEncryptionHelper.getEncryptedFields(info).isEmpty()) {
catalog.save(info);
}
}
Set<Class<?>> configClasses = new HashSet<Class<?>>();
// filter the interesting classes ones
for (GeoServerSecurityProvider prov: lookupSecurityProviders()) {
configClasses.addAll(prov.getFieldsForEncryption().keySet());
}
for (String name : listPasswordValidators()) {
PasswordPolicyConfig config = passwordValidatorHelper.loadConfig(name);
for (Class<?> classWithEncryption : configClasses) {
if (config.getClass().isAssignableFrom(classWithEncryption)) {
passwordValidatorHelper.saveConfig(config);
break;
}
}
}
for (String name : listRoleServices()) {
SecurityNamedServiceConfig config = roleServiceHelper.loadConfig(name);
for (Class<?> classWithEncryption : configClasses) {
if (config.getClass().isAssignableFrom(classWithEncryption)) {
roleServiceHelper.saveConfig(config);
break;
}
}
}
for (String name : listUserGroupServices()) {
SecurityNamedServiceConfig config = userGroupServiceHelper.loadConfig(name);
for (Class<?> classWithEncryption : configClasses) {
if (config.getClass().isAssignableFrom(classWithEncryption)) {
userGroupServiceHelper.saveConfig(config);
break;
}
}
}
for (String name : listAuthenticationProviders()) {
SecurityNamedServiceConfig config = authProviderHelper.loadConfig(name);
for (Class<?> classWithEncryption : configClasses) {
if (config.getClass().isAssignableFrom(classWithEncryption)) {
authProviderHelper.saveConfig(config);
break;
}
}
}
for (String name : listFilters()) {
SecurityNamedServiceConfig config = filterHelper.loadConfig(name);
for (Class<?> classWithEncryption : configClasses) {
if (config.getClass().isAssignableFrom(classWithEncryption)) {
filterHelper.saveConfig(config);
break;
}
}
}
LOGGER.info("End encrypting configuration passwords");
}
/**
* Interface that can be used to assist migration phases, adding XStream behaviours to be used
* only during migration of configurations from previous versions.
* A specific implementation can be passed to {@link FilterHelper.loadConfig} and/or
* {@link FilterHelper.saveConfig} to change XStream mappings and conversions to allow loading
* of old (incompatible) configuration files that need to be updated to a new format.
* The implementation should implement the migrationPersister method to add
* aliases, converters or other XStream behaviours needed only when migrating old
* configurations.
*
* @author Mauro Bartolomeoli (mauro.bartolomeoli@geo-solutions.it)
*
*/
interface MigrationHelper {
/**
* Implement here XStream mappings and conversion behaviours needed to read incompatible
* configurations during migration.
*
* @param xp
*/
public void migrationPersister(XStreamPersister xp);
}
class AuthProviderHelper extends HelperBase<GeoServerAuthenticationProvider, SecurityAuthProviderConfig>{
/**
* Loads the auth provider for the named config from persistence.
*/
public GeoServerAuthenticationProvider load(String name) throws IOException {
SecurityNamedServiceConfig config = loadConfig(name);
if (config == null) {
//no such config
return null;
}
//look up the service for this config
GeoServerAuthenticationProvider authProvider = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getAuthenticationProviderClass() == null) {
continue;
}
if (p.getAuthenticationProviderClass().getName().equals(config.getClassName())) {
authProvider = p.createAuthenticationProvider(config);
break;
}
}
if (authProvider == null) {
throw new IOException("No authentication provider matching config: " + config);
}
authProvider.setName(name);
authProvider.setSecurityManager(GeoServerSecurityManager.this);
authProvider.initializeFromConfig(config);
return authProvider;
}
@Override
protected File getRoot() throws IOException {
return getAuthRoot();
}
}
class FilterHelper extends HelperBase<GeoServerSecurityFilter, SecurityFilterConfig>{
/**
* Loads the filter for the named config from persistence.
*/
public GeoServerSecurityFilter load(String name) throws IOException {
SecurityNamedServiceConfig config = loadConfig(name);
if (config == null) {
//no such config
return null;
}
//look up the service for this config
GeoServerSecurityFilter filter = null;
for (GeoServerSecurityProvider p : lookupSecurityProviders()) {
if (p.getFilterClass() == null) {
continue;
}
if (p.getFilterClass().getName().equals(config.getClassName())) {
filter = p.createFilter(config);
break;
}
}
if (filter == null) {
throw new IOException("No authentication provider matching config: " + config);
}
filter.setName(name);
filter.setSecurityManager(GeoServerSecurityManager.this);
filter.initializeFromConfig(config);
return filter;
}
@Override
protected File getRoot() throws IOException {
return getFilterRoot();
}
}
/**
* custom converter for filter chain
*/
class FilterChainConverter extends AbstractCollectionConverter {
public FilterChainConverter(Mapper mapper) {
super(mapper);
}
@Override
public boolean canConvert(Class type) {
return GeoServerSecurityFilterChain.class.isAssignableFrom(type);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
GeoServerSecurityFilterChain filterChain = (GeoServerSecurityFilterChain) source;
for (RequestFilterChain requestChain : filterChain.getRequestChains()) {
//<filterChain>
// <filters path="..." index="...">
// <filter>name1</filter>
// <filter>name2</filter>
// ...
writer.startNode("filters");
StringBuilder sb = new StringBuilder();
for (String s : requestChain.getPatterns()) {
sb.append(s).append(",");
}
if (sb.length() > 0) {
sb.setLength(sb.length()-1);
}
if (requestChain.getName() != null) {
writer.addAttribute("name", requestChain.getName());
}
writer.addAttribute("class",requestChain.getClass().getName());
if (StringUtils.hasLength( requestChain.getRoleFilterName()))
writer.addAttribute("roleFilterName",requestChain.getRoleFilterName());
if (requestChain instanceof VariableFilterChain ) {
if (StringUtils.hasLength( ((VariableFilterChain)requestChain).getInterceptorName()))
writer.addAttribute("interceptorName",((VariableFilterChain)requestChain).getInterceptorName());
if (StringUtils.hasLength( ((VariableFilterChain)requestChain).getExceptionTranslationName()))
writer.addAttribute("exceptionTranslationName",((VariableFilterChain)requestChain).getExceptionTranslationName());
}
writer.addAttribute("path", sb.toString());
writer.addAttribute("disabled", Boolean.toString(requestChain.isDisabled()));
writer.addAttribute("allowSessionCreation", Boolean.toString(requestChain.isAllowSessionCreation()));
writer.addAttribute("ssl", Boolean.toString(requestChain.isRequireSSL()));
writer.addAttribute("matchHTTPMethod", Boolean.toString(requestChain.isMatchHTTPMethod()));
if (requestChain.getHttpMethods()!=null && requestChain.getHttpMethods().size()>0) {
writer.addAttribute("httpMethods",
StringUtils.collectionToCommaDelimitedString(requestChain.getHttpMethods()));
}
for (String filterName : requestChain.getFilterNames()) {
writer.startNode("filter");
writer.setValue(filterName);
writer.endNode();
}
writer.endNode();
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
GeoServerSecurityFilterChain filterChain = new GeoServerSecurityFilterChain();
while(reader.hasMoreChildren()) {
//<filters name="..." path="..."
reader.moveDown();
String path = reader.getAttribute("path");
String name = reader.getAttribute("name");
String classname = reader.getAttribute("class");
String roleFilterName= reader.getAttribute("roleFilterName");
String disabledString = reader.getAttribute("disabled");
String allowSessionCreationString = reader.getAttribute("allowSessionCreation");
String interceptorName = reader.getAttribute("interceptorName");
String exceptionTranslationName = reader.getAttribute("exceptionTranslationName");
String sslString=reader.getAttribute("ssl");
String matchHTTPMethodString=reader.getAttribute("matchHTTPMethod");
String httpMethodsString=reader.getAttribute("httpMethods");
if (name == null) {
//first version of the serialization did not contain name attribute, if not
// available try to look up well known chain, if not found just use the path
// as the name
RequestFilterChain requestChain = GeoServerSecurityFilterChain
.lookupRequestChainByPattern(path, GeoServerSecurityManager.this);
if (requestChain != null) {
name = requestChain.getName();
}
else {
name = path;
}
}
// this is nasty but no other chance to migrate from GeoServer 2.2.0
if (classname==null) {
if (GeoServerSecurityFilterChain.WEB_CHAIN_NAME.equals(name)) {
classname =HtmlLoginFilterChain.class.getName();
allowSessionCreationString="true";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
}
if (GeoServerSecurityFilterChain.WEB_LOGIN_CHAIN_NAME.equals(name)) {
classname = ConstantFilterChain.class.getName();
allowSessionCreationString="true";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
}
if (GeoServerSecurityFilterChain.WEB_LOGOUT_CHAIN_NAME.equals(name)) {
classname = LogoutFilterChain.class.getName();
allowSessionCreationString="true";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
}
if (GeoServerSecurityFilterChain.REST_CHAIN_NAME.equals(name)) {
classname = ServiceLoginFilterChain.class.getName();
allowSessionCreationString="false";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_REST_INTERCEPTOR;
}
if (GeoServerSecurityFilterChain.GWC_CHAIN_NAME.equals(name)) {
classname = ServiceLoginFilterChain.class.getName();
allowSessionCreationString="false";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_REST_INTERCEPTOR;
}
if (GeoServerSecurityFilterChain.DEFAULT_CHAIN_NAME.equals(name)) {
classname = ServiceLoginFilterChain.class.getName();
allowSessionCreationString="false";
interceptorName=GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
}
}
//<filter
ArrayList<String> filterNames = new ArrayList<String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
filterNames.add(reader.getValue());
reader.moveUp();
}
RequestFilterChain requestChain=null;
try {
Class<?> chainClass =Class.forName(classname);
Constructor<?> cons = chainClass.getConstructor(new Class[] {
String[].class });
String[] args= path.split(",");
requestChain = (RequestFilterChain)cons.newInstance(new Object[] {args});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
requestChain.setName(name);
if (StringUtils.hasLength(disabledString)) {
requestChain.setDisabled(Boolean.parseBoolean(disabledString));
}
if (StringUtils.hasLength(allowSessionCreationString)) {
requestChain.setAllowSessionCreation(Boolean.parseBoolean(allowSessionCreationString));
}
if (StringUtils.hasLength(sslString)) {
requestChain.setRequireSSL(Boolean.parseBoolean(sslString));
}
if (StringUtils.hasLength(matchHTTPMethodString)) {
requestChain.setMatchHTTPMethod(Boolean.parseBoolean(matchHTTPMethodString));
}
if (StringUtils.hasLength(httpMethodsString)) {
for (String method : httpMethodsString.split(",")) {
requestChain.getHttpMethods().add(HTTPMethod.fromString(method));
}
}
requestChain.setRoleFilterName(roleFilterName);
if (requestChain instanceof VariableFilterChain) {
((VariableFilterChain)requestChain).setInterceptorName(interceptorName);
if (StringUtils.hasLength(exceptionTranslationName))
((VariableFilterChain)requestChain).setExceptionTranslationName(exceptionTranslationName);
else
((VariableFilterChain)requestChain).
setExceptionTranslationName(GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER);
}
requestChain.setFilterNames(filterNames);
filterChain.getRequestChains().add(requestChain);
reader.moveUp();
}
// no good idea, how to split them from the gui ???
//filterChain.simplify();
return filterChain;
}
}
/**
* Calculates the union of roles from all role services and
* adds {@link GeoServerRole#ANONYMOUS_ROLE} and {@link GeoServerRole#AUTHENTICATED_ROLE}
*
* @throws IOException
*/
public SortedSet<GeoServerRole> getRolesForAccessControl() throws IOException {
SortedSet<GeoServerRole> allRoles = new TreeSet<GeoServerRole>();
for (String serviceName : listRoleServices()) {
// catch the IOException for each role service.
// As an example, it does not make sense to throw an IOException if
// a jdbc connection cannot be established.
try {
allRoles.addAll(loadRoleService(serviceName).getRoles());
} catch (IOException ex) {
LOGGER.log(Level.WARNING,ex.getMessage(),ex);
}
}
allRoles.add(GeoServerRole.AUTHENTICATED_ROLE);
allRoles.add(GeoServerRole.ANONYMOUS_ROLE);
return allRoles;
}
}