miércoles, 14 de septiembre de 2016

Tareas programadas en WSO2 ESB.

Aunque este es un tema simple de implementar desde versiones anteriores, puede ser que varios iniciados en estos temas al tratar de reproducir el post How to Schedule a Task Using WSO2 ESB 4.9.0 se encuentren con que no funciona del todo para WSO2 ESB 5.0.0.
El problema es basicamente de namespaces así que a continuación les dejo la secuencia con los ajustes realizados y el payload que se manda a través de la tarea.

La secuencia:

<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="iterateSequence">
   <iterate xmlns:ns="http://org.apache.synapse/xsd"
            xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"
            xmlns:ns3="http://org.apache.synapse/xsd"
            xmlns:m0="http://services.samples"
            preservePayload="true"
            attachPath="//m0:getQuote"
            expression="//m0:getQuote/m0:request">
      <target>
         <sequence>
            <call>
               <endpoint>
                  <address uri="http://localhost:9000/services/SimpleStockQuoteService"/>
               </endpoint>
            </call>
            <log level="custom">
               <property xmlns:ax21="http://services.samples/xsd"
                         name="Stock_Quote_on"
                         expression="//m0:getQuoteResponse/m0:return/ax21:lastTradeTimestamp/child::text()"/>
               <property xmlns:ax21="http://services.samples/xsd"
                         name="For_the_organization"
                         expression="//m0:getQuoteResponse/m0:return/ax21:name/child::text()"/>
               <property xmlns:ax21="http://services.samples/xsd"
                         name="Last_Value"
                         expression="//m0:getQuoteResponse/m0:return/ax21:last/child::text()"/>
            </log>
         </sequence>
      </target>
   </iterate>
</sequence>

El payload:

<m0:getQuote xmlns:m0="http://services.samples" xmlns:m1="http://services.samples/xsd">
 <m0:request>
 <m1:symbol>IBM</m1:symbol>
 </m0:request>
 <m0:request>
 <m1:symbol>MSTF</m1:symbol>
 </m0:request>
 <m0:request>
 <m1:symbol>WSO2</m1:symbol>
 </m0:request>
</m0:getQuote>

Los logs se pueden ver en la siguiente imagen:

image

martes, 13 de septiembre de 2016

Mantén actualizado tu WSO2.

wum

Entre versión y versión de cualquier producto de la suite de WSO2 aparecen soluciones a bugs, parches de seguridad y varias mejoras que puede ser que te las pierdas si no estás al pendiente todo el tiempo. Eso era antes. WSO2 como siempre sorprendiéndonos se apareció con una solución que de una manera muy fácil nos permite actualizar antes de empezar a usar determinada herramienta de su suite.

La herramienta WUM, que actualmente se encuentra en una versión beta, permite en pocos pasos mantenernos actualizados cuando queramos.

De momento está en pruebas para las siguiente herramientas y versiones, pero todos esperamos que en breve esté para más versiones.
  • WSO2 Enterprise Service Bus 5.0.0
  • WSO2 Enterprise Service Bus 4.9.x
  • WSO2 Enterprise Service Bus Analytics 5.0.0
  • WSO2 API Manager runtimes and analytics 2.0.0
NOTA: tener en cuenta que las soluciones actualizadas con esta herramienta no tiene la misma licencia open source, pues no está permitido su uso en producción. Tal parece que para eso habrá que pagar Sad smile

Pero veamos como usarla:
  • Paso 1: La descargas de aquí y la instalas.
  • Paso 2: Valida que la instalaste correctamente, ejecutando wum en una consola.
wum1

  • Paso 3: Ejecutamos wum init, si es la primera vez nos pedirá usuario y contraseña con los que nos autenticamos en el sitio de wso2.
image

  • Paso 4: Buscamos las herramientas que pueden ser actualizadas usando esta vía: wum search
image

  • Paso 5: Agregaremos ahora una herramienta que ya tenemos descargada:
image

  • Paso 6: Procedemos a instalar la última actualización.
image


Y así de una manera muy fácil podemos ir actualizando las herramientas que utilizamos en nuestro día a día.

Espero les sea de utilidad.

martes, 23 de agosto de 2016

Cuando falla un JSP en WSO2


A veces nos topamos con errores como el mencionado en esta pregunta de stackoverflow, donde la razón no es evidente.

Es una instalación fresca del WSO2 API Manager 1.10 y al tratar de ver los roles de un usuario salta el siguiente error:

2016-08-23 09:26:05,638 [-] [http-nio-443-exec-2] ERROR JspTilesRequestContext JSPException while including path '/user/edit-user-roles.jsp'. 
javax.servlet.jsp.JspException: ServletException while including page.

Caused by: org.apache.jasper.JasperException: java.lang.ClassNotFoundException: org.apache.jsp.user.edit_002duser_002droles_jsp

Caused by: java.lang.ClassNotFoundException: org.apache.jsp.user.edit_002duser_002droles_jsp
    at java.net.URLClassLoader.findClass(Unknown Source)
    at org.apache.jasper.servlet.JasperLoader.loadClass(JasperLoader.java:132)
    at org.apache.jasper.servlet.JasperLoader.loadClass(JasperLoader.java:63)
    at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:121)
    at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:172)

La solución es realmente fácil y es la siguiente:

Editar el fichero wrapper.conf que se encuentra en [WSO2_HOME]\bin\yajsw\ y agregar estas 2 líneas:

wrapper.java.additional.18 = -Dcomponents.repo=${carbon_home}\\repository\\components
wrapper.java.additional.26 = -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false

La explicación la pueden encontrar en la misma respuesta dada en stackoverflow.

jueves, 14 de abril de 2016

WSO2 ESB. Orquestando APIs con Iterate/Aggregate

Revisando en el sitio de stackoverflow me he topado con una duda ya respondida y aproveché para hacerle algunos cambios y adaptarla a un demo de uso de APIs en el WSO2 ESB que contempla lo siguiente:
  1. Se tienen 2 APIs que tienen cableadas las respuestas en JSON. Esos serían nuestros backend.
  2. Se tiene además otra API que debe implementar la invocación a la primera API que devuelve el listado de personas, luego debe iterar por cada persona para obtener un ID y con ese ID consultar a la segunda API. Dentro de cada iteración debe crear un nuevo mensaje que incluya tanto la respuesta de la primera API como de la segunda y retornarlo para que sea capturada en el flujo de salida. 
  3. La respuesta será entonces la unión de las respuestas de las 2 APIs generada a partir de la iteración y agregación de los mensajes finales.
Veamos como se hace:

Primero la imagen de la implementación en el WSO2 Developer Studio.
image

Lamentablemente WSO2 hizo su diseño gráfico hacia un costado y no hacia abajo como lo tiene Oracle así que es dificil fijar la imagen en una página, por lo que iremos presentando pedazos para que se vea bien.
Primero la definición de la API:

<?xml version="1.0" encoding="UTF-8"?>
<api context="/services/userdetails" name="UserDetailsAPI" xmlns="http://ws.apache.org/ns/synapse">
  <resource methods="GET" protocol="http">
    <inSequence>
    ....
    </inSequence>
    <outSequence>
    ....
    </outSequence>
    <faultSequence>
 ....
    </faultSequence> 
  </resource>
</api>  

Dentro de la secuencia de entrada las acciones son las siguientes:

Invocar al API que devuelve el listado de personas:
image 

<call description="invocacion al BE para obtener las personas">
<endpoint>
  <http method="get" trace="disable" uri-template="http://localhost:8281/services/users"/>
</endpoint>
</call>

Para ello usamos el mediador call e invocamos al API correspondiente usando el método GET.
La respuesta de esta invocación será la siguiente:
{
        "persons":
        [
            {
                "person":
                {
                    "Id": "1",
                    "givenName": "ajith",
                    "lastName": "vitharana",
                    "age": "25",
                    "contactInfos":
                    [
                        {
                            "InfoId": "1",
                            "department": "1",
                            "contactType": "email",
                            "value": "ajith@abc.org"
                        },
                        {
                            "InfoId": "2",
                            "department": "1",
                            "contactType": "mobile",
                            "value": "111111111"
                        },
                        {
                            "InfoId": "3",
                            "department": "1",
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ]
                }
            },
            {
                "person":
                {
                    "Id": "2",
                    "givenName": "shammi",
                    "lastName": "jagasingha",
                    "age": "30",
                    "contactInfos":
                    [
                        {
                            "InfoId": "1",
                            "department": "1",
                            "contactType": "email",
                            "value": "shammi@abc.org"
                        },
                        {
                            "InfoId": "2",
                            "department": "1",
                            "contactType": "mobile",
                            "value": "2222222222"
                        },
                        {
                            "InfoId": "3",
                            "department": "1",
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ]
                }
            }
        ]
    }


Iterar sobre el listado de personas devueltas:
image 

<iterate attachPath="//jsonObject/persons"
description="iterador sobre cada elemento person"
expression="//jsonObject/persons/person" id="it1"
preservePayload="true" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<target>
  <sequence>
 <property expression="$body/jsonObject/persons/person/Id"
   name="uri.var.Id" scope="default" type="STRING" xmlns:ns="http://org.apache.synapse/xsd"/>
 <property expression="$body//jsonObject//person"
   name="response1" scope="default" type="STRING" xmlns:ns="http://org.apache.synapse/xsd"/>
 <enrich>
   <source clone="true" xpath="$body//jsonObject//person"/>
   <target property="Person" type="property"/>
 </enrich>
 <call description="Se invoca al BE para obtener los roles usando el ID del elemento person correspondiente">
   <endpoint>
  <http method="get" trace="disable" uri-template="http://localhost:8281/services/roles/{uri.var.Id}"/>
   </endpoint>
 </call>
 <!--Aqui se enriquece el mensaje sobre el que se itera 
     para incluirle la respuesta de la call con los roles -->
 <enrich>
   <source clone="true" xpath="$body//jsonObject//roles"/>
   <target action="child" xpath="$ctx:Person"/>
 </enrich>
 <enrich>
   <source clone="true" xpath="$ctx:Person"/>
   <target type="body"/>
 </enrich>
  </sequence>
</target>
</iterate>

Los aspectos más interesantes dentro de cada iteración son:
  1. Se hace una copia del objeto person a una propiedad llamada Person. Esto con el objetivo de salvar ese objeto al realizar un call.
  2. Se invoca a la segunda API pasándole como parámetro el ID de la persona.
  3. De dicha invocación se obtiene un objeto usando el xpath $body//jsonObject//roles que es agregado como un hijo al contenido de la propiedad Person.
  4. Finalmente la propiedad Person es usada para reemplazar el contenido del body actual. Y con esto ya la respuesta está creada.

Lo último que se hace en la secuencia de entrada es mover el mensaje para la secuencia de salida usando el mediador LoopBack.

image

En la secuencia de salida los pasos son los siguientes:
image


Crear una propiedad que contenga el elemento padre sobre el cual se van a agregar las respuestas.

<property name="per" scope="default">
  <ns:Personas xmlns:ns="www.personas.org"/>
</property>

Luego hacemos uso del mediador Aggregate para agregar las respuestas que fueron direccionadas a la secuencia de salida.

<aggregate id="it1">
<completeCondition>
  <messageCount max="-1" min="-1"/>
</completeCondition>
<onComplete enclosingElementProperty="per" expression="$body/*[1]">
  <log>
 <property
   expression="json-eval($.Personas.person[0].givenName)" name="NOMBRE PERSONA: "/>
  </log>
  <send>
</onComplete>
</aggregate>

Dentro del onComplete ponemos un ejemplo de como se captura un valor del JSON, aunque no es necesario y luego incluimos el mediador send para enviar el mensaje de vuelta al cliente.
Y eso es todo. La respuesta final del API es la siguiente:
{
        "Personas":
        {
            "person":
            [
                {
                    "Id": 2,
                    "givenName": "shammi",
                    "lastName": "jagasingha",
                    "age": 30,
                    "contactInfos":
                    [
                        {
                            "InfoId": 1,
                            "department": 1,
                            "contactType": "email",
                            "value": "shammi@abc.org"
                        },
                        {
                            "InfoId": 2,
                            "department": 1,
                            "contactType": "mobile",
                            "value": 2222222222
                        },
                        {
                            "InfoId": 3,
                            "department": 1,
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ],
                    "roles":
                    [
                        {
                            "personRoleId": 1,
                            "personKey": 2,
                            "role": "Manager"
                        },
                        {
                            "personRoleId": 2,
                            "personKey": 2,
                            "role": "QA"
                        }
                    ]
                },
                {
                    "Id": 1,
                    "givenName": "ajith",
                    "lastName": "vitharana",
                    "age": 25,
                    "contactInfos":
                    [
                        {
                            "InfoId": 1,
                            "department": 1,
                            "contactType": "email",
                            "value": "ajith@abc.org"
                        },
                        {
                            "InfoId": 2,
                            "department": 1,
                            "contactType": "mobile",
                            "value": 111111111
                        },
                        {
                            "InfoId": 3,
                            "department": 1,
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ],
                    "roles":
                    [
                        {
                            "personRoleId": 1,
                            "personKey": 1,
                            "role": "Developer"
                        },
                        {
                            "personRoleId": 2,
                            "personKey": 1,
                            "role": "Engineer"
                        }
                    ]
                }
            ]
        }
    }
Como pueden ver se ha realizado sin mucha complicación una orquestación simple dentro del WSO2 ESB. Les dejo el código fuente que lo pueden importar en el WSO2 Developer Studio.

viernes, 22 de enero de 2016

WSO2 Data Service Server NPE con REST y JSON.

image

Haciendo algunas pruebas para un servicio de datos a manera de demo, estuvimos revisando sus facilidades para exponer funcionalidades de manera RESTful. Una vez configurada una operación de insersión que recibía varios datos y enviar el JSON que imaginamos debía enviarse nos topamos con un error NPE.

Probamos con POX desde el RESTClient en firefox y sin problema.

image

Pero con JSON, convirtiendo el XML a JSON usando http://codebeautify.org/xmltojson no funcionaba.
image


En la consola se muestra el siguiente error:
image


Hicimos el cambio que pedía en el JSON:
image

Y sin problema. Faltaba incluirle al inicio del elemento raiz el string   _post.

lunes, 11 de enero de 2016

WSO2 DAS: fallo de autenticación en admin-dashboard

image

He estado trabajando en un escenario de integración que incluye las siguientes herramientas:
  • WSO2 Identity Server 5.0.0 como key manager y gestión de usuarios.
  • WSO2 API Manager 1.9.1 para la gestión de las APIs.

La integración se hace bastante rápida y existe un manual bien detallado al respecto en la documentación oficial.

Para ver el uso de las APIs quise integrar además el WSO2 Dashboard Application Server 3.0.0 a esta fiesta de herramientas así que me fui al manual del WSO2 API Manager y seguí los pasos descritos.

Al intentar acceder al Admin Dashboard con las credenciales por defecto admin/admin me topo que no me permite autenticarme. Pruebo autenticarme en el WSO2 IS y funciona, en el DAS también funciona, en el Publisher y en el Store pues también funciona, así que ya es algo extraño.

Activo el modo debug y a bucear en los logs y esto es lo que me encuentro.

Invocación del WSO2 DAS al WSO2 IS para autenticar al usuario vía servicios web:
Request:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
               xmlns:aut="http://authentication.services.core.carbon.wso2.org">
    <soap:Header/>
    <soap:Body>
        <aut:login>
            <aut:username>admin</aut:username>
            <aut:password>admin</aut:password>
            <aut:remoteAddress>localhost</aut:remoteAddress>
        </aut:login>
    </soap:Body>
</soap:Envelope>
Response:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <ns:loginResponse xmlns:ns="http://authentication.services.core.carbon.wso2.org">
            <ns:return>true</ns:return>
        </ns:loginResponse>
    </soapenv:Body>
</soapenv:Envelope>


Invocación para obtener el listado de roles del usuario y validar si se le permite el acceso:
Request:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <ser:getRoleListOfUser xmlns:ser="http://service.ws.um.carbon.wso2.org">
            <ser:userName>admin</ser:userName>
        </ser:getRoleListOfUser>
    </soapenv:Body>
</soapenv:Envelope>
Response:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <ns:getRoleListOfUserResponse xmlns:ns="http://service.ws.um.carbon.wso2.org"
                >
            <ns:return>Internal/admin</ns:return>
            <ns:return>Internal/subscriber</ns:return>
            <ns:return>Internal/WSO2.ORG_admin_DefaultApplication_PRODUCTION</ns:return>
            <ns:return>Internal/WSO2.ORG_admin_DefaultApplication_SANDBOX</ns:return>
            <ns:return>Internal/everyone</ns:return>
        </ns:getRoleListOfUserResponse>
    </soapenv:Body>
</soapenv:Envelope>

Como pueden apreciar el listado de roles que se devuelve es el siguiente:
  • Internal/admin
  • Internal/subscriber
  • Internal/WSO2.ORG_admin_DefaultApplication_PRODUCTION
  • Internal/WSO2.ORG_admin_DefaultApplication_SANDBOX
  • Internal/everyone

Cuando  reviso el fichero donde se definen los roles con permito a admin-dashboard, el fichero site.json  aparece lo siguiente:
Roles permitidos:
  • admin
  • subscriber
{
    "theme" : {
        "base" : "default",
        "subtheme" : "modern"
    },
    "context" : "/admin-dashboard",
    "request_url":"READ_FROM_REQUEST",
    "tasksPerPage": 10,
    "allowedRole":"admin",
    "allowedRoles":"admin,subscriber",
    "workflows":{
     "applicationWorkFlowServerURL": "https://localhost:9446/services/",
     "subscriptionWorkFlowServerURL": "https://localhost:9446/services/",
     "signupWorkFlowServerURL": "https://localhost:9446/services/",
     "appRegistrationWorkFlowServerURL": "https://localhost:9446/services/"

    },
    "ssoConfiguration" : {
        "enabled" : "false",
        "issuer" : "API_WORKFLOW_ADMIN",
        "identityProviderURL" : "https://localhost:9448/samlsso",
        "keyStorePassword" : "",
        "identityAlias" : "",
        "responseSigningEnabled":"true",
        "keyStoreName" :""
    }
}
Tuve que adicionar el rol Internal/admin  al listado de allowedRoles para que mediera acceso a la aplicación.

De esa manera ya tuve acceso y pude terminar la integración con el DAS. En próximas entradas estaré posteando la integración entre estas 3 herramientas.

Espero les sea de utilidad.

miércoles, 6 de enero de 2016

WSO2 SSO: IS + DAS.

Hace poco un cliente me pedía revisara su configuración de SSO brindada por el WSO2 Identity Server 5.1.0 pues no le funcionaba al usar el WSO2 DAS 3.0.0.

El error se puede apreciar en la siguiente imagen:
image_thumb1
En mi ambiente los offset de las herramientas son los siguientes:
  • WSO2 DAS: 0
  • WSO2 IS: 5
La configuración del WSO2 DAS en el fichero authenticators.xml relacionada con el SSO es la siguiente:

<Authenticator name="SAML2SSOAuthenticator" disabled="false">
 <Priority>10</Priority>
 <Config>
   <Parameter name="LoginPage">/carbon/admin/login.jsp</Parameter>
   <Parameter name="ServiceProviderID">carbonServerDAS</Parameter>
   <Parameter name="IdentityProviderSSOServiceURL">https://localhost:9448/samlsso</Parameter>
   <Parameter name="NameIDPolicyFormat">urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</Parameter>
   <Parameter name="AssertionConsumerServiceURL">https://localhost:9443/acs</Parameter>

          <!-- <Parameter name="IdPCertAlias">wso2carbon</Parameter> -->
   <!-- <Parameter name="ResponseSignatureValidationEnabled">false</Parameter> -->
   <!-- <Parameter name="LoginAttributeName"></Parameter> -->
   <!-- <Parameter name="RoleClaimAttribute"></Parameter> -->
   <!-- <Parameter name="AttributeValueSeparator">,</Parameter> -->

          <!-- <Parameter name="JITUserProvisioning">true</Parameter> -->
   <!-- <Parameter name="ProvisioningDefaultUserstore">PRIMARY</Parameter> -->
   <!-- <Parameter name="ProvisioningDefaultRole">admin</Parameter> -->
   <!-- <Parameter name="IsSuperAdminRoleRequired">true</Parameter> -->
        </Config>

<!-- If this authenticator should skip any URI from authentication, specify it under "SkipAuthentication"
<SkipAuthentication>
  <UrlContains></UrlContains>
 </SkipAuthentication> -->

<!-- If this authenticator should skip any URI from session validation, specify it under "SkipAuthentication
 <SkipSessionValidation>
  <UrlContains></UrlContains>
 </SkipSessionValidation> -->
</Authenticator>

Como ven se define el service provider ID, se ajusta el puerto del WSO2 IS a 9448 y el puerto del DAS a 9443 y eso es todo.

En el WSO2 IS, en mi caso estaba usando la versión 5.0.0, no era la misma del cliente, y los ajustes fueron los siguientes:

Me cree un service provider: carbonServerDAS

image_thumb4 

image_thumb6

Y su configuración fue la siguiente:

image_thumb9

Al probar el SSO me funcionaba perfectamente, mientras que al cliente no, así que asumí era un problema de la versión del WSO2 IS y su configuración.

Levanté el WSO2 IS 5.1.0 creando un service provider tal como se muestra a continuación:

image_thumb11
image_thumb13

  Las opciones marcadas fueron las siguientes junto con la definición del endpoint del service provider:

image_thumb16

Luego de esta configuración al tratar de autenticarme en el WSO2 DAS pude reproducir el error.
La solución dada la pueden ver en esta pregunta de stackoverflow.

Espero les sea de utilidad.