/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.config.ldap; import java.io.IOException; import java.net.ServerSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.config.BeanIds; import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** * @author Luke Taylor */ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { private static final String CONTEXT_SOURCE_CLASS = "org.springframework.security.ldap.DefaultSpringSecurityContextSource"; private final 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"; private static final int DEFAULT_PORT = 33389; public static final String OPT_DEFAULT_PORT = String.valueOf(DEFAULT_PORT); 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(); contextSource.setBeanClassName(CONTEXT_SOURCE_CLASS); contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, url); } contextSource.setSource(parserContext.extractSource(elt)); String managerDn = elt.getAttribute(ATT_PRINCIPAL); String managerPassword = elt.getAttribute(ATT_PASSWORD); if (StringUtils.hasText(managerDn)) { if (!StringUtils.hasText(managerPassword)) { parserContext.getReaderContext().error( "You must specify the " + ATT_PASSWORD + " if you supply a " + managerDn, elt); } 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. * * @return the BeanDefinition for the ContextSource for the embedded server. * * @see ApacheDSContainer */ private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); String suffix = element.getAttribute(ATT_ROOT_SUFFIX); if (!StringUtils.hasText(suffix)) { suffix = OPT_DEFAULT_ROOT_SUFFIX; } String port = element.getAttribute(ATT_PORT); if (!StringUtils.hasText(port)) { port = getDefaultPort(); if (logger.isDebugEnabled()) { logger.debug("Using default port of " + port); } } String url = "ldap://127.0.0.1:" + port + "/" + suffix; BeanDefinitionBuilder contextSource = BeanDefinitionBuilder .rootBeanDefinition(CONTEXT_SOURCE_CLASS); contextSource.addConstructorArgValue(url); contextSource.addPropertyValue("userDn", "uid=admin,ou=system"); contextSource.addPropertyValue("password", "secret"); RootBeanDefinition apacheContainer = new RootBeanDefinition( "org.springframework.security.ldap.server.ApacheDSContainer", null, null); apacheContainer.setSource(source); apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(suffix); String ldifs = element.getAttribute(ATT_LDIF_FILE); if (!StringUtils.hasText(ldifs)) { ldifs = OPT_DEFAULT_LDIF_FILE; } apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs); apacheContainer.getPropertyValues().addPropertyValue("port", port); logger.info("Embedded LDAP server bean definition 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 (RootBeanDefinition) contextSource.getBeanDefinition(); } private String getDefaultPort() { ServerSocket serverSocket = null; try { try { serverSocket = new ServerSocket(DEFAULT_PORT); } catch (IOException e) { try { serverSocket = new ServerSocket(0); } catch (IOException e2) { return String.valueOf(DEFAULT_PORT); } } return String.valueOf(serverSocket.getLocalPort()); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { } } } } }