View Javadoc
1   /*
2    * Copyright 2006-2018 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  package org.springframework.security.oauth2.provider.endpoint;
14  
15  import static org.junit.Assert.assertEquals;
16  import static org.junit.Assert.assertFalse;
17  import static org.junit.Assert.assertNull;
18  import static org.junit.Assert.assertTrue;
19  import static org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME;
20  import static org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME;
21  
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.junit.Before;
31  import org.junit.Test;
32  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
33  import org.springframework.security.core.Authentication;
34  import org.springframework.security.core.authority.AuthorityUtils;
35  import org.springframework.security.core.authority.SimpleGrantedAuthority;
36  import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
37  import org.springframework.security.oauth2.common.OAuth2AccessToken;
38  import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
39  import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
40  import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
41  import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
42  import org.springframework.security.oauth2.common.util.OAuth2Utils;
43  import org.springframework.security.oauth2.provider.AuthorizationRequest;
44  import org.springframework.security.oauth2.provider.ClientDetails;
45  import org.springframework.security.oauth2.provider.ClientDetailsService;
46  import org.springframework.security.oauth2.provider.OAuth2Authentication;
47  import org.springframework.security.oauth2.provider.TokenGranter;
48  import org.springframework.security.oauth2.provider.TokenRequest;
49  import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
50  import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler;
51  import org.springframework.security.oauth2.provider.approval.InMemoryApprovalStore;
52  import org.springframework.security.oauth2.provider.client.BaseClientDetails;
53  import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
54  import org.springframework.util.MultiValueMap;
55  import org.springframework.web.bind.support.SimpleSessionStatus;
56  import org.springframework.web.servlet.ModelAndView;
57  import org.springframework.web.servlet.View;
58  import org.springframework.web.servlet.view.RedirectView;
59  import org.springframework.web.util.UriComponentsBuilder;
60  
61  /**
62   * @author Dave Syer
63   * 
64   */
65  public class AuthorizationEndpointTests {
66  
67  	private AuthorizationEndpoint endpoint = new AuthorizationEndpoint();
68  
69  	private HashMap<String, Object> model = new HashMap<String, Object>();
70  
71  	private SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
72  
73  	private UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken("foo", "bar",
74  			Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
75  
76  	private BaseClientDetails client;
77  
78  	private AuthorizationRequest getAuthorizationRequest(String clientId, String redirectUri, String state,
79  			String scope, Set<String> responseTypes) {
80  		HashMap<String, String> parameters = new HashMap<String, String>();
81  		parameters.put(OAuth2Utils.CLIENT_ID, clientId);
82  		if (redirectUri != null) {
83  			parameters.put(OAuth2Utils.REDIRECT_URI, redirectUri);
84  		}
85  		if (state != null) {
86  			parameters.put(OAuth2Utils.STATE, state);
87  		}
88  		if (scope != null) {
89  			parameters.put(OAuth2Utils.SCOPE, scope);
90  		}
91  		if (responseTypes != null) {
92  			parameters.put(OAuth2Utils.RESPONSE_TYPE, OAuth2Utils.formatParameterList(responseTypes));
93  		}
94  		return new AuthorizationRequest(parameters, Collections.<String, String> emptyMap(),
95  				parameters.get(OAuth2Utils.CLIENT_ID),
96  				OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)), null, null, false,
97  				parameters.get(OAuth2Utils.STATE), parameters.get(OAuth2Utils.REDIRECT_URI),
98  				OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.RESPONSE_TYPE)));
99  	}
100 
101 	@Before
102 	public void init() throws Exception {
103 		client = new BaseClientDetails();
104 		client.setRegisteredRedirectUri(Collections.singleton("https://anywhere.com"));
105 		client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "implicit"));
106 		endpoint.setClientDetailsService(new ClientDetailsService() {
107 			public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception {
108 				return client;
109 			}
110 		});
111 		endpoint.setTokenGranter(new TokenGranter() {
112 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
113 				return null;
114 			}
115 		});
116 		endpoint.setRedirectResolver(new DefaultRedirectResolver());
117 		endpoint.afterPropertiesSet();
118 	}
119 
120 	@Test(expected = IllegalStateException.class)
121 	public void testMandatoryProperties() throws Exception {
122 		endpoint = new AuthorizationEndpoint();
123 		endpoint.afterPropertiesSet();
124 	}
125 
126 	@Test
127 	public void testStartAuthorizationCodeFlow() throws Exception {
128 		ModelAndView result = endpoint.authorize(model,
129 				getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code"))
130 						.getRequestParameters(), sessionStatus, principal);
131 		assertEquals("forward:/oauth/confirm_access", result.getViewName());
132 	}
133 
134 	@Test
135 	public void testApprovalStoreAddsScopes() throws Exception {
136 		ApprovalStoreUserApprovalHandler userApprovalHandler = new ApprovalStoreUserApprovalHandler();
137 		userApprovalHandler.setApprovalStore(new InMemoryApprovalStore());
138 		endpoint.setUserApprovalHandler(userApprovalHandler);
139 		ModelAndView result = endpoint.authorize(model,
140 				getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code"))
141 						.getRequestParameters(), sessionStatus, principal);
142 		assertEquals("forward:/oauth/confirm_access", result.getViewName());
143 		assertTrue(result.getModel().containsKey("scopes"));
144 	}
145 
146 	@Test(expected = OAuth2Exception.class)
147 	public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exception {
148 		client.setAuthorizedGrantTypes(Collections.singleton("client_credentials"));
149 		ModelAndView result = endpoint.authorize(model,
150 				getAuthorizationRequest("foo", null, null, null, Collections.singleton("code")).getRequestParameters(),
151 				sessionStatus, principal);
152 		assertEquals("forward:/oauth/confirm_access", result.getViewName());
153 	}
154 
155 	@Test
156 	public void testAuthorizationCodeWithFragment() throws Exception {
157 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
158 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com#bar", null, null, Collections.singleton("code"));
159 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
160 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
161 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
162 				sessionStatus, principal);
163 		assertEquals("https://anywhere.com?code=thecode#bar", ((RedirectView) result).getUrl());
164 	}
165 
166 	@Test
167 	public void testAuthorizationCodeWithQueryParams() throws Exception {
168 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
169 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com?foo=bar", null, null, Collections.singleton("code"));
170 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
171 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
172 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
173 				sessionStatus, principal);
174 		assertEquals("https://anywhere.com?foo=bar&code=thecode", ((RedirectView) result).getUrl());
175 	}
176 
177 	@Test
178 	public void testAuthorizationCodeWithTrickyState() throws Exception {
179 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
180 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com", " =?s", null, Collections.singleton("code"));
181 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
182 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
183 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
184 				sessionStatus, principal);
185 		assertEquals("https://anywhere.com?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl());
186 	}
187 
188 	@Test
189 	public void testAuthorizationCodeWithMultipleQueryParams() throws Exception {
190 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
191 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com?foo=bar&bar=foo", null, null,
192 				Collections.singleton("code"));
193 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
194 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
195 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
196 				sessionStatus, principal);
197 		assertEquals("https://anywhere.com?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl());
198 	}
199 
200 	@Test
201 	public void testAuthorizationCodeWithTrickyQueryParams() throws Exception {
202 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
203 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com?foo=b =&bar=f $", null, null,
204 				Collections.singleton("code"));
205 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
206 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
207 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
208 				sessionStatus, principal);
209 		String url = ((RedirectView) result).getUrl();
210 		assertEquals("https://anywhere.com?foo=b%20=&bar=f%20$&code=thecode", url);
211 		MultiValueMap<String, String> params = UriComponentsBuilder.fromHttpUrl(url).build().getQueryParams();
212 		assertEquals("[b%20=]", params.get("foo").toString());
213 		assertEquals("[f%20$]", params.get("bar").toString());
214 	}
215 
216 	@Test
217 	public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception {
218 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
219 		AuthorizationRequest request = getAuthorizationRequest(
220 				"foo", "https://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, Collections.singleton("code"));
221 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
222 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
223 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
224 				sessionStatus, principal);
225 		assertEquals("https://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecode", ((RedirectView) result).getUrl());
226 	}
227 
228 	@Test
229 	public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception {
230 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices());
231 		AuthorizationRequest request = getAuthorizationRequest(
232 				"foo", "https://anywhere?t=a%3Db%26ep%3Dtest%2540test.me", null, null, Collections.singleton("code"));
233 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
234 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
235 		View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model,
236 				sessionStatus, principal);
237 		assertEquals("https://anywhere?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl());
238 	}
239 
240 	@Test
241 	public void testAuthorizationCodeError() throws Exception {
242 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
243 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
244 					Authentication userAuthentication) {
245 				return authorizationRequest;
246 			}
247 
248 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
249 					Authentication userAuthentication) {
250 				return authorizationRequest;
251 			}
252 
253 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
254 				return true;
255 			}
256 		});
257 		endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices() {
258 			@Override
259 			public String createAuthorizationCode(OAuth2Authentication authentication) {
260 				throw new InvalidScopeException("FOO");
261 			}
262 		});
263 		ModelAndView result = endpoint.authorize(
264 				model,
265 				getAuthorizationRequest("foo", "https://anywhere.com", "mystate", "myscope",
266 						Collections.singleton("code")).getRequestParameters(), sessionStatus, principal);
267 		String url = ((RedirectView) result.getView()).getUrl();
268 		assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com"));
269 		assertTrue("No error: " + result, url.contains("?error="));
270 		assertTrue("Wrong state: " + result, url.contains("&state=mystate"));
271 
272 	}
273 
274 	@Test
275 	public void testAuthorizationCodeWithMultipleResponseTypes() throws Exception {
276 		Set<String> responseTypes = new HashSet<String>();
277 		responseTypes.add("code");
278 		responseTypes.add("other");
279 		ModelAndView result = endpoint.authorize(model,
280 				getAuthorizationRequest("foo", null, null, "read", responseTypes).getRequestParameters(),
281 				sessionStatus, principal);
282 		assertEquals("forward:/oauth/confirm_access", result.getViewName());
283 	}
284 
285 	@Test
286 	public void testImplicitPreApproved() throws Exception {
287 		endpoint.setTokenGranter(new TokenGranter() {
288 
289 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
290 				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
291 				token.setAdditionalInformation(Collections.singletonMap("foo", (Object) "bar"));
292 				return token;
293 
294 			}
295 		});
296 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
297 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
298 					Authentication userAuthentication) {
299 				return authorizationRequest;
300 			}
301 
302 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
303 					Authentication userAuthentication) {
304 				return authorizationRequest;
305 			}
306 
307 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
308 				return true;
309 			}
310 		});
311 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
312 				"myscope", Collections.singleton("token"));
313 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
314 				principal);
315 		String url = ((RedirectView) result.getView()).getUrl();
316 		assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com"));
317 		assertTrue("Wrong state: " + result, url.contains("&state=mystate"));
318 		assertTrue("Wrong token: " + result, url.contains("access_token="));
319 		assertTrue("Wrong token: " + result, url.contains("foo=bar"));
320 	}
321 
322 	@Test
323 	public void testImplicitAppendsScope() throws Exception {
324 		endpoint.setTokenGranter(new TokenGranter() {
325 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
326 				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
327 				token.setScope(Collections.singleton("read"));
328 				return token;
329 			}
330 		});
331 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
332 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
333 					Authentication userAuthentication) {
334 				return authorizationRequest;
335 			}
336 
337 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
338 					Authentication userAuthentication) {
339 				return authorizationRequest;
340 			}
341 
342 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
343 				return true;
344 			}
345 		});
346 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
347 				"myscope", Collections.singleton("token"));
348 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
349 				principal);
350 		String url = ((RedirectView) result.getView()).getUrl();
351 		assertTrue("Wrong scope: " + result, url.contains("&scope=read"));
352 	}
353 
354 	@Test
355 	public void testImplicitWithQueryParam() throws Exception {
356 		endpoint.setTokenGranter(new TokenGranter() {
357 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
358 				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
359 				return token;
360 			}
361 		});
362 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
363 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
364 				return true;
365 			}
366 		});
367 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com?foo=bar",
368 				"mystate", "myscope", Collections.singleton("token"));
369 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
370 				principal);
371 		String url = ((RedirectView) result.getView()).getUrl();
372 		assertTrue("Wrong url: " + result, url.contains("foo=bar"));
373 	}
374 
375 	@Test
376 	public void testImplicitWithAdditionalInfo() throws Exception {
377 		endpoint.setTokenGranter(new TokenGranter() {
378 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
379 				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
380 				token.setAdditionalInformation(Collections.<String, Object> singletonMap("foo", "bar"));
381 				return token;
382 			}
383 		});
384 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
385 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
386 				return true;
387 			}
388 		});
389 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
390 				"myscope", Collections.singleton("token"));
391 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
392 				principal);
393 		String url = ((RedirectView) result.getView()).getUrl();
394 		assertTrue("Wrong url: " + result, url.contains("foo=bar"));
395 	}
396 
397 	@Test
398 	public void testImplicitAppendsScopeWhenDefaulting() throws Exception {
399 		endpoint.setTokenGranter(new TokenGranter() {
400 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
401 				DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
402 				token.setScope(new LinkedHashSet<String>(Arrays.asList("read", "write")));
403 				return token;
404 			}
405 		});
406 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
407 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
408 				return true;
409 			}
410 
411 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
412 					Authentication userAuthentication) {
413 				return authorizationRequest;
414 			}
415 
416 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
417 					Authentication userAuthentication) {
418 				return authorizationRequest;
419 			}
420 		});
421 		client.setScope(Collections.singleton("read"));
422 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
423 				null, Collections.singleton("token"));
424 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
425 				principal);
426 		String url = ((RedirectView) result.getView()).getUrl();
427 		assertTrue("Wrong scope: " + result, url.contains("&scope=read%20write"));
428 	}
429 
430 	@Test(expected = InvalidScopeException.class)
431 	public void testImplicitPreApprovedButInvalid() throws Exception {
432 		endpoint.setTokenGranter(new TokenGranter() {
433 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
434 				throw new IllegalStateException("Shouldn't be called");
435 			}
436 		});
437 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
438 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
439 				return true;
440 			}
441 
442 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
443 					Authentication userAuthentication) {
444 				return authorizationRequest;
445 			}
446 
447 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
448 					Authentication userAuthentication) {
449 				return authorizationRequest;
450 			}
451 		});
452 		client.setScope(Collections.singleton("smallscope"));
453 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
454 				"bigscope", Collections.singleton("token"));
455 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
456 				principal);
457 		String url = ((RedirectView) result.getView()).getUrl();
458 		assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com"));
459 	}
460 
461 	@Test
462 	public void testImplicitUnapproved() throws Exception {
463 		endpoint.setTokenGranter(new TokenGranter() {
464 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
465 				return null;
466 			}
467 		});
468 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
469 				"myscope", Collections.singleton("token"));
470 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
471 				principal);
472 		assertEquals("forward:/oauth/confirm_access", result.getViewName());
473 	}
474 
475 	@Test
476 	public void testImplicitError() throws Exception {
477 		endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() {
478 			public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
479 					Authentication userAuthentication) {
480 				return authorizationRequest;
481 			}
482 
483 			public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
484 					Authentication userAuthentication) {
485 				return authorizationRequest;
486 			}
487 
488 			public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
489 				return true;
490 			}
491 		});
492 		endpoint.setTokenGranter(new TokenGranter() {
493 			public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
494 				return null;
495 			}
496 		});
497 		AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com", "mystate",
498 				"myscope", Collections.singleton("token"));
499 		ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus,
500 				principal);
501 
502 		String url = ((RedirectView) result.getView()).getUrl();
503 		assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com"));
504 		assertTrue("No error: " + result, url.contains("#error="));
505 		assertTrue("Wrong state: " + result, url.contains("&state=mystate"));
506 
507 	}
508 
509 	@Test
510 	public void testApproveOrDeny() throws Exception {
511 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com", null, null,
512 				Collections.singleton("code"));
513 		request.setApproved(true);
514 		Map<String, String> approvalParameters = new HashMap<String, String>();
515 		approvalParameters.put("user_oauth_approval", "true");
516 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
517 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
518 		View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
519 		assertTrue("Wrong view: " + result, ((RedirectView) result).getUrl().startsWith("https://anywhere.com"));
520 	}
521 
522 	@Test
523 	public void testApprovalDenied() throws Exception {
524 		AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com", null, null, Collections.singleton("code"));
525 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
526 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
527 		Map<String, String> approvalParameters = new HashMap<String, String>();
528 		approvalParameters.put("user_oauth_approval", "false");
529 		View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
530 		String url = ((RedirectView) result).getUrl();
531 		assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com"));
532 		assertTrue("Wrong view: " + result, url.contains("error=access_denied"));
533 	}
534 
535 	@Test
536 	public void testDirectApproval() throws Exception {
537 		ModelAndView result = endpoint.authorize(model,
538 				getAuthorizationRequest("foo", "https://anywhere.com", null, "read", Collections.singleton("code"))
539 						.getRequestParameters(), sessionStatus, principal);
540 		// Should go to approval page (SECOAUTH-191)
541 		assertFalse(result.getView() instanceof RedirectView);
542 	}
543 
544 	@Test
545 	public void testRedirectUriOptionalForAuthorization() throws Exception {
546 		ModelAndView result = endpoint.authorize(model,
547 				getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code"))
548 						.getRequestParameters(), sessionStatus, principal);
549 		// RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not
550 		AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get(
551 				AUTHORIZATION_REQUEST_ATTR_NAME);
552 		assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI));
553 		assertEquals("https://anywhere.com", authorizationRequest.getRedirectUri());
554 	}
555 
556 	/**
557 	 * Ensure that if the approval endpoint is called without a resolved redirect URI, the request fails.
558 	 * @throws Exception
559 	 */
560 	@Test(expected = InvalidRequestException.class)
561 	public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Exception {
562 		AuthorizationRequest request = getAuthorizationRequest("foo", null, null, null, Collections.singleton("code"));
563 		request.setApproved(true);
564 		Map<String, String> approvalParameters = new HashMap<String, String>();
565 		approvalParameters.put("user_oauth_approval", "true");
566 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request);
567 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request));
568 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
569 
570 	}
571 
572 	@Test(expected = InvalidRequestException.class)
573 	public void testApproveWithModifiedClientId() throws Exception {
574 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
575 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
576 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
577 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
578 		authorizationRequest.setClientId("bar");		// Modify authorization request
579 		Map<String, String> approvalParameters = new HashMap<String, String>();
580 		approvalParameters.put("user_oauth_approval", "true");
581 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
582 	}
583 
584 	@Test(expected = InvalidRequestException.class)
585 	public void testApproveWithModifiedState() throws Exception {
586 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
587 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
588 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
589 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
590 		authorizationRequest.setState("state-5678");		// Modify authorization request
591 		Map<String, String> approvalParameters = new HashMap<String, String>();
592 		approvalParameters.put("user_oauth_approval", "true");
593 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
594 	}
595 
596 	@Test(expected = InvalidRequestException.class)
597 	public void testApproveWithModifiedRedirectUri() throws Exception {
598 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
599 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
600 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
601 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
602 		authorizationRequest.setRedirectUri("https://somewhere.com");		// Modify authorization request
603 		Map<String, String> approvalParameters = new HashMap<String, String>();
604 		approvalParameters.put("user_oauth_approval", "true");
605 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
606 	}
607 
608 	@Test(expected = InvalidRequestException.class)
609 	public void testApproveWithModifiedResponseTypes() throws Exception {
610 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
611 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
612 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
613 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
614 		authorizationRequest.setResponseTypes(Collections.singleton("implicit"));		// Modify authorization request
615 		Map<String, String> approvalParameters = new HashMap<String, String>();
616 		approvalParameters.put("user_oauth_approval", "true");
617 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
618 	}
619 
620 	@Test(expected = InvalidRequestException.class)
621 	public void testApproveWithModifiedScope() throws Exception {
622 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
623 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
624 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
625 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
626 		authorizationRequest.setScope(Arrays.asList("read", "write"));		// Modify authorization request
627 		Map<String, String> approvalParameters = new HashMap<String, String>();
628 		approvalParameters.put("user_oauth_approval", "true");
629 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
630 	}
631 
632 	@Test(expected = InvalidRequestException.class)
633 	public void testApproveWithModifiedApproved() throws Exception {
634 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
635 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
636 		authorizationRequest.setApproved(false);
637 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
638 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
639 		authorizationRequest.setApproved(true);		// Modify authorization request
640 		Map<String, String> approvalParameters = new HashMap<String, String>();
641 		approvalParameters.put("user_oauth_approval", "true");
642 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
643 	}
644 
645 	@Test(expected = InvalidRequestException.class)
646 	public void testApproveWithModifiedResourceIds() throws Exception {
647 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
648 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
649 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
650 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
651 		authorizationRequest.setResourceIds(Collections.singleton("resource-other"));		// Modify authorization request
652 		Map<String, String> approvalParameters = new HashMap<String, String>();
653 		approvalParameters.put("user_oauth_approval", "true");
654 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
655 	}
656 
657 	@Test(expected = InvalidRequestException.class)
658 	public void testApproveWithModifiedAuthorities() throws Exception {
659 		AuthorizationRequest authorizationRequest = getAuthorizationRequest(
660 				"foo", "https://anywhere.com", "state-1234", "read", Collections.singleton("code"));
661 		model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
662 		model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest));
663 		authorizationRequest.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("authority-other"));		// Modify authorization request
664 		Map<String, String> approvalParameters = new HashMap<String, String>();
665 		approvalParameters.put("user_oauth_approval", "true");
666 		endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal);
667 	}
668 
669 	private class StubAuthorizationCodeServices implements AuthorizationCodeServices {
670 		private OAuth2Authentication authentication;
671 
672 		public String createAuthorizationCode(OAuth2Authentication authentication) {
673 			this.authentication = authentication;
674 			return "thecode";
675 		}
676 
677 		public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException {
678 			return authentication;
679 		}
680 	}
681 
682 }