View Javadoc
1   /*
2    * Copyright 2006-2011 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5    * the License. You may obtain a copy of the License at
6    *
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11   * specific language governing permissions and limitations under the License.
12   */
13  
14  package org.springframework.security.oauth2.provider.expression;
15  
16  import java.util.Arrays;
17  import java.util.LinkedHashSet;
18  import java.util.Set;
19  
20  import org.springframework.security.access.AccessDeniedException;
21  import org.springframework.security.core.Authentication;
22  import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
23  
24  /**
25   * A convenience object for security expressions in OAuth2 protected resources, providing public methods that act on the
26   * current authentication.
27   * 
28   * @author Dave Syer
29   * @author Rob Winch
30   * @author Radek Ostrowski
31   * 
32   */
33  public class OAuth2SecurityExpressionMethods {
34  
35  	private final Authentication authentication;
36  
37  	private Set<String> missingScopes = new LinkedHashSet<String>();
38  
39  	public OAuth2SecurityExpressionMethods(Authentication authentication) {
40  		this.authentication = authentication;
41  	}
42  
43  	/**
44  	 * Check if any scope decisions have been denied in the current context and throw an exception if so. This method
45  	 * automatically wraps any expressions when using {@link OAuth2MethodSecurityExpressionHandler} or
46  	 * {@link OAuth2WebSecurityExpressionHandler}.
47  	 * 
48  	 * OAuth2Example usage:
49  	 * 
50  	 * <pre>
51  	 * access = &quot;#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))&quot;
52  	 * </pre>
53  	 * 
54  	 * Will automatically be wrapped to ensure that explicit errors are propagated rather than a generic error when
55  	 * returning false:
56  	 * 
57  	 * <pre>
58  	 * access = &quot;#oauth2.throwOnError(#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))&quot;
59  	 * </pre>
60  	 * 
61  	 * N.B. normally this method will be automatically wrapped around all your access expressions. You could use it
62  	 * explicitly to get more control, or if you have registered your own <code>ExpressionParser</code> you might need
63  	 * it.
64  	 * 
65  	 * @param decision the existing access decision
66  	 * @return true if the OAuth2 token has one of these scopes
67  	 * @throws InsufficientScopeException if the scope is invalid and we the flag is set to throw the exception
68  	 */
69  	public boolean throwOnError(boolean decision) {
70  		if (!decision && !missingScopes.isEmpty()) {
71  			Throwable failure = new InsufficientScopeException("Insufficient scope for this resource", missingScopes);
72  			throw new AccessDeniedException(failure.getMessage(), failure);
73  		}
74  		return decision;
75  	}
76  
77  	/**
78  	 * Check if the OAuth2 client (not the user) has the role specified. To check the user's roles see
79  	 * {@link #clientHasRole(String)}.
80  	 * 
81  	 * @param role the role to check
82  	 * @return true if the OAuth2 client has this role
83  	 */
84  	public boolean clientHasRole(String role) {
85  		return clientHasAnyRole(role);
86  	}
87  
88  	/**
89  	 * Check if the OAuth2 client (not the user) has one of the roles specified. To check the user's roles see
90  	 * {@link #clientHasAnyRole(String...)}.
91  	 * 
92  	 * @param roles the roles to check
93  	 * @return true if the OAuth2 client has one of these roles
94  	 */
95  	public boolean clientHasAnyRole(String... roles) {
96  		return OAuth2ExpressionUtils.clientHasAnyRole(authentication, roles);
97  	}
98  
99  	/**
100 	 * Check if the current OAuth2 authentication has the scope specified.
101 	 * 
102 	 * @param scope the scope to check
103 	 * @return true if the OAuth2 authentication has the required scope
104 	 */
105 	public boolean hasScope(String scope) {
106 		return hasAnyScope(scope);
107 	}
108 
109 	/**
110 	 * Check if the current OAuth2 authentication has one of the scopes specified.
111 	 * 
112 	 * @param scopes the scopes to check
113 	 * @return true if the OAuth2 token has one of these scopes
114 	 * @throws AccessDeniedException if the scope is invalid and we the flag is set to throw the exception
115 	 */
116 	public boolean hasAnyScope(String... scopes) {
117 		boolean result = OAuth2ExpressionUtils.hasAnyScope(authentication, scopes);
118 		if (!result) {
119 			missingScopes.addAll(Arrays.asList(scopes));
120 		}
121 		return result;
122 	}
123 
124 	/**
125 	 * Check if the current OAuth2 authentication has one of the scopes matching a specified regex expression.
126 	 * 
127 	 * <pre>
128 	 * access = &quot;#oauth2.hasScopeMatching('.*_admin:manage_scopes')))&quot;
129 	 * </pre>
130 	 * 
131 	 * @param scopeRegex the scope regex to match
132 	 * @return true if the OAuth2 authentication has the required scope
133 	 */
134 	public boolean hasScopeMatching(String scopeRegex) {
135 		return hasAnyScopeMatching(scopeRegex);
136 	}
137 
138 	/**
139 	 * Check if the current OAuth2 authentication has one of the scopes matching a specified regex expression.
140 	 * 
141 	 * <pre>
142 	 * access = &quot;#oauth2.hasAnyScopeMatching('admin:manage_scopes','.*_admin:manage_scopes','.*_admin:read_scopes')))&quot;
143 	 * </pre>
144 	 * 
145 	 * @param scopesRegex the scopes regex to match
146 	 * @return true if the OAuth2 token has one of these scopes
147 	 * @throws AccessDeniedException if the scope is invalid and we the flag is set to throw the exception
148 	 */
149 	public boolean hasAnyScopeMatching(String... scopesRegex) {
150 
151 		boolean result = OAuth2ExpressionUtils.hasAnyScopeMatching(authentication, scopesRegex);
152 		if (!result) {
153 			missingScopes.addAll(Arrays.asList(scopesRegex));
154 		}
155 		return result;
156 	}
157 
158 	/**
159 	 * Deny access to oauth requests, so used for example to only allow web UI users to access a resource.
160 	 * 
161 	 * @return true if the current authentication is not an OAuth2 type
162 	 */
163 	public boolean denyOAuthClient() {
164 		return !OAuth2ExpressionUtils.isOAuth(authentication);
165 	}
166 
167 	/**
168 	 * Permit access to oauth requests, so used for example to only allow machine clients to access a resource.
169 	 * 
170 	 * @return true if the current authentication is not an OAuth2 type
171 	 */
172 	public boolean isOAuth() {
173 		return OAuth2ExpressionUtils.isOAuth(authentication);
174 	}
175 
176 	/**
177 	 * Check if the current authentication is acting on behalf of an authenticated user.
178 	 * 
179 	 * @return true if the current authentication represents a user
180 	 */
181 	public boolean isUser() {
182 		return OAuth2ExpressionUtils.isOAuthUserAuth(authentication);
183 	}
184 
185 	/**
186 	 * Check if the current authentication is acting as an authenticated client application not on behalf of a user.
187 	 * 
188 	 * @return true if the current authentication represents a client application
189 	 */
190 	public boolean isClient() {
191 		return OAuth2ExpressionUtils.isOAuthClientAuth(authentication);
192 	}
193 }