Wednesday, December 9, 2015

An Alternative Way for Diaplaying Tenant Domains Dropdown in SSO Login Page Using a Custom Authenticator

In WSO2 Identity Server, the Tenant Domains List Dropdown feature [2] is useful when the users may login to different tenants for authentication in a scenario where a SAAS is enabled in the Service Provider.


However in order to enable this feature, we need to set many configuration changes and also enable Mutual SSL authenticator in the Identity Server. If the environment is clustered having multiple Identity Server nodes, then we need to add several other configurations also to communicate between the servers to keep the active tenant domains list consistent among the nodes. When the cluster is fronted by a Load Balancer like Amazon ELB, there can be problem due to not supporting certificate based authentication (Mutual SSL).

However instead of using this feature, we can get the same output by writing a custom authenticator where inside the authenticator’s code, it can get the list of active tenants and send the list to the login page of the authentication endpoint webapp. Then in the login page, it can extract the tenant list and display in a dropdown. From the authenticator to the authentication endpoint webapp, the tenant list will be sent through a query parameter. When there are many tenants created, this will not be a good solution as the URL size increases.

However this solution would be useful in a scenario like below.

  1. The server is configured with multi step authentication.
  2. Assume the authentication Step 1 is a federated authenticator.
  3. User first logs into the federated authenticator.
  4. Then Identity Server receives claims from the federated authenticator in authentication Step 1.
  5. Based on some claim, inside the authentication Step 2 which is the custom authenticator (local), we can take a decision on which tenants to display. This can be based on the roles that the user (logged into Step 1) has.
  6. In such case, we can display a subset of all the active tenants where the user has some association with.

In a scenario like above, instead of using the Tenant Domains List Dropdown feature which Identity Server provides, we can implement our own custom authenticator and get our requirements satisfied.

In this blog post, I am providing a sample maven project and demonstrate how to get the tenant list displayed in the login page of authentication endpoint webapp.

For the demonstration I am using the Identity Server 5.0.0 version with Service Pack 1 installed.

First you need to clone the Github repository [2] which has the required resources. The maven project of the custom authenticator is the directory with name MyCustomAuthenticator [3]. You can build this project and get the jar file of the autheticator. Or else you can use the pre-built org.wso2.carbon.identity.application.authenticator.mycustomauthenticator-4.2.2.jar file available in the target directory of the project.

Copy the org.wso2.carbon.identity.application.authenticator.mycustomauthenticator-4.2.2.jar file into the IS_HOME/repository/components/dropins directory. (This is because the authenticator is written as an OSGI bundle).

Next step is to copy the two files in “AuthenticationEndpoint Webapp Changes” [4] directory to IS_HOME/repository/deployment/server/webapps/authenticationendpoint/ directory. Here you will be replacing the login.jsp file which is already existing. The customauth.jsp file is a new file which has the UI related to the authenticator. The only change in the login.jsp is that it checks the name of the authenticator and if it is our custom authenticator, then it includes the customauth.jsp file to be displayed.

Now we have done all the changes. Restart the Identity Server.

Then we need to setup a client application which uses Identity Server for authentication. For that, I am setting up the travelocity sample webapp following the steps described in [5].

In the Service Provider, I have enabled ‘Saas Application’ so that users from any tenant can authenticate with this Service Provider.

If you login to the travelocity.com sample application, you will see the usual login page because by default, the Basic Authenticator is used for authentication in the Service Provider.


Now we need to engage our custom authenticator with the Service Provider. Edit the Service Provider configuration and expand the ‘Local & Outbound Authentication Configuration’ section.


In the dropdown associated with the Local Authentication, it should list our custom authenticator with the name ‘custom’. Select the radio button ‘Local Authentication’ and from the authenticators dropdown, select the custom authenticator. Then save the Service Provider configuration. (If you want to use this for a scenario like multi step authentication, then you need to select ‘Advanced Configuration’ and setup this authenticator in a particular step).


Now try to login again to the travelocity.com sample. Then if you do not have any tenants created in the Identity Server, or if you do not have any active tenant (other than super tenant), you will see the below login page. This is loaded from the customauth.jsp file we added to authenticationendpoint webapp previously. You can change the styles as you wish.

If you have active tenants, then those will be displayed in a dropdown in the login page. By default there will be ‘Super Tenant’ in the list which is added from the customauth.jsp.

You can select a particular tenant from the dropdown.


As the username, you do not need to append the tenant domain now. Just type the username (without @tenantdomain), enter the password and you should be able to login to the travelocity sample app.

This way you can get the same functionality as Tenant Domains List Dropdown feature without any configuration other than Service Provider’s configuration.

If we talk about the technical aspects of this, inside the MyCustomAuthenticato.java [6] it has initiateAuthenticationRequest method where it retrieves the list of active tenants and sends to the authenticationendpoint as a query parameter with the name tenantList. The value is a comma separated string with all active tenant domains. Then inside the authenticationendpoint webapp, in customauth.jsp file, it retrieves this query parameter, split the string with “,” delimiter and load the tenant domain names to a dropdown in the UI.

So, in a usecase like multi step authentication which I mentioned previously, what you can do is, after the Step1 (federated authenticator) authentication, you can retrieve claims from the IDP. For example you may retrieve a role claim, and then inside this custom authenticator which is the Step2 of our authentication sequence in that usecase, you can access that particular claim received in previous step and take a decision. An example is based on the roles the user is granted, select a subset of tenants which are related to the user. And then send the list to the authenticationendpoint so that this user will not see all available tenants, instead the user will see a the particular set of tenants which the user is related to.

There can be many usecases where this kind of an implementation would be useful. You can refer to the code and modify it according to your requirements.

References :


Tharindu Edirisinghe
Identity Server Team
WSO2

Tuesday, December 8, 2015

Tracking Last Successful Login Attempt Timestamp of Users with WSO2 Identity Server

In Identity and Access Management Systems, it is important to track the timestamp of last login attempt of users. However WSO2 Identity Server currently (IS 5.1.0 or before) does not support this feature out of the box. When a user logs into the Management Console, Identity Server is adding the following entry to the IS_HOME/repository/logs/wso2carbon.log file.

[2015-12-07 20:58:29,363]  INFO {org.wso2.carbon.core.services.util.CarbonAuthenticationUtil} -  '[email protected] [-1234]' logged in at [2015-12-07 20:58:29,363-0800]

Similarly, when the user logs out, following is added to the same log.

[2015-12-07 21:02:45,295]  INFO {org.wso2.carbon.core.services.authentication.AuthenticationAdmin} -  '[email protected] [-1234]' logged out at [2015-12-07 21:02:45,0295]

At the same time, it adds another entry to IS_HOME/repository/logs/audit.log file upon user login.
[2015-12-07 21:02:35,951]  INFO -  '[email protected] [-1234]' logged in at [2015-12-07 21:02:35,951-0800]

Corresponding entry for user logout is following.
[2015-12-07 21:02:45,296]  INFO -  '[email protected] [-1234]' logged out at [2015-12-07 21:02:45,0295]

However when considering SSO scenario, Identity Server does not persist the timestamp of the last successful login attempt which is a need to have feature. This feature is requested in the JIRA [1] and will be added into a feature version of Identity Server.

However with any WSO2 product, the extensibility is there where we can write a custom component that satisfies our requirement and plug that to the server. In this blog post, I am demonstrating how to write a custom component which can track the last successful login attempt of a user that got authenticated with Identity Server. Here when the user gets authenticated to Identity Server, I am getting the system timestamp and storing it as a claim of the logged in user.

For this demonstration, I am using WSO2 Identity Server 5.0.0 version with Service Pack 1 installed.

First, login to the Management Console of Identity Server and add a new claim under the Default WSO2 Carbon Dialect (http://wso2.org/claims). This is because then we can make this claim appear in the profile of user as an attribute. Here I am giving the claim URI as http://wso2.org/claims/lastLoginTimestamp . You can add any claim URI as you wish and from the custom component, we have to persist the timestamp value with the same claim URI. Here I set ‘Supported by Default’ to ‘true’ so that this claim will appear in the UI when we view the user’s profile. For the mapped attribute, I have added ‘title’ attribute which is a default attribute defined in the underlying LDAP schema of Identity Server. You can add any attribute name which is supported by the underlying userstore.


I have created a user with username ‘tharindu’. Then I view the User Profile of this user.
Now I can see the newly added claim for storing the Last Login Timestamp. Currently it does not have any value.

When writing the custom component, we can either implement the org.wso2.carbon.user.core.listener.UserOperationEventListener [2] interface or extend the org.wso2.carbon.user.core.common.AbstractUserOperationEventListener [3] class. (Currently for Carbon 4.2.0 kernel, the user.core component’s latest patch is patch0013 so here I have referred the links to the SVN for patch0013. But you don’t need to refer any of these classes because we just need to implement one method in our custom component’s class).  

In this example, I am extending the org.wso2.carbon.user.core.common.AbstractUserOperationEventListener class and overriding the doPostAuthenticate method.

The complete source code of this component is available in the GIT repository https://github.com/thariyarox/LastLoginTimestamp  [4].

You can clone this project from github and build it. Then you will get the org.wso2.carbon.custom.user.operation.event.listener-1.0.0.jar file. If you want to skip building the project and just try out this, you can download the pre-built jar file from [5].

Then copy this jar file to IS_HOME/repository/components/dropins directory. (Since this is an OSGI bundle we copy to dropins directory. For non-osgi components, we copy them into the lib directory instead of dropins).

If you want to enable debug logs for this component, you can add following line to IS_HOME/repository/conf/log4j.properties file.
log4j.logger.org.wso2.carbon.custom.user.operation.event.listener=DEBUG

After that restart the server and login to the management console. If you have enabled the debug logs, you will see the following in the wso2carbon.log file (or in the terminal).
[2015-12-07 23:56:35,934] DEBUG {org.wso2.carbon.custom.user.operation.event.listener.CustomUserOperationEventListener} -  Last Login Timestamp for user : tharindu is 1449561395892

Even if you login to a client application that used Identity Server for authentication, those login attempt timestamps are also tracked in the same manner with this.

You can login to the Management Console as admin and view the profile of the user. We can see that the timestamp value for last successful login attempt is persisted as a claim of the user.
In this sample, it is only persisting the timestamp of a successful login attempt. You can modify the code easily to handle failed login attempts scenarios as well according to your requirements.

References :



Tharindu Edirisinghe
Identity Server Team
WSO2

Wednesday, November 25, 2015

Exposing a Servlet from an OSGI Bundle in WSO2 Servers

In this post I'm providing a sample on how to expose a Servlet from an OSGI bundle which can be put in any WSO2 server. One usecase of such a component is when you write a web application to be put in a WSO2 Application Server, you may need to perform some tasks like creating registry resources from the web application. But since it is a web application, it will not be able to access the registry for creating or reading resources. In such case, we can write an OSGI bundle which does the registry operations and expose a servlet to be consumed by the client web application. So from the client web application, it can call this servlet and provide instructions for consuming the registry resources. Then since the servlet is inside an OSGI bundle, it will be able to access registry resources. This is just one usecase and it will be useful in many other scenarios. 

This is a maven project and the file structure is as below.



The pom.xml file is below.

<?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/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.wso2.carbon</groupId>
   <artifactId>org.wso2.carbon.sample.servlet</artifactId>
   <version>1.0</version>
   <packaging>bundle</packaging>
   <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>
   </repositories>
   <dependencies>
       <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
           <version>1.1.1</version>
       </dependency>
       <dependency>
           <groupId>commons-lang</groupId>
           <artifactId>commons-lang</artifactId>
           <version>2.6</version>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>2.0</version>
               <configuration>
                   <source>1.5</source>
                   <target>1.5</target>
               </configuration>
           </plugin>
           <plugin>
               <groupId>org.apache.felix</groupId>
               <artifactId>maven-scr-plugin</artifactId>
               <version>1.0.10</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>
               <version>1.4.0</version>
               <extensions>true</extensions>
               <configuration>
                   <instructions>
                       <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                       <Bundle-Name>${pom.artifactId}</Bundle-Name>
                       <Private-Package>
                           org.wso2.carbon.sample.servlet.internal
                       </Private-Package>
                       <Export-Package>
                           !org.wso2.carbon.sample.servlet.internal,
                           org.wso2.carbon.sample.servlet.*,
                       </Export-Package>
                       <Import-Package>
                           javax.servlet; version=2.4.0,
                           javax.servlet.http; version=2.4.0,
                           org.wso2.carbon.base.*,
                           *;resolution:=optional
                       </Import-Package>
                       <DynamicImport-Package>*</DynamicImport-Package>
                   </instructions>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

First let’s create a Servlet with the name SampleServlet extending the javax.servlet.http.HttpServlet class. This will simply print some message.


package org.wso2.carbon.sample.servlet.servlet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SampleServlet extends HttpServlet {

    private static final long serialVersionUID = -7182121722709941646L;
    private static final Log log = LogFactory.getLog(SampleServlet.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("Sample Servlet doGet got Hit !");

        resp.setContentType("text/plain");
        resp.getWriter().write("You are inside the servlet !");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("Sample Servlet doPost got Hit !");

        resp.setContentType("text/plain");
        resp.getWriter().write("You are inside the servlet !");
    }
}





For registering the Servlet to as an HTTP service, we need to have an instance of org.osgi.service.http.HttpService and for that I am creating a class called DataHolder which is a singleton that holds the HttpService. 


package org.wso2.carbon.sample.servlet;

import org.osgi.service.http.HttpService;

/**
 * Singleton class to hold HTTP Service
 */

public class DataHolder {

    private static volatile DataHolder dataHolder = null;

    private HttpService httpService;

    private DataHolder() {

    }

    public static DataHolder getInstance() {

        if (dataHolder == null) {
            synchronized (DataHolder.class) {
                if (dataHolder == null) {
                    dataHolder = new DataHolder();
                }
            }
        }

        return dataHolder;
    }

    public HttpService getHttpService() {
        return httpService;
    }

    public void setHttpService(HttpService httpService) {
        this.httpService = httpService;
    }

}

Next step is to create a class for activating the OSGI bundle. For that I am creating a class named OSGIServletDSComponent. The bundle will wait for org.osgi.service.http.HttpService and it will call setHttpService method to instantiate the HttpService. In the activate method, I am registering the servlet to the HttpService with the URL /sampleservlet


package org.wso2.carbon.sample.servlet.internal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.equinox.http.helper.ContextPathServletAdaptor;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpService;
import org.wso2.carbon.sample.servlet.DataHolder;
import org.wso2.carbon.sample.servlet.servlet.SampleServlet;

import javax.servlet.Servlet;

/**
 * @scr.component name="osgi.servlet.dscomponent" immediate=true
 * @scr.reference name="osgi.httpservice" interface="org.osgi.service.http.HttpService"
 * cardinality="1..1" policy="dynamic" bind="setHttpService"
 * unbind="unsetHttpService"
 */



public class OSGIServletDSComponent {

    private static Log log = LogFactory.getLog(OSGIServletDSComponent.class);

    public static final String SAMPLE_SERVLET_URL = "/sampleservlet";

    protected void activate(ComponentContext ctxt) {

        // Register Sample Servlet
        Servlet sampleServlet = new ContextPathServletAdaptor(new SampleServlet(), SAMPLE_SERVLET_URL);

        try {
            DataHolder.getInstance().getHttpService().registerServlet(SAMPLE_SERVLET_URL, sampleServlet, nullnull);
        } catch (Exception e) {
            String errMsg = "Error when registering Sample Servlet via the HttpService.";
            log.error(errMsg, e);
            throw new RuntimeException(errMsg, e);
        }

        log.info("OSGI SERVLET  bundle activated successfully..");
    }

    protected void deactivate(ComponentContext ctxt) {

        if (log.isDebugEnabled()) {
            log.debug("OSGI SERVLET  bundle is deactivated ");
        }
    }

    protected void setHttpService(HttpService httpService) {

        DataHolder.getInstance().setHttpService(httpService);

        if (log.isDebugEnabled()) {
            log.info("HTTP Service is set in the OSGI SERVLET bundle");
        }
    }

    protected void unsetHttpService(HttpService httpService) {

        DataHolder.getInstance().setHttpService(null);
        if (log.isDebugEnabled()) {
            log.debug("HTTP Service is unset in the OSGI SERVLET bundle");
        }
    }

}


The source code of the project is available in [1]. Once we have built the project, we need to copy the jar to the <WSO2_SERVER>/repository/components/dropins directory of the server and restart it. Here I am copying this bundle to WSO2 Application Server, but you can try this out with any WSO2 server.

Then in the browser, you can visit the URL https://SERVER_HOST:PORT/sampleservlet . Then the servlet will get hit and you will see the message we wrote inside the servlet. 

 

Referenes :


Tharindu Edirisinghe
Identity Server Team
WSO2