Skip to content

Commit cab137b

Browse files
committed
Merge branch 'releases/2.6.2'
2 parents fc56717 + 8cfac93 commit cab137b

File tree

11 files changed

+181
-30
lines changed

11 files changed

+181
-30
lines changed

docs/UAA-APIs.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,45 @@ URI.
240240
* ``curl -v -H "Accept:application/json" "http://localhost:8080/uaa/oauth/authorize?response_type=code&client_id=app&scope=password.write&redirect_uri=http%3A%2F%2Fwww.example.com%2Fcallback" --cookie cookies.txt --cookie-jar cookies.txt``
241241
* ``curl -v -H "Accept:application/json" http://localhost:8080/uaa/oauth/authorize -d "scope.0=scope.password.write&user_oauth_approval=true" --cookie cookies.txt --cookie-jar cookies.txt``
242242

243+
API Authorization Requests Code: ``GET /oauth/authorize`` (non standard /oauth/authorize)
244+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
245+
246+
247+
* Request: ``GET /oauth/authorize``
248+
* Request Parameters:
249+
250+
* ``client_id=some_valid_client_id``
251+
* ``scope=``
252+
* ``response_type=code``
253+
* ``redirect_uri`` - optional if the client has a redirect URI already registered.
254+
* ``state`` - any random string - will be returned in the Location header as a query parameter
255+
256+
* Request Header:
257+
258+
* Authorization: Bearer <token containing uaa.user scope> - the authentication for this user
259+
260+
* Response Header: Containing the code::
261+
262+
HTTP/1.1 302 Found
263+
Location: https://www.cloudfoundry.example.com?code=F45jH&state=<your state>
264+
or in case of error
265+
Location: https://www.cloudfoundry.example.com?error=<error code>
266+
267+
* Response Codes::
268+
269+
302 - Found
270+
271+
*Notes about this API*
272+
273+
* The client must have autoapprove=true, or you will not get a code back
274+
* If the client doesn't have a redirect_uri registered, it is an required parameter of the request
275+
* The token must have scope "uaa.user" in order for this request to succeed
276+
277+
*Sample curl commands for this flow*
278+
279+
*
280+
281+
243282
Client Obtains Token: ``POST /oauth/token``
244283
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
245284

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=2.6.1
1+
version=2.6.2

uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<entry key="id" value="cf" />
6565
<entry key="authorized-grant-types" value="implicit,password,refresh_token" />
6666
<entry key="scope"
67-
value="cloud_controller.read,cloud_controller.write,openid,password.write,scim.userids,cloud_controller.admin,scim.read,scim.write" />
67+
value="uaa.user,cloud_controller.read,cloud_controller.write,openid,password.write,scim.userids,cloud_controller.admin,scim.read,scim.write" />
6868
<entry key="authorities" value="uaa.none" />
6969
<entry key="autoapprove" value="true" />
7070
</map>

uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,21 @@
124124
<csrf disabled="true"/>
125125
</http>
126126

127+
<!-- Version of the /authorize endpoint for stateless clients such as cf
128+
/oauth/authorize Authorization: Bearer <access_token> response_type=code&client_id=<value>&grant_type=authorization_code
129+
-->
130+
<http name="statelessAuthorizeApiSecurity" request-matcher-ref="oauthAuthorizeApiRequestMatcher" create-session="stateless"
131+
entry-point-ref="oauthAuthenticationEntryPoint" authentication-manager-ref="emptyAuthenticationManager"
132+
xmlns="http://www.springframework.org/schema/security" use-expressions="true">
133+
<intercept-url pattern="/**" access="#oauth2.hasScope('uaa.user')" />
134+
<custom-filter ref="backwardsCompatibleScopeParameter" position="FIRST"/>
135+
<custom-filter ref="resourceAgnosticAuthenticationFilter" position="PRE_AUTH_FILTER" />
136+
<access-denied-handler ref="oauthAccessDeniedHandler" />
137+
<expression-handler ref="oauthWebExpressionHandler" />
138+
<anonymous enabled="false" />
139+
<csrf disabled="true"/>
140+
</http>
141+
127142
<bean id="clientAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
128143
<constructor-arg ref="clientAuthenticationManager" />
129144
<constructor-arg ref="basicAuthenticationEntryPoint" />
@@ -180,6 +195,22 @@
180195
</property>
181196
</bean>
182197

198+
<bean id="oauthAuthorizeApiRequestMatcher" class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher">
199+
<constructor-arg value="/oauth/authorize" />
200+
<property name="headers">
201+
<map>
202+
<entry key="Authorization" value="bearer " />
203+
</map>
204+
</property>
205+
<property name="parameters">
206+
<map>
207+
<entry key="response_type" value="code" />
208+
<entry key="client_id" value="" />
209+
</map>
210+
</property>
211+
</bean>
212+
213+
183214
<bean id="authzAuthenticationFilter" class="org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationFilter">
184215
<constructor-arg ref="zoneAwareAuthzAuthenticationManager" />
185216
<property name="parameterNames">

uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,18 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
215215
String clientSecret = resource.getClientSecret();
216216
String uri = serverRunning.getUrl("/oauth/authorize?response_type={response_type}&"+
217217
"state={state}&client_id={client_id}&redirect_uri={redirect_uri}");
218+
headers.remove("Authorization");
219+
RestTemplate restTemplate = serverRunning.createRestTemplate();
218220

219-
ResponseEntity<Void> result = serverRunning.getForResponse(
220-
uri,
221-
headers,
221+
ResponseEntity<Void> result = restTemplate.exchange(uri,
222+
HttpMethod.GET,
223+
new HttpEntity<Void>(null, headers),
224+
Void.class,
222225
responseType,
223226
state,
224227
clientId,
225-
redirectUri
226-
);
228+
redirectUri);
229+
227230
assertEquals(HttpStatus.FOUND, result.getStatusCode());
228231
String location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
229232

@@ -260,7 +263,11 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
260263
}
261264

262265
location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
263-
response = serverRunning.getForString(location, headers);
266+
//response = serverRunning.getForString(location, headers);
267+
response = restTemplate.exchange(location,
268+
HttpMethod.GET,
269+
new HttpEntity<>(null,headers),
270+
String.class);
264271
if (response.getStatusCode() == HttpStatus.OK) {
265272
// The grant access page should be returned
266273
assertTrue(response.getBody().contains("Application Authorization</h1>"));

uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
import java.io.IOException;
5151
import java.net.URI;
52+
import java.net.URISyntaxException;
5253
import java.util.ArrayList;
5354
import java.util.Arrays;
5455
import java.util.Collection;
@@ -468,14 +469,16 @@ private OAuth2AccessToken getAccessTokenWithPassword(String clientId, String cli
468469
return accessToken;
469470
}
470471

471-
private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, String username, String password) {
472+
private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, String username, String password) throws URISyntaxException {
472473
HttpHeaders headers = new HttpHeaders();
473474
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
474475

475476
URI uri = serverRunning.buildUri("/oauth/authorize").queryParam("response_type", "code")
476477
.queryParam("state", "mystateid").queryParam("client_id", clientId)
477478
.queryParam("redirect_uri", "http://anywhere.com").build();
478-
ResponseEntity<Void> result = serverRunning.getForResponse(uri.toString(), headers);
479+
ResponseEntity<Void> result = serverRunning.createRestTemplate().exchange(
480+
uri.toString(), HttpMethod.GET, new HttpEntity<>(null, headers),
481+
Void.class);
479482
assertEquals(HttpStatus.FOUND, result.getStatusCode());
480483
String location = result.getHeaders().getLocation().toString();
481484

@@ -511,7 +514,11 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S
511514
}
512515
}
513516

514-
response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
517+
response = serverRunning.createRestTemplate().exchange(
518+
new URI(result.getHeaders().getLocation().toString()),
519+
HttpMethod.GET,
520+
new HttpEntity<>(null,headers),
521+
String.class);
515522
if (response.getStatusCode() == HttpStatus.OK) {
516523
// The grant access page should be returned
517524
assertTrue(response.getBody().contains("<h1>Application Authorization</h1>"));

uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ImplicitGrantIT.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Cloud Foundry
2+
* Cloud Foundry
33
* Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved.
44
*
55
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -56,7 +56,7 @@ public class ImplicitGrantIT {
5656

5757
@Autowired
5858
TestAccounts testAccounts;
59-
59+
6060
@Autowired @Rule
6161
public IntegrationTestRule integrationTestRule;
6262

@@ -107,11 +107,12 @@ public void testDefaultScopes() throws Exception {
107107

108108
String[] scopes = UriUtils.decode(params.getFirst("scope"), "UTF-8").split(" ");
109109
Assert.assertThat(Arrays.asList(scopes), containsInAnyOrder(
110-
"scim.userids",
111-
"password.write",
112-
"cloud_controller.write",
113-
"openid",
114-
"cloud_controller.read"
110+
"scim.userids",
111+
"password.write",
112+
"cloud_controller.write",
113+
"openid",
114+
"cloud_controller.read",
115+
"uaa.user"
115116
));
116117

117118
Jwt access_token = JwtHelper.decode(params.getFirst("access_token"));
@@ -127,7 +128,7 @@ public void testDefaultScopes() throws Exception {
127128
Assert.assertThat(((List<String>) claims.get("scope")), containsInAnyOrder(scopes));
128129

129130
Assert.assertThat(((List<String>) claims.get("aud")), containsInAnyOrder(
130-
"scim", "openid", "cloud_controller", "password", "cf"));
131+
"scim", "openid", "cloud_controller", "password", "cf", "uaa"));
131132
}
132133

133134
@Test

uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,12 @@ public void testImplicitGrant() throws Exception {
152152

153153
String[] scopes = UriUtils.decode(params.getFirst("scope"), "UTF-8").split(" ");
154154
Assert.assertThat(Arrays.asList(scopes), containsInAnyOrder(
155-
"scim.userids",
156-
"password.write",
157-
"cloud_controller.write",
158-
"openid",
159-
"cloud_controller.read"
155+
"scim.userids",
156+
"password.write",
157+
"cloud_controller.write",
158+
"openid",
159+
"cloud_controller.read",
160+
"uaa.user"
160161
));
161162

162163
validateToken("access_token", params.toSingleValueMap(), scopes);
@@ -177,7 +178,7 @@ private void validateToken(String paramName, Map params, String[] scopes) throws
177178
Assert.assertThat(((List<String>) claims.get("scope")), containsInAnyOrder(scopes));
178179

179180
Assert.assertThat(((List<String>) claims.get("aud")), containsInAnyOrder(
180-
"scim", "openid", "cloud_controller", "password", "cf"));
181+
"scim", "openid", "cloud_controller", "password", "cf", "uaa"));
181182
}
182183

183184
@Test
@@ -216,7 +217,8 @@ public void testPasswordGrant() throws Exception {
216217
"password.write",
217218
"cloud_controller.write",
218219
"openid",
219-
"cloud_controller.read"
220+
"cloud_controller.read",
221+
"uaa.user"
220222
));
221223

222224
validateToken("access_token", params, scopes);

uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,9 @@ public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serv
433433
URI uri = serverRunning.buildUri("/oauth/authorize").queryParam("response_type", "code")
434434
.queryParam("state", "mystateid").queryParam("client_id", resource.getClientId())
435435
.queryParam("redirect_uri", resource.getPreEstablishedRedirectUri()).build();
436-
ResponseEntity<Void> result = serverRunning.getForResponse(uri.toString(), headers);
436+
ResponseEntity<Void> result = serverRunning.createRestTemplate().exchange(
437+
uri.toString(),HttpMethod.GET, new HttpEntity<>(null,headers),
438+
Void.class);
437439
assertEquals(HttpStatus.FOUND, result.getStatusCode());
438440
String location = result.getHeaders().getLocation().toString();
439441

@@ -473,7 +475,11 @@ public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serv
473475
}
474476
}
475477

476-
response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
478+
response = serverRunning.createRestTemplate().exchange(
479+
result.getHeaders().getLocation().toString(),HttpMethod.GET, new HttpEntity<>(null,headers),
480+
String.class);
481+
482+
477483
if (response.getStatusCode() == HttpStatus.OK) {
478484
// The grant access page should be returned
479485
assertTrue(response.getBody().contains("<h1>Application Authorization</h1>"));

uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public void testLoginUsingPasscodeWithSamlToken() throws Exception {
141141
assertNotNull(accessToken.get("access_token"));
142142
assertNotNull(accessToken.get("refresh_token"));
143143
String[] scopes = ((String) accessToken.get("scope")).split(" ");
144-
assertThat(Arrays.asList(scopes), containsInAnyOrder("scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
144+
assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
145145

146146
Authentication authentication = captureSecurityContextFilter.getAuthentication();
147147
assertNotNull(authentication);
@@ -206,7 +206,7 @@ public void testLoginUsingPasscodeWithUaaToken() throws Exception {
206206
assertNotNull(accessToken.get("access_token"));
207207
assertNotNull(accessToken.get("refresh_token"));
208208
String[] scopes = ((String) accessToken.get("scope")).split(" ");
209-
assertThat(Arrays.asList(scopes), containsInAnyOrder("scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
209+
assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
210210

211211
Authentication authentication = captureSecurityContextFilter.getAuthentication();
212212
assertNotNull(authentication);

uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.cloudfoundry.identity.uaa.client.ClientConstants;
2020
import org.cloudfoundry.identity.uaa.config.PasswordPolicy;
2121
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
22+
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
2223
import org.cloudfoundry.identity.uaa.oauth.Claims;
2324
import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider;
2425
import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices;
@@ -87,6 +88,8 @@
8788
import java.util.UUID;
8889

8990
import static org.hamcrest.Matchers.containsInAnyOrder;
91+
import static org.hamcrest.Matchers.containsString;
92+
import static org.hamcrest.Matchers.stringContainsInOrder;
9093
import static org.junit.Assert.assertEquals;
9194
import static org.junit.Assert.assertNotNull;
9295
import static org.junit.Assert.assertThat;
@@ -388,6 +391,61 @@ public void testClientIdentityProviderRestrictionForPasswordGrant() throws Excep
388391
.andExpect(status().isOk());
389392
}
390393

394+
@Test
395+
public void test_Oauth_Authorize_API_Endpoint() throws Exception {
396+
String clientId = "testclient"+new RandomValueStringGenerator().generate();
397+
String scopes = "openid,uaa.user,scim.me";
398+
setUpClients(clientId, "", scopes, "authorization_code", true);
399+
String username = "testuser"+new RandomValueStringGenerator().generate();
400+
String userScopes = "";
401+
setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId());
402+
403+
String cfAccessToken = MockMvcUtils.utils().getUserOAuthAccessToken(
404+
getMockMvc(),
405+
"cf",
406+
"",
407+
username,
408+
SECRET,
409+
""
410+
);
411+
412+
String state = new RandomValueStringGenerator().generate();
413+
414+
MockHttpServletRequestBuilder oauthTokenPost = get("/oauth/authorize")
415+
.header("Authorization", "Bearer " + cfAccessToken)
416+
.param(OAuth2Utils.RESPONSE_TYPE, "code")
417+
.param(OAuth2Utils.SCOPE, "")
418+
.param(OAuth2Utils.STATE, state)
419+
.param(OAuth2Utils.CLIENT_ID, clientId);
420+
421+
MvcResult result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn();
422+
String location = result.getResponse().getHeader("Location");
423+
assertNotNull("Location must be present", location);
424+
assertThat("Location must have a code parameter.", location, containsString("code="));
425+
URL url = new URL(location);
426+
Map query = splitQuery(url);
427+
assertNotNull(query.get("code"));
428+
String code = ((List<String>) query.get("code")).get(0);
429+
assertNotNull(code);
430+
431+
String body = getMockMvc().perform(post("/oauth/token")
432+
.header("Authorization", "Basic " + new String(Base64.encode((clientId + ":" + SECRET).getBytes())))
433+
.accept(MediaType.APPLICATION_JSON)
434+
.param(OAuth2Utils.RESPONSE_TYPE, "token")
435+
.param(OAuth2Utils.GRANT_TYPE, "authorization_code")
436+
.param(OAuth2Utils.CLIENT_ID, clientId)
437+
.param("code", code))
438+
.andExpect(status().isOk())
439+
.andReturn().getResponse().getContentAsString();
440+
441+
assertNotNull("Token body must not be null.", body);
442+
assertThat(body, stringContainsInOrder(Arrays.asList("access_token", "refresh_token")));
443+
Map<String,Object> map = JsonUtils.readValue(body, new TypeReference<Map<String,Object>>() {});
444+
String accessToken = (String) map.get("access_token");
445+
OAuth2Authentication token = tokenServices.loadAuthentication(accessToken);
446+
assertTrue("Must have uaa.user scope", token.getOAuth2Request().getScope().contains("uaa.user"));
447+
}
448+
391449
@Test
392450
public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenient() throws Exception {
393451
uaaAuthorizationEndpoint.setFallbackToAuthcode(true);

0 commit comments

Comments
 (0)