org.forgerock.restlet.ext.oauth2.flow.AuthorizeServerResource.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.restlet.ext.oauth2.flow.AuthorizeServerResource.java

Source

/*
 * 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 legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. 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]".
 *
 * Copyright 2012-2014 ForgeRock AS. All rights reserved.
 */

package org.forgerock.restlet.ext.oauth2.flow;

import com.iplanet.am.util.SystemProperties;
import com.sun.identity.idm.AMIdentity;
import com.sun.identity.shared.OAuth2Constants;
import java.util.Arrays;

import org.apache.commons.lang.StringUtils;
import org.forgerock.openam.oauth2.exceptions.OAuthProblemException;
import org.forgerock.openam.oauth2.model.CoreToken;
import org.forgerock.openam.oauth2.openid.OpenIDPromptParameter;
import org.forgerock.openam.oauth2.provider.OAuth2ProviderSettings;
import org.forgerock.openam.oauth2.provider.ResponseType;
import org.forgerock.openam.oauth2.utils.OAuth2Utils;
import org.restlet.data.Form;
import org.restlet.data.Parameter;
import org.restlet.data.Reference;
import org.restlet.ext.json.JsonRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.routing.Redirector;

import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

public class AuthorizeServerResource extends AbstractFlow {

    /**
     * Developers should note that some user-agents do not support the inclusion
     * of a fragment component in the HTTP "Location" response header field.
     * Such clients will require using other methods for redirecting the client
     * than a 3xx redirection response. For example, returning an HTML page
     * which includes a 'continue' button with an action linked to the
     * redirection URI.
     * <p/>
     * If TLS is not available, the authorization server SHOULD warn the
     * resource owner about the insecure endpoint prior to redirection.
     *
     * @return
     */
    @Get("html")
    public Representation represent() {

        // Validate the client
        client = validateRemoteClient();
        // Validate Redirect URI throw exception
        sessionClient = client.getClientInstance(
                OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.REDIRECT_URI, String.class));

        // The target contains the state
        String state = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.STATE, String.class);

        // Get the requested scope
        String scope_before = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.SCOPE,
                String.class);

        // Validate the granted scope
        Set<String> checkedScope = executeAuthorizationPageScopePlugin(scope_before);

        String prompt = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Custom.PROMPT, String.class);

        if (StringUtils.isBlank(prompt)) {
            prompt = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Custom._PROMPT, String.class);
        }

        OpenIDPromptParameter openIDPromptParameter = new OpenIDPromptParameter(prompt);
        if (!openIDPromptParameter.isValid()) {
            throw OAuthProblemException.OAuthError.BAD_REQUEST.handle(getRequest(), "Prompt parameter is invalid");
        }

        //check if there is an invalid response type
        String responseType = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.RESPONSE_TYPE,
                String.class);
        if (responseType != null && !responseType.isEmpty()) {
            if (!validResponseTypes(OAuth2Utils.stringToSet(responseType))) {
                OAuth2Utils.DEBUG.warning(
                        "AuthorizeServerResource.represent(): Requested a response type that is not configured.");
                throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                        "Response type is not supported");
            }
        } else {
            OAuth2Utils.DEBUG.warning(
                    "AuthorizeServerResource.represent(): Requested a response type that is not configured.");
            throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                    "Response type is not supported");
        }

        //authenticate the resource owner
        resourceOwner = getAuthenticatedResourceOwner();

        //check for saved consent
        boolean savedConsent = savedConsent(resourceOwner.getIdentifier(), sessionClient.getClientId(),
                checkedScope);

        if (openIDPromptParameter.noPrompts() && !savedConsent) {
            throw OAuthProblemException.OAuthError.CONSENT_REQUIRED.handle(getRequest());
        } else if (!openIDPromptParameter.promptConsent() && openIDPromptParameter.promptLogin() && !savedConsent) {
            throw OAuthProblemException.OAuthError.CONSENT_REQUIRED.handle(getRequest());
        } else if (savedConsent && !openIDPromptParameter.promptConsent()) {
            Map<String, Object> attrs = getRequest().getAttributes();
            attrs.put(OAuth2Constants.Custom.DECISION, OAuth2Constants.Custom.ALLOW);
            getRequest().setAttributes(attrs);
            Representation rep = new JsonRepresentation(new HashMap());
            return represent(rep);
        } else if (!savedConsent || openIDPromptParameter.promptConsent()) {
            return getPage("authorize.ftl", getDataModel(checkedScope));
        }
        return getPage("authorize.ftl", getDataModel(checkedScope));
    }

    @Post("form:json")
    public Representation represent(Representation entity) {
        resourceOwner = getAuthenticatedResourceOwner();
        client = validateRemoteClient();
        sessionClient = client.getClientInstance(
                OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.REDIRECT_URI, String.class));

        String decision = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Custom.DECISION,
                String.class);

        if (OAuth2Constants.Custom.ALLOW.equalsIgnoreCase(decision)) {
            String save_consent = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Custom.SAVE_CONSENT,
                    String.class);

            String scope_after = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.SCOPE,
                    String.class);

            if (save_consent != null && save_consent.equalsIgnoreCase("on")) {
                saveConsent(resourceOwner.getIdentifier(), sessionClient.getClientId(), scope_after);
            }

            String state = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.STATE,
                    String.class);
            String nonce = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Custom.NONCE,
                    String.class);

            Set<String> checkedScope = executeAccessTokenScopePlugin(scope_after);

            Map<String, String> responseTypes = null;
            responseTypes = getResponseTypes(OAuth2Utils.getRealm(getRequest()));

            Set<String> requestedResponseTypes = OAuth2Utils.stringToSet(OAuth2Utils
                    .getRequestParameter(getRequest(), OAuth2Constants.Params.RESPONSE_TYPE, String.class));
            Map<String, CoreToken> listOfTokens = new HashMap<String, CoreToken>();
            Map<String, Object> data = new HashMap<String, Object>();
            data.put(OAuth2Constants.CoreTokenParams.TOKEN_TYPE, client.getClient().getAccessTokenType());
            data.put(OAuth2Constants.CoreTokenParams.SCOPE, checkedScope);
            data.put(OAuth2Constants.CoreTokenParams.REALM, OAuth2Utils.getRealm(getRequest()));
            data.put(OAuth2Constants.CoreTokenParams.USERNAME, resourceOwner.getIdentifier());
            data.put(OAuth2Constants.CoreTokenParams.CLIENT_ID, sessionClient.getClientId());
            data.put(OAuth2Constants.CoreTokenParams.REDIRECT_URI, sessionClient.getRedirectUri());
            data.put(OAuth2Constants.Custom.NONCE, nonce);
            data.put(OAuth2Constants.Custom.SSO_TOKEN_ID,
                    getRequest().getCookies().getValues(SystemProperties.get("com.iplanet.am.cookie.name")));

            if (requestedResponseTypes == null || requestedResponseTypes.isEmpty()) {
                OAuth2Utils.DEBUG.error("AuthorizeServerResource.represent(): Error response_type not set");
                throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                        "Response type is not supported");
            } else {
                try {
                    for (String request : requestedResponseTypes) {
                        if (request.isEmpty()) {
                            throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                                    "Response type is not supported");
                        }
                        String responseClass = responseTypes.get(request);
                        if (responseClass == null || responseClass.isEmpty()) {
                            OAuth2Utils.DEBUG.warning(
                                    "AuthorizeServerResource.represent(): Requested a response type that is not configured. response_type="
                                            + request);
                            throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                                    "Response type is not supported");
                        } else if (responseClass.equalsIgnoreCase("none")) {
                            continue;
                        }
                        //execute response type class
                        Class clazz = Class.forName(responseClass);
                        ResponseType classObj = (ResponseType) clazz.newInstance();

                        //create the response type token
                        CoreToken token = classObj.createToken(data);

                        //get the response type return location
                        String paramName = classObj.URIParamValue();
                        if (listOfTokens.containsKey(paramName)) {
                            OAuth2Utils.DEBUG.error(
                                    "AuthorizeServerResource.represent(): Returning multiple response types with the same url value");
                            throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                                    "Returning multiple response types with the same url value");
                        }
                        listOfTokens.put(classObj.URIParamValue(), token);

                        //if return location is fragment all tokens need to be returned as a fragment
                        if (fragment == false) {
                            String location = classObj.getReturnLocation();
                            if (location.equalsIgnoreCase("FRAGMENT")) {
                                fragment = true;
                            }
                        }

                    }
                } catch (Exception e) {
                    OAuth2Utils.DEBUG.error(
                            "AuthorizeServerResource.represent(): Error invoking classes for response_type", e);
                    throw OAuthProblemException.OAuthError.UNSUPPORTED_RESPONSE_TYPE.handle(getRequest(),
                            "Response type is not supported");
                }
            }

            Form tokenForm = tokensToForm(listOfTokens);

            //execute post token creation pre return scope plugin for extra return data.
            Map<String, String> extraData = new HashMap<String, String>();

            Map<String, String> valuesToAdd = executeAuthorizationExtraDataScopePlugin(extraData, listOfTokens);

            if (valuesToAdd != null && !valuesToAdd.isEmpty()) {
                String returnType = valuesToAdd.remove("returnType");
                if (returnType != null && !returnType.isEmpty()) {
                    if (returnType.equalsIgnoreCase("FRAGMENT")) {
                        fragment = true;
                    }
                }
            }
            for (Map.Entry<String, String> entry : valuesToAdd.entrySet()) {
                tokenForm.add(entry.getKey(), entry.getValue().toString());
            }

            /*
             * scope OPTIONAL, if identical to the scope requested by the
             * client, otherwise REQUIRED. The scope of the access token as
             * described by Section 3.3.
             */
            if (isScopeChanged()) {
                String scope_before = OAuth2Utils.getRequestParameter(getRequest(), OAuth2Constants.Params.SCOPE,
                        String.class);

                checkedScope = executeAccessTokenScopePlugin(scope_before);
                if (checkedScope != null && !checkedScope.isEmpty()) {
                    tokenForm.add(OAuth2Constants.Params.SCOPE,
                            OAuth2Utils.join(checkedScope, OAuth2Utils.getScopeDelimiter(getContext())));
                }

            }
            if (null != state) {
                tokenForm.add(OAuth2Constants.Params.STATE, state);
            }

            Reference redirectReference = new Reference(sessionClient.getRedirectUri());

            if (fragment) {
                redirectReference.setFragment(tokenForm.getQueryString());
            } else {
                Iterator<Parameter> iter = tokenForm.iterator();
                while (iter.hasNext()) {
                    redirectReference.addQueryParameter(iter.next());
                }
            }

            Redirector dispatcher = new Redirector(getContext(), redirectReference.toString(),
                    Redirector.MODE_CLIENT_FOUND);
            dispatcher.handle(getRequest(), getResponse());
        } else {
            OAuth2Utils.DEBUG.warning("AuthorizeServerResource::Resource Owner did not authorize the request");
            throw OAuthProblemException.OAuthError.ACCESS_DENIED.handle(getRequest(),
                    "Resource Owner did not authorize the request");
        }
        return getResponseEntity();
    }

    @Override
    protected String[] getRequiredParameters() {
        return new String[] { OAuth2Constants.Params.RESPONSE_TYPE, OAuth2Constants.Params.CLIENT_ID };
    }

    protected Form tokensToForm(Map<String, CoreToken> tokens) {

        Form result = new Form();
        for (Map.Entry<String, CoreToken> entry : tokens.entrySet()) {
            Map<String, Object> token = entry.getValue().convertToMap();
            Parameter p = new Parameter(entry.getKey(), entry.getValue().getTokenID());
            if (!result.contains(p)) {
                result.add(p);
            }
            //if access token add extra fields
            if (entry.getValue().getTokenName().equalsIgnoreCase(OAuth2Constants.Params.ACCESS_TOKEN)) {
                for (Map.Entry<String, Object> entryInMap : token.entrySet()) {
                    p = new Parameter(entryInMap.getKey(), entryInMap.getValue().toString());
                    if (!result.contains(p)) {
                        result.add(p);
                    }
                }
            }

        }
        return result;
    }

    protected boolean savedConsent(String userid, String clientId, Set<String> scopes) {
        OAuth2ProviderSettings settings = OAuth2Utils.getSettingsProvider(getRequest());
        String attribute = settings.getSharedConsentAttributeName();

        AMIdentity id = OAuth2Utils.getIdentity(userid, OAuth2Utils.getRealm(getRequest()));
        Set<String> attributeSet = null;

        if (id != null) {
            try {
                attributeSet = id.getAttribute(attribute);
            } catch (Exception e) {
                OAuth2Utils.DEBUG.error("AuthorizeServerResource.saveConsent(): Unable to get profile attribute",
                        e);
                return false;
            }
        }

        //check the values of the attribute set vs the scope and client requested
        //attribute set is in the form of client_id|scope1 scope2 scope3
        for (String consent : attributeSet) {
            int loc = consent.indexOf(" ");
            String consentClientId = consent.substring(0, loc);
            String[] scopesArray = null;
            if (loc + 1 < consent.length()) {
                scopesArray = consent.substring(loc + 1, consent.length()).split(" ");
            }
            Set<String> consentScopes = null;
            if (scopesArray != null && scopesArray.length > 0) {
                consentScopes = new HashSet<String>(Arrays.asList(scopesArray));
            } else {
                consentScopes = new HashSet<String>();
            }

            //if both the client and the scopes are identical to the saved consent then approve
            if (clientId.equals(consentClientId) && scopes.equals(consentScopes)) {
                return true;
            }
        }

        return false;
    }

    protected void saveConsent(String userId, String clientId, String scopes) {
        AMIdentity id = OAuth2Utils.getIdentity(userId, OAuth2Utils.getRealm(getRequest()));
        OAuth2ProviderSettings settings = OAuth2Utils.getSettingsProvider(getRequest());
        String consentAttribute = settings.getSharedConsentAttributeName();
        try {

            //get the current set of consents and add our new consent to it.
            Set<String> consents = new HashSet<String>(id.getAttribute(consentAttribute));
            StringBuilder sb = new StringBuilder();
            if (scopes == null || scopes.isEmpty()) {
                sb.append(clientId.trim()).append(" ");
            } else {
                sb.append(clientId.trim()).append(" ").append(scopes.trim());
            }
            consents.add(sb.toString());

            //update the user profile with our new consent settings
            Map<String, Set<String>> attrs = new HashMap<String, Set<String>>();
            attrs.put(consentAttribute, consents);
            id.setAttributes(attrs);
            id.store();
        } catch (Exception e) {
            OAuth2Utils.DEBUG.error("AuthorizeServerResource.saveConsent(): Unable to save consent ", e);
        }
    }

    private boolean validResponseTypes(Set<String> responseTypesRequested) {

        Map<String, String> allResponseTypes = getResponseTypes(OAuth2Utils.getRealm(getRequest()));
        return allResponseTypes.keySet().containsAll(responseTypesRequested);
    }

}