SEC-271: Added namespace handler class and experimental LDAP parser. The latter creates an embedded Apache DS server if no server URL is supplied, so changed dependencies on the latter to compile-time/optional.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
|
||||
import org.apache.directory.server.jndi.ServerContextFactory;
|
||||
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
|
||||
import org.apache.directory.server.core.configuration.ShutdownConfiguration;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.util.Properties;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Starts and stops the embedded apacheDS server defined by the supplied configuration.
|
||||
* Used by {@link LdapBeanDefinitionParser}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
class ApacheDSStartStopBean implements InitializingBean, DisposableBean, ApplicationContextAware {
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private MutableServerStartupConfiguration configuration;
|
||||
private ApplicationContext ctxt;
|
||||
private File workingDir;
|
||||
|
||||
public ApacheDSStartStopBean(MutableServerStartupConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Properties env = new Properties();
|
||||
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
|
||||
|
||||
if (apacheWorkDir == null) {
|
||||
apacheWorkDir = System.getProperty("java.io.tmpdir") + File.separator + "apacheds-spring-security";
|
||||
}
|
||||
|
||||
workingDir = new File(apacheWorkDir);
|
||||
|
||||
// if (workingDir.exists()) {
|
||||
// logger.info("Deleting existing working directory " + workingDir.getAbsolutePath());
|
||||
// deleteDir(workingDir);
|
||||
// }
|
||||
|
||||
configuration.setWorkingDirectory(workingDir);
|
||||
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
|
||||
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
|
||||
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
|
||||
env.putAll(configuration.toJndiEnvironment());
|
||||
|
||||
DirContext serverContext = new InitialDirContext(env);
|
||||
|
||||
// Import any ldif files
|
||||
Resource[] ldifs = ctxt.getResources("classpath:*.ldif");
|
||||
|
||||
|
||||
DirContext dirContext = ((ContextSource)ctxt.getBean("contextSource")).getReadWriteContext();
|
||||
|
||||
if(ldifs != null && ldifs.length > 0) {
|
||||
try {
|
||||
String ldifFile = ldifs[0].getFile().getAbsolutePath();
|
||||
LdifFileLoader loader = new LdifFileLoader(dirContext, ldifFile);
|
||||
loader.execute();
|
||||
} finally {
|
||||
dirContext.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void destroy() throws Exception {
|
||||
Properties env = new Properties();
|
||||
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
|
||||
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
|
||||
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
|
||||
|
||||
ShutdownConfiguration shutdown = new ShutdownConfiguration();
|
||||
env.putAll(shutdown.toJndiEnvironment());
|
||||
|
||||
logger.info("Shutting down server...");
|
||||
new InitialContext(env);
|
||||
|
||||
if (workingDir.exists()) {
|
||||
logger.info("Deleting working directory after shutting down " + workingDir.getAbsolutePath());
|
||||
deleteDir(workingDir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
ctxt = applicationContext;
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
String[] children = dir.list();
|
||||
for (int i=0; i < children.length; i++) {
|
||||
boolean success = deleteDir(new File(dir, children[i]));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir.delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.security.ldap.DefaultInitialDirContextFactory;
|
||||
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
|
||||
import org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.w3c.dom.Element;
|
||||
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
|
||||
import org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Experimental "security:ldap" namespace configuration.
|
||||
*
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class LdapBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/** Defines the Url of the ldap server to use. If not specified, an embedded apache DS instance will be created */
|
||||
private static final String URL_ATTRIBUTE = "url";
|
||||
private static final String AUTH_TYPE_ATTRIBUTE = "auth";
|
||||
// TODO: Setting login/passwords for non embedded server.
|
||||
private static final String PRINCIPAL_ATTRIBUTE = "managerDn";
|
||||
private static final String PASSWORD_ATTRIBUTE = "managerPassword";
|
||||
|
||||
// Properties which apply to embedded server only - when no Url is set
|
||||
|
||||
/** sets the configuration suffix (default is "dc=springframework,dc=org"). */
|
||||
public static final String ROOT_SUFFIX_ATTRIBUTE = "root";
|
||||
|
||||
/**
|
||||
* Optionally defines an ldif resource to be loaded. Otherwise an attempt will be made to load all ldif files
|
||||
* found on the classpath.
|
||||
*/
|
||||
public static final String LDIF_FILE_ATTRIBUTE = "ldif";
|
||||
|
||||
// Defaults
|
||||
private static final String DEFAULT_ROOT_SUFFIX = "dc=springframework,dc=org";
|
||||
|
||||
private static final String DEFAULT_PROVIDER_BEAN_ID = "_ldapProvider";
|
||||
|
||||
private static final String DEFAULT_DN_PATTERN = "uid={0},ou=people";
|
||||
|
||||
private static final String DEFAULT_GROUP_CONTEXT = "ou=groups";
|
||||
|
||||
|
||||
protected AbstractBeanDefinition parseInternal(Element elt, ParserContext parserContext) {
|
||||
String url = elt.getAttribute(URL_ATTRIBUTE);
|
||||
|
||||
RootBeanDefinition initialDirContextFactory;
|
||||
|
||||
if (!StringUtils.hasText(url)) {
|
||||
initialDirContextFactory = createEmbeddedServer(elt, parserContext);
|
||||
} else {
|
||||
initialDirContextFactory = new RootBeanDefinition(DefaultInitialDirContextFactory.class);
|
||||
initialDirContextFactory.getConstructorArgumentValues().addIndexedArgumentValue(0, url);
|
||||
}
|
||||
|
||||
// TODO: Make these default values for 2.0
|
||||
initialDirContextFactory.getPropertyValues().addPropertyValue("useLdapContext", Boolean.TRUE);
|
||||
initialDirContextFactory.getPropertyValues().addPropertyValue("dirObjectFactory", "org.springframework.ldap.core.support.DefaultDirObjectFactory");
|
||||
|
||||
String id = elt.getAttribute(ID_ATTRIBUTE);
|
||||
String contextSourceId = "contextSource";
|
||||
|
||||
if (StringUtils.hasText(id)) {
|
||||
contextSourceId = id + "." + contextSourceId;
|
||||
}
|
||||
|
||||
if (parserContext.getRegistry().containsBeanDefinition(contextSourceId)) {
|
||||
logger.warn("Bean already exists with Id '" + contextSourceId + "'");
|
||||
}
|
||||
|
||||
parserContext.getRegistry().registerBeanDefinition(contextSourceId, initialDirContextFactory);
|
||||
|
||||
RootBeanDefinition bindAuthenticator = new RootBeanDefinition(BindAuthenticator.class);
|
||||
bindAuthenticator.getConstructorArgumentValues().addGenericArgumentValue(initialDirContextFactory);
|
||||
bindAuthenticator.getPropertyValues().addPropertyValue("userDnPatterns", new String[] {DEFAULT_DN_PATTERN});
|
||||
RootBeanDefinition authoritiesPopulator = new RootBeanDefinition(DefaultLdapAuthoritiesPopulator.class);
|
||||
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(initialDirContextFactory);
|
||||
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(DEFAULT_GROUP_CONTEXT);
|
||||
|
||||
RootBeanDefinition ldapProvider = new RootBeanDefinition(LdapAuthenticationProvider.class);
|
||||
ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(bindAuthenticator);
|
||||
ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(authoritiesPopulator);
|
||||
|
||||
return ldapProvider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will be called if no url attribute is supplied.
|
||||
*
|
||||
* Registers beans to create an embedded apache directory server.
|
||||
*
|
||||
* @param element
|
||||
* @param parserContext
|
||||
*
|
||||
* @return the BeanDefinition for the ContextSource for the embedded server.
|
||||
*/
|
||||
private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) {
|
||||
MutableServerStartupConfiguration configuration = new MutableServerStartupConfiguration();
|
||||
MutableBTreePartitionConfiguration partition = new MutableBTreePartitionConfiguration();
|
||||
|
||||
partition.setName("springsecurity");
|
||||
|
||||
DirContextAdapter rootContext = new DirContextAdapter();
|
||||
rootContext.setAttributeValues("objectClass", new String[] {"top", "domain", "extensibleObject"});
|
||||
rootContext.setAttributeValue("dc", "springsecurity");
|
||||
|
||||
partition.setContextEntry(rootContext.getAttributes());
|
||||
|
||||
String suffix = element.getAttribute(ROOT_SUFFIX_ATTRIBUTE);
|
||||
|
||||
if (!StringUtils.hasText(suffix)) {
|
||||
suffix = DEFAULT_ROOT_SUFFIX;
|
||||
}
|
||||
|
||||
try {
|
||||
partition.setSuffix(suffix);
|
||||
} catch (NamingException e) {
|
||||
// TODO: What exception should we be throwing here ?
|
||||
|
||||
logger.error("Failed to set root name suffix to " + suffix, e);
|
||||
}
|
||||
|
||||
HashSet partitions = new HashSet(1);
|
||||
partitions.add(partition);
|
||||
|
||||
//TODO: Allow port configuration
|
||||
configuration.setLdapPort(3389);
|
||||
configuration.setContextPartitionConfigurations(partitions);
|
||||
|
||||
RootBeanDefinition initialDirContextFactory = new RootBeanDefinition(DefaultInitialDirContextFactory.class);
|
||||
initialDirContextFactory.getConstructorArgumentValues().addIndexedArgumentValue(0, "ldap://127.0.0.1:3389/" + suffix);
|
||||
|
||||
initialDirContextFactory.getPropertyValues().addPropertyValue("managerDn", "uid=admin,ou=system");
|
||||
initialDirContextFactory.getPropertyValues().addPropertyValue("managerPassword", "secret");
|
||||
|
||||
RootBeanDefinition apacheDSStartStop = new RootBeanDefinition(ApacheDSStartStopBean.class);
|
||||
apacheDSStartStop.getConstructorArgumentValues().addGenericArgumentValue(configuration);
|
||||
|
||||
if (parserContext.getRegistry().containsBeanDefinition("_apacheDSStartStopBean")) {
|
||||
//TODO: Appropriate exception
|
||||
throw new IllegalArgumentException("Only one embedded server bean is allowed per application context");
|
||||
}
|
||||
|
||||
parserContext.getRegistry().registerBeanDefinition("_apacheDSStartStopBean", apacheDSStartStop);
|
||||
|
||||
|
||||
return initialDirContextFactory;
|
||||
}
|
||||
|
||||
|
||||
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
|
||||
if (StringUtils.hasText(id)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// TODO: Check for duplicate using default id here.
|
||||
|
||||
return DEFAULT_PROVIDER_BEAN_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||
|
||||
|
||||
/**
|
||||
* Registers the bean definition parsers for the "security" namespace (http://www.springframework.org/schema/security).
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("ldap", new LdapBeanDefinitionParser());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<xsd:schema xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://www.springframework.org/schema/security"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||
|
||||
|
||||
<xsd:import namespace="http://www.springframework.org/schema/beans" />
|
||||
|
||||
<xsd:element name="autoconfig" />
|
||||
|
||||
|
||||
<xsd:element name="ldap">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="url" type="xsd:string" />
|
||||
<xsd:attribute name="ldif" default="classpath:*.ldif"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
</xsd:schema>
|
||||
Reference in New Issue
Block a user