com.esri.gpt.framework.security.identity.open.OpenidConsumerServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.esri.gpt.framework.security.identity.open.OpenidConsumerServlet.java

Source

/* See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Esri Inc. licenses this file to You 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 com.esri.gpt.framework.security.identity.open;

import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.security.identity.local.LocalDao;
import com.esri.gpt.framework.security.principal.User;
import com.esri.gpt.framework.util.Val;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.expressme.openid.Association;
import org.expressme.openid.Authentication;
import org.expressme.openid.Endpoint;
import org.expressme.openid.OpenIdException;
import org.expressme.openid.OpenIdManager;

import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;

/**
 * Openid authentication consumer servlet (Openid, oAuth-Twitter).
 * <p/>
 * This is basically a replacement for org.expressme.openid.MainServlet.
 * <p/>
 * An Openid provider must return at least an identifier and an email address.
 * <p/>
 * Twitter return a screen name.
 */
public class OpenidConsumerServlet extends HttpServlet {

    /*
    google/yahoo
    DN = urn:openid:http://host/...?id=...
    username = email
        
    twitter
    DN = urn:openid:twitter:screenname
    username = screenname@twitter
    */

    private static Logger LOGGER = Logger.getLogger(OpenidConsumerServlet.class.getName());

    private static final long ONE_HOUR = 3600000L;
    private static final long TWO_HOUR = ONE_HOUR * 2L;
    private static final String ATTR_ALIAS = "gpt_openid_alias";
    private static final String ATTR_CBINFO = "gpt_openid_cbinfo";
    private static final String ATTR_MAC = "gpt_openid_mac";
    private static final String ATTR_TOKEN = "gpt_oauth_token";
    private static final String ATTR_TOKEN_SECRET = "gpt_oauth_token_secret";

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        RequestContext context = null;
        boolean useFacade = false;
        String err = "";
        try {
            LOGGER.finer("Query string=" + request.getQueryString());

            String op = request.getParameter("op");
            context = RequestContext.extract(request);
            OpenProviders providers = context.getIdentityConfiguration().getOpenProviders();
            if ((providers == null) || (providers.size() == 0)) {
                return;
            }
            String baseContextPath = RequestContext.resolveBaseContextPath(request);
            String callbackUrl = baseContextPath + "/openid";
            String realm = baseContextPath;
            HttpSession session = request.getSession();

            // process a response from an Openid provider
            if (op == null) {
                String identity = null;
                String username = null;
                String email = null;

                // determine the callback info
                String cbinfo = Val.chkStr((String) session.getAttribute(ATTR_CBINFO));
                session.setAttribute(ATTR_CBINFO, null);
                if (cbinfo.length() == 0) {
                    throw new ServletException("Invalid openid callback info.");
                }

                int idx = cbinfo.indexOf(",");
                long millis = Long.parseLong(cbinfo.substring(0, idx));
                cbinfo = cbinfo.substring(idx + 1);
                idx = cbinfo.indexOf(",");
                String cbid = cbinfo.substring(0, idx);
                cbinfo = cbinfo.substring(idx + 1);
                idx = cbinfo.indexOf(",");
                op = cbinfo.substring(0, idx);
                String fwd = cbinfo.substring(idx + 1);
                LOGGER.finer("cbinfo retrieved: " + cbinfo);

                // determine the provider
                OpenProvider provider = providers.get(op);
                if (provider == null) {
                    throw new ServletException("Invalid openid op parameter on callback: " + op);
                }
                boolean isTwitter = provider.getName().equalsIgnoreCase("Twitter");

                // determine the authenticated user attributes
                if (useFacade) {
                    identity = "http://openidfacade/user123";
                    email = "user123@openidfacade.com";
                    username = email;

                    // Twitter callback
                } else if (isTwitter) {
                    try {
                        LOGGER.finer("Determining user attributes for: " + op);
                        String token = (String) session.getAttribute(ATTR_TOKEN);
                        String tokenSecret = (String) session.getAttribute(ATTR_TOKEN_SECRET);
                        Twitter twitter = new Twitter();
                        twitter.setOAuthConsumer(provider.getConsumerKey(), provider.getConsumerSecret());
                        AccessToken accessToken = twitter.getOAuthAccessToken(token, tokenSecret);
                        twitter.setOAuthAccessToken(accessToken);
                        twitter4j.User tUser = twitter.verifyCredentials();
                        String screenName = Val.chkStr(tUser.getScreenName());
                        if (screenName.length() > 0) {
                            username = screenName + "@twitter";
                            identity = "twitter:" + screenName;
                        }
                    } catch (Exception e) {
                        err = "oAuth authentication failed.";
                        LOGGER.log(Level.WARNING, err, e);
                    }

                    // Openid callback
                } else {
                    try {

                        // determine the callback UUID
                        String cbidParam = Val.chkStr(request.getParameter("cbid"));
                        if (cbidParam.length() == 0) {
                            throw new ServletException("Empty cbid parameter on callback.");
                        }

                        if (!cbid.equals(cbidParam)) {
                            throw new ServletException("Invalid openid cbid parameter on callback.");
                        }
                        callbackUrl += "?cbid=" + java.net.URLEncoder.encode(cbid, "UTF-8");
                        LOGGER.finer("cbinfo based callback: " + cbinfo);
                        LOGGER.finer("Determining user attributes for: " + op);

                        OpenIdManager manager = new OpenIdManager();
                        manager.setRealm(realm);
                        manager.setReturnTo(callbackUrl);

                        checkNonce(request.getParameter("openid.response_nonce"));
                        byte[] mac_key = (byte[]) session.getAttribute(ATTR_MAC);
                        String alias = (String) session.getAttribute(ATTR_ALIAS);
                        Authentication authentication = manager.getAuthentication(request, mac_key, alias);
                        identity = authentication.getIdentity();
                        email = authentication.getEmail();
                        username = email;
                    } catch (Exception e) {
                        err = "Openid authentication suceeded, creating local user reference failed.";
                        LOGGER.log(Level.WARNING, err, e);
                    }
                }

                // check the parameters
                identity = Val.chkStr(identity);
                username = Val.chkStr(username);
                email = Val.chkStr(email);
                LOGGER.finer(
                        "User attributes: identity=" + identity + ", username=" + username + ", email=" + email);
                if (identity.length() == 0) {
                    err = "Your openid idenitfier was not determined.";
                } else if (username.length() == 0) {
                    if (isTwitter) {
                        err = "Your opennid screen name was not determined.";
                    } else {
                        err = "Your opennid email address was not determined.";
                    }
                } else {

                    // establish the user
                    identity = "urn:openid:" + identity;
                    User user = context.getUser();
                    user.reset();
                    user.setKey(identity);
                    user.setDistinguishedName(identity);
                    user.setName(username);
                    user.getProfile().setUsername(username);
                    if (email.length() > 0) {
                        user.getProfile().setEmailAddress(email);
                    }
                    user.getAuthenticationStatus().setWasAuthenticated(true);

                    // ensure a local reference for the user
                    try {
                        LocalDao localDao = new LocalDao(context);
                        localDao.ensureReferenceToRemoteUser(user);
                    } catch (Exception e) {
                        user.reset();
                        err = "Openid authentication suceeded, creating local user reference failed.";
                        LOGGER.log(Level.SEVERE, err, e);
                    }
                }

                // redirect to the originating page
                String url = fwd;
                err = Val.chkStr(err);
                if (err.length() > 0) {
                    if (url.indexOf("?") == -1)
                        fwd += "?";
                    else
                        url += "&";
                    url += "err=" + URLEncoder.encode(err, "UTF-8");
                }
                response.sendRedirect(url);

                // process a request to enter Openid credentials
            } else if (op.length() > 0) {
                session.setAttribute(ATTR_CBINFO, null);

                // determine the provider
                OpenProvider provider = providers.get(op);
                if (provider == null) {
                    throw new ServletException("Invalid openid op parameter: " + op);
                }
                boolean isTwitter = provider.getName().equalsIgnoreCase("Twitter");

                // determine the active Geoportal page (forward URL)
                String fwd = Val.chkStr(request.getParameter("fwd"));
                if (fwd.length() == 0) {
                    throw new ServletException("Empty openid fwd parameter.");
                }

                // store the callback info
                String cbid = UUID.randomUUID().toString();
                long millis = System.currentTimeMillis();
                String cbinfo = millis + "," + cbid + "," + op + "," + fwd;
                session.setAttribute(ATTR_CBINFO, cbinfo);

                // determine the Openid Authentication URL
                String url = null;
                if (useFacade) {
                    PrintWriter pw = response.getWriter();
                    pw.println("<html><head><title>Openid Facade</title></head><body><h1>Openid Facade</h1>");
                    pw.println("<a href=\"" + callbackUrl + "\">Supply credentials step</a>");
                    pw.println("</body></html>");
                    pw.flush();
                    return;

                    // Twitter
                } else if (isTwitter) {
                    try {
                        LOGGER.fine("Initiating oAuth request for: " + op + ", callback=" + callbackUrl);
                        Twitter twitter = new Twitter();
                        twitter.setOAuthConsumer(provider.getConsumerKey(), provider.getConsumerSecret());
                        RequestToken requestToken = twitter.getOAuthRequestToken();
                        String token = requestToken.getToken();
                        String tokenSecret = requestToken.getTokenSecret();
                        session.setAttribute(ATTR_TOKEN, token);
                        session.setAttribute(ATTR_TOKEN_SECRET, tokenSecret);
                        url = requestToken.getAuthorizationURL();
                    } catch (TwitterException e) {
                        err = "Unable to determine endpoint for: " + op;
                        LOGGER.log(Level.SEVERE, err, e);
                    }

                    // Openid
                } else {
                    try {
                        callbackUrl += "?cbid=" + java.net.URLEncoder.encode(cbid, "UTF-8");
                        LOGGER.finer("Initiating openid request for: " + op + ", callback=" + callbackUrl);
                        OpenIdManager manager = new OpenIdManager();
                        manager.setRealm(realm);
                        manager.setReturnTo(callbackUrl);

                        // There is an issue here. It seems that the only way to set the endpoint
                        // alias is through the jopenid-1.07.jar openid-providers.properties,
                        // but we would to to configure the provider properties through gpt.xml

                        //Endpoint endpoint = manager.lookupEndpoint(provider.getAuthenticationUrl());
                        Endpoint endpoint = manager.lookupEndpoint(op);

                        Association association = manager.lookupAssociation(endpoint);
                        request.getSession().setAttribute(ATTR_MAC, association.getRawMacKey());
                        request.getSession().setAttribute(ATTR_ALIAS, endpoint.getAlias());
                        url = manager.getAuthenticationUrl(endpoint, association);
                    } catch (Exception e) {
                        err = "Unable to determine Openid endpoint for: " + op;
                        LOGGER.log(Level.SEVERE, err, e);
                    }

                }

                // redirect to the authentication endpoint or to originating page
                err = Val.chkStr(err);
                if (err.length() > 0) {
                    url = fwd;
                    if (url.indexOf("?") == -1)
                        fwd += "?";
                    else
                        url += "&";
                    url += "err=" + URLEncoder.encode(err, "UTF-8");
                }
                LOGGER.finer("Redirecting for authentication: " + url);
                response.sendRedirect(url);

            } else {
                throw new ServletException("Empty openid op parameter.");
            }
        } finally {
            if (context != null)
                context.onExecutionPhaseCompleted();
        }
    }

    /**
     * Taken from org.expressme.openid.MainServlet
     */
    private void showAuthentication(PrintWriter pw, String identity, String email) {
        pw.print("<html><body><h1>Identity</h1><p>");
        pw.print(identity);
        pw.print("</p><h1>Email</h1><p>");
        pw.print(email == null ? "(null)" : email);
        pw.print("</p></body></html>");
        pw.flush();
    }

    /**
     * Taken from org.expressme.openid.MainServlet
     */
    private void checkNonce(String nonce) {
        // check response_nonce to prevent replay-attack:
        if (nonce == null || nonce.length() < 20)
            throw new OpenIdException("Verify failed.");
        long nonceTime = getNonceTime(nonce);
        long diff = System.currentTimeMillis() - nonceTime;
        if (diff < 0)
            diff = (-diff);
        if (diff > ONE_HOUR)
            throw new OpenIdException("Bad nonce time.");
        if (isNonceExist(nonce))
            throw new OpenIdException("Verify nonce failed.");
        storeNonce(nonce, nonceTime + TWO_HOUR);
    }

    /**
     * Taken from org.expressme.openid.MainServlet
     */
    private boolean isNonceExist(String nonce) {
        // check if nonce is exist in database:
        return false;
    }

    /**
     * Taken from org.expressme.openid.MainServlet
     */
    private void storeNonce(String nonce, long expires) {
        // store nonce in database:
    }

    /**
     * Taken from org.expressme.openid.MainServlet
     */
    private long getNonceTime(String nonce) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(nonce.substring(0, 19) + "+0000").getTime();
        } catch (ParseException e) {
            throw new OpenIdException("Bad nonce time.");
        }
    }

}