viernes, 7 de marzo de 2014

Como ya habíamos visto en la entrada anterior, el WSO2 Identity Server tiene entre sus múltiples funcionalidades relacionadas con la seguridad la de actuar como un Identity Provider o IDP.
Se había dejado como pendiente la captura de los atributos contenidos en el almacén de usuarios que estuviera usando el  WSO2 Identity Server y eso será lo que haremos en esta entrada.

El escenario:
  • Digamos que tenemos inicialmente el WSO2 configurado para que use su BD interna de los usuarios, pero queremos que también use un LDAP como almacén de usuarios, pueden usar este enlace para configurar su LDAP y de esta manera se podrán autenticar en las aplicaciones usando los usuarios del LDAP.
  • El Identity server tiene un conjunto de usuarios, los cuales tienen atributos definidos a través de la funcionalidad “My Profile” y el LDAP contiene información de los usuarios la cual depende de la implementación de cada organización y la información que se desee guardar en el LDAP.
  • Se desea que las aplicaciones que participen en el escenario de Single Sign On usando el WSO2 Identity Server puedan obtener los atributos requeridos del usuario autenticado en ellas, bien sea desde el almacén por defecto de la herramienta o bien desde el LDAP.

Veamos cómo se hace:

Lo primero es ir al WSO2 Identity Server y editar la configuración del SSO para la aplicación que requiere dichos atributos. Noten el valor del “Consumer Index” pues luego hará falta.


Cuando entramos en  su configuración debemos marcar la opción que dice “Enable Attribute Profile”:






Y en la parte de Claim seleccionamos aquellas que se correspondan con los atributos que sabemos están en el LDAP y le damos al botón “Add Claim” y así veremos cómo se van agregando los atributos.

NOTA: El WSO2 Identity Server también tiene la posibilidad de gestionar estos atributos, lo cual es muy útil para saber que Claims se corresponden con qué atributos en el LDAP o para agregar nuevas en caso de ser requerido. En el caso del rol y de la URL tuve que realizar ajustes pues no se correspondían con el esquema de atributos soportados por el LDAP.

Cuando nos volvamos a autenticar en la aplicación que usamos de ejemplo veremos entonces los valores de  los atributos.

Mi primera prueba fue con un usuario interno, en este caso el admin de la herramienta y tuve que llenar en “My Profile” los datos que se querían mostrar.



Luego al volver a autenticarme con las credenciales del LDAP se cargaron mis atributos sin problema desde el LDAP. Aquí aconsejo revisar el LDAP de cada cual y ver los atributos que tienen sus usuarios, luego revisar los claims que tengan y ver si tienen para obtener esos atributos. En caso de que no los tengan bien pueden modificar los existentes o agregar nuevos.

Para capturar estos datos se usó la siguiente página JSP:


En el caso de la aplicación PHP el escenario es bastante similar. Hay que editar la configuración del SSO para dicha aplicación y seleccionar los atributos que queremos obtener.

Al revisar el código de la aplicación demo vemos que también tiene implementada la captura de los atributos, como se puede ver en la siguiente imagen:




Pero al probarla no se mostraba nada. Así que volví a repetir el mismo procedimiento de la entrada anterior y comparar los mensajes SAML intercambiados, y resulta que mientras la aplicación JAVA está enviando un atributo AttributeConsumingServiceIndex con el identificador que se muestra en su configuración de SSO en el WSO2 Identity Server, la aplicación web en PHP no lo hacía, por lo que tuve que modificar el código.

En el fichero Settings.php agregué un nuevo atributo AttributeConsumingServiceIndex a la clase OneLogin_Saml_Settings tal y como se muestra en esta imagen:




Luego  en el fichero settings.php le seteo el valor correspondiente:


Y por último en el fichero AuthRequest.php modifico la estructura del mensaje SAML a enviar para que incluya este atributo:



        $request = <<<AUTHNREQUEST
<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="$id"
    Version="2.0"
    IssueInstant="$issueInstant"
 Destination="{$this->_settings->idpSingleSignOnUrl}"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    AssertionConsumerServiceURL="{$this->_settings->spReturnUrl}"
 AttributeConsumingServiceIndex="{$this->_settings->AttributeConsumingServiceIndex}">
    <saml:Issuer>{$this->_settings->spIssuer}</saml:Issuer>
    <samlp:NameIDPolicy
        Format="{$this->_settings->requestedNameIdFormat}"
        AllowCreate="true"></samlp:NameIDPolicy>
    <samlp:RequestedAuthnContext Comparison="exact">
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
AUTHNREQUEST;


De esta manera al tratar de acceder a la aplicación php vemos como el mensaje llega al WSO2 Identity Server con el atributo requerido y también podemos ver como el mensaje SAML de respuesta contiene los valores de los atributos solicitados, pero llegado este punto tampoco se muestran en la página web. De nuevo a revisar código PHP  :-(


Aquí les dejo un fragmento del mensaje obtenido desde el WSO2 Identity Server, es solo un fragmento pues el mensaje es bastante grande:
<saml2:AttributeStatement>
<saml2:Attribute Name="http://wso2.org/claims/country"><saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Cuba</saml2:AttributeValue></saml2:Attribute>
<saml2:Attribute Name="http://wso2.org/claims/mobile"><saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">+5353722213</saml2:AttributeValue></saml2:Attribute>
<saml2:Attribute Name="http://wso2.org/claims/role"><saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">admin,Internal/everyone</saml2:AttributeValue></saml2:Attribute>
<saml2:Attribute Name="http://wso2.org/claims/url"><saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">http://desarrollosoa.blogspot.com/</saml2:AttributeValue></saml2:Attribute>
</saml2:AttributeStatement>



Luego de revisar el código php noté que había un problema con el fichero Response.php que contiene la clase OneLogin_Saml_Response y es que esta clase no capturaba los datos de los mensajes SAML2, solo los SAML, así que la modifiqué de esta manera.

<?php

/**
 * Parse the SAML response and maintain the XML for it.
 */
class OneLogin_Saml_Response
{
    /**
     * @var OneLogin_Saml_Settings
     */
    protected $_settings;

    /**
     * The decoded, unprocessed XML assertion provided to the constructor.
     * @var string
     */
    public $assertion;

    /**
     * A DOMDocument class loaded from the $assertion.
     * @var DomDocument
     */
    public $document;

    /**
     * Construct the response object.
     *
     * @param OneLogin_Saml_Settings $settings Settings containing the necessary X.509 certificate to decode the XML.
     * @param string $assertion A UUEncoded SAML assertion from the IdP.
     */
    public function __construct(OneLogin_Saml_Settings $settings, $assertion)
    {
        $this->_settings = $settings;
        $this->assertion = base64_decode($assertion);
        $this->document = new DOMDocument();
        $this->document->loadXML($this->assertion);
    }

    /**
     * Determine if the SAML Response is valid using the certificate.
     *
     * @throws Exception
     * @return bool Validate the document
     */
    public function isValid()
    {
        $xmlSec = new OneLogin_Saml_XmlSec($this->_settings, $this);
        return $xmlSec->isValid();
    }

    /**
     * Get the NameID provided by the SAML response from the IdP.
     */
    public function getNameId()
    {
        $entries = $this->_queryAssertion('/saml2:Subject/saml2:NameID');
        return $entries->item(0)->nodeValue;
    }

    /**
     * Get the SessionNotOnOrAfter attribute, as Unix Epoc, from the
     * AuthnStatement element.
     * Using this attribute, the IdP suggests the local session expiration
     * time.
     * 
     * @return The SessionNotOnOrAfter as unix epoc or NULL if not present
     */
    public function getSessionNotOnOrAfter()
    {
        $entries = $this->_queryAssertion('/saml2:AuthnStatement[@SessionNotOnOrAfter]');
        if ($entries->length == 0) {
            return NULL;
        }
        $notOnOrAfter = $entries->item(0)->getAttribute('SessionNotOnOrAfter');
        return strtotime($notOnOrAfter);
    }

    public function getAttributes()
    {
        $entries = $this->_queryAssertion('/saml2:AttributeStatement/saml2:Attribute');

        $attributes = array();
        /** @var $entry DOMNode */
        foreach ($entries as $entry) {
            $attributeName = $entry->attributes->getNamedItem('Name')->nodeValue;

            $attributeValues = array();
            foreach ($entry->childNodes as $childNode) {
                if ($childNode->nodeType == XML_ELEMENT_NODE && $childNode->tagName === 'saml2:AttributeValue'){
                    $attributeValues[] = $childNode->nodeValue;
                }
            }

            $attributes[$attributeName] = $attributeValues;
        }
        return $attributes;
    }

    /**
     * @param string $assertionXpath
     * @return DOMNodeList
     */
    protected function _queryAssertion($assertionXpath)
    {
        $xpath = new DOMXPath($this->document);
        $xpath->registerNamespace('samlp'   , 'urn:oasis:names:tc:SAML:2.0:protocol');
        $xpath->registerNamespace('saml'    , 'urn:oasis:names:tc:SAML:2.0:assertion');
        $xpath->registerNamespace('ds'      , 'http://www.w3.org/2000/09/xmldsig#');
  $xpath->registerNamespace('saml2'   , 'urn:oasis:names:tc:SAML:2.0:assertion');

        $signatureQuery = '/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo/ds:Reference';
        $assertionReferenceNode = $xpath->query($signatureQuery)->item(0);
        if (!$assertionReferenceNode) {
            throw new Exception('Unable to query assertion, no Signature Reference found?');
        }
        $id = substr($assertionReferenceNode->attributes->getNamedItem('URI')->nodeValue, 1);

        $nameQuery = "/samlp:Response/saml:Assertion[@ID='$id']" . $assertionXpath;
        return $xpath->query($nameQuery);
    }
}


Así fue entonces como desde la aplicación web en PHP pude mostrar los valores de los atributos.



Es válido aclarar que este problema no es generado por el WSO2 Identity Server, sino por el código PHP empleado en este ejemplo.

Espero les sea de utilidad.

0 comentarios:

Publicar un comentario