com.lp.alm.lyo.client.oslc.jazz.JazzFormAuthClient.java Source code

Java tutorial

Introduction

Here is the source code for com.lp.alm.lyo.client.oslc.jazz.JazzFormAuthClient.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2016 IBM Corporation.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 *  and the Eclipse Distribution License is available at
 *  http://www.eclipse.org/org/documents/edl-v10.php.
 *
 *  Contributors:
 *
 *    Michael Fiedler    - initial API and implementation
 *    Michael Fiedler    - refactoring to remove un-needed GETs in formLogin()
 *    Samuel Padgett     - improve error handling
 *   Jim Ruehin          - added Basic auth for Jazz Auth Server 6.x
 *******************************************************************************/
package com.lp.alm.lyo.client.oslc.jazz;

import java.io.IOException;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.*;

import com.lp.alm.lyo.client.exception.JazzAuthErrorException;
import com.lp.alm.lyo.client.exception.JazzAuthFailedException;
import com.lp.alm.lyo.client.oslc.OslcClient;

import org.apache.xerces.impl.dv.util.Base64;

/**
 * An OSLC client for IBM Rational Jazz servers using Form Auth to authenticate.
 * Accesses the Jazz rootservices URL to lookup the OSLC Catlalog location
 *
 * This class is not currently thread safe.
 *
 */
public class JazzFormAuthClient extends OslcClient {

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

    private String url;
    private String authUrl;
    private String project;
    private String user;
    private String password;
    private HttpResponse lastRedirectResponse = null;
    private String jsaCsrfCookie = null;

    private static final String JAZZ_AUTH_MESSAGE_HEADER = "X-com-ibm-team-repository-web-auth-msg";
    private static final String JAZZ_AUTH_FAILED = "authfailed";
    private static final String WWW_AUTHENTICATE_HEADER = "WWW-Authenticate";
    private static final String JAZZ_JSA_REDIRECT_HEADER = "X-JSA-AUTHORIZATION-REDIRECT";

    public JazzFormAuthClient() {
        super();
    }

    /**
     * Create a new Jazz Form Auth client for the given URL, user and password
     *
     * @param url - the URL of the Jazz server, including the web app context
     * @param user
     * @param password
     **/
    public JazzFormAuthClient(String url, String user, String password) {
        this();
        this.url = url;
        this.authUrl = url; //default to base URL
        this.user = user;
        this.password = password;

    }

    /**
     * Create a new Jazz Form Auth client for the given URL, user and password
     *
     * @param url - the URL of the Jazz server, including the web app context
     * @param authUrl - the base URL to use for authentication.  This is normally the
     * application base URL for RQM and RTC and is the JTS application URL for fronting
     * applications like RRC and DM.
     * @param user
     * @param password
     **/
    public JazzFormAuthClient(String url, String authUrl, String user, String password) {
        this(url, user, password);
        this.authUrl = authUrl;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getAuthUrl() {
        return authUrl;
    }

    public void setAuthUrl(String authUrl) {
        this.authUrl = authUrl;
    }

    public String getProject() {
        return project;
    }

    public void setProject(String project) {
        this.project = project;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Executes the sequence of HTTP requests to perform a form login to a Jazz server
     *
     * @throws JazzAuthFailedException
     * @throws JazzAuthErrorException
     * @throws IOException
     * @throws ClientProtocolException
     *
     * @return The HTTP status code of the final request to verify login is successful
     **/
    public int login()
            throws JazzAuthFailedException, JazzAuthErrorException, ClientProtocolException, IOException {

        int statusCode = -1;
        String location = null;
        HttpResponse resp;

        HttpGet authenticatedIdentity = new HttpGet(this.authUrl + "/authenticated/identity");
        resp = httpClient.execute(authenticatedIdentity);
        statusCode = resp.getStatusLine().getStatusCode();
        location = getHeader(resp, "Location");
        EntityUtils.consume(resp.getEntity());
        statusCode = followRedirects(statusCode, location);

        System.out.println("Jazz login status code is " + statusCode);
        // Check to see if the response is from a Jazz Authorization Server that supports OIDC.
        // In CLM 6.x, the JAS supports Basic auth to be compatible with earlier releases.
        // If we're talking to a JAS that supports OIDC, re-do the request with a Basic auth header to gain access.
        if (HttpStatus.SC_UNAUTHORIZED == statusCode) { // this might be a JSA server. 
            if (true == handleJsaServer()) {
                // Re-do the original request using Basic auth, starting at the last authorization redirect.
                authenticatedIdentity = new HttpGet(
                        lastRedirectResponse.getFirstHeader(JAZZ_JSA_REDIRECT_HEADER).getValue() + "&prompt=none");
                String credentials = new String(user + ":" + password);
                authenticatedIdentity.setHeader("Authorization",
                        "Basic " + Base64.encode(credentials.getBytes("UTF-8")));
                resp = httpClient.execute(authenticatedIdentity);
                statusCode = resp.getStatusLine().getStatusCode();
                EntityUtils.consume(resp.getEntity());
                statusCode = followRedirects(statusCode, getHeader(resp, "Location"));
            }
        }

        HttpPost securityCheck = new HttpPost(this.authUrl + "/j_security_check");
        StringEntity entity = new StringEntity("j_username=" + this.user + "&j_password=" + this.password);
        securityCheck.setHeader("Accept", "*/*");
        securityCheck.setHeader("X-Requested-With", "XMLHttpRequest");
        securityCheck.setEntity(entity);
        securityCheck.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
        securityCheck.addHeader("OSLC-Core-Version", "2.0");
        securityCheck.addHeader("Cookie", jsaCsrfCookie);

        resp = httpClient.execute(securityCheck);
        statusCode = resp.getStatusLine().getStatusCode();
        System.out.println("After intital Jazz login status code is " + statusCode);

        String jazzAuthMessage = null;
        Header jazzAuthMessageHeader = resp.getLastHeader(JAZZ_AUTH_MESSAGE_HEADER);

        System.out.println("jazzAuthMessageHeader is " + jazzAuthMessageHeader);

        if (jazzAuthMessageHeader != null) {
            jazzAuthMessage = jazzAuthMessageHeader.getValue();
        }

        if (jazzAuthMessage != null && jazzAuthMessage.equalsIgnoreCase(JAZZ_AUTH_FAILED)) {
            EntityUtils.consume(resp.getEntity());
            throw new JazzAuthFailedException(this.user, this.url);
        } else if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_MOVED_TEMPORARILY) {
            EntityUtils.consume(resp.getEntity());
            throw new JazzAuthErrorException(statusCode, this.url);
        } else //success
        {
            location = getHeader(resp, "Location");
            EntityUtils.consume(resp.getEntity());
            statusCode = followRedirects(statusCode, location);
        }

        return statusCode;
    }

    /**
     * Checks to see if we're communicating with a JSA server (SSO), and handles auth for JSA
     * This function is called because the server returned a 401 (UNAUTHORIZED).
     * If this is not a JSA server, we return to the normal flow. IF it is a server, we get the 
     * 
     * @return true if it's a JSA server, else false.
     * 
     * @throws IOException 
     * @throws ClientProtocolException 
     * @throws JazzAuthErrorException 
        
     */
    private Boolean handleJsaServer() throws ClientProtocolException, IOException, JazzAuthErrorException {
        if (null == lastRedirectResponse) {
            return false;
        }

        // If this is a JAS response supporting OIDC, then we expect both Basic and Bearer challenges
        Header[] authHeaders = lastRedirectResponse.getHeaders(WWW_AUTHENTICATE_HEADER);

        if (2 > authHeaders.length) { // if we don't have at least 2 auth headers then it's not JSA
            return false; //throw new JazzAuthFailedException(this.user,this.url);
        }

        Boolean basicChallenge = false, bearerChallenge = false;
        for (Header theHeader : authHeaders) {
            if (theHeader.getValue().contains("Basic")) {
                basicChallenge = true;
            } else if (theHeader.getValue().contains("Bearer")) {
                bearerChallenge = true;
            }
        }
        if (!basicChallenge || !bearerChallenge) { // didn't get both challenges, so this isn't a JSA server
            return false; //throw new JazzAuthFailedException(this.user,this.url);
        }

        // Check for the JSA authoriziation redirect header. If we don't have it, it's not a JSA server.
        Header jsaRedirectHeader = lastRedirectResponse.getFirstHeader(JAZZ_JSA_REDIRECT_HEADER);
        if (null == jsaRedirectHeader) {
            return false;
        }

        // Passed all checks - we're interacting with a JAS.
        return true;
    }

    /**
     * Executes the sequence of HTTP requests to perform a form login to a Jazz server
     * @throws JazzAuthFailedException
     * @throws JazzAuthErrorException
     *
     * @return The HTTP status code of the final request to verify login is successful
     *
     * @deprecated Use {@link #login()}.
     */
    @Deprecated
    public int formLogin() throws JazzAuthFailedException, JazzAuthErrorException {
        try {
            return login();
        } catch (JazzAuthFailedException jfe) {
            throw jfe;
        } catch (JazzAuthErrorException jee) {
            throw jee;
        } catch (Exception e) {
            logger.error(e.getLocalizedMessage());
            return -1;
        }
    }

    private int followRedirects(int statusCode, String location) throws ClientProtocolException, IOException {
        while (((statusCode == HttpStatus.SC_MOVED_TEMPORARILY) || (HttpStatus.SC_SEE_OTHER == statusCode))
                && (location != null)) {
            HttpGet get = new HttpGet(location);

            lastRedirectResponse = this.httpClient.execute(get);
            statusCode = lastRedirectResponse.getStatusLine().getStatusCode();
            location = getHeader(lastRedirectResponse, "Location");
            EntityUtils.consume(lastRedirectResponse.getEntity());
        }

        return statusCode;
    }

    private String getHeader(HttpResponse resp, String headerName) {
        String retval = null;
        Header header = resp.getFirstHeader(headerName);
        if (header != null)
            retval = header.getValue();
        return retval;
    }
}