Acabo de publicar la versión 1.3 de Jasypt (Java Simplified Encryption), que trae las siguientes mejoras:
Un nuevo API abierto de providers JCE, gracias al cual ahora Jasypt puede usar cualquier proveedor JCE del mercado (incluyendo Bouncy Castle), por tanto aumentando muchísimo el abanico de algoritmos de cifrado disponibles.
Un nuevo sistema de configuración avanzada para aplicaciones web, que permite introducir las passwords de cifrado (las de los cifradores PBE simétricos) desde un formulario web en el momento de despliegue de la aplicación, de modo que no tengan que almacenarse en ficheros.
Posibilidad de producir resultados codificados en hexadecimal, aparte de BASE64
Aparte de alguna otra mejorilla (que no bug ;-)) como realizar una normalización de texto unicode a la hora de hacer digests de passwords de manera que se eviten problemas debidos a que distintas combinaciones de caracteres unicode generen el mismo caracter visual... aunque bueno, esto puede afectar más a las lenguas asiáticas.
Como imaginaba ayer acabe bastante tarde asi que como no eran plan ponerse a la 1 a escribir la cronica, he juntado las de dos dias puesto que hoy acaba pronto.
Ayer empezo el dia con la sesion general de Motorola, y aunque no estoy metido en el mundillo del desarrollo JavaME, si tengo experiencia con la industria de los moviles.... y debo admitir que nunca dejan de "sorprenderme" (y no en positivo :) ). La cosa empezo bien, con Motorola mostrando trozos de entrevistas a desarrolladores de moviles diciendo lo dificil que era, la distribucion en distintos dispositivos una pesadilla.. etc. y Motorola admitiendo que es un problema: hasta aqui bien.
Y entonces van y concluyen que el problema es como hacer mas facilmente las interfaces graficas (doh!) y muestran una nueva capa de abstraccion para poder abstraer las acciones del movil y como se representan en la pantalla. Fue un momento Homer Simpson.
Ni mencion de los problemas en la especificacion que da problemas con las implementaciones de distintos fabricantes, ni facilitar la distribucion de aplicaciones.... en fin.
Mas tarde estuve en una sesion que si fue interesante, el panel sobre Java Libre (escrito asi literalmente) donde los desarrollores principales de las distintas implementaciones libres de Java respondian a preguntas, etc. sobre el futuro post-Java y GPL. La idea que quedo es que las implementaciones libres de Java de gnu, kafee... han aceptado la nueva situacion y van a unir sus esfuerzos con el OpenJDK, con lo que los proyectos quedaran en todo caso para JVMs mas especializadas (dispositivos etc) y se uniran a OpenJDK para hacer el "Java de proposito general"... excepto Harmony, que seguira con una implementacion completa con el mismo proposito.
La razon final tecnica, no entrare en politica ;), al fin y al cabo se reduce a una cuestion de licencia. Harmony usara una licencia mas permisiva para usos comerciales y OpenJDK usara una licencia GPL, a no ser obtengas licencia comercial de Sun como hasta ahora. Asi que parece que quedaran dos implementaciones de proposito general.
Tambien parecio quedar claro/confirmado, a traves de la gente de RedHat, que la gente de las distribuciones de Linux va a adoptar el OpenJDK y empezaran a incluirlo en sus distribuciones. Fue bastante interesante tener a la gente reponsable de los distintos proyectos juntos, ya que trataron otros temas sobre como avanzar la especificacion y evolucionar el lenguaje sin romper la compatibilidad con cosas que se salen de la especificacion pero la gente usa, etc.
Volviendo al tema de los moviles, me fui al pabellon a una fuente "mas que fiable" y que conozco personalmente para plantearle el tema de los moviles y el JSE completo en ellos. Y bueno, por un lado si es cierto que Sun con Savage tiene una implementacion completa del JSE en el movil, que lo va a abrir y que va a intentar que la cosa cuaje, pero por otro lado la realidad dice que de momento lo que pasara es que el JME ira evolucionando y anyadiendo cosas pero seria raro que hubiese un cambio radical hacia el JSE total, ya que depende de los fabricantes y operadores.
En este caso si creo, personalmente, que Sun hace lo que puede y el anuncio es un poco para "animar" a los fabricantes, ya que si los desarrolladores "reclaman" el JSE los fabricantes se sentiran mas inclinados, pero mi fuente dijo claramente que tampoco hay que esperar milagros, ya que no depende de Sun, el JCP o la comunidad, que pueden hacer su parte pero la sarten por el mango la tiene la industria y si no hay beneficio por medio... y de momento con el mercado semi-propietario parecen tan comodos... Asi que ala, todos a empujar :D.
Ah, y para darles un poco de dosis de realidad de como esta la cosa, los envie a la entrevista que hicimos a Ibon en la comunidad JavaTools sobre desarrollar en los moviles y que dejaba claro el panorama. A veces olvidamos que si no les damos "feedback" a los que toman las decisiones, estan en su mundo y no se enteran. Asi que un poco de critica constructiva con educacion nunca viene mal.
Paseos por el pabellon viendo productos y cosas, una entrada ligera en la fiesta, sobre la que prefiero no decir nada por que no tengo mucho positivo que decir, y luego nuestra sesion, a la que vino bastante gente teniendo en cuenta la hora y que la fiesta era a la vez.
Y por fin el ultimo dia, que se reserva para los anuncios mas "vistosos" con "juguetes" con Gosling como maestro de ceremonias.
Para empezar una aplicacion creada sobre DTrace capaz de mostrar en tiempo real datos sobre lo que hace nuestra aplicacion, bajando hasta lineas de codigo del JDK, bytes que se transmiten en cada llamada I/O etc. La verdad es que casi es demasiada informacion, jejeje, aunque es muy util en segun que entornos donde tienen que "exprimir" al maximo los programas.
Despues un par de demos con impresoras programables en Java con comunicacion a traves de Glassfish con moviles etc, de forma que puedes recoger datos desde aplicaciones o dispositivos, enviarlos a la impresora y alli tener un programa que maneje las colas, decida si imprimirlo o no segun permisos etc..
Como gracia una balanza de supermercado programable en Java... :)
Despues una llamada de los fabricantes de BlueRay para que a los desarrolladores se nos ocurran cosas para hacer con los discos y los lectores, que ademas ahora tendran BlueRay Live y podran actualizar sus programas por Internet. El ejemplo que mas me ha gustado ha sido la peli Master and Commander con un pequenyo mapa transparente que mostraba al mismo tiempo que la peli, donde estaba en barco en una mapamundi y hacia donde iba etc. Aparte de eso, mi opinion es que de nuevo tenemos una "solucion" tecnica en busca de un problema, en vez de al reves, pero bueno asi es la industria a veces. Asi que si teneis ideas, hay un nuevo mercado ahi para programadores Java.
Cambio de tercio y pasamos a los robots. Primero mostrando los herederos de los Robosapien y que son unos robotitos humanoides programables totalmente en Java, con sensores, audio, grabacion de video etc y que han bailado la de "I will survive" :). Todavia les queda trabajar un poco la suavidad de movimientos, pero lo principal para nosotros es que se programa en Java estandar con el Eclipse y zas, a "robotear".
Luego han cambiado a un robot industrial con un "brazo" giratorio de precision capaz de acelerar de 0 a +300Km/h en un segundo, asi que NO es un juguete, programado totalmente en Java Real Time y utilizado ya en la industria.
Despues de eso un helicoptero para reconocimiento de terreno igualmente programado en Java Real Time con laseres y sensores para "grabar" el terreno por el que pasa... para el cual necesitas una licencia de piloto de verdad, una pena :D.
Novedades en Java 6 mostrando soporte para JRuby y mejoras en el editor, sobre esto hay suficientes noticias en Internet :), un robot submarino programado en Java...
Y para variar de tema, un programa de una empresa para estudios de grabacion, el cual conectas a una camara y en tiempo real puedes procesar la imagen y ver como andas de luz, sonido, corregir cosas... a 30FPS y sufrir parones. Ademas te permite grabarlo en disco, hacer efectos de croma... todo con Java, Swing, etc... A ver si eso significa revitalizar un poco la parte de multimedia que andaba un poco muerta.
Y mas cosas que no da tiempo a transmitir que tampoco tiene que ser esto la Biblia en verso :). Quedan un par de horas y unas pocas sesiones que atender, pero a no ser que encuentre algo importante ya lo dejare aqui. Asi que vuelvo a mi jaula y espero que al menos os haya dado una perspectiva de lo que es el JavaOne por dentro, aparte de todo lo que ira apareciendo poco a poco en Internet sobre el evento.
Saludos y como dicen aqui, "roger and out"
ge
PD: No voy a aburriros pero anoche en un bar, estaba hablando con un especialista en rendimiento y es increible las cosas que hacen con maquinas de 700 procesadores, y las "cochinadas" que hacen para que se ejecuten como toca aprovechando toda esa potencia (como no sincronizar nada y reparar solo si ha ido mal) a partir de Java normal y modificando la JVM. Eso y que los fabricantes de hardware se estan poniendo las pilas para intentar que Java corra mas rapido en sus maquinas para poder vender mas, lo cual nos ayuda a todos, jejeje. etc. etc. etc.
Aunque nuestro amigo greeneyed ha sido nuestro mejor corresponsal,hay van mis dos granitos de arena de lo que he podido leer en internet.
Por un lado Sun a lanzado una especie de promesa de algo que siempre se había demandado y que se esperaba la para la jdk 7, y es la modularización del JRE.
Parece ser que han visto que para aprovechar realmente javafx hay que relanzar Java Web Start y... los APPLETS. Para ello estan trabajando en minimizar el tamaño del JRE y que este sea un micro-kernel capaz de instalar las dependencias que necesita. Podeis leer comentarios en divesos blogs Roumen Strobl, Yakov Farin y On Java
Otro cosa que particularmente estaba experando es la primera preview de glassfish v3. La particularidad de esta nueva versión que tardará todavia algunos meses en finalizarse, es que cuenta con un micro-kernel (si tb algo así en el servidor de Sun) que permite configurar que cosas quieres que cargue el servidor: EJB, Seguridad, Ruby??¿?? Si otra de las funcionalidades es la del soporte de JRuby y otros lenguajes de script. El caso es que esta modularización permite arrancar el servidor en menos de 1 segundo ( supongo que en un mac book pro) del screencast que esta publicado desde hace unas semanas en internet.
Irenio Luis Chagua nos ha enviado este magnífico artículo sobre el uso de Netcape Directory SDK para acceder a recursos LDAP.
El artículo es bastante completo y cubre todos los aspectos involucrados, desde una descripción muy precisa sobre LDAP, instalación de OpenLDAP, explicación de clientes y librerías existentes y la construcción de una aplicación Web sobre Tomcat que autentifica usuarios validándolos en LDAP y recuperando todos sus datos de dicho directorio.
Sin duda Irenio ha realizado un estupendo trabajo y la calidad del artículo así lo constata.
Irenio trabaja en NetSolutions Perú S.A.C. En sus ratos libres, colabora con javaHispano y desarrolla software.
Acceso a OpenLDAP mediante Netscape Directory SDK para Java vía JNDI en una aplicación web con Apache Tomcat.
Acceso a OpenLDAP mediante Netscape Directory SDK para Java vía JNDI en una aplicación web con Apache Tomcat.
Fecha de creación: 04.05.2007
Revisión 0.1 (04.05.2007)
Irenio Luis Chagua Aduviri ichagua@nspsac.com
Copyright Copyright (c) 2007, Irenio Luis Chagua Aduviri. Este documento puede ser distribuido solo bajo los términos y condiciones de la licencia de Documentación de javaHispano v1.0 o posterior (la última versión se encuentra en /licencias/). Para cualquier comentario, duda, consulta sobre este tutorial dirigirse a ichagua@nspsac.com.
Introducción
Cuando se está desarrollando aplicaciones empresariales, muchas veces se tiene un directorio de personas, objetos, que son necesarios tenerlos ordenado en un formato estándar, uno de ellos es LDAP (Lightweight Directory Access Protocol), pero las formas de acceso a este directorio son diversas, por eso se ha pensado en implementar el acceso desde un directorio SDK para Java utilizando un servidor web Apache Tomcat. El directorio LDAP utilizado es OpenLDAP, uno de los directorios que está al alcance del mundo del software libre.
En este artículo se presenta el uso del directorio LDAP con la finalidad de conocer con mayor profundidad, creando nuevas especificaciones de esquemas del directorio y nuevas clases de objetos con sus respectivos atributos. El mismo directorio es posible mostrar mediante un Browser LDAP en modo gráfico.
La aplicación desarrollada empieza con la creación de las entradas del directorio LDAP, configuración de la librería Netscape Directory SDK para Java, configuración de recursos JNDI con las clases JavaBean, implementación de las clases DAO para la aplicación haciendo uso del mismo directorio SDK para la búsqueda, autenticación de usuarios, creación y manipulación de las entradas de directorio y finalmente es mostrado el acceso a LDAP mediante Servlets para enviar datos a una página web haciendo uso de JSP.
Algunos de los pasos de la configuración de las herramientas se han obviado, en caso de no concerlos pueden encontrar información básica en los distintos sitios mencionados en la referencia.
LDAP
LDAP (Lightweight Directory Access Protocol) o Protocolo de Acceso Ligero a Directorio es un protocolo de comunicación que permite acceder y modificar información almacenada en un directorio ordenado y distribuido en forma jerárquica, actualmente está en la versión 3. LDAP permite almacenar información de cuentas de usuario, contactos, ubicación de diversos recursos de la red, permisos de usuarios, certificados, entre otros; éste directorio está optimizado para acceso de lectura en forma eficiente y almacenar datos de poco tamaño, las modificaciones se presentan con poca frecuencia como el caso de un correo electrónico.
El protocolo LDAP accede a la información contenida en un árbol de información de directorio, las cuales están formados por un conjunto de atributos, indicando el nombre y su respectivo valor. Este directorio es único en una organización y es posible acceder de diversas aplicaciones.
Utilidades de LDAP
Directorios de información, por ejemplo datos de empleados organizados por departamentos (siguiendo la estructura organizativa de la empresa) ó cualquier tipo de páginas amarillas.
Sistemas de autenticación/autorización centralizada, sistemas donde se guarda gran cantidad de registros y se requiere un uso constante de los mismos, por ejemplo, gestión de cuentas de acceso a una red corporativa, sistema de autenticación para páginas web, sistemas de control de entradas a edificios, oficinas, entre otros.
Sistemas de correo electrónico.
Sistemas de alojamiento de páginas web y FTP.
Sistemas de autenticación basados en RADIUS, para el control de accesos de los usuarios a una red de conexión o ISP.
Servidores de certificados públicos y llaves de seguridad.
Autenticación única ó "single sign-on" para la personalización de aplicaciones.
Perfiles de usuarios centralizados, para permitir itinerancia ó "Roaming".
Libretas de direcciones compartidas.
Características de LDAP
Operaciones de lectura muy rápidas. Debido a la naturaleza de los datos almacenados en los directorios las lecturas son más comunes que las escrituras.
Datos relativamente estáticos. Los datos almacenados en los directorios no suelen actualizarse con mucha frecuencia.
Entorno distribuido, fácil replicación.
Estructura jerárquica. Los directorios almacenan la información de forma jerárquica de forma nativa.
Orientadas a objetos. El directorio representa a elementos y a objetos. Los objetos son creados como entradas, que representan a un conjunto de atributos.
Esquema estándar. Los directorios utilizan un sistema estándar que pueden usar fácilmente diversas aplicaciones.
Atributos multi-valor. Los atributos pueden almacenar un valor único o varios.
Replicación multi-master. Muchos de los servidores LDAP permiten que se realicen escrituras o actualizaciones en múltiples servidores.
Estructura del Arbol LDAP
Entradas
El modelo de información de LDAP está basado en entradas. Una entrada es un conjunto de atributos que tienen un único Nombre Distintivo DN (Distinguished Name) y de forma global. El DN se utiliza para referirse a una entrada sin ambig�edades. Cada atributo de una entrada tiene un tipo y está asociado uno o más valores. Los tipos son normalmente palabras nemotécnicas, como "cn" para common name, o "mail" para una dirección de correo electrónico. La sintaxis de los atributos depende del tipo de atributo. Por ejemplo, un atributo cn puede tener el valor "Mariano Apaza Quispe". Un atributo mail puede contener un valor "mapaza@nspsac.com". El atributo jpegPhoto contiene una fotografía en formato JPEG.
Atributos
Los datos del directorio son un conjunto de atributos y sus respectivos valores. Por ejemplo el atributo commonName, o cn, se usa para representar el nombre de una persona.
Los datos de una persona que se registra en el directorio se definen mediante el conjunto de atributos que están definidas en las especificaciones de esquemas, en este caso la clase de objetos person.
En una entrada de directorio se presentan atributos obligatorios que deben estar presentes en la clase de objetos y atributos opcionales que no es necesario que esté presente en una entrada de clase de objetos.
Tipos de Atributos
Una definición de tipo de atributo especifica la sintaxis de un atributo y cómo se ordenan y comparan los atributos de ese tipo. Los tipos de atributos en el directorio forman un árbol de clases. Por ejemplo, el tipo de atributo "commonName" es una subclase del tipo de atributo "name". Hay atributos obligatorios y opcionales que se muestran en la Tabla 1.1.
Identificador de Atributo
Descripción del Valor de Atributo
NUMERICOID (obligatorio)
Identificador de Objeto único (OID)
NAME
Nombre del Atributo
DESC
Descripción del Atributo
OBSOLETE
"true" si es obsoleto; "false" o ausente si no lo es
SUP
Nombre del tipo de atributo superior del que se deriva el tipo de atributo
EQUALITY
Nombre ó OID de la regla de correspondencia si la igualdad de correspondencia está permitida; ausente si no lo está
ORDERING
Nombre o OID de la regla de correspondencia si está permitida la ordenación; ausente si no lo está.
SUBSTRING
Nombre o OID de la regla de correspondencia si está permitida la correspondencia de sub-string ausente si no lo está.
SYNTAX
"true" si el atributo no es multi-valor; "false" o ausente si lo es
COLLECTIVE
"true" si el atributo es colectivo; "false" o ausente si no lo es
NO-USER-MODIFICATION
"true" si el atributo no es modificable por el usuario; "false" o ausente si lo es
USAGE
Descripción del uso del atributo
Tabla 1: RFC 2252: AttributeTypeDescription
Clase de Objetos
En LDAP, una clase de objetos define el conjunto de atributos a ser usados para definir una entrada. El estándar LDAP proporciona estos tipos básicos para las clases de objetos:
Grupos en el directorio, entre ellos listas no ordenadas de objetos individuales o de grupos de objetos.
Emplazamientos, como por ejemplo el nombre del país y su descripción.
Organizaciones que están en el directorio.
Personas que están en el directorio.
Una entrada determinada puede pertenecer a más de una clase de objetos. Por ejemplo, la entrada para personas se define mediante la clase de objetos person, pero también puede definirse mediante atributos en las clases de objetos inetOrgPerson, groupOfNames y organization La estructura de clases de objetos del servidor determina la lista total de atributos requeridos y permitidos para una entrada concreta.
LDIF
El formato de intercambio de LDAP es un archivo de texto que almacena información de entradas de objetos en forma jerárquica. Esto nos permite importar y exportar información de directorio entre servidores de directorios basados en LDAP.
Modelo de Nombres LDAP
Cada entrada de directorio LDAP está organizada en un árbol de información de directorio y para identificar a alguna entrada de directorio se accede mediante su nombre distintivo (DN). Cada nombre distintivo puede estar formado por una secuencia de nombres distintivos relativos RDN (Relative Distinguished Name), como uid=mapaza. Cada nombre distintivo relativo en un DN corresponde a una rama del árbol de información de directorio. Asimismo está formado por otros atributos que lo relacionan con las jerarquías superiores como componentes de dominio DC (Domain Component), así, dc=nspsac, dc=com. Después del nombre distintivo hay una serie de atributos que definen las entradas.
Para comprender un poco sobre la estructura del directorio veamos el árbol de la gráfica donde se describe a la empresa dc=nspsac, dc=com como principal y teniendo como unidades organizacionales a Admin, People y Developer, de las cuales en cada unidad organizacional es posible agregar directorios, en este caso una cuenta de usuario con su información de datos de identificación, registro del empleado, datos de su jefe, correo electrónico, teléfono fijo, teléfono celular, la dirección en la que vive, la clave de identificación de la cuenta y la foto correspondiente del empleado.
Figura 1: Arbol de Directorio LDAP
La información descrita anteriormente para que se encuentre registrado en el directorio LDAP creamos un formato LDIF estándar, que es un archivo de texto que contiene información de configuración LDAP y contenido de directorios, en este caso será mapaza.ldif que se muestra a continuación:
dn: uid=mapaza,ou=People,dc=nspsac,dc=com givenName: Mariano sn: Apaza Quispe mail: mapaza@nspsac.com objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: contribuyente uid: mapaza cn: Mariano Apaza Quispe dni: 01234567 ruc: 10012345675 employeeNumber: A214 manager: uid=jperez,ou=Admin,dc=nspsac,dc=com telephoneNumber: 051505050 mobile: 0519797979 direccion: Jr. Los Incas Nro. 6548 st: Puno l: Ilave userPassword: abc jpegPhoto:<file:///var/photo.jpg
Además al crear un directorio de entrada los atributos pertenecen a un conjunto de clase de objetos que están definidos en una especificación de esquemas según el estándar, en este caso pertenece a la clase de objetos person, organizationalPerson, inetOrgPerson y contribuyente, de éste último veremos más adelante.
Directorio LDAP
Para almacenar estas descripciones de directorios es necesario el uso de un directorio LDAP, hay varias implementaciones de estos directorios, de distintas empresas, para distintos usos y para aplicaciones diferentes. Para el caso del presente artículo haremos uso de OpenLDAP, un directorio LDAP a nuestro alcance en el mundo del software libre.
Para descargar el instalador de OpenLDAP acceda a http://www.openldap.org/, ó si desea OpenLDAP para Windows. Una vez descargado e instalado OpenLDAP es necesario modificar el archivo de configuración slapd.conf, este archivo contiene configuración de usuario, contraseña, sufijo, dn principal, especificaciones de esquemas, entre otros. En este caso cambiaremos algunos valores de los atributos.
Se ha realizado el cambio del sufijo a "dc=nspsac,dc=com", y dn principal a "cn=root,dc=nspsac,dc=com", luego la contraseña de la cuenta principal se ha cambiado a abc, pero también es posible colocar una contraseña encriptada con {CRYPT}, {MD5}, {SMD5}, {SSHA}, y {SHA}, si usamos alguna de estas encriptaciones la contraseña de la cuenta principal quedaría como el siguiente:
rootpw {SSHA}/Wg8V59/aoeKLn4PkkKWEsdvjyz6R+/E
Muchos de los atributos de la entrada de directorio descrito anteriormente no está disponible en la configuración inicial, estos atributos están basados en unas especificaciones de esquemas que delimitan su creación. Para esto quitaremos el comentario ó agregaremos si no está descrito de las siguientes especificaciones de esquemas:
include ./schema/core.schema include ./schema/cosine.schema include ./schema/inetorgperson.schema
Los atributos dni, ruc, y direccion no está definido en las especificaciones de esquemas que acabamos de activarlas, estos atributos lo he agregado para fines de conocer cómo se crea una nueva especificación de esquemas y por otro lado en mi país (Perú) todo usuario de un sistema al menos tiene su documento nacional de identidad (DNI), su registro único del contribuyente (RUC) y se agregó el atributo dirección por comodidad, aunque el atributo dni y direccion no debiera estar definido en una clase de objetos como la de contribuyente, más bien debería estar definido en la clase de objetos person, para efectos de no complicar la configuración se ha añadido en la clase de objeto contribuyente.
En una especificación de esquema se definen clases de objetos válidos que indican qué atributos debe contener en forma obligatoria y qué atributos son opcionales, así como el tipo de dato (cadenas de texto, números) de un atributo. La clase de objetos y los atributos deben estar definidos en forma global y único mediante cadenas de números (OID), para esto es necesario obtener un OID que nos permitirá crear tantas extensiones como se quiera del esquema.
Los tipos de clase de objetos que existen son tres: Structural Una clase de objeto estructural define las características básicas de un objeto. Auxiliary Una clase de objeto Auxiliar es adicional, complementa los atributos de una clase de objeto estructural. Y por último Abstract Esta clase de objeto abstracto es usado solamente para definir modelo de datos LDAP básicos.
La sintaxis básica para crear una clase de objetos es la siguiente:
objectclass ( 1.1.2.2.2 NAME 'myPerson' DESC 'Mi persona' SUP inetOrgPerson MUST ( myUniqueName $ givenName ) MAY myPhoto )
Donde:
1.1.2.2.2 es el identificador único global (OID).
NAME 'myPerson' es el nombre de la clase de objeto (alias para OID).
DESC 'Mi persona' la descripción para la clase de objeto.
SUP inetOrgPerson es el objeto del que hereda.
MUST(...) aquí se describen los atributos requeridos.
MAY(...) aquí se describen los atributos opcionales.
La sintaxis básica para crear un atributo es la siguiente:
attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber' DESC 'Identifica en forma única a un usuario' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
Donde:
1.3.6.1.1.1.1.0 es el identificador único global (OID).
NAME 'uidNumber' es el nombre del atributo (alias para OID).
DESC '...' la descripción para el atributo.
EQUALITY integerMatch calificador de plantilla de tipos.
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 es la sintaxis OID.
SINGLE-VALUE es un calificador, puede ser SINGLE-VALUE, COLLECTIVE y {LENGTH}.
Tipo de dato
OID
Descripción
Binary
1.3.6.1.4.1.1466.115.121.1.5
BER/DER data
Boolean
1.3.6.1.4.1.1466.115.121.1.7
boolean value
Distinguished Name
1.3.6.1.4.1.1466.115.121.1.12
DN
Directory String
1.3.6.1.4.1.1466.115.121.1.15
UTF-8 string
IA5String
1.3.6.1.4.1.1466.115.121.1.26
ASCII string
Integer
1.3.6.1.4.1.1466.115.121.1.27
Integer
Name and Optional UID
1.3.6.1.4.1.1466.115.121.1.34
DN plus UID
Numeric String
1.3.6.1.4.1.1466.115.121.1.36
Numeric String
OID
1.3.6.1.4.1.1466.115.121.1.38
Object Identifier
Octet String
1.3.6.1.4.1.1466.115.121.1.40
Arbitrary Octets
Printable String
1.3.6.1.4.1.1466.115.121.1.44
Printable String
Tabla 2: Sintaxis de los atributos
Nombre
Contexto
Descripción
booleanMatch
equality
Boolean
objectIdentiferMatch
equality
OID
distinguishedNameMatch
equality
DN
uniqueMemberMatch
equality
DN with optional UID
numericStringMatch
equality
numerical
numericStringOrdering
ordering
numerical
numericStringSubstringsMatch
substrings
numerical
caseIgnoreMatch
equality
case insensitive, space insensitive
caseIgnoreOrderingMatch
ordering
case insensitive, space insensitive
caseIgnoreSubstringsMatch
substrings
case insensitive, space insensitive
caseExactMatch
equality
case sensitive, space insensitive
caseExactOrderingMatch
ordering
case sensitive, space insensitive
caseExactSubstringsMatch
substrings
case sensitive, space insensitive
caseIgnoreIA5Match
equality
case insensitive, space insensitive
caseIgnoreIA5OrderingMatch
ordering
case insensitive, space insensitive
caseIgnoreIA5SubstringsMatch
substrings
case insensitive, space insensitive
caseExactIA5Match
equality
case sensitive, space insensitive
caseExactIA5OrderingMatch
ordering
case sensitive, space insensitive
caseExactIA5SubstringsMatch
substrings
case sensitive, space insensitive
Tabla 3: Reglas de plantillas de atributos
Regresando nuevamente a la clase de objeto contribuyente y con la base de los conceptos para crear las clases de objetos y atributos en una especificación de esquema, se define el esquema en el archivo de texto con el nombre contribuyente.schema en el directorio schema de la ruta donde está instalado OpenLDAP, con el siguiente contenido:
# # OID prefix: 1.3.6.1.4.1.10018 # # Attributes: 1.3.6.1.4.1.10018.1.1 #
attributetype ( 1.3.6.1.4.1.10018.1.1.1 NAME 'ruc' DESC 'Registro único del Contribuyente' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{11} )
attributetype ( 1.3.6.1.4.1.10018.1.1.2 NAME 'razonSocial' DESC 'Razón Social del Contribuyente' SUP name )
attributetype ( 1.3.6.1.4.1.10018.1.1.3 NAME 'dni' DESC 'Documento Nacional de Identidad de la Persona' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{8} )
attributetype ( 1.3.6.1.4.1.10018.1.1.4 NAME 'dniRepLegal' DESC 'Documento Nacional de Identidad del Representante Legal del contribuyente' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{8} )
attributetype ( 1.3.6.1.4.1.10018.1.1.5 NAME 'estadoDomicilio' DESC 'Estado del Domicilio del contribuyente' SUP name )
attributetype ( 1.3.6.1.4.1.10018.1.1.6 NAME 'direccion' DESC 'Domicilio de la Persona' SUP name )
# # Objects: 1.3.6.1.4.1.10018.1.2 #
objectclass ( 1.3.6.1.4.1.10018.1.2.1 NAME 'contribuyente' DESC 'contribuyente' SUP top AUXILIARY MUST ( ruc $ dni ) MAY ( dniRepLegal $ estadoDomicilio $ razonSocial $ direccion) )
La especificación de esquema contribuyente debe ser agregado en el archivo de configuración slapd.conf la siguiente línea.
include ./schema/contribuyente.schema
Una vez creado el archivo y guardado reiniciamos nuestro servicio de directorio OpenLDAP.
Antes de continuar con agregar el directorio definido anteriormente en el fichero mapaza.ldif es necesario crear las unidades organizacionales, tal como se ha visto en el árbol LDAP del gráfico, sino creamos estas unidades organizacionales no es posible agregar esta entrada de directorio. Para esto creamos un archivo de texto con el nombre ounspsac.ldif con el siguiente contenido.
dn: ou=Admin,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: Admin
dn: ou=People,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: People
dn: ou=Developer,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: Developer
Para agregar un directorio de entrada LDAP en formato LDIF ejecutaremos el siguiente comando.
slapadd -v -f slapd.conf -l ounspsac.ldif
De otra forma también es posible insertar los datos de entrada del directorio con el siguiente comando.
De modo similar ahora añadimos al directorio LDAP lo que habíamos definido inicialmente.
slapadd -v -f slapd.conf -l mapaza.ldif
Y así añadimos otras entradas de directorio LDAP, pero ahora la contraseña del usuario será encriptado con {SSHA} y es de la siguiente manera.
dn: uid=jmamani,ou=People,dc=nspsac,dc=com givenName: Juan Antonio sn: Mamani Choque mail: jmamani@nspsac.com objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: contribuyente uid: jmamani cn: Juan Antonio Mamani Choque dni: 01847569 ruc: 10018475691 employeeNumber: 2097 mobile: 0519535353 direccion: Av. Titicaca Nro. 458 st: Puno l: Juli userPassword: {SSHA}IabIHNeVpLkbzDxCANEMj47OJ9QRh9Gj
Para generar una cadena de texto encriptada se hace uso del comando slappasswd que trae OpenLDAP de la siguiente manera:
slappasswd -h {SSHA} -s abc
Y continuando con agregar una entrada de directorio LDAP.
slapadd -v -f slapd.conf -l jmamani.ldif
Como se ha visto se ha tenido que agregar en tres veces las entradas de directorio LDAP definidos, pero no es necesario realizar para cada entrada de directorio, es posible juntar las tres entradas de directorio en un solo formato LDIF denominado nspsac.ldif.
Luego de haber insertado datos de entrada de directorio podemos hacer uso del comando slapcat para mostrar todas las entradas de directorio que se encuentra en nuestro directorio LDAP en formato LDIF, esto mismo nos puede servir para guardar estas entradas para lo que sea necesario.
Si se desea realizar una búsqueda mediante un DN es mediante el comando ldapsearch.
Hasta aquí hemos visto las operaciones de entradas de directorio OpenLDAP desde líneas de comando, se puede utilizar en modo gráfico mediante un navegador de LDAP, para esto utilizaremos LDAPBrowser que está basado en Java y ejecutamos la shell lbe.bat y configuramos como la que se muestra en la figura. Con esto es posible administrar un directorio LDAP en modo gráfico.
Figura 2: Configuración LDAP Browser
Librería para acceder a LDAP
Lo anterior ha sido una descripción breve sobre el manejo de un directorio LDAP, pero cuando se quiere incluir el uso de directorios LDAP en aplicaciones empresariales es necesario utilizar una librería para acceder a la información de los directorios, como un lenguaje de programación. Para el presente artículo haremos uso de una librería desarrollada por Netscape, un directorio SDK para Java que también está a nuestro alcance en el mundo de software libre. Una vez descargado el código fuente y compilado según las instrucciones de cómo generar la librería de Netscape, que también pueden bajárselo una de las librerías que he compilado ldapjdk.jar será posible integrar en las aplicaciones empresariales el acceso a un directorio LDAP.
Para realizar búsquedas y localizar información en diversos sistemas como directorios LDAP es necesario una interfaz de múltiples servicios de directorio y de nombres como JNDI (Java Naming Directory Interface). JNDI nos permitirá interactuar desde Java con OpenLDAP, esto mediante un proveedor de servicios de interfaz SPI (Service Provider Interface), que en este caso es el mismo Netscape que ha desarrollado junto a la librería para el acceso a LDAP, con el nombre Service Provider LDAP que también pueden bajárselo ldapsp.jar.
Los archivos generados, una vez compilado el código fuente, específicamente del directorio packages, es necesario que los archivos JAR sean agregados a la variable de entorno CLASSPATH del sistema operativo, asumiendo que en windows se ha creado en el directorio c:\netscape\ldapjava y en FreeBSD ó UNIX en el direcotorio /usr/netscape/ldapjava, que lo llamaremos a esta ruta de directorio como <LDAPSDKHOME> Mediante los siguientes comandos agregamos a la variable de entorno.
set CLASSPATH=<LDAPSDKHOME>/packages/ldapjdk.jar;<LDAPSDKHOME>/
packages/ldapsp.jar;%CLASSPATH%
API JNDI
Mediante el API de JNDI es posible escribir cualquier tipo de programa para acceder a información en directorios LDAP, gestores de Base de datos relacionales, servicios CORBA (COS, Corba Object Service), NDS de Novell, entre otras aplicaciones. Para que un programa de Java busque información de cualquier tipo en un directorio LDAP debe indicarse dentro del programa la ubicación del directorio LDAP mediante un Naming Manager para la ubicación física del sistema. Esto es importante ya que en cualquier momento es posible cambiar el servidor físico del directorio LDAP y no será necesario la modificación de programa fuente para luego compilarlo, sino basta con cambiar los parámetros de configuración.
Figura 3: API JNDI
Configuración de Recursos JNDI
Para realizar una consulta a una entrada de directorio LDAP mediante la librería Netscape Directory SDK para Java, será necesario la configuración de JNDI en un servidor web, para este artículo lo voy a desarrollar en una aplicación web con Apache Tomcat. El cual trae una implementación JNDI InitialContext para cada instancia de aplicación web que se encuentre ejecutando bajo este servidor. Para esto será necesario que descarguen el instalador de Apache Tomcat y configuralo. El servidor Tomcat es una aplicación web basada en Java creada para ejecutar servlets y páginas JSP que nos ayudará para este propósito.
Para este caso se creará una aplicación web con una estructura de directorios básico, lo nombraremos ldap que se encontrará en el directorio $CATALINA_HOME/webapps Luego es necesario incluir un descriptor de la aplicación que es el archivo web.xml que contendrá la configuración de la aplicación web y estará dentro del directorio WEB-INF/ del directorio raíz de la aplicación creada, es decir $CATALINA_HOME/webapps/ldap/WEB-INF/web.xml
Las entradas InitialContext en una aplicación web son configurados en un elemento <Context> que puede estar definido en $CATALINA_HOME/conf/server.xml ó de preferencia el archivo XML del contexto de la aplicación dentro de META-INF/context.xml
Los recursos definidos en estos elementos pueden estar referidos por los siguientes elementos al utilizar la descripción de una aplicación web que se encuentra en /WEB-INF/web.xml:
<env-entry> - Es la entrada del entorno de aplicación, un parámetro de valor simple, puede ser usado para configurar de cómo la aplicación funcionará.
<resource-ref> - Es la referencia del recurso de la aplicación, que es un objeto Factory para recursos como JDBC DataSource, JavaMail Session ó un objeto Factory personalizado.
<resource-env-ref> - Es el Recurso de referencia del entorno de aplicación, es una nueva variación del elemento <resource-ref> añadido en Servlet 2.4 que es más simple de configurar para recursos que no necesitan información de autenticación.
Para el caso de nuestra aplicación definiremos el elemento <resource-env-ref> en el descriptor de la aplicación web.xml con el siguiente contenido.
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app> <resource-env-ref> <description> Objeto Factory para una instancia de BeanLDAPHost. </description> <resource-env-ref-name> beanldaphost </resource-env-ref-name> <resource-env-ref-type> com.nspsac.bean.BeanLDAPHost </resource-env-ref-type> </resource-env-ref>
</web-app>
Como habrán notado en este descriptor de la aplicación hacemos referencia a un recurso de la aplicación con el nombre beanldaphost que es de tipo de clase JavaBean com.nspsac.bean.BeanLDAPHost, que luego se implementará.
Cada recurso JNDI disponible es configurado en base a una inclusión de los siguientes elementos dentro de un elemento <Context> ó un elemento <DefaultContext>:
<Environment> - Configura nombres y valores de entradas de entorno de aplicación que estará en funcionamiento a través de JNDI InitialContext.
<Resource> - Configura el nombre y el tipo de dato de un recurso disponible para la aplicación.
<ResourceLink> - Agrega un enlace a un recurso definido en el contexto JNDI global.
Para configurar el recurso JNDI en nuestra aplicación agregamos el <Context> en el archivo de configuración $CATALINA_HOME/conf/server.xml que es el recurso que hacemos referencia con el nombre beanldaphost.
En este recurso JNDI estará almacenado la información necesaria para acceder a un directorio LDAP desde una aplicación web; como habrán notado, este recurso utiliza una clase JavaBean y el Resource Factory que es una clase también JavaBean asociado mediante el atributo factory con el valor com.nspsac.bean.BeanLDAPHostFactory, y los demás atributos (iphost, puerto, dnbase, dnmgr, dnpwd y dnraiz) de este elemento <Resource> son información que utilizaremos dentro de las clases JavaBean para realizar la conexión hacia el directorio LDAP definido anteriormente. Adicionalmente, podemos apreciar el atributo ctxfactory asociado a un proveedor de servicios de interfaz (SPI) tal como habíamos indicado anteriormente.
En esta configuración el atributo factory hace referencia a un Resource Factory, pero según la configuración de tomcat podemos incluir de la siguiente forma "org.apache.naming.factory.BeanFactory", y por razones de personalizar nuestra aplicación, se ha creado una clase JavaBean propio.
Creando las clases JavaBean
Según la configuración JNDI que se ha definido en el descriptor de la aplicación y en el Resource Factory, ahora implementamos nuestra clase JavaBean con el nombre BeanLDAPHost, esto será llamado cada vez que la aplicación se ejecute asociado a Resource Factory.
public String getAttribute(String name) throws Exception { if(name == null || name.equals("")) throw new Exception(); else return (String)htLdap.get(name); } }
El Resource Factory predeterminado de Apache a veces puede resultarnos limitado para ciertas operaciones, por tal razón creamos nuestro Resource Factory propio para luego integrarlo en Tomcat, la clase JavaBean Resource Factory personalizado le hemos dado el nombre de BeanLDAPHostFactory.
Para escribir la clase Resource Factory se debe implementar la interfaz del proveedor de servicio JNDI javax.naming.spi.ObjectFactory. Cada vez que la aplicación web llama al método lookup() en una entrada del contexto que está asociado a este Factory, el método getObjectInstance() es invocado con los siguientes argumentos:
Object obj - El objeto que contiene la localización ó referencia de la información que puede ser usado en la creación del objeto.
Name name - Es el nombre al que este Factory está relacionado a nameCtx, ó null si el nombre no está especificado.
Context nameCtx - El contexto relacionado al que el nombre del parámetro es especificado, ó null si el nombre está relacionado al contexto inicial por defecto.
Hashtable environment - Es el entorno que es utilizado en la creación de este objeto.
while (addrs.hasMoreElements()) { RefAddr addr = (RefAddr) addrs.nextElement(); String nombre = addr.getType(); String value = (String) addr.getContent(); if (nombre.equals("iphost")) { iphost = value; } else if (nombre.equals("puerto")) { try { puerto = Integer.parseInt(value); } catch (NumberFormatException e) { throw new NamingException("Invalid 'port' value " + value); } } else if(nombre.equals("dnbase")){ dnbase = value; } else if(nombre.equals("dnmgr")){ dnmgr = value; } else if(nombre.equals("dnpwd")){ dnpwd = value; } else if(nombre.equals("dnraiz")){ dnraiz = value; } else if(nombre.equals("ctxfactory")){ ctxfactory = value; } } bean = new BeanLDAPHost(iphost,puerto,dnmgr,dnpwd,dnbase,dnraiz,ctxfactory); return (bean); } }
Para compilar las clases JavaBean que hemos creado es necesario que la librería que estamos utilizando del proveedor de servicios de interfaz de Netscape Directory SDK para Java ldapsp.jar esté incluido dentro del directorio de librerías de Tomcat que es $CATALINA_HOME/common/lib. Adicionalmente la librería SDK ldapjdk.jar también deberá estar incluido en este directorio de librerías de Tomcat para que funcione nuestros ejemplos de este artículo.
Implementando las clases DAO
Para implementar los accesos a un directorio LDAP mediante una aplicación web utilizaremos el modelo DAO, que es utilizado para separar las operaciones de los datos de bajo nivel desde un nivel más alto de la lógica de negocio. Una aplicación DAO tiene los siguientes componentes:
public class AccesoLDAPFactory { public static AccesoLDAP create() throws NamingException { return new AccesoLDAPImpl(); } }
Y la implementación de la interfaz DAO
La clase InitialContext es configurado como una aplicación web que es desplegado inicialmente, y está disponible para los componentes de la aplicación web (para acceso de solo lectura). Todas las entradas y recursos configurados están asociados al espacio de nombre JNDI java:comp/env para su acceso a un recurso. Para el caso de nuestra clase JavaBean estaría configurado como la que se muestra en el código siguiente.
El método getEnvironment recogerá los valores que habíamos definido en nuestra configuración JNDI que son información acerca del directorio LDAP, esto almacenado en un tipo de dato Collection de Java que es Hashtable, para luego estar disponible la conexión hacia el directorio LDAP, el que prepara los datos de conexión hacia este directorio es el método getInstance que tendrá los datos recogidos en dos variables globales de la clase de tipo private para que pueda ser utilizado en otros métodos de la clase.
Directorio SDK para Java
Como se ha mencionado anteriormente acerca del acceso a un directorio LDAP desde una aplicación empresarial, aquí implementaremos los métodos para el acceso, tales como autenticación de cuentas de usuario, cambio de atributos, agregar atributos, quitar atributos, buscar mediante un RDN, entre otros.
Un directorio SDK para Java tiene las siguientes funcionalidades:
Libertad para el manejo de protocolos
El uso de objetos estándares para devolver y procesar los datos
Utilidad de clases para el manejo de las entidades específicas de LDAP
Acceso completo para todos los servicios LDAP
Modelos de autenticación flexibles
La habilidad de ejecutar en cualquier parte una vez se haya escrito el código
Funcionalidad multicapa
Una plataforma de aplicaciones para directorios
Búsqueda de una Entrada de Directorio
Debido a que la facilidad de un directorio LDAP es su habilidad de mostrar resultados de las consultas en forma rápida, para esto empezaremos con realizar una búsqueda para obtener una entrada de nombre y sus atributos con sus respectivos valores.
Antes de realizar una búsqueda en un directorio LDAP, es necesario tener en cuenta la siguiente información:
Nombre de Servidor donde está instalado el directorio LDAP, también puede ser la dirección IP del servidor, se usa "localhost" cuando se realizan las pruebas en una sola máquina.
Número de puerto del directorio LDAP, que es el puerto TCP de la máquina donde el servidor de directorio es escuchado por las conexiones LDAP, el puerto estándar para LDAP es 389 para las conexiones no SSL. Para las conexiones basados en SSL es el puerto 636.
DN base del árbol de directorio administrado por el servidor, es el nombre base como raíz por donde empezará a realizar la búsqueda, por ejemplo ou=People,dc=nspsac,dc=com.
Alcance de la búsqueda (Scope), es el punto de partida de una búsqueda y la profundidad a la que realiza la búsqueda en un árbol de directorio, hay tres opciones para este alcance:
BASE, representado por la constante LDAPConnection.SCOPE_BASE, es usado solo para búsquedas de DN base.
ONE, representado por la constante LDAPConnection.SCOPE_ONE, es usado para indicar que realice la búsqueda de todas las entradas debajo de DN base, pero no incluye el DN base.
SUBTREE, representado por la constante LDAPConnection.SCOPE_SUB, es usado para indicar que realice la búsqueda de todas las entradas debajo e incluso el DN base.
"(&(objectclass=person)(uid=" + uid + "))"
Filtros de búsqueda, es la consulta que se realiza, es usado para filtrar las entradas de directorios y devolver cierta cantidad de registros. Los filtros son usados mediante los paréntesis y combinaciones de los símbolos '&', '|' y '!' que representan 'And', 'Or' y 'Not' respectivamente. Si se quiere ubicar a todas las personas que sus apellidos (representado mediante el atributo sn) empiecen con "Mamani" se realiza mediante el siguiente filtro:
(&(objectclass=person)(sn=Mamani*))
Los atributos que se quieren mostrar en la búsqueda, puede que a veces solo se requiere algunos atributos y no todos los atributos presentes en una entrada de directorio. Si no se quiere recuperar ningún atributo se puede usar la constante LDAPConnection.NO_ATTRS. Si se desea recibir todos los atributos del usuario se puede usar la constante LDAPConnection.ALL_USER_ATTRS, ó en otro caso puede usar un array de cadenas de caracteres indicando los atributos que se quiere recuperar.
Opcionalemente las preferencias de búsqueda, estas preferencias incluyen la cantidad de tiempo que se desea permitir para la búsqueda, máximo número de registros que se aceptará, y si la búsqueda debe esperar hasta que todos los datos son recibidos. Las preferencias de búsqueda son especificados usando la clase LDAPSearchConstraints. Los métodos usados en esta clase son las siguientes:
setBatchSize especifica cómo deben ser devueltos los resultados de la búsqueda. El valor cero '0' indica que debe esperar hasta que todos los resultados sean devueltos, el valor uno '1' devuelve cada resultado como esté disponible.
setHopLimit especifica cuántas veces deben ser devueltas en una búsqueda de una entrada.
setMaxResults especifica el número máximo de resultados que deben ser devueltas desde una búsqueda. Se usa el valor cero '0' para resultados ilimitados.
setReferrals especifica si el SDK debe ó no seguir las referencias automáticamente.
setServerTimeLimit especifica el número máximo de segundos que debe tardar en entregar los resultados de la búsqueda.
Una vez repasado sobre los conceptos para el acceso a un directorio LDAP, ahora definimos el método que realizará la búsqueda en un directorio mediante un filtro que le pasaremos como parámetro, este filtro será de tipo "atributo=valor", que nos puede servir para realizar búsquedas por los atributos uid, employeeNumber, ruc, dni, mail, cn, sn y de los demás atributos, siempre y cuando el atributo sea de tipo cadena de texto. El método tiene el nombre de buscarEnLDAP(String uid).
Para la búsqueda, ya contamos con información del nombre del servidor, el puerto del servidor, el DN base, los atributos que están definidos en la variable ATTRS, y las preferencias de búsqueda se ha establecido a un número máximo de registro igual a 1, ya que la búsqueda está pensado para los atributos como nombres distintivos relativos y para obtener un conjunto de registros habría que cambiar estas preferencias de búsqueda.
Los resultados de una búsqueda son devueltos como un objeto LDAPSearchResults. Hay dos métodos para realizar el recorrido: nextElement y next, ambos métodos devuelven un objeto que puede ser LDAPEntry, LDAPReferralException ó LDAPException. Para este caso usaremos el método next.
El método next() de LDAPSearchResults devuelve un objeto LDAPEntry. La clase LDAPEntry contiene los siguientes cuatro métodos:
negetDN que devuelve el nombre distintivo completo de una entrada como una cadena de texto (por ejemplo, uid=jmamani, ou=People, dc=nspsac, dc=com).
getAttribute(String name) en este caso el argumento es el nombre del atributo, del que se quiere es su valor respectivo, es devuelto del tipo LDAPAttribute.
getAttributeSet devuelve un objeto LDAPAttributeSet que representa todos los atributos en esta entrada.
toString devuelve la entrada completa, incluye los atributos obtenidos como una cadena de texto.
Una vez que se tiene los atributos de una entrada, se puede obtener los valores de estos atributos. La clase LDAPAttribute tiene varios métodos obtener el valor de los atributos. Los métodos que normalmente se utilizan en la mayoría de los casos son los siguientes:
getStringValues devuelve de tipo Enumeration los valores para un atributo de tipo cadena de texto.
getByteValues devuelve de tipo Enumeration los valores para un atributo de tipo binario, como el caso de las fotos.
Hasta aquí no hemos visto el tema de autenticación a un directorio LDAP. Las conexiones hasta aquí han sido utilizando la cuenta por defecto configurado en el mismo directorio LDAP, más no hemos utilizado la cuenta de usuario y su contraseña. Por ejemplo es posible que en un directorio se quiera restringir el acceso a ciertos atributos, no permitiendo el acceso a algunos atributos como la fotografía de un empleado, que solamente puede tener acceso personal autorizado. Para realizar cambios tales como agregar, modificar, consultar ó eliminar ciertos atributos de una entrada de directorio LDAP, por lo general se debe autenticar.
El protocolo LDAP proporciona una operación para permitir conectar a los clientes para autenticar al servidor. El método más simple de autenticación soportado por el protocolo es un método que al cliente le permite enviar un DN y contraseña al servidor. Para usar el método de autenticación simple, se puede usar mediante el método LDAPConnection.authenticate ó un método LDAPConnection.connect variante que toma una autenticación pasando como parámetros a DN y contraseña. Algunas de las excepciones que se pueden producir son las siguientes:
LDAPException.NO_SUCH_OBJECT. Esta excepción es lanzada si el DN especificado no corresponde a una entrada del directorio.
LDAPException.INVALID_CREDENTIALS. Esta excepción es lanzada si la contraseña especificado no es correcta.
public HashMap autenticar(String uid, String pwd) throws NamingException, IncompleteConversationalState {
HashMap hMap = new HashMap(); LDAPConnection ldap = new LDAPConnection();
hMap = buscarEnLDAP("uid=" + uid); if(hMap.get("dn") != null){ ldap.authenticate( (String) hMap.get("dn"), pwd); } if(hMap.get("Mensaje") == null){ hMap.put("Mensaje","Usuario ".concat(uid).concat(" autenticado")); } } catch (LDAPException e) { e.printStackTrace(); hMap = new HashMap(); switch (e.getLDAPResultCode()) { case LDAPException.NO_SUCH_OBJECT: hMap.put("Mensaje",uid.concat(": El usuario indicado no existe")); break; case LDAPException.INVALID_CREDENTIALS: hMap.put("Mensaje",uid.concat(": Password invalido")); break; default: hMap.put("Mensaje",uid.concat( ": No se ha podido realizar la autenticacion, error:" + e.getLDAPResultCode())); break; } } catch (NumberFormatException e) { e.printStackTrace(); hMap = new HashMap(); hMap.put("Mensaje", "HA OCURRIDO UN PROBLEMA EN LOS PARAMETROS DEL SERVIDOR:" + e.getMessage()); } finally { try { if (ldap.isConnected()) { ldap.disconnect(); } } catch (LDAPException ex) { throw new IncompleteConversationalState( "NO SE PUDO CERRAR EL ENLACE:" + ex.getLDAPResultCode() + "->" + LDAPException.errorCodeToString(ex.getLDAPResultCode()) + "<br>" + ex.getMessage()); } } return hMap; }
Creando y Manipulando entradas de directorio
En una entrada de directorio LDAP es posible realizar las modificaciones de una entrada, tanto en crear una entrada, modificar una entrada con sus respectivos atributos, y eliminar una entrada. En la modificación de entradas es posible el manejo de los atributos como la de añadir atributos, modificar atributos y eliminar atributos. Estas operaciones siempre estarán presentes en un manejo de entrada de directorios LDAP.
Para agregar una nueva entrada de directorio es necesario definir un nombre distintivo DN para la entrada y los atributos para esta entrada. Para agregar una nueva entrada es necesario tener en cuenta los siguientes pasos:
Crear un objeto LDAPAttribute para cada atributo que forma la entrada.
Crear un objeto LDAPAttributeSet y utilizar el método add para agregar cada objeto LDAPAttribute del paso 1.
Crear un objeto LDAPEntry que especifica el nuevo DN y el LDAPAttributeSet del paso 2.
Invocar al método LDAPConnection.add con el objeto LDAPEntry del paso 3.
Con estos pasos se ha creado el método addEntry que recibe dos parámetros, el primero es el nombre distintivo identificador de usuario y el segundo un conjunto de atributos en un Collection HashMap.
public void addEntry(String uid, HashMap attrs) { LDAPAttributeSet entryAttrs = new LDAPAttributeSet(); LDAPConnection ldap = new LDAPConnection(); try{ ldap.connect(3, (String)htEnv.get("DN_HOST"), Integer.parseInt((String)htEnv.get("DN_PORT")), (String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append( (String)htEnv.get("DN_RAIZ")).append(", ").append( (String)htEnv.get("DN_BASE")).toString();
for(Iterator it = attrs.keySet().iterator(); it.hasNext(); ){ String name = (String)it.next(); entryAttrs.add( new LDAPAttribute( name, (String)attrs.get(name))); }
La otra operación con las entradas es la modificación de sus atributos. Para modificar un atributo de una entrada, se ha utilizado el método modifyAttributes del objeto DirContext, también es posible modificar un atributo con el uso del objeto LDAPModification y luego invocando al método modify de LDAPConnection. Para modificar los atributos de una entrada es necesario tener en cuenta los siguientes pasos:
Crear un objeto Attribute para un atributo que se desea modificar.
Crear un conjunto de objetos ModificationItem, puede ser uno solo, especificando DirContext.ADD_ATTRIBUTE, REPLACE_ATTRIBUTE ó REMOVE_ATTRIBUTE para cada objeto Attribute del paso 1.
Invocar al método modifyAttributes con el DN y el ModificationItem.
ModificationItem[] mods = new ModificationItem[1]; Attribute mod = new BasicAttribute(nameAttr, valAttr); mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append( htEnv.get("DN_RAIZ")).append(",").append( htEnv.get("DN_BASE")).toString();
ModificationItem[] mods = new ModificationItem[1]; Attribute mod = new BasicAttribute(nameAttr, valAttr); mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append( htEnv.get("DN_RAIZ")).append(",").append( htEnv.get("DN_BASE")).toString();
nspsacCtx.modifyAttributes(dn, mods); }
public void deleteAttribute(String uid, String nameAttr) throws NamingException {
ModificationItem[] mods = new ModificationItem[1]; Attribute mod = new BasicAttribute(nameAttr); mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append( htEnv.get("DN_RAIZ")).append(",").append( htEnv.get("DN_BASE")).toString();
nspsacCtx.modifyAttributes(dn, mods); }
Los datos de una entrada deben estar bien guardados en un directorio LDAP. Pero llega un momento en que será necesario borrar una entrada LDAP. Para esto quitar una entrada de un directorio es muy simple. Solamente se debe especificar qué DN se debe quitar e invocar al método LDAPConnection.delete.
public void deleteEntry(String uid) { LDAPConnection ldap = new LDAPConnection(); try{ ldap.connect(3, (String)htEnv.get("DN_HOST"), Integer.parseInt((String)htEnv.get("DN_PORT")), (String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append( (String)htEnv.get("DN_RAIZ")).append(", ").append( (String)htEnv.get("DN_BASE")).toString();
Por último, la operación en una entrada es el renombramiento, es decir modificando el RDN de una entrada de directorio LDAP. Por ejemplo se puede cambiar uid=pcarrillo del DN ou=People,dc=nspsac,dc=com para tener el uid=pedro.carrillo del DN ou=People,dc=nspsac,dc=com. El método LDAPConnection.rename invoca la operación LDAP para cambiar el RDN de la entrada.
No solamente es posible cambiar el RDN en un mismo nivel del árbol del directorio LDAP, hay la posibilidad de mover ó copiar a un RDN a una parte diferente del árbol del directorio LDAP. Como ejemplo veamos al RDN uid=jmamani del DN ou=People,dc=nspsac,dc=com lo movemos ó lo copiamos a otra parte del árbol como RDN uid= juan.mamani y DN ou=Admin,dc=nspsac,dc=com.
public void renameEntry(String uid, String newUid) { LDAPConnection ldap = new LDAPConnection(); try{ ldap.connect(3, (String)htEnv.get("DN_HOST"), Integer.parseInt((String)htEnv.get("DN_PORT")), (String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append( (String)htEnv.get("DN_RAIZ")).append(", ").append( (String)htEnv.get("DN_BASE")).toString();
String newRDN = new StringBuffer("uid=").append(newUid).toString();
Una vez que se ha realizado con la implementación de las clases para el acceso a un directorio LDAP, será necesario mostrar el resultado del acceso mediante un Servlet para enviar datos a una página web haciendo uso de JSP. Para esto creamos el Servlet ServletGestionLDAP.
La página JSP que recoge valores enviados desde un servlet como los atributos de una entrada de directorio, son definidos de la siguiente forma.
<% HashMap hmAut = (HashMap)session.getAttribute("hmAut"); HashMap hmSearch = (HashMap)session.getAttribute("hmSearch"); String cambioClave = (String)session.getAttribute("cambioClave"); String opcion = (String)session.getAttribute("opcion"); %> <%//... Para mostrar los datos de una entrada después de autenticar...%> <%if(hmAut != null){ String uid = (String)hmAut.get("uid"); String sn = (String)hmAut.get("sn"); String givenName = (String)hmAut.get("givenName"); String cn = (String)hmAut.get("cn"); String employeeNumber = (String)hmAut.get("employeeNumber"); String dni = (String)hmAut.get("dni"); String ruc = (String)hmAut.get("ruc"); String direccion = (String)hmAut.get("direccion"); String mail = (String)hmAut.get("mail"); String telephoneNumber = (String)hmAut.get("telephoneNumber"); String mobile = (String)hmAut.get("mobile"); String jpegPhoto = (String)hmAut.get("jpegPhoto"); String msg = (String)hmAut.get("Mensaje"); if(msg != null){%> <div class="titulo"><%=msg%></div> <%}%> <table> <tr><td><b>Cuenta de Usuario :</b></td> <td><%=uid%></td></tr> <tr><td><b>Apellidos :</b></td> <td><%=sn%></td></tr> <tr><td><b>Nombres :</b></td> <td><%=givenName%></td></tr> <tr><td><b>Nombre Completo :</b></td> <td><%=cn%></td></tr> <tr><td><b>Registro Empleado :</b></td> <td><%=(employeeNumber!=null?employeeNumber:"-")%></td></tr> <tr><td><b>DNI :</b></td> <td><%=dni%></td></tr> <tr><td><b>RUC :</b></td> <td><%=ruc%></td></tr> <tr><td><b>Dirección :</b></td> <td><%=direccion%> - <%=hmAut.get("l")%>, <%=hmAut.get("st")%></td></tr> <tr><td><b>Correo Electrónico :</b></td> <td><%=mail%></td></tr> <tr><td><b>Teléfono :</b></td> <td><%=(telephoneNumber!=null)?telephoneNumber:"-"%></td></tr> <tr><td><b>Teléfono Móvil :</b></td> <td><%=(mobile!=null)?mobile:"-"%></td></tr> <tr> <td valign="top"><b>Foto :</b></td> <td> <%if(jpegPhoto != null) {%> <img src="imgs/<%=jpegPhoto%>" alt="" border="0" width="70" height="80"> <% } else {%> - <% }%> </td> </tr> </table> <%}%> <%//... Para mostrar datos de una entrada después de una búsqueda... %> <%if(hmSearch != null){ String msg = (String)hmSearch.get("Mensaje"); if(msg != null){%> <div class="titulo"><%=msg%></div> <% hmSearch.remove("Mensaje"); } for (Iterator it = hmSearch.keySet().iterator(); it.hasNext();){ String name = (String)it.next(); String value = (String)hmSearch.get(name); if(!name.equals("jpegPhoto")){%> <%=name%>: <%=value%><br> <%} else{ %> <%=name%>: <br><img src="imgs/<%=value%>" alt="" border="0"><br> <%} } }%>
Y por último realizando las búsquedas de entradas de directorios mediante un RDN, autenticando usuarios y cambiando clave de un usuario. Solamente se ha incluido en la página web las funcionalidades mencionadas, las demás opciones ya los puede incluir con los métodos desarrollados en el presente artículo.