Showing posts with label Identity Server. Show all posts
Showing posts with label Identity Server. Show all posts

Thursday, November 20, 2014

User Self Registration & Account Confirmation with WSO2 Identity Server 5.0.0

If you have any enterprise application, registration of users and confirming there account and emails is a primary task. If your platform's identity server is WSO2 Identity Server, those tasks are built in and very easy to manage. In this article I am going to explain how we can enable User Self Registration & Account Confirmation using WSO2 Identity Server 5.0.0.

As usual the necessary API methods are exposed as a admin level SOAP API in WSO2 Identity Server. First of all you need to enable admin service visible to outside using carbon.xml. Then go to https://idnetity-server-host:idnetity-server-port/services/UserInformationRecoveryService?wsdl. (Default  idnetity-server-port = 9443) Then you can see the WSDL for the UserInformationRecoveryService which expose the API method to do User Self Registration & Account Confirmation. The important methods for our task are,
  • getUserIdentitySupportedClaims() - This method returns the claims URIs needed to register a user in WSO2 Identity Server
  • registerUser() - This method will register a user in WSO2 Identity Server
  • getCaptcha() - This methods help to generate captcha to verify the user is human
  • confirmUserSelfRegistration() - This method will confirm the user registration
Then I need to explain how to configure WSO2 Identity Server to do this process and steps of the process.  The key point of the process are,
  • First of all you need to know what are the data AKA claims needs to register a user in  WSO2 Identity Server. You can get those details by simple calling getUserIdentitySupportedClaims() method of the  UserInformationRecoveryService. The SOAP Request will look like,
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org">
       <soapenv:Header/>
       <soapenv:Body>
          <ser:getUserIdentitySupportedClaims>
             <ser:dialect>http://wso2.org/claims</ser:dialect>
          </ser:getUserIdentitySupportedClaims>
       </soapenv:Body>
    </soapenv:Envelope> 
Actually if you are aware of the claims you need not to call this method.
  • Then you need to call the registerUser method. The SOAP request will look like this,
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ser="http://services.mgt.identity.carbon.wso2.org" xmlns:xsd="http://dto.mgt.identity.carbon.wso2.org/xsd"> <soap:Header/> <soap:Body> <ser:registerUser> <ser:userName>user</ser:userName> <ser:password>password</ser:password> <ser:claims> <xsd:claimUri>http://wso2.org/claims/emailaddress</xsd:claimUri> <xsd:claimValue>email</xsd:claimValue> </ser:claims> <ser:claims> <xsd:claimUri>http://wso2.org/claims/givenname</xsd:claimUri> <xsd:claimValue>firstname</xsd:claimValue> </ser:claims> <ser:claims> <xsd:claimUri>http://wso2.org/claims/lastname</xsd:claimUri> <xsd:claimValue>familyname</xsd:claimValue> </ser:claims> <ser:claims> <xsd:claimUri>http://wso2.org/claims/organization</xsd:claimUri> <xsd:claimValue>organization</xsd:claimValue> </ser:claims> <ser:tenantDomain></ser:tenantDomain> </ser:registerUser> </soap:Body> </soap:Envelope>
  • As soon as the user is registered they will be not allowed to login. They need to confirm there email accounts. To do that we need to configure IS like this. Open the WSO2_Identity_Server_Folder/repository/conf/security/identity­-mgt.properties. In that add this entry,
Authentication.Policy.Account.Lock.On.Creation=true
This will guarantee that user cant login as soon as they register. They need to verify there email.
  • As soon as user register, we need to send an email to user to confirm there account. That can be done with in the IS. To do that open the identity­-mgt.properties file again and add these entries.'
Identity.Listener.Enable=true
Notification.Sending.Internally.Managed=true
Notification.Expire.Time=7200
Notification.Sending.Enable=true
Authentication.Policy.Enable=true 
  • To send emails, you need to enable mailto transport in  WSO2 Identity Server. To do that open WSO2_Identity_Server_Folder/repository/conf/axis2/axis2.xml file. In that un-comment following entries and change to suitable SMTP configurations. 
<transportSender name="mailto" class="org.apache.axis2.transport.mail.MailTransportSender"> <parameter name="mail.smtp.host">smtp.gmail.com</parameter> <parameter name="mail.smtp.port">587</parameter> <parameter name="mail.smtp.starttls.enable">true</parameter> <parameter name="mail.smtp.auth">true</parameter> <parameter name="mail.smtp.user">synapse.demo.0</parameter> <parameter name="mail.smtp.password">mailpassword</parameter> <parameter name="mail.smtp.from">synapse.demo.0@gmail.com</parameter> </transportSender>
  • The account if confirmed when the confirmUserSelfRegistration() method is called. So in the account confirmation email we need to send a link to invoke that method. In my example I am sending a link to a Java servlet which we will call this method.
  • To send account registration email, we need to create a template. We can do that by editing  WSO2_Identity_Server_Folder/repository/conf/email/email­-admin­-config.xml. Edit the accountConfirmation entry. It will look like this,
<configuration type="accountConfirmation"> <targetEpr></targetEpr> <subject>XYZ Registration - Confirm Account Registration</subject> <body> Hi {first-name}, You have created an account in XYZ with following user name User Name: {user-name} Please click the following link to complete the registration. If clicking the link doesn't seem to work, you can copy and paste the link into your browser's address window. https://xyz.com/confirmRegistration?confirmationCode={confirmation-code}&amp;userName={user-name} </body> <footer> Best Regards, XYZ Inc. www.xyz.com </footer> <redirectPath></redirectPath> </configuration>
The given link will call the servlet which I have mentioned above.
  • Before confirm the registration we can check whether this call is made by a human. To do that we can use a captcha. The captcha based validation process is like this,
- Get captcha image from IS
- Captcha will be returned with a image and a secret key for image
- Send the users input for captcha along with secret key while we call confirmation method
To get a captcha we can call getCaptcha method of above service. The SOAP request will look like this,
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org">
    <soapenv:Header/>
    <soapenv:Body>
        <ser:getCaptcha/>
    </soapenv:Body>
</soapenv:Envelope> 
  • If we don't need captcha based validation we can configure it. To do that edit the following entry in WSO2_Identity_Server_Folder/repository/conf/security/identity­-mgt.properties file.
Captcha.Verification.Internally.Managed=true
  • Then you need to call the confirmUserSelfRegistration method to complete the registration. The SOAP request looks like this,
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org" xmlns:xsd="http://beans.mgt.captcha.carbon.wso2.org/xsd"> <soapenv:Header/> <soapenv:Body> <ser:confirmUserSelfRegistration> <ser:username>xyz</ser:username> <ser:code>xyz</ser:code> <ser:captcha> <xsd:imagePath>xyz</xsd:imagePath> <xsd:secretKey>xyz</xsd:secretKey> <xsd:userAnswer>xyz</xsd:userAnswer> </ser:captcha> <ser:tenantDomain></ser:tenantDomain> </ser:confirmUserSelfRegistration> </soapenv:Body> </soapenv:Envelope>
If we are not using captcha validation we don't need to send those parameters.
  • After a successful call the above method will complete the registration. Then user will be able to login. The account will be unlocked.
  • All the above methods should be called wiht Basic Auth Header with admin user name and password.
Lets look at how to do this with JAVA. To call the SOAP service we can use the STUB provided in side the product. That is org.wso2.carbon.identity.mgt.stub. Using that we can call the UserInformationRecoveryService easily. Thisis how you can call user registration service,

import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.emojot.webUI.Config;
import org.wso2.carbon.identity.mgt.stub.UserInformationRecoveryServiceIdentityMgtServiceExceptionException;
import org.wso2.carbon.identity.mgt.stub.UserInformationRecoveryServiceStub;
import org.wso2.carbon.identity.mgt.stub.beans.VerificationBean;
import org.wso2.carbon.identity.mgt.stub.dto.UserIdentityClaimDTO;
import java.rmi.RemoteException;
import java.util.Map;
import java.util.Properties;

public class UserRegistration  {

    public static void main(String[] args) {

        UserInformationRecoveryServiceStub stub=null;
        HttpTransportProperties.Authenticator authenticator = null;
        Map properties=null;

        String serviceURL = Config.isURL + "/services/UserInformationRecoveryService";

        try {
            stub = new UserInformationRecoveryServiceStub(null, serviceURL);
            ServiceClient client = stub._getServiceClient();
            Options options = client.getOptions();

            authenticator = new HttpTransportProperties.Authenticator();
            authenticator.setUsername(Config.isAdminUserName);
            authenticator.setPassword(Config.isAdminPassword);

            properties = new Properties();
            properties.put(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);

            options.setProperties(properties);

        } catch (AxisFault axisFault) {
            System.out.println(axisFault.getMessage());
            System.out.println(axisFault.getDetail().toString());
        }

        String userName="username1";
        String password="password1";
        String emailaddress="username1@XYZ.com";
        String givenname="username1";
        String lastname="username1";
        String organization="XYZ";

            if(stub!=null){
                try {
                    UserIdentityClaimDTO claimEmailaddress=new UserIdentityClaimDTO();
                    claimEmailaddress.setClaimUri(UserMgtConstants.claimEmailaddress);
                    claimEmailaddress.setClaimValue(emailaddress);

                    UserIdentityClaimDTO claimGivenname=new UserIdentityClaimDTO();
                    claimGivenname.setClaimUri(UserMgtConstants.claimGivenname);
                    claimGivenname.setClaimValue(givenname);

                    UserIdentityClaimDTO claimLastname=new UserIdentityClaimDTO();
                    claimLastname.setClaimUri(UserMgtConstants.claimLastname);
                    claimLastname.setClaimValue(lastname);

                    UserIdentityClaimDTO claimOrganization=new UserIdentityClaimDTO();
                    claimOrganization.setClaimUri(UserMgtConstants.claimOrganization);
                    claimOrganization.setClaimValue(organization);

                    UserIdentityClaimDTO claims[]=new UserIdentityClaimDTO[]{claimEmailaddress,claimGivenname,claimLastname,claimOrganization};

                    VerificationBean verificationBean= stub.registerUser(userName, password, claims, null, null);
                    System.out.println("Successfully Registered the User -"+verificationBean.getUserId()+" Verification Key -"+verificationBean.getKey());

                } catch (UserInformationRecoveryServiceIdentityMgtServiceExceptionException e) {
                    System.out.println(e.getMessage());
                    System.out.println(e.getFaultMessage());
                } catch (RemoteException e) {
                    System.out.println(e.getMessage());
                }
            }else{
                System.out.println("Internal Error - Stub Creation Failed");
            }       
    }
}


After that you need to call the confirmation method. That can be also done like this. Here I am not using the internal captcha validation.

import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.emojot.webUI.Config;
import org.wso2.carbon.identity.mgt.stub.UserInformationRecoveryServiceIdentityMgtServiceExceptionException;
import org.wso2.carbon.identity.mgt.stub.UserInformationRecoveryServiceStub;
import org.wso2.carbon.identity.mgt.stub.beans.VerificationBean;
import org.wso2.carbon.identity.mgt.stub.dto.UserIdentityClaimDTO;
import java.rmi.RemoteException;
import java.util.Map;
import java.util.Properties;

public class RegistrationConfirm {

     public static void main(String[] args) {

        UserInformationRecoveryServiceStub stub=null;
        HttpTransportProperties.Authenticator authenticator = null;
        Map properties=null;

        String serviceURL = Config.isURL + "/services/UserInformationRecoveryService";
        try {

            stub = new UserInformationRecoveryServiceStub(null, serviceURL);
            ServiceClient client = stub._getServiceClient();
            Options options = client.getOptions();

            authenticator = new HttpTransportProperties.Authenticator();
            authenticator.setUsername(Config.isAdminUserName);
            authenticator.setPassword(Config.isAdminPassword);

            properties = new Properties();
            properties.put(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);

            options.setProperties(properties);
        } catch (AxisFault axisFault) {
            System.out.println(axisFault.getMessage());
            System.out.println(axisFault.getDetail().toString());
        }

        String userName="username1";
        String confirmationCode="13677e9d-e3c8-4a78-a6e0-569163e66fba";

            if(stub!=null){
                try {
                    VerificationBean verificationBean= stub.confirmUserSelfRegistration(userName, confirmationCode, null,null);
                    System.out.println("Successfully Registered the User-" + userName + " Registration Confirmed -" + verificationBean.getVerified());
                } catch (UserInformationRecoveryServiceIdentityMgtServiceExceptionException e) {
                    System.out.println(e.getMessage());
                    System.out.println(e.getFaultMessage());
                } catch (RemoteException e) {
                    System.out.println(e.getMessage());
                }
            }else{
                System.out.println("Internal Error - Stub Creation Failed");
            }
    }
}

Hoep this post helps you. If you have any query please contact me.

    Friday, June 6, 2014

    Twitter Authenticator for WSO2 Identity Server 5.0.0

    If you have already used http://wso2.com/products/identity-server/ you knows that it allows us to use custom authenticators. Also it comes with several authenticators which are built in. For a example Facebook, Google, OpenID, SAML are such authenticators. Here in my application there was requirement to authenticate users via Twitter. Thus I used the capability of WSO2 Identity Server which is configured to provide SAML login to my application. In the SAML SSO scenario my users can choose Twitter as there authentication option. To do that I had to write my own authenticator.


    To understand the authtication logic form Twitter API side you need to look at,

    https://dev.twitter.com/docs/browser-sign-flow
    https://dev.twitter.com/docs/auth/implementing-sign-twitter

    Also to do these in Java there is a solid library called twitter4j. You need to look at Code Examples of Sign in with Twitter.

    At the point which I did this, there were no documentation provided to do this.Using the knowledge I gathered in my internship @ WSO2 and after getting some ideas from experts, I was able to write my authenticator. I had looked at the WSO2 Identity Server code base to see how other authenticators are written.

    I will start with the structure of a authenticator pom.xml. Authenticators are OSGi bundles. So the pom.xml looks like this and you can find the dependencies for the project. Other than the twitter4j dependency other dependencies are mandatory.
    <?xml version="1.0" encoding="utf-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
        <groupId>org.emojot</groupId>
        <modelVersion>4.0.0</modelVersion>
        <artifactId>authenticator-twitter</artifactId>
        <packaging>bundle</packaging>
        <version>1.0.0</version>
    
        <dependencies>
    
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.logging</artifactId>
                <version>4.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.identity.application.authentication.framework</artifactId>
                <version>4.2.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.ui</artifactId>
                <version>4.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.amber.wso2</groupId>
                <artifactId>amber</artifactId>
                <version>0.22.1358727.wso2v4</version>
            </dependency>
    
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.identity.application.common</artifactId>
                <version>4.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.twitter4j</groupId>
                <artifactId>twitter4j-core</artifactId>
                <version>[4.0,)</version>
            </dependency>
        </dependencies>
    
        <repositories>
            <repository>
                <id>wso2-nexus</id>
                <name>WSO2 Internal Repository</name>
                <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
                <releases>
                    <enabled>true</enabled>
                    <updatePolicy>daily</updatePolicy>
                    <checksumPolicy>ignore</checksumPolicy>
                </releases>
            </repository>
            <repository>
                <id>twitter4j.org</id>
                <name>twitter4j.org Repository</name>
                <url>http://twitter4j.org/maven2</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
        </repositories>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-scr-plugin</artifactId>
                    <version>1.7.4</version>
                    <executions>
                        <execution>
                            <id>generate-scr-scrdescriptor</id>
                            <goals>
                                <goal>scr</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                            <Bundle-Name>${project.artifactId}</Bundle-Name>
                            <Private-Package>org.emojot.authenticator.twitter.internal</Private-Package>
                            <Import-Package>org.twitter4j.*;
                                version="[4.0,)",
                                org.apache.axis2.*;
                                version="[1.6.1.wso2v1, 1.7.0)",
                                org.apache.axiom.*;
                                version="[1.2.11.wso2v2, 1.3.0)",
                                org.wso2.carbon.ui.*,
                                org.apache.commons.logging.*; version="1.0.4",
                                org.osgi.framework,
                                org.wso2.carbon.identity.application.authentication.framework.*,
                                javax.servlet;version="[2.6.0,3.0.0)",
                                javax.servlet.http;version="[2.6.0,3.0.0)",
                                *;resolution:=optional
                            </Import-Package>
                            <Export-Package>!org.emojot.authenticator.twitter.internal,
                                org.emojot.authenticator.twitter.*
                            </Export-Package>
                            <DynamicImport-Package>*</DynamicImport-Package>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    Since your project is a OSGi bundle you need add this class to define bundle activate method and deactivate method.
    package org.emojot.authenticator.twitter.internal;
    
    import java.util.Hashtable;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.osgi.service.component.ComponentContext;
    import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator;
    import org.emojot.authenticator.twitter.TwitterAuthenticator;
    
    /**
     * @scr.component name="authenticator.twitter" immediate="true"
     */
    
    public class TwitterAuthenticatorServiceComponent {
    
        private static final Log LOGGER = LogFactory.getLog(TwitterAuthenticatorServiceComponent.class);
    
        protected void activate(ComponentContext ctxt) {
            try {
                TwitterAuthenticator twitterAuthenticator = new TwitterAuthenticator();
                Hashtable<String, String> props = new Hashtable<String, String>()
                ctxt.getBundleContext().registerService(ApplicationAuthenticator.class.getName(),twitterAuthenticator, props); 
    
                LOGGER.info("----Twitter Authenticator bundle is activated----");
    
            } catch (Throwable e) {
                LOGGER.fatal("----Error while activating Twitter authenticator----", e);
            }
        }
    
        protected void deactivate(ComponentContext ctxt) {
            LOGGER.info("----Twitter Authenticator bundle is deactivated----");
        }
    }
    After adding these to you project you are in a position to write your authenticator. Authenticators are defined by extending AbstractApplicationAuthenticator class and implementing    FederatedApplicationAuthenticator interface. The important methods in these two are,
    • public String getName()
    • public String getFriendlyName()
    • public String getContextIdentifier(HttpServletRequest request) - Returns a uniquer identifier which will map the authentication request and the response. The value return by the invocation of authentication request and the response should be the same.
    • public boolean canHandle(HttpServletRequest request) - Tells whether this authenticator can handle the authentication response.
    • protected void initiateAuthenticationRequest(HttpServletRequest request,HttpServletResponse response, AuthenticationContext context) 
    • protected void processAuthenticationResponse(HttpServletRequest request,HttpServletResponse response, AuthenticationContext context)
    I have implemented the canHandle() method like this. When Twitter sends the OAuth response it will sends parameters oauth_token,oauth_verifier in the request. Than is a notification to identify that this response can be handled by the authenticator.

        public boolean canHandle(HttpServletRequest request) {
            if (request.getParameter("oauth_token")!=null && request.getParameter("oauth_verifier")!=null) {
                return true;
            }
            return false;
        }

    For each authentication request which comes to IS, there is unique value comes as a parameter. That is sessionDataKey. I stored that in the Twitter authentication redirection session to facilitate the requirement of  getContextIdentifier gives same value for authentication request and its response.

        public String getContextIdentifier(HttpServletRequest request) {
            if(request.getSession().getAttribute("contextIdentifier")==null){ 
                request.getSession().setAttribute("contextIdentifier",request.getParameter("sessionDataKey"));
                return request.getParameter("sessionDataKey");
            }else{
                return (String) request.getSession().getAttribute("contextIdentifier");
            }
        } 

    I have implemented the initiateAuthenticationRequest method and processAuthenticationResponse method as follows,
         protected void initiateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException {       
    
            String apiKey= resourceBundle.getString("API_Key");
            String apiSecret= resourceBundle.getString("API_Secret");
    
            Twitter twitter = new TwitterFactory().getInstance();
            twitter.setOAuthConsumer(apiKey, apiSecret);
           
            try {
                String callbackURL = resourceBundle.getString("Call_Back_URL");
                RequestToken requestToken = twitter.getOAuthRequestToken(callbackURL.toString());
                request.getSession().setAttribute("requestToken",requestToken);
                request.getSession().setAttribute("twitter",twitter);
                response.sendRedirect(requestToken.getAuthenticationURL());
    
            } catch (TwitterException e) {
                LOGGER.error("Exception while sending to the Twitter login page.", e);
                throw new AuthenticationFailedException(e.getMessage(), e);
            } catch (IOException e) {
                LOGGER.error("Exception while sending to the Twitter login page.", e);
                throw new AuthenticationFailedException(e.getMessage(), e);
            }
            return;
        }
    
        protected void processAuthenticationResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException {
            Twitter twitter = (Twitter) request.getSession().getAttribute("twitter");
            RequestToken requestToken = (RequestToken) request.getSession().getAttribute("requestToken");
            String verifier = request.getParameter("oauth_verifier");
            try {
                AccessToken token=twitter.getOAuthAccessToken(requestToken, verifier);
                request.getSession().removeAttribute("requestToken");
                User user= twitter.verifyCredentials();
                buildClaims(user,context);
            } catch (TwitterException e) {
                LOGGER.error("Exception while obtaining OAuth token form Twitter", e);
                throw new AuthenticationFailedException("Exception while obtaining OAuth token form Twitter",e);
            }
        }
    
        public void buildClaims(User user, AuthenticationContext context) {
    
                context.setSubject(String.valueOf(user.getId()));
                Map<ClaimMapping, String> claims = new HashMap<ClaimMapping, String>();
                claims.put(ClaimMapping.build("name", "name", null,false), user.getName());
                claims.put(ClaimMapping.build("screen_name", "screen_name", null,false), user.getScreenName());
                claims.put(ClaimMapping.build("url", "url", null,false), user.getURL());
    
                context.setSubjectAttributes(claims);
        }
    The buildClaims method save the retrieved user attributes to the authenticated context in IS. That is need to map the claims to built in claims of IS.

    After implementing these methods you can build your bundle. After building it you have to put that in to IS_Home/repository/components/dropins folder. After restarting this you can use the Twitter authenticator in IS.

    Sunday, June 1, 2014

    Getting User Attributes in SAML2 Bearer Assertion Profile for OAuth 2.0 using JWT Token Generation

    We can configure WSO2 API Manager to send user attributes to backend API, if the internal users of API Manager consumes the API using obtained OAuth token. That process is described in https://docs.wso2.org/display/AM170/Passing+Enduser+attributes+to+the+Backend+Using+JWT. JSON Web Token(JWT) tokens are used in this.

    But when users get OAuth token using SAML2 Bearer Assertion Profile for OAuth 2.0 how can we do that? What we need to do is share the user store with IS as well as API Manger. That kind of a architecture is given below.


    JAVA client to SAML2 Bearer Assertion Profile for OAuth 2.0

    In SAML2 Bearer Assertion Profile for OAuth 2.0 user can get a SAML token from WSO2 Identity Server by authenticating. After that user can give that SAML token to WSO2 API Manger to get an OAuth token without going for authentication.I am giving you a JAVA client to exchange SAML token to OAuth token.

    import org.example.webUI.Config;
    import org.opensaml.Configuration;
    import org.opensaml.DefaultBootstrap;
    import org.opensaml.saml2.core.Response;
    import org.opensaml.xml.ConfigurationException;
    import org.opensaml.xml.XMLObject;
    import org.opensaml.xml.io.Unmarshaller;
    import org.opensaml.xml.io.UnmarshallerFactory;
    import org.opensaml.xml.io.UnmarshallingException;
    import org.opensaml.xml.util.Base64;
    import org.opensaml.xml.util.XMLHelper;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.xml.sax.SAXException;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLEncoder;
    import java.util.HashMap;
    
    public class OAuthUtil {
    
        public static String exchangeSAMLTokenToOauth(String responseMessage) throws ConfigurationException, ParserConfigurationException, IOException, SAXException, UnmarshallingException {
    
            DefaultBootstrap.bootstrap();
    
            byte[] decoded = Base64.decode(responseMessage);
    
            ByteArrayInputStream is = new ByteArrayInputStream(decoded);
    
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            documentBuilderFactory.setNamespaceAware(true);
            DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
    
            Document document = docBuilder.parse(is);
            Element element = document.getDocumentElement();
    
            UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
            Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
            XMLObject responseXmlObj = unmarshaller.unmarshall(element);
    
            Response responseObj = (Response) responseXmlObj;
    
    
            // Get the SAML2 Assertion part from the response
            StringWriter rspWrt = new StringWriter();
            XMLHelper.writeNode(responseObj.getAssertions().get(0).getDOM(), rspWrt);
            String requestMessage = rspWrt.toString();
    
            // Get the Base64 encoded string of the message
            // Then Get it prepared to send it over HTTP protocol
            String encodedRequestMessage = Base64.encodeBytes(requestMessage.getBytes(), Base64.DONT_BREAK_LINES);
            String enc_rslt = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
    
            //Create connection to the Token endpoint of API manger
            URL url = new URL(Config.apiMangerOAuthURL);
    
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    
            String userCredentials = Config.apiMangerClientID+":"+Config.apiMangerClientSecret;
            String basicAuth = "Basic " + new String(Base64.encodeBytes(userCredentials.getBytes()));
            basicAuth = basicAuth.replaceAll("\\r|\\n", "");
    
            // Set the consumer-key and Consumer-secret
            connection.setRequestProperty("Authorization", basicAuth);
            connection.setUseCaches(false);
            connection.setDoInput(true);
            connection.setDoOutput(true);
    
            //Send request
            DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
            wr.writeBytes("grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&assertion=" + enc_rslt + "&scope=PRODUCTION");
            wr.flush();
            wr.close();
    
            //Get Response
            InputStream iss = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(iss));
    
            String line;
            StringBuffer responseString = new StringBuffer();
            while ((line = rd.readLine()) != null) {
                responseString.append(line);
                responseString.append('\r');
            }
    
            rd.close();
            return responseString.toString();
        }
    
        public static boolean revokeToken(Token token) throws IOException {
            //Create connection to the Token endpoint of API manger
            URL url = new URL(Config.apiMangerOAuthRevokeURL);
    
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
    
            String userCredentials = Config.apiMangerClientID+":"+Config.apiMangerClientSecret;
            String basicAuth = "Basic " + new String(Base64.encodeBytes(userCredentials.getBytes()));
            basicAuth = basicAuth.replaceAll("\\r|\\n", "");
    
            // Set the consumer-key and Consumer-secret
            connection.setRequestProperty("Authorization", basicAuth);
            connection.setUseCaches(false);
            connection.setDoInput(true);
            connection.setDoOutput(true);
    
            //Send request
            DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
            wr.writeBytes("token="+token.getAccess_token());
            wr.flush();
            wr.close();
    
            //Get Response
            InputStream iss = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(iss));
    
            String line;
            StringBuffer responseString = new StringBuffer();
            while ((line = rd.readLine()) != null) {
                responseString.append(line);
                responseString.append('\r');
            }
    
            rd.close();
    
            System.out.println("Revoking Token -"+token.getAccess_token());
            System.out.println("Revoking Response -"+responseString.toString());
    
            return true
                    ;
        }
    }

    The related token class is this,

    public class Token {
        private String access_token,refresh_token,expires_in,token_type;
    
        public Token(String access_token, String refresh_token, String expires_in, String token_type) {
            this.access_token = access_token;
            this.refresh_token = refresh_token;
            this.expires_in = expires_in;
            this.token_type = token_type;
        }
    
        public String getRefresh_token() {
            return refresh_token;
        }
    
        public void setRefresh_token(String refresh_token) {
            this.refresh_token = refresh_token;
        }
    
        public String getAccess_token() {
            return access_token;
        }
    
        public void setAccess_token(String access_token) {
            this.access_token = access_token;
        }
    
        public String getExpires_in() {
            return expires_in;
        }
    
        public void setExpires_in(String expires_in) {
            this.expires_in = expires_in;
        }
    
        public String getToken_type() {
            return token_type;
        }
    
        public void setToken_type(String token_type) {
            this.token_type = token_type;
        }
    }
    
    Hope this helped you! 

    SAML2 Bearer Assertion Profile for OAuth 2.0 with WSO2 Identity Server 5.0.0 and WSO2 API Manger 1.7.0

    WSO2 product stack supports SAML2 Bearer Assertion Profile for OAuth 2.0. You can find lot of details about it in https://docs.wso2.org/display/IS460/SAML2+Bearer+Assertion+Profile+for+OAuth+2.0. What happens in here actually is a user can get a SAML token from WSO2 Identity Server by authenticating. After that user can give that SAML token to API Manger to get an OAuth token without going for authentication. To do that WSO2 Identity Server have to be a trusted identity provider for WSO2 API Manager. I will explain how to do it in these products.

    You need,
    Here a sample web application is using SAML SSO to authenticate it's users using  WSO2 Identity Server  and use SAML2 Bearer Assertion Profile to get OAuth token from WSO2 API Manager. Those tokens are used to query a REST API published in API Manager.

    To understand this more I will expalin the flow,
    • When a user access the sample web application using this kind of urls http://example.com/mobile, they will be redirected to WSO2 Identity Server authentication page.
    • When users authenticate them self in the login UI WSO2 Identity Server will redirect the request back to web application with a SAML response like this,
    <?xml version="1.0" encoding="UTF-8"?> 
    <saml2:Assertion xmlns:saml2="urn:oasis:names: tc:SAML:2.0:assertion" ID="hnpnedgkgdbhinomeabplbjdnhlffjbbckodpkhl" IssueInstant="2014-05-31T15:41:03.665Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">localhost</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/2000/09/xmldsig#rsa-sha1"/>
                <ds:Reference URI="#hnpnedgkgdbhinomeabplbjdnhlffjbbckodpkhl">
                    <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#">
                            <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"/>
                        </ds:Transform>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <ds:DigestValue>KaUzRnwIJfhVboyWpcgg555ea0Y=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>
                L3jOrttx7+Vb6iBnDV6hu4GRoGDX6k1U2Z+C7XLh7+jgvq53/N7Ro0rY4d4GiJJb5gOcAg3f8qt+gKQbAvcjvbJCRW1x4GFZ0au4iqZUSbHOPYPiIT4mYzxrmZg+cynVN+mrJCEEFdhFaP/3yPITeeapP9Yqp3QvtnbRC6s8ejk=
            </ds:SignatureValue>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>
                        MIICNTCCAZ6gAwIBAgIES343gjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxDTALBg
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </ds:Signature>
        <saml2:Subject>
            <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">demo</saml2:NameID>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData InResponseTo="0" NotOnOrAfter="2014-05-31T15:46:03.664Z" Recipient="http://example.com/mobile/mobile.jsp"/>
            </saml2:SubjectConfirmation>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData InResponseTo="0" NotOnOrAfter="2014-05-31T15:46:03.664Z" Recipient="https://IP:9448/oauth2/token"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions NotBefore="2014-05-31T15:41:03.665Z" NotOnOrAfter="2014-05-31T15:46:03.664Z"
            <saml2:AudienceRestriction>
                <saml2:Audience>exampleMobile</saml2:Audience>             
    	    <saml2:Audience>https://IP:9448/oauth2/token</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2014-05-31T15:41:03.665Z" SessionIndex="57c87332-97f2-4fe1-bb70-326c0e59fd36">
            <saml2:AuthnContext>
                <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
            </saml2:AuthnContext>
        </saml2:AuthnStatement>
        <saml2:AttributeStatement>
            <saml2:Attribute Name="http://wso2.org/claims/role"
                             NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">company_admin,Internal/everyone</saml2:AttributeValue>
            </saml2:Attribute>
            <saml2:Attribute Name="http://wso2.org/claims/organization" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">5386e408b35805c8649e08b8</saml2:AttributeValue>
            </saml2:Attribute>
        </saml2:AttributeStatement>
    </saml2:Assertion> 
    • By giving this to OAuth token endpoint of API Manger we can get a OAuth token like this,
    {"token_type":"bearer","expires_in":3600,"refresh_token":"b75d83e892994882abf3c19cea4db4a2","access_token":"51ee8f8f417f944a4d9c787adc6c260"}
    To get this done we need to configure like this,

    Step 1 - Start the Identity Server and go to Main Tab > Identity > Service Providers >Add

    Enter a Service Provider Name and click Register,


    Step 2 - In the next UI select Inbound Authentication Configuration > SAML2 Web SSO Configuration >  Configure

    Step 3 - Fill the Like below. Assertion consumer url will be the web application url which will IS sends the SAML response. Give the API Manager's OAuth token url as audience restriction as well as recipient restriction. Also check Enable Attribute Profile to get user attributes with SAML response. And click register.


    Step 4 - In the next UI select Claim Configuration > Requested Claims > Add Claim URI. Add the user claims which you need to get it with SAML response to the web application.

    Step 5 - Click the Update button to save the Service Provider.

    Now the SAML SSO configuration from Identity Server side is done. The web application can be configured using the SAML SSO servlet filter given by the Identity Server it self. That will check user's authentication and will redirect to IS and will process responses form IS. You can find more about that by looking at the code given in https://docs.wso2.org/display/IS500/Configuring+SAML2+SSO.

    Now we need to configure API Manger side.
     
    Step 1 - Start the API Manger and go to Main Tab > Identity > Identity Providers >Add

    Give a Identity Provider Name and  You can keep Alias empty. Also you need to upload the public certificate of Identity Server. You can get it by executing following command in WSO2 IS Home/repository/resources/security/. Then public certificate will be saved in wso2pem.pem file.
    keytool -export -alias wso2carbon -file wso2pem.pem -keystore wso2carbon.jks
    Then click on Federated Authenticators tab.
    Step 2 - In that UI click on SAML2 Web SSO Configuration

    In that you need to give the Issuer ID of the SAML config in IS side ad Identity Provider Entity Id.

    Step 3 - And then click register.

    Now you are ready to go. All the configurations which need to do SAML 2 Bearer Assertion Profile is done. You can use these articles to setup a OAuth application in API Manger to consume APIs.
    After doing that follow the steps given in Invoking Token API to generate tokens section of this article. https://docs.wso2.org/display/AM170/Token+API#TokenAPI-ExchangingSAML2bearertokenswithOAuth2%28SAMLextensiongranttype%29. By doing that you can get a OAuth token as above.

    How to configure LDAP with WSO2 Identity Server with SCIM support - The easy way

    If you want to setup your user store in a LDAP and want to integrate it with WSO2 Identity Server which support SCIM operations, you need to do claim mapping as described in this post http://sureshatt.blogspot.com/2013/06/scim-user-provisioning-with-wso2.html. But if you are going to support lot of user attributes in your user store and complex user operations with SCIM, this mapping and configuration will be a headache for you.

    Thus I will show you a easy way to setup your own LDAP and WSO2 Identity Server to support all these. I am using the configurations in built in LDAP of WSO2 Identity Server.

    What you need,
    As  you know the attributes assigned to LDAP users are defined in object classes of LDAP. If you want to add more attributes, You need to add your own object classes. You can find mroe details about those in these articles,

    http://hasini-gunasinghe.blogspot.com/2011/02/how-to-introduce-custom-attributes-to.html
    http://hasini-gunasinghe.blogspot.com/2011/04/how-to-introduce-custom-object-class-to.html

    After adding these classes you need do lot of mapping to integrate LDAP with SCIM and other complex operation support in WSO2 Identity Server. Believe me its hectic. So what we can do? What we can do is setting up our LDAP with the built in LDAP object classes of WSO2 Identity Server. Those classes define the necessary attributes to support complex operation like SCIM etc. You can find these classes here,
    You can import to these classes to your ApacheDS LDAP using  Apache Directory Studio. In the 'LDAP Browser' window in Apache Directory Studio, Right click on the 'ou=schema' context entry, select 'Import->LDIF Import' and point to above files and import them.After that your LDAP will support all the user attributes as we see in built in LDAP of WSO2 Identity Server.

    After that integrate the LDAP to WSO2 Identity Server by changing user-mgt.xml in WSO2 Identity Server Home/repository/conf. You have to use the read-write ldap mode like this,

            <UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager">
                <Property name="TenantManager">org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager</Property>
                <Property name="ConnectionURL">ldap://IP:10389</Property>
                <Property name="Disabled">false</Property>                       
                <Property name="ConnectionName">uid=admin,ou=system</Property>
                <Property name="ConnectionPassword">your_password</Property>
                <Property name="passwordHashMethod">PLAIN_TEXT</Property>
                <Property name="UserNameListFilter">(objectClass=person)</Property>
                <Property name="UserEntryObjectClass">identityPerson</Property>
                <Property name="UserSearchBase">ou=Users,dc=example,dc=com</Property>
                <Property name="UserNameSearchFilter">(&amp;(objectClass=person)(uid=?))</Property>
                <Property name="UserNameAttribute">uid</Property>
                <Property name="UsernameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
                <Property name="UsernameJavaScriptRegEx">^[\S]{3,30}$</Property>
                <Property name="RolenameJavaScriptRegEx">^[\S]{3,30}$</Property>
                <Property name="RolenameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
                <Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
                <Property name="ReadGroups">true</Property>
                <Property name="WriteGroups">true</Property>
                <Property name="EmptyRolesAllowed">false</Property>
                <Property name="GroupSearchBase">ou=Groups,dc=example,dc=com</Property>
                <Property name="GroupNameListFilter">(objectClass=groupOfNames)</Property>
                <Property name="GroupEntryObjectClass">groupOfNames</Property>
                <Property name="GroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=? </Property>
                <Property name="GroupNameAttribute">cn</Property>
                <Property name="SharedGroupNameAttribute">cn</Property>
                <Property name="SharedGroupSearchBase">ou=SharedGroups,dc=example,dc=com</Property>
                <Property name="SharedGroupEntryObjectClass">groupOfNames</Property>
                <Property name="SharedGroupNameListFilter">(objectClass=groupOfNames)</Property>
                <Property name="SharedGroupNameSearchFilter">(&amp;(objectClass=groupOfNames)(cn=?))</Property>
                <Property name="SharedTenantNameListFilter">(objectClass=organizationalUnit)</Property>
                <Property name="SharedTenantNameAttribute">ou</Property>
                <Property name="SharedTenantObjectClass">organizationalUnit</Property>
                <Property name="MembershipAttribute">member</Property>
                <Property name="UserRolesCacheEnabled">true</Property>
                <Property name="ReplaceEscapeCharactersAtUserLogin">true</Property>
                <Property name="MaxRoleNameListLength">100</Property>
                <Property name="MaxUserNameListLength">100</Property>
                <Property name="SCIMEnabled">true</Property>
            </UserStoreManager>
    
    
    
    
    Also you need to create a admin user in your user search base. That is to log in to the management console. To understand more about the internal LDAP of WSO2 Identity Server you can connect to it via Apache Directory Studio. You can refer,

    http://stackoverflow.com/questions/22494921/wso2-identity-server-ldap-browsing
    http://stackoverflow.com/questions/11841761/browsing-internal-ldap-of-wso2-identity-server

    Hope this helps you to setup your user store easily.

    Tuesday, October 9, 2012

    Securing an exisiting WebApp using Entitlement Servlet Filter

    Entitlement Servlet Filter is for check the Authorization of the requests which are coming to a webapp. This guide will tel you how to add that to a existing web of yours. You can read more about Entitlement Servlet Filter Here.

    The steps to add Entitlement Servlet Filter to your Web App :

    • Add one of J2EE Authentication Mechanism to the WebApp. (Still Entitlement Filter Support Basic Auth Only). To do this task add following to the web.xml of your WebApp.
         <security-constraint>
            <display-name>Example Security Constraint</display-name>
            <web-resource-collection>
                <web-resource-name>Protected Area</web-resource-name>
            <!-- Protected URL -->
                <url-pattern>/protected.jsp</url-pattern>
                <!-- If you list http methods, only those methods are protected -->
                <http-method>DELETE</http-method>
                <http-method>GET</http-method>
                <http-method>POST</http-method>
                <http-method>PUT</http-method>
            </web-resource-collection>
            <auth-constraint>
                <!-- Anyone with one of the listed roles may access this area -->
                <role-name>admin</role-name>
            </auth-constraint>
        </security-constraint>
    
        <!-- Default login configuration uses form-based authentication -->
        <login-config>
            <auth-method>BASIC</auth-method>
            <!--<auth-method>FORM</auth-method>-->
            <realm-name>Example Form-Based Authentication Area</realm-name>
            <form-login-config>
                <form-login-page>/protected.jsp</form-login-page>
            </form-login-config>
        </login-config>
    
        <!-- Security roles referenced by this web application -->
        <security-role>
            <role-name>everyone</role-name>
        </security-role>
        <security-role>
            <role-name>admin</role-name>
        </security-role>
    
    
    • Engage the Entitlement Servlet Filter. To do this task add following to the web.xml of your WebApp.
        <!-- Filter mappings used to configure URLs that need to be authorized  -->
        <filter-mapping>
            <filter-name>EntitlementFilter</filter-name>
            <url-pattern>/protected.jsp</url-pattern>
        </filter-mapping> 
    
    
    • Provide necessary parameters to the Entitlement Servlet filter. To do this task add following to the web.xml of your WebApp.
        <!-- The scope in which the subject would be available.  Legal values are basicAuth, request-param, request-attribute, session -->
        <context-param>
            <param-name>subjectScope</param-name>
            <param-value>basicAuth</param-value>
        </context-param>
    
        <!-- The name of the identifier by which to identify the subject -->
        <context-param>
            <param-name>subjectAttributeName</param-name>
            <param-value>username</param-value>
        </context-param>
    
        <!-- The username to perform EntitlementService query-->
        <context-param>
            <param-name>userName</param-name>
            <param-value>admin</param-value>
        </context-param>
    
        <!-- The password to perform EntitlementService query -->
        <context-param>
            <param-name>password</param-name>
            <param-value>admin</param-value>
        </context-param>
    
        <!-- The URL to perform EntitlementService query-->
        <context-param>
            <param-name>remoteServiceUrl</param-name>
            <param-value>https://localhost:9443/services/</param-value>
        </context-param>
        
        <!-- EntitlementFilter Settings -->
        <filter>
            <filter-name>EntitlementFilter</filter-name>
            <filter-class>org.wso2.carbon.identity.entitlement.filter.EntitlementFilter</filter-class>
    
            <!--Client Class that extends AbstractEntitlementServiceClient. Legal values are basicAuth, soap and thrift.
            Default is 'thrift'.-->
            <init-param>
                <param-name>client</param-name>
                <param-value>basicAuth</param-value>
            </init-param>
    
            <!--Decision caching at PEPProxy. Legal values are simple and carbon.-->
            <init-param>
                <param-name>cacheType</param-name>
                <param-value>simple</param-value>
            </init-param>
    
            <!--Maximum number of cached entries. Legal values are between 0 and 10000 -->
            <init-param>
                <param-name>maxCacheEntries</param-name>
                <param-value>1000</param-value>
            </init-param>
    
            <!-- Time interval for which cached entry is valid.-->
            <init-param>
                <param-name>invalidationInterval</param-name>
                <param-value>100000</param-value>
            </init-param>
    
            <!-- URL ro redirect to if authorization fails -->
            <init-param>
                <param-name>authRedirectUrl</param-name>
                <param-value>/index.jsp</param-value>
            </init-param>
    
        <!-- This will be used if the transport type is thrift. -->
            <init-param>
                <param-name>thriftHost</param-name>
                <param-value>localhost</param-value>
            </init-param>
    
            <!-- This will be used if the transport type is thrift.-->
            <init-param>
                <param-name>thriftPort</param-name>
                <param-value>10500</param-value>
            </init-param>
    
        </filter> 
    
    

    So after following these steps your webApp is successfully secured with Entitlement Filter. You can find a sample project here.
    Also make sure that you have to put the org.wso2.carbon.identity.entitlement.filter_4.0.2.jar, org.wso2.carbon.identity.entitlement.proxy_4.0.2  and org.wso2.carbon.identity.entitlement.stub_4.0.0.jar to your java classpath. The links for those jar is here. Also you can build those jars by using these links.

    https://svn.wso2.org/repos/wso2/carbon/platform/trunk/service-stubs/org.wso2.carbon.identity.entitlement.stub/
    https://svn.wso2.org/repos/wso2/carbon/platform/trunk/components/identity/org.wso2.carbon.identity.entitlement.proxy/
    https://svn.wso2.org/repos/wso2/carbon/platform/trunk/components/identity/org.wso2.carbon.identity.entitlement.filter/

    Sunday, July 22, 2012

    Providing XACML Fine Grained Authorization to WebApps : Using WSO2 Identity Server - Part 2

    I have discussed about XACML Fine Grained Authorization and how we can use WSO2 IS as a XACML engine in the previous post, Providing XACML Fine Grained Authorization to WebApps : Using WSO2 Identity Server - Part 1 .

    So as we know we can use WSO2 Application Server or Apache Tomcat or any other web container to host our web apps. So if there is a requirement to provide authorization to those web apps? That means some one want to allow access to there web apps on a fine grained manner. This has been done in the WSO2 platform. That was a project of mine. So using WSO2 Identity server as the XACML Policy Decision Point (PDP) we can provide fine grained authorization to webapps. This PDP can be access via a web service called Entitlement Service.

    So what will the Policy Enforcement Point(PEP) for the webapp authorization ? After having some discussions we thought that a Servlet Filter will be the ideal solution. Any webapp developer can define a servlet filter which they want to use. Also we can use servlet filter in any kind of webapp container.

    On that decision we have created a servlet filter which user can use to provide authorization to their web apps. We gave the name Entitlement Servlet Filter to that. It uses a Proxy to communicate with WSO2 Identity Server. The Entitlement PEP Proxy is responsible for following tasks,
    • There is a service in the WSO2 IS called Entitlement Service, that service is used to evaluate XCAML requests.
    • That is a admin service which can be used by the Admin Role.
    • To use that service we have to log in to the IS using AuthenticationAdmin Service.
    • So PEP Proxy log in to the IS as admin, so we can use it's Entitlement Service to evaluate requests coming.
    • We provide following parameters to PEP Proxy to evaluate the request against XACML policies in the WSO2 IS, UserName, ResourceName, Action and Environment. So it queries IS using the provided parameters and gives us the authorization decision.
    The following digram shows how the the servlet filter gets the authorization decision,


    The next problem is how we should obtain the parameters, UserName, ResourceName, Action and Environment. Exept the user name others we have. Because they are all related tot the web app. How we can get the user name? For that we used J2EE authentication mechanisms,
    • Basic Authentication
    • Client Cert Authentication
    • Digest Authentication
    • Form Authentication
    After the authentication we can obtain the username in the the servlet filter. So all the parameters are found now.
    As shown in the digram, when a request comes to a particular weapp which has the engaged Entitlement Servlet Filter it obtains the parameters UserName, ResourceName, Action and Environment. Then it initialize the PEP Proxy to communicate with WSO2 IS. After that it send the parameters and get the Entitlement decision. On the provided decision it stop or pass the request which has came to the web app. 

    The next critical thing is how the user can engage the Entitlement Servlet Filter. For that we  use the web.xml. The following shows a example web.xml which configures the Entitlement Servlet Filter.


    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             id="WebApp_ID" version="2.5">
    
        <!-- Sample deployment descriptor configuration for EntitlementFilter -->
    
        <!-- The scope in which the subject would be available.  Legal values are basicAuth,request-param, request-attribute, session -->
        <context-param>
            <param-name>subjectScope</param-name>
            <param-value>request-param</param-value>
        </context-param>
    
        <!-- The name of the identifier by which to identify the subject -->
        <context-param>
            <param-name>subjectAttributeName</param-name>
            <param-name>userName</param-name>
        </context-param>
    
        <!-- The username to perform EntitlementService query-->
        <context-param>
            <param-name>userName</param-name>
            <param-value>admin</param-value>
        </context-param>
    
        <!-- The password to perform EntitlementService query -->
        <context-param>
            <param-name>password</param-name>
            <param-value>admin</param-value>
        </context-param>
    
        <!-- The URL to perform EntitlementService query-->
        <context-param>
            <param-name>remoteServiceUrl</param-name>
            <param-value>https://localhost:9443/services</param-value>
        </context-param>
    
        <!-- EntitlementFilter Settings -->
        <filter>
            <filter-name>EntitlementFilter</filter-name>
            <filter-class>org.wso2.carbon.identity.entitlement.filter.EntitlementFilter</filter-class>
    
            <!--
      Client class that extends AbstractEntitlementServiceClient. Legal values are basicAuth, soap and thrift.
      Default is 'thrift'.
            -->
            <init-param>
                <param-name>client</param-name>
                <param-value>basicAuth</param-value>
            </init-param>
    
            <!--
             Decision caching at PEPProxy. Legal values are simple and carbon.This parameter is optional.
             If not specified no caching is done.
            -->
            <init-param>
                <param-name>cacheType</param-name>
                <param-value>simple</param-value>
            </init-param>
    
            <!--
             Maximum number of cached entries. Legal values are between 0 and 10000 .
             Only works with caching.
            -->
            <init-param>
                <param-name>maxCacheEntries</param-name>
                <param-value>1000</param-value>
            </init-param>
    
            <!-- Time interval for which cached entry is valid. Only works with simple cache type. -->
            <init-param>
                <param-name>invalidationInterval</param-name>
                <param-value>100000</param-value>
            </init-param>
    
            <!-- URL ro redirect to if authorization fails -->
            <init-param>
                <param-name>authRedirectUrl</param-name>
                <param-value>/index.jsp</param-value>
            </init-param>
    
            <!-- Thrift host to be used if using 'thrift' client -->
            <init-param>
                <param-name>thriftHost</param-name>
                <param-value>localhost</param-value>
            </init-param>
    
            <!-- Thrift port to be used if using 'thrift' client -->
            <init-param>
                <param-name>thriftPort</param-name>
                <param-value>10500</param-value>
            </init-param>
        </filter>
    
        <!-- Filter mappings used to configure URLs that need to be authorized  -->
        <filter-mapping>
            <filter-name>EntitlementFilter</filter-name>
            <url-pattern>/protected.jsp</url-pattern>
        </filter-mapping>
    
        <!-- Mandatory mapping that needs to be present to work with PEP cache update authorization-->
        <filter-mapping>
            <filter-name>EntitlementFilter</filter-name>
            <url-pattern>/updateCacheAuth.do</url-pattern>
            <dispatcher>FORWARD</dispatcher>
        </filter-mapping>
    
        <!-- EntitlementCacheUpdateServlet settings-->
        <servlet>
            <servlet-name>EntitlementCacheUpdateServlet</servlet-name>
            <servlet-class>org.wso2.carbon.identity.entitlement.filter.EntitlementCacheUpdateServlet
            </servlet-class>
    
            <!-- HTTPS port of the web container used when redirecting request to come over https port for cache update authentication -->
            <init-param>
                <param-name>httpsPort</param-name>
                <param-value>9453</param-value>
            </init-param>
    
            <!-- Authentication mode for cache update. Legal values are webapp and wso2is -->
            <init-param>
                <param-name>authentication</param-name>
                <param-value>webapp</param-value>
            </init-param>
    
            <!-- Authentication page used for cache update authentication. Legal values are default and custom -->
            <init-param>
                <param-name>authenticationPage</param-name>
                <param-value>default</param-value>
            </init-param>
    
            <!-- Authentication page URL used for cache update authentication. Works only with custom for authenticationPage -->
            <init-param>
                <param-name>authenticationPageUrl</param-name>
                <param-value>/updateCache.html</param-value>
            </init-param>
        </servlet>
    
        <!-- Servlet mapping needed for cache update authentication -->
        <servlet-mapping>
            <servlet-name>EntitlementCacheUpdateServlet</servlet-name>
            <url-pattern>/updateCache.do</url-pattern>
        </servlet-mapping>
    </web-app>