ApacheDS: tests de integración contra un servidor LDAP embebido.

0
8057

ApacheDS: tests de integración contra un servidor LDAP embebido.

0. Índice de contenidos.


1. Introducción

Vimos hace poco cómo disponer de un servidor LDAP embebido en nuestro entorno de tets de integración con el soporte de Spring Security,
dentro del contexto de inyección de dependencias de Spring.

En este tutorial vamos a ver cómo hacer lo mismo pero sin disponer del soporte de Spring y, como consecuencia, tampoco de Spring Security.

Será tan similar que el servidor LDAP que usaremos sera también Apache Directory Server.


2. Entorno.

El tutorial, y el código que contiene, han sido escritos usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.4 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.7.4


3. Configuración.

Como de costumbre, haciendo uso de maven, lo primero es declarar nuestras dependencias en el fichero pom.xml:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.autentia.tutorial.apacheDS</groupId>
  <artifactId>apacheDS-integration-tests</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
	<properties>
		<java.version>1.7</java.version>
		<apache.ds.version>2.0.0-M15</apache.ds.version>
	</properties>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.5.1</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	
	<dependencies>
		<!-- Apache DS dependencies -->
		<dependency>
			<groupId>org.apache.directory.server</groupId>
			<artifactId>apacheds-all</artifactId>
			<version>${apache.ds.version}</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.directory.shared</groupId>
					<artifactId>shared-ldap-schema</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.apache.directory.api</groupId>
					<artifactId>api-ldap-schema-data</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
			<groupId>org.apache.directory.server</groupId>
			<artifactId>apacheds-server-integ</artifactId>
			<version>${apache.ds.version}</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.directory.shared</groupId>
					<artifactId>shared-ldap-schema</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.apache.directory.api</groupId>
					<artifactId>api-ldap-schema-data</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.apache.directory.jdbm</groupId>
					<artifactId>apacheds-jdbm1</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.6.0</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.6.0</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
</project>

El servidor que usaremos será Apache Directory que se alimentará en su arranque de un fichero ldif (autentia-identity-repository.ldif), con la siguiente información sobre nuestra organización:

version: 1

dn: O=autentia 
changetype: add 
objectClass: extensibleObject
objectClass: organization
objectClass: top 
description: autentia

dn: ou=users,o=autentia
changetype: add
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: users

dn: ou=groups,o=autentia
changetype: add 
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: groups

dn: cn=administrativos,ou=groups,o=autentia
changetype: add 
objectClass: groupOfUniqueNames
objectClass: top
cn: administrativos
uniqueMember: cn=jmsanchez,ou=users,o=autentia
uniqueMember: cn=psanchez,ou=users,o=autentia

dn: cn=tramitadores,ou=groups,o=autentia
changetype: add 
objectClass: groupOfUniqueNames
objectClass: top
cn: tramitadores
uniqueMember: cn=ablanco,ou=users,o=autentia
uniqueMember: cn=msanchez,ou=users,o=autentia

dn: cn=admin,ou=groups,o=autentia
changetype: add 
objectClass: groupOfUniqueNames
objectClass: top
cn: admin
uniqueMember: cn=administrador,ou=users,o=autentia

dn: cn=jmsanchez,ou=users,o=autentia
changetype: add 
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Jose Manuel Sánchez
sn: jmsanchez
uid: jmsanchez
mail: jmsanchez@autentia.com
userPassword:: cGFzcw==

dn: cn=psanchez,ou=users,o=autentia
changetype: add 
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Pablo Sánchez
sn: psanchez
uid: psanchez
mail: psanchez@autentia.com
userPassword:: cGFzcw==

dn: cn=msanchez,ou=users,o=autentia
changetype: add 
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Mario Sánchez
sn: msanchez
uid: msanchez
mail: msanchez@autentia.com
userPassword:: cGFzcw==

dn: cn=ablanco,ou=users,o=autentia
changetype: add 
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Alfonso Blanco
sn: ablanco
uid: ablanco
mail: ablanco@autentia.com
userPassword:: cGFzcw==

dn: cn=administrador,ou=users,o=autentia
changetype: add 
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: admin
sn: admin
uid: administrador
userPassword:: cGFzcw==

Al igual que en el tutorial anterior, tenemos una pequeña jerarquía, con dos grupos de usuarios: administrativos y tramitadores (además del grupo de administradores ) y varios usuarios asociados a los mismos.


4. Test de integración.

Vamos a escribir, un test que comprueba el acceso y recuperación de un usuario:

package com.autentia.tutorial.apacheDS.accounts;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;

public class UserAccountRepositoryIntegrationTest {
	
	private UserAccountRepository userAccountDao;

	@Test 
	public void shouldFindUserAccounyByLogin(){
		final String login = "jmsanchez";
		final UserAccount userAccount = userAccountDao.getByLogin(login);
		assertNotNull(userAccount);
		assertEquals("Jose Manuel Sánchez", userAccount.getName());
	}
}

En este punto nuestro test estará en ROJO (errores de compilación), porque:

  • no disponemos de la clase UserAccountRepository, que nos proporcionará acceso al repositorio de usuarios o personas,
  • no tenemos la clase UserAccount, que tendrá las propiedades de un usuario, y
  • aún no sabemos cómo configurar el servidor ldap embebido 😉

Veamos la interfaz de servicio que hemos pensado para el repositorio de usuarios

package com.autentia.tutoriales.spring.security.ldap;

import java.util.List;

public interface UserAccountRepository {

	UserAccount getByLogin(String login);
	
}

Y ahora el contenido del POJO que tendrá las propiedades de un usuario:

package com.autentia.tutoriales.spring.security.ldap;

public class UserAccount {

	private String login;

	private String name;

	private String email;

	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

Ahora no tendremos errores de compilación, pero fallará la ejecución del test.


5. Implementación.

Veamos el contenido que proponemos para el repositorio de usuarios:

package com.autentia.tutorial.apacheDS.accounts;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;

public class UserAccountLDAPRepository implements UserAccountRepository{
	
	LdapContext ldapContext;
	
	public UserAccountLDAPRepository(LdapContext LdapContext) {
		this.ldapContext = LdapContext;
	}
	
	public UserAccount getByLogin(String login) {
		try {
			NamingEnumeration<SearchResult> results = searchDirectory(login);
			if (results != null){
				while (results.hasMoreElements()) {
					SearchResult searchResult = (SearchResult) results
							.nextElement();
					final UserAccount userAccount = new UserAccount();
					userAccount.setLogin((String) searchResult.getAttributes().get("uid").get());
					userAccount.setName((String) searchResult.getAttributes().get("cn").get());
					userAccount.setEmail((String) searchResult.getAttributes().get("mail").get());
					return userAccount;
				}
			}
		} catch (NamingException e) {
			throw new IllegalStateException("login not found",e);
		}
		return null;
	}

	public NamingEnumeration<SearchResult> searchDirectory(
			final String searchString) throws NamingException {
		final SearchControls searchControls = new SearchControls();
		searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		return ldapContext.search("", "(uid=" + searchString + ")", searchControls);
	}
	
}

Recibe el contexto de ldap como dependencia en el constructor, realiza la búsqueda y mapea el resultado contra el objeto de respuesta, todo ello sin usar el soporte de plantillas de Spring.

Ahora vamos a añadir el soporte del servidor LDAP embebido a nuestro test de integración.

package com.autentia.tutorial.apacheDS.accounts;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(FrameworkRunner.class)
@CreateDS(allowAnonAccess = true, name = "Autentia", partitions = { @CreatePartition(name = "Autentia", suffix = "o=autentia") })
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP", port=9445) })
public class UserAccountRepositoryIntegrationTest extends AbstractLdapTestUnit {
	
	private UserAccountRepository userAccountDao;

	@Before
	public void setUp() throws NamingException{
		final LdapContext ldapContext = createLdapContext(ldapServer.getTransports()[0].getAddress(), ldapServer.getTransports()[0].getPort());
		userAccountDao = new UserAccountLDAPRepository(ldapContext); 
	}
	
	@ApplyLdifFiles("autentia-identity-repository.ldif")
	@Test 
	public void shouldFindUserAccounyByLogin(){
		final String login = "jmsanchez";
		final UserAccount userAccount = userAccountDao.getByLogin(login);
		assertNotNull(userAccount);
		assertEquals("Jose Manuel Sánchez", userAccount.getName());
	}
	
	private LdapContext createLdapContext(String host, int port) throws NamingException {
		final Hashtable<String, String> environment = new Hashtable<String, String>();
		environment.put(Context.INITIAL_CONTEXT_FACTORY,
				"com.sun.jndi.ldap.LdapCtxFactory");
		environment.put(Context.PROVIDER_URL, "ldap://" + host + ":" + port
				+ "/");

		LdapContext ldapCtx = new InitialLdapContext(environment,
				new Control[0]);
		return ldapCtx;
	}
}

Hemos hecho las siguientes modificaciones:

  • nuestra clase entiende AbstractLdapTestUnit que nos va a permitir acceder al estado del servidor embebido en el entorno del test,
  • FrameworkRunner.class nos permite ejecutar el test con el soporte de apacheDS,
  • la anotación @CreateDS, establece las características estructurales de creación del servidor,
  • la anotación @CreateLDAPServer establece las caracteristicas de conectividad con el servidor,
  • con la anotación @ApplyLdifFiles podemos establecer los ficheros de carga con la estructura y datos del nuestro server para el entorno de tests.

Sin más ni menos ya disponemos del soporte para comprobar nuestra lógica de acceso al directorio LDAP.


6. Referencias.


7. Conclusiones.

Este tutorial responde al… pero,… ¿y si no tengo el soporte de Spring? 😉

Un saludo.

Jose

jmsanchez@autentia.com

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad