Pages

Friday, February 27, 2015

How to develop SAML-ECP Custom Client

Pre-Requisite:

1. Installed and Configured Shibboleth IDP 3.0
2. Installed and Configured Shibboleth native SP 2.5.3 with ECP Enabled

Environment:

1. JDK 1.7
2. Eclipse
3. Gradle 1.5
4. Open SAML2.6.4
5. Apache Http Client 4.4
6. SL4J 1.7.10


Developing SAMl-ECP client

The following tasks needs to be performed to build the ECP clients:

1. Sending Request from ECP  to SP 
2. Sending Request from ECP  to IDP
3. Sending Request from ECP  to SP  


1. Sending Request from ECP  to SP

 Accessing the Secure URL through Enhanced Client Proxy(ECP) using the Get Request. In get request you need to add two header fields in the request:

1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'

Please refer example code : EcpRequestInterceptor  for adding the header variable in the request.

 1.1 Send the Get Request Secure URL from ECP to SP with PAOS Header

   The PAOS header variables are added in the EcpRequestInterceptor class.
     
     public Envelope accessProtectedPage()
    {
        Envelope envelope=null;
        HttpGet request= new HttpGet(ecp.getProtectedUrl());
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(AuthnRequest.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));  
                        }
                        else
                        {
                            throw new RuntimeException("Invalid ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
    }


  1.2 SP will send the SOAP Response to ECP with PAOS Request  in SOAP Headers and Authentication Request Statement in Soap Body.

The Ecp response from the SP is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Header>
        <paos:Request xmlns:paos="urn:liberty:paos:2003-08" S:actor="http://schemas.xmlsoap.org/soap/actor/next" S:mustUnderstand="1"
        responseConsumerURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP" service="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"/>
        <ecp:Request xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" IsPassive="0" S:actor="http://schemas.xmlsoap.org/soap/actor/next" S:mustUnderstand="1">
        <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
            <samlp:IDPList xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
                <samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/>
            </samlp:IDPList>
        </ecp:Request>
        <ecp:RelayState xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" S:actor="http://schemas.xmlsoap.org/soap/actor/next"
         S:mustUnderstand="1">ss:mem:ec7e3978ddd1958ca9efb27be7c9445017daf9068b62c647cc398a4dfc73573c</ecp:RelayState>
    </S:Header>
    <S:Body>
        <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_c06ac9aa1e8e172b920bd32435547b05"    
            IssueInstant="2015-02-27T00:04:38Z"
            ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
            Version="2.0">
                <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
                <samlp:NameIDPolicy AllowCreate="1"/>
                <samlp:Scoping>
                    <samlp:IDPList>
                        <samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/>
                    </samlp:IDPList>
                </samlp:Scoping>
        </samlp:AuthnRequest>
    </S:Body>
</S:Envelope>
     

2. Sending the Authentication Request from ECP to IDP with PAOS and Authorization Headers

 2.1 Detach the SP Soap Body and Create a new Soap Envelop and set the detached soap body to new envelop

You need to remove the SOAP headers from the SP Initial Secure URL response or You can copy the authentication request from the  SP Initial Secure URL response and build a new Soap Envelope. I have detached (copy) from the initial soap response and building the new soap request and sample code is given below:

public Envelope buildIdpAuthRequest(Envelope envelope)
    {
       
        Envelope newAuthRequest = new EnvelopeBuilder().buildObject();
       
        Body authBody= envelope.getBody();
        authBody.detach();
        newAuthRequest.setBody(authBody);
       
        return newAuthRequest;
       
    }

Sample Soap XML : 

<?xml version="1.0" encoding="UTF-8"?>
<soap11:Envelope xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
        <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_b2c92bc8216ff317a5c42d797a7cd8ca" IssueInstant="2015-02-27T18:49:37Z"
            ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
            Version="2.0">
            <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
            <samlp:NameIDPolicy AllowCreate="1"/>
                <samlp:Scoping>
                    <samlp:IDPList><samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/></samlp:IDPList>
                </samlp:Scoping>
        </samlp:AuthnRequest>
    </S:Body>
</soap11:Envelope>

2.2 Send the Authentication Request to IDP with PAOS and Authorization Headers

You need to add the following header variables into the post request because these variables are required  for IDP ECP profile:

1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'
3. Authorization: Basic  + User Name and password encode with base 64.

The sample code is given below:

    public Envelope sendAuthenticationToIdp(String authRequestXml)
    {
        logger.info("Authentication Request from ECP to IDP :" + authRequestXml);
        Envelope envelope=null;
        HttpPost request= new HttpPost(ecp.getIdpUrl());
        request.addHeader(HttpHeaders.AUTHORIZATION, "Basic "+ Base64.encodeBytes((ecp.getUserName()+":"+ecp.getPassword()).getBytes()));
        try
        {
            request.setEntity(new StringEntity(authRequestXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));
                            org.opensaml.saml2.core.Response samlResp=(org.opensaml.saml2.core.Response)envelope.getBody().getUnknownXMLObjects(org.opensaml.saml2.core.Response.DEFAULT_ELEMENT_NAME).get(0);
                            if(samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI))
                            {
                                System.out.println("Status  :"+samlResp.getStatus().getStatusCode().getValue());   
                            }
                            else
                            {
                               
                                StatusDetail detail= (StatusDetail)samlResp.getStatus().getStatusDetail().getUnknownXMLObjects(StatusDetail.DEFAULT_ELEMENT_NAME).get(0);
                                throw new RuntimeException("SAML Authentication Failed "+samlResp.getStatus().getStatusCode().getValue() + " - ");
                            }
                           
                        }
                        else
                        {
                            throw new RuntimeException("Invalid IDP ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }


2.3 IDP will send the SOAP response to ECP with SAML Response in soap body and ECP response in SOAP Headers.

The sample Authentication Successful response from IDP as follows:
  
<?xml version="1.0" encoding="UTF-8"?>
<soap11:Envelope xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
    <soap11:Header>
        <ecp:Response xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            soap11:actor="http://schemas.xmlsoap.org/soap/actor/next" soap11:mustUnderstand="1"/>
        <samlec:GeneratedKey xmlns:samlec="urn:ietf:params:xml:ns:samlec"
            soap11:actor="http://schemas.xmlsoap.org/soap/actor/next">Saml keys</samlec:GeneratedKey>
    </soap11:Header>
    <soap11:Body>
        <saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
            Destination="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_c860f132c4115581218971d92a52f500"
            InResponseTo="_b2c92bc8216ff317a5c42d797a7cd8ca"
            IssueInstant="2015-02-27T18:49:37.867Z" Version="2.0">
                <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-idp.example.edu/idp/shibboleth</saml2:Issuer>
                <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                    <ds:SignedInfo>
                        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"/>
                        <ds:Reference URI="#_c860f132c4115581218971d92a52f500">
                            <ds:Transforms>
                                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                                <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                            </ds:Transforms>
                            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
                            <ds:DigestValue>Digest Base 64 encoded value </ds:DigestValue>
                        </ds:Reference>
                    </ds:SignedInfo>
                    <ds:SignatureValue>Signature Value Base 64 encoded</ds:SignatureValue>
                    <ds:KeyInfo>
                        <ds:X509Data>
                            <ds:X509Certificate>Certificate</ds:X509Certificate>
                        </ds:X509Data>
                    </ds:KeyInfo>
                </ds:Signature>
                <saml2p:Status>
                    <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
                </saml2p:Status>
                <saml2:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
                    <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
                        Id="_37f9853b6561b3722b3691392ace994b"
                        Type="http://www.w3.org/2001/04/xmlenc#Element">
                            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/>
                            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                                <xenc:EncryptedKey Id="_7dc4db379206ea3e491f5e6e15bbf6fb"
                                    Recipient="https://shib-sp.example.edu/shibboleth">
                                        <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep">
                                            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                                            <xenc11:MGF xmlns:xenc11="http://www.w3.org/2009/xmlenc11#"
                                                Algorithm="http://www.w3.org/2009/xmlenc11#mgf1sha1"/>
                                        </xenc:EncryptionMethod>
                                        <ds:KeyInfo>
                                            <ds:X509Data>
                                                <ds:X509Certificate>Certificate</ds:X509Certificate>
                                            </ds:X509Data>
                                        </ds:KeyInfo>
                                        <xenc:CipherData>
                                            <xenc:CipherValue>Cipher Value</xenc:CipherValue>
                                        </xenc:CipherData>
                                </xenc:EncryptedKey>
                            </ds:KeyInfo>
                            <xenc:CipherData>
                                <xenc:CipherValue>Certificate</xenc:CipherValue>
                            </xenc:CipherData>
                    </xenc:EncryptedData>
                </saml2:EncryptedAssertion>
        </saml2p:Response>
    </soap11:Body>
</soap11:Envelope>

3. Sending the IDP Authentication Response from ECP to SP to access the Secure URL Access. 

3.1 Add Initial Secure URL Access Relay State in IDP Soap Response Headers

The RelayState information is extracted from the initial Sp Secure Access Response from the SP Soap Envelop. This information is required in the soap header because, the SP going to accept the idp authentication SAML response and also make a decision to allow or denied requested secure page.

            RelayState relayState= (RelayState)secureResourceResponse.getHeader().getUnknownXMLObjects(RelayState.DEFAULT_ELEMENT_NAME).get(0);
            relayState.detach();

            Header header = new HeaderBuilder().buildObject();
            header.getUnknownXMLObjects().clear();
            header.getUnknownXMLObjects().add(relayState);
            idpAuthResponse.setHeader(header);



3.2 Extract the AssertionConsumerService URL from the IDP Soap Response

This assertionConsumerUrl parameter extracted from the Soap Header from the IDP Authentication soap response. This assertionConsumerUrl is being used to post the IDP Authentication response with Relay State header from ECP to Service provider post request.

String assertionConsumerUrl=((Response)idpAuthResponse.getHeader().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME).get(0)).getAssertionConsumerServiceURL();

3.3 Send IDP Authentication response to Service Provider with PAOS and Conetent-Type headers.

Sample Code:


    public Envelope sendAuthResponseToSp(String authResponseXml,String consumeServiceUrl)
    {
        Envelope envelope=null;
        HttpPost request= new HttpPost(consumeServiceUrl);
        request.addHeader(HttpHeaders.CONTENT_TYPE, EcpFlowConstants.PAOS_CONTENT_TYPE);
       
        try
        {
            request.setEntity(new StringEntity(authResponseXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    entity.writeTo(System.out);
                    logger.info("Cookies   "+store.getCookies());
                    EntityUtils.consume(entity);
                   
                   
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }



Example Code:


I have classified the example code 4 classes. They are

1. ECP Object

   This object is being used to capture the ECP Endpoint IDP Url, Protected Resource from the SP, User and Password to authenticate the IDP Server

2.  EcpRequestInterceptor

    This object is being used to add the ECP headers in each request.

3. EcpClientFlow

    This object being used to send the GET, POST request to IDP and SP using apache HttpClient.

4. EcpClient

This object is being used to Initialize the SAML Boot strap, and Interacting with EcpClientFlow object invoking the various request such as Accessing the Protected Page, Authenticating User from the IDP, and Finally Send the Authenticated IDP Response to Service Provider.


The Sample Code is given below:





package edu.example.ecp.client;

public class Ecp
{
    private String idpUrl;
    private String protectedUrl;
    private String userName;
    private String password;
    public String getIdpUrl() {
        return idpUrl;
    }
    public void setIdpUrl(String idpUrl) {
        this.idpUrl = idpUrl;
    }
    public String getProtectedUrl() {
        return protectedUrl;
    }
    public void setProtectedUrl(String protectedUrl) {
        this.protectedUrl = protectedUrl;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
   
   
}
   


package edu.example.ecp.client;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.ProxySelector;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.apache.http.util.EntityUtils;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml1.core.AuthenticationStatement;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.StatusDetail;
import org.opensaml.saml2.ecp.Response;
import org.opensaml.ws.soap.soap11.Body;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.ws.soap.soap11.impl.EnvelopeBuilder;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


package edu.example.ecp.client;

public class EcpClientFlow
{
    private Ecp ecp;
    private BasicParserPool pool;
    private CloseableHttpClient client;
    private CookieStore store;
    private Logger logger= LoggerFactory.getLogger(EcpClientFlow.class);
   
    public EcpClientFlow(Ecp ecp)
    {
        this.ecp=ecp;
       
        //Creating the Parse Object
       
        pool= new BasicParserPool();
        pool.setNamespaceAware(true);
       
        //Creating the Pooled Http Connection Manager
        PoolingHttpClientConnectionManager manager= new PoolingHttpClientConnectionManager();
        manager.setMaxTotal(20);
        manager.setDefaultMaxPerRoute(10);
       
        //Creating the Cookie Store store the Cookies
        store= new BasicCookieStore();
        //Creating the Browser Compatible Request Cookie Object
        RequestConfig config= RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).build();
       
        // Building the Client Builder
       
        HttpClientBuilder clientBuildert= HttpClients.custom().setConnectionManager(manager)
                .setDefaultCookieStore(store)
                // Add the ECP Header every request
                .addInterceptorFirst(new EcpRequestInterceptor())
                // Redirecting the Request
                .setRedirectStrategy(new LaxRedirectStrategy())
                .setDefaultRequestConfig(config);
        client=clientBuildert.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())).build();
    }
   
    public Envelope accessProtectedPage()
    {
        Envelope envelope=null;
        HttpGet request= new HttpGet(ecp.getProtectedUrl());
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(AuthnRequest.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));   
                        }
                        else
                        {
                            throw new RuntimeException("Invalid ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
    }
   
    public Envelope sendAuthenticationToIdp(String authRequestXml)
    {
        logger.info("Authentication Request from ECP to IDP :" + authRequestXml);
        Envelope envelope=null;
        HttpPost request= new HttpPost(ecp.getIdpUrl());
        request.addHeader(HttpHeaders.AUTHORIZATION, "Basic "+ Base64.encodeBytes((ecp.getUserName()+":"+ecp.getPassword()).getBytes()));
        try
        {
            request.setEntity(new StringEntity(authRequestXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));
                            org.opensaml.saml2.core.Response samlResp=(org.opensaml.saml2.core.Response)envelope.getBody().getUnknownXMLObjects(org.opensaml.saml2.core.Response.DEFAULT_ELEMENT_NAME).get(0);
                            if(samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI))
                            {
                                System.out.println("Status  :"+samlResp.getStatus().getStatusCode().getValue());   
                            }
                            else
                            {
                               
                                StatusDetail detail= (StatusDetail)samlResp.getStatus().getStatusDetail().getUnknownXMLObjects(StatusDetail.DEFAULT_ELEMENT_NAME).get(0);
                                throw new RuntimeException("SAML Authentication Failed "+samlResp.getStatus().getStatusCode().getValue() + " - ");
                            }
                           
                        }
                        else
                        {
                            throw new RuntimeException("Invalid IDP ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }
   
   
    public Envelope sendAuthResponseToSp(String authResponseXml,String consumeServiceUrl)
    {
        Envelope envelope=null;
        HttpPost request= new HttpPost(consumeServiceUrl);
        request.addHeader(HttpHeaders.CONTENT_TYPE, EcpFlowConstants.PAOS_CONTENT_TYPE);
       
        try
        {
            request.setEntity(new StringEntity(authResponseXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    entity.writeTo(System.out);
                    logger.info("Cookies   "+store.getCookies());
                    EntityUtils.consume(entity);
                   
                   
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }

   
    public Envelope buildIdpAuthRequest(Envelope envelope)
    {
       
        Envelope newAuthRequest = new EnvelopeBuilder().buildObject();
       
        Body authBody= envelope.getBody();
        authBody.detach();
        newAuthRequest.setBody(authBody);
       
        return newAuthRequest;
       
    }
   
    public Envelope convertRequestToSoap(HttpEntity entity)
    {
        Envelope envlope=null;
        Document soapDoc;
        try
        {
            soapDoc = pool.parse(entity.getContent());
            Unmarshaller unmarshall = Configuration.getUnmarshallerFactory().getUnmarshaller(soapDoc.getDocumentElement());
            envlope=(Envelope)unmarshall.unmarshall(soapDoc.getDocumentElement());
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (XMLParserException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnmarshallingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envlope;
    }
   

    public String convertSoapToString(Envelope envelope)
    {
        String result=null;
        try
        {
            Marshaller marshaller= Configuration.getMarshallerFactory().getMarshaller(envelope);
            Element element= marshaller.marshall(envelope);
            Transformer transformer;
            transformer = TransformerFactory.newInstance().newTransformer();
            Source source = new DOMSource(element);
            StringWriter writer= new StringWriter();
            StreamResult output = new StreamResult(writer);
            transformer.transform(source, output);
            result=writer.getBuffer().toString();
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MarshallingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerFactoryConfigurationError e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

}


package edu.example.ecp.client;

import org.opensaml.common.xml.SAMLConstants;

public interface EcpFlowConstants
{
    public String PAOS_CONTENT_TYPE="application/vnd.paos+xml";
    public String PAOS_HEADER=SAMLConstants.PAOS_PREFIX.toUpperCase();
}

   
   
package edu.example.ecp.client;

   
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.ecp.RelayState;
import org.opensaml.saml2.ecp.Response;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.ws.soap.soap11.Header;
import org.opensaml.ws.soap.soap11.impl.HeaderBuilder;
import org.opensaml.xml.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class EcpClient {

    private static Logger logger= LoggerFactory.getLogger(EcpClient.class);

    private static String SECURE_URL="https://shib-sp.example.edu/sample/";
    private static String IDP_URL="https://shib-idp.example.edu/idp/profile/SAML2/SOAP/ECP";
    private static String USER_NAME="<User Name>";
    private static String PASSWORD="<Password>";

    public static void main(String[] args)
    {
        /*
        System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog");
        System.setProperty("org.apache.commons.logging.simplelog.showdatetime","true");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http","DEBUG");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire","DEBUG");
        System.setProperty("org.apache.commons.logging.de.tudarmstadt.ukp.shibhttpclient","DEBUG");
        */
        Ecp ecp= new Ecp();
        ecp.setProtectedUrl(SECURE_URL);
        ecp.setIdpUrl(IDP_URL);
        ecp.setUserName(USER_NAME);
        ecp.setPassword(PASSWORD);
       
        try
        {
            DefaultBootstrap.bootstrap();
           
            EcpClientFlow clientFlow= new EcpClientFlow(ecp);
            Envelope secureResourceResponse = clientFlow.accessProtectedPage();
            Envelope authRequest= clientFlow.buildIdpAuthRequest(secureResourceResponse);
            Envelope idpAuthResponse = clientFlow.sendAuthenticationToIdp(clientFlow.convertSoapToString(authRequest));
            String assertionConsumerUrl=((Response)idpAuthResponse.getHeader().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME).get(0)).getAssertionConsumerServiceURL();
           
            RelayState relayState= (RelayState)secureResourceResponse.getHeader().getUnknownXMLObjects(RelayState.DEFAULT_ELEMENT_NAME).get(0);
            relayState.detach();
           
            Header header = new HeaderBuilder().buildObject();
            header.getUnknownXMLObjects().clear();
            header.getUnknownXMLObjects().add(relayState);
            idpAuthResponse.setHeader(header);
           
            clientFlow.sendAuthResponseToSp(clientFlow.convertSoapToString(idpAuthResponse), assertionConsumerUrl);
           
        } catch (ConfigurationException e)
        {
            logger.error("Ecp Client Error", e);
        }
       

    }

}

package edu.example.ecp.client;

import java.io.IOException;

import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;
import org.opensaml.common.xml.SAMLConstants;


public class EcpRequestInterceptor implements HttpRequestInterceptor,EcpFlowConstants {

    @Override
    public void process(HttpRequest request, HttpContext context)
            throws HttpException, IOException
    {
        request.addHeader(HttpHeaders.ACCEPT, PAOS_CONTENT_TYPE);
        request.addHeader(PAOS_HEADER,"ver="+SAMLConstants.PAOS_NS+";"+SAMLConstants.SAML20ECP_NS);
    }
}


Testing the Code:

 

Create the Source and Resource Directories


mkdir ecp-client/src/main/java -p
mkdir ecp-client/src/main/resources -p

Copy the Java Source files into  ecp-client/src/main/java directory.

Creating Build File



Create a build.gradle File and add the following content in the File and also copy the file to ecp-client directory:


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'

repositories{
    mavenCentral()
}
dependencies{
    compile 'org.opensaml:opensaml:2.6.4','org.apache.httpcomponents:httpclient:4.4',
        'org.slf4j:log4j-over-slf4j:1.7.10','org.slf4j:slf4j-simple:1.7.10',
        'org.slf4j:slf4j-api:1.7.10'
}
mainClassName='edu.example.ecp.client.EcpClient'

Setting JAVA_HOME, GRADLE_HOME and PATH:


export JAVA_HOME=JavaHOME
export GRADLE_HOME=gradle_home
export PATH=$GRADLE_HOME/bin:$PATH


Setting the Classpath

Run the following command to creating the eclipse project and also setting the classpath

 gradle cleanEclipse eclipse


Running the Code

execute the gradle run and it will execute the ecp client code and display the output in the command line.



Common Errors:

Problem 1. Service Provider not sending the SOAP response with ECP Header while accessing the secure access page using the http client.

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'


Solution: Add the above headers in the request and SP will send the SOAP request with ECP Request Headers and also Authentication Request in the Soap Body.


Problem 2. The Identity Provider throwing 500 error while sending the authentication request from ECP to IDP

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'


Solution: Add the above headers in the request and DP will authenticate the user and send the SAML response in the soap body and also ecp response headers.

Problem 3. The Service Provider throwing 500 error while sending the authentication response from ECP to SP

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'
3. Content-Type:  application/vnd.paos+xml


Solution: Add the above headers in the request and SP will accept the SAML response from the IDP issued assertion and It will redirect to requested target page.


Reference:

SAML 2.0 Profiles




 

 

Friday, February 20, 2015

How to configure Shibboleth 3.0 ECP

The Shibboleth 3.0 Enhanced Client Proxy (ECP) to establish the Single-Sign on between Identity Provider (IDP) to Service Provider (SP) non supported browser applications such as Desk Top and Java Client Application.

Installed Software:

1. JDK 1.8
2. Shibboleth IDP 3.0
3. Shibboleth Native Service Provider 2.5.3
4. Tomcat 8
5. Apache Http Server 2.2
6. Red Hat 6

Pre-Requisite:

1. Installed and Configured the Shibboleth Identity Provider 3.0
2. Installed and Configured the Linux Native Service Provider 2.5.3

The following components are involved to configure the shibboleth ECP between IDP to SP:

1. Identity Provider

Login to the IDP server and configure the following components to enable the Enhanced Client Proxy (ECP):

1.1 Defining ECP End Point

Go to the ID_HOME/metadata directory and add the following content in idp.metadata.xml file after <SingleSignOnService closing tag to enable the IDP ECP endpoint:

<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://shib-idp-sandbox.ops.sfsu.edu/idp/profile/SAML2/SOAP/ECP"/>

1.2 Protect the ECP Endpoint Container Authentication

   Enable the Basic Authentication at the Web Server level as follows:
 
   1.2.1 Go to the /etc/httpd/conf.d directory and create a file idp.conf
   1.2.2 Add the following content in the idp.conf file.
  
 <Location /idp/profile/SAML2/SOAP/ECP>
                AuthType Basic
                AuthName "Demo Organization- ECP profile"
                AuthzLDAPAuthoritative Off
                AuthBasicProvider ldap
                AuthLDAPURL ldap://<LdapHostName>/<Ldap Ou>?<Authentication Parameter>
                AuthLDAPBindDN "<Ldap Admin Bind Dn>"
                AuthLDAPBindPassword "<Ldap Bind Password>"
                Require valid-user
                SSLRequireSSL
</Location>





Replace LdapHostName with your Host Name, Ldap Admin Bind Dn with your admin dn, Ldap Bind Password with you password, Authentication Parameter with your own parameter. My parameter is CN. After replacing the configuration looks as follows:

<Location /idp/profile/SAML2/SOAP/ECP>
                AuthType Basic
                AuthName "Demo - ECP profile"
                AuthzLDAPAuthoritative Off
                AuthBasicProvider ldap
                AuthLDAPURL ldap://dcs01.example.edu/ou=people,dc=example,dc=edu?cn
                AuthLDAPBindDN "CN=shibadmin,OU=Users,DC=example,dc=edu"
                AuthLDAPBindPassword "password"
                Require valid-user
                SSLRequireSSL
</Location>


1.3 Restarting the Tomcat Server

Go to the tomcat_home/bin directory and execute the following commands to stop and start the tomcat server.

./shutdown.sh
./startup.sh

1.4 Restart the Apache Web Server

 Login as a root and execute the following command to restart the apache web server .

 /etc/init.d/httpd restart

2. Service Provider

Login as a root in service provider machine and perform the following tasks to enable the ECP.

2.1 Enabling the ECP 

 I have installed the shibboleth sp in /apps/shibboleth-sp directory. Go to the Shibboleth-sp/etc/shibboleth directory and edit the shibboleth2.xml file and add the ECP="true" parameter under <SSO section.

<SSO entityID="<IDp Entity>" ECP="true"
                 discoveryProtocol="SAMLDS" discoveryURL="https://shib-idp.example.ed/DS/WAYF">
              SAML2 SAML1
 </SSO>

2.2 Protecting the Resource

 After installing the shibboleth service provide and apache22.config file generated in the /apps/shibboleth-sp/etc/shibboleth directory. I have renamed  apache22.config file to shib.conf and copy to /etc/httpd/conf.d directory. By default /secure application is protected in the shib.conf file. I am using the default configuration as it is and configuration is given below:

<Location /secure>
  AuthType shibboleth
  ShibCompatWith24 On
  ShibRequestSetting requireSession 1
  require shib-session
</Location>


2.3 Restart the Apache Web Server 

  /etc/init.d/httpd restart

Testing the ECP through Java


 Sample Code


  The following sample code needs to be executed to test the ecp configuration and also modify the idPUrl to your actual Url, spUrl to your actual Sp Url, User Name with your user name and password with your password:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.opensaml.DefaultBootstrap;
import org.opensaml.xml.ConfigurationException;

import de.tudarmstadt.ukp.shibhttpclient.ShibHttpClient;


public class ShibEcpTestClient {

    private static String idpBaseUrl="https://shib-idp.example.edu";
    private static String spUrl="https://shib-sp.example.edu";
   
    private static String userName="<User Name>";
    private static String password="<Password>";

    public static void main(String[] args) {
        // TODO Auto-generated method stub
       
       
        System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog");
        System.setProperty("org.apache.commons.logging.simplelog.showdatetime","true");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http","DEBUG");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire","DEBUG");
       

        try {
            DefaultBootstrap.bootstrap();
        } catch (ConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
       
       
       
       
        HttpClient client= new ShibHttpClient(idpBaseUrl+"/idp/profile/SAML2/SOAP/ECP", userName, password);
        HttpGet req = new HttpGet(spUrl+"/secure");
        try
        {
            HttpResponse res = client.execute(req);
            InputStream ins= res.getEntity().getContent();
           
           
            BufferedReader br= new BufferedReader(new InputStreamReader(ins));
            String readLine=null;
            while((readLine=br.readLine()) != null)
            {
                System.out.println("Read Line Data  :"+readLine);
            }
           
           
           
               
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
   

}

   



Friday, February 13, 2015

Office 365 Integrating with Shibboleth 3.0

Enabling the Browser based Single Sign-On or Service Provider (SP) initiated Single Sign-On  between  Office 365 to Shibboleth Identity Provider (IDP 3.0) as follows:

Pre-Requisite:

1. Installed and Configured the IDP Server 3.0
2. Office 365 Domain Should be created and verified.


Shibboleth IDP 3.0 Configuration:


1. Setting the Trust IDP to SP

    1.1 Import the Office 365 metadata

    
    Go to the ID_HOME/metadata directory and execute the following command to download the office 365 metadata:

   wget https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml

    1.2 Register the metadata with IDP

   
    Go to the ID_HOME/conf directory and add the following content in metadata-providers.xml file:
   
<MetadataProvider id="microsoftonline"  xsi:type="FilesystemMetadataProvider" metadataFile="%{idp.home}/metadata/federationmetadata.xml"/>

 

2. Setting up the Encryption optional for SP's

    Go to the IDP_HOME/conf directory and modify the idp.properties file. 

   2.3 Modifying the idp.properties

      This configuration is required to setup optional encryption parameter for SP's metadata encryption keys. If the metadata contains encryption keys and it will apply while encrypting the data else it is optional.

 idp.encryption.optional = true



3. Releasing the Attributes from IDP to SP

   As per the Microsoft the following attributes are required for office 365 SSO:

   1. ImmutableID
   2. UserId

    Go to the IDP_HOME/conf directory and modify the attribute-resolver.xml and attribute-filter.xml file to release the attributes from IDP to SP.

   3.1 Defining Attributes in attribute-resolver.xml File 

   The following content needs to be added in the attribute-resolver.xml file:

    ImmutableID Attribute Definition
   
The immutableID is unqiue and shouldn't change any time. In my case common name (cn) is unique.

    <resolver:AttributeDefinition id="ImmutableID"  xsi:type="ad:Simple" sourceAttributeID="cn">
            <resolver:Dependency ref="myLDAP" />
            <resolver:AttributeEncoder xsi:type="enc:SAML2StringNameID" nameFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" />
   </resolver:AttributeDefinition>

     The SAML2StringNameID is deprecated in shibboleth 3.0. If you want to compatible with Shibboleth IDP 3.0, you need to follow the step 5 to generate the Name ID Policy and also modify the attribute definition configuration as follows:

      <resolver:AttributeDefinition id="ImmutableID"  xsi:type="ad:Simple" sourceAttributeID="cn">
               <resolver:Dependency ref="myLDAP" />
               <resolver:AttributeEncoder xsi:type="enc:SAML2String" name="urn:mace:dir:attribute-def:ImmutableID" friendlyName="ImmutableID"  encodeType="false" />
   </resolver:AttributeDefinition>

    
 UserId Attribute Definition

<resolver:AttributeDefinition id="UserId" xsi:type="ad:Simple" sourceAttributeID="mail">
            <resolver:Dependency ref="myLDAP" />
            <resolver:AttributeEncoder xsi:type="enc:SAML2String" name="IDPEmail" friendlyName="UserId" />
   </resolver:AttributeDefinition>

 

 3.2 Releasing Attributes from IDP to SP in attribute-filter.xml File:


 The following content needs to be added in the attribute-filter.xml file:

   <afp:AttributeFilterPolicy id="PolicyForWindowsAzureAD">
        <afp:PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="urn:federation:MicrosoftOnline" />


       <!-- Release mail as Windows Azure AD User ID -->

       <afp:AttributeRule attributeID="UserId">
                <afp:PermitValueRule xsi:type="basic:ANY" />
       </afp:AttributeRule>


       <!-- Release Immutable ID to Windows Azure AD -->

       <afp:AttributeRule attributeID="ImmutableID" >
            <afp:PermitValueRule xsi:type="basic:ANY" />
       </afp:AttributeRule>

       <!-- Denying the transient ID release to Windows Azure AD -->

       <afp:AttributeRule attributeID="transientId">
            <afp:DenyValueRule xsi:type="basic:ANY"/>
       </afp:AttributeRule>

   </afp:AttributeFilterPolicy>


4. Generating the Name ID Configuration

   Go to the IDP_HOME/conf directory and modify the saml-nameid.properties and saml-nameid.xml file to generate the Persistence Name Id and this Id going to release to Service Providers as Name ID Policy and This Id going to be used to retrieve the attributes using AttributeService.
 

   4.1 Configuring the SAML Name ID

     The Name ID generation needs to be configured to release persistent ID as follows:

      Persistent ID Generator configuration in saml-nameid.properties

     
      The following properties needs to uncommented to configure the Persistence Name Id   Generator:

      idp.persistentId.generator = shibboleth.ComputedPersistentIdGenerator
      idp.persistentId.sourceAttribute = cn
      idp.persistentId.salt = changethistosomethingrandom

    In the above configuration I have chosen cn is my source attribute. If you want to modify the source attribute according to your environment. IDP going to release the name id policy as a cn persistence attribute.


   

Persistent ID Generator configuration in saml-nameid.xml

The following content needs to be added in the saml-nameid.xml file and also you need to modify the  attribute source ids. In my case my persistence source id is ImmutableID.

<ref bean="shibboleth.SAML2PersistentGenerator" />
<bean parent="shibboleth.SAML2AttributeSourcedGenerator"
            p:format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
            p:attributeSourceIds="#{ {'ImmutableID'} }" />

5. Generating Name ID Consumption Configuration

Go to the IDP_HOME/conf/c14n directory and modify the attribute-sourced-subject-c14n-config.xml and subject-c14n.xml to compatible with Attribute Encoders as per Shibboleth 3.0 Specification.

5.1 Configuring the Name ID Attribute in attribute-sourced-subject-c14n-config.xml 

 This configuration is required to add the name identifier in the attribute-sourced-subject-c14n-config.xml because IDP going to release NameIDPolicy in the SAML Assertion.  I am using the common name (cn) as the subject name identifier and I have configured the cn as follows:


    <util:list id="shibboleth.c14n.attribute.AttributesToResolve">
        <value>cn</value>
    </util:list>

    <util:list id="shibboleth.c14n.attribute.AttributeSourceIds">
        <value>cn</value>
    </util:list>

If you want to add more than one field as a subject name identifier as follows:


    <util:list id="shibboleth.c14n.attribute.AttributesToResolve">
        <value>cn</value>
        <value>mail</value>
    </util:list>

    <util:list id="shibboleth.c14n.attribute.AttributeSourceIds">
        <value>cn</value>
        <value>mail</value>
    </util:list>


Note: These attributes are configured in the attribute-resolver.xml file.


5.2 Configuring the subject-c14n.xml File


     Configuring the SAML2 Persistence Name ID

      Un-Comment the following lines to configure the SAML Name Id:

      <bean id="c14n/attribute" parent="shibboleth.PostLoginSubjectCanonicalizationFlow" />
      <ref bean="c14n/SAML2Persistent" />
    

       Configuring the SAML2 Name ID Format


       Add the <value>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</value> in the <util:list id="shibboleth.NameTransformFormats"> bean.


6. Restart the Tomcat Web Server


Office 365 Configuration:


1. Setting the Trust From SP to IDP

   
   Open the Windows Azure Active Directory Power Shell Window.
   Execute the following command to authenticate the user and also establish the connectivity from client machine to windows azure AD Server.

     Connect-MsolService

     After executing the above command, you need to provide the user name and password to authenticate the user against the windows azure ad.

    Configuring the SSO

 The following scripts needs to be executed to configure the Single Sign-On. I have used the idp-signing.crt certificate in the script because this certificate is self signed and this certificate is available in the IDP_HOME/credentials directory.


 $dom = "<Replace Your Federated Domain>"
 $idpHost="<Replace Your IDP Host>"

$fedBrandName="IDMGT Shibboleth"

$url = "$idpHost/idp/profile/SAML2/POST/SSO"
$ecpUrl = "$idpHost/idp/profile/SAML2/SOAP/ECP"
$uri = "$idpHost/idp/shibboleth"
$logoutUrl = "$idpHost/profile/SAML2/POST/SLO"


$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("idp-signing.crt")

$certData = [system.convert]::tobase64string($cert.rawdata)


Set-MsolDomainAuthentication –DomainName $dom –federationBrandName $FedBrandName -Authentication Federated  -PassiveLogOnUri $url -SigningCertificate $certData -IssuerUri $uri -ActiveLogOnUri $ecpUrl -LogOffUri $logoutUrl -PreferredAuthenticationProtocol SAMLP

Get-MsolDomainFederationSettings -DomainName $dom



Testing the SSO Will Come soon