diff --git a/core/src/main/java/org/springframework/security/config/AbstractUserDetailsServiceBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/AbstractUserDetailsServiceBeanDefinitionParser.java index 7bf5bc7969..70ecb4dfe2 100644 --- a/core/src/main/java/org/springframework/security/config/AbstractUserDetailsServiceBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/AbstractUserDetailsServiceBeanDefinitionParser.java @@ -21,6 +21,12 @@ public class AbstractUserDetailsServiceBeanDefinitionParser extends AbstractSing return id; } + // If it's nested in a parent auth-provider, generate an id automatically + if(Elements.AUTHENTICATION_PROVIDER.equals(element.getParentNode().getNodeName())) { + return parserContext.getReaderContext().generateBeanName(definition); + } + + // If top level, use the default name or throw an exception if already used if (parserContext.getRegistry().containsBeanDefinition(BeanIds.USER_DETAILS_SERVICE)) { throw new SecurityConfigurationException("No id supplied in <" + element.getNodeName() + "> and another " + "bean is already registered as " + BeanIds.USER_DETAILS_SERVICE); diff --git a/core/src/main/java/org/springframework/security/config/ApacheDSContainer.java b/core/src/main/java/org/springframework/security/config/ApacheDSContainer.java index 8181762124..584f09895d 100644 --- a/core/src/main/java/org/springframework/security/config/ApacheDSContainer.java +++ b/core/src/main/java/org/springframework/security/config/ApacheDSContainer.java @@ -28,7 +28,7 @@ import java.io.IOException; /** * Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration. - * Used by {@link LdapBeanDefinitionParser}. An instance will be stored in the application context for + * Used by {@link LdapServerBeanDefinitionParser}. An instance will be stored in the application context for * each embedded server instance. It will start the server when the context is initialized and shut it down when * it is closed. It is intended for temporary embedded use and will not retain changes across start/stop boundaries. The * working directory is deleted on shutdown. @@ -40,8 +40,6 @@ import java.io.IOException; * prior to attempting to start it again. *
* - * - * * @author Luke Taylor * @version $Id$ */ @@ -54,10 +52,12 @@ class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, private ContextSource contextSource; private boolean running; + private String ldifResources; - public ApacheDSContainer(MutableServerStartupConfiguration configuration, ContextSource contextSource) { - this.configuration = configuration; + public ApacheDSContainer(MutableServerStartupConfiguration config, ContextSource contextSource, String ldifs) { + this.configuration = config; this.contextSource = contextSource; + this.ldifResources = ldifs; } public void afterPropertiesSet() throws Exception { @@ -98,7 +98,7 @@ class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, public void setWorkingDirectory(File workingDir) { Assert.notNull(workingDir); - logger.info("Setting working directory for LDAP: " + workingDir.getAbsolutePath()); + logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath()); if (workingDir.exists()) { throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath() + @@ -151,7 +151,7 @@ class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, private void importLdifs() throws IOException, NamingException { // Import any ldif files - Resource[] ldifs = ctxt.getResources("classpath*:*.ldif"); + Resource[] ldifs = ctxt.getResources(ldifResources); // Note that we can't just import using the ServerContext returned // from starting Apace DS, apparently because of the long-running issue DIRSERVER-169. diff --git a/core/src/main/java/org/springframework/security/config/AuthenticationProviderBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/AuthenticationProviderBeanDefinitionParser.java index 52130f2bf9..6470cfa304 100644 --- a/core/src/main/java/org/springframework/security/config/AuthenticationProviderBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/AuthenticationProviderBeanDefinitionParser.java @@ -19,8 +19,7 @@ import org.w3c.dom.Element; * @version $Id$ */ class AuthenticationProviderBeanDefinitionParser implements BeanDefinitionParser { - private static String ATT_REF = "ref"; - static final String ATT_DATA_SOURCE = "data-source"; + private static String ATT_USER_DETAILS_REF = "user-service-ref"; public BeanDefinition parse(Element element, ParserContext parserContext) { RootBeanDefinition authProvider = new RootBeanDefinition(DaoAuthenticationProvider.class); @@ -28,12 +27,17 @@ class AuthenticationProviderBeanDefinitionParser implements BeanDefinitionParser Element passwordEncoderElt = DomUtils.getChildElementByTagName(element, Elements.PASSWORD_ENCODER); if (passwordEncoderElt != null) { - //TODO: Parse password encoder object and add to dao provider + PasswordEncoderParser pep = new PasswordEncoderParser(passwordEncoderElt, parserContext); + authProvider.getPropertyValues().addPropertyValue("passwordEncoder", pep.getPasswordEncoder()); + + if (pep.getSaltSource() != null) { + authProvider.getPropertyValues().addPropertyValue("saltSource", pep.getSaltSource()); + } } ConfigUtils.getRegisteredProviders(parserContext).add(authProvider); - String ref = element.getAttribute(ATT_REF); + String ref = element.getAttribute(ATT_USER_DETAILS_REF); Element userServiceElt = DomUtils.getChildElementByTagName(element, Elements.USER_SERVICE); Element jdbcUserServiceElt = DomUtils.getChildElementByTagName(element, Elements.JDBC_USER_SERVICE); @@ -57,7 +61,7 @@ class AuthenticationProviderBeanDefinitionParser implements BeanDefinitionParser userDetailsService = new UserServiceBeanDefinitionParser().parse(userServiceElt, parserContext); } else { throw new SecurityConfigurationException(Elements.AUTHENTICATION_PROVIDER - + " requireds a UserDetailsService" ); + + " requires a UserDetailsService" ); } authProvider.getPropertyValues().addPropertyValue("userDetailsService", userDetailsService); diff --git a/core/src/main/java/org/springframework/security/config/BeanIds.java b/core/src/main/java/org/springframework/security/config/BeanIds.java index 18a0553845..73a20dc260 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -10,8 +10,9 @@ public abstract class BeanIds { /** Package protected as end users shouldn't really be using this BFPP directly */ static final String INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR = "_interceptMethodsBeanfactoryPP"; + static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = "_contextSettingPostProcessor"; - public static final String JDBC_USER_DETAILS_MANAGER = "_jdbcUserDetailsManager"; + public static final String JDBC_USER_DETAILS_MANAGER = "_jdbcUserDetailsManager"; public static final String USER_DETAILS_SERVICE = "_userDetailsService"; public static final String ANONYMOUS_PROCESSING_FILTER = "_anonymousProcessingFilter"; public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "_anonymousAuthenticationProvider"; @@ -39,5 +40,6 @@ public abstract class BeanIds { public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor"; public static final String SECURITY_ANNOTATION_ATTRIBUTES = "_securityAnnotationAttributes"; public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes"; - + public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer"; + public static final String CONTEXT_SOURCE = "_securityContextSource"; } diff --git a/core/src/main/java/org/springframework/security/config/Elements.java b/core/src/main/java/org/springframework/security/config/Elements.java index e7f8607164..cad1483fbe 100644 --- a/core/src/main/java/org/springframework/security/config/Elements.java +++ b/core/src/main/java/org/springframework/security/config/Elements.java @@ -14,8 +14,9 @@ abstract class Elements { public static final String INTERCEPT_METHODS = "intercept-methods"; public static final String AUTHENTICATION_PROVIDER = "authentication-provider"; public static final String HTTP = "http"; - public static final String LDAP = "ldap"; - public static final String PROTECT = "protect"; + public static final String LDAP_PROVIDER = "ldap-authentication-provider"; + public static final String LDAP_SERVER = "ldap-server"; + public static final String PROTECT = "protect"; public static final String CONCURRENT_SESSIONS = "concurrent-session-control"; public static final String LOGOUT = "logout"; public static final String FORM_LOGIN = "form-login"; diff --git a/core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java new file mode 100644 index 0000000000..129586d5a6 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java @@ -0,0 +1,103 @@ +package org.springframework.security.config; + +import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator; +import org.springframework.security.ldap.SpringSecurityContextSource; +import org.springframework.security.providers.ldap.LdapAuthenticationProvider; +import org.springframework.security.providers.ldap.authenticator.BindAuthenticator; +import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; +import org.springframework.security.ui.rememberme.RememberMeServices; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.beans.BeansException; +import org.springframework.core.Ordered; +import org.springframework.ldap.core.ContextSource; +import org.springframework.util.StringUtils; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + +import java.util.Map; + +/** + * Experimental "security:ldap" namespace configuration. + * + * + * @author Luke Taylor + * @version $Id$ + * @since 2.0 + */ +public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser { + private Log logger = LogFactory.getLog(getClass()); + + private static final String ATT_AUTH_TYPE = "auth-type"; + private static final String ATT_SERVER = "server-ref"; + + private static final String OPT_DEFAULT_DN_PATTERN = "uid={0},ou=people"; + private static final String DEFAULT_GROUP_CONTEXT = "ou=groups"; + + + public BeanDefinition parse(Element elt, ParserContext parserContext) { + String server = elt.getAttribute(ATT_SERVER); + + if (!StringUtils.hasText(server)) { + server = BeanIds.CONTEXT_SOURCE; + } + + RuntimeBeanReference contextSource = new RuntimeBeanReference(server); + + RootBeanDefinition bindAuthenticator = new RootBeanDefinition(BindAuthenticator.class); + bindAuthenticator.getConstructorArgumentValues().addGenericArgumentValue(contextSource); + bindAuthenticator.getPropertyValues().addPropertyValue("userDnPatterns", new String[] {OPT_DEFAULT_DN_PATTERN}); + RootBeanDefinition authoritiesPopulator = new RootBeanDefinition(DefaultLdapAuthoritiesPopulator.class); + authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(contextSource); + authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(DEFAULT_GROUP_CONTEXT); + + RootBeanDefinition ldapProvider = new RootBeanDefinition(LdapAuthenticationProvider.class); + ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(bindAuthenticator); + ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(authoritiesPopulator); + + registerPostProcessorIfNecessary(parserContext.getRegistry()); + + ConfigUtils.getRegisteredProviders(parserContext).add(ldapProvider); + + return null; + } + + // Todo: Move to utility class when we add ldap-user-service, as this check will be needed even if no + // provider is added. + private static class ContextSourceSettingPostProcessor implements BeanFactoryPostProcessor, Ordered { + + public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException { + Map beans = bf.getBeansOfType(SpringSecurityContextSource.class); + + if (beans.size() == 0) { + throw new SecurityConfigurationException("No SpringSecurityContextSource instances found. Have you " + + "added an <" + Elements.LDAP_SERVER + " /> element to your application context?"); + } else if (beans.size() > 1) { + throw new SecurityConfigurationException("More than one SpringSecurityContextSource instance found. " + + "Please specify a specific server id when configuring your <" + Elements.LDAP_PROVIDER + ">"); + } + } + + public int getOrder() { + return LOWEST_PRECEDENCE; + } + + } + + public void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry) { + if (registry.containsBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR)) { + return; + } + + registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR, + new RootBeanDefinition(LdapProviderBeanDefinitionParser.ContextSourceSettingPostProcessor.class)); + } +} diff --git a/core/src/main/java/org/springframework/security/config/LdapServerBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/LdapServerBeanDefinitionParser.java new file mode 100644 index 0000000000..6e7b86366f --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/LdapServerBeanDefinitionParser.java @@ -0,0 +1,166 @@ +package org.springframework.security.config; + +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.util.StringUtils; +import org.springframework.util.Assert; + +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; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { + 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 ATT_URL = "url"; + + private static final String ATT_PRINCIPAL = "manager-dn"; + private static final String ATT_PASSWORD = "manager-password"; + + // 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 ATT_ROOT_SUFFIX = "root"; + private static final String OPT_DEFAULT_ROOT_SUFFIX = "dc=springframework,dc=org"; + /** + * 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 ATT_LDIF_FILE = "ldif"; + private static final String OPT_DEFAULT_LDIF_FILE = "classpath*:*.ldif"; + + /** Defines the port the LDAP_PROVIDER server should run on */ + public static final String ATT_PORT = "port"; + public static final String OPT_DEFAULT_PORT = "33389"; + + + public BeanDefinition parse(Element elt, ParserContext parserContext) { + String url = elt.getAttribute(ATT_URL); + + RootBeanDefinition contextSource; + + if (!StringUtils.hasText(url)) { + contextSource = createEmbeddedServer(elt, parserContext); + } else { + contextSource = new RootBeanDefinition(DefaultSpringSecurityContextSource.class); + contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, url); + } + + String managerDn = elt.getAttribute(ATT_PRINCIPAL); + String managerPassword = elt.getAttribute(ATT_PASSWORD); + + if (StringUtils.hasText(managerDn)) { + Assert.hasText(managerPassword, "You must specify the " + ATT_PASSWORD + + " if you supply a " + managerDn); + + contextSource.getPropertyValues().addPropertyValue("userDn", managerDn); + contextSource.getPropertyValues().addPropertyValue("password", managerPassword); + } + + String id = elt.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE); + + String contextSourceId = StringUtils.hasText(id) ? id : BeanIds.CONTEXT_SOURCE; + + parserContext.getRegistry().registerBeanDefinition(contextSourceId, contextSource); + + return null; + } + + /** + * 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. + * + * @see ApacheDSContainer + */ + 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(ATT_ROOT_SUFFIX); + + if (!StringUtils.hasText(suffix)) { + suffix = OPT_DEFAULT_ROOT_SUFFIX; + } + + try { + partition.setSuffix(suffix); + } catch (NamingException e) { + parserContext.getReaderContext().error("Failed to set root name suffix to " + suffix, element, e); + } + + HashSet partitions = new HashSet(1); + partitions.add(partition); + + String port = element.getAttribute(ATT_PORT); + + if (!StringUtils.hasText(port)) { + port = OPT_DEFAULT_PORT; + } + + configuration.setLdapPort(Integer.parseInt(port)); + + // We shut down the server ourself when the app context is closed so we don't need + // the extra shutdown hook from apache DS itself. + configuration.setShutdownHookEnabled(false); + configuration.setExitVmOnShutdown(false); + configuration.setContextPartitionConfigurations(partitions); + + String url = "ldap://127.0.0.1:" + port + "/" + suffix; + + RootBeanDefinition contextSource = new RootBeanDefinition(DefaultSpringSecurityContextSource.class); + contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, url); + contextSource.getPropertyValues().addPropertyValue("userDn", "uid=admin,ou=system"); + contextSource.getPropertyValues().addPropertyValue("password", "secret"); + + RootBeanDefinition apacheContainer = new RootBeanDefinition(ApacheDSContainer.class); + apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(configuration); + apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(contextSource); + + String ldifs = element.getAttribute(ATT_LDIF_FILE); + if (!StringUtils.hasText(ldifs)) { + ldifs = OPT_DEFAULT_LDIF_FILE; + } + + apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs); + + logger.info("Embedded LDAP server bean created for URL: " + url); + + if (parserContext.getRegistry().containsBeanDefinition(BeanIds.EMBEDDED_APACHE_DS)) { + parserContext.getReaderContext().error("Only one embedded server bean is allowed per application context", + element); + } + + parserContext.getRegistry().registerBeanDefinition(BeanIds.EMBEDDED_APACHE_DS, apacheContainer); + + return contextSource; + } +} diff --git a/core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 2e11d16af7..ce3ff270aa 100644 --- a/core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -13,7 +13,8 @@ public class SecurityNamespaceHandler extends NamespaceHandlerSupport { public void init() { // Parsers - registerBeanDefinitionParser(Elements.LDAP, new LdapBeanDefinitionParser()); + registerBeanDefinitionParser(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser()); + registerBeanDefinitionParser(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser()); registerBeanDefinitionParser(Elements.HTTP, new HttpSecurityBeanDefinitionParser()); registerBeanDefinitionParser(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser()); registerBeanDefinitionParser(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser()); diff --git a/core/src/main/java/org/springframework/security/util/InMemoryResource.java b/core/src/main/java/org/springframework/security/util/InMemoryResource.java index dab9bda1c9..eb27be8f66 100644 --- a/core/src/main/java/org/springframework/security/util/InMemoryResource.java +++ b/core/src/main/java/org/springframework/security/util/InMemoryResource.java @@ -32,27 +32,31 @@ import java.io.InputStream; public class InMemoryResource extends AbstractResource { //~ Instance fields ================================================================================================ - private ByteArrayInputStream in; + private byte[] source; private String description; //~ Constructors =================================================================================================== + public InMemoryResource(String source) { + this(source.getBytes()); + } + public InMemoryResource(byte[] source) { this(source, null); } public InMemoryResource(byte[] source, String description) { - in = new ByteArrayInputStream(source); + this.source = source; this.description = description; } //~ Methods ======================================================================================================== public String getDescription() { - return (description == null) ? in.toString() : description; + return description; } public InputStream getInputStream() throws IOException { - return in; + return new ByteArrayInputStream(source); } } diff --git a/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java b/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java new file mode 100644 index 0000000000..b0bbfa229c --- /dev/null +++ b/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java @@ -0,0 +1,38 @@ +package org.springframework.security.util; + +import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.core.io.Resource; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext { + private static final String BEANS_OPENING = + "