mobi.jenkinsci.ci.client.sso.GoogleSsoHandler.java Source code

Java tutorial

Introduction

Here is the source code for mobi.jenkinsci.ci.client.sso.GoogleSsoHandler.java

Source

// Copyright (C) 2013 GerritForge www.gerritforge.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mobi.jenkinsci.ci.client.sso;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.util.HashMap;

import mobi.jenkinsci.ci.client.JenkinsFormAuthHttpClient;
import mobi.jenkinsci.exceptions.TwoPhaseAuthenticationRequiredException;

import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.protocol.HttpContext;
import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;

public class GoogleSsoHandler implements SsoHandler {
    public static final String GOOGLE_ANDROID_APPS_AUTHENTICATOR2_APP_ID = "com.google.android.apps.authenticator2";
    static final Logger log = Logger.getLogger(GoogleSsoHandler.class);
    private static final HashMap<FormId, String> ssoFormIds = new HashMap<SsoHandler.FormId, String>();

    public static void init() {
        ssoFormIds.put(SsoHandler.FormId.USER, "Email");
        ssoFormIds.put(FormId.PASS, "Passwd");
        JenkinsFormAuthHttpClient.registerSsoHander("google.com", new GoogleSsoHandler());
    }

    @Override
    public IOException getException(final HttpResponse response) {
        final StatusLine httpStatusLine = response.getStatusLine();
        final int statusCode = httpStatusLine.getStatusCode();
        switch (statusCode) {
        case HttpURLConnection.HTTP_OK:
            try {
                final Document responseDoc = Jsoup.parse(response.getEntity().getContent(), "UTF-8", "");
                final Element errorDiv = responseDoc.select("div[id~=(error|errormsg)").first();
                if (errorDiv != null) {
                    return new IOException(getDivText(errorDiv));
                }
            } catch (final Exception e) {
            }
            // Break is not needed here: we want to fallback to the 'default' case
            // if no error div is found

        default:
            return new IOException("Google Authentication FAILED");
        }
    }

    @Override
    public String getSsoLoginFieldId(final FormId formId) {
        return ssoFormIds.get(formId);
    }

    @Override
    public String doTwoStepAuthentication(final HttpClient httpClient, final HttpContext httpContext,
            final HttpResponse response, final String otp) throws IOException {
        final HttpPost formPost = getOtpFormPost(httpContext, response, otp);
        Element otpResponseForm;
        try {
            final HttpResponse otpResponse = httpClient.execute(formPost, httpContext);
            if (otpResponse.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_OK) {
                throw getException(otpResponse);
            }

            final Document otpResponseDoc = Jsoup.parse(otpResponse.getEntity().getContent(), "UTF-8", "");
            otpResponseForm = otpResponseDoc.select("form[id=hiddenpost]").first();
            if (otpResponseForm == null) {
                final Element errorDiv = otpResponseDoc.select("div[id=error]").first();
                if (errorDiv == null) {
                    throw new IOException(
                            "2nd-step authentication FAILED: Google did not return positive response form.");
                } else {
                    throw new TwoPhaseAuthenticationRequiredException(getDivText(errorDiv),
                            GOOGLE_ANDROID_APPS_AUTHENTICATOR2_APP_ID);
                }
            }
        } finally {
            formPost.releaseConnection();
        }

        final HttpPost formCompletePost = JenkinsFormAuthHttpClient
                .getPostForm(JenkinsFormAuthHttpClient.getLatestRedirectedUrl(httpContext), otpResponseForm, null);
        try {
            final HttpResponse otpCompleteResponse = httpClient.execute(formCompletePost, httpContext);
            if (otpCompleteResponse.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_MOVED_TEMP) {
                throw new IOException(
                        String.format("2nd-step authentication failed: Google returned HTTP-Status:%d %s",
                                otpCompleteResponse.getStatusLine().getStatusCode(),
                                otpCompleteResponse.getStatusLine().getReasonPhrase()));
            }

            return otpCompleteResponse.getFirstHeader("Location").getValue();
        } finally {
            formCompletePost.releaseConnection();
        }
    }

    private String getDivText(final Element errorDiv) {
        for (final Node child : errorDiv.childNodes()) {
            if (child instanceof TextNode) {
                return ((TextNode) child).getWholeText().trim();
            }
        }
        return "";
    }

    private HttpPost getOtpFormPost(final HttpContext httpContext, final HttpResponse response, final String otp)
            throws IOException, MalformedURLException, UnsupportedEncodingException {
        final String requestUri = JenkinsFormAuthHttpClient.getLatestRedirectedUrl(httpContext);
        final String requestBaseUrl = requestUri.substring(0, requestUri.lastIndexOf('/'));
        log.debug("Looking for HTML input form retrieved from " + requestUri);
        final Document doc = Jsoup.parse(response.getEntity().getContent(), "UTF-8", requestBaseUrl);
        final org.jsoup.nodes.Element form = doc.select("form[id=verify-form]").first();

        if (otp == null) {
            final Element otpLabel = doc.select("div[id=verifyText]").first();
            throw new TwoPhaseAuthenticationRequiredException("Google 2-step Authenticator: \n"
                    + "1. Tap on AuthApp to authenticate.\n" + "2. " + getDivText(otpLabel),
                    GOOGLE_ANDROID_APPS_AUTHENTICATOR2_APP_ID);
        }

        final HashMap<String, String> formMapping = new HashMap<String, String>();
        formMapping.put("smsUserPin", otp);
        return JenkinsFormAuthHttpClient.getPostForm(requestBaseUrl, form, formMapping);
    }

    @Override
    public String getSsoLoginButtonName() {
        return null;
    }
}