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;
/**
* This voter checks scope in request is consistent with that held by the client. If there is no user in the request
* (client_credentials grant) it checks against authorities of client instead of scopes by default. Activate by adding
* <code>CLIENT_HAS_SCOPE</code> to security attributes.
*
* @author Dave Syer
*
*/
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;
}
}