diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java b/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java index dbe61e07351..6390823dda8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -47,6 +47,7 @@ public class UaaConfiguration { @Pattern(regexp = "(default|postgresql|hsqldb|mysql|oracle)") public String platform; public String spring_profiles; + public String internalHostnames; @URL(message = "issuer.uri must be a valid URL") public String issuerUri; public boolean dump_requests; @@ -73,6 +74,8 @@ public class UaaConfiguration { @Valid public Map login; @Valid + public Map logout; + @Valid public Map links; @Valid public Map smtp; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java index 6c10ce10b28..83c00d70e62 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java @@ -274,7 +274,7 @@ private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorization if (accessToken == null) { throw new UnsupportedResponseTypeException("Unsupported response type: token"); } - return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken, authentication), false, true, + return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken, authentication, true), false, true, false)); } catch (OAuth2Exception e) { if (authorizationRequest.getResponseTypes().contains("token") || fallbackToAuthcode == false) { @@ -307,17 +307,14 @@ private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequ private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken, - Authentication authUser) { + Authentication authUser, + boolean fragment) { String requestedRedirect = authorizationRequest.getRedirectUri(); if (accessToken == null) { throw new InvalidRequestException("An implicit grant could not be made"); } - boolean fragment = true; - if (requestedRedirect.contains("#")) { - fragment = false; - } StringBuilder url = new StringBuilder(); url.append("token_type=").append(encode(accessToken.getTokenType())); @@ -359,7 +356,7 @@ private String appendAccessToken(AuthorizationRequest authorizationRequest, } } - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(requestedRedirect); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(requestedRedirect); if (fragment) { String existingFragment = builder.build(true).getFragment(); if (StringUtils.hasText(existingFragment)) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java index d856635c09d..2e298334719 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java @@ -38,15 +38,13 @@ public class JdbcQueryableClientDetailsService extends AbstractQueryable T readValue(String s, Class clazz) throws JsonUtilException } } - public static T readValue(String s, TypeReference typeReference) { + public static T readValue(String s, TypeReference typeReference) { try { return objectMapper.readValue(s, typeReference); } catch (IOException e) { @@ -52,6 +52,14 @@ public static T readValue(String s, TypeReference typeReference) { } } + public static T convertValue(Object object, Class toClazz) throws JsonUtilException { + try { + return objectMapper.convertValue(object, toClazz); + } catch (IllegalArgumentException e) { + throw new JsonUtilException(e); + } + } + public static class JsonUtilException extends RuntimeException { private static final long serialVersionUID = -4804245225960963421L; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java index f9367174527..7ccf68c4853 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; +import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.web.filter.OncePerRequestFilter; @@ -20,6 +21,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -36,7 +38,6 @@ public class IdentityZoneResolvingFilter extends OncePerRequestFilter { private IdentityZoneProvisioning dao; - private Set internalHostnames = new HashSet<>(); @Override @@ -81,11 +82,12 @@ public void setIdentityZoneProvisioning(IdentityZoneProvisioning dao) { this.dao = dao; } - public void setInternalHostnames(Set hostnames) { - internalHostnames = Collections.unmodifiableSet(hostnames); + @Value("${internalHostnames:localhost}") + public void setInternalHostnames(String hostnames) { + this.internalHostnames.addAll(Arrays.asList(hostnames.split("[ ,]+"))); } - public Set getInternalHostnames() { - return internalHostnames; + public void setInternalHostnames(Set hostnames) { + this.internalHostnames.addAll(Collections.unmodifiableSet(hostnames)); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java index 71d8c238ca6..812ad6469b6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java @@ -34,11 +34,11 @@ public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisioning { - public static final String ID_PROVIDER_FIELDS = "id,version,created,lastModified,name,origin_key,type,config,identity_zone_id,active"; + public static final String ID_PROVIDER_FIELDS = "id,version,created,lastmodified,name,origin_key,type,config,identity_zone_id,active"; public static final String CREATE_IDENTITY_PROVIDER_SQL = "insert into identity_provider(" + ID_PROVIDER_FIELDS + ") values (?,?,?,?,?,?,?,?,?,?)"; - public static final String ID_PROVIDER_UPDATE_FIELDS = "version,lastModified,name,type,config,active".replace(",","=?,")+"=?"; + public static final String ID_PROVIDER_UPDATE_FIELDS = "version,lastmodified,name,type,config,active".replace(",","=?,")+"=?"; public static final String IDENTITY_PROVIDERS_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider where identity_zone_id=?"; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java index 206c8dad2cb..115e6e33b79 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java @@ -28,9 +28,9 @@ public class JdbcIdentityZoneProvisioning implements IdentityZoneProvisioning { - public static final String ID_ZONE_FIELDS = "id,version,created,lastModified,name,subdomain,description"; + public static final String ID_ZONE_FIELDS = "id,version,created,lastmodified,name,subdomain,description"; - public static final String ID_ZONE_UPDATE_FIELDS = "version,lastModified,name,subdomain,description".replace(",","=?,")+"=?"; + public static final String ID_ZONE_UPDATE_FIELDS = "version,lastmodified,name,subdomain,description".replace(",","=?,")+"=?"; public static final String CREATE_IDENTITY_ZONE_SQL = "insert into identity_zone(" + ID_ZONE_FIELDS + ") values (?,?,?,?,?,?,?)"; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java index c992cd5f5ac..09c0b293ba8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java @@ -14,7 +14,10 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -58,7 +61,7 @@ public class MultitenantJdbcClientDetailsService extends JdbcClientDetailsServic private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, " + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " - + "refresh_token_validity, additional_information, autoapprove"; + + "refresh_token_validity, additional_information, autoapprove, lastmodified"; private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE; @@ -70,7 +73,7 @@ public class MultitenantJdbcClientDetailsService extends JdbcClientDetailsServic private static final String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ? and identity_zone_id = ?"; private static final String DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (" + CLIENT_FIELDS - + ", client_id, identity_zone_id) values (?,?,?,?,?,?,?,?,?,?,?,?)"; + + ", client_id, identity_zone_id) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"; private static final String DEFAULT_UPDATE_STATEMENT = "update oauth_client_details " + "set " + CLIENT_FIELDS_FOR_UPDATE.replaceAll(", ", "=?, ") + "=? where client_id = ? and identity_zone_id = ?"; @@ -187,7 +190,8 @@ private Object[] getFieldsForUpdate(ClientDetails clientDetails) { clientDetails.getAuthorities() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails .getAuthorities()) : null, clientDetails.getAccessTokenValiditySeconds(), clientDetails.getRefreshTokenValiditySeconds(), json, getAutoApproveScopes(clientDetails), - clientDetails.getClientId(), IdentityZoneHolder.get().getId() }; + new Timestamp(System.currentTimeMillis()), + clientDetails.getClientId(), IdentityZoneHolder.get().getId()}; } private String getAutoApproveScopes(ClientDetails clientDetails) { @@ -262,6 +266,8 @@ public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException { if (rs.getObject(9) != null) { details.setRefreshTokenValiditySeconds(rs.getInt(9)); } + + String json = rs.getString(10); if (json != null) { try { @@ -276,6 +282,12 @@ public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException { if (scopes != null) { details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet(scopes)); } + + // lastModified + if (rs.getObject(12) != null) { + details.addAdditionalInformation("lastModified", rs.getTimestamp(12)); + } + return details; } } diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql new file mode 100644 index 00000000000..71dd195d027 --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql @@ -0,0 +1 @@ +ALTER TABLE oauth_client_details ADD COLUMN lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql new file mode 100644 index 00000000000..2bad673fb4a --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql @@ -0,0 +1,5 @@ +ALTER TABLE oauth_client_details ADD COLUMN lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; + +ALTER TABLE identity_provider CHANGE lastModified lastmodified TIMESTAMP NULL; + +ALTER TABLE identity_zone CHANGE lastModified lastmodified TIMESTAMP NULL; \ No newline at end of file diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql new file mode 100644 index 00000000000..71dd195d027 --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql @@ -0,0 +1 @@ +ALTER TABLE oauth_client_details ADD COLUMN lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTest.java index 3e934c6d462..1e316e5ed0e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTest.java @@ -52,8 +52,8 @@ private void assertFindsCorrectSubdomain(final String expectedSubdomain, final S IdentityZoneResolvingFilter filter = new IdentityZoneResolvingFilter(); IdentityZoneProvisioning dao = Mockito.mock(IdentityZoneProvisioning.class); filter.setIdentityZoneProvisioning(dao); - filter.setInternalHostnames(new HashSet<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray(internalHostnames)))); - + filter.setInternalHostnames(internalHostnames); + IdentityZone identityZone = new IdentityZone(); identityZone.setSubdomain(expectedSubdomain); Mockito.when(dao.retrieveBySubdomain(Mockito.eq(expectedSubdomain))).thenReturn(identityZone); @@ -86,7 +86,7 @@ public void holderIsNotSetWithNonMatchingIdentityZone() throws Exception { IdentityZoneProvisioning dao = Mockito.mock(IdentityZoneProvisioning.class); FilterChain chain = Mockito.mock(FilterChain.class); filter.setIdentityZoneProvisioning(dao); - filter.setInternalHostnames(new HashSet<>(new LinkedList<>(Arrays.asList(uaaHostname)))); + filter.setInternalHostnames(uaaHostname); IdentityZone identityZone = new IdentityZone(); identityZone.setSubdomain(incomingSubdomain); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java index 5414e5e9f50..c491ea5644d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java @@ -6,8 +6,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.sql.Timestamp; import java.util.Arrays; import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -35,9 +38,9 @@ public class MultitenantJdbcClientDetailsServiceTests { private EmbeddedDatabase db; - private static final String SELECT_SQL = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity from oauth_client_details where client_id=?"; + private static final String SELECT_SQL = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, lastmodified from oauth_client_details where client_id=?"; - private static final String INSERT_SQL = "insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, autoapprove, identity_zone_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + private static final String INSERT_SQL = "insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, autoapprove, identity_zone_id, lastmodified) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; private IdentityZone otherIdentityZone; @@ -75,7 +78,7 @@ public void testLoadingClientForNonExistingClientId() { @Test public void testLoadingClientIdWithNoDetails() { int rowsInserted = jdbcTemplate.update(INSERT_SQL, "clientIdWithNoDetails", null, null, - null, null, null, null, null, null, null, IdentityZoneHolder.get().getId()); + null, null, null, null, null, null, null, IdentityZoneHolder.get().getId(), new Timestamp(System.currentTimeMillis())); assertEquals(1, rowsInserted); @@ -96,8 +99,11 @@ public void testLoadingClientIdWithNoDetails() { @Test public void testLoadingClientIdWithAdditionalInformation() { + + Timestamp lastModifiedDate = new Timestamp(System.currentTimeMillis()); + jdbcTemplate.update(INSERT_SQL, "clientIdWithAddInfo", null, null, - null, null, null, null, null, null, null, IdentityZoneHolder.get().getId()); + null, null, null, null, null, null, null, IdentityZoneHolder.get().getId(), lastModifiedDate); jdbcTemplate .update("update oauth_client_details set additional_information=? where client_id=?", "{\"foo\":\"bar\"}", "clientIdWithAddInfo"); @@ -106,15 +112,20 @@ public void testLoadingClientIdWithAdditionalInformation() { .loadClientByClientId("clientIdWithAddInfo"); assertEquals("clientIdWithAddInfo", clientDetails.getClientId()); - assertEquals(Collections.singletonMap("foo", "bar"), - clientDetails.getAdditionalInformation()); + + Map additionalInfoMap = new HashMap<>(); + additionalInfoMap.put("foo", "bar"); + additionalInfoMap.put("lastModified", lastModifiedDate); + + assertEquals(additionalInfoMap, clientDetails.getAdditionalInformation()); + assertEquals(lastModifiedDate, clientDetails.getAdditionalInformation().get("lastModified")); } @Test public void testLoadingClientIdWithSingleDetails() { jdbcTemplate.update(INSERT_SQL, "clientIdWithSingleDetails", "mySecret", "myResource", "myScope", "myAuthorizedGrantType", - "myRedirectUri", "myAuthority", 100, 200, "true", IdentityZoneHolder.get().getId()); + "myRedirectUri", "myAuthority", 100, 200, "true", IdentityZoneHolder.get().getId(), new Timestamp(System.currentTimeMillis())); ClientDetails clientDetails = service .loadClientByClientId("clientIdWithSingleDetails"); @@ -148,7 +159,7 @@ public void testLoadingClientIdWithMultipleDetails() { "mySecret", "myResource1,myResource2", "myScope1,myScope2", "myAuthorizedGrantType1,myAuthorizedGrantType2", "myRedirectUri1,myRedirectUri2", "myAuthority1,myAuthority2", - 100, 200, "read,write", IdentityZoneHolder.get().getId()); + 100, 200, "read,write", IdentityZoneHolder.get().getId(), new Timestamp(System.currentTimeMillis())); ClientDetails clientDetails = service .loadClientByClientId("clientIdWithMultipleDetails"); diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 7da9514320f..5dc75adb47e 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -892,8 +892,36 @@ You will be able to see all tokens used by these steps in the ``~/.uaac.yml`` fi Identity Provider API Documentation ----------------------------------- +================== ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +Request ``GET /identity-providers/{id}`` (returns a single provider) or ``GET /identity-providers`` (returns an array of providers) +Header ``X-Identity-Zone-Id`` (if using zones..admin scope against default UAA zone) +Scopes Required ``zones..admin`` or ``idps.read`` +Request Parameters active_only (optional parameter for /identity-providers). Set to true to retrieve only active Identity Providers +Response body *example* :: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "id":"50cf6125-4372-475e-94e8-c43f84111e75", + "originKey":"uaa", + "name":"internal", + "type":"internal", + "config":null, + "version":0, + "created":1426260091149, + "active":true, + "identityZoneId": + "testzone1", + "last_modified":1426260091149 + } + ] + +================== ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== + ================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== -Request ``POST /identity-providers`` or ``PUT /identity-providers/{id}`` or ``GET /identity-providers/{id}`` (returns a single provider) or ``GET /identity-providers`` (returns an array of providers) +Request ``POST /identity-providers`` or ``PUT /identity-providers/{id}`` Header ``X-Identity-Zone-Id`` (if using zones..admin scope against default UAA zone) Scopes Required ``zones..admin`` or ``idps.read`` and ``idps.write`` Request body *example* :: @@ -2063,14 +2091,16 @@ Response body *example* :: "scope" : ["uaa.none"], "resource_ids" : ["none"], "authorities" : ["cloud_controller.read","cloud_controller.write","scim.read"], - "authorized_grant_types" : ["client_credentials"] + "authorized_grant_types" : ["client_credentials"], + "lastModified" : 1426260091149 }, "bar": { "client_id" : "bar", "scope" : ["cloud_controller.read","cloud_controller.write","openid"], "resource_ids" : ["none"], "authorities" : ["uaa.none"], - "authorized_grant_types" : ["authorization_code"] + "authorized_grant_types" : ["authorization_code"], + "lastModified" : 1426260091145 }} ============== =========================================================================== @@ -2091,7 +2121,8 @@ Response body *example*:: "scope" : ["uaa.none"], "resource_ids" : ["none"], "authorities" : ["cloud_controller.read","cloud_controller.write","scim.read"], - "authorized_grant_types" : ["client_credentials"] + "authorized_grant_types" : ["client_credentials"], + "lastModified" : 1426260091145 } =============== =============================================================== diff --git a/gradle.properties b/gradle.properties index fe4e88c2397..bcc6e51b451 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.2.4.1 +version=2.2.5 diff --git a/login/src/main/resources/login-ui.xml b/login/src/main/resources/login-ui.xml index c1606b8145a..9fa70c18a9d 100644 --- a/login/src/main/resources/login-ui.xml +++ b/login/src/main/resources/login-ui.xml @@ -1,6 +1,6 @@ @@ -394,7 +395,7 @@ - + diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailResetPasswordServiceTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailResetPasswordServiceTests.java index 2602a5be0e5..8c795cd7eb8 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailResetPasswordServiceTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailResetPasswordServiceTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -15,6 +15,7 @@ import java.sql.Timestamp; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.Map; import static org.hamcrest.Matchers.containsString; @@ -30,6 +31,7 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints; @@ -193,6 +195,7 @@ public void testForgotPasswordWhenTheCodeIsDenied() throws Exception { @Test public void testResetPassword() throws Exception { ScimUser user = new ScimUser("usermans-id","userman","firstName","lastName"); + user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve(eq("usermans-id"))).thenReturn(user); when(codeStore.retrieveCode(eq("secret_code"))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), "usermans-id")); diff --git a/samples/authcode/README.md b/samples/authcode/README.md new file mode 100644 index 00000000000..b795294f8d8 --- /dev/null +++ b/samples/authcode/README.md @@ -0,0 +1,38 @@ +# Authorization Code Sample Application + +This application is a sample for how you can set up your own application that uses an authorization code grant type. The application is written in java and uses Spring Cloud Security for the SSO flow. +Authorization code grant is the most common OAuth flow. + +## Quick Start + +Start your UAA + + $ git clone git@github.com:cloudfoundry/uaa.git + $ cd uaa + $ ./gradlew run + +Verify that the uaa has started by going to http://localhost:8080/uaa + +Start the authcode sample application + +### Using Gradle: + + $ cd samples/authcode + $ ./gradlew run + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.authcode.Application : Started Application in 4.937 seconds (JVM running for 5.408) + + +### Using Maven: + + $ cd samples/authcode + $ mvn package + $ java -jar target/authcode-sample-1.0.0-SNAPSHOT.jar + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.authcode.Application : Started Application in 4.937 seconds (JVM running for 5.408) + +You can start the authcode grant flow by going to http://localhost:8888 + +Login with the pre-created UAA user/password of "marissa/koala" \ No newline at end of file diff --git a/samples/authcode/application.yml b/samples/authcode/application.yml index 262a69d61a8..7543f0cefc7 100644 --- a/samples/authcode/application.yml +++ b/samples/authcode/application.yml @@ -1,5 +1,5 @@ server: - port: 8889 + port: 8888 idServiceUrl: ${ID_SERVICE_URL:https://localhost:8080/uaa} spring: thymeleaf: diff --git a/samples/authcode/pom.xml b/samples/authcode/pom.xml index a94bd52b6be..fd61e0aae6f 100644 --- a/samples/authcode/pom.xml +++ b/samples/authcode/pom.xml @@ -10,7 +10,7 @@ org.cloudfoundry.identity authcode-sample - 0.0.1-SNAPSHOT + 1.0.0-SNAPSHOT jar diff --git a/samples/client_credentials/.gitignore b/samples/client_credentials/.gitignore new file mode 100644 index 00000000000..b83d22266ac --- /dev/null +++ b/samples/client_credentials/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/samples/client_credentials/README.md b/samples/client_credentials/README.md new file mode 100644 index 00000000000..10f57833e78 --- /dev/null +++ b/samples/client_credentials/README.md @@ -0,0 +1,36 @@ +# Client Credentials Sample Application + +This application is a sample for how you can set up your own application that uses a client credentials grant type. The application is written in java and uses the Spring framework. +Client Credentials grant is typically used for service to service applications. + +## Quick Start + +Start your UAA + + $ git clone git@github.com:cloudfoundry/uaa.git + $ cd uaa + $ ./gradlew run + +Verify that the uaa has started by going to http://localhost:8080/uaa + +Start the authcode sample application + +### Using Gradle: + + $ cd samples/client_credentials + $ ./gradlew run + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.clientcredentials.Application : Started Application in 4.937 seconds (JVM running for 5.408) + + +### Using Maven: + + $ cd samples/client_credentials + $ mvn package + $ java -jar target/clientcredentials-sample-1.0.0-SNAPSHOT.jar + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.clientcredentials.Application : Started Application in 4.937 seconds (JVM running for 5.408) + +You can start the client credentials grant flow by going to http://localhost:8888 \ No newline at end of file diff --git a/samples/client_credentials/application.yml b/samples/client_credentials/application.yml new file mode 100644 index 00000000000..f7aa3965856 --- /dev/null +++ b/samples/client_credentials/application.yml @@ -0,0 +1,14 @@ +server: + port: 8888 +idServiceUrl: ${ID_SERVICE_URL:https://localhost:8080/uaa} +spring: + thymeleaf: + cache: false + oauth2: + client: + authorizationUri: ${idServiceUrl}/oauth/authorize + accessTokenUri: ${idServiceUrl}/oauth/token + clientId: oauth_showcase_client_credentials + clientSecret: secret +logging.level: + org.springframework.security: DEBUG \ No newline at end of file diff --git a/samples/client_credentials/build.gradle b/samples/client_credentials/build.gradle new file mode 100644 index 00000000000..a3cc8c1121f --- /dev/null +++ b/samples/client_credentials/build.gradle @@ -0,0 +1,27 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE") + } +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'spring-boot' + +repositories { + mavenCentral() +} + +dependencies { + compile 'org.springframework.boot:spring-boot-starter-thymeleaf' + compile 'org.springframework.cloud:spring-cloud-starter-oauth2:1.0.0.RELEASE' +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.12' +} + diff --git a/samples/client_credentials/gradle.properties b/samples/client_credentials/gradle.properties new file mode 100644 index 00000000000..8d0c7be9623 --- /dev/null +++ b/samples/client_credentials/gradle.properties @@ -0,0 +1 @@ +version=1.0.0-SNAPSHOT diff --git a/samples/client_credentials/gradle/wrapper/gradle-wrapper.jar b/samples/client_credentials/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..b7612167031 Binary files /dev/null and b/samples/client_credentials/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/client_credentials/gradle/wrapper/gradle-wrapper.properties b/samples/client_credentials/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..6708eb39e60 --- /dev/null +++ b/samples/client_credentials/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 18 08:04:57 MDT 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip diff --git a/samples/client_credentials/gradlew b/samples/client_credentials/gradlew new file mode 100755 index 00000000000..91a7e269e19 --- /dev/null +++ b/samples/client_credentials/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/samples/client_credentials/gradlew.bat b/samples/client_credentials/gradlew.bat new file mode 100644 index 00000000000..8a0b282aa68 --- /dev/null +++ b/samples/client_credentials/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/client_credentials/manifest.yml b/samples/client_credentials/manifest.yml new file mode 100644 index 00000000000..a107743438f --- /dev/null +++ b/samples/client_credentials/manifest.yml @@ -0,0 +1,11 @@ +--- +applications: + - name: clientcredentials-sample + memory: 512M + instances: 1 + path: target/clientcredentials-sample-0.0.1-SNAPSHOT.jar + env: + SKIP_SSL_VALIDATION: "true" + ID_SERVICE_URL: https://uaa.10.244.0.34.xip.io + CLIENT_ID: oauth_showcase_client_credentials + CLIENT_SECRET: secret diff --git a/samples/client_credentials/pom.xml b/samples/client_credentials/pom.xml new file mode 100644 index 00000000000..81769f88a62 --- /dev/null +++ b/samples/client_credentials/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-starter-parent + 1.0.0.RELEASE + + + org.cloudfoundry.identity + clientcredentials-sample + 1.0.0-SNAPSHOT + jar + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + UTF-8 + 1.7 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/Application.java b/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/Application.java new file mode 100644 index 00000000000..0399ba4ba9e --- /dev/null +++ b/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/Application.java @@ -0,0 +1,91 @@ +package org.cloudfoundry.identity.samples.clientcredentials; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +@EnableAutoConfiguration +@ComponentScan +@Controller +public class Application { + + public static void main(String[] args) { + if ("true".equals(System.getenv("SKIP_SSL_VALIDATION"))) { + SSLValidationDisabler.disableSSLValidation(); + } + SpringApplication.run(Application.class, args); + } + + @Autowired + private ObjectMapper objectMapper; + + @Value("${idServiceUrl}") + private String uaaLocation; + + @Autowired + @Qualifier("clientCredentialsRestTemplate") + private OAuth2RestTemplate clientCredentialsRestTemplate; + + @RequestMapping("/") + public String index(HttpServletRequest request, Model model) { + return "index"; + } + + @RequestMapping("/client_credentials") + public String clientCredentials(Model model) throws Exception { + Object clientResponse = clientCredentialsRestTemplate.getForObject("{uaa}/oauth/clients", Object.class, + uaaLocation); + model.addAttribute("clients", clientResponse); + model.addAttribute("token", getToken(clientCredentialsRestTemplate.getOAuth2ClientContext())); + return "client_credentials"; + } + + @Configuration + @EnableConfigurationProperties + @EnableOAuth2Client + public static class Config { + @Bean + @ConfigurationProperties(prefix = "spring.oauth2.client") + ClientCredentialsResourceDetails clientCredentialsResourceDetails() { + return new ClientCredentialsResourceDetails(); + } + + @Bean + OAuth2RestTemplate clientCredentialsRestTemplate() { + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(clientCredentialsResourceDetails()); + return restTemplate; + } + } + + private Map getToken(OAuth2ClientContext clientContext) throws Exception { + if (clientContext.getAccessToken() != null) { + String tokenBase64 = clientContext.getAccessToken().getValue().split("\\.")[1]; + return objectMapper.readValue(Base64.decodeBase64(tokenBase64), new TypeReference>() { + }); + } + return null; + } +} \ No newline at end of file diff --git a/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/SSLValidationDisabler.java b/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/SSLValidationDisabler.java new file mode 100644 index 00000000000..fb03cbdada2 --- /dev/null +++ b/samples/client_credentials/src/main/java/org/cloudfoundry/identity/samples/clientcredentials/SSLValidationDisabler.java @@ -0,0 +1,43 @@ +package org.cloudfoundry.identity.samples.clientcredentials; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class SSLValidationDisabler { + public static void disableSSLValidation() { + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } }; + + // Install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } catch (GeneralSecurityException e) { + } + } + +} diff --git a/samples/client_credentials/src/main/resources/application.yml b/samples/client_credentials/src/main/resources/application.yml new file mode 100644 index 00000000000..2b74ef4eca9 --- /dev/null +++ b/samples/client_credentials/src/main/resources/application.yml @@ -0,0 +1,13 @@ +security: + ignored: /favicon.ico, / + basic: + enabled: false +idServiceUrl: ${ID_SERVICE_URL} +spring.oauth2: + client: + accessTokenUri: ${ID_SERVICE_URL}/oauth/token + userAuthorizationUri: ${ID_SERVICE_URL}/oauth/authorize + clientId: ${CLIENT_ID} + clientSecret: ${CLIENT_SECRET} + resource: + jwt.keyUri: ${ID_SERVICE_URL}/token_key \ No newline at end of file diff --git a/samples/client_credentials/src/main/resources/log4j.xml b/samples/client_credentials/src/main/resources/log4j.xml new file mode 100644 index 00000000000..c0c7e9f5697 --- /dev/null +++ b/samples/client_credentials/src/main/resources/log4j.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/client_credentials/src/main/resources/templates/client_credentials.html b/samples/client_credentials/src/main/resources/templates/client_credentials.html new file mode 100644 index 00000000000..16edce77dc3 --- /dev/null +++ b/samples/client_credentials/src/main/resources/templates/client_credentials.html @@ -0,0 +1,42 @@ + + + + Client Credentials + + + + +

Client Credentials

+ +

You've used a client_credentials grant to access protected data on UAA. Here's the result of calling /oauth/clients:

+

Clients

+ + + + + + + + + + + + +
Client IDAuthorized Grant TypesScopesAuthorities
client_idauthorized_grant_typesscopesauthorities
+

This is the token that was used:

+

+
+

What do you want to do?

+ + + + \ No newline at end of file diff --git a/samples/client_credentials/src/main/resources/templates/index.html b/samples/client_credentials/src/main/resources/templates/index.html new file mode 100644 index 00000000000..efe110ab5be --- /dev/null +++ b/samples/client_credentials/src/main/resources/templates/index.html @@ -0,0 +1,17 @@ + + + + Client Credentials Sample + + + +

Client Credentials sample

+

What do you want to do?

+ + + + \ No newline at end of file diff --git a/samples/implicit/.gitignore b/samples/implicit/.gitignore new file mode 100644 index 00000000000..b83d22266ac --- /dev/null +++ b/samples/implicit/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/samples/implicit/README.md b/samples/implicit/README.md new file mode 100644 index 00000000000..7fd530161fe --- /dev/null +++ b/samples/implicit/README.md @@ -0,0 +1,38 @@ +# Implicit Grant Sample Application + +This application is a sample for how you can set up your own application that uses an implicit grant type. The application is written in java and uses Spring framework. +Implicit grants are typically used for Single-page javascript apps. + +## Quick Start + +Start your UAA + + $ git clone git@github.com:cloudfoundry/uaa.git + $ cd uaa + $ ./gradlew run + +Verify that the uaa has started by going to http://localhost:8080/uaa + +Start the implicit sample application + +### Using Gradle: + + $ cd samples/implicit + $ ./gradlew run + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.implicit.Application : Started Application in 4.937 seconds (JVM running for 5.408) + + +### Using Maven: + + $ cd samples/implicit + $ mvn package + $ java -jar target/implicit-sample-1.0.0-SNAPSHOT.jar + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.implicit.Application : Started Application in 4.937 seconds (JVM running for 5.408) + +You can start the implicit grant flow by going to http://localhost:8888 + +Login with the pre-created UAA user/password of "marissa/koala" \ No newline at end of file diff --git a/samples/implicit/application.yml b/samples/implicit/application.yml new file mode 100644 index 00000000000..b290570bb53 --- /dev/null +++ b/samples/implicit/application.yml @@ -0,0 +1,16 @@ +server: + port: 8888 +idServiceUrl: ${ID_SERVICE_URL:https://localhost:8080/uaa} +spring: + thymeleaf: + cache: false + oauth2: + client: + clientId: oauth_showcase_implicit_grant + accessTokenUri: ${idServiceUrl}/oauth/token + userAuthorizationUri: ${idServiceUrl}/oauth/authorize + resource: + jwt.keyUri: ${idServiceUrl}/token_key + +logging.level: + org.springframework.security: DEBUG \ No newline at end of file diff --git a/samples/implicit/build.gradle b/samples/implicit/build.gradle new file mode 100644 index 00000000000..a3cc8c1121f --- /dev/null +++ b/samples/implicit/build.gradle @@ -0,0 +1,27 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE") + } +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'spring-boot' + +repositories { + mavenCentral() +} + +dependencies { + compile 'org.springframework.boot:spring-boot-starter-thymeleaf' + compile 'org.springframework.cloud:spring-cloud-starter-oauth2:1.0.0.RELEASE' +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.12' +} + diff --git a/samples/implicit/gradle.properties b/samples/implicit/gradle.properties new file mode 100644 index 00000000000..8d0c7be9623 --- /dev/null +++ b/samples/implicit/gradle.properties @@ -0,0 +1 @@ +version=1.0.0-SNAPSHOT diff --git a/samples/implicit/gradle/wrapper/gradle-wrapper.jar b/samples/implicit/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..b7612167031 Binary files /dev/null and b/samples/implicit/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/implicit/gradle/wrapper/gradle-wrapper.properties b/samples/implicit/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..6708eb39e60 --- /dev/null +++ b/samples/implicit/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 18 08:04:57 MDT 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip diff --git a/samples/implicit/gradlew b/samples/implicit/gradlew new file mode 100755 index 00000000000..91a7e269e19 --- /dev/null +++ b/samples/implicit/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/samples/implicit/gradlew.bat b/samples/implicit/gradlew.bat new file mode 100644 index 00000000000..8a0b282aa68 --- /dev/null +++ b/samples/implicit/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/implicit/manifest.yml b/samples/implicit/manifest.yml new file mode 100644 index 00000000000..a264d15421d --- /dev/null +++ b/samples/implicit/manifest.yml @@ -0,0 +1,11 @@ +--- +applications: + - name: implicit-sample + memory: 512M + instances: 1 + path: target/implicit-sample-0.0.1-SNAPSHOT.jar + env: + SKIP_SSL_VALIDATION: "true" + ID_SERVICE_URL: https://uaa.10.244.0.34.xip.io + CLIENT_ID: oauth_showcase_implicit_grant + CLIENT_SECRET: secret diff --git a/samples/implicit/pom.xml b/samples/implicit/pom.xml new file mode 100644 index 00000000000..228d131736b --- /dev/null +++ b/samples/implicit/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-starter-parent + 1.0.0.RELEASE + + + org.cloudfoundry.identity + implicit-sample + 1.0.0-SNAPSHOT + jar + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + UTF-8 + 1.7 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/Application.java b/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/Application.java new file mode 100644 index 00000000000..1f8b5f5bb08 --- /dev/null +++ b/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/Application.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.identity.samples.implicit; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +@Configuration +@EnableAutoConfiguration +@ComponentScan +@Controller +public class Application { + + public static void main(String[] args) { + if ("true".equals(System.getenv("SKIP_SSL_VALIDATION"))) { + SSLValidationDisabler.disableSSLValidation(); + } + SpringApplication.run(Application.class, args); + } + + @Value("${idServiceUrl}") + private String idServiceUrl; + + @RequestMapping("/") + public String index(HttpServletRequest request, Model model) { + request.getSession().invalidate(); + model.addAttribute("idServiceUrl", idServiceUrl); + model.addAttribute("thisUrl", UrlUtils.buildFullRequestUrl(request)); + return "index"; + } +} \ No newline at end of file diff --git a/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/SSLValidationDisabler.java b/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/SSLValidationDisabler.java new file mode 100644 index 00000000000..f24684598a2 --- /dev/null +++ b/samples/implicit/src/main/java/org/cloudfoundry/identity/samples/implicit/SSLValidationDisabler.java @@ -0,0 +1,43 @@ +package org.cloudfoundry.identity.samples.implicit; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class SSLValidationDisabler { + public static void disableSSLValidation() { + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } }; + + // Install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } catch (GeneralSecurityException e) { + } + } + +} diff --git a/samples/implicit/src/main/resources/application.yml b/samples/implicit/src/main/resources/application.yml new file mode 100644 index 00000000000..558fafe1d9d --- /dev/null +++ b/samples/implicit/src/main/resources/application.yml @@ -0,0 +1,12 @@ +security: + ignored: /favicon.ico, / + basic: + enabled: false +idServiceUrl: ${ID_SERVICE_URL} +spring.oauth2: + client: + accessTokenUri: ${ID_SERVICE_URL}/oauth/token + userAuthorizationUri: ${ID_SERVICE_URL}/oauth/authorize + clientId: ${CLIENT_ID} + resource: + jwt.keyUri: ${ID_SERVICE_URL}/token_key \ No newline at end of file diff --git a/samples/implicit/src/main/resources/log4j.xml b/samples/implicit/src/main/resources/log4j.xml new file mode 100644 index 00000000000..c0c7e9f5697 --- /dev/null +++ b/samples/implicit/src/main/resources/log4j.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/implicit/src/main/resources/public/implicit.html b/samples/implicit/src/main/resources/public/implicit.html new file mode 100644 index 00000000000..425832ef48e --- /dev/null +++ b/samples/implicit/src/main/resources/public/implicit.html @@ -0,0 +1,55 @@ + + + +Implicit sample + + + + + + +

Implicit sample

+

This is a static HTML page! The server only saw a request for /implicit.html. Everything after the # in the address bar is stuff that only your browser can see.

+

Here's the result of calling /userinfo:

+

+  

Your access token is:

+

+  

What do you want to do?

+ + + \ No newline at end of file diff --git a/samples/implicit/src/main/resources/templates/index.html b/samples/implicit/src/main/resources/templates/index.html new file mode 100644 index 00000000000..08384479c98 --- /dev/null +++ b/samples/implicit/src/main/resources/templates/index.html @@ -0,0 +1,18 @@ + + + +Implicit sample + + + +

Implicit sample

+

What do you want to do?

+ + + \ No newline at end of file diff --git a/samples/password/README.md b/samples/password/README.md new file mode 100644 index 00000000000..009313e523f --- /dev/null +++ b/samples/password/README.md @@ -0,0 +1,38 @@ +# Password Grant Sample Application + +This application is a sample for how you can set up your own application that uses a password grant type. The application is written in java uses the Spring framework. +Password grant is typically used when there is a high degree of trust between the resource owner and the client. + +## Quick Start + +Start your UAA + + $ git clone git@github.com:cloudfoundry/uaa.git + $ cd uaa + $ ./gradlew run + +Verify that the uaa has started by going to http://localhost:8080/uaa + +Start the password grant sample application + +### Using Gradle: + + $ cd samples/password + $ ./gradlew run + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.password.Application : Started Application in 4.937 seconds (JVM running for 5.408) + + +### Using Maven: + + $ cd samples/password + $ mvn package + $ java -jar target/password-sample-1.0.0-SNAPSHOT.jar + >> 2015-04-24 15:39:15.862 INFO 88632 --- [main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + >> 2015-04-24 15:39:15.947 INFO 88632 --- [main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http) + >> 2015-04-24 15:39:15.948 INFO 88632 --- [main] o.c.i.samples.password.Application : Started Application in 4.937 seconds (JVM running for 5.408) + +You can start the password grant flow by going to http://localhost:8888 + +Login with the pre-created UAA user/password of "marissa/koala" \ No newline at end of file diff --git a/samples/password/pom.xml b/samples/password/pom.xml index 1e9213ac60a..5ea09c3a56e 100644 --- a/samples/password/pom.xml +++ b/samples/password/pom.xml @@ -10,7 +10,7 @@ org.cloudfoundry.identity password-grant-sample - 0.0.1-SNAPSHOT + 1.0.0-SNAPSHOT jar diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoints.java index 6d33c03fbdc..716fe2f05cb 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoints.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -22,6 +22,8 @@ import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.JsonUtils.JsonUtilException; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.context.ApplicationEvent; @@ -49,6 +51,7 @@ import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNAUTHORIZED; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @Controller public class PasswordResetEndpoints implements ApplicationEventPublisherAware { @@ -85,7 +88,8 @@ public ResponseEntity> resetPassword(@RequestBody String emai } } ScimUser scimUser = results.get(0); - String code = expiringCodeStore.generateCode(scimUser.getId(), new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME)).getCode(); + PasswordChange change = new PasswordChange(scimUser.getId(), scimUser.getUserName()); + String code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME)).getCode(); publish(new ResetPasswordRequestEvent(email, code, SecurityContextHolder.getContext().getAuthentication())); response.put("code", code); response.put("user_id", scimUser.getId()); @@ -139,20 +143,31 @@ private ResponseEntity> changePasswordUsernamePasswordAuthent } } - private ResponseEntity> changePasswordCodeAuthenticated(PasswordChange passwordChange) { + protected ResponseEntity> changePasswordCodeAuthenticated(PasswordChange passwordChange) { ExpiringCode expiringCode = expiringCodeStore.retrieveCode(passwordChange.getCode()); if (expiringCode == null) { return new ResponseEntity<>(BAD_REQUEST); } - String userId = expiringCode.getData(); + String userId; + String userName = null; + try { + PasswordChange change = JsonUtils.readValue(expiringCode.getData(), PasswordChange.class); + userId = change.getUserId(); + userName = change.getUsername(); + } catch (JsonUtilException x) { + userId = expiringCode.getData(); + } ScimUser user = scimUserProvisioning.retrieve(userId); + Map userInfo = new HashMap<>(); try { + if (isUserModified(user, expiringCode.getExpiresAt(), userName)) { + return new ResponseEntity<>(BAD_REQUEST); + } if (!user.isVerified()) { scimUserProvisioning.verifyUser(userId, -1); } scimUserProvisioning.changePassword(userId, null, passwordChange.getNewPassword()); publish(new PasswordChangeEvent("Password changed", getUaaUser(user), SecurityContextHolder.getContext().getAuthentication())); - Map userInfo = new HashMap<>(); userInfo.put("user_id", user.getId()); userInfo.put("username", user.getUserName()); userInfo.put("email", user.getPrimaryEmail()); @@ -169,7 +184,19 @@ private ResponseEntity> changePasswordCodeAuthenticated(Passw } } - private UaaUser getUaaUser(ScimUser scimUser) { + protected boolean isUserModified(ScimUser user, Timestamp expiresAt, String userName) { + if (userName!=null) { + return ! userName.equals(user.getUserName()); + } + //left over from when all we stored in the code was the user ID + //here we will check the timestamp + //TODO - REMOVE THIS IN FUTURE RELEASE, ALL LINKS HAVE BEEN EXPIRED (except test created ones) + long codeCreated = expiresAt.getTime() - PASSWORD_RESET_LIFETIME; + long userModified = user.getMeta().getLastModified().getTime(); + return (userModified > codeCreated); + } + + protected UaaUser getUaaUser(ScimUser scimUser) { Date today = new Date(); return new UaaUser(scimUser.getId(), scimUser.getUserName(), "N/A", scimUser.getPrimaryEmail(), null, scimUser.getGivenName(), @@ -178,6 +205,16 @@ private UaaUser getUaaUser(ScimUser scimUser) { } public static class PasswordChange { + public PasswordChange() {} + + public PasswordChange(String userId, String username) { + this.userId = userId; + this.username = username; + } + + @JsonProperty("user_id") + private String userId; + @JsonProperty("username") private String username; @@ -221,6 +258,14 @@ public String getNewPassword() { public void setNewPassword(String newPassword) { this.newPassword = newPassword; } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } } protected void publish(ApplicationEvent event) { diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointsTest.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointsTest.java index 8aaf8ccabde..5d1701f261f 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointsTest.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointsTest.java @@ -16,9 +16,12 @@ import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints.PasswordChange; import org.cloudfoundry.identity.uaa.test.MockAuthentication; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.codehaus.jackson.map.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -30,7 +33,9 @@ import java.sql.Timestamp; import java.util.Arrays; +import java.util.Date; +import static org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints.PASSWORD_RESET_LIFETIME; import static org.hamcrest.Matchers.containsString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; @@ -54,12 +59,22 @@ public void setUp() throws Exception { mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); Mockito.when(expiringCodeStore.generateCode(eq("id001"), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), "id001")); + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME), "id001")); + + PasswordChange change = new PasswordChange("id001", "user@example.com"); + Mockito.when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME), "id001")); + + change = new PasswordChange("id001", "user\"'@example.com"); + Mockito.when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME), "id001")); + } @Test public void testCreatingAPasswordResetWhenTheUsernameExists() throws Exception { ScimUser user = new ScimUser("id001", "user@example.com", null, null); + user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.addEmail("user@example.com"); Mockito.when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + Origin.UAA + "\"")) .thenReturn(Arrays.asList(user)); @@ -95,6 +110,7 @@ public void testCreatingAPasswordResetWhenTheUserHasNonUaaOrigin() throws Except .thenReturn(Arrays.asList()); ScimUser user = new ScimUser("id001", "user@example.com", null, null); + user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.addEmail("user@example.com"); user.setOrigin(Origin.LDAP); Mockito.when(scimUserProvisioning.query("userName eq \"user@example.com\"")) @@ -113,6 +129,7 @@ public void testCreatingAPasswordResetWhenTheUserHasNonUaaOrigin() throws Except @Test public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() throws Exception { ScimUser user = new ScimUser("id001", "user\"'@example.com", null, null); + user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.addEmail("user\"'@example.com"); Mockito.when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\" and origin eq \"" + Origin.UAA + "\"")) .thenReturn(Arrays.asList(user)); @@ -146,9 +163,10 @@ public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() @Test public void testChangingAPasswordWithAValidCode() throws Exception { Mockito.when(expiringCodeStore.retrieveCode("secret_code")) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis()), "eyedee")); + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis()+ PASSWORD_RESET_LIFETIME), "eyedee")); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); + scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); scimUser.addEmail("user@example.com"); Mockito.when(scimUserProvisioning.retrieve("eyedee")).thenReturn(scimUser); @@ -170,9 +188,10 @@ public void testChangingAPasswordWithAValidCode() throws Exception { @Test public void testChangingAPasswordForUnverifiedUser() throws Exception { Mockito.when(expiringCodeStore.retrieveCode("secret_code")) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis()), "eyedee")); + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis()+ PASSWORD_RESET_LIFETIME), "eyedee")); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); + scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); scimUser.addEmail("user@example.com"); scimUser.setVerified(false); Mockito.when(scimUserProvisioning.retrieve("eyedee")).thenReturn(scimUser); @@ -196,6 +215,7 @@ public void testChangingAPasswordForUnverifiedUser() throws Exception { @Test public void testChangingAPasswordWithAUsernameAndPassword() throws Exception { ScimUser user = new ScimUser("id001", "user@example.com", null, null); + user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.addEmail("user@example.com"); Mockito.when(scimUserProvisioning.query("userName eq \"user@example.com\"")) .thenReturn(Arrays.asList(user)); diff --git a/uaa/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/uaa/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java index 65160c9116e..138c411160e 100644 --- a/uaa/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/uaa/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java @@ -14,15 +14,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.JsonUtils.JsonUtilException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; @@ -32,6 +29,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.PrintWriter; @@ -85,8 +83,9 @@ public ResponseEntity updateIdentityProvider(@PathVariable Str } @RequestMapping(method = GET) - public ResponseEntity> retrieveIdentityProviders() { - List identityProviderList = identityProviderProvisioning.retrieveAll(false,IdentityZoneHolder.get().getId()); + public ResponseEntity> retrieveIdentityProviders(@RequestParam(value = "active_only", required = false) String activeOnly) { + Boolean retrieveActiveOnly = Boolean.valueOf(activeOnly); + List identityProviderList = identityProviderProvisioning.retrieveAll(retrieveActiveOnly, IdentityZoneHolder.get().getId()); return new ResponseEntity<>(identityProviderList, OK); } diff --git a/uaa/src/main/resources/login.yml b/uaa/src/main/resources/login.yml index 8802f6e780c..66d36afebc1 100644 --- a/uaa/src/main/resources/login.yml +++ b/uaa/src/main/resources/login.yml @@ -37,6 +37,11 @@ links: #notifications: # url: http://localhost:3001 +#logout: +# redirect: +# url: /login +# parameter: +# disable: true login: # Enable create account and forgot password links on the Login Server (enabled by default) diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 8fffcca377b..04387130bc7 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -99,14 +99,14 @@ - + #{T(org.cloudfoundry.identity.uaa.util.UaaUrlUtils).getHostForURI(@uaaUrl)} #{T(org.cloudfoundry.identity.uaa.util.UaaUrlUtils).getHostForURI(@loginUrl)} localhost - - + + @@ -180,11 +180,6 @@ - - - - - diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml index bf626aa55ab..e6210f0d816 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml @@ -157,7 +157,6 @@ - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index 0de41a6f07f..8bf0c45b322 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -44,6 +44,8 @@ import org.springframework.security.saml.log.SAMLDefaultLogger; import org.springframework.security.saml.websso.WebSSOProfileConsumer; import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; import org.springframework.web.servlet.ViewResolver; @@ -51,6 +53,8 @@ import javax.servlet.RequestDispatcher; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -98,6 +102,7 @@ public void cleanup() throws Exception { System.clearProperty("spring.profiles.active"); System.clearProperty("uaa.url"); System.clearProperty("login.url"); + System.clearProperty("internalHostnames"); if (context != null) { context.close(); } @@ -115,13 +120,22 @@ public void cleanup() throws Exception { @Test public void testRootContextDefaults() throws Exception { + String uaa = "uaa.some.test.domain.com"; + String login = uaa.replace("uaa", "login"); + System.setProperty("uaa.url", "https://"+uaa+":555/uaa"); + System.setProperty("login.url", "https://"+login+":555/uaa"); context = getServletContext(null, "login.yml","uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); assertNotNull(context.getBean("viewResolver", ViewResolver.class)); assertNotNull(context.getBean("resetPasswordController", ResetPasswordController.class)); assertEquals(864000, context.getBean("webSSOprofileConsumer", WebSSOProfileConsumerImpl.class).getMaxAuthenticationAge()); IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); - Set defaultHostnames = new HashSet<>(Arrays.asList("localhost")); - assertEquals(filter.getInternalHostnames(), defaultHostnames); + Set defaultHostnames = new HashSet<>(Arrays.asList(uaa, login, "localhost")); + + Field internalHostnames = filter.getClass().getDeclaredField("internalHostnames"); + internalHostnames.setAccessible(true); + + assertEquals(internalHostnames.get(filter), defaultHostnames); + } @Test @@ -130,10 +144,15 @@ public void testInternalHostnames() throws Exception { String login = uaa.replace("uaa", "login"); System.setProperty("uaa.url", "https://"+uaa+":555/uaa"); System.setProperty("login.url", "https://"+login+":555/uaa"); + System.setProperty("internalHostnames", "some-other-hostname.com"); context = getServletContext(null, "login.yml","uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); - Set defaultHostnames = new HashSet<>(Arrays.asList(uaa,login, "localhost")); - assertEquals(filter.getInternalHostnames(), defaultHostnames); + Set defaultHostnames = new HashSet<>(Arrays.asList(uaa, login, "localhost", "some-other-hostname.com")); + + Field internalHostnames = filter.getClass().getDeclaredField("internalHostnames"); + internalHostnames.setAccessible(true); + + assertEquals(internalHostnames.get(filter), defaultHostnames); } @Test @@ -168,11 +187,37 @@ public void testSamlProfileNoData() throws Exception { System.setProperty("login.saml.metadataTrustCheck", "false"); context = getServletContext("default", "login.yml","uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); assertEquals(3600, context.getBean("webSSOprofileConsumer", WebSSOProfileConsumerImpl.class).getMaxAuthenticationAge()); - Assume.assumeTrue(context.getEnvironment().getProperty("login.idpMetadataURL")==null); + Assume.assumeTrue(context.getEnvironment().getProperty("login.idpMetadataURL") == null); assertNotNull(context.getBean("viewResolver", ViewResolver.class)); assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)); assertFalse(context.getBean(IdentityProviderConfigurator.class).isLegacyMetadataTrustCheck()); assertEquals(0, context.getBean(IdentityProviderConfigurator.class).getIdentityProviderDefinitions().size()); + SimpleUrlLogoutSuccessHandler handler = context.getBean(SimpleUrlLogoutSuccessHandler.class); + Method getDefaultTargetUrl = ReflectionUtils.findMethod(SimpleUrlLogoutSuccessHandler.class, "getDefaultTargetUrl"); + getDefaultTargetUrl.setAccessible(true); + Method isAlwaysUseDefaultTargetUrl = ReflectionUtils.findMethod(SimpleUrlLogoutSuccessHandler.class, "isAlwaysUseDefaultTargetUrl"); + isAlwaysUseDefaultTargetUrl.setAccessible(true); + assertEquals(true, ReflectionUtils.invokeMethod(isAlwaysUseDefaultTargetUrl, handler)); + assertEquals("/login", ReflectionUtils.invokeMethod(getDefaultTargetUrl, handler)); + } + + @Test + public void testLogoutRedirectConfiguration() throws Exception { + System.setProperty("logout.redirect.parameter.disable", "false"); + System.setProperty("logout.redirect.url", "/login?parameter=true"); + try { + context = getServletContext("default", "login.yml", "uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + SimpleUrlLogoutSuccessHandler handler = context.getBean(SimpleUrlLogoutSuccessHandler.class); + Method getDefaultTargetUrl = ReflectionUtils.findMethod(SimpleUrlLogoutSuccessHandler.class, "getDefaultTargetUrl"); + getDefaultTargetUrl.setAccessible(true); + Method isAlwaysUseDefaultTargetUrl = ReflectionUtils.findMethod(SimpleUrlLogoutSuccessHandler.class, "isAlwaysUseDefaultTargetUrl"); + isAlwaysUseDefaultTargetUrl.setAccessible(true); + assertEquals(false, ReflectionUtils.invokeMethod(isAlwaysUseDefaultTargetUrl, handler)); + assertEquals("/login?parameter=true", ReflectionUtils.invokeMethod(getDefaultTargetUrl, handler)); + } finally { + System.clearProperty("logout.redirect.parameter.disable"); + System.clearProperty("logout.redirect.url"); + } } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 122cd2b99fe..3bb26c81dec 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -48,6 +48,7 @@ import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.PortResolverImpl; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.savedrequest.DefaultSavedRequest; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.test.web.servlet.MockMvc; @@ -72,7 +73,6 @@ import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_HTML; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; @@ -127,6 +127,46 @@ public void testLogin() throws Exception { .andExpect(model().attributeExists("prompts")); } + @Test + public void testLogOut() throws Exception { + mockMvc.perform(get("/logout.do")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login")); + } + + @Test + public void testLogOutIgnoreRedirectParameter() throws Exception { + mockMvc.perform(get("/logout.do").param("redirect", "https://www.google.com")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login")); + } + + @Test + public void testLogOutEnableRedirectParameter() throws Exception { + SimpleUrlLogoutSuccessHandler logoutSuccessHandler = webApplicationContext.getBean(SimpleUrlLogoutSuccessHandler.class); + logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); + try { + mockMvc.perform(get("/logout.do").param("redirect", "https://www.google.com")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("https://www.google.com")); + } finally { + logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + } + } + + @Test + public void testLogOutChangeUrlValue() throws Exception { + SimpleUrlLogoutSuccessHandler logoutSuccessHandler = webApplicationContext.getBean(SimpleUrlLogoutSuccessHandler.class); + logoutSuccessHandler.setDefaultTargetUrl("https://www.google.com"); + try { + mockMvc.perform(get("/logout.do")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("https://www.google.com")); + } finally { + logoutSuccessHandler.setDefaultTargetUrl("/login"); + } + } + @Test public void testLoginWithAnalytics() throws Exception { mockEnvironment.setProperty("analytics.code", "secret_code"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerIntegrationTests.java deleted file mode 100644 index 360d2ad6e77..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerIntegrationTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; - -import com.googlecode.flyway.core.Flyway; -import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; -import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; -import org.cloudfoundry.identity.uaa.test.YamlServletProfileInitializerContextInitializer; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.support.XmlWebApplicationContext; - -import java.sql.Timestamp; -import java.util.List; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -public class ResetPasswordControllerIntegrationTests extends TestClassNullifier { - - XmlWebApplicationContext webApplicationContext; - - private MockMvc mockMvc; - private ExpiringCodeStore codeStore; - - @Before - public void setUp() throws Exception { - webApplicationContext = new XmlWebApplicationContext(); - new YamlServletProfileInitializerContextInitializer().initializeContext(webApplicationContext, "login.yml,uaa.yml"); - webApplicationContext.setConfigLocation("file:./src/main/webapp/WEB-INF/spring-servlet.xml"); - webApplicationContext.refresh(); - FilterChainProxy springSecurityFilterChain = webApplicationContext.getBean("springSecurityFilterChain", FilterChainProxy.class); - - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) - .addFilter(springSecurityFilterChain) - .build(); - codeStore = webApplicationContext.getBean(ExpiringCodeStore.class); - } - - @After - public void cleanUpAfterPasswordReset() throws Exception { - Flyway flyway = webApplicationContext.getBean(Flyway.class); - flyway.clean(); - webApplicationContext.destroy(); - } - - @Test - public void testResettingAPassword() throws Exception { - List users = webApplicationContext.getBean(ScimUserProvisioning.class).query("username eq \"marissa\""); - assertNotNull(users); - assertEquals(1, users.size()); - ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis()+50000)); - - MockHttpServletRequestBuilder post = post("/reset_password.do") - .param("code", code.getCode()) - .param("email", users.get(0).getPrimaryEmail()) - .param("password", "newpassword") - .param("password_confirmation", "newpassword"); - - MvcResult mvcResult = mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("home")) - .andReturn(); - - SecurityContext securityContext = (SecurityContext) mvcResult.getRequest().getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - Authentication authentication = securityContext.getAuthentication(); - assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); - UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); - assertThat(principal.getId(), equalTo(users.get(0).getId())); - assertThat(principal.getName(), equalTo(users.get(0).getUserName())); - assertThat(principal.getEmail(), equalTo(users.get(0).getPrimaryEmail())); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); - } -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java new file mode 100644 index 00000000000..3a7e7f2097a --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.cloudfoundry.identity.uaa.login; + +import com.googlecode.flyway.core.Flyway; +import org.cloudfoundry.identity.uaa.TestClassNullifier; +import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; +import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints; +import org.cloudfoundry.identity.uaa.test.YamlServletProfileInitializerContextInitializer; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.support.XmlWebApplicationContext; + +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.List; + +import static org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints.PASSWORD_RESET_LIFETIME; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ResetPasswordControllerMockMvcTests extends TestClassNullifier { + + static XmlWebApplicationContext webApplicationContext; + + private static MockMvc mockMvc; + private static ExpiringCodeStore codeStore; + + @BeforeClass + public static void initResetPasswordTest() throws Exception { + webApplicationContext = new XmlWebApplicationContext(); + new YamlServletProfileInitializerContextInitializer().initializeContext(webApplicationContext, "login.yml,uaa.yml"); + webApplicationContext.setConfigLocation("file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + webApplicationContext.refresh(); + FilterChainProxy springSecurityFilterChain = webApplicationContext.getBean("springSecurityFilterChain", FilterChainProxy.class); + + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .addFilter(springSecurityFilterChain) + .build(); + codeStore = webApplicationContext.getBean(ExpiringCodeStore.class); + } + + @AfterClass + public static void cleanUpAfterPasswordReset() throws Exception { + Flyway flyway = webApplicationContext.getBean(Flyway.class); + flyway.clean(); + webApplicationContext.destroy(); + } + + + @Test + public void testResettingAPasswordUsingUsernameToEnsureNoModification() throws Exception { + + List users = webApplicationContext.getBean(ScimUserProvisioning.class).query("username eq \"marissa\""); + assertNotNull(users); + assertEquals(1, users.size()); + PasswordResetEndpoints.PasswordChange change = new PasswordResetEndpoints.PasswordChange(); + change.setUserId(users.get(0).getId()); + change.setUsername(users.get(0).getUserName()); + + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis()+ PASSWORD_RESET_LIFETIME)); + + MockHttpServletRequestBuilder post = post("/reset_password.do") + .param("code", code.getCode()) + .param("email", users.get(0).getPrimaryEmail()) + .param("password", "newpassword") + .param("password_confirmation", "newpassword"); + + MvcResult mvcResult = mockMvc.perform(post) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("home")) + .andReturn(); + + SecurityContext securityContext = (SecurityContext) mvcResult.getRequest().getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + Authentication authentication = securityContext.getAuthentication(); + assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); + UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); + assertThat(principal.getId(), equalTo(users.get(0).getId())); + assertThat(principal.getName(), equalTo(users.get(0).getUserName())); + assertThat(principal.getEmail(), equalTo(users.get(0).getPrimaryEmail())); + assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + } + + @Test + public void testResettingAPasswordFailsWhenUsernameChanged() throws Exception { + + ScimUserProvisioning userProvisioning = webApplicationContext.getBean(ScimUserProvisioning.class); + List users = userProvisioning.query("username eq \"marissa\""); + assertNotNull(users); + assertEquals(1, users.size()); + ScimUser user = users.get(0); + PasswordResetEndpoints.PasswordChange change = new PasswordResetEndpoints.PasswordChange(); + change.setUserId(user.getId()); + change.setUsername(user.getUserName()); + + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis()+50000)); + + String formerUsername = user.getUserName(); + user.setUserName("newusername"); + user = userProvisioning.update(user.getId(), user); + try { + MockHttpServletRequestBuilder post = post("/reset_password.do") + .param("code", code.getCode()) + .param("email", user.getPrimaryEmail()) + .param("password", "newpassword") + .param("password_confirmation", "newpassword"); + + mockMvc.perform(post) + .andExpect(status().isUnprocessableEntity()); + } finally { + user.setUserName(formerUsername); + userProvisioning.update(user.getId(), user); + } + } + + @Test + public void testResettingAPasswordUsingTimestampForUserModification() throws Exception { + List users = webApplicationContext.getBean(ScimUserProvisioning.class).query("username eq \"marissa\""); + assertNotNull(users); + assertEquals(1, users.size()); + ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis()+ PASSWORD_RESET_LIFETIME)); + + MockHttpServletRequestBuilder post = post("/reset_password.do") + .param("code", code.getCode()) + .param("email", users.get(0).getPrimaryEmail()) + .param("password", "newpassword") + .param("password_confirmation", "newpassword"); + + MvcResult mvcResult = mockMvc.perform(post) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("home")) + .andReturn(); + + SecurityContext securityContext = (SecurityContext) mvcResult.getRequest().getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + Authentication authentication = securityContext.getAuthentication(); + assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); + UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); + assertThat(principal.getId(), equalTo(users.get(0).getId())); + assertThat(principal.getName(), equalTo(users.get(0).getUserName())); + assertThat(principal.getEmail(), equalTo(users.get(0).getPrimaryEmail())); + assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + } + + @Test + public void testResettingAPasswordUsingTimestampUserModified() throws Exception { + ScimUserProvisioning userProvisioning = webApplicationContext.getBean(ScimUserProvisioning.class); + List users = userProvisioning.query("username eq \"marissa\""); + assertNotNull(users); + assertEquals(1, users.size()); + ScimUser user = users.get(0); + ExpiringCode code = codeStore.generateCode(user.getId(), new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME)); + + MockHttpServletRequestBuilder post = post("/reset_password.do") + .param("code", code.getCode()) + .param("email", user.getPrimaryEmail()) + .param("password", "newpassword") + .param("password_confirmation", "newpassword"); + + if (Arrays.asList(webApplicationContext.getEnvironment().getActiveProfiles()).contains("mysql")) { + Thread.sleep(1050); + } else { + Thread.sleep(50); + } + + userProvisioning.update(user.getId(), user); + + mockMvc.perform(post) + .andExpect(status().isUnprocessableEntity()); + + + } +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 3b0edb72d38..1809ba0588d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -25,7 +25,9 @@ import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.test.YamlServletProfileInitializerContextInitializer; import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -48,6 +50,7 @@ import javax.servlet.http.HttpServletResponse; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Map; @@ -1006,6 +1009,46 @@ public void testCreateAsWritePermissions() throws Exception { result.andExpect(status().isCreated()); } + @Test + public void testGetClientDetailsSortedByLastModified() throws Exception{ + + ClientDetails adminsClient = createReadWriteClient(adminToken); + + String token = testClient.getClientCredentialsOAuthAccessToken( + + adminsClient.getClientId(), + "secret", + "clients.read"); + + MockHttpServletRequestBuilder get = get("/oauth/clients") + .header("Authorization", "Bearer " + token) + .param("sortBy", "lastModified") + .param("sortOrder", "descending") + .accept(APPLICATION_JSON); + + MvcResult result = mockMvc.perform(get).andExpect(status().isOk()).andReturn(); + String body = result.getResponse().getContentAsString(); + + Collection clientDetails = JsonUtils.> readValue(body, new TypeReference>() { + }).getResources(); + + assertNotNull(clientDetails); + + Date lastDate = null; + + for(ClientDetails clientDetail : clientDetails){ + assertTrue(clientDetail.getAdditionalInformation().containsKey("lastModified")); + + Date currentDate = JsonUtils.convertValue(clientDetail.getAdditionalInformation().get("lastModified"), Date.class); + + if(lastDate != null){ + assertTrue(currentDate.getTime() <= lastDate.getTime()); + } + + lastDate = currentDate; + } + } + private Approval[] getApprovals(String token, String clientId) throws Exception { String filter = "client_id eq \""+clientId+"\""; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index e283ed2fa93..b18e3228f38 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -188,7 +188,7 @@ private IdentityProvider setupIdentityProvider(String origin) { } protected void setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove) { - setUpClients(id,authorities,scopes,grantTypes,autoapprove,null); + setUpClients(id, authorities, scopes, grantTypes, autoapprove, null); } protected void setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri) { setUpClients(id, authorities, scopes, grantTypes, autoapprove, redirectUri, null); @@ -438,8 +438,8 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenient() throws Exc SecurityContextHolder.getContext().setAuthentication(auth); MockHttpSession session = new MockHttpSession(); session.setAttribute( - HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, - new MockSecurityContext(auth) + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockSecurityContext(auth) ); String state = new RandomValueStringGenerator().generate(); @@ -621,6 +621,54 @@ public void testAuthorizationCodeGrantWithEncodedRedirectURL() throws Exception assertEquals(redirectUri, location); } + @Test + public void testImplicitGrantWithFragmentInRedirectURL() throws Exception { + String redirectUri = "https://example.com/dashboard/?appGuid=app-guid#test"; + testImplicitGrantRedirectUri(redirectUri, "&"); + } + + @Test + public void testImplicitGrantWithNoFragmentInRedirectURL() throws Exception { + String redirectUri = "https://example.com/dashboard/?appGuid=app-guid"; + testImplicitGrantRedirectUri(redirectUri, "#"); + } + + protected void testImplicitGrantRedirectUri(String redirectUri, String delim) throws Exception { + String clientId = "authclient-"+new RandomValueStringGenerator().generate(); + String scopes = "openid"; + setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); + String username = "authuser"+new RandomValueStringGenerator().generate(); + String userScopes = "openid"; + ScimUser developer = setUpUser(username, userScopes); + String basicDigestHeaderValue = "Basic " + + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); + Assert.assertTrue(auth.isAuthenticated()); + + SecurityContextHolder.getContext().setAuthentication(auth); + MockHttpSession session = new MockHttpSession(); + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockSecurityContext(auth) + ); + + String state = new RandomValueStringGenerator().generate(); + MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") + .header("Authorization", basicDigestHeaderValue) + .session(session) + .param(OAuth2Utils.RESPONSE_TYPE, "token") + .param(OAuth2Utils.SCOPE, "openid") + .param(OAuth2Utils.STATE, state) + .param(OAuth2Utils.CLIENT_ID, clientId) + .param(OAuth2Utils.REDIRECT_URI, redirectUri); + + MvcResult result = mockMvc.perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); + String location = result.getResponse().getHeader("Location"); + assertTrue(location.startsWith(redirectUri + delim + "token_type=bearer&access_token")); + } + + @Test public void testOpenIdToken() throws Exception { String clientId = "testclient"+new RandomValueStringGenerator().generate(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index e2c3d975336..e89559c904d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -55,6 +55,7 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -152,6 +153,15 @@ public void testCreateSamlProvider() throws Exception { @Test public void testEnsureWeRetrieveInactiveIDPsToo() throws Exception { + testRetrieveIdps(false); + } + + @Test + public void testRetrieveOnlyActiveIdps() throws Exception { + testRetrieveIdps(true); + } + + private void testRetrieveIdps(boolean retrieveActive) throws Exception { String clientId = RandomStringUtils.randomAlphabetic(6); BaseClientDetails client = new BaseClientDetails(clientId,null,"idps.write,idps.read","password",null); client.setClientSecret("test-client-secret"); @@ -163,11 +173,12 @@ public void testEnsureWeRetrieveInactiveIDPsToo() throws Exception { IdentityProvider identityProvider = MultitenancyFixture.identityProvider(randomOriginKey, IdentityZone.getUaa().getId()); IdentityProvider createdIDP = createIdentityProvider(null, identityProvider, accessToken, status().isCreated()); - MockHttpServletRequestBuilder requestBuilder = get("/identity-providers") - .header("Authorization", "Bearer" + accessToken) - .contentType(APPLICATION_JSON); + String retrieveActiveParam = retrieveActive ? "?active_only=true" : ""; + MockHttpServletRequestBuilder requestBuilder = get("/identity-providers" + retrieveActiveParam) + .header("Authorization", "Bearer" + accessToken) + .contentType(APPLICATION_JSON); - int numberOfIdps = identityProviderProvisioning.retrieveAll(false, IdentityZone.getUaa().getId()).size(); + int numberOfIdps = identityProviderProvisioning.retrieveAll(retrieveActive, IdentityZone.getUaa().getId()).size(); MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() {}); @@ -180,8 +191,13 @@ public void testEnsureWeRetrieveInactiveIDPsToo() throws Exception { result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { }); - assertEquals(numberOfIdps, identityProviderList.size()); - assertTrue(identityProviderList.contains(createdIDP)); + if (!retrieveActive) { + assertEquals(numberOfIdps, identityProviderList.size()); + assertTrue(identityProviderList.contains(createdIDP)); + } else { + assertEquals(numberOfIdps - 1, identityProviderList.size()); + assertFalse(identityProviderList.contains(createdIDP)); + } } private void createAndUpdateIdentityProvider(String accessToken, String zoneId) throws Exception {