package org.springframework.security.oauth2.provider.vote;

import java.util.Collection;
import java.util.Set;

import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;

public class ClientScopeVoter implements AccessDecisionVoter<Object> {

	private String clientHasScope = "CLIENT_HAS_SCOPE";

	private boolean throwException = true;

	private ClientDetailsService clientDetailsService;

	private boolean clientAuthoritiesAreScopes = true;

	/**
	 * ClientDetailsService for looking up clients by ID.
	 * 
	 * @param clientDetailsService the client details service (mandatory)
	 */
	public void setClientDetailsService(ClientDetailsService clientDetailsService) {
		this.clientDetailsService = clientDetailsService;
	}

	/**
	 * Flag to determine the behaviour on access denied. If set then we throw an {@link InsufficientScopeException}
	 * instead of returning {@link AccessDecisionVoter#ACCESS_DENIED}. This is unconventional for an access decision
	 * voter because it vetos the other voters in the chain, but it enables us to pass a message to the caller with
	 * information about the required scope.
	 * 
	 * @param throwException the flag to set (default true)
	 */
	public void setThrowException(boolean throwException) {
		this.throwException = throwException;
	}

	/**
	 * Flag to signal that when there is no user authentication client authorities are to be treated as scopes.
	 * 
	 * @param clientAuthoritiesAreScopes the flag value (default true)
	 */
	public void setClientAuthoritiesAreScopes(boolean clientAuthoritiesAreScopes) {
		this.clientAuthoritiesAreScopes = clientAuthoritiesAreScopes;
	}

	/**
	 * The name of the config attribute that can be used to deny access to OAuth2 client. Defaults to
	 * <code>DENY_OAUTH</code>.
	 * 
	 * @param denyAccess the deny access attribute value to set
	 */
	public void setDenyAccess(String denyAccess) {
		this.clientHasScope = denyAccess;
	}

	public boolean supports(ConfigAttribute attribute) {
		if (clientHasScope.equals(attribute.getAttribute())) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * This implementation supports any type of class, because it does not query the presented secure object.
	 * 
	 * @param clazz the secure object
	 * 
	 * @return always <code>true</code>
	 */
	public boolean supports(Class<?> clazz) {
		return true;
	}

	public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {

		int result = ACCESS_ABSTAIN;

		if (!(authentication instanceof OAuth2Authentication)) {
			return result;
		}

		OAuth2Authentication oauth2Authentication = (OAuth2Authentication) authentication;
		OAuth2Request clientAuthentication = oauth2Authentication.getOAuth2Request();
		ClientDetails client = clientDetailsService.loadClientByClientId(clientAuthentication.getClientId());
		Set<String> scopes = clientAuthentication.getScope();
		if (oauth2Authentication.isClientOnly() && clientAuthoritiesAreScopes) {
			scopes = AuthorityUtils.authorityListToSet(clientAuthentication.getAuthorities());
		}

		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {

				result = ACCESS_GRANTED;

				for (String scope : scopes) {
					if (!client.getScope().contains(scope)) {
						result = ACCESS_DENIED;
						break;
					}
				}

				if (result == ACCESS_DENIED && throwException) {
					InsufficientScopeException failure = new InsufficientScopeException(
							"Insufficient scope for this resource", client.getScope());
					throw new AccessDeniedException(failure.getMessage(), failure);
				}

				return result;
			}
		}

		return result;
	}

}

@@ -791,16 +791,346 @@
 quest;%0A%0A
+/**%0A * This voter checks scope in request is consistent with that held by the client. If there is no user in the request%0A * (client_credentials grant) it checks against authorities of client instead of scopes by default. Activate by adding%0A * %3Ccode%3ECLIENT_HAS_SCOPE%3C/code%3E to security attributes.%0A * %0A * @author Dave Syer%0A * %0A */%0A
 public c

Add javadocs