View Javadoc
1   /*
2    * Copyright 2002-2011 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.security.oauth2.provider.vote;
18  
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.Set;
22  
23  import org.springframework.security.access.AccessDecisionVoter;
24  import org.springframework.security.access.AccessDeniedException;
25  import org.springframework.security.access.ConfigAttribute;
26  import org.springframework.security.core.Authentication;
27  import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
28  import org.springframework.security.oauth2.provider.AuthorizationRequest;
29  import org.springframework.security.oauth2.provider.OAuth2Authentication;
30  import org.springframework.security.oauth2.provider.OAuth2Request;
31  
32  /**
33   * <p>
34   * Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating that it is an OAuth2 scope. The
35   * default prefix string is <code>SCOPE_</code>, but this may be overridden to any value. Can also be used to deny
36   * access to an OAuth2 client by explicitly specifying an attribute value <code>DENY_OAUTH</code>. Typically you would
37   * want to explicitly deny access to all non-public resources that are not part of any scope.
38   * </p>
39   * 
40   * <p>
41   * Abstains from voting if no configuration attribute commences with the scope prefix, or if the current
42   * <code>Authentication</code> is not a {@link OAuth2Authentication} or the current client authentication is not a
43   * {@link AuthorizationRequest} (which contains the scope data). Votes to grant access if there is an exact matching
44   * {@link AuthorizationRequest#getScope() authorized scope} to a <code>ConfigAttribute</code> starting with the scope
45   * prefix. Votes to deny access if there is no exact matching authorized scope to a <code>ConfigAttribute</code>
46   * starting with the scope prefix.
47   * </p>
48   * 
49   * <p>
50   * All comparisons and prefixes are case insensitive so you can use (e.g.) <code>SCOPE_READ</code> for simple
51   * Facebook-like scope names that might be lower case in the resource definition, or
52   * <code>scope=https://my.company.com/scopes/read/</code> (<code>scopePrefix="scope="</code>) for Google-like URI scope
53   * names.
54   * </p>
55   * 
56   * @author Dave Syer
57   * 
58   */
59  public class ScopeVoter implements AccessDecisionVoter<Object> {
60  
61  	private String scopePrefix = "SCOPE_";
62  
63  	private String denyAccess = "DENY_OAUTH";
64  
65  	private boolean throwException = true;
66  
67  	/**
68  	 * Flag to determine the behaviour on access denied. If set then we throw an {@link InsufficientScopeException}
69  	 * instead of returning {@link AccessDecisionVoter#ACCESS_DENIED}. This is unconventional for an access decision
70  	 * voter because it vetos the other voters in the chain, but it enables us to pass a message to the caller with
71  	 * information about the required scope.
72  	 * 
73  	 * @param throwException the flag to set (default true)
74  	 */
75  	public void setThrowException(boolean throwException) {
76  		this.throwException = throwException;
77  	}
78  
79  	/**
80  	 * Allows the default role prefix of <code>SCOPE_</code> to be overridden. May be set to an empty value, although
81  	 * this is usually not desirable.
82  	 * 
83  	 * @param scopePrefix the new prefix
84  	 */
85  	public void setScopePrefix(String scopePrefix) {
86  		this.scopePrefix = scopePrefix;
87  	}
88  
89  	/**
90  	 * The name of the config attribute that can be used to deny access to OAuth2 client. Defaults to
91  	 * <code>DENY_OAUTH</code>.
92  	 * 
93  	 * @param denyAccess the deny access attribute value to set
94  	 */
95  	public void setDenyAccess(String denyAccess) {
96  		this.denyAccess = denyAccess;
97  	}
98  
99  	public boolean supports(ConfigAttribute attribute) {
100 		if (denyAccess.equals(attribute.getAttribute()) || (attribute.getAttribute() != null)
101 				&& attribute.getAttribute().startsWith(scopePrefix)) {
102 			return true;
103 		}
104 		else {
105 			return false;
106 		}
107 	}
108 
109 	/**
110 	 * This implementation supports any type of class, because it does not query the presented secure object.
111 	 * 
112 	 * @param clazz the secure object
113 	 * 
114 	 * @return always <code>true</code>
115 	 */
116 	public boolean supports(Class<?> clazz) {
117 		return true;
118 	}
119 
120 	public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
121 
122 		int result = ACCESS_ABSTAIN;
123 
124 		if (!(authentication instanceof OAuth2Authentication)) {
125 			return result;
126 
127 		}
128 
129 		for (ConfigAttribute attribute : attributes) {
130 			if (denyAccess.equals(attribute.getAttribute())) {
131 				return ACCESS_DENIED;
132 			}
133 		}
134 
135 		OAuth2Request clientAuthentication = ((OAuth2Authentication) authentication).getOAuth2Request();
136 
137 		for (ConfigAttribute attribute : attributes) {
138 			if (this.supports(attribute)) {
139 				result = ACCESS_DENIED;
140 
141 				Set<String> scopes = clientAuthentication.getScope();
142 				for (String scope : scopes) {
143 					if (attribute.getAttribute().toUpperCase().equals((scopePrefix + scope).toUpperCase())) {
144 						return ACCESS_GRANTED;
145 					}
146 				}
147 				if (result == ACCESS_DENIED && throwException) {
148 					InsufficientScopeException failure = new InsufficientScopeException(
149 							"Insufficient scope for this resource", Collections.singleton(attribute.getAttribute()
150 									.substring(scopePrefix.length())));
151 					throw new AccessDeniedException(failure.getMessage(), failure);
152 				}
153 			}
154 		}
155 
156 		return result;
157 	}
158 
159 }