org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationRequestManager.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationRequestManager.java

Source

/*
 * Cloud Foundry 2012.02.03 Beta
 * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
 *
 * This product is licensed to you under the Apache License, Version 2.0 (the "License").
 * You may not use this product except in compliance with the License.
 *
 * This product includes a number of subcomponents with
 * separate copyright notices and license terms. Your use of these
 * subcomponents is subject to the terms and conditions of the
 * subcomponent's license, as noted in the LICENSE file.
 */
package org.cloudfoundry.identity.uaa.oauth;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.cloudfoundry.identity.uaa.authorization.ExternalGroupMappingAuthorizationManager;
import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor;
import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.AuthorizationRequestManager;
import org.springframework.security.oauth2.provider.BaseClientDetails;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest;

/**
 * An {@link AuthorizationRequestManager} that applies various UAA-specific rules to an authorization request,
 * validating it and setting the default values for scopes and resource ids.
 *
 * @author Dave Syer
 *
 */
public class UaaAuthorizationRequestManager implements AuthorizationRequestManager {

    private final ClientDetailsService clientDetailsService;

    private Map<String, String> scopeToResource = Collections.singletonMap("openid", "openid");

    private String scopeSeparator = ".";

    private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor();

    private Collection<String> defaultScopes = new HashSet<String>();

    private ExternalGroupMappingAuthorizationManager externalGroupMappingAuthorizationManager = null;

    public UaaAuthorizationRequestManager(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    /**
     * Default scopes that are always added to a user token (and then removed if the client doesn't have permission).
     *
     * @param defaultScopes the defaultScopes to set
     */
    public void setDefaultScopes(Collection<String> defaultScopes) {
        this.defaultScopes = defaultScopes;
    }

    /**
     * A helper to pull stuff out of the current security context.
     *
     * @param securityContextAccessor the securityContextAccessor to set
     */
    public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) {
        this.securityContextAccessor = securityContextAccessor;
    }

    /**
     * A map from scope name to resource id, for cases (like openid) that cannot be extracted from the scope name.
     *
     * @param scopeToResource the map to use
     */
    public void setScopesToResources(Map<String, String> scopeToResource) {
        this.scopeToResource = new HashMap<String, String>(scopeToResource);
    }

    /**
     * The string used to separate resource ids from feature names in scopes (e.g. "cloud_controller.read").
     *
     * @param scopeSeparator the scope separator to set. Default is period "."
     */
    public void setScopeSeparator(String scopeSeparator) {
        this.scopeSeparator = scopeSeparator;
    }

    /**
     * Create an authorization request applying various UAA rules to the authorizationParameters and the registered
     * client details.
     * <ul>
     * <li>For client_credentials grants, the default scopes are the client's granted authorities</li>
     * <li>For other grant types the default scopes are the registered scopes in the client details</li>
     * <li>Only scopes in those lists are valid, otherwise there is an exception</li>
     * <li>If the scopes contain separators then resource ids are extracted as the scope value up to the last index of
     * the separator</li>
     * <li>Some scopes can be hard-wired to resource ids (like the open id connect values), in which case the separator
     * is ignored</li>
     * </ul>
     *
     * @see org.springframework.security.oauth2.provider.AuthorizationRequestFactory#createAuthorizationRequest(java.util.Map,
     * java.lang.String, java.lang.String, java.util.Set)
     */
    @Override
    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {

        String clientId = authorizationParameters.get("client_id");
        BaseClientDetails clientDetails = new BaseClientDetails(
                clientDetailsService.loadClientByClientId(clientId));

        Set<String> scopes = OAuth2Utils.parseParameterList(authorizationParameters.get("scope"));
        String grantType = authorizationParameters.get("grant_type");
        if ((scopes == null || scopes.isEmpty())) {
            if ("client_credentials".equals(grantType)) {
                // The client authorities should be a list of scopes
                scopes = AuthorityUtils.authorityListToSet(clientDetails.getAuthorities());
            } else {
                // The default for a user token is the scopes registered with the client
                scopes = clientDetails.getScope();
            }
        }

        Set<String> scopesFromExternalAuthorities = null;
        if (!"client_credentials".equals(grantType) && securityContextAccessor.isUser()) {
            scopes = checkUserScopes(scopes, securityContextAccessor.getAuthorities(), clientDetails);

            // TODO: will the grantType ever contain client_credentials or authorization_code
            // External Authorities are things like LDAP groups that will be mapped to Oauth scopes
            // Add those scopes to the request. These scopes will not be validated against the scopes
            // registered to a client.
            // These scopes also do not need approval. The fact that they are already in an external
            // group communicates user approval. Denying approval does not mean much
            scopesFromExternalAuthorities = findScopesFromAuthorities(authorizationParameters.get("authorities"));
        }

        Set<String> resourceIds = getResourceIds(clientDetails, scopes);
        clientDetails.setResourceIds(resourceIds);
        DefaultAuthorizationRequest request = new DefaultAuthorizationRequest(authorizationParameters);
        if (!scopes.isEmpty()) {
            request.setScope(scopes);
        }
        if (scopesFromExternalAuthorities != null) {
            Map<String, String> existingAuthorizationParameters = new LinkedHashMap<String, String>();
            existingAuthorizationParameters.putAll(request.getAuthorizationParameters());
            existingAuthorizationParameters.put("external_scopes",
                    OAuth2Utils.formatParameterList(scopesFromExternalAuthorities));
            request.setAuthorizationParameters(existingAuthorizationParameters);
        }

        request.addClientDetails(clientDetails);

        return request;
    }

    private Set<String> findScopesFromAuthorities(String authorities) {
        if (null == externalGroupMappingAuthorizationManager) {
            return new HashSet<String>();
        }
        return externalGroupMappingAuthorizationManager.findScopesFromAuthorities(authorities);
    }

    /**
     * Apply UAA rules to validate the requested scope. For client credentials grants the valid scopes are actually in
     * the authorities of the client.
     *
     * @see org.springframework.security.oauth2.provider.endpoint.ParametersValidator#validateParameters(java.util.Map,
     * org.springframework.security.oauth2.provider.ClientDetails)
     */
    @Override
    public void validateParameters(Map<String, String> parameters, ClientDetails clientDetails) {
        if (parameters.containsKey("scope")) {
            Set<String> validScope = clientDetails.getScope();
            if ("client_credentials".equals(parameters.get("grant_type"))) {
                validScope = AuthorityUtils.authorityListToSet(clientDetails.getAuthorities());
            }
            for (String scope : OAuth2Utils.parseParameterList(parameters.get("scope"))) {
                if (!validScope.contains(scope)) {
                    throw new InvalidScopeException(
                            "Invalid scope: " + scope
                                    + ". Did you know that you can get default scopes by simply sending no value?",
                            validScope);
                }
            }
        }
    }

    /**
     * Add or remove scopes derived from the current authenticated user's authorities (if any)
     *
     * @param scopes the initial set of scopes from the client registration
     * @param clientDetails
     * @param collection the users authorities
     * @return modified scopes adapted according to the rules specified
     */
    private Set<String> checkUserScopes(Set<String> scopes, Collection<? extends GrantedAuthority> authorities,
            ClientDetails clientDetails) {

        Set<String> result = new LinkedHashSet<String>(scopes);
        Set<String> allowed = new LinkedHashSet<String>(AuthorityUtils.authorityListToSet(authorities));

        // Add in all default scopes
        allowed.addAll(defaultScopes);
        // Find intersection of user authorities, default scopes and client scopes:
        for (Iterator<String> iter = allowed.iterator(); iter.hasNext();) {
            String scope = iter.next();
            if (!clientDetails.getScope().contains(scope)) {
                iter.remove();
            }
        }

        // Weed out disallowed scopes:
        for (Iterator<String> iter = result.iterator(); iter.hasNext();) {
            String scope = iter.next();
            if (!allowed.contains(scope)) {
                iter.remove();
            }
        }

        // Check that a token with empty scope is not going to be granted
        if (result.isEmpty() && !clientDetails.getScope().isEmpty()) {
            throw new InvalidScopeException(
                    "Invalid scope (empty) - this user is not allowed any of the requested scopes: " + scopes
                            + " (either you requested a scope that was not allowed or client '"
                            + clientDetails.getClientId() + "' is not allowed to act on behalf of this user)",
                    allowed);
        }

        return result;

    }

    private Set<String> getResourceIds(ClientDetails clientDetails, Set<String> scopes) {
        Set<String> resourceIds = new LinkedHashSet<String>();
        for (String scope : scopes) {
            if (scopeToResource.containsKey(scope)) {
                resourceIds.add(scopeToResource.get(scope));
            } else if (scope.contains(scopeSeparator) && !scope.endsWith(scopeSeparator)
                    && !scope.equals("uaa.none")) {
                String id = scope.substring(0, scope.lastIndexOf(scopeSeparator));
                resourceIds.add(id);
            }
        }
        return resourceIds.isEmpty() ? clientDetails.getResourceIds() : resourceIds;
    }

    public void setExternalGroupMappingAuthorizationManager(
            ExternalGroupMappingAuthorizationManager externalGroupMappingAuthorizationManager) {
        this.externalGroupMappingAuthorizationManager = externalGroupMappingAuthorizationManager;
    }

}