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 = "#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))"
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 = "#oauth2.throwOnError(#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))"
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 = "#oauth2.hasScopeMatching('.*_admin:manage_scopes')))"
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 = "#oauth2.hasAnyScopeMatching('admin:manage_scopes','.*_admin:manage_scopes','.*_admin:read_scopes')))"
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 }