View Javadoc
1   package org.springframework.security.oauth2.provider.vote;
2   
3   import java.util.Collection;
4   import java.util.Set;
5   
6   import org.springframework.security.access.AccessDecisionVoter;
7   import org.springframework.security.access.AccessDeniedException;
8   import org.springframework.security.access.ConfigAttribute;
9   import org.springframework.security.core.Authentication;
10  import org.springframework.security.core.authority.AuthorityUtils;
11  import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
12  import org.springframework.security.oauth2.provider.ClientDetails;
13  import org.springframework.security.oauth2.provider.ClientDetailsService;
14  import org.springframework.security.oauth2.provider.OAuth2Authentication;
15  import org.springframework.security.oauth2.provider.OAuth2Request;
16  
17  /**
18   * This voter checks scope in request is consistent with that held by the client. If there is no user in the request
19   * (client_credentials grant) it checks against authorities of client instead of scopes by default. Activate by adding
20   * <code>CLIENT_HAS_SCOPE</code> to security attributes.
21   * 
22   * @author Dave Syer
23   * 
24   */
25  public class ClientScopeVoter implements AccessDecisionVoter<Object> {
26  
27  	private String clientHasScope = "CLIENT_HAS_SCOPE";
28  
29  	private boolean throwException = true;
30  
31  	private ClientDetailsService clientDetailsService;
32  
33  	private boolean clientAuthoritiesAreScopes = true;
34  
35  	/**
36  	 * ClientDetailsService for looking up clients by ID.
37  	 * 
38  	 * @param clientDetailsService the client details service (mandatory)
39  	 */
40  	public void setClientDetailsService(ClientDetailsService clientDetailsService) {
41  		this.clientDetailsService = clientDetailsService;
42  	}
43  
44  	/**
45  	 * Flag to determine the behaviour on access denied. If set then we throw an {@link InsufficientScopeException}
46  	 * instead of returning {@link AccessDecisionVoter#ACCESS_DENIED}. This is unconventional for an access decision
47  	 * voter because it vetos the other voters in the chain, but it enables us to pass a message to the caller with
48  	 * information about the required scope.
49  	 * 
50  	 * @param throwException the flag to set (default true)
51  	 */
52  	public void setThrowException(boolean throwException) {
53  		this.throwException = throwException;
54  	}
55  
56  	/**
57  	 * Flag to signal that when there is no user authentication client authorities are to be treated as scopes.
58  	 * 
59  	 * @param clientAuthoritiesAreScopes the flag value (default true)
60  	 */
61  	public void setClientAuthoritiesAreScopes(boolean clientAuthoritiesAreScopes) {
62  		this.clientAuthoritiesAreScopes = clientAuthoritiesAreScopes;
63  	}
64  
65  	/**
66  	 * The name of the config attribute that can be used to deny access to OAuth2 client. Defaults to
67  	 * <code>DENY_OAUTH</code>.
68  	 * 
69  	 * @param denyAccess the deny access attribute value to set
70  	 */
71  	public void setDenyAccess(String denyAccess) {
72  		this.clientHasScope = denyAccess;
73  	}
74  
75  	public boolean supports(ConfigAttribute attribute) {
76  		if (clientHasScope.equals(attribute.getAttribute())) {
77  			return true;
78  		}
79  		else {
80  			return false;
81  		}
82  	}
83  
84  	/**
85  	 * This implementation supports any type of class, because it does not query the presented secure object.
86  	 * 
87  	 * @param clazz the secure object
88  	 * 
89  	 * @return always <code>true</code>
90  	 */
91  	public boolean supports(Class<?> clazz) {
92  		return true;
93  	}
94  
95  	public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
96  
97  		int result = ACCESS_ABSTAIN;
98  
99  		if (!(authentication instanceof OAuth2Authentication)) {
100 			return result;
101 		}
102 
103 		OAuth2Authentication oauth2Authentication = (OAuth2Authentication) authentication;
104 		OAuth2Request clientAuthentication = oauth2Authentication.getOAuth2Request();
105 		ClientDetails client = clientDetailsService.loadClientByClientId(clientAuthentication.getClientId());
106 		Set<String> scopes = clientAuthentication.getScope();
107 		if (oauth2Authentication.isClientOnly() && clientAuthoritiesAreScopes) {
108 			scopes = AuthorityUtils.authorityListToSet(clientAuthentication.getAuthorities());
109 		}
110 
111 		for (ConfigAttribute attribute : attributes) {
112 			if (this.supports(attribute)) {
113 
114 				result = ACCESS_GRANTED;
115 
116 				for (String scope : scopes) {
117 					if (!client.getScope().contains(scope)) {
118 						result = ACCESS_DENIED;
119 						break;
120 					}
121 				}
122 
123 				if (result == ACCESS_DENIED && throwException) {
124 					InsufficientScopeException failure = new InsufficientScopeException(
125 							"Insufficient scope for this resource", client.getScope());
126 					throw new AccessDeniedException(failure.getMessage(), failure);
127 				}
128 
129 				return result;
130 			}
131 		}
132 
133 		return result;
134 	}
135 
136 }