ApacheDS: tests de integración contra un servidor LDAP embebido.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Configuración.
- 4. Test de integración.
- 5. Implementación.
- 6. Referencias.
- 7. Conclusiones.
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