org.forgerock.openam.authentication.modules.oauth2.OAuth.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openam.authentication.modules.oauth2.OAuth.java

Source

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright  2011-2015 ForgeRock AS.
* Copyright  2011 Cybernetica AS.
 *
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
 *
*/

package org.forgerock.openam.authentication.modules.oauth2;

import static org.forgerock.openam.authentication.modules.oauth2.OAuthParam.*;

import com.iplanet.sso.SSOException;
import com.sun.identity.authentication.client.AuthClientUtils;
import com.sun.identity.authentication.service.AuthUtils;
import com.sun.identity.authentication.spi.AMLoginModule;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.authentication.spi.RedirectCallback;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.idm.AMIdentity;
import com.sun.identity.idm.IdRepoException;
import com.sun.identity.shared.encode.Base64;
import com.sun.identity.shared.encode.CookieUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.RandomStringUtils;
import org.forgerock.openam.guice.InjectorHolder;
import org.forgerock.openam.cts.CTSPersistentStore;
import org.forgerock.openam.cts.api.TokenType;
import org.forgerock.openam.cts.api.fields.CoreTokenField;
import org.forgerock.openam.cts.api.tokens.Token;
import org.forgerock.openam.cts.exceptions.CoreTokenException;
import org.json.JSONException;
import org.json.JSONObject;
import org.owasp.esapi.ESAPI;

public class OAuth extends AMLoginModule {

    private String authenticatedUser = null;
    private Map sharedState;
    private OAuthConf config;
    String serverName = "";
    private ResourceBundle bundle = null;
    private static final SecureRandom random = new SecureRandom();
    String data = "";
    String userPassword = "";
    String proxyURL = "";
    private final CTSPersistentStore ctsStore;

    public OAuth() {
        OAuthUtil.debugMessage("OAuth()");
        ctsStore = InjectorHolder.getInstance(CTSPersistentStore.class);
    }

    public void init(Subject subject, Map sharedState, Map config) {
        this.sharedState = sharedState;
        this.config = new OAuthConf(config);
        bundle = amCache.getResBundle(BUNDLE_NAME, getLoginLocale());
        setAuthLevel(this.config.getAuthnLevel());
    }

    public int process(Callback[] callbacks, int state) throws LoginException {

        OAuthUtil.debugMessage("process: state = " + state);
        HttpServletRequest request = getHttpServletRequest();
        HttpServletResponse response = getHttpServletResponse();

        if (request == null) {
            OAuthUtil.debugError("OAuth.process(): The request was null, this is " + "an interactive module");
            return ISAuthConstants.LOGIN_IGNORE;
        }

        // We are being redirected back from an OAuth 2 Identity Provider
        String code = request.getParameter(PARAM_CODE);
        if (code != null) {
            OAuthUtil.debugMessage("OAuth.process(): GOT CODE: " + code);
            state = GET_OAUTH_TOKEN_STATE;
        }

        // The Proxy is used to return with a POST to the module
        proxyURL = config.getProxyURL();

        switch (state) {
        case ISAuthConstants.LOGIN_START: {
            config.validateConfiguration();
            serverName = request.getServerName();
            String requestedURI = request.getRequestURI();
            String requestedQuery = request.getQueryString();

            String authCookieName = AuthUtils.getAuthCookieName();

            if (requestedQuery != null) {
                if (requestedQuery.endsWith(authCookieName + "=")) {
                    requestedQuery = requestedQuery.substring(0,
                            requestedQuery.length() - authCookieName.length() - 1);
                }
                requestedURI += "?" + requestedQuery;
            }

            // Find the domains for which we are configured
            Set<String> domains = AuthClientUtils.getCookieDomains();

            String ProviderLogoutURL = config.getLogoutServiceUrl();

            String csrfStateTokenId = RandomStringUtils.randomAlphanumeric(32);
            String csrfState = createAuthorizationState();
            Token csrfStateToken = new Token(csrfStateTokenId, TokenType.GENERIC);
            csrfStateToken.setAttribute(CoreTokenField.STRING_ONE, csrfState);
            try {
                ctsStore.create(csrfStateToken);
            } catch (CoreTokenException e) {
                OAuthUtil.debugError("OAuth.process(): Authorization redirect failed to be sent because the state "
                        + "could not be stored");
                throw new AuthLoginException("OAuth.process(): Authorization redirect failed to be sent because "
                        + "the state could not be stored", e);
            }

            // Set the return URL Cookie
            // Note: The return URL cookie from the RedirectCallback can not
            // be used because the framework changes the order of the
            // parameters in the query. OAuth2 requires an identical URL
            // when retrieving the token
            for (String domain : domains) {
                CookieUtils.addCookieToResponse(response,
                        CookieUtils.newCookie(COOKIE_PROXY_URL, proxyURL, "/", domain));
                CookieUtils.addCookieToResponse(response,
                        CookieUtils.newCookie(COOKIE_ORIG_URL, requestedURI, "/", domain));
                CookieUtils.addCookieToResponse(response,
                        CookieUtils.newCookie(NONCE_TOKEN_ID, csrfStateTokenId, "/", domain));
                if (ProviderLogoutURL != null && !ProviderLogoutURL.isEmpty()) {
                    CookieUtils.addCookieToResponse(response,
                            CookieUtils.newCookie(COOKIE_LOGOUT_URL, ProviderLogoutURL, "/", domain));
                }
            }

            // The Proxy is used to return with a POST to the module
            setUserSessionProperty(ISAuthConstants.FULL_LOGIN_URL, requestedURI);

            setUserSessionProperty(SESSION_LOGOUT_BEHAVIOUR, config.getLogoutBhaviour());

            String authServiceUrl = config.getAuthServiceUrl(proxyURL, csrfState);
            OAuthUtil.debugMessage("OAuth.process(): New RedirectURL=" + authServiceUrl);

            Callback[] callbacks1 = getCallback(2);
            RedirectCallback rc = (RedirectCallback) callbacks1[0];
            RedirectCallback rcNew = new RedirectCallback(authServiceUrl, null, "GET", rc.getStatusParameter(),
                    rc.getRedirectBackUrlCookieName());
            replaceCallback(2, 0, rcNew);
            return GET_OAUTH_TOKEN_STATE;
        }

        case GET_OAUTH_TOKEN_STATE: {
            final String csrfState = request.getParameter("state");
            if (csrfState == null) {
                OAuthUtil.debugError("OAuth.process(): Authorization call-back failed because there was no state "
                        + "parameter");
                throw new AuthLoginException(BUNDLE_NAME, "noState", null);
            }

            try {
                Token csrfStateToken = ctsStore.read(OAuthUtil.findCookie(request, NONCE_TOKEN_ID));
                ctsStore.delete(csrfStateToken);
                String expectedCsrfState = csrfStateToken.getValue(CoreTokenField.STRING_ONE);
                if (!expectedCsrfState.equals(csrfState)) {
                    OAuthUtil.debugError(
                            "OAuth.process(): Authorization call-back failed because the state parameter "
                                    + "contained an unexpected value");
                    throw new AuthLoginException(BUNDLE_NAME, "incorrectState", null);
                }

                // We are being redirected back from an OAuth 2 Identity Provider
                code = request.getParameter(PARAM_CODE);
                if (code == null || code.isEmpty()) {
                    OAuthUtil.debugMessage("OAuth.process(): LOGIN_IGNORE");
                    return ISAuthConstants.LOGIN_START;
                }

                validateInput("code", code, "HTTPParameterValue", 512, false);

                OAuthUtil.debugMessage("OAuth.process(): code parameter: " + code);

                String tokenSvcResponse = getContent(config.getTokenServiceUrl(code, proxyURL));
                OAuthUtil.debugMessage("OAuth.process(): token=" + tokenSvcResponse);

                String token = extractToken(tokenSvcResponse);

                setUserSessionProperty(SESSION_OAUTH_TOKEN, token);

                String profileSvcResponse = getContent(config.getProfileServiceUrl(token));

                OAuthUtil.debugMessage("OAuth.process(): Profile Svc " + "response: " + profileSvcResponse);

                String realm = getRequestOrg();

                if (realm == null) {
                    realm = "/";
                }

                AccountMapper accountMapper = instantiateAccountMapper();
                Map<String, Set<String>> userNames = new HashMap<String, Set<String>>();

                userNames = accountMapper.getAccount(config.getAccountMapperConfig(), profileSvcResponse);

                String user = null;
                if (!userNames.isEmpty()) {
                    user = getUser(realm, accountMapper, userNames);
                }

                if (user == null && !config.getCreateAccountFlag()) {
                    authenticatedUser = getDynamicUser(userNames);

                    if (authenticatedUser != null) {
                        if (config.getSaveAttributesToSessionFlag()) {
                            Map<String, Set<String>> attributes = getAttributesMap(profileSvcResponse);
                            saveAttributes(attributes);
                        }
                        OAuthUtil
                                .debugMessage("OAuth.process(): LOGIN_SUCCEED " + "with user " + authenticatedUser);
                        return ISAuthConstants.LOGIN_SUCCEED;
                    } else {
                        throw new AuthLoginException("No user mapped!");
                    }

                }

                if (user == null && config.getCreateAccountFlag()) {
                    if (config.getPromptPasswordFlag()) {
                        setUserSessionProperty("ATTRIBUTES", profileSvcResponse);
                        return SET_PASSWORD_STATE;
                    } else {
                        authenticatedUser = provisionAccountNow(realm, profileSvcResponse, getRandomData());
                        if (authenticatedUser != null) {
                            OAuthUtil.debugMessage("User created: " + authenticatedUser);
                            return ISAuthConstants.LOGIN_SUCCEED;
                        } else {
                            return ISAuthConstants.LOGIN_IGNORE;
                        }
                    }
                }

                if (user != null) {
                    authenticatedUser = user;
                    OAuthUtil.debugMessage("OAuth.process(): LOGIN_SUCCEED " + "with user " + authenticatedUser);
                    if (config.getSaveAttributesToSessionFlag()) {
                        Map<String, Set<String>> attributes = getAttributesMap(profileSvcResponse);
                        saveAttributes(attributes);
                    }
                    return ISAuthConstants.LOGIN_SUCCEED;
                }

            } catch (JSONException je) {
                OAuthUtil.debugError("OAuth.process(): JSONException: " + je.getMessage());
                throw new AuthLoginException(BUNDLE_NAME, "json", null, je);
            } catch (SSOException ssoe) {
                OAuthUtil.debugError("OAuth.process(): SSOException: " + ssoe.getMessage());
                throw new AuthLoginException(BUNDLE_NAME, "ssoe", null, ssoe);
            } catch (IdRepoException ire) {
                OAuthUtil.debugError("OAuth.process(): IdRepoException: " + ire.getMessage());
                throw new AuthLoginException(BUNDLE_NAME, "ire", null, ire);
            } catch (CoreTokenException e) {
                OAuthUtil.debugError("OAuth.process(): Authorization call-back failed because the state parameter "
                        + "contained an unexpected value");
                throw new AuthLoginException(BUNDLE_NAME, "incorrectState", null, e);
            }
            break;
        }

        case SET_PASSWORD_STATE: {
            if (!config.getCreateAccountFlag()) {
                return ISAuthConstants.LOGIN_IGNORE;
            }
            userPassword = request.getParameter(PARAM_TOKEN1);
            validateInput(PARAM_TOKEN1, userPassword, "HTTPParameterValue", 512, false);
            String userPassword2 = request.getParameter(PARAM_TOKEN2);
            validateInput(PARAM_TOKEN2, userPassword2, "HTTPParameterValue", 512, false);

            if (!userPassword.equals(userPassword2)) {
                OAuthUtil.debugWarning("OAuth.process(): Passwords did not match!");
                return SET_PASSWORD_STATE;
            }

            String terms = request.getParameter("terms");
            if (!terms.equalsIgnoreCase("accept")) {
                return SET_PASSWORD_STATE;
            }

            String profileSvcResponse = getUserSessionProperty("ATTRIBUTES");
            data = getRandomData();
            String mail = getMail(profileSvcResponse, config.getMailAttribute());
            OAuthUtil.debugMessage("Mail found = " + mail);
            try {
                OAuthUtil.sendEmail(config.getEmailFrom(), mail, data, config.getSMTPConfig(), bundle, proxyURL);
            } catch (NoEmailSentException ex) {
                OAuthUtil.debugError("No mail sent due to error", ex);
                throw new AuthLoginException("Aborting authentication, because "
                        + "the mail could not be sent due to a mail sending error");
            }
            OAuthUtil.debugMessage("User to be created, we need to activate: " + data);
            return CREATE_USER_STATE;
        }

        case CREATE_USER_STATE: {
            String activation = request.getParameter(PARAM_ACTIVATION);
            validateInput(PARAM_ACTIVATION, activation, "HTTPParameterValue", 512, false);
            OAuthUtil.debugMessage("code entered by the user: " + activation);

            if (activation == null || activation.isEmpty() || !activation.trim().equals(data.trim())) {
                return CREATE_USER_STATE;
            }

            String profileSvcResponse = getUserSessionProperty("ATTRIBUTES");
            String realm = getRequestOrg();
            if (realm == null) {
                realm = "/";
            }

            OAuthUtil.debugMessage("Got Attributes: " + profileSvcResponse);
            authenticatedUser = provisionAccountNow(realm, profileSvcResponse, userPassword);
            if (authenticatedUser != null) {
                OAuthUtil.debugMessage("User created: " + authenticatedUser);
                return ISAuthConstants.LOGIN_SUCCEED;
            } else {
                return ISAuthConstants.LOGIN_IGNORE;
            }
        }

        default: {
            OAuthUtil.debugError("OAuth.process(): Illegal State");
            return ISAuthConstants.LOGIN_IGNORE;
        }
        }

        throw new AuthLoginException(BUNDLE_NAME, "unknownState", null);
    }

    private String createAuthorizationState() {
        return new BigInteger(160, new SecureRandom()).toString(Character.MAX_RADIX);
    }

    // Search for the user in the realm, using the instantiated account mapper
    private String getUser(String realm, AccountMapper accountMapper, Map<String, Set<String>> userNames)
            throws AuthLoginException, JSONException, SSOException, IdRepoException {

        String user = null;
        if ((userNames != null) && !userNames.isEmpty()) {
            AMIdentity userIdentity = accountMapper.searchUser(getAMIdentityRepository(realm), userNames);
            if (userIdentity != null) {
                user = userIdentity.getName();
            }
        }

        return user;
    }

    // Generate random data
    private String getRandomData() {
        byte[] pass = new byte[20];
        random.nextBytes(pass);
        return Base64.encode(pass);
    }

    // Create an instance of the pluggable account mapper
    private AccountMapper instantiateAccountMapper() throws AuthLoginException {

        try {
            AccountMapper accountMapper = Class.forName(config.getAccountMapper()).asSubclass(AccountMapper.class)
                    .newInstance();
            return accountMapper;
        } catch (Exception ex) {
            throw new AuthLoginException("Problem when trying to instantiate " + "the account mapper", ex);
        }
    }

    // Obtain the attributes configured for the module, by using the pluggable
    // Attribute mapper
    private Map<String, Set<String>> getAttributesMap(String svcProfileResponse) {

        Map<String, Set<String>> attributes = new HashMap<String, Set<String>>();

        try {
            AttributeMapper attributeMapper = Class.forName(config.getAttributeMapper())
                    .asSubclass(AttributeMapper.class).newInstance();
            attributes = attributeMapper.getAttributes(config.getAttributeMapperConfig(), svcProfileResponse);
        } catch (Exception ex) {
            OAuthUtil.debugError("OAuth.getUser: Problem when trying to get the " + "Attribute Mapper", ex);
        }
        OAuthUtil.debugMessage("OAuth.getUser: creating new user; attributes = " + attributes);
        return attributes;
    }

    // Save the attributes configured for the attribute mapper as session attributes
    public void saveAttributes(Map<String, Set<String>> attributes) throws AuthLoginException {

        if (attributes != null && !attributes.isEmpty()) {
            for (String attributeName : attributes.keySet()) {
                String attributeValue = attributes.get(attributeName).iterator().next().toString();
                setUserSessionProperty(attributeName, attributeValue);
                OAuthUtil.debugMessage("OAuth.saveAttributes: " + attributeName + "=" + attributeValue);
            }
        } else {
            OAuthUtil.debugMessage("OAuth.saveAttributes: NO attributes to set");
        }
    }

    // Generate a user name, either using the anonymous user if configured or by
    // extracting the user from the userName map.
    // Return null, if nothing was found
    private String getDynamicUser(Map<String, Set<String>> userNames) throws AuthLoginException {

        String dynamicUser = null;
        if (config.getUseAnonymousUserFlag()) {
            String anonUser = config.getAnonymousUser();
            if (anonUser != null && !anonUser.isEmpty()) {
                dynamicUser = anonUser;
            }
        } else { // Do not use anonymous
            if (userNames != null && !userNames.isEmpty()) {
                Iterator<Set<String>> usersIt = userNames.values().iterator();
                dynamicUser = usersIt.next().iterator().next();
            }

        }
        return dynamicUser;
    }

    // Obtain the user profile information from the OAuth 2.0 Identity Provider
    // Profile service configured for this module, either using first GET and
    // POST as a fall back
    private String getContent(String serviceUrl) throws LoginException {

        BufferedReader in = new BufferedReader(new InputStreamReader(getContentStreamByGET(serviceUrl)));
        StringBuilder buf = new StringBuilder();
        try {
            String str;
            while ((str = in.readLine()) != null) {
                buf.append(str);
            }
        } catch (IOException ioe) {
            OAuthUtil.debugError("OAuth.getContent: IOException: " + ioe.getMessage());
            throw new AuthLoginException(BUNDLE_NAME, "ioe", null, ioe);
        } finally {
            try {
                in.close();
            } catch (IOException ioe) {
                OAuthUtil.debugError("OAuth.getContent: IOException: " + ioe.getMessage());
                throw new AuthLoginException(BUNDLE_NAME, "ioe", null, ioe);
            }
        }
        return buf.toString();
    }

    // Create the account in the realm, by using the pluggable account mapper and
    // the attributes configured in the attribute mapper
    public String provisionAccountNow(String realm, String profileSvcResponse, String userPassword)
            throws AuthLoginException {

        AccountMapper accountMapper = instantiateAccountMapper();
        Map<String, Set<String>> attributes = getAttributesMap(profileSvcResponse);
        if (config.getSaveAttributesToSessionFlag()) {
            saveAttributes(attributes);
        }
        attributes.put("userPassword", OAuthUtil.addToSet(new HashSet<String>(), userPassword));
        attributes.put("inetuserstatus", OAuthUtil.addToSet(new HashSet<String>(), "Active"));
        AMIdentity userIdentity = accountMapper.provisionUser(getAMIdentityRepository(realm), attributes);
        if (userIdentity != null) {
            return userIdentity.getName().trim();
        } else {
            return null;
        }
    }

    // Obtain the Profile Service information using GET
    public InputStream getContentStreamByGET(String serviceUrl) throws LoginException {

        OAuthUtil.debugMessage("service url: " + serviceUrl);
        try {
            InputStream is = null;
            URL urlC = new URL(serviceUrl);

            HttpURLConnection connection = (HttpURLConnection) urlC.openConnection();
            connection.setDoOutput(true);
            connection.setRequestMethod("GET");
            connection.connect();

            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                OAuthUtil.debugMessage("OAuth.getContentStreamByGET: HTTP Conn OK");
                is = connection.getInputStream();
            } else {
                // Server returned HTTP error code.
                String errorStream = getErrorStream(connection);
                if (OAuthUtil.debugMessageEnabled()) {
                    OAuthUtil.debugMessage("OAuth.getContentStreamByGET: HTTP Conn Error:\n" + " Response code: "
                            + connection.getResponseCode() + "\n " + " Response message: "
                            + connection.getResponseMessage() + "\n" + " Error stream: " + errorStream + "\n");
                }
                is = getContentStreamByPOST(serviceUrl);
            }

            return is;

        } catch (MalformedURLException mfe) {
            throw new AuthLoginException(BUNDLE_NAME, "malformedURL", null, mfe);
        } catch (IOException ioe) {
            throw new AuthLoginException(BUNDLE_NAME, "ioe", null, ioe);
        }
    }

    private String getErrorStream(HttpURLConnection connection) {
        InputStream errStream = connection.getErrorStream();
        if (errStream == null) {
            return "Empty error stream";
        } else {
            BufferedReader in = new BufferedReader(new InputStreamReader(errStream));
            StringBuilder buf = new StringBuilder();
            try {
                String str;
                while ((str = in.readLine()) != null) {
                    buf.append(str);
                }
            } catch (IOException ioe) {
                OAuthUtil.debugError("OAuth.getErrorStream: IOException: " + ioe.getMessage());
            } finally {
                try {
                    in.close();
                } catch (IOException ioe) {
                    OAuthUtil.debugError("OAuth.getContent: IOException: " + ioe.getMessage());
                }
            }
            return buf.toString();
        }
    }

    // Obtain the Profile Service information using POST
    public InputStream getContentStreamByPOST(String serviceUrl) throws LoginException {

        InputStream is = null;

        try {
            OAuthUtil.debugMessage("OAuth.getContentStreamByPOST: URL = " + serviceUrl);

            URL url = new URL(serviceUrl);

            String query = url.getQuery();
            OAuthUtil.debugMessage("OAuth.getContentStreamByPOST: Query: " + query);

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
            writer.write(query);
            writer.close();

            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                OAuthUtil.debugMessage("OAuth.getContentStreamByPOST: HTTP Conn OK");
                is = connection.getInputStream();
            } else { // Error Code
                String data2[] = { String.valueOf(connection.getResponseCode()) };
                OAuthUtil.debugError("OAuth.getContentStreamByPOST: HTTP Conn Error:\n" + " Response code: "
                        + connection.getResponseCode() + "\n" + " Response message: "
                        + connection.getResponseMessage() + "\n" + " Error stream: " + getErrorStream(connection)
                        + "\n");
                throw new AuthLoginException(BUNDLE_NAME, "httpErrorCode", data2);
            }
        } catch (MalformedURLException e) {
            throw new AuthLoginException(BUNDLE_NAME, "malformedURL", null, e);
        } catch (IOException e) {
            throw new AuthLoginException(BUNDLE_NAME, "ioe", null, e);
        }

        return is;

    }

    // Extract the Token from the OAuth 2.0 response
    // Todo: Maybe this should be pluggable
    public String extractToken(String response) {

        String token = "";
        try {
            JSONObject jsonToken = new JSONObject(response);
            if (jsonToken != null && !jsonToken.isNull(PARAM_ACCESS_TOKEN)) {
                token = jsonToken.getString(PARAM_ACCESS_TOKEN);
                OAuthUtil.debugMessage(PARAM_ACCESS_TOKEN + ": " + token);
            }
        } catch (JSONException je) {
            OAuthUtil.debugMessage("OAuth.extractToken: Not in JSON format" + je);
            token = OAuthUtil.getParamValue(response, PARAM_ACCESS_TOKEN);
        }

        return token;
    }

    // Obtain the email address field from the response provided by the
    // OAuth 2.0 Profile service.
    public String getMail(String svcResponse, String mailAttribute) {
        String mail = "";
        OAuthUtil.debugMessage("mailAttribute: " + mailAttribute);
        try {
            JSONObject jsonData = new JSONObject(svcResponse);

            if (mailAttribute != null && mailAttribute.indexOf(".") != -1) {
                StringTokenizer parts = new StringTokenizer(mailAttribute, ".");
                mail = jsonData.getJSONObject(parts.nextToken()).getString(parts.nextToken());
            } else {
                mail = jsonData.getString(mailAttribute);
            }
            OAuthUtil.debugMessage("mail: " + mail);

        } catch (JSONException je) {
            OAuthUtil.debugMessage("OAuth.getMail: Not in JSON format" + je);
        }

        return mail;
    }

    // Validate the field provided as input
    public void validateInput(String tag, String inputField, String rule, int maxLength, boolean allowNull)
            throws AuthLoginException {
        if (!ESAPI.validator().isValidInput(tag, inputField, rule, maxLength, allowNull)) {
            OAuthUtil.debugError("OAuth.validateInput(): OAuth 2.0 Not valid input !");
            String msgdata[] = { tag, inputField };
            throw new AuthLoginException(BUNDLE_NAME, "invalidField", msgdata);
        }
        ;
    }

    public Principal getPrincipal() {
        if (authenticatedUser != null) {
            return new OAuthPrincipal(authenticatedUser);
        }
        return null;
    }

    public void destroyModuleState() {
        authenticatedUser = null;
    }

    public void nullifyUsedVars() {
        config = null;
        sharedState = null;
    }

}