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.approval;
18  
19  import java.util.Calendar;
20  import java.util.Collection;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.springframework.beans.factory.InitializingBean;
31  import org.springframework.security.core.Authentication;
32  import org.springframework.security.oauth2.common.util.OAuth2Utils;
33  import org.springframework.security.oauth2.provider.AuthorizationRequest;
34  import org.springframework.security.oauth2.provider.ClientDetails;
35  import org.springframework.security.oauth2.provider.ClientDetailsService;
36  import org.springframework.security.oauth2.provider.ClientRegistrationException;
37  import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
38  import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus;
39  import org.springframework.util.Assert;
40  
41  /**
42   * A user approval handler that remembers approval decisions by consulting existing approvals.
43   * 
44   * @author Dave Syer
45   * 
46   */
47  public class ApprovalStoreUserApprovalHandler implements UserApprovalHandler, InitializingBean {
48  
49  	private static Log logger = LogFactory.getLog(ApprovalStoreUserApprovalHandler.class);
50  
51  	private String scopePrefix = OAuth2Utils.SCOPE_PREFIX;
52  
53  	private ApprovalStore approvalStore;
54  
55  	private int approvalExpirySeconds = -1;
56  
57  	private ClientDetailsService clientDetailsService;
58  
59  	/**
60  	 * Service to load client details (optional) for auto approval checks.
61  	 * 
62  	 * @param clientDetailsService a client details service
63  	 */
64  	public void setClientDetailsService(ClientDetailsService clientDetailsService) {
65  		this.clientDetailsService = clientDetailsService;
66  	}
67  
68  	/**
69  	 * The prefix applied to incoming parameters that signal approval or denial of a scope.
70  	 * 
71  	 * @param scopePrefix the prefix (default {@link OAuth2Utils#SCOPE_PREFIX})
72  	 */
73  	public void setScopePrefix(String scopePrefix) {
74  		this.scopePrefix = scopePrefix;
75  	}
76  
77  	/**
78  	 * @param store the approval to set
79  	 */
80  	public void setApprovalStore(ApprovalStore store) {
81  		this.approvalStore = store;
82  	}
83  
84  	private OAuth2RequestFactory requestFactory;
85  
86  	public void setRequestFactory(OAuth2RequestFactory requestFactory) {
87  		this.requestFactory = requestFactory;
88  	}
89  
90  	public void setApprovalExpiryInSeconds(int approvalExpirySeconds) {
91  		this.approvalExpirySeconds = approvalExpirySeconds;
92  	}
93  
94  	public void afterPropertiesSet() {
95  		Assert.state(approvalStore != null, "ApprovalStore must be provided");
96  		Assert.state(requestFactory != null, "OAuth2RequestFactory must be provided");
97  	}
98  
99  	public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
100 		return authorizationRequest.isApproved();
101 	}
102 
103 	public AuthorizationRequestringframework/security/oauth2/provider/AuthorizationRequest.html#AuthorizationRequest">AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
104 			Authentication userAuthentication) {
105 
106 		String clientId = authorizationRequest.getClientId();
107 		Collection<String> requestedScopes = authorizationRequest.getScope();
108 		Set<String> approvedScopes = new HashSet<String>();
109 		Set<String> validUserApprovedScopes = new HashSet<String>();
110 
111 		if (clientDetailsService != null) {
112 			try {
113 				ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
114 				for (String scope : requestedScopes) {
115 					if (client.isAutoApprove(scope)) {
116 						approvedScopes.add(scope);
117 					}
118 				}
119 				if (approvedScopes.containsAll(requestedScopes)) {
120 					// gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store.
121 					Set<Approval> approvals = new HashSet<Approval>();
122 					Date expiry = computeExpiry();
123 					for (String approvedScope : approvedScopes) {
124 						approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(),
125 								approvedScope, expiry, ApprovalStatus.APPROVED));
126 					}
127 					approvalStore.addApprovals(approvals);
128 
129 					authorizationRequest.setApproved(true);
130 					return authorizationRequest;
131 				}
132 			}
133 			catch (ClientRegistrationException e) {
134 				logger.warn("Client registration problem prevent autoapproval check for client=" + clientId);
135 			}
136 		}
137 
138 		if (logger.isDebugEnabled()) {
139 			StringBuilder builder = new StringBuilder("Looking up user approved authorizations for ");
140 			builder.append("client_id=" + clientId);
141 			builder.append(" and username=" + userAuthentication.getName());
142 			logger.debug(builder.toString());
143 		}
144 
145 		// Find the stored approvals for that user and client
146 		Collection<Approval> userApprovals = approvalStore.getApprovals(userAuthentication.getName(), clientId);
147 
148 		// Look at the scopes and see if they have expired
149 		Date today = new Date();
150 		for (Approval approval : userApprovals) {
151 			if (approval.getExpiresAt().after(today)) {
152 				if (approval.getStatus() == ApprovalStatus.APPROVED) {
153 					validUserApprovedScopes.add(approval.getScope());
154 					approvedScopes.add(approval.getScope());
155 				}
156 			}
157 		}
158 
159 		if (logger.isDebugEnabled()) {
160 			logger.debug("Valid user approved/denied scopes are " + validUserApprovedScopes);
161 		}
162 
163 		// If the requested scopes have already been acted upon by the user,
164 		// this request is approved
165 		if (validUserApprovedScopes.containsAll(requestedScopes)) {
166 			approvedScopes.retainAll(requestedScopes);
167 			// Set only the scopes that have been approved by the user
168 			authorizationRequest.setScope(approvedScopes);
169 			authorizationRequest.setApproved(true);
170 		}
171 
172 		return authorizationRequest;
173 
174 	}
175 
176 	private Date computeExpiry() {
177 		Calendar expiresAt = Calendar.getInstance();
178 		if (approvalExpirySeconds == -1) { // use default of 1 month
179 			expiresAt.add(Calendar.MONTH, 1);
180 		}
181 		else {
182 			expiresAt.add(Calendar.SECOND, approvalExpirySeconds);
183 		}
184 		return expiresAt.getTime();
185 	}
186 
187 	/**
188 	 * Requires the authorization request to be explicitly approved, including all individual scopes, and the user to be
189 	 * authenticated. A scope that was requested in the authorization request can be approved by sending a request
190 	 * parameter <code>scope.&lt;scopename&gt;</code> equal to "true" or "approved" (otherwise it will be assumed to
191 	 * have been denied). The {@link ApprovalStore} will be updated to reflect the inputs.
192 	 * 
193 	 * @param authorizationRequest The authorization request.
194 	 * @param userAuthentication the current user authentication
195 	 * 
196 	 * @return An approved request if all scopes have been approved by the current user.
197 	 */
198 	public AuthorizationRequestringframework/security/oauth2/provider/AuthorizationRequest.html#AuthorizationRequest">AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
199 			Authentication userAuthentication) {
200 		// Get the approved scopes
201 		Set<String> requestedScopes = authorizationRequest.getScope();
202 		Set<String> approvedScopes = new HashSet<String>();
203 		Set<Approval> approvals = new HashSet<Approval>();
204 
205 		Date expiry = computeExpiry();
206 
207 		// Store the scopes that have been approved / denied
208 		Map<String, String> approvalParameters = authorizationRequest.getApprovalParameters();
209 		for (String requestedScope : requestedScopes) {
210 			String approvalParameter = scopePrefix + requestedScope;
211 			String value = approvalParameters.get(approvalParameter);
212 			value = value == null ? "" : value.toLowerCase();
213 			if ("true".equals(value) || value.startsWith("approve")) {
214 				approvedScopes.add(requestedScope);
215 				approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(),
216 						requestedScope, expiry, ApprovalStatus.APPROVED));
217 			}
218 			else {
219 				approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(),
220 						requestedScope, expiry, ApprovalStatus.DENIED));
221 			}
222 		}
223 		approvalStore.addApprovals(approvals);
224 
225 		boolean approved;
226 		authorizationRequest.setScope(approvedScopes);
227 		if (approvedScopes.isEmpty() && !requestedScopes.isEmpty()) {
228 			approved = false;
229 		}
230 		else {
231 			approved = true;
232 		}
233 		authorizationRequest.setApproved(approved);
234 		return authorizationRequest;
235 	}
236 
237 	@Override
238 	public Map<String, Object> getUserApprovalRequest(AuthorizationRequest authorizationRequest,
239 			Authentication userAuthentication) {
240 		Map<String, Object> model = new HashMap<String, Object>();
241 		model.putAll(authorizationRequest.getRequestParameters());
242 		Map<String, String> scopes = new LinkedHashMap<String, String>();
243 		for (String scope : authorizationRequest.getScope()) {
244 			scopes.put(scopePrefix + scope, "false");
245 		}
246 		for (Approval approval : approvalStore.getApprovals(userAuthentication.getName(),
247 				authorizationRequest.getClientId())) {
248 			if (authorizationRequest.getScope().contains(approval.getScope())) {
249 				scopes.put(scopePrefix + approval.getScope(),
250 						approval.getStatus() == ApprovalStatus.APPROVED ? "true" : "false");
251 			}
252 		}
253 		model.put("scopes", scopes);
254 		return model;
255 	}
256 }