lunes, octubre 26, 2009

Receta CXF, WSS4J y Spring Security


Como utilizar la seguridad de Spring Security para autenticar un usuario/contraseña de un servicio web implementado con CXF mediante WSS4J (Java WS-Security de Apache):

En el aplication context de spring, primero definimos los namespaces, importamos los xml de CXF y definimos el "Interceptor" que gestionará la seguridad:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"
    >

    <!-- Load CXF modules from cxf.jar -->
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="WSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
     <property name="properties">
      <map>
          <entry key="action" value="UsernameToken Timestamp"/>
          <entry key="passwordType" value="PasswordDigest"/>
          <entry key="passwordCallbackRef">
           <bean class="my.company.SecurityInPasswordHandler"/>
          </entry>
      </map>
     </property>
   </bean>
</beans>

Y luego agregamos el interceptor a los webservices que necesiten seguridad:
<jaxws:endpoint id="salaWebService" implementor="#salaService" address="/salas">
    <jaxws:inInterceptors>
        <ref bean="WSS4JInInterceptor"/>
    </jaxws:inInterceptors>
</jaxws:endpoint>

Asumiendo que tenemos el siguiente servicio:
@WebService
@SOAPBinding
public interface SalaService {
    @WebResult(name = "sala")
    public abstract Sala get(@WebParam(name = "id") Long id);
}

@Service("salaService")
@WebService(serviceName = "SalaService", portName = "SalaPort", endpointInterface = "my.company.service.SalaService")
public class SalaServiceImpl implements SalaService {
    public Sala get(Long id) {
        return (Sala) executor.execute(SalaBusinessOperations.GET, new Long(id));
    }
}

Y que CXF está configurado en el web.xml de la siguiente forma:
<servlet>
    <servlet-name>CXFServlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>    
<servlet-mapping>
    <servlet-name>CXFServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
</servlet-mapping>

La seguridad de spring debería estar configurada, por ejemplo, con lo siguiente:
<http >
    <intercept-url pattern="/services/**" access="ROLE_ANONYMOUS" />
    <intercept-url pattern="/**" access="ROLE_USER" />
    <logout/>
    <anonymous/>
</http>

Finalmente podemos dedicarnos a la clase que gestionará la seguridad:
public class SecurityInPasswordHandler implements CallbackHandler {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userService;

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException, AuthenticationException {

        WSPasswordCallback pwdCallback = (WSPasswordCallback) callbacks[0];

        int usage = pwdCallback.getUsage();
        if ((usage == WSPasswordCallback.USERNAME_TOKEN) || (usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN)) {
            String password = pwdCallback.getPassword();
            if (usage == WSPasswordCallback.USERNAME_TOKEN) {
                UserDetails userDetails = userService.loadUserByUsername(pwdCallback.getIdentifier());
                password = userDetails.getPassword();
            }
            Authentication authentication = new UsernamePasswordAuthenticationToken(pwdCallback.getIdentifier(), password);
            authentication = authenticationManager.authenticate(authentication); //throws AuthenticationException
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // Return the password to the caller
            pwdCallback.setPassword(password);
        }
    }
}

Con esto logramos gestionar passwords enviadas en plano (PasswordText) y encriptadas (PasswordDigest). En ambos casos creando un SecurityContext para que la petición tenga los roles del usuario y puedan utilizarse las anotaciones "@Secured" de Spring Security.

miércoles, agosto 26, 2009

OSGi/JTA

Existe un problema al utilizar JTA (Java Transaction API) en OSGi: la implementación JTA que ofrece el bundle "0" (el del runtime OSGi) es la del JDK y está incompleta.
Para solucionar esto hay seguir dos pasos:
1: Colocar un jar de JTA con el MANIFEST.MF de OSGi en la carpeta "lib/ext" del JRE. El Jar se puede obtener del repositorio de SpringSource: com.springsource.javax.transaction-1.1.0.jar
2: Modificar la propiedad "org.osgi.framework.system.packages" del runtime para agregar los packages de la versión "1.1.0": "javax.transaction, javax.transaction;version=1.1.0, javax.transaction.xa, javax.transaction.xa;version=1.1.0"

De hecho esta solución nos permite también ofrecer nuestros propios packages como parte del bundle "0", solo debemos agregarlos a la propiedad arriba mencionada.

viernes, marzo 13, 2009

Workast

Tanto tiempo sin escribir ha hecho de mi conciencia algo molesto, por lo que mi primer post será ni mas ni menos que una justificación!
Estos últimos meses he estado dedicado a un proyecto personal, Open Source (como no podía ser de otra manera), de microblogging.
El proyecto en cuestión es Workast y para explicarlo, nada mejor que una comparación: Twitter.
El "problema" con Twitter, aunque parezca una contradicción, es su naturaleza pública. Si bien eso lo ha llevado a donde hoy está, en las empresas no siempre es bien visto debido a que la información que allí se vuelca es visible para todos.
Así han surgido alternativas como laconi.ca con la que cualquier empresa puede instalar dentro de sus fronteras un servidor de microbloging para sus empleados. También hay servicios como Yammer, gratuitos hasta que se quieren administrar, momento en el cual cobran a la empresa por esa posibilidad.
Esto me ha llevado a plantearme la posibilidad de hacer un software yo mismo y devolver un poco de todo lo que tomamos cada día de la comunidad Open Source.
Por supuesto me ha servido también para probar diferentes alternativas a las problemáticas que van surgiendo, y aprender en el camino.

¿Que hay hecho hasta ahora?
5700+ líneas de Java mas algunas mas de javascript (en ohloh.net la cantidad de líneas javascript es desproporcionada, ya que el 99% es de bibliotecas de terceros).

¿Y que hacen esas 5700 líneas?

Mas pantallas aquí.

¿Como?
- Escribiendo lo que te pase por la cabeza en formato wiki
- Dejando eventos para formar un calendario
- Comentando lo que otros escriben
- Organizando lo que te parece útil mediante etiquetas
- Siguiendo a otra gente para que aparezca en tu vista de novedades
- Creando y escribiendo dentro de grupos, para organizar mejor las novedades
- Filtrando las novedades por etiqueta, texto o tipo de actividad

¿Y con qué tecnología está desarrollado?
- Spring framework
- Hibernate
- Hibernate Validator (JSR-303)
- RESTEasy (JAX-RS)
- jQuery
- MySQL/HSQL
- Joda Time
- info.bliki

Próximos pasos
- Investigar Spring 3.0, con su soporte para REST y Bean Validation
- Completar el roadmap
- Actualizar este blog con las novedades que vayan ocurriendo en el desarrollo

Luego de este resumen, queda por decir que ya hay una versión descargable aquí (alpha), y que todos los comentarios y sugerencias que me hagáis llegar son más que bienvenidos.

viernes, febrero 29, 2008

Spring Integration


El nuevo miembro de la famila, Spring Integration, nos permite utilizar los famosos patrones de integración dentro de nuestra aplicación, sin alejarnos del modelo de programación de Spring.

Con una configuración tan sencilla como esta se logra leer ficheros de un directorio y procesarlos:


<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd">

<message-bus/>
<channel id="fileInputChannel" />
<annotation-driven/>

<context:component-scan base-package="com.habuma.si.example" />

<file-source directory="/Users/wallsc/sucker"
poll-period="1000" channel="fileInputChannel"/>
</beans:beans>
----
package com.habuma.si.example;
import org.apache.log4j.Logger;
import org.springframework.integration.annotation.Handler;
import org.springframework.integration.annotation.MessageEndpoint;

@MessageEndpoint(input = "fileInputChannel")
public class FileSucker {
private static final Logger LOGGER = Logger.getLogger(FileSucker.class);

@Handler
public void suckAFile(String fileContents) {
LOGGER.debug(fileContents);
}
}


Pero todo no acaba allí. Los mensajes de entrada (los ficheros en el ejemplo anterior) se pueden enrutar hacia otras salidas: otros ficheros, colas JMS, servicios web o cualquier sistema del que dispongamos (o desarrollemos) adaptadores.
Del mismo modo, los mensajes de entrada pueden provenir de diversas fuentes.

El ejemplo anterior está amablemente copiado de Spring Integration: Meet the channel adapters. En el mismo blog de Craig Walls encontraremos mas información y ejemplos de utilización.

miércoles, agosto 01, 2007

Atlassian compra Cenqua


Quién sabe que puede surgir de esta unión: Atlassian compra Cenqua
Con aplicaciones como FishEye, Crucible y Clover, la gama de productos de Atlassian es impresionante.

jueves, julio 12, 2007

Usando Confluence wiki para documentar en forma agil


Gestionar los requisitos de un proyecto no es tarea fácil. ¿Pero no es fácil por la complejidad de obtenerlos o la de documentarlos? Son dos caras de una misma moneda y en definitiva la buena gestión de ambas tareas hará que el proyecto salga adelante con éxito o no.
Para obtenerlos hay muchas metodologías, tantas como gustos. Quizás RUP sea la mas conocida en proyectos grandes y las variantes de Agile para proyectos mas pequeños. Pero cualquiera sea la metodología elegida del proyecto, y por lo tanto de como gestionar los requisitos, las herramientas para documentar son un poco mas que penosas.
El rey de las herramientas de requisitos, RequisitePro es una arcaica mezcla de MS Word y aplicación propietaria, junto con una herramienta de reportes (SODA) en MS Word. Nada ágil por cierto. Quizás con el nuevo desarrollo Jazz todo mejore para Rational, pero para eso faltan no menos de 2 años. ¿Que nos queda entonces para documentar los requisitos? Quizás Enterprise Arquitect sea bueno para esto, pero sigue siendo un cliente pesado.
¡Bienvenidos a la web entonces! Quizás el actual sistema de documentación distribuído mas popular de estos días sea la wikipedia, por que no documentar entonces un proyecto en un wiki? El primer pensamiento en contra es por lo "desestructurada" que suele ser la información guardada en un wiki, pero alguas herramientas nos permiten agregar control sobre los formularios de entrada y edición de datos. XWiki es un ejemplo de ello, pero el ejemplo que nos ocupa hoy está desarrollado con Confluence y una serie de plugins disponibles para esta plataforma.

Paso 1. Crear plantilla de doucmento Cliente.
primero crearemos una plantilla para contener los datos de los clientes, de los cuales, por simplicidad, solo guardaremos el nombre. Para ello deberemos ir a "Browse Space/Advanced/Templates/Add New Space Template" y agregar el siguiente código bajo el nombre "Client":



| Nombre: | {page-info:title} |

{report-table}
{content-reporter:spaces=@self|type=page|scope=@self > children}
{text-sort:content:title|order=ascending}
{text-filter:data:FormName|include=Project}
{content-reporter}
{report-column:title=Nombre}{report-info:content:title|link=true}{report-column}
{report-column:title=Project Leader}{report-info:data:ProjectLeader|link=true}{report-column}
{report-column:title=Start Date}{report-info:data:StartDate|link=true}{report-column}
{report-column:title=End Date}{report-info:data:EndDate|link=true}{report-column}
{report-column:title=Status}{report-info:data:Status|link=true}{report-column}
{report-table}
(+) {add-page:template=Project|live=true}Agregar proyecto{add-page}

{set-data:FormName|hidden=true}Client{set-data}


En esta plantilla vemos una tabla con el título del documento, un reporte de proyectos y un link para agregar un nuevo proyecto. Por último el tag {set-data}, del plugin Scaffolding, nos permite identificar este documento con la propiedad "FormName=Client", lo que nos facilitará los reportes de clientes que querramos hacer.

Paso 2. Crear documento raíz.
Luego necesitamos una página en algún lugar de nuestro wiki, que contenga el listado de clientes. Para el listado utilizaremos el plugin Reporting:



{report-table}
{content-reporter:spaces=@self|type=page|scope=Clients > children}
{text-sort:content:title|order=ascending}
{content-reporter}
{report-column:title=Nombre}{report-info:content:title|link=true}{report-column}
{report-table}

(+) {add-page:template=Client|live=true}Agregar cliente{add-page}


Al final de la página se puede ver el tag {add-page} para crear documentos de tipo cliente mediante una plantilla previamente creada. Luego de salvar la página con el título "Clients", podemos agregar un cliente:

Paso 3. Agregar un cliente.


Lo que nos presentará un documento a crear en base a la plantilla "Client":



Que luego de salvarlo tendrá la siguiente apariencia:



Paso 4. Crear plantilla de proyecto
Análogamente, para crear un proyecto deberemos crear primero una plantilla, con título "Project":

| Nombre: | {page-info:title} |
| Cliente: | {report-link:content:parent > content:url}{report-info:key=content:parent}{report-link} |
| Codigo JIRA | {text-data:JiraCode|content=text|width=100px}{text-data} |
| Project Leader: | {list-data:ProjectLeader|required=true}
{user-options:groups=confluence-users}
{list-data} |
| Team Members: | {list-data:TeamMembers|type=check|multiple=true}
{user-options:confluence-users}
{list-data} |
| Start Date: | {date-data:StartDate|format=dd-MMM-yyyy}today{date-data} |
| End Date: | {date-data:EndDate|format=dd-MMM-yyyy}{date-data} |
| Status: | {list-data:Status}
{list-option}Unstarted{list-option}
{list-option}In progress{list-option}
{list-option}Awaiting approval{list-option}
{list-option}Completed{list-option}
{list-data} |

[Requerimientos|Requirements]
[Casos de Uso|UseCases]
[Iteraciones|Iterations]
[Actas de Reunión|MeetingMinutes]
[Informes de estado de proyecto|StatusReports]

{set-data:FormName|hidden=true}Project{set-data}


Esta plantilla tiene los datos propios del proyecto, incluyendo su project leader, participantes, fecha de inicio y fin y estado. Al final también hay links a los documentos que harán de padres de los requisitos, las iteraciones y todos los demás documentos que nuestra metodología requiera.

Paso 5. Crear un proyecto.
Luego de creada la plantilla de proyecto podremos crear proyectos, mediante el link en el formulario correspondiente al cliente:



Lo cual nos preguntará el título del proyecto, y luego los datos que especifica la plantilla correspondiente:



Que una vez salvada tendrá la siguiente apariencia:



Paso 6. El listado de requierimientos.
Queda como ejercicio resolver que cada proyecto contenga su propia lista de requerimientos, ya que como ven en la plantilla de proyecto el link a los requerimientos es a una página genérica, llamada "Requirements": [Requerimientos|Requirements]. La mejor posibilidad es utilizar un "space" para cada proyecto.
Pero vamos con el diseño de la plantilla de la citada página padre "Requirements":

h3.Requerimientos Funcionales

{report-table}
{content-reporter:type=page|scope=@self > children}
{text-sort:data:Date|order=ascending}
{text-filter:data:FormName|include=Requirement}
{text-filter:data:Type|include=Funcional}
{content-reporter}
{report-column:title=Título}{report-info:content:title|link=true}{report-column}
{report-column:title=Estado}{report-info:data:State}{report-column}
{report-column:title=Prioridad}{report-info:data:Priority}{report-column}
{report-column:title=Dueño}{report-info:data:Owner}{report-column}
{report-column:title=Creado}{report-info:content:creation date}{report-column}
{report-column:title=Creado por}{report-info:content:creator}{report-column}
{report-table}

h3.Requerimientos No Funcionales
{report-table}
{content-reporter:type=page|scope=@self > children}
{text-sort:data:Date|order=ascending}
{text-filter:data:FormName|include=Requirement}
{text-filter:data:Type|include=No Funcional}
{content-reporter}
{report-column:title=Título}{report-info:content:title|link=true}{report-column}
{report-column:title=Estado}{report-info:data:State}{report-column}
{report-column:title=Prioridad}{report-info:data:Priority}{report-column}
{report-column:title=Dueño}{report-info:data:Owner}{report-column}
{report-column:title=Creado}{report-info:content:creation date}{report-column}
{report-column:title=Creado por}{report-info:content:creator}{report-column}
{report-table}

(+) {add-page:template=Requirement|live=true}Agregar requerimiento{add-page}


Se ha dividido la página en requerimientos funcionales y no funcionales, pero eso es a gusto del consumidor. Lo que hay que saber es que esta página lista los documentos de tipo "Requirement", creados a su vez con la plantilla "Requirement":

Paso 7. El formulario de requirimientos:

{section}
{column:width=300px}

*Detalles*
{table:width=300px}

{table-row}
{table-cell}
Tipo:
{table-cell}
{table-cell}
{list-data:Type|required=true}
{list-option}Funcional{list-option}
{list-option}No Funcional{list-option}
{list-data}
{table-cell}
{table-row}
{table-row}
{table-cell:width=100px}
Prioridad:
{table-cell}
{table-cell:width=200px}
{list-data:Priority}
{list-option}Sin asignar{list-option}
{list-option}Alta{list-option}
{list-option}Media{list-option}
{list-option}Baja{list-option}
{list-data}
{table-cell}
{table-row}
{table-row}
{table-cell:width=100px}
Estado:
{table-cell}
{table-cell:width=200px}
{list-data:State}
{list-option}Propuesto{list-option}
{list-option}Rechazado{list-option}
{list-option}Aprobado{list-option}
{list-option}Diferido{list-option}
{list-data}
{table-cell}
{table-row}
{table-row}
{table-cell}
Dueño:
{table-cell}
{table-cell}
{list-data:Owner|required=true}{user-options:groups=confluence-users}{list-data}
{table-cell}
{table-row}

{table}

{column}
{column:width=90%}
*Descripción*
{text-data:Description|type=area|content=wiki|width=100%|height=500px}
{column}

{section}

{set-data:FormName|hidden=true}Requirement{set-data}


El resultado es un formulario:



Y un listado de todos requerimientos:



Finalmente, queda por decir que el plugin para Graphviz nos permite hacer diagramas sin salir del wiki (ver UML class diagrams in Confluence using Graphviz and DOT), lo que aumenta varios grados la agilidad de la documentación y permite gráficos como este: