Thursday, July 30, 2015

Integrate Atlassian Jira with SSO

Today we will see how can we integrate Jira with our corp SSO. 


JIRA integrate with SSO system Seraph, the Atlassian authentication library. Seraph is a very simple, pluggable J2EE web application security framework developed by Atlassian and used in our products.
Seraph allows you to write custom authenticators which will accept the login credentials of your existing single sign-on system.
So on a very high level, we need to write a custom Authenticator class which should extend DefaultAuthenticator. And then we need to override login() to check if user has a valid Jira session or is authenticated by our corp SSO.
Next how do we do that?? Here are step by step details


  • Create a jira plugin project:
Although this is not mandatory but I prefer this way, by creating plugin project we have access to all Atlassian libraries.
  • Create CustomSSOAuthenticator class which will handle login authentication of Jira: 
CustomSSOAuthenticator class will extend  com.atlassian.seraph.auth.DefaultAuthenticator abstract class and thus have to implement 2 methods: a) Principal getUser(String username) and b) boolean authenticate(Principal user, String password)
Apart from this we also have to override Principal getUser(HttpServletRequest request,
HttpServletResponse response) method which gets called for every authentication

Now some code:

package com.pratik.jira.sso;

import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

import com.atlassian.crowd.embedded.api.CrowdService;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.seraph.auth.AuthenticatorException;
import com.atlassian.seraph.auth.DefaultAuthenticator;

public class CustomSSOAuthenticator extends DefaultAuthenticator {

//For Logging purpose
private final static Log log = LogFactory
.getLog(CustomSSOAuthenticator.class);

// Randomly generated serial id
private static final long serialVersionUID = -7673520003580149993L;

@Override
protected boolean authenticate(Principal user, String password)
throws AuthenticatorException {
// This method probably never gets called for SSO use case
log.info("Inside custom authenticate. User is " + user + "Password is "
+ password);
return true;
}

@Override
protected Principal getUser(String username) {
// Validating username
log.info("Inside custom getUser. User is " + username);
return getCrowdService().getUser(username);
}

private CrowdService getCrowdService() {
return ComponentAccessor.getCrowdService();
}

public Principal getUser(HttpServletRequest request,
HttpServletResponse response) {
log.info("Inside getUser of CustomSSOAuthenticator");
Principal user = null;

try {

//Check if user is already logged in and has a valid session
if (request.getSession() != null
&& request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY) != null) {
log.info("Session found; user already logged in");
user = (Principal) request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY);
else {

//Get Cookie from SSO (Will write SSOnCookie class in a while)
SSOnCookie ssoCookie = SSOnCookie.getSSOCookie(request);
log.info("Got SSOnCookie " + ssoCookie);

//Check if it is a valid cookie
if (ssoCookie != null && !ssoCookie.isExpired()) {
log.info("Trying seamless Single Sign-on...");

String username = ssoCookie.getLoginId();
log.info("Got username " + username);

if (username != null) {
user = getUser(username);
log.info("Logged in via SSO, with User " + user);

request.getSession().setAttribute(
DefaultAuthenticator.LOGGED_IN_KEY, user);
request.getSession().setAttribute(
DefaultAuthenticator.LOGGED_OUT_KEY, null);
}
else {
log.warn("SSOCookie is null; redirecting");

// user was not found, or not currently valid
return null;
}
}
} catch (Exception e) // catch class cast exceptions
{
log.error("Exception: " + e, e);
}
return user;
}

}

And SSOnCookie class which retrieves cookie values set by Corp SSO. Usually SSO will set authenticated user information as either Domain Cookie or will return to redirected call as encrypted token, what ever be the case we need to read cookie information set by SSO

Code here:


package com.pratik.jira.sso;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

public class SSOnCookie {

//For logging
private final static Log log = LogFactory.getLog(SSOnCookie.class);

//Variable to hold if cookie is expired
boolean isExpired;

//Login ID of user
String loginId;

public SSOnCookie(String loginId,boolean isExpired){
this.loginId=loginId;
this.isExpired=isExpired;
}

public static SSOnCookie getSSOCookie(HttpServletRequest request)
{
try{
//From request object get cookie details and from cookie get username/email
//Jira takes username and not email for authentication
log.debug("Username----"+userName);

return new SSOnCookie(userName, false);
catch(Exception e){
log.error("Exception -- "+e);

return null;
}
    

public void setExpired(boolean isExpired) {
this.isExpired = isExpired;
}

public void setLoginId(String loginId) {
this.loginId = loginId;
}

public boolean isExpired()
{
return this.isExpired;
}

    /** Return the username implied by the cookie in the request. */
public String getLoginId()
{
return this.loginId;
}

@Override
public String toString() {
return "SSOnCookie [isExpired=" + isExpired + ", loginId=" + loginId
+ "]";

}

}

  • Build/Deploy project. This will create a jar file. Copy jar file to <Jira Installation Dir>/atlassian-jira/WEB-INF/lib
  • Change seraph-config.xml
seraph-config.xml file is located under - <Jira Installation Dir>/atlassian-jira/WEB-INF/classes
Make following changes in file:


<param-name>login.url</param-name> 
<param-value>Your SSO URL</param-value>

<param-name>link.login.url</param-name>
<param-value>Your SSO URL</param-value>

<authenticator class="com.inmobi.pratik.sso.CustomSSOAuthenticator"/>

If you have to implement logout from SSO as well just change logout.url in seraph-config.xml

And then restart your Jira Server... Thats All!!! SSO is now configured with Jira


Some other important points:

  • If you want all login to happen via SSO only then you will have to disable login gadget in jpm.xml (<Jira Installation Dir>/atlassian-jira/WEB-INF/classes/jpm.xml): 
<property>            
<key>jira.disable.login.gadget</key>
<default-value>true</default-value>
<type>boolean</type>
<admin-editable>false</admin-editable>
<sysadmin-editable>false</sysadmin-editable>
</property>

  • In Jira if user directly opens login url which is <jira.corp.abc.com/login.jsp> then SSO does not work and it will display Jira login page to user which will block login via SSO. To stop this we need to put a small hack in login.jsp. In login.jsp just before <html> tag put a check to see if user is logged in. If user is logged in do nothing else redirect user to your SSO page. Something like this:
if (request.getSession() != null
&& request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY) != null) {
//Do nothing
}
else{
response.sendRedirect(redirectURL);
}


Thats all. Do let me know if you have any comments and Happy Coding!!

Tuesday, July 28, 2015

Github API using Java for Corp/Enterprise Github

Other day I was trying to use Github API using Java to do some operation on our github corp account. Though there are bunch of Java Libraries available for Git operation, I didn't find a good documentation for any of them.

I used kohsuke github-api library which is very simple and object oriented.
To use this library in maven project add following dependency:

<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.69</version>
</dependency>

  • Connect to Corp Git Hub account:
There are 2 ways to connect to enterprise github account either using OAuth Token, or using username/password. Here are the APIs to do that:
              

org.kohsuke.github.GitHub github = GitHub.connectToEnterprise(
"https://github.corp.<abc>.com/api/v3""<Oauth Token>");

org.kohsuke.github.GitHub github = GitHub.connectToEnterprise(
"https://github.corp.<abc>.com/api/v3""<User Name>","<Password>");

  • Get your information
GHMyself my = github.getMyself();

  • Search a keyword in code
Create GHContentSearchBuilder object from github object. Set keyword in GHContentSearchBuilder object.

GHContentSearchBuilder search = github.searchContent();
GHContentSearchBuilder s = search.q(<keyword>);
PagedSearchIterable<GHContent> res = s.list();

  • Get repositories for each searched code

For each GHContent object use getOwner()

for (GHContent ghContent : res) {
repoSet.add(ghContent.getOwner()
.getFullName());
}

  • Search repositories

Create GHRepositorySearchBuilder object from github object. Set keyword in GHRepositorySearchBuilder object.

GHRepositorySearchBuilder repo = github.searchRepositories();
GHRepositorySearchBuilder repo = repo.q(<keyword>);
PagedSearchIterable<GHRepository> repoList = repo.list();

And there is much more, APIs are simple and you can explore it more to get other information. Feel free to drop a comment if you have any question and till than Happy Coding :)

Wednesday, July 22, 2015

Lets understand Apache Thrift

Apache Thrift is framework, for implementing RPC in services, with cross-language support. 
RPC (Remote Procedure Call) is very similar to normal function, only that it is present remotely on a different server as a service. A service exposes many such functions/procedure to its client. And client requires some way to know what are the functions/procedures exposed by this service and what are their parameters.

This is where Apache Thrift comes in. It has its own "Interface Definition Language" (IDL). In this language you define what are the functions and what are their parameters. And then use Thrift compiler to generate corresponding code for any language of your choice. What this means, is that you can implement a function in java, host it on a server and then remotely call it from python or any other language for that matter. 


In this post we will try to understand what thrift does, its architecture and components:

Thrift networking stack can be represented as:



Lets understand each component.

Transport

The Transport layer provides a simple abstraction for reading/writing from/to the network. This enables Thrift to decouple the underlying transport from the rest of the system (serialization/deserialization, for instance).

Here are some of the methods exposed by the Transport interface:
  • open
  • close
  • read
  • write
  • flush
Here are some of the transports available for majority of the Thrift-supported languages:
  • file: read/write to/from a file on disk
  • http: as the name suggests
Eg - We create a thrift HTTP transport in java as following
org.apache.thrift.transport.THttpClient transport = new THttpClient("http://" + serverIP + ":" + serverPortserviceUrl);

Protocol

The Protocol abstraction defines a mechanism to map in-memory data structures to a wire-format. In other words, a protocol specifies how datatypes use the underlying Transport to encode/decode themselves. Thus the protocol implementation governs the encoding scheme and is responsible for (de)serialization. Some examples of protocols in this sense include JSON, XML, plain text, compact binary etc.

Eg- We create a thrift Binary Protocol in java as following
org.apache.thrift.protocol.TProtocol protocol = new TBinaryProtocol(transport);

Processor

A Processor encapsulates the ability to read data from input streams and write to output streams. The input and output streams are represented by Protocol objects. Service-specific processor implementations are generated by the compiler. The Processor essentially reads data from the wire (using the input protocol), delegates processing to the handler (implemented by the user) and writes the response over the wire (using the output protocol).

Incase you have to create a thrift processor (Eg - Thrift with servlet), we create as following in java
Processor<Iface> processor = new Processor<Iface>(<ThriftInterfaceImpl.Iface>);

Server

A Server pulls together all of the various features described above. 


  • Create a transport
  • Create input/output protocols for the transport
  • Create a processor based on the input/output protocols
  • Wait for incoming connections and hand them off to the processor
That concludes this session, do let me know if you have any comments. And Happy coding :)

Tuesday, July 7, 2015

Using Thrift with Java servlet

Today we will see how can we use Thrift for webservice call in Java. 

We will start with our Thrift Servlet. This will be a very simple class which will be used to delegate our http request

public class ThriftDelegateServlet extends TServlet {

private static final long serialVersionUID = 5744887369513855991L;

public ThriftDelegateServlet(TProcessor processor, TProtocolFactory protocolFactory) {
super(processor, protocolFactory);
}

@Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        super.doPost(request, response);
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        super.doGet(request, response);
    }

}

As you can see, we are just extending org.apache.thrift.server.TServlet class and then overriding doPost and doGet method.

Next we will create our servlet class which will be used as end point of our service. 

public class TestServlet extends HttpServlet {

private static final long serialVersionUID = -8777659712204723236L;
private ThriftDelegateServlet thriftServlet;

    @Override
    public void init() throws ServletException {

            Iface serviceHandler = BeanFactoryHolder.getBean("serviceImpl");

            Processor<Iface> processor = new Processor<Iface>(serviceHandler);
            thriftServlet = new ThriftDelegateServlet(processor, new TBinaryProtocol.Factory());
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

            if (thriftServlet == null) {
                init();
            }
            thriftServlet.doPost(req, resp);

    }

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

        this.doPost(req, resp);
    }

}


Self explanatory, but couple of points to add here:
  • In the init(), we are using Bean Factory to get bean of "serviceImpl". serviceImpl is basically our service implemented code i.e Implementation of your Thrift IDL. Hence it will give you an object of <Class>.Iface
  • Next we will create a Thrift Processor of Iface type
  • And finally create an object of ThriftDelegateServlet using processor and thrift protocol (TBinary protocol in our case)
  • When ever we get a request (doPost()) we just see if thriftdelegate is initialised and pass the request to thriftDelegate
Finally we will see how our web.xml should look:
  • Add SpringContext
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
            classpath*:context.xml
</param-value>
</context-param>
  • Add Spring listner

<!-- Spring Listeners -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

  • Add Servlet details

<!-- Start: Servlet for starting thrift over http protocol -->
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.test.tutorial.service.thrift.common.TestServlet</eservlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/service</url-pattern>
</servlet-mapping>

That's it, we are good to go now. We can now create thrift HTTP client and hit webservice. This is how we can create client:

private final String serviceUrl = "/dummy/service";
protected Client getClient() {

THttpClient transport = null;
try {
transport = new THttpClient("http://" + serverIP + ":" + serverPort
+ serviceUrl);
} catch (TTransportException e) {
throw new RuntimeException(e);
}
TProtocol protocol = new TBinaryProtocol(transport);
return new Client(protocol);
}

Before we wrap this is what happens in a nut shell:

  • We create a servlet which is our end point for webservice
  • Whenever a request comes it will first call its init() where we initialise thriftDelegateServlet by passing Thrift Processor and Protocol. To create Processor we get an object of Service Implemented Class
  • ThriftDelegateServlet simply passes the call to thrift Servlet
Will come back with more posts, do let me know your thought and till then Happy Coding :)