From 5b5f94718e40d0e6b25b229d7095359aca790f8f Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 4 Feb 2016 17:12:56 -0700 Subject: [PATCH 01/87] Make sure that SAML SP metadata is generated for each request. https://www.pivotaltracker.com/story/show/113009497 [#113009497] --- .../saml/ZoneAwareMetadataDisplayFilter.java | 54 +++++++++++++++++++ .../webapp/WEB-INF/spring/saml-providers.xml | 4 +- .../saml/SamlIDPRefreshMockMvcTests.java | 14 +++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java new file mode 100644 index 00000000000..9c5386f48f6 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml; + +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.xml.io.MarshallingException; +import org.springframework.security.saml.metadata.MetadataDisplayFilter; +import org.springframework.security.saml.metadata.MetadataGenerator; + +import javax.servlet.ServletException; +import java.io.PrintWriter; + +public class ZoneAwareMetadataDisplayFilter extends MetadataDisplayFilter { + + protected final MetadataGenerator generator; + + public ZoneAwareMetadataDisplayFilter(MetadataGenerator generator) { + this.generator = generator; + } + + public MetadataGenerator getGenerator() { + return generator; + } + + @Override + protected void displayMetadata(String spEntityName, PrintWriter writer) throws ServletException { + try { + EntityDescriptor descriptor = getGenerator().generateMetadata(); + if (descriptor == null) { + throw new ServletException("Metadata entity with ID " + manager.getHostedSPName() + " wasn't found"); + } else { + writer.print(getMetadataAsString(descriptor)); + } + } catch (MarshallingException e) { + log.error("Error marshalling entity descriptor", e); + throw new ServletException(e); + } catch (Exception e) { + log.error("Error retrieving metadata", e); + throw new ServletException("Error retrieving metadata", e); + } + } +} diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index f162ab0eebc..d7f5dae7462 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -149,7 +149,9 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java index 1faf68d7dcb..35a4a5c741c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java @@ -414,6 +414,20 @@ public void metadataInZoneGeneratesCorrectId() throws Exception { .andExpect(content().string(containsString("ID=\"zone2.cloudfoundry-saml-login\" entityID=\"zone2.cloudfoundry-saml-login\""))) .andExpect(content().string(containsString(""))); + config2.getSamlConfig().setRequestSigned(true); + config2.getSamlConfig().setWantAssertionSigned(true); + zone2.setConfig(config2); + zone2 = zoneProvisioning.update(zone2); + assertTrue(zone2.getConfig().getSamlConfig().isRequestSigned()); + assertTrue(zone2.getConfig().getSamlConfig().isWantAssertionSigned()); + + getMockMvc().perform( + get("/saml/metadata") + .with(new SetServerNameRequestPostProcessor(zone2.getSubdomain() + ".localhost"))) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("ID=\"zone2.cloudfoundry-saml-login\" entityID=\"zone2.cloudfoundry-saml-login\""))) + .andExpect(content().string(containsString(""))); + } @Test From a159be105bc15357ef281a6ba0b29d2100ceca06 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 4 Feb 2016 15:18:37 -0800 Subject: [PATCH 02/87] Deleting a zone should create an audit log [#109812284] https://www.pivotaltracker.com/story/show/109812284 Signed-off-by: Jeremy Coffield Signed-off-by: Madhura Bhave --- .../identity/uaa/audit/AuditEventType.java | 3 +- .../uaa/audit/event/EntityDeletedEvent.java | 17 +++++++---- .../provider/IdentityProviderEndpoints.java | 3 +- .../scim/endpoints/ScimGroupEndpoints.java | 4 +-- .../uaa/zone/IdentityZoneEndpoints.java | 3 +- .../uaa/audit/AuditEventTypeTests.java | 4 +-- .../JdbcScimGroupMembershipManagerTests.java | 8 ++--- .../jdbc/JdbcScimUserProvisioningTests.java | 10 +++---- ...JdbcIdentityProviderProvisioningTests.java | 6 ++-- .../JdbcIdentityZoneProvisioningTests.java | 4 +-- ...titenantJdbcClientDetailsServiceTests.java | 4 +-- .../IdentityZoneEndpointsMockMvcTests.java | 29 +++++++++++++++++-- .../saml/SamlIDPRefreshMockMvcTests.java | 3 +- 13 files changed, 67 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java index bf8d1c5ccd2..e87132f9e0c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java @@ -52,7 +52,8 @@ public enum AuditEventType { IdentityProviderCreatedEvent(28), IdentityProviderModifiedEvent(29), IdentityZoneCreatedEvent(30), - IdentityZoneModifiedEvent(31); + IdentityZoneModifiedEvent(31), + EntityDeletedEvent(32); private final int code; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java index 9b937e9fb5f..34e686b4a36 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java @@ -15,18 +15,25 @@ package org.cloudfoundry.identity.uaa.audit.event; -import org.springframework.context.ApplicationEvent; +import org.cloudfoundry.identity.uaa.audit.AuditEvent; +import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.springframework.security.core.Authentication; -public class EntityDeletedEvent extends ApplicationEvent { +public class EntityDeletedEvent extends AbstractUaaEvent { - private final T deleted; + private final T deleted; - public EntityDeletedEvent(T deleted) { - super(deleted); + public EntityDeletedEvent(T deleted, Authentication authentication) { + super(deleted, authentication); this.deleted = deleted; } public T getDeleted() { return deleted; } + + @Override + public AuditEvent getAuditEvent() { + return createAuditRecord(getAuthentication().getName(), AuditEventType.EntityDeletedEvent, getOrigin(getAuthentication()), source.toString()); + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java index 43ad213a76e..b8072c07941 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java @@ -34,6 +34,7 @@ import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; @@ -115,7 +116,7 @@ public ResponseEntity createIdentityProvider(@RequestBody Iden public ResponseEntity deleteIdentityProvider(@PathVariable String id) throws MetadataProviderException { IdentityProvider existing = identityProviderProvisioning.retrieve(id); if (publisher!=null && existing!=null) { - publisher.publishEvent(new EntityDeletedEvent<>(existing)); + publisher.publishEvent(new EntityDeletedEvent<>(existing, SecurityContextHolder.getContext().getAuthentication())); return new ResponseEntity<>(existing, OK); } else { return new ResponseEntity<>(UNPROCESSABLE_ENTITY); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index 55e6bc53b5e..2601a1f1d4e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -380,7 +380,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String } /* - * SCIM spec lists the PATCH operaton as optional, so leaving it + * SCIM spec lists the PATCH operation as optional, so leaving it * un-implemented for now while we wait for * https://jira.springsource.org/browse/SPR-7985 which adds support for * RequestMethod.PATCH in version '3.2 M2' @@ -389,7 +389,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String * @RequestMapping(value = { "/Group/{groupId}", "/Groups/{groupId}" }, * method = RequestMethod.PATCH) * @ResponseBody - * public ScimGroup updateGroup(@RequeudstBody ScimGroup group, @PathVariable + * public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable * String groupId, * @RequestHeader(value = "If-Match", required = false) String etag) { * } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index 0cf8bd8c473..a526d29c38f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -28,6 +28,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.NoSuchClientException; @@ -203,7 +204,7 @@ public ResponseEntity deleteIdentityZone(@PathVariable String id) // ignore the id in the body, the id in the path is the only one that matters IdentityZoneHolder.set(zone); if (publisher!=null && zone!=null) { - publisher.publishEvent(new EntityDeletedEvent<>(zone)); + publisher.publishEvent(new EntityDeletedEvent<>(zone, SecurityContextHolder.getContext().getAuthentication())); logger.debug("Zone - deleted id[" + zone.getId() + "]"); return new ResponseEntity<>(zone, OK); } else { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java index 4f518d36dae..092a1cfad05 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java @@ -14,6 +14,6 @@ public void testAuditEventType() { assertEquals(type, AuditEventType.fromCode(count)); count++; } - assertEquals(32,count); + assertEquals(33,count); } -} \ No newline at end of file +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index f401cddb447..767c356aa26 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -265,7 +265,7 @@ public void test_zone_deleted() { assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", new Object[]{IdentityZoneHolder.get().getId()}, Integer.class), is(3)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=? and displayName like ?)", new Object[]{IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"}, Integer.class), is(1)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=? and displayName like ?", new Object[]{IdentityZone.getUaa().getId(), "zones." + IdentityZoneHolder.get().getId() + ".%"}, Integer.class), is(1)); - gdao.onApplicationEvent(new EntityDeletedEvent<>(zone)); + gdao.onApplicationEvent(new EntityDeletedEvent<>(zone, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[]{IdentityZoneHolder.get().getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[]{IdentityZoneHolder.get().getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", new Object[]{IdentityZoneHolder.get().getId()}, Integer.class), is(0)); @@ -285,7 +285,7 @@ public void test_provider_deleted() { new IdentityProvider() .setOriginKey(LOGIN_SERVER) .setIdentityZoneId(zone.getId()); - gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)", new Object[] {LOGIN_SERVER, IdentityZoneHolder.get().getId()}, Integer.class), is(0)); @@ -296,7 +296,7 @@ public void test_cannot_delete_uaa_zone() { addMembers(); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); - gdao.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + gdao.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa(), null)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); } @@ -311,7 +311,7 @@ public void test_cannot_delete_uaa_provider() { new IdentityProvider() .setOriginKey(UAA) .setIdentityZoneId(zone.getId()); - gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java index e041b4a512a..e9edeb298b0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java @@ -185,7 +185,7 @@ public void test_can_delete_provider_users_in_default_zone() throws Exception { new IdentityProvider() .setOriginKey(LOGIN_SERVER) .setIdentityZoneId(IdentityZone.getUaa().getId()); - db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {LOGIN_SERVER, IdentityZone.getUaa().getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); @@ -213,7 +213,7 @@ public void test_can_delete_provider_users_in_other_zone() throws Exception { new IdentityProvider() .setOriginKey(LOGIN_SERVER) .setIdentityZoneId(zone.getId()); - db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {LOGIN_SERVER, zone.getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); @@ -237,7 +237,7 @@ public void test_can_delete_zone_users() throws Exception { assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(1)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(1)); - db.onApplicationEvent(new EntityDeletedEvent<>(zone)); + db.onApplicationEvent(new EntityDeletedEvent<>(zone, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); @@ -257,7 +257,7 @@ public void test_cannot_delete_uaa_zone_users() throws Exception { new IdentityProvider() .setOriginKey(UAA) .setIdentityZoneId(IdentityZone.getUaa().getId()); - db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, IdentityZone.getUaa().getId()}, Integer.class), is(3)); } @@ -279,7 +279,7 @@ public void test_cannot_delete_uaa_provider_users_in_other_zone() throws Excepti new IdentityProvider() .setOriginKey(UAA) .setIdentityZoneId(zone.getId()); - db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(1)); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index 66d708c1311..2ab4f4e5ef0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -56,7 +56,7 @@ public void test_delete_providers_in_zone() { IdentityProvider createdIdp = db.create(idp); assertNotNull(createdIdp); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); - db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get(), null)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); } @@ -68,7 +68,7 @@ public void test_delete_providers_in_uaa_zone() { IdentityProvider createdIdp = db.create(idp); assertNotNull(createdIdp); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(5)); - db.onApplicationEvent(new EntityDeletedEvent<>(createdIdp)); + db.onApplicationEvent(new EntityDeletedEvent<>(createdIdp, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); } @@ -78,7 +78,7 @@ public void test_cannot_delete_uaa_providers() { //should not do anything assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); IdentityProvider uaa = db.retrieveByOrigin(UAA, IdentityZoneHolder.get().getId()); - db.onApplicationEvent(new EntityDeletedEvent<>(uaa)); + db.onApplicationEvent(new EntityDeletedEvent<>(uaa, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java index 9484594ae83..87a07dc3263 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java @@ -29,14 +29,14 @@ public void test_delete_zone() { IdentityZone createdIdZone = db.create(identityZone); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {createdIdZone.getId()}, Integer.class), is(1)); - db.onApplicationEvent(new EntityDeletedEvent<>(identityZone)); + db.onApplicationEvent(new EntityDeletedEvent<>(identityZone, null)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {createdIdZone.getId()}, Integer.class), is(0)); } @Test public void test_cannot_delete_uaa_zone() { assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {IdentityZone.getUaa().getId()}, Integer.class), is(1)); - db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa(), null)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {IdentityZone.getUaa().getId()}, Integer.class), is(1)); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java index 895de271d0e..746c4a249d7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java @@ -95,7 +95,7 @@ public void test_can_delete_zone_clients() throws Exception { addApproval(id); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(1)); - service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get(), null)); assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(0)); } @@ -112,7 +112,7 @@ public void test_cannot_delete_uaa_zone_clients() throws Exception { addApproval(id); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(1)); - service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get(), null)); assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is (1)); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 4484bc762a8..1a8c73d657a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.scim.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; @@ -62,6 +63,7 @@ import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -74,7 +76,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -93,6 +94,7 @@ public class IdentityZoneEndpointsMockMvcTests extends InjectedMockContextTest { private TestApplicationEventListener clientDeleteEventListener; private TestApplicationEventListener groupModifiedEventListener; private TestApplicationEventListener userModifiedEventListener; + private TestApplicationEventListener uaaEventListener; @Before public void setUp() throws Exception { @@ -102,7 +104,7 @@ public void setUp() throws Exception { clientDeleteEventListener = mockMvcUtils.addEventListener(getWebApplicationContext(), ClientDeleteEvent.class); groupModifiedEventListener = mockMvcUtils.addEventListener(getWebApplicationContext(), GroupModifiedEvent.class); userModifiedEventListener = mockMvcUtils.addEventListener(getWebApplicationContext(), UserModifiedEvent.class); - + uaaEventListener = mockMvcUtils.addEventListener(getWebApplicationContext(), AbstractUaaEvent.class); identityClientToken = testClient.getClientCredentialsOAuthAccessToken( "identity", @@ -608,6 +610,29 @@ public void test_delete_zone_cleans_db() throws Exception { } + @Test + public void testDeleteZonePublishesEvent() throws Exception { + String id = generator.generate(); + IdentityZone zone = createZone(id, HttpStatus.CREATED, identityClientToken); + + uaaEventListener.clearEvents(); + + getMockMvc().perform( + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()); + + assertThat(uaaEventListener.getEventCount(), is(1)); + AbstractUaaEvent event = uaaEventListener.getLatestEvent(); + assertThat(event, instanceOf(EntityDeletedEvent.class)); + assertThat(((EntityDeletedEvent) event).getDeleted(), instanceOf(IdentityZone.class)); + + IdentityZone deletedZone = (IdentityZone) ((EntityDeletedEvent) event).getDeleted(); + assertThat(deletedZone.getId(), is(id)); + assertThat(event.getIdentityZone().getId(), is(id)); + } + @Test public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws Exception { String id = generator.generate(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java index 1faf68d7dcb..995f7c50d2c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java @@ -19,6 +19,7 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.test.MockAuthentication; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -176,7 +177,7 @@ public void testThatDBDeletedXMLProviderDoesNotShowOnLoginPage() throws Exceptio IdentityProvider provider = addXmlProviderToDatabase(); SamlIdentityProviderDefinition definition = provider.getConfig(); //delete from DB - EntityDeletedEvent event = new EntityDeletedEvent(provider); + EntityDeletedEvent event = new EntityDeletedEvent(provider, new MockAuthentication()); getWebApplicationContext().publishEvent(event); //verify that provider is deleted assertThat(getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select count(*) from identity_provider where id=?", new Object[] {provider.getId()}, Integer.class), is(0)); From f13008cd582300cd01df7df474c1d5b3ab96086a Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 5 Feb 2016 07:27:16 -0700 Subject: [PATCH 03/87] Bump next prerelease/3.1.0 version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5b2a680f918..d72697bd6c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.1.0 +version=3.1.1-SNAPSHOT From 60474fff623b1c7c26164ddbb9c8c8eabaaf4d12 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 5 Feb 2016 14:31:17 -0800 Subject: [PATCH 04/87] Refer to Json web key spec in documentation [finishes #111187566] https://www.pivotaltracker.com/story/show/111187566 Signed-off-by: Jonathan Lo --- docs/UAA-APIs.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index da292bfe49c..24255e59c30 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -2483,8 +2483,7 @@ OAuth2 protected resources which deal with listing and revoking access tokens. Get the Token Signing Key: ``GET /token_key`` --------------------------------------------- -An endpoint which returns the JWT token key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. -Key is in JSON Web Key format, for RSA public keys, the values n, modulues, and e, exponent, are available. +An endpoint which returns the JWT token key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. The key is in JSON Web Key format. For complete information about JSON Web Keys, see [RFC 7517](https://tools.ietf.org/html/rfc7517). In the case when the token key is symmetric, signer key and verifier key are the same, then this call is authenticated with client credentials using the HTTP Basic method. ================ ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= From 8ee55facbda17a7ffb5ad5bd3d6632384d4cec87 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 5 Feb 2016 15:47:26 -0800 Subject: [PATCH 05/87] Git markdown doesn't support standard markdown links. [finishes #111187566] https://www.pivotaltracker.com/story/show/111187566 --- docs/UAA-APIs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 24255e59c30..b591021da6f 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -2483,7 +2483,7 @@ OAuth2 protected resources which deal with listing and revoking access tokens. Get the Token Signing Key: ``GET /token_key`` --------------------------------------------- -An endpoint which returns the JWT token key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. The key is in JSON Web Key format. For complete information about JSON Web Keys, see [RFC 7517](https://tools.ietf.org/html/rfc7517). +An endpoint which returns the JWT token key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. The key is in JSON Web Key format. For complete information about JSON Web Keys, see RFC 7517 (https://tools.ietf.org/html/rfc7517). In the case when the token key is symmetric, signer key and verifier key are the same, then this call is authenticated with client credentials using the HTTP Basic method. ================ ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= From 2f22c588ae144b96b69e5302ce90d2149d4152bc Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 5 Feb 2016 15:23:46 -0800 Subject: [PATCH 06/87] Use "off" instead of "false" to disable password autocomplete [#112370175] https://www.pivotaltracker.com/story/show/112370175 Signed-off-by: Jeremy Coffield --- server/src/main/resources/templates/web/login.html | 2 +- .../identity/uaa/integration/feature/LoginIT.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/main/resources/templates/web/login.html b/server/src/main/resources/templates/web/login.html index 31e989b39f9..1fd755fdba1 100644 --- a/server/src/main/resources/templates/web/login.html +++ b/server/src/main/resources/templates/web/login.html @@ -12,7 +12,7 @@

diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java index 1b02765684e..8751247da37 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java @@ -24,6 +24,7 @@ import org.junit.runner.RunWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; @@ -91,6 +92,13 @@ public void testSuccessfulLogin() throws Exception { assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); } + @Test + public void testAutocompleteIsDisabledForPasswordField() { + webDriver.get(baseUrl + "/login"); + WebElement password = webDriver.findElement(By.name("password")); + assertEquals("off", password.getAttribute("autocomplete")); + } + @Test public void testPasscodeRedirect() throws Exception { webDriver.get(baseUrl + "/passcode"); From fb4cc74d3ff55eaf2765d22bb7ad313411739694 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 08:27:21 -0700 Subject: [PATCH 07/87] Enhance requirements for auth code grant --- .../identity/client/UaaContextFactory.java | 13 ++-- .../identity/client/token/TokenRequest.java | 31 +++++++- .../ClientAPITokenIntegrationTest.java | 25 +++++-- .../integration/IsUAAListeningRule.java | 72 +++++++++++++++++++ .../client/token/TokenRequestTest.java | 2 + 5 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IsUAAListeningRule.java diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index d320037d535..f9a239ffdd0 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -33,7 +33,6 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpMessageConverterExtractor; import org.springframework.web.client.ResponseExtractor; @@ -146,10 +145,15 @@ public UaaContext authenticate(TokenRequest request) { protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUriRedirectUri().toString()); + details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString()); configureResourceDetails(tokenRequest, details); setClientCredentials(tokenRequest, details); setRequestScopes(tokenRequest, details); - OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); + DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(); + oAuth2ClientContext.getAccessTokenRequest().setStateKey(tokenRequest.getState()); + oAuth2ClientContext.setPreservedState(tokenRequest.getState(), details.getPreEstablishedRedirectUri()); + oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri()); + OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext); template.getAccessToken(); throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented"); } @@ -176,9 +180,8 @@ protected ResponseExtractor getResponseExtractor() { setRequestScopes(tokenRequest, details); details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString()); DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(); - String state = new RandomValueStringGenerator().generate(); - oAuth2ClientContext.getAccessTokenRequest().setStateKey(state); - oAuth2ClientContext.setPreservedState(state, details.getPreEstablishedRedirectUri()); + oAuth2ClientContext.getAccessTokenRequest().setStateKey(tokenRequest.getState()); + oAuth2ClientContext.setPreservedState(tokenRequest.getState(), details.getPreEstablishedRedirectUri()); oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri()); Map> headers = (Map>) oAuth2ClientContext.getAccessTokenRequest().getHeaders(); headers.put("Authorization", Arrays.asList("bearer " + tokenRequest.getAuthCodeAPIToken())); diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java index 3c0a3d1f9c9..9989e39f049 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -42,6 +42,7 @@ public class TokenRequest { private boolean idToken = false; private URI redirectUri; private String authCodeAPIToken; + private String state; /** * Constructs a token request @@ -99,7 +100,8 @@ public boolean isValid() { clientSecret, username, password, - redirectUri + redirectUri, + state ) ); case AUTHORIZATION_CODE_WITH_TOKEN: @@ -112,7 +114,8 @@ public boolean isValid() { username, password, redirectUri, - authCodeAPIToken + authCodeAPIToken, + state ) ); default: return false; @@ -338,6 +341,30 @@ public TokenRequest setPasscode(String passcode) { return this; } + /** + * Returns the state key, used with + * {@link GrantType#AUTHORIZATION_CODE} and + * {@link GrantType#IMPLICIT} and + * {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} + * @return String representing a random string + */ + public String getState() { + return state; + } + + /** + * Sets the state key, used with + * {@link GrantType#AUTHORIZATION_CODE} and + * {@link GrantType#IMPLICIT} and + * {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} + * @param state - a random string + * @return this mutable object + */ + public TokenRequest setState(String state) { + this.state = state; + return this; + } + /** * Returns true if the list or any item in the list is null * @param objects a list of items to be evaluated for null references diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index a855abf20e2..eb43210d407 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -21,11 +21,14 @@ import org.cloudfoundry.identity.client.token.TokenRequest; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.net.URI; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; @@ -41,6 +44,11 @@ public class ClientAPITokenIntegrationTest { private UaaContextFactory factory; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + + @Rule + public IsUAAListeningRule uaaListeningRule = new IsUAAListeningRule(uaaURI, false); + @Before public void setUp() throws Exception { factory = @@ -146,14 +154,16 @@ public void test_password_token_with_passcode() throws Exception { @Test - @Ignore //until we have decided if we want to be able to do this without a UI + @Ignore("test_auth_code_token_with_id_token ignored - No UI/browser implementation yet") //until we have decided if we want to be able to do this without a UI public void test_auth_code_token_with_id_token() throws Exception { TokenRequest authorizationCode = factory.tokenRequest() .withIdToken() .setGrantType(AUTHORIZATION_CODE) .setRedirectUri(new URI("http://localhost/redirect")) - .setClientId("cf") - .setClientSecret("") + .setState(generator.generate()) + .setScopes(Collections.singleton("openid")) + .setClientId("app") + .setClientSecret("appclientsecret") .setUsername("marissa") .setPassword("koala"); UaaContext context = factory.authenticate(authorizationCode); @@ -165,13 +175,15 @@ public void test_auth_code_token_with_id_token() throws Exception { } @Test - @Ignore //until we have decided if we want to be able to do this without a UI + @Ignore("test_auth_code_token_without_id_token ignred - No UI/browser implementation yet") //until we have decided if we want to be able to do this without a UI public void test_auth_code_token_without_id_token() throws Exception { TokenRequest authorizationCode = factory.tokenRequest() .setGrantType(AUTHORIZATION_CODE) .setRedirectUri(new URI("http://localhost/redirect")) - .setClientId("cf") - .setClientSecret("") + .setState(generator.generate()) + .setScopes(Collections.singleton("openid")) + .setClientId("app") + .setClientSecret("appclientsecret") .setUsername("marissa") .setPassword("koala"); UaaContext context = factory.authenticate(authorizationCode); @@ -189,6 +201,7 @@ public void test_auth_code_token_using_api() throws Exception { TokenRequest authorizationCode = factory.tokenRequest() .setGrantType(AUTHORIZATION_CODE_WITH_TOKEN) .setRedirectUri(new URI("http://localhost:8080/app/")) + .setState(generator.generate()) .setClientId("app") .setClientSecret("appclientsecret") .setUsername("marissa") diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IsUAAListeningRule.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IsUAAListeningRule.java new file mode 100644 index 00000000000..bcb3fd743d2 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IsUAAListeningRule.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.client.integration; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Assume; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; + +public class IsUAAListeningRule implements TestRule { + private static Log logger = LogFactory.getLog(IsUAAListeningRule.class); + + private static Map sharedStatuses = new HashMap<>(); + + private final String baseUrl; + private final boolean forceIntegrationTests; + + public IsUAAListeningRule(String baseUrl, boolean forceIntegrationTests) { + this.baseUrl = baseUrl; + this.forceIntegrationTests = forceIntegrationTests; + } + + @Override + public Statement apply(Statement statement, Description description) { + Assume.assumeTrue("Test ignored as the server cannot be reached at " + baseUrl, forceIntegrationTests || getStatus()); + return statement; + } + + private synchronized Boolean getStatus() { + Boolean available = sharedStatuses.get(baseUrl); + if (available == null) { + available = connectionAvailable(); + sharedStatuses.put(baseUrl, available); + } + return available; + } + + private boolean connectionAvailable() { + UriComponents components = UriComponentsBuilder.fromHttpUrl(baseUrl).build(); + String host = components.getHost(); + int port = components.getPort(); + + logger.info("Testing connectivity for " + baseUrl); + try (Socket socket = new Socket(host, port)) { + logger.info("Connectivity test succeeded for " + baseUrl); + return true; + + } catch (IOException e) { + logger.warn("Connectivity test failed for " + baseUrl, e); + return false; + } + } +} diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java index d1cfdec0089..7a0fbd2cc23 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -77,6 +77,7 @@ public void test_is_auth_code_grant_valid() throws Exception { assertFalse(request.setClientSecret("client_secret").isValid()); assertFalse(request.setUsername("username").isValid()); assertFalse(request.setPassword("password").isValid()); + assertFalse(request.setState("state").isValid()); assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid()); } @@ -89,6 +90,7 @@ public void test_is_auth_code_grant_api_valid() throws Exception { assertFalse(request.setUsername("username").isValid()); assertFalse(request.setPassword("password").isValid()); assertFalse(request.setAuthCodeAPIToken("some token").isValid()); + assertFalse(request.setState("state").isValid()); assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid()); } From 69e2fb1bd908db197fa7c4102d0cacfd900b8369 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 08:45:45 -0700 Subject: [PATCH 08/87] Add helpful notes --- .../org/cloudfoundry/identity/client/UaaContextFactory.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index f9a239ffdd0..043e4a87f57 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -149,10 +149,14 @@ protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { configureResourceDetails(tokenRequest, details); setClientCredentials(tokenRequest, details); setRequestScopes(tokenRequest, details); + + //begin - work around for not having UI for now DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(); oAuth2ClientContext.getAccessTokenRequest().setStateKey(tokenRequest.getState()); oAuth2ClientContext.setPreservedState(tokenRequest.getState(), details.getPreEstablishedRedirectUri()); oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri()); + //end - work around for not having UI for now + OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext); template.getAccessToken(); throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented"); From 330a7998e2216f8eb00b2fac045b7c50d8056a25 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 11:13:18 -0700 Subject: [PATCH 09/87] Move properties into Yaml instead of setting system properties --- .../identity/uaa/login/BootstrapTests.java | 87 +------------------ uaa/src/test/resources/test/hostnames/uaa.yml | 63 +++++++++++++- 2 files changed, 66 insertions(+), 84 deletions(-) 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 36ee08b94f8..00c8dabddec 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 @@ -249,54 +249,10 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { try { String uaa = "uaa.some.test.domain.com"; String login = uaa.replace("uaa", "login"); - System.setProperty("login.prompt.username.text","Username"); - System.setProperty("login.prompt.password.text","Your Secret"); - - System.setProperty("smtp.host", ""); - System.setProperty("uaa.url", "https://" + uaa + ":555/uaa"); - System.setProperty("login.url", "https://" + login + ":555/uaa"); - System.setProperty("login.entityBaseURL", "https://" + login + ":555/uaa"); - System.setProperty("database.maxactive", "50"); - System.setProperty("database.maxidle", "5"); - System.setProperty("database.removeabandoned", "true"); - System.setProperty("database.logabandoned", "false"); - System.setProperty("database.abandonedtimeout", "45"); - System.setProperty("database.evictionintervalms", "30000"); - System.setProperty("database.caseinsensitive", "true"); - - System.setProperty("password.policy.minLength", "8"); - System.setProperty("password.policy.maxLength", "100"); - System.setProperty("password.policy.requireUpperCaseCharacter", "0"); - System.setProperty("password.policy.requireLowerCaseCharacter", "0"); - System.setProperty("password.policy.requireDigit", "0"); - System.setProperty("password.policy.requireSpecialCharacter", "1"); - System.setProperty("password.policy.expirePasswordInMonths", "6"); - - System.setProperty("password.policy.global.minLength", "8"); - System.setProperty("password.policy.global.maxLength", "100"); - System.setProperty("password.policy.global.requireUpperCaseCharacter", "0"); - System.setProperty("password.policy.global.requireLowerCaseCharacter", "0"); - System.setProperty("password.policy.global.requireDigit", "0"); - System.setProperty("password.policy.global.requireSpecialCharacter", "1"); - System.setProperty("password.policy.global.expirePasswordInMonths", "6"); - - System.setProperty("authentication.policy.lockoutAfterFailures", "10"); - System.setProperty("authentication.policy.countFailuresWithinSeconds", "7200"); - System.setProperty("authentication.policy.lockoutPeriodSeconds", "600"); - - System.setProperty("authentication.policy.global.lockoutAfterFailures", "1"); - System.setProperty("authentication.policy.global.countFailuresWithinSeconds", "2222"); - System.setProperty("authentication.policy.global.lockoutPeriodSeconds", "152"); - - System.setProperty("jwt.token.policy.global.accessTokenValiditySeconds", "3600"); - System.setProperty("jwt.token.policy.global.refreshTokenValiditySeconds", "7200"); - - System.setProperty("jwt.token.policy.accessTokenValiditySeconds", "4800"); - System.setProperty("jwt.token.policy.refreshTokenValiditySeconds", "9600"); context = getServletContext(null, "login.yml", "test/hostnames/uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); - IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); + IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); DataSource ds = context.getBean(DataSource.class); assertEquals(50, ds.getMaxActive()); @@ -360,45 +316,7 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { assertEquals("One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )", passcode.getDetails()[1]); } finally { - System.clearProperty("login.entityBaseURL"); - System.clearProperty("login.prompt.username.text"); - System.clearProperty("login.prompt.password.text"); - System.clearProperty("database.maxactive"); - System.clearProperty("database.maxidle"); - System.clearProperty("database.removeabandoned"); - System.clearProperty("database.logabandoned"); - System.clearProperty("database.abandonedtimeout"); - System.clearProperty("database.evictionintervalms"); - System.clearProperty("smtp.host"); - - System.clearProperty("password.policy.minLength"); - System.clearProperty("password.policy.maxLength"); - System.clearProperty("password.policy.requireUpperCaseCharacter"); - System.clearProperty("password.policy.requireLowerCaseCharacter"); - System.clearProperty("password.policy.requireDigit"); - System.clearProperty("password.policy.requireSpecialCharacter"); - System.clearProperty("password.policy.expirePasswordInMonths"); - - System.clearProperty("password.policy.global.minLength"); - System.clearProperty("password.policy.global.maxLength"); - System.clearProperty("password.policy.global.requireUpperCaseCharacter"); - System.clearProperty("password.policy.global.requireLowerCaseCharacter"); - System.clearProperty("password.policy.global.requireDigit"); - System.clearProperty("password.policy.global.requireSpecialCharacter"); - System.clearProperty("password.policy.global.expirePasswordInMonths"); - - System.clearProperty("authentication.policy.lockoutAfterFailures"); - System.clearProperty("authentication.policy.countFailuresWithinSeconds"); - System.clearProperty("authentication.policy.lockoutPeriodSeconds"); - - System.clearProperty("authentication.policy.global.lockoutAfterFailures"); - System.clearProperty("authentication.policy.global.countFailuresWithinSeconds"); - System.clearProperty("authentication.policy.global.lockoutPeriodSeconds"); - System.clearProperty("token.policy.global.accessTokenValiditySeconds"); - System.clearProperty("token.policy.global.refreshTokenValiditySeconds"); - System.clearProperty("token.policy.refreshTokenValiditySeconds"); - System.clearProperty("token.policy.refreshTokenValiditySeconds"); } } @@ -437,6 +355,9 @@ public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception { } finally { System.clearProperty("database.maxactive"); System.clearProperty("database.maxidle"); + System.clearProperty("smtp.host"); + System.clearProperty("uaa.url"); + System.clearProperty("login.url"); } } diff --git a/uaa/src/test/resources/test/hostnames/uaa.yml b/uaa/src/test/resources/test/hostnames/uaa.yml index 4c67fa5fb7f..8d7cbada96a 100644 --- a/uaa/src/test/resources/test/hostnames/uaa.yml +++ b/uaa/src/test/resources/test/hostnames/uaa.yml @@ -4,4 +4,65 @@ zones: - host1.domain.com - host2 - test3.localhost - - test4.localhost \ No newline at end of file + - test4.localhost + +login: + prompt: + username: + text: Username + password: + text: Your Secret + url: https://login.some.test.domain.com:555/uaa + entityBaseURL: https://login.some.test.domain.com:555/uaa + +smtp: + host: '' + +uaa: + url: https://uaa.some.test.domain.com:555/uaa + +database: + maxactive: 50 + maxidle: 5 + removeabandoned: true + logabandoned: false + abandonedtimeout: 45 + evictionintervalms: 30000 + caseinsensitive: true + +password: + policy: + minLength: 8 + maxLength: 100 + requireUpperCaseCharacter: 0 + requireLowerCaseCharacter: 0 + requireDigit: 0 + requireSpecialCharacter: 1 + expirePasswordInMonths: 6 + global: + minLength: 8 + maxLength: 100 + requireUpperCaseCharacter: 0 + requireLowerCaseCharacter: 0 + requireDigit: 0 + requireSpecialCharacter: 1 + expirePasswordInMonths: 6 + +authentication: + policy: + lockoutAfterFailures: 10 + countFailuresWithinSeconds: 7200 + lockoutPeriodSeconds: 600 + global: + lockoutAfterFailures: 1 + countFailuresWithinSeconds: 2222 + lockoutPeriodSeconds: 152 + +jwt: + token: + policy: + global: + accessTokenValiditySeconds: 3600 + refreshTokenValiditySeconds: 7200 + accessTokenValiditySeconds: 4800 + refreshTokenValiditySeconds: 9600 From 19577e71a20bbe738b7ca713f48e6aca6e6340e9 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 11:21:54 -0700 Subject: [PATCH 10/87] test yaml file has a lot more than hostnames get rid of the hostnames directory. --- .../org/cloudfoundry/identity/uaa/login/BootstrapTests.java | 2 +- .../test/{hostnames/uaa.yml => bootstrap/bootstrap-test.yml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename uaa/src/test/resources/test/{hostnames/uaa.yml => bootstrap/bootstrap-test.yml} (100%) 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 00c8dabddec..309865f32a0 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 @@ -250,7 +250,7 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { String uaa = "uaa.some.test.domain.com"; String login = uaa.replace("uaa", "login"); - context = getServletContext(null, "login.yml", "test/hostnames/uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); diff --git a/uaa/src/test/resources/test/hostnames/uaa.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml similarity index 100% rename from uaa/src/test/resources/test/hostnames/uaa.yml rename to uaa/src/test/resources/test/bootstrap/bootstrap-test.yml From 0d1b46af89b4ca0199e33b3165900151db04dfa7 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 11:28:45 -0700 Subject: [PATCH 11/87] Ensure that the disableInternalUserManagement flag is bootstrapped https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../identity/uaa/login/BootstrapTests.java | 134 +++++++++--------- .../test/bootstrap/bootstrap-test.yml | 2 + 2 files changed, 69 insertions(+), 67 deletions(-) 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 309865f32a0..b4eaed80f08 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 @@ -31,6 +31,7 @@ import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.saml.ZoneAwareMetadataGenerator; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; @@ -246,78 +247,77 @@ public void testRootContextDefaults() throws Exception { @Test public void testPropertyValuesWhenSetInYaml() throws Exception { - try { - String uaa = "uaa.some.test.domain.com"; - String login = uaa.replace("uaa", "login"); + String uaa = "uaa.some.test.domain.com"; + String login = uaa.replace("uaa", "login"); - context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); - IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); - assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); - DataSource ds = context.getBean(DataSource.class); - assertEquals(50, ds.getMaxActive()); - assertEquals(5, ds.getMaxIdle()); - assertTrue(ds.isRemoveAbandoned()); - assertFalse(ds.isLogAbandoned()); - assertEquals(45, ds.getRemoveAbandonedTimeout()); - assertEquals(30000, ds.getTimeBetweenEvictionRunsMillis()); - assertTrue(context.getBean(SimpleSearchQueryConverter.class).isDbCaseInsensitive()); - //check java mail sender - EmailService emailService = context.getBean("emailService", EmailService.class); - assertNotNull("Unable to find the JavaMailSender object on EmailService for validation.", emailService.getMailSender()); - assertEquals(FakeJavaMailSender.class, emailService.getMailSender().getClass()); + IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); + assertTrue(uaaIdp.getConfig().isDisableInternalUserManagement()); - PasswordPolicy passwordPolicy = context.getBean("defaultUaaPasswordPolicy",PasswordPolicy.class); - assertEquals(8, passwordPolicy.getMinLength()); - assertEquals(100, passwordPolicy.getMaxLength()); - assertEquals(0,passwordPolicy.getRequireUpperCaseCharacter()); - assertEquals(0,passwordPolicy.getRequireLowerCaseCharacter()); - assertEquals(0,passwordPolicy.getRequireDigit()); - assertEquals(1,passwordPolicy.getRequireSpecialCharacter()); - assertEquals(6, passwordPolicy.getExpirePasswordInMonths()); - - context.getBean("globalPasswordPolicy", PasswordPolicy.class); - assertEquals(8, passwordPolicy.getMinLength()); - assertEquals(100, passwordPolicy.getMaxLength()); - assertEquals(0,passwordPolicy.getRequireUpperCaseCharacter()); - assertEquals(0,passwordPolicy.getRequireLowerCaseCharacter()); - assertEquals(0,passwordPolicy.getRequireDigit()); - assertEquals(1,passwordPolicy.getRequireSpecialCharacter()); - assertEquals(6, passwordPolicy.getExpirePasswordInMonths()); - - PeriodLockoutPolicy periodLockoutPolicy = context.getBean("defaultUaaLockoutPolicy", PeriodLockoutPolicy.class); - LockoutPolicy lockoutPolicy = periodLockoutPolicy.getLockoutPolicy(); - Assert.assertThat(lockoutPolicy.getLockoutAfterFailures(), equalTo(10)); - Assert.assertThat(lockoutPolicy.getCountFailuresWithin(), equalTo(7200)); - Assert.assertThat(lockoutPolicy.getLockoutPeriodSeconds(), equalTo(600)); - - PeriodLockoutPolicy globalPeriodLockoutPolicy = context.getBean("globalPeriodLockoutPolicy", PeriodLockoutPolicy.class); - LockoutPolicy globalLockoutPolicy = globalPeriodLockoutPolicy.getLockoutPolicy(); - Assert.assertThat(globalLockoutPolicy.getLockoutAfterFailures(), equalTo(1)); - Assert.assertThat(globalLockoutPolicy.getCountFailuresWithin(), equalTo(2222)); - Assert.assertThat(globalLockoutPolicy.getLockoutPeriodSeconds(), equalTo(152)); - - UaaTokenServices uaaTokenServices = context.getBean("tokenServices",UaaTokenServices.class); - Assert.assertThat(uaaTokenServices.getTokenPolicy().getAccessTokenValidity(), equalTo(3600)); - Assert.assertThat(uaaTokenServices.getTokenPolicy().getRefreshTokenValidity(), equalTo(7200)); - - TokenPolicy tokenPolicy = context.getBean("uaaTokenPolicy",TokenPolicy.class); - Assert.assertThat(tokenPolicy.getAccessTokenValidity(), equalTo(4800)); - Assert.assertThat(tokenPolicy.getRefreshTokenValidity(), equalTo(9600)); - - List prompts = (List) context.getBean("prompts"); - assertNotNull(prompts); - assertEquals(3, prompts.size()); - Prompt passcode = prompts.get(0); - assertEquals("Username", passcode.getDetails()[1]); - passcode = prompts.get(1); - assertEquals("Your Secret", passcode.getDetails()[1]); - passcode = prompts.get(2); - assertEquals("One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )", passcode.getDetails()[1]); + IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); + assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); + DataSource ds = context.getBean(DataSource.class); + assertEquals(50, ds.getMaxActive()); + assertEquals(5, ds.getMaxIdle()); + assertTrue(ds.isRemoveAbandoned()); + assertFalse(ds.isLogAbandoned()); + assertEquals(45, ds.getRemoveAbandonedTimeout()); + assertEquals(30000, ds.getTimeBetweenEvictionRunsMillis()); + assertTrue(context.getBean(SimpleSearchQueryConverter.class).isDbCaseInsensitive()); + //check java mail sender + EmailService emailService = context.getBean("emailService", EmailService.class); + assertNotNull("Unable to find the JavaMailSender object on EmailService for validation.", emailService.getMailSender()); + assertEquals(FakeJavaMailSender.class, emailService.getMailSender().getClass()); - } finally { + PasswordPolicy passwordPolicy = context.getBean("defaultUaaPasswordPolicy",PasswordPolicy.class); + assertEquals(8, passwordPolicy.getMinLength()); + assertEquals(100, passwordPolicy.getMaxLength()); + assertEquals(0,passwordPolicy.getRequireUpperCaseCharacter()); + assertEquals(0,passwordPolicy.getRequireLowerCaseCharacter()); + assertEquals(0,passwordPolicy.getRequireDigit()); + assertEquals(1,passwordPolicy.getRequireSpecialCharacter()); + assertEquals(6, passwordPolicy.getExpirePasswordInMonths()); - } + context.getBean("globalPasswordPolicy", PasswordPolicy.class); + assertEquals(8, passwordPolicy.getMinLength()); + assertEquals(100, passwordPolicy.getMaxLength()); + assertEquals(0,passwordPolicy.getRequireUpperCaseCharacter()); + assertEquals(0,passwordPolicy.getRequireLowerCaseCharacter()); + assertEquals(0,passwordPolicy.getRequireDigit()); + assertEquals(1,passwordPolicy.getRequireSpecialCharacter()); + assertEquals(6, passwordPolicy.getExpirePasswordInMonths()); + + PeriodLockoutPolicy periodLockoutPolicy = context.getBean("defaultUaaLockoutPolicy", PeriodLockoutPolicy.class); + LockoutPolicy lockoutPolicy = periodLockoutPolicy.getLockoutPolicy(); + Assert.assertThat(lockoutPolicy.getLockoutAfterFailures(), equalTo(10)); + Assert.assertThat(lockoutPolicy.getCountFailuresWithin(), equalTo(7200)); + Assert.assertThat(lockoutPolicy.getLockoutPeriodSeconds(), equalTo(600)); + + PeriodLockoutPolicy globalPeriodLockoutPolicy = context.getBean("globalPeriodLockoutPolicy", PeriodLockoutPolicy.class); + LockoutPolicy globalLockoutPolicy = globalPeriodLockoutPolicy.getLockoutPolicy(); + Assert.assertThat(globalLockoutPolicy.getLockoutAfterFailures(), equalTo(1)); + Assert.assertThat(globalLockoutPolicy.getCountFailuresWithin(), equalTo(2222)); + Assert.assertThat(globalLockoutPolicy.getLockoutPeriodSeconds(), equalTo(152)); + + UaaTokenServices uaaTokenServices = context.getBean("tokenServices",UaaTokenServices.class); + Assert.assertThat(uaaTokenServices.getTokenPolicy().getAccessTokenValidity(), equalTo(3600)); + Assert.assertThat(uaaTokenServices.getTokenPolicy().getRefreshTokenValidity(), equalTo(7200)); + + TokenPolicy tokenPolicy = context.getBean("uaaTokenPolicy",TokenPolicy.class); + Assert.assertThat(tokenPolicy.getAccessTokenValidity(), equalTo(4800)); + Assert.assertThat(tokenPolicy.getRefreshTokenValidity(), equalTo(9600)); + + List prompts = (List) context.getBean("prompts"); + assertNotNull(prompts); + assertEquals(3, prompts.size()); + Prompt passcode = prompts.get(0); + assertEquals("Username", passcode.getDetails()[1]); + passcode = prompts.get(1); + assertEquals("Your Secret", passcode.getDetails()[1]); + passcode = prompts.get(2); + assertEquals("One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )", passcode.getDetails()[1]); } @Test diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index 8d7cbada96a..61d073fe15a 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -1,3 +1,5 @@ +disableInternalUserManagement: true + zones: internal: hostnames: From cc6249efd85d0461e2e571c8482de1e7e67493b3 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 11:40:10 -0700 Subject: [PATCH 12/87] Ensure that the LoginInfoEndpoint derives the property disableInternalUserManagement from the zone UAA provider config https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../identity/uaa/login/LoginInfoEndpoint.java | 8 +++--- .../uaa/login/LoginInfoEndpointTests.java | 10 ++++--- .../main/webapp/WEB-INF/spring-servlet.xml | 1 - .../identity/uaa/login/LoginMockMvcTests.java | 26 ++++++++++++------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index b7f64283bce..5e40172b246 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; @@ -126,17 +127,12 @@ public class LoginInfoEndpoint { private ClientDetailsService clientDetailsService; private boolean selfServiceLinksEnabled = true; - private boolean disableInternalUserManagement; private IdentityProviderProvisioning providerProvisioning; public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { this.selfServiceLinksEnabled = selfServiceLinksEnabled; } - public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { - this.disableInternalUserManagement = disableInternalUserManagement; - } - public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) { this.expiringCodeStore = expiringCodeStore; } @@ -497,6 +493,8 @@ public String generatePasscode(Map model, Principal principal) } protected Map getLinksInfo() { + IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl())); if (getBaseUrl().contains("localhost:")) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index 374562241b3..cedc707874d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -9,8 +9,8 @@ import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -61,6 +61,7 @@ public class LoginInfoEndpointTests { private SamlIdentityProviderConfigurator mockIDPConfigurator; private List idps; private IdentityProviderProvisioning identityProviderProvisioning; + private IdentityProvider uaaProvider; @Before public void setUpPrincipal() { @@ -73,7 +74,8 @@ public void setUpPrincipal() { linksSet.put("passwd", "/forgot_password"); mockIDPConfigurator = mock(SamlIdentityProviderConfigurator.class); identityProviderProvisioning = mock(IdentityProviderProvisioning.class); - when(identityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.UAA), anyString())).thenReturn(new IdentityProvider()); + uaaProvider = new IdentityProvider(); + when(identityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.UAA), anyString())).thenReturn(uaaProvider); when(identityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.LDAP), anyString())).thenReturn(new IdentityProvider()); idps = getIdps(); } @@ -263,12 +265,14 @@ public void saml_links_for_html() throws Exception { @Test public void no_self_service_links_if_internal_user_management_disabled() throws Exception { + UaaIdentityProviderDefinition uaaIdentityProviderDefinition = new UaaIdentityProviderDefinition(); + uaaIdentityProviderDefinition.setDisableInternalUserManagement(true); + uaaProvider.setConfig(uaaIdentityProviderDefinition); LoginInfoEndpoint endpoint = getEndpoint(); Map linksSet = new HashMap<>(); linksSet.put("register", "/create_account"); linksSet.put("passwd", "/forgot_password"); endpoint.setLinks(linksSet); - endpoint.setDisableInternalUserManagement(true); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 80ec3ccc962..f570e5dd93b 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -380,7 +380,6 @@ - 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 c7931e6c16c..af0348b979c 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 @@ -156,17 +156,25 @@ public void testLogin() throws Exception { .andExpect(content().string(containsString("/create_account"))); } + protected void setDisableInternalUserManagement(boolean disabled) { + IdentityProviderProvisioning provisioning = webApplicationContext.getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + uaaIdp.getConfig().setDisableInternalUserManagement(disabled); + provisioning.update(uaaIdp); + } + @Test public void testLogin_When_DisableInternalUserManagement_Is_True() throws Exception { - webApplicationContext.getBean(LoginInfoEndpoint.class).setDisableInternalUserManagement(true); - - getMockMvc().perform(get("/login")) - .andExpect(status().isOk()) - .andExpect(view().name("login")) - .andExpect(model().attributeExists("prompts")) - .andExpect(content().string(not(containsString("/create_account")))); - - webApplicationContext.getBean(LoginInfoEndpoint.class).setDisableInternalUserManagement(false); + setDisableInternalUserManagement(true); + try { + getMockMvc().perform(get("/login")) + .andExpect(status().isOk()) + .andExpect(view().name("login")) + .andExpect(model().attributeExists("prompts")) + .andExpect(content().string(not(containsString("/create_account")))); + } finally { + setDisableInternalUserManagement(false); + } } @Test From 769c505240456cef8ac6ff660c4cb7a634c34c4e Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 14:29:53 -0700 Subject: [PATCH 13/87] Make selfServiceLinksEnabled configurable per zone https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../UaaIdentityProviderDefinition.java | 9 +++++ .../config/IdentityProviderBootstrap.java | 25 ++++++++++--- .../identity/uaa/login/LoginInfoEndpoint.java | 6 +--- .../uaa/login/LoginInfoEndpointTests.java | 4 ++- .../main/webapp/WEB-INF/spring-servlet.xml | 2 +- .../identity/uaa/login/BootstrapTests.java | 2 ++ .../identity/uaa/login/LoginMockMvcTests.java | 35 +++++++++++-------- .../test/bootstrap/bootstrap-test.yml | 2 ++ 8 files changed, 58 insertions(+), 27 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java index f9621bdad24..a6d11a2889c 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java @@ -20,6 +20,7 @@ public class UaaIdentityProviderDefinition extends AbstractIdentityProviderDefin private PasswordPolicy passwordPolicy; private LockoutPolicy lockoutPolicy; private boolean disableInternalUserManagement = false; + private boolean selfServiceLinksEnabled = true; public UaaIdentityProviderDefinition() { } @@ -57,4 +58,12 @@ public boolean isDisableInternalUserManagement() { public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { this.disableInternalUserManagement = disableInternalUserManagement; } + + public boolean isSelfServiceLinksEnabled() { + return selfServiceLinksEnabled; + } + + public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { + this.selfServiceLinksEnabled = selfServiceLinksEnabled; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java index cfe229c6b66..0e9e88b3bb3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java @@ -55,6 +55,7 @@ public class IdentityProviderBootstrap implements InitializingBean { private PasswordPolicy defaultPasswordPolicy; private LockoutPolicy defaultLockoutPolicy; private boolean disableInternalUserManagement; + private boolean selfServiceLinksEnabled = true; public IdentityProviderBootstrap(IdentityProviderProvisioning provisioning, Environment environment) { if (provisioning==null) { @@ -211,12 +212,18 @@ protected void updateDefaultZoneUaaIDP() throws JSONException { UaaIdentityProviderDefinition identityProviderDefinition = new UaaIdentityProviderDefinition(defaultPasswordPolicy, defaultLockoutPolicy, disableInternalUserManagement); internalIDP.setConfig(identityProviderDefinition); String disableInternalAuth = environment.getProperty("disableInternalAuth"); - if (disableInternalAuth != null) { - internalIDP.setActive(!Boolean.valueOf(disableInternalAuth)); + internalIDP.setActive(!getBooleanValue(disableInternalAuth, false)); + String selfServiceLinksEnabled = environment.getProperty("login.selfServiceLinksEnabled"); + identityProviderDefinition.setSelfServiceLinksEnabled(getBooleanValue(selfServiceLinksEnabled, true)); + provisioning.update(internalIDP); + } + + protected boolean getBooleanValue(String s, boolean defaultValue) { + if (s != null) { + return Boolean.valueOf(s); } else { - internalIDP.setActive(true); + return defaultValue; } - provisioning.update(internalIDP); } private boolean isAmongProviders(String originKey) { @@ -241,6 +248,14 @@ public boolean isDisableInternalUserManagement() { } public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { - this.disableInternalUserManagement = disableInternalUserManagement; + this.disableInternalUserManagement = disableInternalUserManagement; + } + + public boolean isSelfServiceLinksEnabled() { + return selfServiceLinksEnabled; + } + + public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { + this.selfServiceLinksEnabled = selfServiceLinksEnabled; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 5e40172b246..7fb74652a91 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -126,13 +126,8 @@ public class LoginInfoEndpoint { private ExpiringCodeStore expiringCodeStore; private ClientDetailsService clientDetailsService; - private boolean selfServiceLinksEnabled = true; private IdentityProviderProvisioning providerProvisioning; - public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { - this.selfServiceLinksEnabled = selfServiceLinksEnabled; - } - public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) { this.expiringCodeStore = expiringCodeStore; } @@ -495,6 +490,7 @@ public String generatePasscode(Map model, Principal principal) protected Map getLinksInfo() { IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; + boolean selfServiceLinksEnabled = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isSelfServiceLinksEnabled() : true; Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl())); if (getBaseUrl().contains("localhost:")) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index cedc707874d..62e2414a6c1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -205,7 +205,9 @@ public void check_links_urls(IdentityZone zone) throws Exception { public void no_self_service_links_if_self_service_disabled() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); endpoint.setLinks(linksSet); - endpoint.setSelfServiceLinksEnabled(false); + UaaIdentityProviderDefinition uaaIdentityProviderDefinition = new UaaIdentityProviderDefinition(); + uaaIdentityProviderDefinition.setSelfServiceLinksEnabled(false); + uaaProvider.setConfig(uaaIdentityProviderDefinition); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index f570e5dd93b..34aea641400 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -379,7 +379,6 @@ - @@ -393,6 +392,7 @@ + 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 b4eaed80f08..a579bf23f88 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 @@ -255,6 +255,8 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertTrue(uaaIdp.getConfig().isDisableInternalUserManagement()); + assertFalse(uaaIdp.isActive()); + assertFalse(uaaIdp.getConfig().isSelfServiceLinksEnabled()); IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); 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 af0348b979c..b8beb31aff7 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 @@ -136,6 +136,8 @@ public void setUpContext() throws Exception { @After public void tearDown() throws Exception { //restore all properties + setSelfServiceLinksEnabled(true); + setDisableInternalUserManagement(false); webApplicationContext.getBean(LoginInfoEndpoint.class).setLinks(configuredDefaultLinks); mockEnvironment.getPropertySources().remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); MockPropertySource originalPropertySource = new MockPropertySource(originalProperties); @@ -163,18 +165,21 @@ protected void setDisableInternalUserManagement(boolean disabled) { provisioning.update(uaaIdp); } + protected void setSelfServiceLinksEnabled(boolean enabled) { + IdentityProviderProvisioning provisioning = webApplicationContext.getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + uaaIdp.getConfig().setSelfServiceLinksEnabled(enabled); + provisioning.update(uaaIdp); + } + @Test public void testLogin_When_DisableInternalUserManagement_Is_True() throws Exception { setDisableInternalUserManagement(true); - try { - getMockMvc().perform(get("/login")) - .andExpect(status().isOk()) - .andExpect(view().name("login")) - .andExpect(model().attributeExists("prompts")) - .andExpect(content().string(not(containsString("/create_account")))); - } finally { - setDisableInternalUserManagement(false); - } + getMockMvc().perform(get("/login")) + .andExpect(status().isOk()) + .andExpect(view().name("login")) + .andExpect(model().attributeExists("prompts")) + .andExpect(content().string(not(containsString("/create_account")))); } @Test @@ -500,7 +505,7 @@ public void testAccessConfirmationPage() throws Exception { @Test public void testSignupsAndResetPasswordEnabled() throws Exception { - webApplicationContext.getBean(LoginInfoEndpoint.class).setSelfServiceLinksEnabled(true); + setSelfServiceLinksEnabled(true); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']").exists()) @@ -509,7 +514,7 @@ public void testSignupsAndResetPasswordEnabled() throws Exception { @Test public void testSignupsAndResetPasswordDisabledWithNoLinksConfigured() throws Exception { - webApplicationContext.getBean(LoginInfoEndpoint.class).setSelfServiceLinksEnabled(false); + setSelfServiceLinksEnabled(false); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']").doesNotExist()) @@ -523,7 +528,7 @@ public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws links.put("signup", "http://example.com/signup"); links.put("passwd", "http://example.com/reset_passwd"); endpoint.setLinks(links); - endpoint.setSelfServiceLinksEnabled(false); + setSelfServiceLinksEnabled(false); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']").doesNotExist()) @@ -537,7 +542,7 @@ public void testSignupsAndResetPasswordEnabledWithCustomLinks() throws Exception links.put("signup", "http://example.com/signup"); links.put("passwd", "http://example.com/reset_passwd"); endpoint.setLinks(links); - endpoint.setSelfServiceLinksEnabled(true); + setSelfServiceLinksEnabled(true); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']/@href").string("http://example.com/signup")) @@ -644,7 +649,7 @@ public void testDefaultAndCustomSignupLink() throws Exception { @Test public void testLocalSignupDisabled() throws Exception { - webApplicationContext.getBean(LoginInfoEndpoint.class).setSelfServiceLinksEnabled(false); + setSelfServiceLinksEnabled(false); getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(model().attribute("createAccountLink", nullValue())); @@ -652,7 +657,7 @@ public void testLocalSignupDisabled() throws Exception { @Test public void testCustomSignupLinkWithLocalSignupDisabled() throws Exception { - webApplicationContext.getBean(LoginInfoEndpoint.class).setSelfServiceLinksEnabled(false); + setSelfServiceLinksEnabled(false); LoginInfoEndpoint endpoint = webApplicationContext.getBean(LoginInfoEndpoint.class); Map links = endpoint.getLinks(); links.put("signup", "http://example.com/signup"); diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index 61d073fe15a..c5186daddbb 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -1,4 +1,5 @@ disableInternalUserManagement: true +disableInternalAuth: true zones: internal: @@ -16,6 +17,7 @@ login: text: Your Secret url: https://login.some.test.domain.com:555/uaa entityBaseURL: https://login.some.test.domain.com:555/uaa + selfServiceLinksEnabled: false smtp: host: '' From bf25cc7970f03407c8b5265f14faa8280c958c07 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 16:31:23 -0700 Subject: [PATCH 14/87] Add plumbing around zonifying common configuration attributes --- build.gradle | 1 - .../uaa/zone/IdentityZoneConfiguration.java | 22 +++ .../cloudfoundry/identity/uaa/zone/Links.java | 134 ++++++++++++++++++ .../identity/uaa/home/HomeController.java | 5 - .../main/resources/templates/web/home.html | 1 - .../uaa/login/HomeControllerViewTests.java | 12 +- uaa/src/main/resources/login.yml | 7 +- .../mock/DefaultConfigurationTestSuite.java | 1 - 8 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java diff --git a/build.gradle b/build.gradle index 069f93aae7c..afd286d6e4e 100644 --- a/build.gradle +++ b/build.gradle @@ -247,7 +247,6 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> //property 'uaa.allowUnverifiedUsers', 'false' property 'smtp.host', 'localhost' property 'smtp.port', 2525 - property 'login.invitationsEnabled', 'true' } } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index 34c51c01113..e75e797a671 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -13,12 +13,16 @@ */ package org.cloudfoundry.identity.uaa.zone; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class IdentityZoneConfiguration { private TokenPolicy tokenPolicy = new TokenPolicy(); private SamlConfig samlConfig = new SamlConfig(); + private boolean disableInternalUserManagement = false; + private Links links = new Links(); public IdentityZoneConfiguration() {} @@ -42,4 +46,22 @@ public IdentityZoneConfiguration setSamlConfig(SamlConfig samlConfig) { this.samlConfig = samlConfig; return this; } + + public boolean isDisableInternalUserManagement() { + return disableInternalUserManagement; + } + + public IdentityZoneConfiguration setDisableInternalUserManagement(boolean disableInternalUserManagement) { + this.disableInternalUserManagement = disableInternalUserManagement; + return this; + } + + public Links getLinks() { + return links; + } + + public IdentityZoneConfiguration setLinks(Links links) { + this.links = links; + return this; + } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java new file mode 100644 index 00000000000..f15d7ea80c0 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java @@ -0,0 +1,134 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.zone; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.HashSet; +import java.util.Set; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Links { + + private SelfService service = new SelfService(); + private Logout logout = new Logout(); + private String homeRedirect = null; + + public Logout getLogout() { + return logout; + } + + public Links setLogout(Logout logout) { + this.logout = logout; + return this; + } + + public SelfService getService() { + return service; + } + + public Links setService(SelfService service) { + this.service = service; + return this; + } + + public String getHomeRedirect() { + return homeRedirect; + } + + public Links setHomeRedirect(String homeRedirect) { + this.homeRedirect = homeRedirect; + return this; + } + + public static class Logout { + private String redirectUrl = "/login"; + private String redirectParameterName = "redirect"; + private boolean disableRedirectParameter = false; + private Set whitelist = new HashSet<>(); + + public boolean isDisableRedirectParameter() { + return disableRedirectParameter; + } + + public Logout setDisableRedirectParameter(boolean disableRedirectParameter) { + this.disableRedirectParameter = disableRedirectParameter; + return this; + } + + public String getRedirectParameterName() { + return redirectParameterName; + } + + public Logout setRedirectParameterName(String redirectParameterName) { + this.redirectParameterName = redirectParameterName; + return this; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public Logout setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + return this; + } + + public Set getWhitelist() { + return whitelist; + } + + public Logout setWhitelist(Set whitelist) { + this.whitelist = whitelist; + return this; + } + } + + public static class SelfService { + private boolean selfServiceLinksEnabled = true; + private String signup = "/create_account"; + private String passwd = "/forgot_password"; + + public boolean isSelfServiceLinksEnabled() { + return selfServiceLinksEnabled; + } + + public SelfService setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { + this.selfServiceLinksEnabled = selfServiceLinksEnabled; + return this; + } + + public String getPasswd() { + return passwd; + } + + public SelfService setPasswd(String passwd) { + this.passwd = passwd; + return this; + } + + public String getSignup() { + return signup; + } + + public SelfService setSignup(String signup) { + this.signup = signup; + return this; + } + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java index 4a2887dd4b7..b98132ea3d4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java @@ -116,11 +116,6 @@ public String home(Model model, Principal principal) { model.addAttribute("tiles", tiles); } - boolean invitationsEnabled = "true".equalsIgnoreCase(environment.getProperty("login.invitationsEnabled")); - if (invitationsEnabled) { - model.addAttribute("invitationsLink", "/invitations/new"); - } - populateBuildAndLinkInfo(model); return "home"; } diff --git a/server/src/main/resources/templates/web/home.html b/server/src/main/resources/templates/web/home.html index a1ec184b3f2..ed3c1a6f73a 100644 --- a/server/src/main/resources/templates/web/home.html +++ b/server/src/main/resources/templates/web/home.html @@ -19,6 +19,5 @@

Where to?

- Invite Users diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java index b859f02b902..d9a862947e1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java @@ -75,6 +75,7 @@ public void setUp() throws Exception { @After public void tearDown() { SecurityContextHolder.clearContext(); + IdentityZoneHolder.clear(); } @Test @@ -118,6 +119,16 @@ public void testConfiguredHomePage() throws Exception { mockMvc.perform(get("/home")) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", customHomePage)); + +// IdentityZone zone = MultitenancyFixture.identityZone("zone","zone"); +// IdentityZoneHolder.set(zone); +// mockMvc.perform(get("/home")) +// .andExpect(status().isOk()); +// +// zone.getConfig().getLinks().setHomeRedirect(customHomePage); +// mockMvc.perform(get("/home")) +// .andExpect(status().is3xxRedirection()) +// .andExpect(header().string("Location", customHomePage)); } @Configuration @@ -190,7 +201,6 @@ MockEnvironment environment() { @Bean HomeController homeController(MockEnvironment environment) { - environment.setProperty("login.invitationsEnabled","true"); HomeController homeController = new HomeController(environment); homeController.setUaaBaseUrl("http://uaa.example.com"); return homeController; diff --git a/uaa/src/main/resources/login.yml b/uaa/src/main/resources/login.yml index db9c5437269..0644dd737db 100644 --- a/uaa/src/main/resources/login.yml +++ b/uaa/src/main/resources/login.yml @@ -10,6 +10,7 @@ # subcomponents is subject to the terms and conditions of the # subcomponent's license, as noted in the LICENSE file. +# Deprecated: More to follow # customize static asset source, provides control over visual branding # (defaults to /resources/oss) #assetBaseUrl: /resources/pivotal @@ -30,7 +31,7 @@ # If selfServiceLinksEnabled is true and these custom links are not provided then the Login Server # will use internal links. # passwd: /forgot_password -# signup: /blah_account +# signup: /create_account #notifications: # url: http://localhost:3001 @@ -46,8 +47,6 @@ login: # Enable create account and forgot password links on the Login Server (enabled by default) #selfServiceLinksEnabled: true - # Enable sending invitations on the Login Server (disabled by default) - #invitationsEnabled: true #base URL that the login server can be reached at url: http://localhost:8080/uaa @@ -197,7 +196,7 @@ login: url: http://localhost:8080/uaa/oauth/authorize # homeRedirect: http://example.com/ - + # branding: # companyName: My Company # productLogo: iVBORw0KGgoAAAANSUhEUgAAAJIAAACoCAYAAAALmrdFAAAgAElEQVR4Ae19CZwUxfX/t3quPdhlD/bgUhBMVOQS8EBUPEJQMZooaExMoiYiV9R4/I1X1kg0iQYiN+rP+PsZowJJjHgfAREMKCi3IiCIXMtes8dcfdT7f2pmeqZnpntm9gRN12d7u7rq1atXr99U1/HeK8AONgdsDtgcsDlgc8DmgM0BmwM2B2wO2BywOWBzwOaAzQGbAzYHbA7YHLA5YHPA5oDNAZsDNgdsDtgcsDlgc8DmgM0BmwM2B2wO2BywOWBzwOaAzQGbAzYHbA7YHLA5YHPA5oDNAZsDNgdsDtgcsDlgc8DmgM0BmwM2B2wO2BywOWBzwOaAzQGbAzYHbA7YHLA5YHPA5oDNAZsDNgdsDtgcsDlgc8DmgM0BmwM2B2wO2BywOWBzwOaAzQGbAzYHvn4cYJlInvFRw3Au4TwzOAlUM3dEyXNmecdcWtUKZ2VZ2XA4MIqAExnHADAMAFACIA9AoYHmZgDi2g/CfgbawSVscnC+8eDUoTsMcG2Ovrbf30fS+FVtRSBJ9Mm4vgXvZSrf0NAwHLB4f5JU07179w55f85MhAghYqDZZnAEbALQIYSY4W9vWvniLQMYlyYyRuNAOCMsMASEfz3pf0IFAMTVCwynExgYAZw5ULlwazUIKxmjV4NS6OWGySMb20InU7SBTGKmfM0GH+fscQAZBUkIEWPm9RBRh72/jIKUTaOOJZjjFmwulpnjBoB+BI7hAIX/OpDGCjBcTWBXe3iOXLlwyzsSOZ482GPrckyapHVgPV8rVN8YQeq1ePNJGme3ymDXASQ+VV0R3AC7hDN+SWXdKfuxaMvjmiYtqpk2qKUrKj+W6pCOJWLaQkufhRt791yw9SnOHVsZ2OToeKctqNpbpg+IPeqQaG/loi13DFqyzd1ehF+n8l9bQRIvqnLhlioVjl3E6EaAOyLfMMJRvpeC8GhdHd9WsXDLhK+TMLSH1q/lp63noi0j62q1ZwA2qA2NDxDwMSPsYMAXkGgvI/g4SY2QSEihuBURQwWR1AugbwMYDOAkkdWK+gYyYHnPBVv/5gKfvm/qkIZWlP3agWYUJDHFj87OUhrHgA6ZCqcgtkogYhULt/6aOB4CWGte6ocEvMQYe/ewFPwEk0cqVlVYpRcvXt/dzT3nMM6+C0aXA+hrBWtMJ9C1MjC254JN1xyaOvR9Pc/hkFoI4VmTnpR6JxwHoDg1I/sUSZJqorMzs0Id9v7ST4LNqj5KaSVzdhZ6XKH/Iwq/xCyooINg7AlJkv5ycPKgfVkUyB6EiPVcuHkMh3QTAyYC8GRRWGXE7jo07dSsp/xvftnyDICfmuEm4PHxx3e71SzvaKQ539zbstKqYnJoPx/ft/suq/yuSq9c+Ek/UOANIojPTKawi4DfVDvkpW3peaZ+eKRScjhfsKqEa+o1Cxg7fAgQvcv75U9uuUNS6DYA0wHkW5UD4CRGswa/uOvO0f1Lr1l8evGqNLBfuywnmPmqp2iJpDq6He0WVczdPJg43gTQMz0trBaM7q0+Uvc0qs5X08Na57pdrhyVk+lKvigl8o2lj/xicDWAu8v/vGU2c2MmSAz8I2ueRjg9fqTW3/ONZvmPg5ZsO3fbpEGynv51v7dmnNHlba1YuOlUSPRvgHqmn4nhrw5n6OTqqUOeaI8QtaeBR24dXF09dfAvwGg0gXakozcYUs6orVGXfZOWCI5ZQaqYu6k/NLwBQg+xOG1+sSYCXVk9bch1ByePrG2PIHRU2eqpQ9fm5RUO612W32xOc6wtl9VVq8+jio7Zd9AanhyTjRDbHMTodYB6W0gQQLSVa9rII9OG/aM1De4K2L3X9w+OHlBSf9qJJWDh6Yz5L4EY/aCybOMfuoKmzq7j2BMkIhbS+LNMDKzN+Q8ivC+71TE1twzf2dkMag/+AT3yMWZQOZwOyfr3wNkdFXM23tCeeo6FssecIFXM++T/AXSpJedBrzjd6vi27rp3NdMrCzwYO7gCLpfomix+GQzzxaSiq2nryPqcAD1ohVABO2yV1xnp5XM/ORtEMyMMT62BGFbldWucuPf684OpuR2W4k3HE4B5s6mJwP7MwIsEbHGuE6cNKK34aEfN9ZyTyZoT5RC0pf3+smJYQtsYewnE95rWJ7G1pulHKbFDFiRramp6OZ1O001Kznl9aWlpU6b2/WDV3p4fbGp4n4iEsplZ2Cx7+LnpeqKb1h/My3PklpsV1iSuzh1auv/VPS2VEhwJU3gdPgdB7/n9i7MSFL2M1V0orjGFJewcPPCfL8fsq/WLRUaHWTm3x7n4ytP7/p404o+PLOmQRdTXdtYVMmeeUN5LCQ4Hl8cdl3/wrX2+Xpommb4/Uv31l5yY+f0lNDSlpiwTXC7XawCGmoFLkiQW6/5slmdM230g9A/i3FSIJInJHI4rGiafllaJzI3ccSqnfxrxxuKcfQmgn5PhBTDNdJ1IhlP0zlWxMu2IODW+miQcb0Qx8+w+eOWLBizZfMSYHIvLIWVydXNocmm+W7Qz3JvFMtsYkdyeGxg009X06NbXMAK9Jkma6fsjtyer93dMjJHK56wfcPhI8ygigtl1Sr+S1dUzhu5pIy+PqWITTihGYZ7zP2btJE7YsPuYWMVoNc+OCUGCxu8nIlM1kL49C3By74KvWt2yY7jAnWf0nS8xNJgNvr2NAXxR0+L6uq0vdcinrT3vrHLO2lO4hh+LOX1ycLsdGNGvNDn5a/98fJHHV9aj26zqI00PmTVm/aeH88q7H9Yw+8MACHvA2OfE+FoH2L8P3zJqPZjQID+2wlEXJK6we8BEb5QaBp/QA26n6DSPOb6lEtvKlPO+VbH6HVlBfYM/XclcAKeA6BRGuIKDUDb7w6/Yn9Y9L0l84eHbzjKf0aXD2El5R/XTVvHomnKAJoZ7I9EjGa78fDdOKBOGHN/ccNqAHgltNrbfKs6I+gJ0F+dsd/mf1j7b87E1CQP6o8WtoypInDluICK32cDz1H6l0e2Fo8Wazq+3JN+DnhUFphMMM54kpUlE9GMV0o6yx9bejaoVR/Xr0iGVS5J0taZpohtOCU6n80BKop5A/Od61HiXJLavd0nBFVyKfNNcGuqN+VZxGbTCKTFhEJgSHMTDKhtCx8pKPaYjF2BV0CVOzkzXZhgP7eF5qgbZNbyom7vvQaKXWqnGa2yfB6BHyvI936PZq66sve3cQ8ZM7qbnnEFmqnOmEQ8IWK7yqx1MMn1/Wg5Zvz9DRRkFKRtL23t3qiM5WJkBryGqYtqGVHXlL4409l6/9YDpulFlecHHDmHdyyNoOGg7gH3T1tdPAWMmK8OA5OTL5g4t2Q9go6HylOirNeowTrxPSkb4TfK1M9bW+blLstz7khT+9Pcqck/RJH6mGQ4G2v/d4wuWvV4nn8lJMlrvxsAlkpodLkeIc4w9tW8P7K9u3tDUFBgVA2hb5CwnuXZMeHPvouN7dD8oUEjM+dzr+9U+6SylAey4+ITCdqvcZhSkbCxtCbiTgUwXtKx40uQLRcYHSQCSxHD6iZVXMNAVehZJ+F8AbzGGRwDqrqcb7yRLQoCEIKUNRGw6g7niGgEPOj3SYeLmlsUCscMjvcQlbTwD+41pRcSE9esyIvYAA5mOXwjYC1nyMilSz4XDjkNzMK7jRoSWdz7afdmgAZUPKho/t67Rj4YGf/gTaFpnNFFTtYIN2w7emT/chbLCXGhMXcm6yFI6oyClI7w9eQermwCeOhsrLsmHx2U6iWtPdcd0WTEzLe2WsGuj1d55zsrBH9ftAbFzBfGyqmH34Ubs/LIOAX9c6JIbpsoa3v/4S1wwqj8KCxJwJoN26PNRGWwrKkdLs/g8C0FKvPqUf7Nnam19e26nAyf3KcGE0QMx9JSekCSILj2FfyJNUVSs2vglahv86XTI20qKabmjIkg1TX4Q56ZXn1JbkEzfVDRRYgwn9S7B+WcM+KvEab0VH/0tQaz5ZM896XB1ZN7RESSv33T9xON2oluOqyPb943F1aObp2X0iN6XVpQXmvJSrEOFgsolW/YcPrkrmNAlYySNEw7Vt+BgbRPqvX40NwVMB9rFRV3l+6ErWNv5dRxfXir3LC3GvzfsQW2NuabO57uP/ODUfuUQPVlnhoyC1B5L25aAjG17j2D/gXrIcmYLoYI8t1gDMdOxCOvmMGALRfwWpfLEwbLzAEJsFxiZq2gwdlglVQYcwm+QaRD5xNyHmZWVLINuByiWLEx1mySgkQt6I/6JUuphESdfIl2025QWBnbA7VM1Ndex6dyh/aTX1mz/VsAvpyyNKDLvsf3Lmq9O7VeeshbXkZbSHSKm0zfUi6l3bPovZhgbdx7Cnj1HwE02Y1M4F01gjC6vvX/cy1b5dro1B0p/+84ogITWpNlw5dO6B75zinXp9uew6RvqTVc9BWoOx88XjMhsaWsUpEN1zfjPx7vF97nV1I0//9R1xd1yU9VoCW/MG1ny++kb6l8BYGq0ySV2KzTqJjHMbHXF0QIM7BlNU95IZ2mbGTdtnDei9NbpG+qFtW6lGTwn3AcHa5E4ZVT4Mysv0hiwTMtRn5GCTsGTcFi5Yfe3Dh2sNzUkPXvUiZuOqyxK7CGJ7Zo3svjn09c3PAVGA3U8xjtjNG/uaaXLjGlmcfFpM9UWDANL3PSlmSESadu+OIzN2/ZFZqVWQBbpTGJKcbdc4Z4vNUik73KPAWC6ICmpKAJjRbBYbExFmppC4CszWdqmlkpOiXXyYuXbdEFSAusBFU5Y+OZMxmj2TKCNbtnlVA3tHXVKXyw/WG+6cHmwtnHocZVJX3T9E89opPGLYqyPkyS2bzIGs24wYyEzgPXb92HTlr2RKT1xUJaXJEGo12wrK+u+zQyvnZY9B/Jz3aio7G7K+0OHUoZI2SPOAjLjYDsLHNjw6b7yz3dk3J2A0+lAeWUxKnoUorgwD4X5OcjLccMpsTEq50JXelg29dkw1hzo17sUh/anqusGWgLwBWXk55juI1sjzDKn3YJUfM8rl+749CvT77JOgyfHjVNO7ouBfcvgcv53bX/oPOiqe++y7qZLK6L+jZ/uw4n9KlFe3KoRS1akt0uQuv36tTJAeYaiu/RmNfY7oRKjBp8A93/Z/pkZL7oize1yIj8/By3NqZqXe3cfgrjyu+XiWyf2wrf6VXbY0KZdguQiZRaIepgxSKx/nTbiRJzUP21nZVbUTmsnBwoLc9HS5LPE4mv245OPd2H7tn0nldz9zystAVuRkdbSFopqaWlbdPey4SDtR1Z1Dfx235dO6l+5KZspnNfbGMwrLHxDAiVOT3XkPKwiIlD9HoxMt7SdTrZX1dKOsRqBDNNtLq2EJBYRra2PdZKs7gxSeIZptLRNheWfAQ7TpYFUWOsUr9cb7FZYmGIpLVRHQHysdclITigYFPtRy5a/vX7td84b9rsctyNl1ViKrE1lQgXnvBGlaQ0CrSw1b/yft37rD8mxua6xJo/H/eToYQMfFmlaiNefU1ASLMgL9jLCGOPrTiuRz9zf9CGp7s+M6Xo8x6O0zANweXHu31TJdMENOcg5/Pc6czmM4vGKtgqPbMnOsvR6hBAVeYtavEVeYQ1rGoq8Rfu9RV4xyEiaS0fAZUUJr4MJRTsHl0x7fL8WOOJGbrsFqd/YfrJ3YyqtLY0th4lrGQVJb2BjXeOZ/3p5jefmC4b8dPTAXuLojFgQlrZziNxer9fy/RUXF+81bWgMi1gmNbHUbPTLCIZkU32iboV5uOySM3+hcvqFwEMu6baiPN9KkvCJEa8xft5eb3FIcj0gSdotxnQ9LiuSUGz7GXdqmyWLdaQgbzlfh093F4uN1h7Z6EEhRConS2NMb5G3v8r5z2Ch2CY5nEKxbSypbLVqodjmRu73rbZP0tGenOfd6C00o3Xs2UOweu021B3xhojMfA0kYwIUWRn+0ie7No/o3wO5hvGssLT1er0rGWOW70+sj2YUpNQqgQ92HQTXzE9LGD5sIBxiccgOR40D3fJzMP7CEWgJhs4paWy4tiUo3/rRF0ew52D6taSvDjdg1psf454Jo6x9F1q0qk2CtH5PdXjhMRlnfkEejuttobqdDGw/dzoH8vJylHF9jvtSjD+uHDEQNc0BvLBuB9Zs/cpyD3TTzkN4fdMeXDK0f6voa1PXsWt/HcB5ynV8v0qwTlZXaFXrbOAEDpQV5GLGRcPwwMTRKMxzp7w//Z0+v2obvEKnvhWh1YIkdvP9/qCpdmOvXqYrAa0gxwbtCg6c0rsUf7j2PJQU5pq+x2BQxtJ1pvMeS/JaL0hCYV+sQJpcPYpNrW8sK7czjh4HSgtyce/3z0LEIj71fa7c9AX8oew1OFotSGG7D64BSZfLyeBytWnIdfS4+V9ec98ehbjq7JNT3qV4t3JIxurPsvf1lfHNczn0NHPmxVQJ9tU1eohTSr+nhNQap8ROT343Yh3J688X60iWo7f3+uU3nbm/6bdcdZvq54h1JIFXUh1D0q0jyTgk5Um5pvUIj20Ch/Dcb7mOBKavI5niEOWj60iCTtO1Jn0diTlpTLp1JLklxIuKiizrSeZj8jMLOJq6n1XY5N3otcRR5C46CPjSriddNvJbWL72M7T4U8dE6z4/gO8MHYCioqLtXq91PYK2jIIUdfsWVwieMcdTwFM0OsEkqVDhiBk16g2XHNJ7kwaF1yB0nSI9K/ku5qZp56fcpV7KyNzSNuAILPMgz6NwuiwZsXiWCKINTy84vdxytV7AiQVYye1JaYeOk3erf9rlzD3J2tLWuX8BsAwaximUcE6ujgIe5C1/4vxeuyEMJdOEGevrxnEmmWo2Sjn8kyoWNsZMi+ONL9NrIAszp7FD+uGVD4RmcGLYuU8caiDUfJgwpEtbT0ZBSkQNIOThwgQmOWice0LB0OycJDUFLjHhOi7dYlYyKutnYo8wiwVJpmgbCVRkef4uhV3/PW2NPJIj/C1aucoTEKJ35pLafktbQAhS2kASrmVE5ofaEGV7pm3aOkTmaSf0wvLVW1PgfL4gFJVnNfzJCiihhl6HNLOBtkirrU3boSSgsR+OHQ4MqCw2nTyJd6qqmuk2WDL1rRekqioOTTsEsbKddO3fk5XjimQa7OejzAGh7OYQVrtJ71M8E+OdJEhi/4z4CjNV2r07vgDXUj97R5lPdvVZcEBiwhFsqop0FkXDIK3vkUQxTXudhLQmXf6mFnz+WcZPf7a02XBdxAF/SEYoKKe8T/F+HZIUXvHJREqbBMkn5wiP9D6zsdLG9z+CLGe/kJWJQDu/8znwxYFayzGSk0lZfWJaP2sT7Vo6rQXX/OFZEN2c3EzRK61Y/q73u1eOFw7SISx1k2Ha+kxgWxgjUy8T4oxYYZDCLSxTAYSdT2WqW3jDtzrDV5QV+RxSuy1tM9ERzU9raZsNDkaogWRurauXf2/T572gaSm77QX5uYrT4TCzfNaLxu5ZDaRi0IZIzg9mHs8ckjidyNTrA2P0oH/JvWmV5gzo7OjR4sDEWbm5COyDsLVLDf8ILL0nK1XcjD3SjA31PxIe2VLrAFYuf2f37i07xDHmKUGcK5t75UN5Aenbv77lrvMHapLjxRSgaEJeQDvnwj65N0OCpequVVk9nYH9fNxx+ev1Z6v7m/tangIgDAJTAoEtGn9c/qKUjDYkeL1ecayGpVZhOpSMsebu3bufkw5G5N21uqbAn+uIndydDO/g2tWPjypL69Yvh/t+BcBMiMSqWUYLW73OjIIkfENaufU7+7vnbtm1efsuEEzNfQHcmcO3j9ywvmTWsNNPi/kG0CvX73K+00EMvRnF/QfoedneNZ6lVTDHQDDzehh4u9VfDfSKFWlTS1sDjGmUiNKeuaIXEnwDt3a5qDidpg5G9fJ5V/52OOfq/WSuxtYSVPEvHTbTPaMgpUPgcru5xLVrOLEPxLnBFrDnb3jz/bP9Dc0YNGooupeYqjpbFLWTO4sDnu/9ZiBX1eWRo+bNJmZsHpY/mGrTZEFQuwRJ4PS/9NCG3Evv/TGJU4fMPWGIpS739g/WQ1wlleWo7N8HxeWlyM3Lg8vjxgevvjvGNX50n4oSU7N+C9ITkz/Y+OmwnAn3JSaaPK3bsrMoLyd1r1CA7q+p75cz4b60m5wmKE2TVmzZkyP2sdoSNCJnNnQ8P+fpbmdPuMiyih0bNo/MmXBfyi+XMV5OXJ0LwPRIMsbQ6HG4/pDqzcOyKvM+zQg+bUPDrVb7V8J3z7wRJWEz65yL776BgCethMmI044f2xxgjP0k+Nojz7aGynb3SHplwdd//7Rn/F21RHjOyvWMDmvfj2EOEF4IvfXHVgmRaE2HCZJAFnrjjy97vnPnaQT6GxB2lXIMc8wmzYQD6+ScghtN0jMmZRQkCXytcGRuiomlnnkbevvRnaiqOsO12juZET0AsI6cCZmSYSe2nwMMWB1inglYXpX1ANtYa5sXJI1ILONn3Zbr8mg3MEY3EOE0Szg746hyoHt+znNLqm58aPjAihQ1yWzPJO5cQYqyp6GhYdjeww2frNy4Ex9+9hV27a/BobpG+AMhNJuoeB5Vrv5XVU67iXBz7SuPPGblsY2IbisuLjZVgTayKuOnzQjcnnj/niXo3/MMXH9xqnc/IiouLm7/6dYNDQ1XMMbMD0cGviwqKurXnjZ8c8s+0u6mtWn3v9212gi+cRywBekb90qPToNsQTo6fP/G1WoL0jfulR6dBtmCdHT4/o2rtUtmbZksNYuKiuIGmO1gcSAQeCs319zSlvOIpW070H9jiyqKconT6TTV3hDrSN/YhtsNszlgc8DmgM0BmwM2B2wO2BywOWBzwObAfw0HumT3P5mb5Yu3DGAc54GxwYxYLxCJs8s0EGoYYSeHtLa6x9aPMGlSqg/mKpLKy7eequPMAX21b+qQBv25zfcqknpVbB/KQWeD4SQQKkBh5/AKwL6CRFskjVYcnDb4q7R1tJG+fn/Zk+MPtHxLx13WQ/ps26RBwi9ROPSct/14TdIsldolSVI1jWSAtdQ4A3WYPLJLzZ27ZB1JZ0b5n9YNZYw9Tr7Aebrdgn7XYfR7+YF+u9mfPry3+vbTE+zhSkrWdWOqO3bOqwx2vZX3NB1XpnvFrA8v41j/BzVAaU+kFrbLZbM+XE4Su7P21lGm9mJtpc/n9Z4kOSjmR6ruABOe2GLOrYgHZzGOH1i1RfBRX12u0CQVcza8yZj02OEZwy1PCLXC1Zb0LhOk0pnvTudB3ywry1wT4gcAeKHH7/79vaKC4A27fnlJROmqHuA5Bi9kVpJogjAlqWqJu4ezxzwt0BI5pSAFwDRBeIT7TunDK26uu+d8cSJBYmgrfYofPBTvgB0Uj4sKNL84pCbrxor3eqm4yn6/alnI7Zzc9KvRnbqw2CWCVHLfK7dTMPCYCRuEbfsagB0BqAgMZ4CQbLl7bZ2fhKmTsMEKewfUDMqgjNr4dZ64xFGs5S3hqv/yCN74fwb6hMA2E6iFgYnjncRxraVxCIiDdZ4puf8VT/1DE54wpIfp4wEDTYZoIlzSk98LTnqfAihJ5TRfk/DBl1Qoq8ernH4MKal67cL6qksyn86YFcpUoE4XpO53vHgRD/iFBl4siGPCucR+2fjoxLdiidFIya9eOFNj0mwGiDNhwQhvNXwFYf4cCXX14DnGA5LaxFx0Pw4zye+/PEm4X9GI394865rP9erCdyF0femHxNgfARjPDVtYfOeL2xoevXpNDL6uHlquwcA1qYIYXFJE8YnhjcHxR9IxHDwQCJ+IHC7G8DmIX52EojsjqS8YziFA5BnHU2Ls9Vqf25acsX/2pEBSuQ557FxBqlriptrgYjJ2yQyrHaHghMYnJpuaJdfPumYtqqrOLqztX8WA7zM5OAlLJyf08xSK84K14jh4nWPFU/53iBYK3JXwjonNbJr/k/t1mIT70klaA/DX3Jv/d4XLiTcBDIrmiy7kSVStGIKq82NHVFHQSF+Wgq74QcKHeTQwKdG4ksShS/G2BhrnXLdRh026/7XHXf9zhxr0PEKgaYa8wc1EDwG4w5DWYdF4X9phKOOICg54r6VQ4AQKBRG+gsHDXPFf2WAhRLGSVVW8ad5PH2hs4aNSYevBg4HYpQVbYw8aqUFVQ/dTKCjF6JKD/7AUohhRQGDRTw8wOXg5hYLNsbKh4Mnd9u9I8NjBQwHol6ZYH8BnQA3F54uVEWURTDwyjAd8sTaL9qcLtX+8sdk758fTtaD/ASOveCg4I3/q/E6x6ulUQaJQ8DqSQ4hdqvxgy6IpWfnbCTPqmetTpaQecXxyCIgcj5aOr4l5E6vcJAeviNEkhxQ1GBSed7MKjU9M3k1KcJahPJgi/yxWuL4+8qOJ/niQraArStpyJMuJ7Y5VaB1peeKmmSSH1hpodUs+/hPrEm3P6URBIsaV0BguhxC9lBaf9Ne2kxovyeUgjFc8J3MsF4VlXA45DXS9Efi/W7J3cS/mTiFayOUQ6Tg0OSQG47HAlRBiV7be62RfvIwSSpE/Y3t5KPX3Fas8IcJICwUeM5bVlOB3EkA66KHzBGniUokU2U2KjPClyhvCnt7aSXg96hH5dYpfqAxNad26myb5CmM0hWlTVrWWJN/zt1aTomw14MlFVVWYl6n0ZfnSRY8U7nUi7QISyxnzSE0xP7NsQkDyv51QVg7FFnMtC7Uho/MG2zXbGRXFB4/E0DFTT/Fp88QWfMW8rnXNDqguwyxbzKjbRBep8n6ABscqX8mEIHFxdkEifTGI9BEhSEaIpN8HV+VYS1ksZixgEX+uqomu+I1QHNRPHKqwgGxXcucJkiBLjU1kxAeh4+oy4mUJ7M+CGQqgxoWPYBSrLIrrIGrSmy77PE6IMS9b+mQZkOJ0wZkwUQU0BWjrmpmqGqeASYj1BrXv3nEvN5mOsm1EzQnnrbTJe1kyWqAOpMbXaVji7zgVPDklSDJJcQEn0AnJINtc364AABDeSURBVNk8k6b0Tah60CDC0qWAy09kWE1kCPdUGVHKjDskLS6LzCDs4cKKGqsuDpURLfK+e09PTVXyDZBpz2IxwLUq2nljpKVLOWlayOCLeyjG3m7hq7BVNCf4g9Yszta1wuhkgUYDTWAab/XgM//CWypI1U414GmAOBFBBMrTSFURuxS5xIoWY7ojpBbHyqgqGJcTeg5DXeH2G8umi2ta6KqksrF9ynTlWpvXeYIkls80bZXhWALJzbTwnlZriUyB11QgdhlWg1MAUxP8hUV10DRZp4s07VzXRb8angppnaIQm6KXD99VLb5Cr9ZpxrPsiKx9PBprINIGGctJgkZjiLVXtD1BxoxQifGLZxQSqb+O8yos4PFdgkTodj11piCBkfoMcXEGTuy623PBDLEZm1VwXzjD9IgpcTqTfoWZnxW2KNDSKpk07SUDTYCqzMeIm0zdPCej9px/y7eJa7cby3OmxU9den2uwG/oidUJGHeH8dOSjDL8zDU1oefwOVyGnWlxzly8zaLujOHiGR53AC+SxnvqvCKNNynE/paxbBsAOlWQZOqxBMR3xE4IAC/kGl+eO3ZGn7S0Dqpyu86bPp9UvsU9dupVCbBiD9t4MHO2v04DEkY0Myrdusf7s1zdXM9hUJWpSY5eNGfMlBM4V18F8W6xNpH2vvrvufEeSWzRk/Z+PJ8XueTAH3QcZnf32GnXgPgYQ5kv8dZjiUviYntEb7c4BjZNcI2dOszl46uI8/GxMuLHR/xhrPxz4pJ5GjytyTJME1pTLHtY55ipYxjDewZ1GVFYnAZwr6J6/or/zI6v90+c6HAcKr9YAj0aVi6LVKOAS6OVNfMiPrTPmFHocqvGfbq5DPh3NhTJlbXLsXRp+OfsHjPlIWIs2XvpNk7sbq3nkdd1uDDei27q7gq6fgpGwgF9saEun0TstNDq+QmbvO5zp/6ACH83wIllir8obuev8e7jkdP0ROZZt+U6nfJ0BvqdUb2GiG5XVy8UKjex4Bwz5e+MxfSR9jIgcTWeWC6X+PGMs3FgEA5VE98tYYXSs/Y7Ce2KYW9/JLGy9uMzxeAaM+UmAItNMsWvbhMR1TCGbmBsGChBXSNchIiPU9csfjv8IATJpRgFyQSteZLiDOZi5TORlb6JEx2uQz2eBzDRBFr0e1sIaGKAWHcRDleTeyuFEy7X1ix83aQ8c42Z+iZAyQN5DsImxnCYGCsCkcAbn4JGEG1VCpwj8frchFXHsCCBLBXbTGgwJq1RcvileOeJNvHNiMgq3qmfNr1SZfXCJwD6CUBiCzuqoBW+5wM0mjEInaALQVSamA8fI7o6JkQ6wgQURnQZ4np5cV+6VFN61v4QECcxpiAsAeg8BnGsKZ0OkDsJppokfpGFEAnspICuBgmNxwTcEhgNJ9DFIH4WQLlJ+V84nHRZshCFyU5Ak6GdcVgNRH9WnMGLOlOIBH1dIkiiImX1omclTRpMRP8CkTgcLKIWYX4XAMsckjJYXrNoSZiRCf/inEp6ERk4nIAkIkyrF91KwPkgWpeBJkFvEJweVzTlFHXV4vRbK6sXNijB8LhnNojkDLg1cP60oimjgisXxdRrE6kV46Ks290AoieYREOUNYtvi/XCiQg79KlLPm0pFJ9zfZmDu0dJRAMZWBkYcjhRC2NSHYe2W3Oxj7DyidqUcpEEhtE3p5zkYwGbmPzBIjE2E2/DNOSMnnY8dyjDiUv9GVEJGDwc1MwYqyaOz9QQrceGJwz6maZoUhPHXZfv8uWdzMBOIEZF4PCAMZkxaiKS9ihB7VNsyPDZGXFTd3gkc0/zeo0SU6DlBBPGnXqefbc5YHPA5oDNAZsDNgdsDtgcsDlgc8DmgM2B/2IOdO70vypLpbHfgMAsNMAWr3cVA3m5KHBJDpUrbjVU/ZOhiftQxhdIxPBgdHugymgoZgQSiwBRuOS6dZrTlRWozOD0tKSqkFxHcr541ukxyzMrb4Q3yzfiMYGtnPNxmcPh6a05IBOptdVThh4R/gd8fr+zZtqghA1jIyqreOcJUtUKZ0VZaZIaoTkZDPTY4WlDE87NLZ/98VnMgYdBJBTrk+lsIOBxJjsfq74zUagq5296lMDuAOjL6mlDLT39V8zfLGzizwNhe/X0IWE7tfIFW4YworC+jsBxZNrgP5lRXDZ/2zAJWsROX6IK8RIEXMX8zZZrVADEZulbYDS7eurQtcl4K+dv+juBpdsCET+elzmkB2qmnbqrYsGmM0HsP1E866prBo+GmfAvWeKoqDlpHYARApZJWv/DU4bvFYIEp3MKJMcyaHwSk9h/iNNZkLDy8JTBrfYX0HkaksJevTFbc/NEOSmb+c493Ncw00SAdP6LjVOxgXpN0cx3x3vvuzB8NLzIVJu90SXHRJx6Qf2uNZk4MGlogBbvxP5Q9vDKD2vuGZt6+LD3CLSo+bTDFdc+ydBecZLjJHH1eHjlk0Xd/DNi/gzCvGqwXimNEC1UUX4I4LLSmasurJ4yZF3ZI++9TYDYzzuj1PPezXXAAr19+r3HrvJbNNSHhQiEp2rvHRteOT/8y9NqKudv3nf45kHbK+dv3suBb0sS7bQ431ZHZ3nvXEFqie0R/osBVpahgriYyXPJXf+8UW1uFrvhImgg9qQE+ovsoN0SUXeJO8YDdDcY+gI4iYFeLatacmZN1aRwd8ybG9OsXUexii9Jc2PKi1N8CcIl9JxfLLvz1dNqHr00QT1V8XnBortLQVdcN0jT28uwiRFeErURmBOMyolwHgN0tzW/qGthlaiqukLXrBRlw90ZwyZi/FadUokzDzHWHxQ+gXxM9FDFxWBsuHzfy5MlTlsACCH7fY+7/vVS7R8vP6iXLbr7n/20lkZhXSvCQeZ23R6Nh2/EHOFfurhXTxn0TI+5n/VyuZQ2nQzeqYLEW5rDBDNiLzXM/dEzxkaYxfNufbanFmieE81Tieh7TfN+atxdrwOwoPuU556HUxOm06OE+bTsJ6EOcrcop/mEILHUj2FShao/ApfQbwlr10Rdn54y6AVMXHIhlhp8Nfn94Lp5tRzXDdLbS8DGpjk/Fj1mQiia8dwPSKKnQWG7/Mu6Y8ANjYA4Ph6arzn6AyBv45zrUj8tE5c82b1Sfh8MZwlthILb/9ajceb39hTOeO4exsTGMwo4mOBdTH+L/L7FBOQJ/ER0U9Pc6xLcUFdPHfSyyNPvtTNOEkIYE8QE4jM8dOKm7Xvg/pbwpQUT6LckSWppma75fXnChYvm881MEqJYucaFP2qQW5qv1Py+oIBV/b7pmLgkbCnBhTCIen3W43GBiPuitPnj40rZ543RzH0t6yL0+84rKGp4OFa52IDW6wiXjdejt5f8kR+QsYyIe+f+6B+ar/kKHU7z+2K+BnR6RJ5pWDpJ4wHfy3pZ5m0IH3rcVLpzHve3rImkN19Z8IuFE0T5whsXXsf9LePC6QHfs01zr3vVFG8HJXaeIK0EtIAvfHFfdvucPOC/jAf94EG/JjUHxCnQliHwl2lf8YDveQFPwUB+Qe6RMGPDNvIBP8Q9XRDePXgYzkCbL0KvoFsONF2nBfz7w23wt9yVd+2suPsbAxx88Revt1fcrULLU1NWagHfWwKGB3zHYeKssD6SFvBHeBU00JOERAu09NPrUH0tEUBhdOBr+bkW9IXCeX7/gpxrZvdTg/5ZUdhqhJREJbgkvB3x2KmfNuFoQQQC7s696ndx+3gj5cQOB/5+zzVhuFBAH0Nsb1p2X8aROldCqxiR8NgGlQXCvm4iDhYSPljG2mJx3RFDAqSwdI0m5KpKTUhyToTEhLqIGFE/47n6oZGhF+/fDeE5RJ+fGXT09fZmMpEiObQCROMEMd3dUk4jENA9rOhoY4SGI8Ryr/rdVRQK6jw8EDhViY3bmp+/47Ocqx7+bVTTsi8D1pOqKwiyqc3LbhdDgk4NnStIcszs+NsAxJUaGGIzLpJDUTUJZv5tSCrNQoGm2CxD5eG2cEVB2NVNgoQkFRTCLRxQRIQ8lqkoMiRExzySA4F/3LfWc/n9dzCCGIMUScDfMfG2syCsYnUFfEe8U6doexPc+MSwxyM8FPDq5HFNDSMQ9vnRtJG537s/YWJCeKAnySiPYuAS0XRUzYwPzgAEa+U/egq1qxggLGLCTsEIWBZ6+aF/xGvuvFgnC1LMokboZccekpoTG0CRLItpnnAQlWBZmQQfe+SKfJzehTjIEcbP5IAWES79VcXAEyLCbj86uo2rtMo+ECJGqd6cSHLoXw/NyRn//84i4BoChroV93yZ+eZIPCpAukCFhTPSxJhwJ9RoeAjJ39Z7vmaWG1lrE71hBCRf1GOANkZ3AdIt/jceSTUpWlml8kvuvEHi0kfR09MbXUyaGm+cEU3HxztXkNQoYxmbqrw9K+OsjWvKh4zC6yI9nRf8arT671kfpGsyl5XLWHQ9xy9pYYHkitISnbL1xNgqJ1ZWxc1qDchIkXuLR4osFEZyRE/DotN5g61FMNj8C7fTI16ucFZ6vfAcS4iiNTjEomh79a7FUF08OnZqN9Lk8Kc87CtAzQ2PdWJlgQNEWBYuIIXX+8Xx6WJ6H3CAXxh453FLzynKa49udF902wEAx4Owt+Xd2UKRr0tCvF/uhOp0y1HxKcgqqMoLujGfxJXH0tmaeS6YcRnj2gVheK5+opvvkKp9FcXh9rD68DgkuW7P2NsGQlPFBUlTPzXmx2g2Jq5c0AJSryJV9Yl8qMoMHa4F8cG2nkZW7R1xk8vFnE+TqlYKWK6qr+qCzoVJdsRCd5fy7uxbw9fbs28hTftVND1XVbRnMXGi0Y7fSGU4HqNBGFR2YehUQYrZVGVpe6ZQ6f+Rpn0WNjHm/CxnvuvvOGdGilqt+5zpV3KVP6+bInOu3qPzjHH+cSxd0x5PsaEbO7Ub56GndRgyWskKRw667ZiOMHqX3527nXG6MZavw8XlKF5WX2PScQyqcjvOm36xK9/1PjRtYhSHLCn81zpIePQexpk43FZWzHmCOF8eLkN0rrO6LGErKVZej2g8Qkfiepie22n3Tv20hRsvSM/WG+vKKpWdM/lKgiT2kAoZcJkLfBfOuflVYmy7RCgk4AKCNkIfUDBGv1NWLnpD55C8at4W1zlThJ3bBQAGqhr/zHXOlGWMsb1EVM40+j4BldHx0SbZUZG4viJepggmPzF51bwXXWdPEQ7dZ+j1Jdz1ssBVrjFThG2Z+Mp5CNVl0KKDr0gBlYH9TF6zYFusvGGsFUuLRlSSbnSRuhVAOQMeco2Z8rayeuGGZLjwsxCgRFk0BevoRBN2dWAVuoWI+JVkGeT3F28H8XNB9GnU8qIQhB8yTg8R0Z0gGhFNDxJwl7xqUbKRIxRZuRZE66Nw+SD6KXH+GxBNIaLKaPqnDgddoX9aYuTpNMcSEiOKt/YOEOIWJ8ZsvSyRqPN4cUXrc0TrFJa9G4lwgbx6gbCpiwfx8kX55N5MQLw/t4aDbozicILwNwyamGxnF8EVpyGOuwtindcjrQTHaPqlaAPjDrH7nHVQVi/ehLFVQ1zy4R8zhkkEjARIfOICAO1kkF5zqOr8wLqnzJ1krXuqWhk08WxXUckNIHYNWNiRg1iwFAPyLWBYqgToSSXJIkQOSQdcTjVMM3K1uAWwkfJtS2Vl7M/Gu1T3j8CZBMqLL1VQpL1G8HCcUYARq2ES/zS0+smd+nTRCEdQn2IkrWQSM22TtnrRKxh90wQJ7ATR5eR0L+8ZRHzpJI5L/GBYIQPrsoG2qPv/AxKoa7GH1PhyAAAAAElFTkSuQmCC diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java index 3b798ce734e..6baa21cdffb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java @@ -65,7 +65,6 @@ public static Object[] setUpContext() throws Exception { if (System.getProperty("spring.profiles.active")!=null) { mockEnvironment.setActiveProfiles(StringUtils.commaDelimitedListToStringArray(System.getProperty("spring.profiles.active"))); } - mockEnvironment.setProperty("login.invitationsEnabled", "true"); webApplicationContext.setEnvironment(mockEnvironment); webApplicationContext.setServletContext(new MockServletContext()); new YamlServletProfileInitializerContextInitializer().initializeContext(webApplicationContext, "uaa.yml,login.yml"); From 75d19644e5bee3818ecdd648ba4270d2e1f9a4fb Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 17:17:03 -0700 Subject: [PATCH 15/87] Add selfServiceLinksEnabled to zone configuration https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../provider/UaaIdentityProviderDefinition.java | 9 --------- .../identity/uaa/impl/LoginServerConfig.java | 16 ++-------------- .../impl/config/IdentityProviderBootstrap.java | 12 +----------- .../IdentityZoneConfigurationBootstrap.java | 7 +++++++ .../identity/uaa/login/LoginInfoEndpoint.java | 4 +++- .../IdentityZoneConfigurationBootstrapTests.java | 13 +++++++++++++ .../uaa/login/LoginInfoEndpointTests.java | 8 +++++--- uaa/src/main/webapp/WEB-INF/spring-servlet.xml | 2 +- .../identity/uaa/login/BootstrapTests.java | 6 +++++- .../identity/uaa/login/LoginMockMvcTests.java | 9 +++++---- 10 files changed, 42 insertions(+), 44 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java index a6d11a2889c..0d98438ccad 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java @@ -20,8 +20,6 @@ public class UaaIdentityProviderDefinition extends AbstractIdentityProviderDefin private PasswordPolicy passwordPolicy; private LockoutPolicy lockoutPolicy; private boolean disableInternalUserManagement = false; - private boolean selfServiceLinksEnabled = true; - public UaaIdentityProviderDefinition() { } @@ -59,11 +57,4 @@ public void setDisableInternalUserManagement(boolean disableInternalUserManageme this.disableInternalUserManagement = disableInternalUserManagement; } - public boolean isSelfServiceLinksEnabled() { - return selfServiceLinksEnabled; - } - - public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { - this.selfServiceLinksEnabled = selfServiceLinksEnabled; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java index 26751d75965..2c98ecd784f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java @@ -1,34 +1,22 @@ package org.cloudfoundry.identity.uaa.impl; +import org.cloudfoundry.identity.uaa.account.AccountCreationService; +import org.cloudfoundry.identity.uaa.account.AccountsController; import org.cloudfoundry.identity.uaa.message.EmailService; import org.cloudfoundry.identity.uaa.message.MessageService; import org.cloudfoundry.identity.uaa.message.NotificationsService; -import org.cloudfoundry.identity.uaa.account.AccountCreationService; -import org.cloudfoundry.identity.uaa.account.AccountsController; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotatedTypeMetadata; @Configuration public class LoginServerConfig { @Bean - @Conditional(CreateAccountCondition.class) public AccountsController accountsController(AccountCreationService accountCreationService) { return new AccountsController(accountCreationService); } - public static class CreateAccountCondition implements Condition { - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return !"false".equalsIgnoreCase(context.getEnvironment().getProperty("login.selfServiceLinksEnabled")); - } - } - @Bean public MessageService messageService(EmailService emailService, NotificationsService notificationsService, Environment environment) { if (environment.getProperty("notifications.url") != null && !environment.getProperty("notifications.url").equals("")) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java index 0e9e88b3bb3..b9ccd37da35 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java @@ -55,7 +55,6 @@ public class IdentityProviderBootstrap implements InitializingBean { private PasswordPolicy defaultPasswordPolicy; private LockoutPolicy defaultLockoutPolicy; private boolean disableInternalUserManagement; - private boolean selfServiceLinksEnabled = true; public IdentityProviderBootstrap(IdentityProviderProvisioning provisioning, Environment environment) { if (provisioning==null) { @@ -213,8 +212,6 @@ protected void updateDefaultZoneUaaIDP() throws JSONException { internalIDP.setConfig(identityProviderDefinition); String disableInternalAuth = environment.getProperty("disableInternalAuth"); internalIDP.setActive(!getBooleanValue(disableInternalAuth, false)); - String selfServiceLinksEnabled = environment.getProperty("login.selfServiceLinksEnabled"); - identityProviderDefinition.setSelfServiceLinksEnabled(getBooleanValue(selfServiceLinksEnabled, true)); provisioning.update(internalIDP); } @@ -244,18 +241,11 @@ public void setDefaultLockoutPolicy(LockoutPolicy defaultLockoutPolicy) { } public boolean isDisableInternalUserManagement() { - return disableInternalUserManagement; + return disableInternalUserManagement; } public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { this.disableInternalUserManagement = disableInternalUserManagement; } - public boolean isSelfServiceLinksEnabled() { - return selfServiceLinksEnabled; - } - - public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { - this.selfServiceLinksEnabled = selfServiceLinksEnabled; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index 63d43270e87..86110d910c1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -22,6 +22,8 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private TokenPolicy tokenPolicy; private IdentityZoneProvisioning provisioning; + private boolean selfServiceLinksEnabled = true; + public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { this.provisioning = provisioning; @@ -31,6 +33,7 @@ public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) public void afterPropertiesSet() { IdentityZone identityZone = provisioning.retrieve(IdentityZone.getUaa().getId()); IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); + definition.getLinks().getService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); identityZone.setConfig(definition); provisioning.update(identityZone); } @@ -38,4 +41,8 @@ public void afterPropertiesSet() { public void setTokenPolicy(TokenPolicy tokenPolicy) { this.tokenPolicy = tokenPolicy; } + + public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { + this.selfServiceLinksEnabled = selfServiceLinksEnabled; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 7fb74652a91..77f484e3f06 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -29,6 +29,7 @@ import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.dao.EmptyResultDataAccessException; @@ -488,9 +489,10 @@ public String generatePasscode(Map model, Principal principal) } protected Map getLinksInfo() { + IdentityZone zone = IdentityZoneHolder.get(); IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; - boolean selfServiceLinksEnabled = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isSelfServiceLinksEnabled() : true; + boolean selfServiceLinksEnabled = (zone.getConfig()!=null) ? zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled() : true; Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl())); if (getBaseUrl().contains("localhost:")) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 83366fbe2f3..9b0b3529a53 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -14,6 +14,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /******************************************************************************* * Cloud Foundry @@ -78,4 +79,16 @@ public void tokenPolicy_configured_fromValuesInYaml() throws Exception { assertEquals(PUBLIC_KEY, definition.getTokenPolicy().getKeys().get(ID).getVerificationKey()); assertEquals(PRIVATE_KEY, definition.getTokenPolicy().getKeys().get(ID).getSigningKey()); } + + @Test + public void disable_self_service_links() throws Exception { + IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); + bootstrap.setSelfServiceLinksEnabled(false); + bootstrap.afterPropertiesSet(); + + IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); + assertFalse(zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + } + } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index 62e2414a6c1..e71ee711c1c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -14,6 +14,7 @@ import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; @@ -203,11 +204,12 @@ public void check_links_urls(IdentityZone zone) throws Exception { @Test public void no_self_service_links_if_self_service_disabled() throws Exception { + IdentityZone zone = MultitenancyFixture.identityZone("zone","zone"); + zone.setConfig(new IdentityZoneConfiguration()); + zone.getConfig().getLinks().getService().setSelfServiceLinksEnabled(false); + IdentityZoneHolder.set(zone); LoginInfoEndpoint endpoint = getEndpoint(); endpoint.setLinks(linksSet); - UaaIdentityProviderDefinition uaaIdentityProviderDefinition = new UaaIdentityProviderDefinition(); - uaaIdentityProviderDefinition.setSelfServiceLinksEnabled(false); - uaaProvider.setConfig(uaaIdentityProviderDefinition); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 34aea641400..451d1f4d958 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -392,7 +392,6 @@ - @@ -402,6 +401,7 @@ + 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 a579bf23f88..d1d6ff14435 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 @@ -41,6 +41,7 @@ import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; @@ -252,11 +253,14 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); + assertFalse(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertTrue(uaaIdp.getConfig().isDisableInternalUserManagement()); assertFalse(uaaIdp.isActive()); - assertFalse(uaaIdp.getConfig().isSelfServiceLinksEnabled()); + IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); 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 b8beb31aff7..502ad07f6fc 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 @@ -36,6 +36,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -166,10 +167,10 @@ protected void setDisableInternalUserManagement(boolean disabled) { } protected void setSelfServiceLinksEnabled(boolean enabled) { - IdentityProviderProvisioning provisioning = webApplicationContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); - uaaIdp.getConfig().setSelfServiceLinksEnabled(enabled); - provisioning.update(uaaIdp); + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(IdentityZone.getUaa().getId()); + uaaZone.getConfig().getLinks().getService().setSelfServiceLinksEnabled(enabled); + provisioning.update(uaaZone); } @Test From b65f278f394c6e55c52158155f7e96616d7221f4 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 17:34:48 -0700 Subject: [PATCH 16/87] Added login.homeRedirect to zone configuration https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../identity/uaa/home/HomeController.java | 4 +- .../IdentityZoneConfigurationBootstrap.java | 10 ++++ ...entityZoneConfigurationBootstrapTests.java | 46 ++++++++++++++----- .../uaa/login/HomeControllerViewTests.java | 34 +++++++------- .../main/webapp/WEB-INF/spring-servlet.xml | 3 +- .../identity/uaa/login/BootstrapTests.java | 8 ++++ .../test/bootstrap/bootstrap-test.yml | 1 + 7 files changed, 75 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java index b98132ea3d4..37a5a9709f1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java @@ -17,6 +17,7 @@ import org.cloudfoundry.identity.uaa.client.ClientMetadata; import org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -91,7 +92,8 @@ protected void populateBuildAndLinkInfo(Model model) { @RequestMapping(value = { "/", "/home" }) public String home(Model model, Principal principal) { - String homePage = environment.getProperty("login.homeRedirect"); + IdentityZoneConfiguration config = IdentityZoneHolder.get().getConfig(); + String homePage = config!=null?config.getLinks().getHomeRedirect() : null; if (homePage != null) { return "redirect:" + homePage; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index 86110d910c1..a60d6c11422 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -23,6 +23,7 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private TokenPolicy tokenPolicy; private IdentityZoneProvisioning provisioning; private boolean selfServiceLinksEnabled = true; + private String homeRedirect = null; public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { @@ -34,6 +35,7 @@ public void afterPropertiesSet() { IdentityZone identityZone = provisioning.retrieve(IdentityZone.getUaa().getId()); IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); definition.getLinks().getService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); + definition.getLinks().setHomeRedirect(homeRedirect); identityZone.setConfig(definition); provisioning.update(identityZone); } @@ -45,4 +47,12 @@ public void setTokenPolicy(TokenPolicy tokenPolicy) { public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { this.selfServiceLinksEnabled = selfServiceLinksEnabled; } + + public void setHomeRedirect(String homeRedirect) { + if ("null".equals(homeRedirect)) { + this.homeRedirect = null; + } else { + this.homeRedirect = homeRedirect; + } + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 9b0b3529a53..212f5e8cf62 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.config; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; @@ -15,19 +27,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] 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. - *******************************************************************************/ public class IdentityZoneConfigurationBootstrapTests extends JdbcTestBase { public static final String PRIVATE_KEY = @@ -91,4 +92,25 @@ public void disable_self_service_links() throws Exception { assertFalse(zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled()); } + @Test + public void set_home_redirect() throws Exception { + IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); + bootstrap.setHomeRedirect("http://some.redirect.com/redirect"); + bootstrap.afterPropertiesSet(); + + IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); + assertEquals("http://some.redirect.com/redirect", zone.getConfig().getLinks().getHomeRedirect()); + } + + @Test + public void null_home_redirect() throws Exception { + IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); + bootstrap.setHomeRedirect("null"); + bootstrap.afterPropertiesSet(); + + IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); + assertNull(zone.getConfig().getLinks().getHomeRedirect()); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java index d9a862947e1..4f55a1f4f78 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java @@ -8,6 +8,7 @@ import org.cloudfoundry.identity.uaa.home.TileInfo; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; @@ -64,18 +65,23 @@ public class HomeControllerViewTests extends TestClassNullifier { private MockMvc mockMvc; + private IdentityZoneConfiguration originalConfiguration; + @Before public void setUp() throws Exception { SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .build(); + originalConfiguration = IdentityZoneHolder.get().getConfig(); + IdentityZoneHolder.get().setConfig(new IdentityZoneConfiguration()); } @After public void tearDown() { SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); + IdentityZoneHolder.get().setConfig(originalConfiguration); } @Test @@ -102,33 +108,27 @@ public void tiles_notVisible_onOtherZoneHomepage() throws Exception { mockMvc.perform(get("/")).andExpect(model().attributeDoesNotExist("tiles")); } - @Test - public void testInviteLink() throws Exception { - mockMvc.perform(get("/home")) - .andExpect(xpath("//*[text()='Invite Users']").exists()); - } - @Test public void testConfiguredHomePage() throws Exception { mockMvc.perform(get("/home")) .andExpect(status().isOk()); String customHomePage = "http://custom.home/page"; - environment.setProperty("login.homeRedirect", customHomePage); - + IdentityZoneHolder.get().getConfig().getLinks().setHomeRedirect(customHomePage); mockMvc.perform(get("/home")) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", customHomePage)); -// IdentityZone zone = MultitenancyFixture.identityZone("zone","zone"); -// IdentityZoneHolder.set(zone); -// mockMvc.perform(get("/home")) -// .andExpect(status().isOk()); -// -// zone.getConfig().getLinks().setHomeRedirect(customHomePage); -// mockMvc.perform(get("/home")) -// .andExpect(status().is3xxRedirection()) -// .andExpect(header().string("Location", customHomePage)); + IdentityZone zone = MultitenancyFixture.identityZone("zone","zone"); + zone.setConfig(new IdentityZoneConfiguration()); + IdentityZoneHolder.set(zone); + mockMvc.perform(get("/home")) + .andExpect(status().isOk()); + + zone.getConfig().getLinks().setHomeRedirect(customHomePage); + mockMvc.perform(get("/home")) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string("Location", customHomePage)); } @Configuration diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 451d1f4d958..5073e8e0ed6 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -402,7 +402,8 @@ - + + 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 d1d6ff14435..8d86aad8fe5 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 @@ -92,6 +92,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -159,6 +160,11 @@ public void testRootContextDefaults() throws Exception { assertSame(UaaTokenStore.class, context.getBean(AuthorizationCodeServices.class).getClass()); + IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); + assertTrue(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + assertNull(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); + + //check java mail sender EmailService emailService = context.getBean("emailService", EmailService.class); Field f = ReflectionUtils.findField(EmailService.class, "mailSender"); @@ -255,6 +261,8 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); assertFalse(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + assertEquals("http://some.redirect.com/redirect", zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); + IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index c5186daddbb..e9642a69dd4 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -18,6 +18,7 @@ login: url: https://login.some.test.domain.com:555/uaa entityBaseURL: https://login.some.test.domain.com:555/uaa selfServiceLinksEnabled: false + homeRedirect: http://some.redirect.com/redirect smtp: host: '' From 1a482b72f4fe0abdac3ce43ca076bacde3492993 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 8 Feb 2016 18:01:37 -0700 Subject: [PATCH 17/87] Bootstrap and zonify login.signup and login.passwd https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../identity/uaa/zone/IdentityZone.java | 6 ++- .../IdentityZoneConfigurationBootstrap.java | 21 ++++++++ .../identity/uaa/login/LoginInfoEndpoint.java | 24 +++------ ...entityZoneConfigurationBootstrapTests.java | 42 +++++++++++---- .../uaa/login/LoginInfoEndpointTests.java | 30 ++++------- .../main/webapp/WEB-INF/spring-servlet.xml | 2 +- .../identity/uaa/login/BootstrapTests.java | 12 +++-- .../identity/uaa/login/LoginMockMvcTests.java | 54 +++++++++---------- .../test/bootstrap/bootstrap-test.yml | 4 ++ 9 files changed, 116 insertions(+), 79 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 9963f9ab6f7..8cd97992b1b 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -19,6 +21,8 @@ import java.util.Calendar; import java.util.Date; +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class IdentityZone { public static final IdentityZone getUaa() { Calendar calendar = Calendar.getInstance(); @@ -40,7 +44,7 @@ public static final IdentityZone getUaa() { @NotNull private String subdomain; - private IdentityZoneConfiguration config; + private IdentityZoneConfiguration config = new IdentityZoneConfiguration(); @NotNull diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index a60d6c11422..730343b5202 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -18,12 +18,17 @@ import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.springframework.beans.factory.InitializingBean; +import java.util.Map; + +import static org.springframework.util.StringUtils.hasText; + public class IdentityZoneConfigurationBootstrap implements InitializingBean { private TokenPolicy tokenPolicy; private IdentityZoneProvisioning provisioning; private boolean selfServiceLinksEnabled = true; private String homeRedirect = null; + private Map selfServiceLinks; public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { @@ -36,10 +41,22 @@ public void afterPropertiesSet() { IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); definition.getLinks().getService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); definition.getLinks().setHomeRedirect(homeRedirect); + if (selfServiceLinks!=null) { + String signup = selfServiceLinks.get("signup"); + String passwd = selfServiceLinks.get("passwd"); + if (hasText(signup)) { + definition.getLinks().getService().setSignup(signup); + } + if (hasText(passwd)) { + definition.getLinks().getService().setPasswd(passwd); + } + } identityZone.setConfig(definition); provisioning.update(identityZone); } + + public void setTokenPolicy(TokenPolicy tokenPolicy) { this.tokenPolicy = tokenPolicy; } @@ -55,4 +72,8 @@ public void setHomeRedirect(String homeRedirect) { this.homeRedirect = homeRedirect; } } + + public void setSelfServiceLinks(Map links) { + this.selfServiceLinks = links; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 77f484e3f06..ad880ea0de1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -108,8 +108,6 @@ public class LoginInfoEndpoint { private Properties buildProperties = new Properties(); - private Map links = new HashMap<>(); - private String baseUrl; private String externalLoginUrl; @@ -493,6 +491,8 @@ public String generatePasscode(Map model, Principal principal) IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; boolean selfServiceLinksEnabled = (zone.getConfig()!=null) ? zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled() : true; + String signup = zone.getConfig()!=null ? zone.getConfig().getLinks().getService().getSignup() : null; + String passwd = zone.getConfig()!=null ? zone.getConfig().getLinks().getService().getPasswd() : null; Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl())); if (getBaseUrl().contains("localhost:")) { @@ -508,13 +508,13 @@ public String generatePasscode(Map model, Principal principal) model.put(FORGOT_PASSWORD_LINK, "/forgot_password"); model.put("passwd", "/forgot_password"); if(IdentityZoneHolder.isUaa()) { - if (hasText(links.get("signup"))) { - model.put(CREATE_ACCOUNT_LINK, links.get("signup")); - model.put("register", getLinks().get("signup")); + if (hasText(signup)) { + model.put(CREATE_ACCOUNT_LINK, signup); + model.put("register", signup); } - if (hasText(links.get("passwd"))) { - model.put(FORGOT_PASSWORD_LINK, links.get("passwd")); - model.put("passwd", links.get("passwd")); + if (hasText(passwd)) { + model.put(FORGOT_PASSWORD_LINK, passwd); + model.put("passwd", passwd); } } } @@ -535,14 +535,6 @@ public void setUaaBaseUrl(String baseUrl) { } } - public Map getLinks() { - return links; - } - - public void setLinks(Map links) { - this.links = links; - } - public String getBaseUrl() { return baseUrl; } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 212f5e8cf62..646e245998e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -20,6 +20,7 @@ import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +import org.junit.Before; import org.junit.Test; import java.util.HashMap; @@ -57,13 +58,18 @@ public class IdentityZoneConfigurationBootstrapTests extends JdbcTestBase { public static final String PASSWORD = "password"; public static final String ID = "id"; - - + private IdentityZoneProvisioning provisioning; + private IdentityZoneConfigurationBootstrap bootstrap; + private Map links = new HashMap<>();; + + @Before + public void configureProvisioning() { + provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); + } @Test public void tokenPolicy_configured_fromValuesInYaml() throws Exception { - IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); - IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); TokenPolicy tokenPolicy = new TokenPolicy(); KeyPair key = new KeyPair(PRIVATE_KEY, PUBLIC_KEY, PASSWORD); Map keys = new HashMap<>(); @@ -83,8 +89,6 @@ public void tokenPolicy_configured_fromValuesInYaml() throws Exception { @Test public void disable_self_service_links() throws Exception { - IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); - IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); bootstrap.setSelfServiceLinksEnabled(false); bootstrap.afterPropertiesSet(); @@ -94,8 +98,6 @@ public void disable_self_service_links() throws Exception { @Test public void set_home_redirect() throws Exception { - IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); - IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); bootstrap.setHomeRedirect("http://some.redirect.com/redirect"); bootstrap.afterPropertiesSet(); @@ -105,12 +107,32 @@ public void set_home_redirect() throws Exception { @Test public void null_home_redirect() throws Exception { - IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); - IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); bootstrap.setHomeRedirect("null"); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); assertNull(zone.getConfig().getLinks().getHomeRedirect()); } + + @Test + public void signup_link_configured() throws Exception { + links.put("signup", "/configured_signup"); + bootstrap.setSelfServiceLinks(links); + bootstrap.afterPropertiesSet(); + + IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); + assertEquals("/configured_signup", zone.getConfig().getLinks().getService().getSignup()); + assertEquals("/forgot_password", zone.getConfig().getLinks().getService().getPasswd()); + } + + @Test + public void passwd_link_configured() throws Exception { + links.put("passwd", "/configured_passwd"); + bootstrap.setSelfServiceLinks(links); + bootstrap.afterPropertiesSet(); + + IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); + assertEquals("/create_account", zone.getConfig().getLinks().getService().getSignup()); + assertEquals("/configured_passwd", zone.getConfig().getLinks().getService().getPasswd()); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index e71ee711c1c..ae65879ba1b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -57,34 +57,35 @@ public class LoginInfoEndpointTests { public static final String HTTP_LOCALHOST_8080_UAA = "http://localhost:8080/uaa"; private UaaPrincipal marissa; private List prompts; - private Map linksSet = new HashMap<>(); private ExtendedModelMap model = new ExtendedModelMap(); private SamlIdentityProviderConfigurator mockIDPConfigurator; private List idps; private IdentityProviderProvisioning identityProviderProvisioning; private IdentityProvider uaaProvider; + private IdentityZoneConfiguration originalConfiguration; @Before public void setUpPrincipal() { + IdentityZoneHolder.clear(); marissa = new UaaPrincipal("marissa-id","marissa","marissa@test.org","origin",null, IdentityZoneHolder.get().getId()); prompts = new LinkedList<>(); prompts.add(new Prompt("username", "text", "Email")); prompts.add(new Prompt("password", "password", "Password")); prompts.add(new Prompt("passcode", "text", "One Time Code ( Get one at "+HTTP_LOCALHOST_8080_UAA+"/passcode )")); - linksSet.put("register", "/create_account"); - linksSet.put("passwd", "/forgot_password"); mockIDPConfigurator = mock(SamlIdentityProviderConfigurator.class); identityProviderProvisioning = mock(IdentityProviderProvisioning.class); uaaProvider = new IdentityProvider(); when(identityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.UAA), anyString())).thenReturn(uaaProvider); when(identityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.LDAP), anyString())).thenReturn(new IdentityProvider()); idps = getIdps(); + originalConfiguration = IdentityZoneHolder.get().getConfig(); + IdentityZoneHolder.get().setConfig(new IdentityZoneConfiguration()); } - @Before @After public void clearZoneHolder() { IdentityZoneHolder.clear(); + IdentityZoneHolder.get().setConfig(originalConfiguration); } @Test @@ -111,10 +112,8 @@ public void testLoginReturnsOtherZone() throws Exception { @Test public void customSelfserviceLinks_OnlyApplyToDefaultZone_Html() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); - Map links = new HashMap<>(); - links.put("signup", "http://custom_signup_link"); - links.put("passwd", "http://custom_passwd_link"); - endpoint.setLinks(links); + IdentityZoneHolder.get().getConfig().getLinks().getService().setSignup("http://custom_signup_link"); + IdentityZoneHolder.get().getConfig().getLinks().getService().setPasswd("http://custom_passwd_link"); endpoint.loginForHtml(model, null, new MockHttpServletRequest()); assertEquals("http://custom_signup_link", ((Map) model.asMap().get("links")).get("createAccountLink")); assertEquals("http://custom_passwd_link", ((Map) model.asMap().get("links")).get("forgotPasswordLink")); @@ -138,10 +137,8 @@ public void customSelfserviceLinks_OnlyApplyToDefaultZone_Html() throws Exceptio @Test public void customSelfserviceLinks_OnlyApplyToDefaultZone_Json() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); - Map links = new HashMap<>(); - links.put("signup", "http://custom_signup_link"); - links.put("passwd", "http://custom_passwd_link"); - endpoint.setLinks(links); + IdentityZoneHolder.get().getConfig().getLinks().getService().setSignup("http://custom_signup_link"); + IdentityZoneHolder.get().getConfig().getLinks().getService().setPasswd("http://custom_passwd_link"); endpoint.loginForJson(model, null); assertNull(((Map) model.asMap().get("links")).get("createAccountLink")); assertNull(((Map) model.asMap().get("links")).get("forgotPasswordLink")); @@ -183,7 +180,6 @@ public void check_links_urls(IdentityZone zone) throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); String baseUrl = "http://uaa.domain.com"; endpoint.setBaseUrl(baseUrl); - endpoint.setLinks(linksSet); endpoint.infoForJson(model, null); assertEquals(addSubdomainToUrl(baseUrl), ((Map) model.asMap().get("links")).get("uaa")); assertEquals(addSubdomainToUrl(baseUrl.replace("uaa", "login")), ((Map) model.asMap().get("links")).get("login")); @@ -209,7 +205,6 @@ public void no_self_service_links_if_self_service_disabled() throws Exception { zone.getConfig().getLinks().getService().setSelfServiceLinksEnabled(false); IdentityZoneHolder.set(zone); LoginInfoEndpoint endpoint = getEndpoint(); - endpoint.setLinks(linksSet); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); @@ -220,7 +215,6 @@ public void no_self_service_links_if_self_service_disabled() throws Exception { @Test public void no_ui_links_for_json() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); - endpoint.setLinks(linksSet); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); @@ -240,7 +234,6 @@ public void saml_links_for_json() throws Exception { endpoint.setIdpDefinitions(mockIDPConfigurator); when(mockIDPConfigurator.getIdentityProviderDefinitions(anyObject(), anyObject())).thenReturn(idps); endpoint.setIdpDefinitions(mockIDPConfigurator); - endpoint.setLinks(linksSet); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertEquals("http://someurl", links.get("login")); @@ -258,7 +251,6 @@ public void saml_links_for_json() throws Exception { public void saml_links_for_html() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); endpoint.setIdpDefinitions(mockIDPConfigurator); - endpoint.setLinks(linksSet); endpoint.infoForHtml(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); @@ -273,10 +265,6 @@ public void no_self_service_links_if_internal_user_management_disabled() throws uaaIdentityProviderDefinition.setDisableInternalUserManagement(true); uaaProvider.setConfig(uaaIdentityProviderDefinition); LoginInfoEndpoint endpoint = getEndpoint(); - Map linksSet = new HashMap<>(); - linksSet.put("register", "/create_account"); - linksSet.put("passwd", "/forgot_password"); - endpoint.setLinks(linksSet); endpoint.infoForJson(model, null); Map links = (Map) model.asMap().get("links"); assertNotNull(links); diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 5073e8e0ed6..caa7481604a 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -373,7 +373,6 @@ - @@ -402,6 +401,7 @@ + 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 8d86aad8fe5..917a8e5c821 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 @@ -40,6 +40,7 @@ import org.cloudfoundry.identity.uaa.security.web.CorsFilter; import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; @@ -76,6 +77,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -164,6 +166,8 @@ public void testRootContextDefaults() throws Exception { assertTrue(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); assertNull(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); + Object links = context.getBean("links"); + assertEquals(Collections.EMPTY_MAP, links); //check java mail sender EmailService emailService = context.getBean("emailService", EmailService.class); @@ -260,9 +264,11 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); - assertFalse(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); - assertEquals("http://some.redirect.com/redirect", zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); - + IdentityZoneConfiguration zoneConfig = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); + assertFalse(zoneConfig.getLinks().getService().isSelfServiceLinksEnabled()); + assertEquals("http://some.redirect.com/redirect", zoneConfig.getLinks().getHomeRedirect()); + assertEquals("/configured_signup", zoneConfig.getLinks().getService().getSignup()); + assertEquals("/configured_passwd", zoneConfig.getLinks().getService().getPasswd()); IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); 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 502ad07f6fc..9bc9725e424 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 @@ -34,6 +34,7 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -78,6 +79,7 @@ import static java.util.Collections.EMPTY_LIST; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.cloudfoundry.identity.uaa.zone.IdentityZone.getUaa; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasEntry; @@ -118,7 +120,8 @@ public class LoginMockMvcTests extends InjectedMockContextTest { private String adminToken; private XmlWebApplicationContext webApplicationContext; - private Map configuredDefaultLinks; + private IdentityZoneConfiguration originalConfiguration; + private IdentityZoneConfiguration identityZoneConfiguration; @Before public void setUpContext() throws Exception { @@ -131,7 +134,8 @@ public void setUpContext() throws Exception { originalProperties.put(s, propertySource.getProperty(s)); } adminToken = MockMvcUtils.utils().getClientCredentialsOAuthAccessToken(getMockMvc(), "admin", "adminsecret", null, null); - configuredDefaultLinks = new HashMap<>(webApplicationContext.getBean(LoginInfoEndpoint.class).getLinks()); + originalConfiguration = getWebApplicationContext().getBean(IdentityZoneProvisioning.class).retrieve(getUaa().getId()).getConfig(); + identityZoneConfiguration = getWebApplicationContext().getBean(IdentityZoneProvisioning.class).retrieve(getUaa().getId()).getConfig(); } @After @@ -139,7 +143,7 @@ public void tearDown() throws Exception { //restore all properties setSelfServiceLinksEnabled(true); setDisableInternalUserManagement(false); - webApplicationContext.getBean(LoginInfoEndpoint.class).setLinks(configuredDefaultLinks); + setZoneConfiguration(originalConfiguration); mockEnvironment.getPropertySources().remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); MockPropertySource originalPropertySource = new MockPropertySource(originalProperties); ReflectionUtils.setField(f, mockEnvironment, new MockPropertySource(originalProperties)); @@ -168,8 +172,16 @@ protected void setDisableInternalUserManagement(boolean disabled) { protected void setSelfServiceLinksEnabled(boolean enabled) { IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(IdentityZone.getUaa().getId()); - uaaZone.getConfig().getLinks().getService().setSelfServiceLinksEnabled(enabled); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + IdentityZoneConfiguration config = uaaZone.getConfig(); + config.getLinks().getService().setSelfServiceLinksEnabled(enabled); + setZoneConfiguration(config); + } + + protected void setZoneConfiguration(IdentityZoneConfiguration configuration) { + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + uaaZone.setConfig(configuration); provisioning.update(uaaZone); } @@ -524,13 +536,10 @@ public void testSignupsAndResetPasswordDisabledWithNoLinksConfigured() throws Ex @Test public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws Exception { - LoginInfoEndpoint endpoint = webApplicationContext.getBean(LoginInfoEndpoint.class); - Map links = endpoint.getLinks(); - links.put("signup", "http://example.com/signup"); - links.put("passwd", "http://example.com/reset_passwd"); - endpoint.setLinks(links); - setSelfServiceLinksEnabled(false); - + identityZoneConfiguration.getLinks().getService().setSignup("http://example.com/signup"); + identityZoneConfiguration.getLinks().getService().setPasswd("http://example.com/reset_passwd"); + identityZoneConfiguration.getLinks().getService().setSelfServiceLinksEnabled(false); + setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']").doesNotExist()) .andExpect(xpath("//a[text()='Reset password']").doesNotExist()); @@ -538,13 +547,10 @@ public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws @Test public void testSignupsAndResetPasswordEnabledWithCustomLinks() throws Exception { - LoginInfoEndpoint endpoint = webApplicationContext.getBean(LoginInfoEndpoint.class); - Map links = endpoint.getLinks(); - links.put("signup", "http://example.com/signup"); - links.put("passwd", "http://example.com/reset_passwd"); - endpoint.setLinks(links); - setSelfServiceLinksEnabled(true); - + identityZoneConfiguration.getLinks().getService().setSignup("http://example.com/signup"); + identityZoneConfiguration.getLinks().getService().setPasswd("http://example.com/reset_passwd"); + identityZoneConfiguration.getLinks().getService().setSelfServiceLinksEnabled(true); + setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']/@href").string("http://example.com/signup")) .andExpect(xpath("//a[text()='Reset password']/@href").string("http://example.com/reset_passwd")); @@ -639,10 +645,8 @@ public void testDefaultAndCustomSignupLink() throws Exception { getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(model().attribute("links", hasEntry("createAccountLink", "/create_account"))); - LoginInfoEndpoint endpoint = webApplicationContext.getBean(LoginInfoEndpoint.class); - Map links = endpoint.getLinks(); - links.put("signup", "http://www.example.com/signup"); - endpoint.setLinks(links); + identityZoneConfiguration.getLinks().getService().setSignup("http://www.example.com/signup"); + setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(model().attribute("links", hasEntry("createAccountLink", "http://www.example.com/signup"))); @@ -659,10 +663,6 @@ public void testLocalSignupDisabled() throws Exception { @Test public void testCustomSignupLinkWithLocalSignupDisabled() throws Exception { setSelfServiceLinksEnabled(false); - LoginInfoEndpoint endpoint = webApplicationContext.getBean(LoginInfoEndpoint.class); - Map links = endpoint.getLinks(); - links.put("signup", "http://example.com/signup"); - endpoint.setLinks(links); getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(model().attribute("createAccountLink", nullValue())); diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index e9642a69dd4..df81b7aa10a 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -20,6 +20,10 @@ login: selfServiceLinksEnabled: false homeRedirect: http://some.redirect.com/redirect +links: + passwd: /configured_passwd + signup: /configured_signup + smtp: host: '' From 0023d4483673f43a50e8ce780692473536a1f7cc Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 09:19:44 -0700 Subject: [PATCH 18/87] Rename get/set-Service to get/set-SelfService --- .../cloudfoundry/identity/uaa/zone/Links.java | 4 ++-- .../IdentityZoneConfigurationBootstrap.java | 6 +++--- .../identity/uaa/login/LoginInfoEndpoint.java | 6 +++--- .../IdentityZoneConfigurationBootstrapTests.java | 10 +++++----- .../uaa/login/LoginInfoEndpointTests.java | 10 +++++----- .../identity/uaa/login/BootstrapTests.java | 8 ++++---- .../identity/uaa/login/LoginMockMvcTests.java | 16 ++++++++-------- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java index f15d7ea80c0..6e9d69d641f 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java @@ -37,11 +37,11 @@ public Links setLogout(Logout logout) { return this; } - public SelfService getService() { + public SelfService getSelfService() { return service; } - public Links setService(SelfService service) { + public Links setSelfService(SelfService service) { this.service = service; return this; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index 730343b5202..bab9bd10286 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -39,16 +39,16 @@ public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) public void afterPropertiesSet() { IdentityZone identityZone = provisioning.retrieve(IdentityZone.getUaa().getId()); IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); - definition.getLinks().getService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); + definition.getLinks().getSelfService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); definition.getLinks().setHomeRedirect(homeRedirect); if (selfServiceLinks!=null) { String signup = selfServiceLinks.get("signup"); String passwd = selfServiceLinks.get("passwd"); if (hasText(signup)) { - definition.getLinks().getService().setSignup(signup); + definition.getLinks().getSelfService().setSignup(signup); } if (hasText(passwd)) { - definition.getLinks().getService().setPasswd(passwd); + definition.getLinks().getSelfService().setPasswd(passwd); } } identityZone.setConfig(definition); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index ad880ea0de1..38dbbddbf71 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -490,9 +490,9 @@ public String generatePasscode(Map model, Principal principal) IdentityZone zone = IdentityZoneHolder.get(); IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; - boolean selfServiceLinksEnabled = (zone.getConfig()!=null) ? zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled() : true; - String signup = zone.getConfig()!=null ? zone.getConfig().getLinks().getService().getSignup() : null; - String passwd = zone.getConfig()!=null ? zone.getConfig().getLinks().getService().getPasswd() : null; + boolean selfServiceLinksEnabled = (zone.getConfig()!=null) ? zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled() : true; + String signup = zone.getConfig()!=null ? zone.getConfig().getLinks().getSelfService().getSignup() : null; + String passwd = zone.getConfig()!=null ? zone.getConfig().getLinks().getSelfService().getPasswd() : null; Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl())); if (getBaseUrl().contains("localhost:")) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 646e245998e..c56cdc95d8e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -93,7 +93,7 @@ public void disable_self_service_links() throws Exception { bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); - assertFalse(zone.getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + assertFalse(zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled()); } @Test @@ -121,8 +121,8 @@ public void signup_link_configured() throws Exception { bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); - assertEquals("/configured_signup", zone.getConfig().getLinks().getService().getSignup()); - assertEquals("/forgot_password", zone.getConfig().getLinks().getService().getPasswd()); + assertEquals("/configured_signup", zone.getConfig().getLinks().getSelfService().getSignup()); + assertEquals("/forgot_password", zone.getConfig().getLinks().getSelfService().getPasswd()); } @Test @@ -132,7 +132,7 @@ public void passwd_link_configured() throws Exception { bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); - assertEquals("/create_account", zone.getConfig().getLinks().getService().getSignup()); - assertEquals("/configured_passwd", zone.getConfig().getLinks().getService().getPasswd()); + assertEquals("/create_account", zone.getConfig().getLinks().getSelfService().getSignup()); + assertEquals("/configured_passwd", zone.getConfig().getLinks().getSelfService().getPasswd()); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index ae65879ba1b..0f0786578f7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -112,8 +112,8 @@ public void testLoginReturnsOtherZone() throws Exception { @Test public void customSelfserviceLinks_OnlyApplyToDefaultZone_Html() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); - IdentityZoneHolder.get().getConfig().getLinks().getService().setSignup("http://custom_signup_link"); - IdentityZoneHolder.get().getConfig().getLinks().getService().setPasswd("http://custom_passwd_link"); + IdentityZoneHolder.get().getConfig().getLinks().getSelfService().setSignup("http://custom_signup_link"); + IdentityZoneHolder.get().getConfig().getLinks().getSelfService().setPasswd("http://custom_passwd_link"); endpoint.loginForHtml(model, null, new MockHttpServletRequest()); assertEquals("http://custom_signup_link", ((Map) model.asMap().get("links")).get("createAccountLink")); assertEquals("http://custom_passwd_link", ((Map) model.asMap().get("links")).get("forgotPasswordLink")); @@ -137,8 +137,8 @@ public void customSelfserviceLinks_OnlyApplyToDefaultZone_Html() throws Exceptio @Test public void customSelfserviceLinks_OnlyApplyToDefaultZone_Json() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); - IdentityZoneHolder.get().getConfig().getLinks().getService().setSignup("http://custom_signup_link"); - IdentityZoneHolder.get().getConfig().getLinks().getService().setPasswd("http://custom_passwd_link"); + IdentityZoneHolder.get().getConfig().getLinks().getSelfService().setSignup("http://custom_signup_link"); + IdentityZoneHolder.get().getConfig().getLinks().getSelfService().setPasswd("http://custom_passwd_link"); endpoint.loginForJson(model, null); assertNull(((Map) model.asMap().get("links")).get("createAccountLink")); assertNull(((Map) model.asMap().get("links")).get("forgotPasswordLink")); @@ -202,7 +202,7 @@ public void check_links_urls(IdentityZone zone) throws Exception { public void no_self_service_links_if_self_service_disabled() throws Exception { IdentityZone zone = MultitenancyFixture.identityZone("zone","zone"); zone.setConfig(new IdentityZoneConfiguration()); - zone.getConfig().getLinks().getService().setSelfServiceLinksEnabled(false); + zone.getConfig().getLinks().getSelfService().setSelfServiceLinksEnabled(false); IdentityZoneHolder.set(zone); LoginInfoEndpoint endpoint = getEndpoint(); endpoint.infoForJson(model, null); 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 917a8e5c821..febca7e6734 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 @@ -163,7 +163,7 @@ public void testRootContextDefaults() throws Exception { assertSame(UaaTokenStore.class, context.getBean(AuthorizationCodeServices.class).getClass()); IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); - assertTrue(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getService().isSelfServiceLinksEnabled()); + assertTrue(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled()); assertNull(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); Object links = context.getBean("links"); @@ -265,10 +265,10 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); IdentityZoneConfiguration zoneConfig = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); - assertFalse(zoneConfig.getLinks().getService().isSelfServiceLinksEnabled()); + assertFalse(zoneConfig.getLinks().getSelfService().isSelfServiceLinksEnabled()); assertEquals("http://some.redirect.com/redirect", zoneConfig.getLinks().getHomeRedirect()); - assertEquals("/configured_signup", zoneConfig.getLinks().getService().getSignup()); - assertEquals("/configured_passwd", zoneConfig.getLinks().getService().getPasswd()); + assertEquals("/configured_signup", zoneConfig.getLinks().getSelfService().getSignup()); + assertEquals("/configured_passwd", zoneConfig.getLinks().getSelfService().getPasswd()); IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); 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 9bc9725e424..2a46c21a9b6 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 @@ -174,7 +174,7 @@ protected void setSelfServiceLinksEnabled(boolean enabled) { IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); IdentityZoneConfiguration config = uaaZone.getConfig(); - config.getLinks().getService().setSelfServiceLinksEnabled(enabled); + config.getLinks().getSelfService().setSelfServiceLinksEnabled(enabled); setZoneConfiguration(config); } @@ -536,9 +536,9 @@ public void testSignupsAndResetPasswordDisabledWithNoLinksConfigured() throws Ex @Test public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws Exception { - identityZoneConfiguration.getLinks().getService().setSignup("http://example.com/signup"); - identityZoneConfiguration.getLinks().getService().setPasswd("http://example.com/reset_passwd"); - identityZoneConfiguration.getLinks().getService().setSelfServiceLinksEnabled(false); + identityZoneConfiguration.getLinks().getSelfService().setSignup("http://example.com/signup"); + identityZoneConfiguration.getLinks().getSelfService().setPasswd("http://example.com/reset_passwd"); + identityZoneConfiguration.getLinks().getSelfService().setSelfServiceLinksEnabled(false); setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']").doesNotExist()) @@ -547,9 +547,9 @@ public void testSignupsAndResetPasswordDisabledWithSomeLinksConfigured() throws @Test public void testSignupsAndResetPasswordEnabledWithCustomLinks() throws Exception { - identityZoneConfiguration.getLinks().getService().setSignup("http://example.com/signup"); - identityZoneConfiguration.getLinks().getService().setPasswd("http://example.com/reset_passwd"); - identityZoneConfiguration.getLinks().getService().setSelfServiceLinksEnabled(true); + identityZoneConfiguration.getLinks().getSelfService().setSignup("http://example.com/signup"); + identityZoneConfiguration.getLinks().getSelfService().setPasswd("http://example.com/reset_passwd"); + identityZoneConfiguration.getLinks().getSelfService().setSelfServiceLinksEnabled(true); setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(MockMvcRequestBuilders.get("/login")) .andExpect(xpath("//a[text()='Create account']/@href").string("http://example.com/signup")) @@ -645,7 +645,7 @@ public void testDefaultAndCustomSignupLink() throws Exception { getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(model().attribute("links", hasEntry("createAccountLink", "/create_account"))); - identityZoneConfiguration.getLinks().getService().setSignup("http://www.example.com/signup"); + identityZoneConfiguration.getLinks().getSelfService().setSignup("http://www.example.com/signup"); setZoneConfiguration(identityZoneConfiguration); getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) From 34a2317a6fa5d2c9a5d9c36b85cc50594b4b80d8 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 10:33:28 -0700 Subject: [PATCH 19/87] Zonify logout: redirect links/parameters/whitelist/flag https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../cloudfoundry/identity/uaa/zone/Links.java | 10 +- .../WhitelistLogoutHandler.java | 14 ++ .../ZoneAwareWhitelistLogoutHandler.java | 59 +++++++ .../IdentityZoneConfigurationBootstrap.java | 33 ++++ server/src/main/resources/login-ui.xml | 11 +- .../ZoneAwareWhitelistLogoutHandlerTests.java | 156 ++++++++++++++++++ ...entityZoneConfigurationBootstrapTests.java | 15 ++ .../main/webapp/WEB-INF/spring-servlet.xml | 12 +- .../identity/uaa/login/BootstrapTests.java | 15 +- .../test/bootstrap/bootstrap-test.yml | 9 + 10 files changed, 317 insertions(+), 17 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java index 6e9d69d641f..46984bbe24f 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import java.util.HashSet; -import java.util.Set; +import java.util.Collections; +import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @@ -59,7 +59,7 @@ public static class Logout { private String redirectUrl = "/login"; private String redirectParameterName = "redirect"; private boolean disableRedirectParameter = false; - private Set whitelist = new HashSet<>(); + private List whitelist = Collections.EMPTY_LIST; public boolean isDisableRedirectParameter() { return disableRedirectParameter; @@ -88,11 +88,11 @@ public Logout setRedirectUrl(String redirectUrl) { return this; } - public Set getWhitelist() { + public List getWhitelist() { return whitelist; } - public Logout setWhitelist(Set whitelist) { + public Logout setWhitelist(List whitelist) { this.whitelist = whitelist; return this; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java index 223c9c13c88..3980e9a9ca7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java @@ -27,6 +27,20 @@ public WhitelistLogoutHandler(List whitelist) { this.whitelist = whitelist; } + @Override + protected String getTargetUrlParameter() { + return super.getTargetUrlParameter(); + } + + @Override + protected boolean isAlwaysUseDefaultTargetUrl() { + return super.isAlwaysUseDefaultTargetUrl(); + } + + public String getDefaultTargetUrl1() { + return super.getDefaultTargetUrl(); + } + public List getWhitelist() { return whitelist; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java new file mode 100644 index 00000000000..bb148d86340 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java @@ -0,0 +1,59 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.authentication; + + +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class ZoneAwareWhitelistLogoutHandler implements LogoutSuccessHandler { + + private final ClientDetailsService clientDetailsService; + + public ZoneAwareWhitelistLogoutHandler(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + getZoneHandler().onLogoutSuccess(request, response, authentication); + } + + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { + return getZoneHandler().determineTargetUrl(request, response); + } + + protected WhitelistLogoutHandler getZoneHandler() { + IdentityZoneConfiguration config = IdentityZoneHolder.get().getConfig(); + if (config==null) { + config = new IdentityZoneConfiguration(); + } + WhitelistLogoutHandler handler = new WhitelistLogoutHandler(config.getLinks().getLogout().getWhitelist()); + handler.setTargetUrlParameter(config.getLinks().getLogout().getRedirectParameterName()); + handler.setDefaultTargetUrl(config.getLinks().getLogout().getRedirectUrl()); + handler.setAlwaysUseDefaultTargetUrl(config.getLinks().getLogout().isDisableRedirectParameter()); + handler.setClientDetailsService(clientDetailsService); + return handler; + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index bab9bd10286..83f23eab4d2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -18,8 +18,10 @@ import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.springframework.beans.factory.InitializingBean; +import java.util.List; import java.util.Map; +import static java.util.Objects.nonNull; import static org.springframework.util.StringUtils.hasText; public class IdentityZoneConfigurationBootstrap implements InitializingBean { @@ -29,6 +31,10 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private boolean selfServiceLinksEnabled = true; private String homeRedirect = null; private Map selfServiceLinks; + private List logoutRedirectWhitelist; + private String logoutRedirectParameterName; + private String logoutDefaultRedirectUrl; + private boolean logoutDisableRedirectParameter = true; public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { @@ -51,6 +57,17 @@ public void afterPropertiesSet() { definition.getLinks().getSelfService().setPasswd(passwd); } } + if (nonNull(logoutRedirectWhitelist)) { + definition.getLinks().getLogout().setWhitelist(logoutRedirectWhitelist); + } + if (hasText(logoutRedirectParameterName)) { + definition.getLinks().getLogout().setRedirectParameterName(logoutRedirectParameterName); + } + if (hasText(logoutDefaultRedirectUrl)) { + definition.getLinks().getLogout().setRedirectUrl(logoutDefaultRedirectUrl); + } + definition.getLinks().getLogout().setDisableRedirectParameter(logoutDisableRedirectParameter); + identityZone.setConfig(definition); provisioning.update(identityZone); } @@ -76,4 +93,20 @@ public void setHomeRedirect(String homeRedirect) { public void setSelfServiceLinks(Map links) { this.selfServiceLinks = links; } + + public void setLogoutDefaultRedirectUrl(String logoutDefaultRedirectUrl) { + this.logoutDefaultRedirectUrl = logoutDefaultRedirectUrl; + } + + public void setLogoutDisableRedirectParameter(boolean logoutDisableRedirectParameter) { + this.logoutDisableRedirectParameter = logoutDisableRedirectParameter; + } + + public void setLogoutRedirectParameterName(String logoutRedirectParameterName) { + this.logoutRedirectParameterName = logoutRedirectParameterName; + } + + public void setLogoutRedirectWhitelist(List logoutRedirectWhitelist) { + this.logoutRedirectWhitelist = logoutRedirectWhitelist; + } } diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index bdaef0984ed..1fd61b37198 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -308,15 +308,8 @@ - - - - - - + + diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java new file mode 100644 index 00000000000..9d5845b7ed6 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java @@ -0,0 +1,156 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.authentication; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; + + +public class ZoneAwareWhitelistLogoutHandlerTests { + + private MockHttpServletRequest request = new MockHttpServletRequest(); + private MockHttpServletResponse response = new MockHttpServletResponse(); + private BaseClientDetails client = new BaseClientDetails(CLIENT_ID, "", "", "", "", "http://*.testing.com,http://testing.com"); + private ClientDetailsService clientDetailsService = mock(ClientDetailsService.class); + private ZoneAwareWhitelistLogoutHandler handler; + IdentityZoneConfiguration configuration = new IdentityZoneConfiguration(); + IdentityZoneConfiguration original; + + + @Before + public void setUp() throws Exception { + original = IdentityZone.getUaa().getConfig(); + configuration.getLinks().getLogout() + .setRedirectUrl("/login") + .setDisableRedirectParameter(true) + .setRedirectParameterName("redirect"); + when(clientDetailsService.loadClientByClientId(CLIENT_ID)).thenReturn(client); + handler = new ZoneAwareWhitelistLogoutHandler(clientDetailsService); + IdentityZoneHolder.get().setConfig(configuration); + } + + @After + public void tearDown() throws Exception { + IdentityZoneHolder.clear(); + IdentityZone.getUaa().setConfig(original); + } + + @Test + public void test_defaults() throws Exception { + WhitelistLogoutHandler whandler = handler.getZoneHandler(); + assertEquals(Collections.EMPTY_LIST, whandler.getWhitelist()); + assertEquals("redirect", whandler.getTargetUrlParameter()); + assertEquals("/login", whandler.getDefaultTargetUrl1()); + assertTrue(whandler.isAlwaysUseDefaultTargetUrl()); + } + + @Test + public void test_null_config_defaults() throws Exception { + IdentityZoneHolder.get().setConfig(null); + test_default_redirect_uri(); + } + + + @Test + public void test_default_redirect_uri() throws Exception { + assertEquals("/login", handler.determineTargetUrl(request, response)); + assertEquals("/login", handler.determineTargetUrl(request, response)); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + assertEquals("/login", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_whitelist_reject() throws Exception { + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://testing.com"); + assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + request.setParameter("redirect", "http://www.testing.com"); + assertEquals("/login", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_allow_open_redirect() throws Exception { + configuration.getLinks().getLogout().setWhitelist(null); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://testing.com"); + assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + request.setParameter("redirect", "http://www.testing.com"); + assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_whitelist_redirect() throws Exception { + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://somethingelse.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://somethingelse.com"); + assertEquals("http://somethingelse.com", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_whitelist_redirect_with_wildcard() throws Exception { + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://*.somethingelse.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://www.somethingelse.com"); + assertEquals("http://www.somethingelse.com", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_client_redirect() throws Exception { + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://somethingelse.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://testing.com"); + request.setParameter(CLIENT_ID, CLIENT_ID); + assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + } + + @Test + public void client_not_found_exception() throws Exception { + when(clientDetailsService.loadClientByClientId("test")).thenThrow(new NoSuchClientException("test")); + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter("redirect", "http://notwhitelisted.com"); + request.setParameter(CLIENT_ID, "test"); + assertEquals("/login", handler.determineTargetUrl(request, response)); + } + + @Test + public void test_client_redirect_using_wildcard() throws Exception { + configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com")); + configuration.getLinks().getLogout().setDisableRedirectParameter(false); + request.setParameter(CLIENT_ID, CLIENT_ID); + request.setParameter("redirect", "http://www.testing.com"); + assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); + } + +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index c56cdc95d8e..5fd500e1af6 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Test; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -135,4 +136,18 @@ public void passwd_link_configured() throws Exception { assertEquals("/create_account", zone.getConfig().getLinks().getSelfService().getSignup()); assertEquals("/configured_passwd", zone.getConfig().getLinks().getSelfService().getPasswd()); } + + @Test + public void test_logout_redirect() throws Exception { + bootstrap.setLogoutDefaultRedirectUrl("/configured_login"); + bootstrap.setLogoutDisableRedirectParameter(false); + bootstrap.setLogoutRedirectParameterName("test"); + bootstrap.setLogoutRedirectWhitelist(Arrays.asList("http://single-url")); + bootstrap.afterPropertiesSet(); + IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); + assertEquals("/configured_login", config.getLinks().getLogout().getRedirectUrl()); + assertEquals("test", config.getLinks().getLogout().getRedirectParameterName()); + assertEquals(Arrays.asList("http://single-url"), config.getLinks().getLogout().getWhitelist()); + assertFalse(config.getLinks().getLogout().isDisableRedirectParameter()); + } } diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index caa7481604a..3d044c37009 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -403,7 +403,17 @@ - + + + + + + + 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 febca7e6734..b7d6d9a88ce 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 @@ -163,8 +163,13 @@ public void testRootContextDefaults() throws Exception { assertSame(UaaTokenStore.class, context.getBean(AuthorizationCodeServices.class).getClass()); IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); - assertTrue(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled()); - assertNull(zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig().getLinks().getHomeRedirect()); + IdentityZoneConfiguration zoneConfiguration = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); + assertTrue(zoneConfiguration.getLinks().getSelfService().isSelfServiceLinksEnabled()); + assertNull(zoneConfiguration.getLinks().getHomeRedirect()); + assertEquals("redirect", zoneConfiguration.getLinks().getLogout().getRedirectParameterName()); + assertEquals("/login", zoneConfiguration.getLinks().getLogout().getRedirectUrl()); + assertEquals(Collections.EMPTY_LIST, zoneConfiguration.getLinks().getLogout().getWhitelist()); + assertTrue(zoneConfiguration.getLinks().getLogout().isDisableRedirectParameter()); Object links = context.getBean("links"); assertEquals(Collections.EMPTY_MAP, links); @@ -270,6 +275,12 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { assertEquals("/configured_signup", zoneConfig.getLinks().getSelfService().getSignup()); assertEquals("/configured_passwd", zoneConfig.getLinks().getSelfService().getPasswd()); + assertEquals("redirect", zoneConfig.getLinks().getLogout().getRedirectParameterName()); + assertEquals("/configured_login", zoneConfig.getLinks().getLogout().getRedirectUrl()); + assertEquals(Arrays.asList("https://url1.domain1.com/logout-success","https://url2.domain2.com/logout-success"), zoneConfig.getLinks().getLogout().getWhitelist()); + assertFalse(zoneConfig.getLinks().getLogout().isDisableRedirectParameter()); + + IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertTrue(uaaIdp.getConfig().isDisableInternalUserManagement()); diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index df81b7aa10a..08e9e0e2130 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -20,6 +20,15 @@ login: selfServiceLinksEnabled: false homeRedirect: http://some.redirect.com/redirect +logout: + redirect: + url: /configured_login + parameter: + disable: false + whitelist: + - https://url1.domain1.com/logout-success + - https://url2.domain2.com/logout-success + links: passwd: /configured_passwd signup: /configured_signup From 8ee59a72a11c0b7e55f677ececcc89bdfadea5ec Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 11:38:59 -0700 Subject: [PATCH 20/87] Fix bugs with logout redirect values Zonify prompts https://www.pivotaltracker.com/story/show/108723714 [#108723714] --- .../identity/uaa/login/Prompt.java | 23 ++- .../uaa/zone/IdentityZoneConfiguration.java | 19 +++ .../cloudfoundry/identity/uaa/zone/Links.java | 3 +- .../IdentityZoneConfigurationBootstrap.java | 9 ++ .../identity/uaa/login/LoginInfoEndpoint.java | 21 +-- .../ZoneAwareWhitelistLogoutHandlerTests.java | 4 +- ...entityZoneConfigurationBootstrapTests.java | 56 +++++--- .../uaa/login/LoginInfoEndpointTests.java | 2 +- .../main/webapp/WEB-INF/spring-servlet.xml | 4 +- .../identity/uaa/login/BootstrapTests.java | 37 +++-- .../identity/uaa/login/LoginMockMvcTests.java | 136 +++++++++++------- 11 files changed, 205 insertions(+), 109 deletions(-) rename {server => model}/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java (81%) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java b/model/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java similarity index 81% rename from server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java index c9a6c7cc4d3..92251353da6 100755 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,19 +12,21 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.util.StringUtils; -/** - * @author Dave Syer - * - */ public class Prompt { private final String name; private final String text; private final String type; - public Prompt(String name, String type, String text) { + @JsonCreator + public Prompt(@JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("text") String text) { this.name = name; this.type = type; this.text = text; @@ -34,6 +36,15 @@ public String getName() { return name; } + public String getText() { + return text; + } + + public String getType() { + return type; + } + + @JsonIgnore public String[] getDetails() { return new String[] { type, text }; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index e75e797a671..870ea688326 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -15,14 +15,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import org.cloudfoundry.identity.uaa.login.Prompt; + +import java.util.Arrays; +import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class IdentityZoneConfiguration { + private TokenPolicy tokenPolicy = new TokenPolicy(); private SamlConfig samlConfig = new SamlConfig(); private boolean disableInternalUserManagement = false; private Links links = new Links(); + private List prompts = Arrays.asList( + new Prompt("username", "text", "Email"), + new Prompt("password", "password", "Password"), + new Prompt("passcode", "password", "One Time Code (Get on at /passcode)") + ); public IdentityZoneConfiguration() {} @@ -64,4 +74,13 @@ public IdentityZoneConfiguration setLinks(Links links) { this.links = links; return this; } + + public List getPrompts() { + return prompts; + } + + public IdentityZoneConfiguration setPrompts(List prompts) { + this.prompts = prompts; + return this; + } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java index 46984bbe24f..d01bacd6e66 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import java.util.Collections; import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -59,7 +58,7 @@ public static class Logout { private String redirectUrl = "/login"; private String redirectParameterName = "redirect"; private boolean disableRedirectParameter = false; - private List whitelist = Collections.EMPTY_LIST; + private List whitelist = null; public boolean isDisableRedirectParameter() { return disableRedirectParameter; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index 83f23eab4d2..b8301be3735 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.impl.config; +import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -35,6 +36,7 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private String logoutRedirectParameterName; private String logoutDefaultRedirectUrl; private boolean logoutDisableRedirectParameter = true; + private List prompts; public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { @@ -67,6 +69,9 @@ public void afterPropertiesSet() { definition.getLinks().getLogout().setRedirectUrl(logoutDefaultRedirectUrl); } definition.getLinks().getLogout().setDisableRedirectParameter(logoutDisableRedirectParameter); + if (nonNull(prompts)) { + definition.setPrompts(prompts); + } identityZone.setConfig(definition); provisioning.update(identityZone); @@ -109,4 +114,8 @@ public void setLogoutRedirectParameterName(String logoutRedirectParameterName) { public void setLogoutRedirectWhitelist(List logoutRedirectWhitelist) { this.logoutRedirectWhitelist = logoutRedirectWhitelist; } + + public void setPrompts(List prompts) { + this.prompts = prompts; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 38dbbddbf71..45e1f02e543 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -30,6 +30,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.dao.EmptyResultDataAccessException; @@ -75,6 +76,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Objects.isNull; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl; import static org.springframework.util.StringUtils.hasText; @@ -170,19 +172,6 @@ public LoginInfoEndpoint() { } } - private List prompts = Arrays.asList( - new Prompt("username", "text", "Email"), - new Prompt("password", "password", "Password") - ); - - public void setPrompts(List prompts) { - this.prompts = prompts; - } - - public List getPrompts() { - return prompts; - } - @RequestMapping(value = {"/login"}, headers = "Accept=application/json") public String loginForJson(Model model, Principal principal) { return login(model, principal, Collections.emptyList(), true); @@ -349,8 +338,12 @@ private void setCommitInfo(Model model) { public void populatePrompts(Model model, List exclude, boolean jsonResponse) { + IdentityZoneConfiguration zoneConfiguration = IdentityZoneHolder.get().getConfig(); + if (isNull(zoneConfiguration)) { + zoneConfiguration = new IdentityZoneConfiguration(); + } Map map = new LinkedHashMap<>(); - for (Prompt prompt : getPrompts()) { + for (Prompt prompt : zoneConfiguration.getPrompts()) { if (!exclude.contains(prompt.getName())) { String[] details = prompt.getDetails(); if (PASSCODE.equals(prompt.getName()) && !IdentityZoneHolder.isUaa()) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java index 9d5845b7ed6..a255c05d56c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java @@ -27,9 +27,9 @@ import org.springframework.security.oauth2.provider.client.BaseClientDetails; import java.util.Arrays; -import java.util.Collections; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -68,7 +68,7 @@ public void tearDown() throws Exception { @Test public void test_defaults() throws Exception { WhitelistLogoutHandler whandler = handler.getZoneHandler(); - assertEquals(Collections.EMPTY_LIST, whandler.getWhitelist()); + assertNull(whandler.getWhitelist()); assertEquals("redirect", whandler.getTargetUrlParameter()); assertEquals("/login", whandler.getDefaultTargetUrl1()); assertTrue(whandler.isAlwaysUseDefaultTargetUrl()); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 5fd500e1af6..91f67ef9f66 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.config; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; +import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; @@ -25,6 +26,7 @@ import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -35,33 +37,34 @@ public class IdentityZoneConfigurationBootstrapTests extends JdbcTestBase { public static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + - "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + - "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + - "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + - "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + - "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + - "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + - "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + - "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + - "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + - "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + - "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + - "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + - "-----END RSA PRIVATE KEY-----"; + "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + + "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + + "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + + "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + + "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + + "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + + "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + + "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + + "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + + "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + + "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + + "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + + "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + + "-----END RSA PRIVATE KEY-----"; public static final String PUBLIC_KEY = "-----BEGIN RSA PUBLIC KEY-----\n" + - "MIGJAoGBAMStmxljvRABrtZ0MPp46/dEsEDgjknTNk6JczOgUHnKHrirSyYRI21X\n" + - "ilrI5gTlOcfFaMyjTLuAOwaMjWiYAbrCB/Knrcj1ZwtfsUMvJ57jd8bn5v2uih+i\n" + - "wv47MlJcRJK6WxP1jVfFIUUzlEy7gh724zj+LMosKwAqKCAyGcCZAgMBAAE=\n" + - "-----END RSA PUBLIC KEY-----"; + "MIGJAoGBAMStmxljvRABrtZ0MPp46/dEsEDgjknTNk6JczOgUHnKHrirSyYRI21X\n" + + "ilrI5gTlOcfFaMyjTLuAOwaMjWiYAbrCB/Knrcj1ZwtfsUMvJ57jd8bn5v2uih+i\n" + + "wv47MlJcRJK6WxP1jVfFIUUzlEy7gh724zj+LMosKwAqKCAyGcCZAgMBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; public static final String PASSWORD = "password"; public static final String ID = "id"; private IdentityZoneProvisioning provisioning; private IdentityZoneConfigurationBootstrap bootstrap; - private Map links = new HashMap<>();; + private Map links = new HashMap<>(); + ; @Before public void configureProvisioning() { @@ -73,7 +76,7 @@ public void configureProvisioning() { public void tokenPolicy_configured_fromValuesInYaml() throws Exception { TokenPolicy tokenPolicy = new TokenPolicy(); KeyPair key = new KeyPair(PRIVATE_KEY, PUBLIC_KEY, PASSWORD); - Map keys = new HashMap<>(); + Map keys = new HashMap<>(); keys.put(ID, key); tokenPolicy.setKeys(keys); tokenPolicy.setAccessTokenValidity(3600); @@ -150,4 +153,17 @@ public void test_logout_redirect() throws Exception { assertEquals(Arrays.asList("http://single-url"), config.getLinks().getLogout().getWhitelist()); assertFalse(config.getLinks().getLogout().isDisableRedirectParameter()); } + + + @Test + public void test_prompts() throws Exception { + List prompts = Arrays.asList( + new Prompt("name1", "type1", "text1"), + new Prompt("name2", "type2", "text2") + ); + bootstrap.setPrompts(prompts); + bootstrap.afterPropertiesSet(); + IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); + assertEquals(prompts, config.getPrompts()); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index 0f0786578f7..51c3698e96b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -525,7 +525,7 @@ private LoginInfoEndpoint getEndpoint() { endpoint.setBaseUrl("http://someurl"); SamlIdentityProviderConfigurator emptyConfigurator = new SamlIdentityProviderConfigurator(); endpoint.setIdpDefinitions(emptyConfigurator); - endpoint.setPrompts(prompts); + IdentityZoneHolder.get().getConfig().setPrompts(prompts); endpoint.setProviderProvisioning(identityProviderProvisioning); return endpoint; } diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 3d044c37009..372bed94369 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -376,7 +376,6 @@ - @@ -411,8 +410,7 @@ - - + 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 b7d6d9a88ce..844a4b06af5 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 @@ -168,9 +168,18 @@ public void testRootContextDefaults() throws Exception { assertNull(zoneConfiguration.getLinks().getHomeRedirect()); assertEquals("redirect", zoneConfiguration.getLinks().getLogout().getRedirectParameterName()); assertEquals("/login", zoneConfiguration.getLinks().getLogout().getRedirectUrl()); - assertEquals(Collections.EMPTY_LIST, zoneConfiguration.getLinks().getLogout().getWhitelist()); + assertNull(zoneConfiguration.getLinks().getLogout().getWhitelist()); assertTrue(zoneConfiguration.getLinks().getLogout().isDisableRedirectParameter()); + assertEquals( + Arrays.asList( + new Prompt("username", "text", "Email"), + new Prompt("password", "password", "Password"), + new Prompt("passcode", "password", "One Time Code ( Get one at http://localhost:8080/uaa/passcode )") + ), + zoneConfiguration.getPrompts() + ); + Object links = context.getBean("links"); assertEquals(Collections.EMPTY_MAP, links); @@ -269,17 +278,25 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); - IdentityZoneConfiguration zoneConfig = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); - assertFalse(zoneConfig.getLinks().getSelfService().isSelfServiceLinksEnabled()); - assertEquals("http://some.redirect.com/redirect", zoneConfig.getLinks().getHomeRedirect()); - assertEquals("/configured_signup", zoneConfig.getLinks().getSelfService().getSignup()); - assertEquals("/configured_passwd", zoneConfig.getLinks().getSelfService().getPasswd()); + IdentityZoneConfiguration zoneConfiguration = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); + assertFalse(zoneConfiguration.getLinks().getSelfService().isSelfServiceLinksEnabled()); + assertEquals("http://some.redirect.com/redirect", zoneConfiguration.getLinks().getHomeRedirect()); + assertEquals("/configured_signup", zoneConfiguration.getLinks().getSelfService().getSignup()); + assertEquals("/configured_passwd", zoneConfiguration.getLinks().getSelfService().getPasswd()); - assertEquals("redirect", zoneConfig.getLinks().getLogout().getRedirectParameterName()); - assertEquals("/configured_login", zoneConfig.getLinks().getLogout().getRedirectUrl()); - assertEquals(Arrays.asList("https://url1.domain1.com/logout-success","https://url2.domain2.com/logout-success"), zoneConfig.getLinks().getLogout().getWhitelist()); - assertFalse(zoneConfig.getLinks().getLogout().isDisableRedirectParameter()); + assertEquals("redirect", zoneConfiguration.getLinks().getLogout().getRedirectParameterName()); + assertEquals("/configured_login", zoneConfiguration.getLinks().getLogout().getRedirectUrl()); + assertEquals(Arrays.asList("https://url1.domain1.com/logout-success","https://url2.domain2.com/logout-success"), zoneConfiguration.getLinks().getLogout().getWhitelist()); + assertFalse(zoneConfiguration.getLinks().getLogout().isDisableRedirectParameter()); + assertEquals( + Arrays.asList( + new Prompt("username", "text", "Username"), + new Prompt("password", "password", "Your Secret"), + new Prompt("passcode", "password", "One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )") + ), + zoneConfiguration.getPrompts() + ); IdentityProviderProvisioning idpProvisioning = context.getBean(IdentityProviderProvisioning.class); IdentityProvider uaaIdp = idpProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); 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 2a46c21a9b6..7810b3e0c51 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 @@ -13,31 +13,31 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.WhitelistLogoutHandler; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.security.web.CorsFilter; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.security.web.CorsFilter; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.Links; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -55,7 +55,6 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.web.PortResolverImpl; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.savedrequest.DefaultSavedRequest; @@ -185,6 +184,36 @@ protected void setZoneConfiguration(IdentityZoneConfiguration configuration) { provisioning.update(uaaZone); } + protected void setPrompts(List prompts) { + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + IdentityZoneConfiguration config = uaaZone.getConfig(); + config.setPrompts(prompts); + setZoneConfiguration(config); + } + + protected List getPrompts() { + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + IdentityZoneConfiguration config = uaaZone.getConfig(); + return config.getPrompts(); + } + + protected Links.Logout getLogout() { + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + IdentityZoneConfiguration config = uaaZone.getConfig(); + return config.getLinks().getLogout(); + } + + protected void setLogout(Links.Logout logout) { + IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); + IdentityZoneConfiguration config = uaaZone.getConfig(); + config.getLinks().setLogout(logout); + setZoneConfiguration(config); + } + @Test public void testLogin_When_DisableInternalUserManagement_Is_True() throws Exception { setDisableInternalUserManagement(true); @@ -341,92 +370,105 @@ public void testLogOutIgnoreRedirectParameter() throws Exception { @Test public void testLogOutEnableRedirectParameter() throws Exception { - SimpleUrlLogoutSuccessHandler logoutSuccessHandler = getWebApplicationContext().getBean(SimpleUrlLogoutSuccessHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + setLogout(logout); try { getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://www.google.com")); } finally { - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + setLogout(original); } } @Test public void testLogOutWhitelistedRedirectParameter() throws Exception { - WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); - logoutSuccessHandler.setWhitelist(Arrays.asList("https://www.google.com")); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + logout.setWhitelist(Arrays.asList("https://www.google.com")); + setLogout(logout); try { getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://www.google.com")); } finally { - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + setLogout(original); } } @Test public void testLogOutNotWhitelistedRedirectParameter() throws Exception { - WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); - logoutSuccessHandler.setWhitelist(Arrays.asList("https://www.yahoo.com")); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + logout.setWhitelist(Arrays.asList("https://www.yahoo.com")); + setLogout(logout); try { getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); } finally { - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + setLogout(original); } } @Test public void testLogOutNullWhitelistedRedirectParameter() throws Exception { - WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); - logoutSuccessHandler.setWhitelist(null); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + logout.setWhitelist(null); + setLogout(logout); try { getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://www.google.com")); } finally { - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + setLogout(original); } } @Test public void testLogOutEmptyWhitelistedRedirectParameter() throws Exception { - WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); - logoutSuccessHandler.setWhitelist(EMPTY_LIST); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + logout.setWhitelist(EMPTY_LIST); + setLogout(logout); try { getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); } finally { - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true); + setLogout(logout); } } @Test public void testLogOutChangeUrlValue() throws Exception { - SimpleUrlLogoutSuccessHandler logoutSuccessHandler = getWebApplicationContext().getBean(SimpleUrlLogoutSuccessHandler.class); - logoutSuccessHandler.setDefaultTargetUrl("https://www.google.com"); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setRedirectUrl("https://www.google.com"); + setLogout(logout); try { getMockMvc().perform(get("/logout.do")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://www.google.com")); } finally { - logoutSuccessHandler.setDefaultTargetUrl("/login"); + setLogout(original); } } @Test public void testLogOutWithClientRedirect() throws Exception { - WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class); - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false); - List originalWhiteList = logoutSuccessHandler.getWhitelist(); - logoutSuccessHandler.setWhitelist(EMPTY_LIST); + Links.Logout original = getLogout(); + Links.Logout logout = getLogout(); + logout.setDisableRedirectParameter(false); + logout.setWhitelist(EMPTY_LIST); + setLogout(logout); try { String clientId = generator.generate(); String accessToken = mockMvcUtils.getClientOAuthAccessToken(getMockMvc(), "admin", "adminsecret", ""); @@ -457,13 +499,7 @@ public void testLogOutWithClientRedirect() throws Exception { .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); } finally { - String setAlwaysUseDefaultTargetUrl = getWebApplicationContext().getEnvironment().getProperty("logout.redirect.parameter.disable"); - boolean doUseTargetUrl = true; - if ("false".equals(setAlwaysUseDefaultTargetUrl)) { - doUseTargetUrl = false; - } - logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(doUseTargetUrl); - logoutSuccessHandler.setWhitelist(originalWhiteList); + setLogout(original); } } @@ -558,12 +594,11 @@ public void testSignupsAndResetPasswordEnabledWithCustomLinks() throws Exception @Test public void testLoginWithExplicitPrompts() throws Exception { - LoginInfoEndpoint controller = webApplicationContext.getBean(LoginInfoEndpoint.class); - List original = controller.getPrompts(); + List original = getPrompts(); try { Prompt first = new Prompt("how", "text", "How did I get here?"); Prompt second = new Prompt("where", "password", "Where does that highway go to?"); - controller.setPrompts(Arrays.asList(first, second)); + setPrompts(Arrays.asList(first, second)); getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) @@ -572,18 +607,17 @@ public void testLoginWithExplicitPrompts() throws Exception { .andExpect(model().attribute("prompts", hasKey("where"))) .andExpect(model().attribute("prompts", not(hasKey("password")))); } finally { - controller.setPrompts(original); + setPrompts(original); } } @Test public void testLoginWithExplicitJsonPrompts() throws Exception { - LoginInfoEndpoint controller = webApplicationContext.getBean(LoginInfoEndpoint.class); - List original = controller.getPrompts(); + List original = getPrompts(); try { Prompt first = new Prompt("how", "text", "How did I get here?"); Prompt second = new Prompt("where", "password", "Where does that highway go to?"); - controller.setPrompts(Arrays.asList(first, second)); + setPrompts(Arrays.asList(first, second)); getMockMvc().perform(get("/login") .accept(APPLICATION_JSON)) @@ -593,7 +627,7 @@ public void testLoginWithExplicitJsonPrompts() throws Exception { .andExpect(model().attribute("prompts", hasKey("where"))) .andExpect(model().attribute("prompts", not(hasKey("password")))); } finally { - controller.setPrompts(original); + setPrompts(original); } } From a569c7f1217cd03a20a82b26ee4a9352e9f8be4a Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 11:44:43 -0700 Subject: [PATCH 21/87] [skip ci] Add Zone configuration as a documented example to POST/PUT /identity-zones --- docs/UAA-APIs.rst | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index b591021da6f..f80ea73fa54 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -784,17 +784,54 @@ Request body *example* :: "subdomain": "testzone1", "config": { - "tokenPolicy": - { - "accessTokenValidity": 43200, - "refreshTokenValidity": 2592000 + "disableInternalUserManagement": false, + "links": { + "homeRedirect": "http://some.redirect.com/redirect", + "logout": { + "disableRedirectParameter": false, + "redirectParameterName": "redirect", + "redirectUrl": "/configured_login", + "whitelist": [ + "https://url1.domain1.com/logout-success", + "https://url2.domain2.com/logout-success" + ] + }, + "selfService": { + "passwd": "/configured_passwd", + "selfServiceLinksEnabled": false, + "signup": "/configured_signup" + } }, - "samlConfig": - { - "requestSigned": false, + "prompts": [ + { + "name": "username", + "text": "Username", + "type": "text" + }, + { + "name": "password", + "text": "Your Secret", + "type": "password" + }, + { + "name": "passcode", + "text": "One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )", + "type": "password" + } + ], + "samlConfig": { + "certificate": null, + "privateKey": null, + "privateKeyPassword": null, + "requestSigned": true, "wantAssertionSigned": false + }, + "tokenPolicy": { + "accessTokenValidity": 4800, + "keys": {}, + "refreshTokenValidity": 9600 } - }, + } "name": "The Twiglet Zone", "description": "Like the Twilight Zone but tastier." } From f012ea48fd1843fc592cbd647e5ad0d4eddeee2b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 13:46:54 -0700 Subject: [PATCH 22/87] Remove redirect config validations. They are now present in testRootContextDefaults and testPropertyValuesWhenSetInYaml --- .../identity/uaa/login/BootstrapTests.java | 28 ------------------- 1 file changed, 28 deletions(-) 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 844a4b06af5..9e5efe1c519 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 @@ -65,7 +65,6 @@ import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.saml.log.SAMLDefaultLogger; 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; @@ -75,7 +74,6 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -500,32 +498,6 @@ public void testSamlProfileNoData() throws Exception { assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)); assertFalse(context.getBean(SamlIdentityProviderConfigurator.class).isLegacyMetadataTrustCheck()); assertEquals(0, context.getBean(SamlIdentityProviderConfigurator.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 From 470a037e1d8f6c9a26990e27a0698f84bfc6eaab Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 15:06:51 -0700 Subject: [PATCH 23/87] Correct all RST formatting errors --- docs/UAA-APIs.rst | 126 +++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index f80ea73fa54..e030b8e9ec9 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -435,8 +435,7 @@ Request ``POST /oauth/token`` Authorization Basic authentication, client ID and client secret (or ``client_id`` and ``client_secret`` can be provided as url encoded form parameters) -Request Body the ``username`` and ``password`` (form encoded), e.g. - :: +Request Body the ``username`` and ``password`` (form encoded), e.g. :: [client_id=client] [client_secret=clientsecret] @@ -774,7 +773,7 @@ When an Identity Zone is created, an internal Identity Provider is automatically POST and PUT requires the ``zones.write`` or ``zones..admin`` scope. -================ ========================================================================================================== +================ ============================================================================================================= Request ``POST /identity-zones`` or ``PUT /identity-zones/{id}`` Request Header Authorization: Bearer Token containing ``zones.write`` or ``zones..admin`` Request body *example* :: @@ -877,34 +876,34 @@ Response *Codes* :: Fields *Available Fields* :: Identity Zone Fields - ===================== ==================== ======== ======================================================================================================================================================================== - id String(36) Required Unique identifier for this zone, often set to same as subdomain - subdomain String(255) Required Unique subdomain for the running instance. May only contain legal characters for a sub domain name - name String(255) Required Human readable zone name - version int Optional Reserved for future use of E-Tag versioning - description String Optional Description of the zone - created epoch timestamp Auto UAA sets the creation date - last_modified epoch timestamp Auto UAA sets the modification date - - Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration) - ===================== ==================== ======== ======================================================================================================================================================================== - tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details. - samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. - - Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) - ===================== ==================== ======== ======================================================================================================================================================================== - accessTokenValidity int Optional How long the access token is valid for in seconds. - refreshTokenValidity int Optional How long the refresh token is valid for seconds. - - SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) - ===================== ==================== ======== ======================================================================================================================================================================== - requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``. - wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. - certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. - privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. - privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. - - ===================== ==================== ======== ======================================================================================================================================================================== + ============================== ==================== ======== ======================================================================================================================================================================== + id String(36) Required Unique identifier for this zone, often set to same as subdomain + subdomain String(255) Required Unique subdomain for the running instance. May only contain legal characters for a sub domain name + name String(255) Required Human readable zone name + version int Optional Reserved for future use of E-Tag versioning + description String Optional Description of the zone + created epoch timestamp Auto UAA sets the creation date + last_modified epoch timestamp Auto UAA sets the modification date + + Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration) + ============================== ==================== ======== ======================================================================================================================================================================== + tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details. + samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. + + Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) + ============================== ==================== ======== ======================================================================================================================================================================== + accessTokenValidity int Optional How long the access token is valid for in seconds. + refreshTokenValidity int Optional How long the refresh token is valid for seconds. + + SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) + ============================== ==================== ======== ======================================================================================================================================================================== + requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``. + wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. + certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. + privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. + privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. + + ============================= ==================== ======== ======================================================================================================================================================================== Curl Example POST (Token contains ``zones.write`` scope) :: @@ -923,7 +922,7 @@ Curl Example POST (Token contains ``zones.write`` scope) :: -H"Content-Type:application/json" \ -XPUT http://localhost:8080/uaa/identity-zones/testzone1 -================ ========================================================================================================== +================ ============================================================================================================= Note that if you specify a subdomain in mixed or upper case, it will be converted into lower case before stored in the database. @@ -1508,7 +1507,7 @@ Request body *example* :: The ``userName`` / ``origin`` combination is unique in the UAA, but is allowed to change. Each user also has a fixed primary key which is a UUID (stored in the ``id`` field of the core schema). -* Response Body:: +Response Body *example* :: HTTP/1.1 201 Created Content-Type: application/json @@ -1544,12 +1543,12 @@ Request body *example* :: "schemas":["urn:scim:schemas:core:1.0"] } -* Response Codes:: +Response Codes *example* :: - 201 - Created successfully - 400 - Bad Request - unparseable, syntactically incorrect etc - 401 - Unauthorized - Invalid token - 403 - Forbidden - insufficient scope + 201 - Created successfully + 400 - Bad Request - unparseable, syntactically incorrect etc + 401 - Unauthorized - Invalid token + 403 - Forbidden - insufficient scope Fields *Available Fields* :: @@ -1608,7 +1607,7 @@ Request body *example* :: -* Response Body:: +Response Body *example* :: HTTP/1.1 200 Ok Content-Type: application/json @@ -1644,14 +1643,14 @@ Request body *example* :: "schemas":["urn:scim:schemas:core:1.0"] } -* Response Codes:: +Response Codes *example* :: - 201 - Created successfully - 400 - Bad Request - unparseable, syntactically incorrect etc - 401 - Unauthorized - Invalid token - 403 - Forbidden - insufficient scope - 404 - Not Found - non existent ID - 409 - Conflict - If-Match header, version mismatch + 201 - Created successfully + 400 - Bad Request - unparseable, syntactically incorrect etc + 401 - Unauthorized - Invalid token + 403 - Forbidden - insufficient scope + 404 - Not Found - non existent ID + 409 - Conflict - If-Match header, version mismatch Fields *Available Fields* :: @@ -1697,7 +1696,7 @@ Header Authorization Bearer token Header If-Match with the value of the current version of the user, or * to disable version check Scopes Required scim.write -* Response Body:: +Response Body *example* :: HTTP/1.1 200 Ok Content-Type: application/json @@ -1733,12 +1732,12 @@ Scopes Required scim.write "schemas":["urn:scim:schemas:core:1.0"] } -* Response Codes:: +Response Codes *example* :: - 200 - Ok success - 401 - Unauthorized - Invalid token - 403 - Forbidden - insufficient scope - 404 - Not Found - non existent ID + 200 - Ok success + 401 - Unauthorized - Invalid token + 403 - Forbidden - insufficient scope + 404 - Not Found - non existent ID Curl Example DELETE Delete a user:: @@ -1797,14 +1796,14 @@ See `SCIM - Changing Password &redirect_uri=http://redirect.here.after.accept @@ -2223,8 +2222,9 @@ See `SCIM - Deleting Resources `_. @@ -2907,10 +2907,10 @@ Response body ClientDetails - the newly created client ============== =========================================================================== Client Metadata Administration APIs -======================================= +=================================== Add or Modify Client Metadata: ``PUT /oauth/clients/{client_id}/meta`` ------------------------------------- +---------------------------------------------------------------------- ============== =========================================================================== Request ``PUT /oauth/client/{client_id}/meta`` @@ -2930,7 +2930,7 @@ Scope Required ``clients.write`` or ``clients.admin`` Get Client Metadata: ``GET /oauth/clients/{client_id}/meta`` --------------------------------------------------- +------------------------------------------------------------ =============== =============================================================== Request ``GET /oauth/clients/{client_id}/meta`` @@ -2949,7 +2949,7 @@ Scope Required ``clients.read`` or ``clients.admin`` =============== =============================================================== Get All Client Metadata: ``GET /oauth/clients/meta`` --------------------------------------------------- +---------------------------------------------------- =============== =============================================================== Request ``GET /oauth/clients/meta`` From 7243f240a717fc5f74bef2c6859738f9fe3ca088 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 15:08:26 -0700 Subject: [PATCH 24/87] Remove disableInternalUserManagement flag. It was somehow stuck on the provider, and we will leave it there because it is a backwards incompatible change. We will have to deprecate it --- docs/UAA-APIs.rst | 1 - .../identity/uaa/zone/IdentityZoneConfiguration.java | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index e030b8e9ec9..67d21a29926 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -783,7 +783,6 @@ Request body *example* :: "subdomain": "testzone1", "config": { - "disableInternalUserManagement": false, "links": { "homeRedirect": "http://some.redirect.com/redirect", "logout": { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index 870ea688326..4c966c282b9 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -26,7 +26,6 @@ public class IdentityZoneConfiguration { private TokenPolicy tokenPolicy = new TokenPolicy(); private SamlConfig samlConfig = new SamlConfig(); - private boolean disableInternalUserManagement = false; private Links links = new Links(); private List prompts = Arrays.asList( new Prompt("username", "text", "Email"), @@ -57,15 +56,6 @@ public IdentityZoneConfiguration setSamlConfig(SamlConfig samlConfig) { return this; } - public boolean isDisableInternalUserManagement() { - return disableInternalUserManagement; - } - - public IdentityZoneConfiguration setDisableInternalUserManagement(boolean disableInternalUserManagement) { - this.disableInternalUserManagement = disableInternalUserManagement; - return this; - } - public Links getLinks() { return links; } From 3b2526005ebfa38ba7d42e8c578b3587a27ab003 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 9 Feb 2016 15:30:23 -0700 Subject: [PATCH 25/87] [skip ci] fix the header --- docs/UAA-APIs.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 67d21a29926..146444db0a4 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -1,6 +1,6 @@ -================================================== +============================================ User Account and Authentication Service APIs -================================================== +============================================ .. contents:: Table of Contents From c2a51ac39fce2d4e0fdfa0d58a2105e50eee3b07 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 9 Feb 2016 14:53:19 -0800 Subject: [PATCH 26/87] Open "Where to?" links in new tabs [finishes #113130265] https://www.pivotaltracker.com/story/show/113130265 Signed-off-by: Priyata Agrawal --- server/src/main/resources/templates/web/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/resources/templates/web/home.html b/server/src/main/resources/templates/web/home.html index ed3c1a6f73a..061588bebf2 100644 --- a/server/src/main/resources/templates/web/home.html +++ b/server/src/main/resources/templates/web/home.html @@ -13,7 +13,7 @@

Where to?

  • - +
    From dadbf89738652a69902f435a3b5a008142800b1f Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 10 Feb 2016 09:59:04 -0700 Subject: [PATCH 27/87] Add in test case that generates 500 error when internal auth is disabled --- .../identity/uaa/login/LoginMockMvcTests.java | 65 ++++++++++--------- .../uaa/mock/token/TokenMvcMockTests.java | 26 ++++++++ .../identity/uaa/mock/util/MockMvcUtils.java | 61 +++++++++++++++++ 3 files changed, 121 insertions(+), 31 deletions(-) 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 7810b3e0c51..41b896f23c1 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 @@ -95,6 +95,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; @@ -162,56 +163,58 @@ public void testLogin() throws Exception { .andExpect(content().string(containsString("/create_account"))); } + protected void setDisableInternalAuth(boolean disable) { + MockMvcUtils.setDisableInternalAuth(getWebApplicationContext(), getUaa().getId(), disable); + } + protected void setDisableInternalUserManagement(boolean disabled) { - IdentityProviderProvisioning provisioning = webApplicationContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); - uaaIdp.getConfig().setDisableInternalUserManagement(disabled); - provisioning.update(uaaIdp); + MockMvcUtils.setDisableInternalUserManagement(getWebApplicationContext(), getUaa().getId(), disabled); } protected void setSelfServiceLinksEnabled(boolean enabled) { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - IdentityZoneConfiguration config = uaaZone.getConfig(); - config.getLinks().getSelfService().setSelfServiceLinksEnabled(enabled); - setZoneConfiguration(config); + MockMvcUtils.setSelfServiceLinksEnabled(getWebApplicationContext(), getUaa().getId(), enabled); } protected void setZoneConfiguration(IdentityZoneConfiguration configuration) { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - uaaZone.setConfig(configuration); - provisioning.update(uaaZone); + MockMvcUtils.setZoneConfiguration(getWebApplicationContext(), getUaa().getId(), configuration); } protected void setPrompts(List prompts) { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - IdentityZoneConfiguration config = uaaZone.getConfig(); - config.setPrompts(prompts); - setZoneConfiguration(config); + MockMvcUtils.setPrompts(getWebApplicationContext(), getUaa().getId(), prompts); } protected List getPrompts() { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - IdentityZoneConfiguration config = uaaZone.getConfig(); - return config.getPrompts(); + return MockMvcUtils.getPrompts(getWebApplicationContext(), getUaa().getId()); } protected Links.Logout getLogout() { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - IdentityZoneConfiguration config = uaaZone.getConfig(); - return config.getLinks().getLogout(); + return MockMvcUtils.getLogout(getWebApplicationContext(), getUaa().getId()); } protected void setLogout(Links.Logout logout) { - IdentityZoneProvisioning provisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - IdentityZone uaaZone = provisioning.retrieve(getUaa().getId()); - IdentityZoneConfiguration config = uaaZone.getConfig(); - config.getLinks().setLogout(logout); - setZoneConfiguration(config); + MockMvcUtils.setLogout(getWebApplicationContext(), getUaa().getId(), logout); + } + + @Test + public void testLogin_Post_When_DisableInternalUserManagement_Is_True() throws Exception { + ScimUser user = createUser("", adminToken); + setDisableInternalAuth(true); + try { + getMockMvc().perform(post("/login.do") + .with(cookieCsrf()) + .param("username", user.getUserName()) + .param("password", user.getPassword())) + .andDo(print()) + .andExpect(redirectedUrl("/login?error=login_failure")); + } finally { + setDisableInternalAuth(false); + } + getMockMvc().perform(post("/login.do") + .with(cookieCsrf()) + .param("username", user.getUserName()) + .param("password", user.getPassword())) + .andDo(print()) + .andExpect(redirectedUrl("/")); } @Test 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 06ca4ac76e6..69a6ae7feae 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 @@ -18,6 +18,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; import org.cloudfoundry.identity.uaa.oauth.SignerProvider; import org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationEndpoint; @@ -245,6 +246,31 @@ protected ScimGroup createIfNotExist(String scope, String zoneId) { } } + @Test + public void getOauthToken_Password_Grant_When_UAA_Provider_is_Disabled() throws Exception { + String clientId = "testclient"+new RandomValueStringGenerator().generate(); + setUpClients(clientId, "uaa.user", "uaa.user", "password", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); + + String username = "testuser"+new RandomValueStringGenerator().generate(); + String userScopes = "uaa.user"; + setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); + MockMvcUtils.setDisableInternalAuth(getWebApplicationContext(), IdentityZone.getUaa().getId(), true); + try { + getMockMvc().perform(post("/oauth/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + SAml .param(OAuth2Utils.RESPONSE_TYPE, "token") + .param(OAuth2Utils.GRANT_TYPE, "password") + .param(OAuth2Utils.CLIENT_ID, clientId) + .param("client_secret", SECRET) + .param("username", username) + .param("password", SECRET)) + .andExpect(status().isUnauthorized()); + } finally { + MockMvcUtils.setDisableInternalAuth(getWebApplicationContext(), IdentityZone.getUaa().getId(), false); + } + } + + @Test public void getOauthToken_usingAuthCode_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { String clientId = "testclient"+new RandomValueStringGenerator().generate(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 6408c277088..2006b673a5f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; +import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -45,8 +46,11 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.cloudfoundry.identity.uaa.zone.Links; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.Assert; import org.springframework.context.ApplicationContext; @@ -212,6 +216,63 @@ public static String extractInvitationCode(String inviteLink) throws Exception { } } + public static void setDisableInternalAuth(ApplicationContext context, String zoneId, boolean disable) { + IdentityProviderProvisioning provisioning = context.getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, zoneId); + uaaIdp.setActive(!disable); + provisioning.update(uaaIdp); + } + + public static void setDisableInternalUserManagement(ApplicationContext context, String zoneId, boolean disabled) { + IdentityProviderProvisioning provisioning = context.getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, zoneId); + uaaIdp.getConfig().setDisableInternalUserManagement(disabled); + provisioning.update(uaaIdp); + } + + public static void setSelfServiceLinksEnabled(ApplicationContext context, String zoneId,boolean enabled) { + IdentityZoneConfiguration config = getZoneConfiguration(context, zoneId); + config.getLinks().getSelfService().setSelfServiceLinksEnabled(enabled); + setZoneConfiguration(context, zoneId, config); + } + + public static void setZoneConfiguration(ApplicationContext context, String zoneId, IdentityZoneConfiguration configuration) { + IdentityZoneProvisioning provisioning = context.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(zoneId); + uaaZone.setConfig(configuration); + provisioning.update(uaaZone); + } + + public static IdentityZoneConfiguration getZoneConfiguration(ApplicationContext context, String zoneId) { + IdentityZoneProvisioning provisioning = context.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(zoneId); + return uaaZone.getConfig(); + } + + public static void setPrompts(ApplicationContext context, String zoneId, List prompts) { + IdentityZoneConfiguration config = getZoneConfiguration(context, zoneId); + config.setPrompts(prompts); + setZoneConfiguration(context, zoneId, config); + } + + public static List getPrompts(ApplicationContext context, String zoneId) { + IdentityZoneConfiguration config = getZoneConfiguration(context, zoneId); + return config.getPrompts(); + } + + public static Links.Logout getLogout(ApplicationContext context, String zoneId) { + IdentityZoneConfiguration config = getZoneConfiguration(context, zoneId); + return config.getLinks().getLogout(); + } + + public static void setLogout(ApplicationContext context, String zoneId, Links.Logout logout) { + IdentityZoneProvisioning provisioning = context.getBean(IdentityZoneProvisioning.class); + IdentityZone uaaZone = provisioning.retrieve(zoneId); + IdentityZoneConfiguration config = uaaZone.getConfig(); + config.getLinks().setLogout(logout); + setZoneConfiguration(context, zoneId, config); + } + public static InvitationsResponse sendRequestWithTokenAndReturnResponse(ApplicationContext context, MockMvc mockMvc, String token, From 92430efdd3b210fd9747284aed72d5998c16baa0 Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Wed, 10 Feb 2016 10:35:39 -0800 Subject: [PATCH 28/87] make return value part of response body [#113134351] https://www.pivotaltracker.com/story/show/113134351 Signed-off-by: Jonathan Lo --- .../uaa/client/ClientMetadataAdminEndpoints.java | 4 ++++ .../ClientMetadataAdminEndpointsMockMvcTest.java | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java index 7e68cab59ec..d8e7051b3a3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.View; @@ -43,6 +44,7 @@ public class ClientMetadataAdminEndpoints { @RequestMapping(value = "/oauth/clients/{client}/meta", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) + @ResponseBody public ClientMetadata retrieveClientMetadata(@PathVariable("client") String clientId) { try { return clientMetadataProvisioning.retrieve(clientId); @@ -53,12 +55,14 @@ public ClientMetadata retrieveClientMetadata(@PathVariable("client") String clie @RequestMapping(value = "/oauth/clients/meta", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) + @ResponseBody public List retrieveAllClientMetadata() { return clientMetadataProvisioning.retrieveAll(); } @RequestMapping(value = "/oauth/clients/{client}/meta", method = RequestMethod.PUT) @ResponseStatus(HttpStatus.OK) + @ResponseBody public ClientMetadata updateClientMetadata(@RequestBody ClientMetadata clientMetadata, @PathVariable("client") String clientId) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java index 02b14312aa7..be8ab434897 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertThat; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_PLAIN; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -131,6 +132,21 @@ public void getAllClientMetadata() throws Exception { assertThat(clientMetadataList, PredicateMatcher.has(m -> m.getClientId().equals(clientId4) && m.getAppIcon().equals(client4Metadata.getAppIcon()) && m.getAppLaunchUrl().equals(client4Metadata.getAppLaunchUrl()) && m.isShowOnHomePage() == client4Metadata.isShowOnHomePage())); } + @Test + public void missingAcceptHeader_isOk() throws Exception { + getMockMvc().perform(get("/oauth/clients/meta") + .header("Authorization", "Bearer " + getUserAccessToken(generator.generate()))) + .andExpect(status().isOk()); + } + + @Test + public void wrongAcceptHeader_isNotAcceptable() throws Exception { + getMockMvc().perform(get("/oauth/clients/meta") + .header("Authorization", "Bearer " + getUserAccessToken(generator.generate())) + .accept(TEXT_PLAIN)) + .andExpect(status().isNotAcceptable()); + } + @Test public void updateClientMetadata() throws Exception { String clientId = generator.generate(); From 3ae2e4d6de3d561135d15e14708a70d441e17365 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 9 Feb 2016 13:46:00 -0800 Subject: [PATCH 29/87] Add parameter to retrieve scim entities as part of GET /members [#113200357] https://www.pivotaltracker.com/story/show/113200357 Signed-off-by: Priyata Agrawal --- .../identity/uaa/scim/ScimGroupMember.java | 28 ++++++- .../uaa/scim/ScimGroupMembershipManager.java | 3 +- .../scim/bootstrap/ScimGroupBootstrap.java | 2 +- .../scim/endpoints/ScimGroupEndpoints.java | 13 +-- .../jdbc/JdbcScimGroupMembershipManager.java | 31 ++++--- .../JdbcScimGroupMembershipManagerTests.java | 8 +- .../IdentityZoneEndpointsMockMvcTests.java | 2 +- .../ScimGroupEndpointsMockMvcTests.java | 84 +++++++++++++++++-- 8 files changed, 138 insertions(+), 33 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java index 3b5c936f979..def072edfa8 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java @@ -21,7 +21,15 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; @JsonInclude(JsonInclude.Include.NON_NULL) -public class ScimGroupMember { +public class ScimGroupMember { + + public TEntity getEntity() { + return entity; + } + + public void setEntity(TEntity entity) { + this.entity = entity; + } @JsonInclude(JsonInclude.Include.NON_NULL) public enum Role { @@ -43,6 +51,8 @@ public enum Type { private Type type; + private TEntity entity; + @JsonIgnore private List roles; @@ -118,4 +128,20 @@ public ScimGroupMember(String memberId, Type type, List roles) { this.type = type; this.roles = roles; } + + public ScimGroupMember(TEntity entity) { + this(entity, GROUP_MEMBER); + } + + public ScimGroupMember(TEntity entity, List roles) { + this(entity.getId(), getEntityType(entity), roles); + this.entity = entity; + } + + private static Type getEntityType(ScimCore entity) { + Type type = null; + if(entity instanceof ScimGroup) { type = Type.GROUP; } + else if(entity instanceof ScimUser) { type = Type.USER; } + return type; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java index 2da05ff1f44..850208cf947 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java @@ -39,10 +39,11 @@ ScimGroupMember addMember(String groupId, ScimGroupMember member) throws ScimRes * Retrieve all members of a group * * @param groupId + * @param includeEntities * @return * @throws ScimResourceNotFoundException */ - List getMembers(String groupId) throws ScimResourceNotFoundException; + List getMembers(String groupId, boolean includeEntities) throws ScimResourceNotFoundException; /** * Retrieve members that have the specified authority on the group diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java index d6e9e42d346..12b0179a224 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java @@ -255,7 +255,7 @@ ScimGroup getGroup(String name) { List g = scimGroupProvisioning.query(String.format(GROUP_BY_NAME_FILTER, name)); if (g != null && !g.isEmpty()) { ScimGroup gr = g.get(0); - gr.setMembers(membershipManager.getMembers(gr.getId())); + gr.setMembers(membershipManager.getMembers(gr.getId(), false)); return gr; } logger.debug("could not find group with name"); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index 2601a1f1d4e..549ed511191 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -127,7 +127,7 @@ private List filterForCurrentUser(List input, int startInd boolean needMore = response.size() < expectedResponseSize; while (needMore && startIndex <= input.size()) { for (ScimGroup group : UaaPagingUtils.subList(input, startIndex, count)) { - group.setMembers(membershipManager.getMembers(group.getId())); + group.setMembers(membershipManager.getMembers(group.getId(), false)); response.add(group); needMore = response.size() < expectedResponseSize; if (!needMore) { @@ -314,7 +314,7 @@ private String getGroupId(String displayName) { public ScimGroup getGroup(@PathVariable String groupId, HttpServletResponse httpServletResponse) { logger.debug("retrieving group with id: " + groupId); ScimGroup group = dao.retrieve(groupId); - group.setMembers(membershipManager.getMembers(groupId)); + group.setMembers(membershipManager.getMembers(groupId, false)); addETagHeader(httpServletResponse, group); return group; } @@ -336,7 +336,7 @@ public ScimGroup createGroup(@RequestBody ScimGroup group, HttpServletResponse h } } } - created.setMembers(membershipManager.getMembers(created.getId())); + created.setMembers(membershipManager.getMembers(created.getId(), false)); addETagHeader(httpServletResponse, created); return created; } @@ -361,7 +361,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String } else { membershipManager.removeMembersByGroupId(updated.getId()); } - updated.setMembers(membershipManager.getMembers(updated.getId())); + updated.setMembers(membershipManager.getMembers(updated.getId(), false)); addETagHeader(httpServletResponse, updated); return updated; } catch (IncorrectResultSizeDataAccessException ex) { @@ -487,9 +487,10 @@ public ResponseEntity getGroupMembership(@PathVariable String g } @RequestMapping("/Groups/{groupId}/members") - public ResponseEntity> listGroupMemberships(@PathVariable String groupId) { + public ResponseEntity> listGroupMemberships(@PathVariable String groupId, + @RequestParam(required = false, defaultValue = "false") boolean returnEntities) { dao.retrieve(groupId); - List members = membershipManager.getMembers(groupId); + List members = membershipManager.getMembers(groupId, returnEntities); return new ResponseEntity<>(members, HttpStatus.OK); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index c8d92b96b69..3feb2fdb2c0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -34,6 +34,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException; import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException; @@ -213,14 +214,24 @@ public void setValues(PreparedStatement ps) throws SQLException { } @Override - public List getMembers(final String groupId) throws ScimResourceNotFoundException { - List result = jdbcTemplate.query(GET_MEMBERS_SQL, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, groupId); - ps.setString(2, IdentityZoneHolder.get().getId()); - } + public List getMembers(final String groupId, boolean includeEntities) throws ScimResourceNotFoundException { + List result = jdbcTemplate.query(GET_MEMBERS_SQL, ps -> { + ps.setString(1, groupId); + ps.setString(2, IdentityZoneHolder.get().getId()); }, rowMapper); + + if(includeEntities) { + for(ScimGroupMember member : result) { + if(member.getType().equals(ScimGroupMember.Type.USER)) { + ScimUser user = userProvisioning.retrieve(member.getMemberId()); + member.setEntity(user); + } else if(member.getType().equals(ScimGroupMember.Type.GROUP)) { + ScimGroup group = groupProvisioning.retrieve(member.getMemberId()); + member.setEntity(group); + } + } + } + return result; } @@ -326,7 +337,7 @@ public void setValues(PreparedStatement ps) throws SQLException { @Override public List updateOrAddMembers(String groupId, List members) throws ScimResourceNotFoundException { - List currentMembers = getMembers(groupId); + List currentMembers = getMembers(groupId, false); logger.debug("current-members: " + currentMembers + ", in request: " + members); List currentMembersToRemove = new ArrayList<>(currentMembers); @@ -350,7 +361,7 @@ public List updateOrAddMembers(String groupId, List removeMembersByGroupId(final String groupId) throws ScimResourceNotFoundException { - List members = getMembers(groupId); + List members = getMembers(groupId, false); logger.debug("removing " + members + " members from group: " + groupId); int deleted = jdbcTemplate.update(DELETE_MEMBERS_IN_GROUP_SQL, new PreparedStatementSetter() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index 767c356aa26..b5505b7a497 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -386,11 +386,11 @@ public void canGetMembers() throws Exception { addMember("g1", "g2", "GROUP", "READER"); addMember("g3", "m2", "USER", "READER,WRITER"); - List members = dao.getMembers("g1"); + List members = dao.getMembers("g1", false); assertNotNull(members); assertEquals(2, members.size()); - members = dao.getMembers("g2"); + members = dao.getMembers("g2", false); assertNotNull(members); assertEquals(0, members.size()); @@ -402,7 +402,7 @@ public void canGetMembers_Fails_In_Other_Zone() throws Exception { addMember("g1", "g2", "GROUP", "READER"); addMember("g3", "m2", "USER", "READER,WRITER"); IdentityZoneHolder.set(MultitenancyFixture.identityZone(generator.generate(), generator.generate())); - assertEquals(0, dao.getMembers("g1").size()); + assertEquals(0, dao.getMembers("g1", false).size()); } @Test @@ -411,7 +411,7 @@ public void testBackwardsCompatibilityToMemberAuthorities() { addMember("g1", "g2", "GROUP", "member"); addMember("g1", "m2", "USER", "READER,write"); - List members = dao.getMembers("g1"); + List members = dao.getMembers("g1", false); assertNotNull(members); assertEquals(3, members.size()); List readers = new ArrayList(), writers = new ArrayList(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 1a8c73d657a..1d7bd68ccf7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -542,7 +542,7 @@ public void test_delete_zone_cleans_db() throws Exception { assertEquals(zone.getId(), group.getZoneId()); assertNotNull(groupProvisioning.retrieve(group.getId())); assertEquals("Delete Test Group", groupProvisioning.retrieve(group.getId()).getDisplayName()); - assertEquals(1, membershipManager.getMembers(group.getId()).size()); + assertEquals(1, membershipManager.getMembers(group.getId(), false).size()); //failed authenticated user getMockMvc().perform( diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index c593f2f85f4..e3a3fbef91b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -97,7 +97,7 @@ public class ScimGroupEndpointsMockMvcTests extends InjectedMockContextTest { private JdbcTemplate template; private ScimExternalGroupBootstrap bootstrap; - private List ephemeralUserIds = new ArrayList<>(); + private ArrayList ephemeralResources = new ArrayList<>(); @Before public void setUp() throws Exception { @@ -137,10 +137,10 @@ public void setUp() throws Exception { @After public void cleanUp() { - for(String userId : ephemeralUserIds) { - template.update("delete from group_membership where member_id = ? and member_type = 'USER'", userId); + for(Object[] resource : ephemeralResources) { + template.update("delete from group_membership where member_id = ? and member_type = ?", resource); } - ephemeralUserIds.clear(); + ephemeralResources.clear(); } @Test @@ -894,7 +894,8 @@ public void get_group_membership_nonexistent_user() throws Exception { @Test public void get_all_group_memberships() throws Exception { String groupId = getGroupId("scim.write"); - ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList("scim.write"))); + ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); + ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/") .header("Authorization", "Bearer " + scimReadToken); @@ -902,10 +903,11 @@ public void get_all_group_memberships() throws Exception { .andExpect(status().isOk()) .andReturn(); String responseContent = mvcResult.getResponse().getContentAsString(); - Set retrievedMembers = ((List>) JsonUtils.readValue(responseContent, new TypeReference>>() {})) - .stream().map(m -> JsonUtils.writeValueAsString(m)).collect(Collectors.toSet()); + List listMembers = JsonUtils.readValue(responseContent, new TypeReference>() {}); + Set retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o)).collect(Collectors.toSet()); Matcher> containsExpectedMembers = containsInAnyOrder( + JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup.getId(), ScimGroupMember.Type.GROUP, Arrays.asList(ScimGroupMember.Role.MEMBER))), JsonUtils.writeValueAsString(new ScimGroupMember(secondUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER))), JsonUtils.writeValueAsString(new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER))) ); @@ -913,6 +915,31 @@ public void get_all_group_memberships() throws Exception { Assert.assertThat(retrievedMembers, containsExpectedMembers); } + @Test + public void get_group_memberships_with_entities() throws Exception { + String groupId = getGroupId("scim.write"); + ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); + ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); + + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/") + .header("Authorization", "Bearer " + scimReadToken) + .param("returnEntities", "true"); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + String responseContent = mvcResult.getResponse().getContentAsString(); + List listMembers = JsonUtils.readValue(responseContent, new TypeReference>() {}); + Set retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o)).collect(Collectors.toSet()); + + Matcher> containsExpectedMembers = containsInAnyOrder( + JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup)), + JsonUtils.writeValueAsString(new ScimGroupMember(secondUser)), + JsonUtils.writeValueAsString(new ScimGroupMember(scimUser)) + ); + + Assert.assertThat(retrievedMembers, containsExpectedMembers); + } + @Test public void get_group_memberships_for_nonexistent_group() throws Exception { MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/") @@ -1295,7 +1322,7 @@ private ScimUser createUserAndAddToGroups(IdentityZone zone, Set groupNa IdentityZoneHolder.set(zone); } user = usersRepository.createUser(user, "password"); - ephemeralUserIds.add(user.getId()); + ephemeralResources.add(new String[] {user.getId(), "USER"}); Collection scimUserGroups = new LinkedList<>(); for (String groupName : groupNames) { @@ -1317,11 +1344,50 @@ private ScimUser createUserAndAddToGroups(IdentityZone zone, Set groupNa scimGroupMembershipManager.addMember(group.getId(), member); } catch (MemberAlreadyExistsException x) {} } - user.setGroups(scimUserGroups); } finally { IdentityZoneHolder.set(originalZone); } return user; } + private ScimGroup createGroupWithinGroups(IdentityZone zone, Set groupNames) throws Exception { + if (zone == null) { + zone = IdentityZone.getUaa(); + } + ScimGroupProvisioning groupRepository = getWebApplicationContext().getBean(ScimGroupProvisioning.class); + ScimGroup newGroup = new ScimGroup(null, generator.generate(), zone.getId()); + IdentityZone originalZone = IdentityZoneHolder.get(); + try { + if (zone != null) { + IdentityZoneHolder.set(zone); + } + newGroup = groupRepository.create(newGroup); + ephemeralResources.add(new String[] {newGroup.getId(), "GROUP"}); + + Collection scimUserGroups = new LinkedList<>(); + for (String groupName : groupNames) { + List scimGroups = groupRepository.query("displayName eq \""+ groupName +"\""); + ScimUser.Group scimUserGroup; + ScimGroup group; + if (scimGroups==null || scimGroups.isEmpty()) { + group = new ScimGroup(null, groupName,IdentityZoneHolder.get().getId()); + group = groupRepository.create(group); + scimUserGroup = new ScimUser.Group(group.getId(), groupName); + } else { + group = scimGroups.get(0); + scimUserGroup = new ScimUser.Group(scimGroups.get(0).getId(), groupName); + } + scimUserGroups.add(scimUserGroup); + ScimGroupMembershipManager scimGroupMembershipManager = getWebApplicationContext().getBean(ScimGroupMembershipManager.class); + ScimGroupMember member = new ScimGroupMember(newGroup.getId(), ScimGroupMember.Type.GROUP, Arrays.asList(ScimGroupMember.Role.READER)); + try { + scimGroupMembershipManager.addMember(group.getId(), member); + } catch (MemberAlreadyExistsException x) {} + } + } finally { + IdentityZoneHolder.set(originalZone); + } + return newGroup; + } + } From 863d5dd11debaec63cfb042158b0364b2a19b992 Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Tue, 9 Feb 2016 14:30:58 -0800 Subject: [PATCH 30/87] Add SCIM filter to /Groups/id/members GET [#113200357] https://www.pivotaltracker.com/story/show/113200357 Signed-off-by: Jeremy Coffield --- .../uaa/resources/jdbc/AbstractQueryable.java | 2 +- .../uaa/scim/ScimGroupMembershipManager.java | 6 ++--- .../scim/bootstrap/ScimGroupBootstrap.java | 2 +- .../scim/endpoints/ScimGroupEndpoints.java | 13 +++++----- .../jdbc/JdbcScimGroupMembershipManager.java | 26 ++++++++++--------- .../JdbcScimGroupMembershipManagerTests.java | 8 +++--- .../IdentityZoneEndpointsMockMvcTests.java | 2 +- .../ScimGroupEndpointsMockMvcTests.java | 23 ++++++++++++++++ 8 files changed, 54 insertions(+), 28 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java index 4e5b5feaa4e..244817e6495 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java @@ -28,7 +28,7 @@ public abstract class AbstractQueryable implements Queryable { private JdbcPagingListFactory pagingListFactory; - private RowMapper rowMapper; + protected RowMapper rowMapper; private final Log logger = LogFactory.getLog(getClass()); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java index 850208cf947..4d76c4f00e6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java @@ -39,11 +39,11 @@ ScimGroupMember addMember(String groupId, ScimGroupMember member) throws ScimRes * Retrieve all members of a group * * @param groupId - * @param includeEntities - * @return + * @param filter + *@param includeEntities @return * @throws ScimResourceNotFoundException */ - List getMembers(String groupId, boolean includeEntities) throws ScimResourceNotFoundException; + List getMembers(String groupId, String filter, boolean includeEntities) throws ScimResourceNotFoundException; /** * Retrieve members that have the specified authority on the group diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java index 12b0179a224..e957b6c8708 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java @@ -255,7 +255,7 @@ ScimGroup getGroup(String name) { List g = scimGroupProvisioning.query(String.format(GROUP_BY_NAME_FILTER, name)); if (g != null && !g.isEmpty()) { ScimGroup gr = g.get(0); - gr.setMembers(membershipManager.getMembers(gr.getId(), false)); + gr.setMembers(membershipManager.getMembers(gr.getId(), null, false)); return gr; } logger.debug("could not find group with name"); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index 549ed511191..7f262e8903f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -127,7 +127,7 @@ private List filterForCurrentUser(List input, int startInd boolean needMore = response.size() < expectedResponseSize; while (needMore && startIndex <= input.size()) { for (ScimGroup group : UaaPagingUtils.subList(input, startIndex, count)) { - group.setMembers(membershipManager.getMembers(group.getId(), false)); + group.setMembers(membershipManager.getMembers(group.getId(), null, false)); response.add(group); needMore = response.size() < expectedResponseSize; if (!needMore) { @@ -314,7 +314,7 @@ private String getGroupId(String displayName) { public ScimGroup getGroup(@PathVariable String groupId, HttpServletResponse httpServletResponse) { logger.debug("retrieving group with id: " + groupId); ScimGroup group = dao.retrieve(groupId); - group.setMembers(membershipManager.getMembers(groupId, false)); + group.setMembers(membershipManager.getMembers(groupId, null, false)); addETagHeader(httpServletResponse, group); return group; } @@ -336,7 +336,7 @@ public ScimGroup createGroup(@RequestBody ScimGroup group, HttpServletResponse h } } } - created.setMembers(membershipManager.getMembers(created.getId(), false)); + created.setMembers(membershipManager.getMembers(created.getId(), null, false)); addETagHeader(httpServletResponse, created); return created; } @@ -361,7 +361,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String } else { membershipManager.removeMembersByGroupId(updated.getId()); } - updated.setMembers(membershipManager.getMembers(updated.getId(), false)); + updated.setMembers(membershipManager.getMembers(updated.getId(), null, false)); addETagHeader(httpServletResponse, updated); return updated; } catch (IncorrectResultSizeDataAccessException ex) { @@ -488,9 +488,10 @@ public ResponseEntity getGroupMembership(@PathVariable String g @RequestMapping("/Groups/{groupId}/members") public ResponseEntity> listGroupMemberships(@PathVariable String groupId, - @RequestParam(required = false, defaultValue = "false") boolean returnEntities) { + @RequestParam(required = false, defaultValue = "false") boolean returnEntities, + @RequestParam(required = false, defaultValue = "") String filter) { dao.retrieve(groupId); - List members = membershipManager.getMembers(groupId, returnEntities); + List members = membershipManager.getMembers(groupId, filter, returnEntities); return new ResponseEntity<>(members, HttpStatus.OK); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index 3feb2fdb2c0..f199923617b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -71,8 +71,6 @@ public class JdbcScimGroupMembershipManager extends AbstractQueryable rowMapper = new ScimGroupMemberRowMapper(); - private ScimUserProvisioning userProvisioning; private ScimGroupProvisioning groupProvisioning; @@ -214,11 +210,17 @@ public void setValues(PreparedStatement ps) throws SQLException { } @Override - public List getMembers(final String groupId, boolean includeEntities) throws ScimResourceNotFoundException { - List result = jdbcTemplate.query(GET_MEMBERS_SQL, ps -> { - ps.setString(1, groupId); - ps.setString(2, IdentityZoneHolder.get().getId()); - }, rowMapper); + public List getMembers(final String groupId, String filter, boolean includeEntities) throws ScimResourceNotFoundException { + String scopedFilter; + if (StringUtils.hasText(filter)) { + // validate filter syntax + getQueryConverter().convert(filter, "member_id", true); + scopedFilter = String.format("group_id eq '%s' and (%s)", groupId, filter); + } + else { + scopedFilter = String.format("group_id eq '%s'", groupId); + } + List result = query(scopedFilter, "member_id", true); if(includeEntities) { for(ScimGroupMember member : result) { @@ -337,7 +339,7 @@ public void setValues(PreparedStatement ps) throws SQLException { @Override public List updateOrAddMembers(String groupId, List members) throws ScimResourceNotFoundException { - List currentMembers = getMembers(groupId, false); + List currentMembers = getMembers(groupId, null, false); logger.debug("current-members: " + currentMembers + ", in request: " + members); List currentMembersToRemove = new ArrayList<>(currentMembers); @@ -361,7 +363,7 @@ public List updateOrAddMembers(String groupId, List removeMembersByGroupId(final String groupId) throws ScimResourceNotFoundException { - List members = getMembers(groupId, false); + List members = getMembers(groupId, null, false); logger.debug("removing " + members + " members from group: " + groupId); int deleted = jdbcTemplate.update(DELETE_MEMBERS_IN_GROUP_SQL, new PreparedStatementSetter() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index b5505b7a497..5cb4c8c6f37 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -386,11 +386,11 @@ public void canGetMembers() throws Exception { addMember("g1", "g2", "GROUP", "READER"); addMember("g3", "m2", "USER", "READER,WRITER"); - List members = dao.getMembers("g1", false); + List members = dao.getMembers("g1", null, false); assertNotNull(members); assertEquals(2, members.size()); - members = dao.getMembers("g2", false); + members = dao.getMembers("g2", null, false); assertNotNull(members); assertEquals(0, members.size()); @@ -402,7 +402,7 @@ public void canGetMembers_Fails_In_Other_Zone() throws Exception { addMember("g1", "g2", "GROUP", "READER"); addMember("g3", "m2", "USER", "READER,WRITER"); IdentityZoneHolder.set(MultitenancyFixture.identityZone(generator.generate(), generator.generate())); - assertEquals(0, dao.getMembers("g1", false).size()); + assertEquals(0, dao.getMembers("g1", null, false).size()); } @Test @@ -411,7 +411,7 @@ public void testBackwardsCompatibilityToMemberAuthorities() { addMember("g1", "g2", "GROUP", "member"); addMember("g1", "m2", "USER", "READER,write"); - List members = dao.getMembers("g1", false); + List members = dao.getMembers("g1", null, false); assertNotNull(members); assertEquals(3, members.size()); List readers = new ArrayList(), writers = new ArrayList(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 1d7bd68ccf7..c60f535ec4d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -542,7 +542,7 @@ public void test_delete_zone_cleans_db() throws Exception { assertEquals(zone.getId(), group.getZoneId()); assertNotNull(groupProvisioning.retrieve(group.getId())); assertEquals("Delete Test Group", groupProvisioning.retrieve(group.getId()).getDisplayName()); - assertEquals(1, membershipManager.getMembers(group.getId(), false).size()); + assertEquals(1, membershipManager.getMembers(group.getId(), null, false).size()); //failed authenticated user getMockMvc().perform( diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index e3a3fbef91b..2c31b6d3b97 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -940,6 +940,29 @@ public void get_group_memberships_with_entities() throws Exception { Assert.assertThat(retrievedMembers, containsExpectedMembers); } + @Test + public void get_filtered_group_memberships() throws Exception { + String groupId = getGroupId("scim.write"); + ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); + ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton("scim.write")); + + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/") + .header("Authorization", "Bearer " + scimReadToken) + .param("filter", "member_type eq 'GROUP'"); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + String responseContent = mvcResult.getResponse().getContentAsString(); + List listMembers = JsonUtils.readValue(responseContent, new TypeReference>() {}); + Set retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o)).collect(Collectors.toSet()); + + Matcher> containsExpectedMembers = containsInAnyOrder( + JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup.getId(), ScimGroupMember.Type.GROUP, Arrays.asList(ScimGroupMember.Role.MEMBER))) + ); + + Assert.assertThat(retrievedMembers, containsExpectedMembers); + } + @Test public void get_group_memberships_for_nonexistent_group() throws Exception { MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/") From 7f477fedd20f7d5615dae7a953a702d51e7eaf08 Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Tue, 9 Feb 2016 17:40:25 -0800 Subject: [PATCH 31/87] Wrap immutable list in an ArrayList to prevent invalid operation [#113200357] https://www.pivotaltracker.com/story/show/113200357 Signed-off-by: Jeremy Coffield --- .../identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index f199923617b..27b6f5f472b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -234,7 +234,7 @@ public List getMembers(final String groupId, String filter, boo } } - return result; + return new ArrayList<>(result); } @Override From 6998f0783871e91cae7f2c0989a30c03a7012b51 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 10 Feb 2016 11:29:41 -0800 Subject: [PATCH 32/87] Fix casing bug in JDBC paging list [#113200357] https://www.pivotaltracker.com/story/show/113200357 Signed-off-by: Madhura Bhave --- .../identity/uaa/resources/jdbc/JdbcPagingList.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java index cb7fb38a87b..c48aea343e0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java @@ -104,10 +104,12 @@ public List subList(int fromIndex, int toIndex) { } private String getCountSql(String sql) { - String result = sql.toLowerCase().replaceAll("select (.*?) from (.*)", "select count(*) from $2"); - if (result.contains("order by")) { - result = result.substring(0, result.lastIndexOf("order by")); + String result = sql.replaceAll("(?i)select (.*?) from (.*)", "select count(*) from $2"); + int orderByPos = result.toLowerCase().lastIndexOf("order by"); + if (orderByPos >= 0) { + result = result.substring(0, orderByPos); } + return result; } From 9cbb965746a9796dbd4464b7b1ac210848d7256b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 10 Feb 2016 14:15:03 -0700 Subject: [PATCH 33/87] When there are no authentication providers, throw a ProviderNotFoundException https://www.pivotaltracker.com/story/show/113134303 [#113134303] --- .../authentication/manager/ChainedAuthenticationManager.java | 4 ++++ .../identity/uaa/mock/token/TokenMvcMockTests.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java index 83911962ac8..c775bcad331 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -63,6 +64,9 @@ public Authentication authenticate(Authentication authentication) throws Authent AuthenticationException lastException = null; boolean lastResult = false; boolean shallContinue = true; + if (delegates==null || delegates.length==0) { + throw new ProviderNotFoundException("No available authentication providers."); + } for (int i=0; shallContinue && i Date: Wed, 10 Feb 2016 14:30:54 -0700 Subject: [PATCH 34/87] Reference the Oauth2 documentation rather then adding the same information here. https://www.pivotaltracker.com/story/show/109325232 [finishes #109325232] --- docs/UAA-APIs.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 146444db0a4..c8b7ef03d2b 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -150,6 +150,7 @@ Several modes of operation and other optional features can be set in configurati OAuth2 Token Endpoint: ``POST /oauth/token`` ============================================ An OAuth2 defined endpoint which accepts authorization code or refresh tokens and provides access_tokens, in the case of authorization code grant. This endpoint also supports client credentials and password grant, which takes the client id and client secret for the former, in addition to username and password in case of the latter. The access_tokens can then be used to gain access to resources within a resource server. +We support all standard OAuth2 grant types, documented in https://tools.ietf.org/html/rfc6749 * Request: ``POST /oauth/token`` From 6b60aea67dc8048ea8fd51c624a5ae54c43b5bf2 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 10 Feb 2016 14:33:11 -0700 Subject: [PATCH 35/87] Adjust test to new logic --- .../DynamicZoneAwareAuthenticationManagerTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java index 730c6a35d32..1ba549353e8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java @@ -16,6 +16,7 @@ import org.junit.Test; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.Authentication; import static org.junit.Assert.assertNull; @@ -185,7 +186,12 @@ public void testAuthenticateInNoneUaaZoneWithInactiveProviders() throws Exceptio DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); when(mockManager.authenticate(any(Authentication.class))).thenReturn(success); when(mockManager.getDefinition()).thenReturn(ldapIdentityProviderDefinition); - assertNull(manager.authenticate(success)); + try { + manager.authenticate(success); + fail("Was expecting a "+ProviderNotFoundException.class); + } catch (ProviderNotFoundException x) { + //expected + } verifyZeroInteractions(uaaAuthenticationMgr); verifyZeroInteractions(mockManager); } From cc67be2acfda9ab070b8c008b851702ef8b190a1 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 12 Feb 2016 08:38:38 -0700 Subject: [PATCH 36/87] Add support to configure the session cookie programmatically https://www.pivotaltracker.com/story/show/113630271 [#113630271] --- .../uaa/web/UaaSessionCookieConfig.java | 127 ++++++++++++++++++ .../main/webapp/WEB-INF/spring-servlet.xml | 10 ++ .../uaa/integration/feature/LoginIT.java | 45 +++++++ .../identity/uaa/login/BootstrapTests.java | 36 ++++- .../test/bootstrap/bootstrap-test.yml | 10 ++ 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java new file mode 100644 index 00000000000..0c80c6eaaac --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.web; + +import org.springframework.web.context.ServletContextAware; + +import javax.servlet.ServletContext; +import javax.servlet.SessionCookieConfig; + +import static org.springframework.util.StringUtils.hasText; + +public class UaaSessionCookieConfig implements SessionCookieConfig, ServletContextAware { + + private String comment; + private String domain; + private int maxAge; + private String path; + private boolean httpOnly; + private String name; + private boolean secure; + + + + @Override + public void setServletContext(ServletContext servletContext) { + SessionCookieConfig config = servletContext.getSessionCookieConfig(); + if (hasText(getComment())) { + config.setComment(getComment()); + } + if (hasText(getDomain())) { + config.setDomain(getDomain()); + } + if (getMaxAge()>Integer.MIN_VALUE) { + config.setMaxAge(getMaxAge()); + } + if (getPath()!=null) { + config.setPath(getPath()); + } + config.setHttpOnly(isHttpOnly()); + config.setSecure(isSecure()); + if (hasText(getName())) { + config.setName(getName()); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public boolean isSecure() { + return secure; + } + + @Override + public void setSecure(boolean secure) { + this.secure = secure; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public String getDomain() { + return domain; + } + + @Override + public void setDomain(String domain) { + this.domain = domain; + } + + @Override + public boolean isHttpOnly() { + return httpOnly; + } + + @Override + public void setHttpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + } + + @Override + public int getMaxAge() { + return maxAge; + } + + @Override + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } + + @Override + public String getPath() { + return path; + } + + @Override + public void setPath(String path) { + this.path = path; + } +} diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 372bed94369..281cae232d1 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -31,6 +31,16 @@ + + + + + + + + + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java index 8751247da37..635bc2e5b1e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java @@ -43,7 +43,9 @@ import java.io.IOException; import java.security.SecureRandom; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -84,6 +86,49 @@ public void logout_and_clear_cookies() { webDriver.manage().deleteAllCookies(); } + @Test + public void check_JSESSIONID_defaults() throws Exception { + RestTemplate template = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + List cookies = Collections.EMPTY_LIST; + LinkedMultiValueMap requestBody = new LinkedMultiValueMap<>(); + requestBody.add("username", testAccounts.getUserName()); + requestBody.add("password", testAccounts.getPassword()); + + headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity loginResponse = template.exchange(baseUrl + "/login", + HttpMethod.GET, + new HttpEntity<>(null, headers), + String.class); + + if (loginResponse.getHeaders().containsKey("Set-Cookie")) { + for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) { + headers.add("Cookie", cookie); + } + } + String csrf = IntegrationTestUtils.extractCookieCsrf(loginResponse.getBody()); + requestBody.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); + + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + loginResponse = template.exchange(baseUrl + "/login.do", + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + String.class); + cookies = loginResponse.getHeaders().get("Set-Cookie"); + assertEquals(2, cookies.size()); + headers.clear(); + boolean jsessionIdValidated = false; + for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) { + if (cookie.contains("JSESSIONID")) { + jsessionIdValidated = true; + assertTrue(cookie.contains("HttpOnly")); + assertFalse(cookie.contains("Secure")); + + } + } + assertTrue("Did not find JSESSIONID", jsessionIdValidated); + } + @Test public void testSuccessfulLogin() throws Exception { webDriver.get(baseUrl + "/login"); 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 9e5efe1c519..c0ca81fc1b1 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 @@ -39,6 +39,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.security.web.CorsFilter; import org.cloudfoundry.identity.uaa.util.PredicateMatcher; +import org.cloudfoundry.identity.uaa.web.UaaSessionCookieConfig; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -133,6 +134,7 @@ public void cleanup() throws Exception { System.clearProperty("spring.profiles.active"); System.clearProperty("uaa.url"); System.clearProperty("login.url"); + System.clearProperty("require_https"); if (context != null) { context.close(); } @@ -151,6 +153,17 @@ public void cleanup() throws Exception { @Test public void testRootContextDefaults() throws Exception { context = getServletContext(null, "login.yml","uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + + UaaSessionCookieConfig sessionCookieConfig = context.getBean(UaaSessionCookieConfig.class); + assertNotNull(sessionCookieConfig); + assertNull(sessionCookieConfig.getComment()); + assertNull(sessionCookieConfig.getDomain()); + assertNull(sessionCookieConfig.getPath()); + assertNull(sessionCookieConfig.getName()); + assertEquals(Integer.MIN_VALUE, sessionCookieConfig.getMaxAge()); + assertTrue(sessionCookieConfig.isHttpOnly()); + assertFalse(sessionCookieConfig.isSecure()); + assertNotNull(context.getBean("viewResolver", ViewResolver.class)); assertNotNull(context.getBean("resetPasswordController", ResetPasswordController.class)); assertEquals(864000, context.getBean("webSSOprofileConsumer", WebSSOProfileConsumerImpl.class).getMaxAuthenticationAge()); @@ -266,6 +279,7 @@ public void testRootContextDefaults() throws Exception { assertTrue(corFilter.getXhrConfiguration().isAllowedCredentials()); assertFalse(corFilter.getDefaultConfiguration().isAllowedCredentials()); + } @Test @@ -275,6 +289,16 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { context = getServletContext(null, "login.yml", "test/bootstrap/bootstrap-test.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + UaaSessionCookieConfig sessionCookieConfig = context.getBean(UaaSessionCookieConfig.class); + assertNotNull(sessionCookieConfig); + assertEquals("C is for Cookie", sessionCookieConfig.getComment()); + assertEquals("sesame.com", sessionCookieConfig.getDomain()); + assertEquals("/the/path/to/the/jar", sessionCookieConfig.getPath()); + assertEquals("cookiemonster", sessionCookieConfig.getName()); + assertEquals(30, sessionCookieConfig.getMaxAge()); + assertFalse(sessionCookieConfig.isHttpOnly()); + assertTrue(sessionCookieConfig.isSecure()); + IdentityZoneProvisioning zoneProvisioning = context.getBean(IdentityZoneProvisioning.class); IdentityZoneConfiguration zoneConfiguration = zoneProvisioning.retrieve(IdentityZone.getUaa().getId()).getConfig(); assertFalse(zoneConfiguration.getLinks().getSelfService().isSelfServiceLinksEnabled()); @@ -367,8 +391,12 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { } @Test - public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception { + public void testDefaultInternalHostnamesAndNoDBSettings_and_Cookie_isSecure() throws Exception { try { + //testing to see if session cookie config confirms to this + System.setProperty("require_https","true"); + + System.setProperty("smtp.host","localhost"); //travis profile script overrides these properties System.setProperty("database.maxactive", "100"); @@ -378,6 +406,12 @@ public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception { 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"); + + UaaSessionCookieConfig sessionCookieConfig = context.getBean(UaaSessionCookieConfig.class); + assertNotNull(sessionCookieConfig); + assertTrue(sessionCookieConfig.isSecure()); + + IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); Set defaultHostnames = new HashSet<>(Arrays.asList(uaa, login, "localhost")); assertEquals(filter.getDefaultZoneHostnames(), defaultHostnames); diff --git a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml index 08e9e0e2130..4773b77a2c0 100644 --- a/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml +++ b/uaa/src/test/resources/test/bootstrap/bootstrap-test.yml @@ -84,3 +84,13 @@ jwt: refreshTokenValiditySeconds: 7200 accessTokenValiditySeconds: 4800 refreshTokenValiditySeconds: 9600 + +servlet: + session-cookie: + secure: true + http-only: false + max-age: 30 + name: cookiemonster + comment: C is for Cookie + path: /the/path/to/the/jar + domain: sesame.com From e1f06b8c808500f48f17b078ab0a34daa03d1af7 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 11 Feb 2016 15:49:23 -0800 Subject: [PATCH 37/87] Accept list of scopes for validation on /check_token [#79786766] https://www.pivotaltracker.com/story/show/79786766 --- .../uaa/oauth/CheckTokenEndpoint.java | 20 ++- .../uaa/oauth/CheckTokenEndpointTests.java | 127 +++++++++++++----- .../main/resources/login-integration-test.yml | 0 .../main/resources/uaa-integration-test.yml | 0 4 files changed, 112 insertions(+), 35 deletions(-) create mode 100644 uaa/src/main/resources/login-integration-test.yml create mode 100644 uaa/src/main/resources/uaa-integration-test.yml diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java index 4de1f2b9381..2fc04941de0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java @@ -22,6 +22,7 @@ import org.springframework.security.jwt.Jwt; import org.springframework.security.jwt.JwtHelper; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; @@ -34,6 +35,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + /** * Controller which decodes access tokens for clients who are not able to do so * (or where opaque token values are used). @@ -59,7 +64,7 @@ public void afterPropertiesSet() throws Exception { @RequestMapping(value = "/check_token") @ResponseBody - public Claims checkToken(@RequestParam("token") String value) { + public Claims checkToken(@RequestParam("token") String value, @RequestParam(name = "scopes", required = false, defaultValue = "") List scopes) { OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value); if (token == null) { @@ -76,8 +81,19 @@ public Claims checkToken(@RequestParam("token") String value) { throw new InvalidTokenException((x.getMessage())); } - Claims response = getClaimsForToken(value); + List claimScopes = response.getScope().stream().map(s -> s.toLowerCase()).collect(Collectors.toList()); + + List missingScopes = new ArrayList<>(); + for(String expectedScope : scopes) { + if (!claimScopes.contains(expectedScope.toLowerCase())) { + missingScopes.add(expectedScope); + } + } + + if (!missingScopes.isEmpty()) { + throw new InvalidScopeException(String.join(",", missingScopes)); + } return response; } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 9dec2232303..005c9302b70 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -37,6 +37,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; @@ -243,7 +244,7 @@ public void setUp() { tokenServices.setApprovalStore(approvalStore); tokenServices.setTokenPolicy(new TokenPolicy(43200, 2592000)); - defaultClient = new BaseClientDetails("client", "scim, cc", "read, write", "authorization_code, password","scim.read, scim.write", "http://localhost:8080/uaa"); + defaultClient = new BaseClientDetails("client", "scim, cc", "read, write", "authorization_code, password","scim.read, scim.write, cat.pet", "http://localhost:8080/uaa"); clientDetailsStore = Collections.singletonMap( "client", @@ -279,13 +280,13 @@ public void testClientWildcard() throws Exception { accessToken = tokenServices.createAccessToken(authentication); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = InvalidTokenException.class) public void testRejectInvalidIssuer() { tokenServices.setIssuer("http://some.other.issuer"); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = InvalidTokenException.class) @@ -293,7 +294,7 @@ public void testRejectInvalidVerifier() throws Exception { signerProvider.setSigningKey(alternateSignerKey); signerProvider.setVerifierKey(alternateVerifierKey); signerProvider.afterPropertiesSet(); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = TokenRevokedException.class) @@ -315,7 +316,7 @@ public void testRejectUserSaltChange() throws Exception { "changedsalt", new Date(System.currentTimeMillis() - 2000)); mockUserDatabase(userId, user); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = TokenRevokedException.class) @@ -337,7 +338,7 @@ public void testRejectUserUsernameChange() throws Exception { "salt", new Date(System.currentTimeMillis() - 2000)); mockUserDatabase(userId, user); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = TokenRevokedException.class) @@ -359,7 +360,7 @@ public void testRejectUserEmailChange() throws Exception { "salt", new Date(System.currentTimeMillis() - 2000)); mockUserDatabase(userId, user); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @@ -384,26 +385,86 @@ public void testRejectUserPasswordChange() throws Exception { new Date(System.currentTimeMillis() - 2000)); mockUserDatabase(userId,user); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = TokenRevokedException.class) public void testRejectClientSaltChange() throws Exception { defaultClient.addAdditionalInformation(ClientConstants.TOKEN_SALT, "changedsalt"); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = TokenRevokedException.class) public void testRejectClientPasswordChange() throws Exception { defaultClient.setClientSecret("changedsecret"); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); + } + + @Test(expected = InvalidScopeException.class) + public void testValidateScopesNotPresent() { + try { + authentication = new OAuth2Authentication(new AuthorizationRequest("client", + Collections.singleton("scim.read")).createOAuth2Request(), null); + accessToken = tokenServices.createAccessToken(authentication); + + endpoint.checkToken(accessToken.getValue(), Collections.singletonList("scim.write")); + } catch(InvalidScopeException ex) { + assertEquals("scim.write", ex.getMessage()); + throw ex; + } + } + + @Test(expected = InvalidScopeException.class) + public void testValidateScopesMultipleNotPresent() { + try { + authentication = new OAuth2Authentication(new AuthorizationRequest("client", + Collections.singletonList("cat.pet")).createOAuth2Request(), null); + accessToken = tokenServices.createAccessToken(authentication); + + endpoint.checkToken(accessToken.getValue(), Arrays.asList("scim.write", "scim.read")); + } catch(InvalidScopeException ex) { + assertEquals("scim.write,scim.read", ex.getMessage()); + throw ex; + } + } + + @Test + public void testValidateScopeSinglePresent() { + authentication = new OAuth2Authentication(new AuthorizationRequest("client", + Collections.singleton("scim.read")).createOAuth2Request(), null); + accessToken = tokenServices.createAccessToken(authentication); + + endpoint.checkToken(accessToken.getValue(), Collections.singletonList("scim.read")); + } + + @Test + public void testValidateScopesMultiplePresent() { + authentication = new OAuth2Authentication(new AuthorizationRequest("client", + Arrays.asList("scim.read", "scim.write")).createOAuth2Request(), null); + accessToken = tokenServices.createAccessToken(authentication); + + endpoint.checkToken(accessToken.getValue(), Arrays.asList("scim.write", "scim.read")); + } + + @Test(expected = InvalidScopeException.class) + public void testValidateScopesSomeNotPresent() { + try { + authentication = new OAuth2Authentication(new AuthorizationRequest("client", + Arrays.asList("scim.read", "scim.write")).createOAuth2Request(), null); + accessToken = tokenServices.createAccessToken(authentication); + + endpoint.checkToken(accessToken.getValue(), Arrays.asList("scim.read", "ponies.ride")); + } catch(InvalidScopeException ex) { + assertEquals("ponies.ride", ex.getMessage()); + throw ex; + } } @Test(expected = InvalidTokenException.class) public void revokingScopesFromUser_invalidatesToken() throws Exception { user = user.authorities(UaaAuthority.NONE_AUTHORITIES); mockUserDatabase(userId, user); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = InvalidTokenException.class) @@ -414,7 +475,7 @@ public void revokingScopesFromClient_invalidatesToken() throws Exception { defaultClient ); clientDetailsService.setClientDetailsStore(clientDetailsStore); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = InvalidTokenException.class) @@ -429,7 +490,7 @@ public void revokingAuthoritiesFromClients_invalidatesToken() throws Exception { authentication = new OAuth2Authentication(new AuthorizationRequest("client", Collections.singleton("scim.read")).createOAuth2Request(), null); accessToken = tokenServices.createAccessToken(authentication); - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test @@ -438,9 +499,9 @@ public void testSwitchVerifierKey() throws Exception { signerProvider.setVerifierKey(alternateVerifierKey); signerProvider.afterPropertiesSet(); OAuth2AccessToken alternateToken = tokenServices.createAccessToken(authentication); - endpoint.checkToken(alternateToken.getValue()); + endpoint.checkToken(alternateToken.getValue(), Collections.emptyList()); try { - endpoint.checkToken(accessToken.getValue()); + endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); fail(); } catch (InvalidTokenException x) { @@ -449,7 +510,7 @@ public void testSwitchVerifierKey() throws Exception { @Test public void testUserIdInResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals("olds", result.getUserName()); assertEquals("12345", result.getUserId()); } @@ -459,7 +520,7 @@ public void testIssuerInResults() throws Exception { tokenServices.setIssuer("http://some.other.issuer"); tokenServices.afterPropertiesSet(); accessToken = tokenServices.createAccessToken(authentication); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertNotNull("iss field is not present", result.getIss()); assertEquals("http://some.other.issuer/oauth/token",result.getIss()); } @@ -472,7 +533,7 @@ public void testIssuerInResultsInNonDefaultZone() throws Exception { tokenServices.setIssuer("http://some.other.issuer"); tokenServices.afterPropertiesSet(); accessToken = tokenServices.createAccessToken(authentication); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertNotNull("iss field is not present", result.getIss()); assertEquals("http://subdomain.some.other.issuer/oauth/token", result.getIss()); } finally { @@ -483,7 +544,7 @@ public void testIssuerInResultsInNonDefaultZone() throws Exception { @Test public void testValidateAudParameter() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); List aud = result.getAud(); assertEquals(2, aud.size()); assertTrue(aud.contains("scim")); @@ -492,7 +553,7 @@ public void testValidateAudParameter() { @Test public void testClientId() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals("client", result.getAzp()); assertEquals("client", result.getCid()); assertEquals("client", result.getClientId()); @@ -500,13 +561,13 @@ public void testClientId() { @Test public void validateAuthTime() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertNotNull(result.getAuthTime()); } @Test public void validatateIssuedAtIsSmallerThanExpiredAt() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); Integer iat = result.getIat(); assertNotNull(iat); Integer exp = result.getExp(); @@ -516,38 +577,38 @@ public void validatateIssuedAtIsSmallerThanExpiredAt() { @Test public void testEmailInResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals("olds@vmware.com", result.getEmail()); } @Test public void testClientIdInResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals("client", result.getClientId()); } @Test public void testClientIdInAud() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertTrue(result.getAud().contains("client")); } @Test public void testExpiryResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertTrue(expiresIn + System.currentTimeMillis() / 1000 >= result.getExp()); } @Test public void testUserAuthoritiesNotInResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals(null, result.getAuthorities()); } @Test public void testClientAuthoritiesNotInResult() { - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals(null, result.getAuthorities()); } @@ -561,7 +622,7 @@ public void testExpiredToken() throws Exception { tokenServices.setClientDetailsService(clientDetailsService); accessToken = tokenServices.createAccessToken(authentication); Thread.sleep(1000); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @Test(expected = InvalidTokenException.class) @@ -573,7 +634,7 @@ public void testUpdatedApprovals() { .setScope("read") .setExpiresAt(thirtySecondsAhead) .setStatus(ApprovalStatus.APPROVED)); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals(null, result.getAuthorities()); } @@ -595,7 +656,7 @@ public void testDeniedApprovals() { .setExpiresAt(thirtySecondsAhead) .setStatus(ApprovalStatus.DENIED) .setLastUpdatedAt(oneSecondAgo)); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals(null, result.getAuthorities()); } @@ -613,7 +674,7 @@ public void testExpiredApprovals() { .setScope("read") .setExpiresAt(new Date()) .setStatus(ApprovalStatus.APPROVED)); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals(null, result.getAuthorities()); } @@ -622,7 +683,7 @@ public void testClientOnly() { authentication = new OAuth2Authentication(new AuthorizationRequest("client", Collections.singleton("scim.read")).createOAuth2Request(), null); accessToken = tokenServices.createAccessToken(authentication); - Claims result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); assertEquals("client", result.getClientId()); assertEquals("client", result.getUserId()); } diff --git a/uaa/src/main/resources/login-integration-test.yml b/uaa/src/main/resources/login-integration-test.yml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/uaa/src/main/resources/uaa-integration-test.yml b/uaa/src/main/resources/uaa-integration-test.yml new file mode 100644 index 00000000000..e69de29bb2d From 7707be3b10f9e1787853a761d8a0441a5cf931f7 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 9 Feb 2016 17:25:26 -0800 Subject: [PATCH 38/87] Implement single logout for SAML - if IDP has single logout service configured [#63392710] https://www.pivotaltracker.com/story/show/63392710 Signed-off-by: Jonathan Lo --- .../authentication/UaaSamlLogoutFilter.java | 42 +++++++ .../src/main/resources/templates/web/nav.html | 2 +- .../webapp/WEB-INF/spring/saml-providers.xml | 2 +- .../uaa/integration/feature/SamlLoginIT.java | 104 ++++++++++++++++++ .../util/IntegrationTestUtils.java | 37 ++++--- 5 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java new file mode 100644 index 00000000000..0222f4117cf --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java @@ -0,0 +1,42 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.SingleLogoutService; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml.SAMLConstants; +import org.springframework.security.saml.SAMLCredential; +import org.springframework.security.saml.SAMLLogoutFilter; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +public class UaaSamlLogoutFilter extends SAMLLogoutFilter { + + + public UaaSamlLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler[] localHandler, LogoutHandler[] globalHandlers) { + super(logoutSuccessHandler, localHandler, globalHandlers); + } + + @Override + protected boolean isGlobalLogout(HttpServletRequest request, Authentication auth) { + Assert.isInstanceOf(SAMLCredential.class, auth.getCredentials(), "Authentication object doesn't contain SAML credential, cannot perform global logout"); + SAMLMessageContext context; + try { + SAMLCredential credential = (SAMLCredential) auth.getCredentials(); + request.setAttribute(SAMLConstants.LOCAL_ENTITY_ID, credential.getLocalEntityID()); + request.setAttribute(SAMLConstants.PEER_ENTITY_ID, credential.getRemoteEntityID()); + context = contextProvider.getLocalAndPeerEntity(request, null); + IDPSSODescriptor idp = (IDPSSODescriptor) context.getPeerEntityRoleMetadata(); + List singleLogoutServices = idp.getSingleLogoutServices(); + return singleLogoutServices.size() != 0; + } catch (MetadataProviderException e) { + logger.debug("Error processing metadata", e); + } + return true; + } +} diff --git a/server/src/main/resources/templates/web/nav.html b/server/src/main/resources/templates/web/nav.html index 29b52484369..8097a4954bb 100644 --- a/server/src/main/resources/templates/web/nav.html +++ b/server/src/main/resources/templates/web/nav.html @@ -16,7 +16,7 @@ diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index d7f5dae7462..803269835c0 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -208,7 +208,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 0fc4bc63d20..64d10ef04cf 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -18,6 +18,7 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; +import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail; import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; @@ -94,6 +95,9 @@ public class SamlLoginIT { @Autowired @Rule public IntegrationTestRule integrationTestRule; + @Rule + public ScreenshotOnFail screenShootRule = new ScreenshotOnFail(); + @Autowired RestOperations restOperations; @@ -125,6 +129,7 @@ public void clearWebDriverOfCookies() throws Exception { webDriver.manage().deleteAllCookies(); webDriver.get("http://simplesamlphp.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout"); webDriver.get("http://simplesamlphp2.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout"); + screenShootRule.setWebDriver(webDriver); } @Test @@ -269,6 +274,61 @@ public void testSimpleSamlPhpLogin() throws Exception { testSimpleSamlLogin("/login", "Where to?"); } + @Test + public void testSingleLogout() throws Exception { + IdentityProvider provider = createIdentityProvider("simplesamlphp"); + + webDriver.get(baseUrl + "/login"); + Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(testAccounts.getUserName()); + webDriver.findElement(By.name("password")).sendKeys(testAccounts.getPassword()); + webDriver.findElement(By.xpath("//input[@value='Login']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); + + webDriver.findElement(By.cssSelector(".dropdown-trigger")).click(); + webDriver.findElement(By.linkText("Sign Out")).click(); + webDriver.get(baseUrl); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + + webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); + } + + @Test + public void testSingleLogoutWithNoLogoutUrlOnIDP() throws Exception { + SamlIdentityProviderDefinition providerDefinition = createIDPWithNoSLOSConfigured("simplesamlphp"); + IdentityProvider provider = new IdentityProvider(); + provider.setIdentityZoneId(OriginKeys.UAA); + provider.setType(OriginKeys.SAML); + provider.setActive(true); + provider.setConfig(providerDefinition); + provider.setOriginKey(providerDefinition.getIdpEntityAlias()); + provider.setName("simplesamlphp for uaa"); + + String zoneAdminToken = IntegrationTestUtils.getZoneAdminToken(baseUrl, serverRunning); + + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + + webDriver.get(baseUrl + "/login"); + Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(testAccounts.getUserName()); + webDriver.findElement(By.name("password")).sendKeys(testAccounts.getPassword()); + webDriver.findElement(By.xpath("//input[@value='Login']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); + + webDriver.findElement(By.cssSelector(".dropdown-trigger")).click(); + webDriver.findElement(By.linkText("Sign Out")).click(); + webDriver.get(baseUrl); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); + } + @Test public void testGroupIntegration() throws Exception { testSimpleSamlLogin("/login", "Where to?", "marissa4", "saml2"); @@ -1076,4 +1136,48 @@ public SamlIdentityProviderDefinition createTestZoneIDP(String alias, String zon return createSimplePHPSamlIDP(alias, zoneSubdomain); } + public SamlIdentityProviderDefinition createIDPWithNoSLOSConfigured(String alias) { + String idpMetaData = "\n" + + "\n" + + " \n" + + " \n" + + " begl1WVCsXSn7iHixtWPP8d/X+k=BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg==\n" + + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " Filip\n" + + " Hanik\n" + + " fhanik@pivotal.io\n" + + " \n" + + ""; + + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + def.setZoneId("uaa"); + def.setMetaDataLocation(idpMetaData); + def.setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); + def.setAssertionConsumerIndex(0); + def.setMetadataTrustCheck(false); + def.setShowSamlLink(true); + def.setIdpEntityAlias(alias); + def.setLinkText("Login with Simple SAML PHP("+alias+")"); + return def; + } + } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index d6397deee78..aae338cf525 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -583,23 +583,7 @@ public static List getProviders(String zoneAdminToken, * @throws Exception on error */ public static IdentityProvider createIdentityProvider(String originKey, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning) throws Exception { - RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret") - ); - RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") - ); - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); - IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); - - String zoneAdminToken = - IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + String zoneAdminToken = getZoneAdminToken(baseUrl, serverRunning); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(originKey, OriginKeys.UAA); samlIdentityProviderDefinition.setAddShadowUserOnLogin(addShadowUserOnLogin); @@ -615,6 +599,25 @@ public static IdentityProvider createIdentityProvider(String originKey, boolean return provider; } + public static String getZoneAdminToken(String baseUrl, ServerRunning serverRunning) throws Exception { + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret") + ); + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); + + return IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); + } + public static SamlIdentityProviderDefinition createSimplePHPSamlIDP(String alias, String zoneId) { if (!("simplesamlphp".equals(alias) || "simplesamlphp2".equals(alias))) { throw new IllegalArgumentException("Only valid origins are: simplesamlphp,simplesamlphp2"); From 9eec4e38a8d24f48f1b527d38717bf33e72c442b Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 12 Feb 2016 15:38:50 -0800 Subject: [PATCH 39/87] Persist redirect params as RelayState in SAML logout request. [#63392710] https://www.pivotaltracker.com/story/show/63392710 Signed-off-by: Jonathan Lo --- .../identity/uaa/util/JsonUtils.java | 4 +- .../RedirectSavingSamlContextProvider.java | 46 +++++++++++ .../SamlRedirectLogoutHandler.java | 80 +++++++++++++++++++ .../authentication/UaaSamlLogoutFilter.java | 6 +- .../WhitelistLogoutHandler.java | 35 +++----- .../invitations/EmailInvitationsService.java | 10 +-- .../identity/uaa/util/UaaUrlUtils.java | 11 +-- server/src/main/resources/login-ui.xml | 3 +- .../src/main/resources/templates/web/nav.html | 4 +- .../webapp/WEB-INF/spring/saml-providers.xml | 21 +++-- .../uaa/integration/feature/SamlLoginIT.java | 68 +++++++++++++++- .../util/IntegrationTestUtils.java | 11 +++ .../ClientAdminEndpointsMockMvcTests.java | 7 +- 13 files changed, 248 insertions(+), 58 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java index 3f56235b096..5d4e8f06088 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java @@ -64,7 +64,7 @@ public static T readValue(byte[] data, Class clazz) throws JsonUtilExcept } } - public static T readValue(String s, TypeReference typeReference) { + public static T readValue(String s, TypeReference typeReference) { try { if (StringUtils.hasText(s)) { return objectMapper.readValue(s, typeReference); @@ -76,7 +76,7 @@ public static T readValue(String s, TypeReference typeReference) { } } - public static T readValue(byte[] data, TypeReference typeReference) { + public static T readValue(byte[] data, TypeReference typeReference) { try { if (data!=null && data.length>0) { return objectMapper.readValue(data, typeReference); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java new file mode 100644 index 00000000000..6bb782f1664 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.flywaydb.core.internal.util.StringUtils; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.security.saml.context.SAMLContextProvider; +import org.springframework.security.saml.context.SAMLMessageContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + +public class RedirectSavingSamlContextProvider implements SAMLContextProvider { + + private final SAMLContextProvider contextProviderDelegate; + + public RedirectSavingSamlContextProvider(SAMLContextProvider contextProviderDelegate) { + this.contextProviderDelegate = contextProviderDelegate; + } + + @Override + public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException { + SAMLMessageContext context = contextProviderDelegate.getLocalEntity(request, response); + return setRelayState(request, context); + } + + @Override + public SAMLMessageContext getLocalAndPeerEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException { + SAMLMessageContext context = contextProviderDelegate.getLocalAndPeerEntity(request, response); + return setRelayState(request, context); + } + + private static SAMLMessageContext setRelayState(HttpServletRequest request, SAMLMessageContext context) { + Map params = new HashMap<>(); + + String redirectUri = request.getParameter("redirect"); + if(StringUtils.hasText(redirectUri)) { params.put("redirect", redirectUri); } + + String clientId = request.getParameter("client_id"); + if(StringUtils.hasText(clientId)) { params.put("client_id", clientId); } + + context.setRelayState(JsonUtils.writeValueAsString(params)); + return context; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java new file mode 100644 index 00000000000..317c3fab03f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java @@ -0,0 +1,80 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class SamlRedirectLogoutHandler implements LogoutSuccessHandler { + private final LogoutSuccessHandler wrappedHandler; + + public SamlRedirectLogoutHandler(LogoutSuccessHandler wrappedHandler) { + this.wrappedHandler = wrappedHandler; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + RequestWrapper requestWrapper = new RequestWrapper(request); + String relayState = request.getParameter("RelayState"); + Map params = JsonUtils.readValue(relayState, new TypeReference>() {}); + if(params != null) { + requestWrapper.setParameter("redirect", params.get("redirect")); + requestWrapper.setParameter("client_id", params.get("client_id")); + } + + wrappedHandler.onLogoutSuccess(requestWrapper, response, authentication); + } + + private static class RequestWrapper extends HttpServletRequestWrapper { + private final Map parameterMap; + + public RequestWrapper(HttpServletRequest request) { + super(request); + parameterMap = new HashMap<>(request.getParameterMap()); + } + + public void setParameter(String name, String... value) { + parameterMap.put(name, value); + } + + public String getParameter(String name) { + String[] values = parameterMap.get(name); + return values != null && values.length > 0 ? values[0] : null; + } + + public Map getParameterMap() { + return parameterMap; + } + + public Enumeration getParameterNames() { + return new Enumeration() { + Iterator iterator = parameterMap.keySet().iterator(); + + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public String nextElement() { + return iterator.next(); + } + }; + } + + public String[] getParameterValues(String name) { + return parameterMap.get(name); + } + + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java index 0222f4117cf..8792d55fde9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java @@ -20,11 +20,13 @@ public class UaaSamlLogoutFilter extends SAMLLogoutFilter { public UaaSamlLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler[] localHandler, LogoutHandler[] globalHandlers) { super(logoutSuccessHandler, localHandler, globalHandlers); + setFilterProcessesUrl("/logout.do"); } @Override protected boolean isGlobalLogout(HttpServletRequest request, Authentication auth) { - Assert.isInstanceOf(SAMLCredential.class, auth.getCredentials(), "Authentication object doesn't contain SAML credential, cannot perform global logout"); + if (!(auth.getCredentials() instanceof SAMLCredential)) { return false; } + SAMLMessageContext context; try { SAMLCredential credential = (SAMLCredential) auth.getCredentials(); @@ -36,7 +38,7 @@ protected boolean isGlobalLogout(HttpServletRequest request, Authentication auth return singleLogoutServices.size() != 0; } catch (MetadataProviderException e) { logger.debug("Error processing metadata", e); + return false; } - return true; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java index 3980e9a9ca7..32f1142c0e8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java @@ -57,12 +57,8 @@ public void setClientDetailsService(ClientDetailsService clientDetailsService) { this.clientDetailsService = clientDetailsService; } - public String getClientRedirect(HttpServletRequest request, String redirectUri) { + private Set getClientWhitelist(HttpServletRequest request) { String clientId = request.getParameter(CLIENT_ID); - logger.debug(String.format("Evaluating client logout redirect client_id:%s and redirect:%s", clientId, redirectUri)); - if (!StringUtils.hasText(clientId) || !StringUtils.hasText(redirectUri)) { - return null; - } Set redirectUris = null; try { ClientDetails client = clientDetailsService.loadClientByClientId(clientId); @@ -70,28 +66,21 @@ public String getClientRedirect(HttpServletRequest request, String redirectUri) } catch (NoSuchClientException x) { logger.debug(String.format("Unable to find client with ID:%s for logout redirect", clientId)); } - return UaaUrlUtils.findMatchingRedirectUri(redirectUris, redirectUri); + return redirectUris; } @Override protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { - String url = super.determineTargetUrl(request, response); - String whiteListRedirect = UaaUrlUtils.findMatchingRedirectUri(getWhitelist(), url); - boolean whitelisted = false; - if (StringUtils.hasText(whiteListRedirect)) { - url = whiteListRedirect; - whitelisted = true; - } - String clientRedirectUri = getClientRedirect(request, url); - if (StringUtils.hasText(clientRedirectUri)) { - url = clientRedirectUri; - whitelisted = true; - } - if (!whitelisted && getWhitelist()!=null) { //if we didn't find a matching URL, and whitelist is set to enforce (!=null) - url = getDefaultTargetUrl(); + String targetUrl = super.determineTargetUrl(request, response); + String defaultTargetUrl = getDefaultTargetUrl(); + if(targetUrl.equals(defaultTargetUrl)) { + return targetUrl; } - logger.debug("Logout redirect[whitelisted:"+whitelisted+"; redirect:"+request.getParameter(getTargetUrlParameter())+"] returning:"+url); - return url; - } + Set clientWhitelist = getClientWhitelist(request); + String whiteListRedirect = UaaUrlUtils.findMatchingRedirectUri(whitelist, targetUrl, defaultTargetUrl); + String redirectUrl = UaaUrlUtils.findMatchingRedirectUri(clientWhitelist, targetUrl, whiteListRedirect); + + return redirectUrl; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java index 0c2cad25c04..f7771180565 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java @@ -6,8 +6,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.message.MessageService; -import org.cloudfoundry.identity.uaa.message.MessageType; import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -19,13 +17,7 @@ import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClientException; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring4.SpringTemplateEngine; -import javax.swing.*; -import java.util.List; import java.util.Map; import java.util.Set; @@ -72,7 +64,7 @@ public AcceptedInvitation acceptInvitation(String code, String password) { try { ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); Set redirectUris = clientDetails.getRegisteredRedirectUri(); - String matchingRedirectUri = UaaUrlUtils.findMatchingRedirectUri(redirectUris, redirectUri); + String matchingRedirectUri = UaaUrlUtils.findMatchingRedirectUri(redirectUris, redirectUri, redirectLocation); if (StringUtils.hasText(matchingRedirectUri)) { redirectLocation = redirectUri; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java index 3846197274b..81b07a353fd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java @@ -47,14 +47,11 @@ private static UriComponentsBuilder getURIBuilder(String path) { return builder; } - public static String findMatchingRedirectUri(Collection wildcardUris, String requestedRedirectUri) { - if (wildcardUris != null) { - Set wildcards = UaaStringUtils.constructWildcards(wildcardUris); - if (UaaStringUtils.matches(wildcards, requestedRedirectUri)) { - return requestedRedirectUri; - } + public static String findMatchingRedirectUri(Collection wildcardUris, String requestedRedirectUri, String fallbackRedirectUri) { + if (wildcardUris == null || UaaStringUtils.matches(UaaStringUtils.constructWildcards(wildcardUris), requestedRedirectUri)) { + return requestedRedirectUri; } - return null; + return fallbackRedirectUri; } public static String getHostForURI(String uri) { diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index 1fd61b37198..8be2b46c5a5 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -222,7 +222,8 @@ authentication-details-source-ref="authenticationDetailsSource" default-target-url="/"/> - + + diff --git a/server/src/main/resources/templates/web/nav.html b/server/src/main/resources/templates/web/nav.html index 8097a4954bb..2ed6099b290 100644 --- a/server/src/main/resources/templates/web/nav.html +++ b/server/src/main/resources/templates/web/nav.html @@ -16,8 +16,8 @@ - \ No newline at end of file + diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index 803269835c0..f5c901d5df1 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -108,7 +108,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -117,7 +117,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -191,14 +191,14 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + - + @@ -206,22 +206,31 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + + + + + - + + + + + @@ -258,7 +267,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 64d10ef04cf..50e86d7fb62 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -35,6 +35,7 @@ import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.flywaydb.core.internal.util.StringUtils; import org.hamcrest.Matchers; import org.junit.Assert; @@ -74,6 +75,7 @@ import java.util.UUID; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; +import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; @@ -244,7 +246,7 @@ public void failureResponseFromSamlIDP_showErrorFromSaml() throws Exception { provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone2"); - IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); + IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); webDriver.get(zoneUrl); webDriver.findElement(By.linkText("Login with Simple SAML PHP(simplesamlphp)")).click(); @@ -290,12 +292,73 @@ public void testSingleLogout() throws Exception { webDriver.findElement(By.cssSelector(".dropdown-trigger")).click(); webDriver.findElement(By.linkText("Sign Out")).click(); - webDriver.get(baseUrl); webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); } + @Test + public void testSingleLogoutWithLogoutRedirect() throws Exception { + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + String zoneId = "testzone2"; + String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); + + //identity client token + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + ); + //admin client token - to create users + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + //create the zone + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config -> { + config.getLinks().getLogout().setDisableRedirectParameter(false); + }); + + //create a zone admin user + String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), zoneId); + + //get the zone admin token + String zoneAdminToken = + IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); + SamlIdentityProviderDefinition providerDefinition = createIDPWithNoSLOSConfigured("simplesamlphp"); + IdentityProvider provider = new IdentityProvider(); + provider.setIdentityZoneId(zoneId); + provider.setType(OriginKeys.SAML); + provider.setActive(true); + provider.setConfig(providerDefinition); + provider.setOriginKey(providerDefinition.getIdpEntityAlias()); + provider.setName("simplesamlphp for uaa"); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + + webDriver.get(zoneUrl + "/login"); + Assert.assertTrue(webDriver.getTitle().contains("testzone2")); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(testAccounts.getUserName()); + webDriver.findElement(By.name("password")).sendKeys(testAccounts.getPassword()); + webDriver.findElement(By.xpath("//input[@value='Login']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); + + String redirectUrl = URLEncoder.encode(zoneUrl + "/login?test=test", "UTF-8"); + BaseClientDetails clientDetails = new BaseClientDetails("test-logout-redirect", null, null, "authorization_code", null); + clientDetails.setRegisteredRedirectUri(Collections.singleton(redirectUrl)); + clientDetails.setClientSecret("secret"); + IntegrationTestUtils.createOrUpdateClient(zoneAdminToken, baseUrl, zoneId, clientDetails); + + webDriver.get(zoneUrl + "/logout.do?redirect=" + redirectUrl + "&client_id=test-logout-redirect"); + assertEquals(zoneUrl + "/login?test=test", webDriver.getCurrentUrl()); + } + @Test public void testSingleLogoutWithNoLogoutUrlOnIDP() throws Exception { SamlIdentityProviderDefinition providerDefinition = createIDPWithNoSLOSConfigured("simplesamlphp"); @@ -323,7 +386,6 @@ public void testSingleLogoutWithNoLogoutUrlOnIDP() throws Exception { webDriver.findElement(By.cssSelector(".dropdown-trigger")).click(); webDriver.findElement(By.linkText("Sign Out")).click(); - webDriver.get(baseUrl); webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index aae338cf525..c3805eb8ca0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -31,6 +31,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Assert; import org.openqa.selenium.OutputType; @@ -71,6 +72,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -417,6 +419,14 @@ public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, String url, String id, String subdomain) { + return createZoneOrUpdateSubdomain(client, url, id, subdomain, x -> {}); + } + + public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, + String url, + String id, + String subdomain, + Consumer configureZone) { ResponseEntity zoneGet = client.getForEntity(url + "/identity-zones/{id}", String.class, id); if (zoneGet.getStatusCode()==HttpStatus.OK) { @@ -426,6 +436,7 @@ public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, return existing; } IdentityZone identityZone = fixtureIdentityZone(id, subdomain); + configureZone.accept(identityZone.getConfig()); ResponseEntity zone = client.postForEntity(url + "/identity-zones", identityZone, IdentityZone.class); return zone.getBody(); 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 e1373c58509..088772dc48d 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 @@ -28,6 +28,7 @@ import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -1183,7 +1184,7 @@ public void testGetClientDetailsSortedByLastModified() throws Exception{ MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn(); String body = result.getResponse().getContentAsString(); - Collection clientDetails = JsonUtils.> readValue(body, new TypeReference>() { + Collection clientDetails = JsonUtils.readValue(body, new TypeReference>() { }).getResources(); assertNotNull(clientDetails); @@ -1265,10 +1266,10 @@ public void testPutClientModifyName() throws Exception { MockHttpServletResponse response = getClientHttpResponse(client.getClientId()); Map map = JsonUtils.readValue(response.getContentAsString(), new TypeReference>() {}); - assertThat(map, hasEntry(is("name"), is("New Client Name"))); + assertThat(map, hasEntry(is("name"), PredicateMatcher.is(value -> value.equals("New Client Name")))); client = getClientResponseAsClientDetails(response); - assertThat(client.getAdditionalInformation(), hasEntry(is("name"), is("New Client Name"))); + assertThat(client.getAdditionalInformation(), hasEntry(is("name"), PredicateMatcher.is(value -> value.equals("New Client Name")))); } private Approval[] getApprovals(String token, String clientId) throws Exception { From b644b1eb5bc8f95694269a2e7a3d65b86fa26daf Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 16 Feb 2016 09:49:54 -0800 Subject: [PATCH 40/87] Skip client whitelist when lacking client ID [#63392710] https://www.pivotaltracker.com/story/show/63392710 --- .../SamlRedirectLogoutHandler.java | 8 ++--- .../WhitelistLogoutHandler.java | 34 ++++++++++++++----- .../WhitelistLogoutHandlerTest.java | 2 +- .../ZoneAwareWhitelistLogoutHandlerTests.java | 2 +- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java index 317c3fab03f..11ffc68d0f2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java @@ -28,8 +28,8 @@ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse resp String relayState = request.getParameter("RelayState"); Map params = JsonUtils.readValue(relayState, new TypeReference>() {}); if(params != null) { - requestWrapper.setParameter("redirect", params.get("redirect")); - requestWrapper.setParameter("client_id", params.get("client_id")); + requestWrapper.setParameterIfAbsent("redirect", params.get("redirect")); + requestWrapper.setParameterIfAbsent("client_id", params.get("client_id")); } wrappedHandler.onLogoutSuccess(requestWrapper, response, authentication); @@ -43,8 +43,8 @@ public RequestWrapper(HttpServletRequest request) { parameterMap = new HashMap<>(request.getParameterMap()); } - public void setParameter(String name, String... value) { - parameterMap.put(name, value); + public void setParameterIfAbsent(String name, String... value) { + parameterMap.putIfAbsent(name, value); } public String getParameter(String name) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java index 32f1142c0e8..3b4a514eec8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java @@ -11,6 +11,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -60,11 +62,14 @@ public void setClientDetailsService(ClientDetailsService clientDetailsService) { private Set getClientWhitelist(HttpServletRequest request) { String clientId = request.getParameter(CLIENT_ID); Set redirectUris = null; - try { - ClientDetails client = clientDetailsService.loadClientByClientId(clientId); - redirectUris = client.getRegisteredRedirectUri(); - } catch (NoSuchClientException x) { - logger.debug(String.format("Unable to find client with ID:%s for logout redirect", clientId)); + + if (StringUtils.hasText(clientId)) { + try { + ClientDetails client = clientDetailsService.loadClientByClientId(clientId); + redirectUris = client.getRegisteredRedirectUri(); + } catch (NoSuchClientException x) { + logger.debug(String.format("Unable to find client with ID:%s for logout redirect", clientId)); + } } return redirectUris; } @@ -73,14 +78,25 @@ private Set getClientWhitelist(HttpServletRequest request) { protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { String targetUrl = super.determineTargetUrl(request, response); String defaultTargetUrl = getDefaultTargetUrl(); - if(targetUrl.equals(defaultTargetUrl)) { + if (targetUrl.equals(defaultTargetUrl)) { return targetUrl; } Set clientWhitelist = getClientWhitelist(request); - String whiteListRedirect = UaaUrlUtils.findMatchingRedirectUri(whitelist, targetUrl, defaultTargetUrl); - String redirectUrl = UaaUrlUtils.findMatchingRedirectUri(clientWhitelist, targetUrl, whiteListRedirect); + Set combinedWhitelist = combineSets(whitelist, clientWhitelist); + String whiteListRedirect = UaaUrlUtils.findMatchingRedirectUri(combinedWhitelist, targetUrl, defaultTargetUrl); + + return whiteListRedirect; + } - return redirectUrl; + private static Set combineSets(Collection... sets) { + Set combined = null; + for(Collection set : sets) { + if(set != null) { + if(combined == null) { combined = new HashSet<>(set); } + else { combined.addAll(set); } + } + } + return combined; } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java index c5b94ce658e..b5db504e6bb 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java @@ -121,4 +121,4 @@ public void test_client_redirect_using_wildcard() throws Exception { assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); } -} \ No newline at end of file +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java index a255c05d56c..7400587e1ec 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java @@ -153,4 +153,4 @@ public void test_client_redirect_using_wildcard() throws Exception { assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); } -} \ No newline at end of file +} From 4e09ae9b0fda8ecf018dbcf9badd62346dcd4477 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 16 Feb 2016 10:24:51 -0800 Subject: [PATCH 41/87] Fix a URL encoding problem in test. [#63392710] https://www.pivotaltracker.com/story/show/63392710 --- .../authentication/SamlRedirectLogoutHandler.java | 12 ++++++++---- .../cloudfoundry/identity/uaa/util/UaaUrlUtils.java | 2 +- .../uaa/integration/feature/SamlLoginIT.java | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java index 11ffc68d0f2..019f100ff43 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java @@ -4,6 +4,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.util.StringUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -28,8 +29,11 @@ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse resp String relayState = request.getParameter("RelayState"); Map params = JsonUtils.readValue(relayState, new TypeReference>() {}); if(params != null) { - requestWrapper.setParameterIfAbsent("redirect", params.get("redirect")); - requestWrapper.setParameterIfAbsent("client_id", params.get("client_id")); + String redirect = params.get("redirect"); + if(StringUtils.hasText(redirect)) { requestWrapper.setParameter("redirect", redirect); } + + String clientId = params.get("client_id"); + if(StringUtils.hasText(clientId)) { requestWrapper.setParameter("client_id", clientId); } } wrappedHandler.onLogoutSuccess(requestWrapper, response, authentication); @@ -43,8 +47,8 @@ public RequestWrapper(HttpServletRequest request) { parameterMap = new HashMap<>(request.getParameterMap()); } - public void setParameterIfAbsent(String name, String... value) { - parameterMap.putIfAbsent(name, value); + public void setParameter(String name, String... value) { + parameterMap.put(name, value); } public String getParameter(String name) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java index 81b07a353fd..00573a3a3bb 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java @@ -48,7 +48,7 @@ private static UriComponentsBuilder getURIBuilder(String path) { } public static String findMatchingRedirectUri(Collection wildcardUris, String requestedRedirectUri, String fallbackRedirectUri) { - if (wildcardUris == null || UaaStringUtils.matches(UaaStringUtils.constructWildcards(wildcardUris), requestedRedirectUri)) { + if (wildcardUris == null || UaaStringUtils.matches(UaaStringUtils.constructWildcards(wildcardUris), requestedRedirectUri) ) { return requestedRedirectUri; } return fallbackRedirectUri; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 50e86d7fb62..88eb42fbbea 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -349,14 +349,14 @@ public void testSingleLogoutWithLogoutRedirect() throws Exception { webDriver.findElement(By.xpath("//input[@value='Login']")).click(); assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to")); - String redirectUrl = URLEncoder.encode(zoneUrl + "/login?test=test", "UTF-8"); + String redirectUrl = zoneUrl + "/login?test=test"; BaseClientDetails clientDetails = new BaseClientDetails("test-logout-redirect", null, null, "authorization_code", null); clientDetails.setRegisteredRedirectUri(Collections.singleton(redirectUrl)); clientDetails.setClientSecret("secret"); IntegrationTestUtils.createOrUpdateClient(zoneAdminToken, baseUrl, zoneId, clientDetails); - webDriver.get(zoneUrl + "/logout.do?redirect=" + redirectUrl + "&client_id=test-logout-redirect"); - assertEquals(zoneUrl + "/login?test=test", webDriver.getCurrentUrl()); + webDriver.get(zoneUrl + "/logout.do?redirect=" + URLEncoder.encode(redirectUrl, "UTF-8") + "&client_id=test-logout-redirect"); + assertEquals(redirectUrl, webDriver.getCurrentUrl()); } @Test From 3d18e6449d5becbb99388f77a57e579086711289 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 16 Feb 2016 10:46:53 -0800 Subject: [PATCH 42/87] Redirect invite acceptance to real whitelist result. [#63392710] https://www.pivotaltracker.com/story/show/63392710 Signed-off-by: Priyata Agrawal --- .../identity/uaa/invitations/EmailInvitationsService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java index f7771180565..7ce76839d8f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java @@ -64,10 +64,7 @@ public AcceptedInvitation acceptInvitation(String code, String password) { try { ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); Set redirectUris = clientDetails.getRegisteredRedirectUri(); - String matchingRedirectUri = UaaUrlUtils.findMatchingRedirectUri(redirectUris, redirectUri, redirectLocation); - if (StringUtils.hasText(matchingRedirectUri)) { - redirectLocation = redirectUri; - } + redirectLocation = UaaUrlUtils.findMatchingRedirectUri(redirectUris, redirectUri, redirectLocation); } catch (NoSuchClientException x) { logger.debug("Unable to find client_id for invitation:"+clientId); } catch (Exception x) { From f25926dbd227fcbb44ac328fd2fddb12a4e41a5a Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 16 Feb 2016 11:03:26 -0700 Subject: [PATCH 43/87] Log info when setting up cookie. --- .../identity/uaa/web/UaaSessionCookieConfig.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java index 0c80c6eaaac..82208b99e5a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java @@ -14,6 +14,8 @@ package org.cloudfoundry.identity.uaa.web; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.web.context.ServletContextAware; import javax.servlet.ServletContext; @@ -23,6 +25,8 @@ public class UaaSessionCookieConfig implements SessionCookieConfig, ServletContextAware { + protected static Log logger = LogFactory.getLog(UaaSessionCookieConfig.class); + private String comment; private String domain; private int maxAge; @@ -35,22 +39,30 @@ public class UaaSessionCookieConfig implements SessionCookieConfig, ServletConte @Override public void setServletContext(ServletContext servletContext) { + logger.debug("Configuring session cookie."); SessionCookieConfig config = servletContext.getSessionCookieConfig(); if (hasText(getComment())) { + logger.debug(String.format("Configuring session cookie - Comment: %s", getComment())); config.setComment(getComment()); } if (hasText(getDomain())) { + logger.debug(String.format("Configuring session cookie - Domain: %s", getDomain())); config.setDomain(getDomain()); } if (getMaxAge()>Integer.MIN_VALUE) { + logger.debug(String.format("Configuring session cookie - MaxAge: %s", getMaxAge())); config.setMaxAge(getMaxAge()); } if (getPath()!=null) { + logger.debug(String.format("Configuring session cookie - Path: %s", getPath())); config.setPath(getPath()); } + logger.debug(String.format("Configuring session cookie - HttpOnly: %s", isHttpOnly())); config.setHttpOnly(isHttpOnly()); + logger.debug(String.format("Configuring session cookie - Secure: %s", isSecure())); config.setSecure(isSecure()); if (hasText(getName())) { + logger.debug(String.format("Configuring session cookie - Name: %s", getName())); config.setName(getName()); } } From 7a93dd0b1dc57a86593401097f79757c08e0a05c Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 12 Feb 2016 16:45:46 -0800 Subject: [PATCH 44/87] Add public key comparison for cert and private key [#111794828] https://www.pivotaltracker.com/story/show/111794828 Signed-off-by: Jeremy Coffield --- .../saml/SamlLoginServerKeyManager.java | 5 ++ .../login/SamlLoginServerKeyManagerTests.java | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java index d1af5c9ffc3..ce8a6aece89 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java @@ -18,6 +18,7 @@ import java.security.KeyStore; import java.security.Security; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Set; @@ -72,6 +73,10 @@ public SamlLoginServerKeyManager(String key, String password, String certificate "Could not load service provider certificate. Check serviceProviderKey and certificate parameters"); } + if (!cert.getPublicKey().equals(pkey.getPublic())) { + throw new CertificateException("Certificate does not match private key."); + } + logger.info("Loaded service provider certificate " + keyManager.getDefaultCredentialName()); } catch (Throwable t) { logger.error("Could not load certificate", t); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java index a5dd0594520..54e0e6ea683 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java @@ -244,4 +244,56 @@ public void testWithNonWorkingCertificate() throws Exception { } } } + + @Test(expected = IllegalArgumentException.class) + public void testKeyPairValidated() throws Exception { + String key = "-----BEGIN RSA PRIVATE KEY-----\n" + + "Proc-Type: 4,ENCRYPTED\n" + + "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + + "\n" + + "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + + "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + + "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + + "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + + "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + + "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + + "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + + "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + + "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + + "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + + "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + + "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + + "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + + "-----END RSA PRIVATE KEY-----"; + String certificate = "-----BEGIN CERTIFICATE-----\n" + + "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + + "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + + "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + + "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + + "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + + "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + + "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + + "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + + "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + + "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + + "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + + "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + + "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + + "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + + "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + + "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + + "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + + "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + + "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + + "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + + "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + + "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + + "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + + "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + + "-----END CERTIFICATE-----"; + + String password = "password"; + + keyManager = new SamlLoginServerKeyManager(key, password, certificate); + } } From 6820a7b37f55a96520ef0cddb4696c5b811a19dc Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 16 Feb 2016 14:37:44 -0800 Subject: [PATCH 45/87] Validate public key match between cert and private key for zone SAML config [#111794828] https://www.pivotaltracker.com/story/show/111794828 Signed-off-by: Priyata Agrawal --- model/build.gradle | 2 + .../identity/uaa/util/KeyWithCert.java | 42 ++++++++ .../identity/uaa/zone/SamlConfig.java | 43 +++++++-- .../identity/uaa/zone/SamlConfigTest.java | 96 ++++++++++++++++++- .../saml/SamlLoginServerKeyManager.java | 34 +------ .../IdentityZoneEndpointsMockMvcTests.java | 52 +++++++++- .../saml/SamlIDPRefreshMockMvcTests.java | 3 +- 7 files changed, 227 insertions(+), 45 deletions(-) create mode 100644 model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java diff --git a/model/build.gradle b/model/build.gradle index c39581b0e95..15c733c9e66 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -14,6 +14,8 @@ dependencies { compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: parent.springSecurityOAuthVersion) { exclude(module: 'jackson-mapper-asl') } + compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.47' + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.50' compile group: 'org.slf4j', name: 'slf4j-log4j12', version:parent.slf4jVersion compile group: 'org.slf4j', name: 'slf4j-api', version:parent.slf4jVersion diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java new file mode 100644 index 00000000000..2ed639f9394 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java @@ -0,0 +1,42 @@ +package org.cloudfoundry.identity.uaa.util; + +import org.bouncycastle.openssl.PEMReader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +public class KeyWithCert { + private X509Certificate cert; + private KeyPair pkey; + + public KeyWithCert(String key, String passphrase, String certificate) throws CertificateException { + if(passphrase == null) { passphrase = ""; } + + try { + PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certificate.getBytes()))); + cert = (X509Certificate) reader.readObject(); + + reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(key.getBytes())), passphrase::toCharArray); + pkey = (KeyPair) reader.readObject(); + } catch (IOException ex) { + throw new CertificateException("Failed to read private key or certificate.", ex); + } + + if (!cert.getPublicKey().equals(pkey.getPublic())) { + throw new CertificateException("Certificate does not match private key."); + } + } + + public X509Certificate getCert() { + return cert; + } + + public KeyPair getPkey() { + return pkey; + } + +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index a2ce2d388e2..bc8fdd61d79 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -14,6 +14,13 @@ package org.cloudfoundry.identity.uaa.zone; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.springframework.util.StringUtils; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + public class SamlConfig { private boolean requestSigned = true; private boolean wantAssertionSigned = false; @@ -21,6 +28,9 @@ public class SamlConfig { private String privateKey; private String privateKeyPassword; + @JsonIgnore + private KeyWithCert keyCert; + public boolean isRequestSigned() { return requestSigned; } @@ -37,18 +47,27 @@ public void setWantAssertionSigned(boolean wantAssertionSigned) { this.wantAssertionSigned = wantAssertionSigned; } - public void setCertificate(String certificate) { + public void setCertificate(String certificate) throws CertificateException { this.certificate = certificate; - } - public void setPrivateKey(String privateKey) { - this.privateKey = privateKey; + if(StringUtils.hasText(privateKey)) { + validateCert(); + } } public String getCertificate() { return certificate; } + public void setPrivateKeyAndPassword(String privateKey, String privateKeyPassword) throws CertificateException { + this.privateKey = privateKey; + this.privateKeyPassword = privateKeyPassword; + + if(StringUtils.hasText(certificate)) { + validateCert(); + } + } + public String getPrivateKey() { return privateKey; } @@ -57,7 +76,19 @@ public String getPrivateKeyPassword() { return privateKeyPassword; } - public void setPrivateKeyPassword(String privateKeyPassword) { - this.privateKeyPassword = privateKeyPassword; + @JsonIgnore + public java.security.KeyPair getKeyPair() { + if(keyCert != null) { return keyCert.getPkey(); } + else { return null; } + } + + @JsonIgnore + public X509Certificate getParsedCertificate() { + if(keyCert != null) { return keyCert.getCert(); } + else { return null; } + } + + private void validateCert() throws CertificateException { + keyCert = new KeyWithCert(privateKey, privateKeyPassword, certificate); } } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index 5704b6cfc77..fe9e07df13b 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -17,6 +17,9 @@ import org.junit.Before; import org.junit.Test; +import java.security.Security; +import java.security.cert.CertificateException; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -25,6 +28,10 @@ public class SamlConfigTest { SamlConfig config; + public SamlConfigTest() { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + @Before public void setUp() { config = new SamlConfig(); @@ -42,9 +49,92 @@ public void testIsWantAssertionSigned() throws Exception { } @Test - public void testSetPassphrase() { + public void testSetKeyAndCert() throws CertificateException { + String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + + "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + + "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + + "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + + "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + + "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + + "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + + "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + + "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + + "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + + "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + + "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + + "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + + "-----END RSA PRIVATE KEY-----\n"; String passphrase = "password"; - config.setPrivateKeyPassword(passphrase); + + String certificate = "-----BEGIN CERTIFICATE-----\n" + + "MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\n" + + "VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\n" + + "BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\n" + + "VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\n" + + "aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\n" + + "N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\n" + + "YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\n" + + "MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\n" + + "ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\n" + + "gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\n" + + "jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\n" + + "zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\n" + + "ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\n" + + "1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\n" + + "MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\n" + + "Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\n" + + "BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\n" + + "QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n" + + "BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\n" + + "enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\n" + + "hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\n" + + "-----END CERTIFICATE-----\n"; + + config.setPrivateKeyAndPassword(privateKey, passphrase); + config.setCertificate(certificate); + assertEquals(privateKey, config.getPrivateKey()); assertEquals(passphrase, config.getPrivateKeyPassword()); } -} \ No newline at end of file + + @Test(expected = CertificateException.class) + public void testCertificateExceptionThrownWhenCertDoesNotMatchKey() throws CertificateException { + String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + + "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + + "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + + "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + + "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + + "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + + "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + + "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + + "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + + "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + + "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + + "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + + "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + + "-----END RSA PRIVATE KEY-----\n"; + String passphrase = "password"; + + String certificate = "-----BEGIN CERTIFICATE-----\n" + + "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + + "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + + "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + + "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + + "ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY\n" + + "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + + "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + + "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + + "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + + "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + + "-----END CERTIFICATE-----\n"; + + try { + config.setPrivateKeyAndPassword(privateKey, passphrase); + config.setCertificate(certificate); + } catch(CertificateException ex) { + assertEquals("Certificate does not match private key.", ex.getMessage()); + throw ex; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java index ce8a6aece89..c3ba800da53 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java @@ -12,13 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; import java.security.KeyPair; import java.security.KeyStore; import java.security.Security; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Set; @@ -26,8 +23,7 @@ import javax.net.ssl.KeyManagerFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMReader; -import org.bouncycastle.openssl.PasswordFinder; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; import org.opensaml.xml.security.CriteriaSet; import org.opensaml.xml.security.SecurityException; import org.opensaml.xml.security.credential.Credential; @@ -49,12 +45,9 @@ public SamlLoginServerKeyManager(String key, String password, String certificate } try { - PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certificate.getBytes()))); - X509Certificate cert = (X509Certificate) reader.readObject(); - - reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(key.getBytes())), - new StringPasswordFinder(password)); - KeyPair pkey = (KeyPair) reader.readObject(); + KeyWithCert keyWithCert = new KeyWithCert(key, password, certificate); + X509Certificate cert = keyWithCert.getCert(); + KeyPair pkey = keyWithCert.getPkey(); KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(null); @@ -73,10 +66,6 @@ public SamlLoginServerKeyManager(String key, String password, String certificate "Could not load service provider certificate. Check serviceProviderKey and certificate parameters"); } - if (!cert.getPublicKey().equals(pkey.getPublic())) { - throw new CertificateException("Certificate does not match private key."); - } - logger.info("Loaded service provider certificate " + keyManager.getDefaultCredentialName()); } catch (Throwable t) { logger.error("Could not load certificate", t); @@ -86,21 +75,6 @@ public SamlLoginServerKeyManager(String key, String password, String certificate } } - private class StringPasswordFinder implements PasswordFinder { - - private String password = null; - - public StringPasswordFinder(String password) { - this.password = password; - } - - @Override - public char[] getPassword() { - return password.toCharArray(); - } - - } - @Override public Iterable resolve(CriteriaSet criteria) throws SecurityException { return keyManager.resolve(criteria); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index c60f535ec4d..999afa6a0c2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -432,9 +432,53 @@ public void testCreateZoneAndIdentityProvider() throws Exception { pair.setVerificationKey("public_key_2"); keyPairs.put("key_id_2", pair2); tokenPolicy.setKeys(keyPairs); + SamlConfig samlConfig = new SamlConfig(); - samlConfig.setCertificate("saml-certificate"); - samlConfig.setPrivateKey("saml-private-key"); + + String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + + "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + + "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + + "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + + "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + + "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + + "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + + "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + + "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + + "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + + "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + + "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + + "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + + "-----END RSA PRIVATE KEY-----\n"; + String samlKeyPassphrase = "password"; + + String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + + "MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\n" + + "VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\n" + + "BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\n" + + "VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\n" + + "aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\n" + + "N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\n" + + "YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\n" + + "MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\n" + + "ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\n" + + "gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\n" + + "jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\n" + + "zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\n" + + "ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\n" + + "1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\n" + + "MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\n" + + "Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\n" + + "BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\n" + + "QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n" + + "BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\n" + + "enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\n" + + "hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\n" + + "-----END CERTIFICATE-----\n"; + + samlConfig.setCertificate(samlCertificate); + samlConfig.setPrivateKeyAndPassword(samlPrivateKey, samlKeyPassphrase); + IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); identityZone.setConfig(definition.setSamlConfig(samlConfig)); @@ -465,8 +509,8 @@ public void testCreateZoneAndIdentityProvider() throws Exception { IdentityZone createdZone = identityZoneProvisioning.retrieve(id); assertEquals(JsonUtils.writeValueAsString(definition), JsonUtils.writeValueAsString(createdZone.getConfig())); - assertEquals("saml-certificate", createdZone.getConfig().getSamlConfig().getCertificate()); - assertEquals("saml-private-key", createdZone.getConfig().getSamlConfig().getPrivateKey()); + assertEquals(samlCertificate, createdZone.getConfig().getSamlConfig().getCertificate()); + assertEquals(samlPrivateKey, createdZone.getConfig().getSamlConfig().getPrivateKey()); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java index d6cc07f1ef9..6b1aec7fb10 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java @@ -439,8 +439,7 @@ public void test_zone_saml_properties() throws Exception { SamlConfig config1 = new SamlConfig(); config1. setWantAssertionSigned(true); config1. setRequestSigned(true); - config1.setPrivateKey(serviceProviderKey); - config1.setPrivateKeyPassword(serviceProviderKeyPassword); + config1.setPrivateKeyAndPassword(serviceProviderKey, serviceProviderKeyPassword); config1.setCertificate(serviceProviderCertificate); IdentityZoneConfiguration zoneConfig1 = new IdentityZoneConfiguration(null); From 645b8a911902c66d368f0dc56f19afb63253d416 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 16 Feb 2016 18:04:25 -0700 Subject: [PATCH 46/87] Make sure we ignore unknown LDAP attributes (such as ldapdebug) during upgrade https://www.pivotaltracker.com/story/show/113881297 [#113881297] --- .../uaa/provider/AbstractIdentityProviderDefinition.java | 4 +++- .../uaa/config/IdentityProviderBootstrapTest.java | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java index b2d00fbb1ee..20971d06d53 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java @@ -14,9 +14,11 @@ package org.cloudfoundry.identity.uaa.provider; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import java.util.List; import java.util.Map; - +@JsonIgnoreProperties(ignoreUnknown = true) public class AbstractIdentityProviderDefinition { public static final String EMAIL_DOMAIN_ATTR = "emailDomain"; public static final String PROVIDER_DESCRIPTION = "providerDescription"; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index c3795782b7e..3925315b416 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -62,6 +62,15 @@ public void clearIdentityHolder() { IdentityZoneHolder.clear(); } + @Test + public void testUpgradeLDAPProvider() throws Exception { + String insertSQL = "INSERT INTO identity_provider (id,identity_zone_id,name,origin_key,type,config)VALUES ('ldap','uaa','ldap','ldap2','ldap','{\"ldapdebug\":\"Test debug\",\"profile\":{\"file\":\"ldap/ldap-search-and-bind.xml\"},\"base\":{\"url\":\"ldap://localhost:389/\",\"userDn\":\"cn=admin,dc=test,dc=com\",\"password\":\"password\",\"searchBase\":\"dc=test,dc=com\",\"searchFilter\":\"cn={0}\",\"referral\":\"follow\"},\"groups\":{\"file\":\"ldap/ldap-groups-map-to-scopes.xml\",\"searchBase\":\"dc=test,dc=com\",\"groupSearchFilter\":\"member={0}\",\"searchSubtree\":true,\"maxSearchDepth\":10,\"autoAdd\":true,\"ignorePartialResultException\":true}}')"; + jdbcTemplate.update(insertSQL); + IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); + IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, environment); + bootstrap.afterPropertiesSet(); + } + @Test public void testLdapProfileBootstrap() throws Exception { MockEnvironment environment = new MockEnvironment(); From b93c87aa210097539a5778448cec5ca281b5de17 Mon Sep 17 00:00:00 2001 From: amiri Date: Sun, 31 Jan 2016 19:22:39 -0800 Subject: [PATCH 47/87] Added feature that turns UAA into a SAML Identity Provider. --- .../identity/uaa/zone/SamlConfig.java | 27 + .../identity/uaa/audit/AuditEventType.java | 4 +- .../SamlServiceProviderEndpoints.java | 148 +++ .../saml/ZoneAwareMetadataManager.java | 39 +- .../saml/idp/IdpExtendedMetadata.java | 39 + .../saml/idp/IdpMetadataGenerator.java | 887 ++++++++++++++++++ .../saml/idp/IdpMetadataGeneratorFilter.java | 229 +++++ .../provider/saml/idp/IdpMetadataManager.java | 30 + .../saml/idp/IdpSamlAuthentication.java | 83 ++ .../idp/IdpSamlAuthenticationProvider.java | 33 + .../IdpSamlAuthenticationSuccessHandler.java | 125 +++ .../saml/idp/IdpSamlContextProviderImpl.java | 26 + .../saml/idp/IdpWebSSOProfileOptions.java | 33 + .../provider/saml/idp/IdpWebSsoProfile.java | 20 + .../saml/idp/IdpWebSsoProfileImpl.java | 300 ++++++ .../JdbcSamlServiceProviderProvisioning.java | 215 +++++ .../saml/idp/SamlServiceProvider.java | 307 ++++++ .../SamlServiceProviderChangedListener.java | 79 ++ .../idp/SamlServiceProviderConfigurator.java | 298 ++++++ .../idp/SamlServiceProviderDefinition.java | 294 ++++++ .../idp/SamlServiceProviderDeletable.java | 33 + .../idp/SamlServiceProviderProvisioning.java | 20 + .../idp/SamlSpAlreadyExistsException.java | 15 + .../idp/ZoneAwareIdpMetadataGenerator.java | 75 ++ .../saml/idp/ZoneAwareIdpMetadataManager.java | 738 +++++++++++++++ .../event/ServiceProviderEventPublisher.java | 41 + .../event/ServiceProviderModifiedEvent.java | 50 + server/src/main/resources/login-ui.xml | 10 +- .../db/hsqldb/V2_7_6__SAML_SP_Management.sql | 13 + .../db/mysql/V2_7_6__SAML_SP_Management.sql | 13 + .../postgresql/V2_7_6__SAML_SP_Management.sql | 13 + .../uaa/audit/AuditEventTypeTests.java | 2 +- ...pSamlAuthenticationSuccessHandlerTest.java | 171 ++++ .../saml/idp/IdpWebSsoProfileImplTest.java | 81 ++ ...bcSamlServiceProviderProvisioningTest.java | 222 +++++ .../SamlServiceProviderConfiguratorTest.java | 100 ++ .../SamlServiceProviderDefinitionTest.java | 90 ++ .../uaa/provider/saml/idp/SamlTestUtils.java | 414 ++++++++ .../ZoneAwareIdpMetadataGeneratorTest.java | 53 ++ .../main/webapp/WEB-INF/spring-servlet.xml | 1 + .../WEB-INF/spring/multitenant-endpoints.xml | 48 + .../main/webapp/WEB-INF/spring/saml-idp.xml | 145 +++ .../webapp/WEB-INF/spring/saml-providers.xml | 30 +- .../feature/SamlLoginWithLocalIdpIT.java | 561 +++++++++++ .../util/IntegrationTestUtils.java | 75 +- 45 files changed, 6201 insertions(+), 29 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderEventPublisher.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderModifiedEvent.java create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGeneratorTest.java create mode 100644 uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml create mode 100644 uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index bc8fdd61d79..024d73ab2c4 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -22,8 +22,11 @@ import java.security.cert.X509Certificate; public class SamlConfig { + private boolean assertionSigned = true; private boolean requestSigned = true; private boolean wantAssertionSigned = false; + private boolean wantAuthnRequestSigned = false; + private int assertionTimeToLiveSeconds = 600; private String certificate; private String privateKey; private String privateKeyPassword; @@ -31,6 +34,14 @@ public class SamlConfig { @JsonIgnore private KeyWithCert keyCert; + public boolean isAssertionSigned() { + return assertionSigned; + } + + public void setAssertionSigned(boolean assertionSigned) { + this.assertionSigned = assertionSigned; + } + public boolean isRequestSigned() { return requestSigned; } @@ -55,6 +66,22 @@ public void setCertificate(String certificate) throws CertificateException { } } + public boolean isWantAuthnRequestSigned() { + return wantAuthnRequestSigned; + } + + public void setWantAuthnRequestSigned(boolean wantAuthnRequestSigned) { + this.wantAuthnRequestSigned = wantAuthnRequestSigned; + } + + public int getAssertionTimeToLiveSeconds() { + return assertionTimeToLiveSeconds; + } + + public void setAssertionTimeToLiveSeconds(int assertionTimeToLiveSeconds) { + this.assertionTimeToLiveSeconds = assertionTimeToLiveSeconds; + } + public String getCertificate() { return certificate; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java index e87132f9e0c..0dc123f0950 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java @@ -53,7 +53,9 @@ public enum AuditEventType { IdentityProviderModifiedEvent(29), IdentityZoneCreatedEvent(30), IdentityZoneModifiedEvent(31), - EntityDeletedEvent(32); + EntityDeletedEvent(32), + ServiceProviderCreatedEvent(33), + ServiceProviderModifiedEvent(34); private final int code; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java new file mode 100644 index 00000000000..b1e500a2f33 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.provider; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderConfigurator; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderProvisioning; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.web.bind.annotation.ExceptionHandler; +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; + +@RequestMapping("/service-providers") +@RestController +public class SamlServiceProviderEndpoints { + + protected static Log logger = LogFactory.getLog(SamlServiceProviderEndpoints.class); + + private final SamlServiceProviderProvisioning serviceProviderProvisioning; + private final SamlServiceProviderConfigurator samlConfigurator; + + public SamlServiceProviderEndpoints(SamlServiceProviderProvisioning serviceProviderProvisioning, + SamlServiceProviderConfigurator samlConfigurator) { + this.serviceProviderProvisioning = serviceProviderProvisioning; + this.samlConfigurator = samlConfigurator; + } + + @RequestMapping(method = POST) + public ResponseEntity createServiceProvider(@RequestBody SamlServiceProvider body) + throws MetadataProviderException { + String zoneId = IdentityZoneHolder.get().getId(); + body.setIdentityZoneId(zoneId); + + SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), + SamlServiceProviderDefinition.class); + definition.setZoneId(zoneId); + definition.setSpEntityId(body.getEntityId()); + samlConfigurator.addSamlServiceProviderDefinition(definition); + body.setConfig(definition); + + SamlServiceProvider createdSp = serviceProviderProvisioning.create(body); + return new ResponseEntity<>(createdSp, HttpStatus.CREATED); + } + + @RequestMapping(value = "{id}", method = PUT) + public ResponseEntity updateServiceProvider(@PathVariable String id, + @RequestBody SamlServiceProvider body) throws MetadataProviderException { + SamlServiceProvider existing = serviceProviderProvisioning.retrieve(id); + String zoneId = IdentityZoneHolder.get().getId(); + body.setId(id); + body.setIdentityZoneId(zoneId); + if (!body.configIsValid()) { + return new ResponseEntity<>(UNPROCESSABLE_ENTITY); + } + body.setEntityId(existing.getEntityId()); + SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), + SamlServiceProviderDefinition.class); + definition.setZoneId(zoneId); + definition.setSpEntityId(body.getEntityId()); + samlConfigurator.addSamlServiceProviderDefinition(definition); + body.setConfig(definition); + + SamlServiceProvider updatedSp = serviceProviderProvisioning.update(body); + return new ResponseEntity<>(updatedSp, OK); + } + + @RequestMapping(method = GET) + public ResponseEntity> retrieveServiceProviders( + @RequestParam(value = "active_only", required = false) String activeOnly) { + Boolean retrieveActiveOnly = Boolean.valueOf(activeOnly); + List serviceProviderList = serviceProviderProvisioning.retrieveAll(retrieveActiveOnly, + IdentityZoneHolder.get().getId()); + return new ResponseEntity<>(serviceProviderList, OK); + } + + @RequestMapping(value = "{id}", method = GET) + public ResponseEntity retrieveServiceProvider(@PathVariable String id) { + SamlServiceProvider serviceProvider = serviceProviderProvisioning.retrieve(id); + return new ResponseEntity<>(serviceProvider, OK); + } + + @ExceptionHandler(MetadataProviderException.class) + public ResponseEntity handleMetadataProviderException(MetadataProviderException e) { + if (e.getMessage().contains("Duplicate")) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT); + } else { + return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } + } + + @ExceptionHandler(JsonUtils.JsonUtilException.class) + public ResponseEntity handleMetadataProviderException() { + return new ResponseEntity<>("Invalid provider configuration.", HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(EmptyResultDataAccessException.class) + public ResponseEntity handleProviderNotFoundException() { + return new ResponseEntity<>("Provider not found.", HttpStatus.NOT_FOUND); + } + + protected String getExceptionString(Exception x) { + StringWriter writer = new StringWriter(); + x.printStackTrace(new PrintWriter(writer)); + return writer.getBuffer().toString(); + } + + protected static class NoOpLdapLoginAuthenticationManager extends LdapLoginAuthenticationManager { + @Override + public Authentication authenticate(Authentication request) throws AuthenticationException { + return request; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java index add96683342..bd4deade0ff 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java @@ -14,13 +14,26 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -45,18 +58,6 @@ import org.springframework.security.saml.metadata.MetadataManager; import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer; -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; - public class ZoneAwareMetadataManager extends MetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean, BeanNameAware { private static final Log logger = LogFactory.getLog(ZoneAwareMetadataManager.class); @@ -69,13 +70,11 @@ public class ZoneAwareMetadataManager extends MetadataManager implements Extende private long lastRefresh = 0; private Timer timer; private String beanName = ZoneAwareMetadataManager.class.getName()+"-"+System.identityHashCode(this); - private ProviderChangedListener providerChangedListener; public ZoneAwareMetadataManager(IdentityProviderProvisioning providerDao, IdentityZoneProvisioning zoneDao, SamlIdentityProviderConfigurator configurator, - KeyManager keyManager, - ProviderChangedListener listener) throws MetadataProviderException { + KeyManager keyManager) throws MetadataProviderException { super(Collections.emptyList()); this.providerDao = providerDao; this.zoneDao = zoneDao; @@ -87,7 +86,6 @@ public ZoneAwareMetadataManager(IdentityProviderProvisioning providerDao, if (metadataManagers==null) { metadataManagers = new ConcurrentHashMap<>(); } - providerChangedListener = listener; } private class RefreshTask extends TimerTask { @@ -114,11 +112,11 @@ public void checkAllProviders() throws MetadataProviderException { refreshAllProviders(); timer = new Timer("ZoneAwareMetadataManager.Refresh["+beanName+"]", true); timer.schedule(new RefreshTask(),refreshInterval , refreshInterval); - providerChangedListener.setMetadataManager(this); } protected void refreshAllProviders() throws MetadataProviderException { refreshAllProviders(true); + //refreshAllSamlServiceProviders(true); } protected String getThreadNameAndId() { @@ -175,7 +173,7 @@ protected void removeSamlProvider(IdentityZone zone, ExtensionMetadataManager ma } } - protected ExtensionMetadataManager getManager(IdentityZone zone) { + public ExtensionMetadataManager getManager(IdentityZone zone) { if (metadataManagers==null) { //called during super constructor metadataManagers = new ConcurrentHashMap<>(); } @@ -191,7 +189,7 @@ protected ExtensionMetadataManager getManager(IdentityZone zone) { } return metadataManagers.get(zone); } - protected ExtensionMetadataManager getManager() { + public ExtensionMetadataManager getManager() { return getManager(IdentityZoneHolder.get()); } @@ -454,6 +452,7 @@ protected Set refreshZoneManager(ExtensionMetadataManager ma //just so that we can override protected methods public static class ExtensionMetadataManager extends CachingMetadataManager { + public ExtensionMetadataManager(List providers) throws MetadataProviderException { super(providers); //disable internal timers (they only get created when afterPropertiesSet) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java new file mode 100644 index 00000000000..99efe0f0bfc --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.springframework.security.saml.metadata.ExtendedMetadata; + +/** + * A SAML IdP needs information beyond what the standard ExtendedMetadata provides. + * This class exists to provide that extra information. + */ +public class IdpExtendedMetadata extends ExtendedMetadata { + + /** + * Generated serialization id. + */ + private static final long serialVersionUID = -7933870052729540864L; + + private boolean assertionsSigned = true; + private int assertionTimeToLiveSeconds = 500; + + public boolean isAssertionsSigned() { + return assertionsSigned; + } + + public void setAssertionsSigned(boolean assertionsSigned) { + this.assertionsSigned = assertionsSigned; + } + + public int getAssertionTimeToLiveSeconds() { + return assertionTimeToLiveSeconds; + } + + public void setAssertionTimeToLiveSeconds(int assertionTimeToLiveSeconds) { + this.assertionTimeToLiveSeconds = assertionTimeToLiveSeconds; + } + + @Override + public IdpExtendedMetadata clone() { + return (IdpExtendedMetadata) super.clone(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java new file mode 100644 index 00000000000..1d56a014975 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java @@ -0,0 +1,887 @@ +/* Copyright 2009 Vladimir Schäfer +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.opensaml.Configuration; +import org.opensaml.common.SAMLObjectBuilder; +import org.opensaml.common.SAMLRuntimeException; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.common.Extensions; +import org.opensaml.saml2.common.impl.ExtensionsBuilder; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.NameIDType; +import org.opensaml.saml2.metadata.*; +import org.opensaml.samlext.idpdisco.DiscoveryResponse; +import org.opensaml.util.URLBuilder; +import org.opensaml.xml.XMLObjectBuilderFactory; +import org.opensaml.xml.security.SecurityHelper; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.keyinfo.KeyInfoGenerator; +import org.opensaml.xml.signature.KeyInfo; +import org.opensaml.xml.util.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.saml.*; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.util.SAMLUtil; + +import javax.xml.namespace.QName; +import java.util.*; + +/** + * The class is responsible for generating the metadata that describes the identity provider in the current deployment + * environment. All the URLs in the metadata derive from information provided by the ServletContext. + * + * This code for this class is based on org.springframework.security.saml.metadata.MetadataGenerator. + */ +public class IdpMetadataGenerator { + + private String id; + private String entityId; + private String entityBaseURL; + + private boolean wantAuthnRequestSigned = true; + + /** + * Index of the assertion consumer endpoint marked as default. + */ + private int assertionConsumerIndex = 0; + + /** + * Extended metadata with details on metadata generation. + */ + private IdpExtendedMetadata extendedMetadata; + + // List of case-insensitive alias terms + private static TreeMap aliases = new TreeMap(String.CASE_INSENSITIVE_ORDER); + + static { + aliases.put(SAMLConstants.SAML2_POST_BINDING_URI, SAMLConstants.SAML2_POST_BINDING_URI); + aliases.put("post", SAMLConstants.SAML2_POST_BINDING_URI); + aliases.put("http-post", SAMLConstants.SAML2_POST_BINDING_URI); + aliases.put(SAMLConstants.SAML2_PAOS_BINDING_URI, SAMLConstants.SAML2_PAOS_BINDING_URI); + aliases.put("paos", SAMLConstants.SAML2_PAOS_BINDING_URI); + aliases.put(SAMLConstants.SAML2_ARTIFACT_BINDING_URI, SAMLConstants.SAML2_ARTIFACT_BINDING_URI); + aliases.put("artifact", SAMLConstants.SAML2_ARTIFACT_BINDING_URI); + aliases.put("http-artifact", SAMLConstants.SAML2_ARTIFACT_BINDING_URI); + aliases.put(SAMLConstants.SAML2_REDIRECT_BINDING_URI, SAMLConstants.SAML2_REDIRECT_BINDING_URI); + aliases.put("redirect", SAMLConstants.SAML2_REDIRECT_BINDING_URI); + aliases.put("http-redirect", SAMLConstants.SAML2_REDIRECT_BINDING_URI); + aliases.put(SAMLConstants.SAML2_SOAP11_BINDING_URI, SAMLConstants.SAML2_SOAP11_BINDING_URI); + aliases.put("soap", SAMLConstants.SAML2_SOAP11_BINDING_URI); + aliases.put(NameIDType.EMAIL, NameIDType.EMAIL); + aliases.put("email", NameIDType.EMAIL); + aliases.put(NameIDType.TRANSIENT, NameIDType.TRANSIENT); + aliases.put("transient", NameIDType.TRANSIENT); + aliases.put(NameIDType.PERSISTENT, NameIDType.PERSISTENT); + aliases.put("persistent", NameIDType.PERSISTENT); + aliases.put(NameIDType.UNSPECIFIED, NameIDType.UNSPECIFIED); + aliases.put("unspecified", NameIDType.UNSPECIFIED); + aliases.put(NameIDType.X509_SUBJECT, NameIDType.X509_SUBJECT); + aliases.put("x509_subject", NameIDType.X509_SUBJECT); + } + + /** + * Bindings for single sign-on + */ + private Collection bindingsSSO = Arrays.asList("post", "artifact"); + + /** + * Bindings for single sign-on holder of key + */ + private Collection bindingsHoKSSO = Arrays.asList(); + + /** + * Bindings for single logout + */ + private Collection bindingsSLO = Arrays.asList("post", "redirect"); + + /** + * Flag indicates whether to include extension with discovery endpoints in metadata. + */ + private boolean includeDiscoveryExtension; + + /** + * NameIDs to be included in generated metadata. + */ + private Collection nameID = null; + + /** + * Default set of NameIDs included in metadata. + */ + public static final Collection defaultNameID = Arrays.asList(NameIDType.EMAIL, NameIDType.TRANSIENT, + NameIDType.PERSISTENT, NameIDType.UNSPECIFIED, NameIDType.X509_SUBJECT); + + protected XMLObjectBuilderFactory builderFactory; + + /** + * Source of certificates. + */ + protected KeyManager keyManager; + + /** + * Filters for loading of paths. + */ + protected SAMLProcessingFilter samlWebSSOFilter; + protected SAMLWebSSOHoKProcessingFilter samlWebSSOHoKFilter; + protected SAMLLogoutProcessingFilter samlLogoutProcessingFilter; + protected SAMLEntryPoint samlEntryPoint; + protected SAMLDiscovery samlDiscovery; + + /** + * Class logger. + */ + protected final static Logger log = LoggerFactory.getLogger(IdpMetadataGenerator.class); + + /** + * Default constructor. + */ + public IdpMetadataGenerator() { + this.builderFactory = Configuration.getBuilderFactory(); + } + + public EntityDescriptor generateMetadata() { + + boolean wantAuthnRequestSigned = isWantAuthnRequestSigned(); + + Collection includedNameID = getNameID(); + + String entityId = getEntityId(); + String entityBaseURL = getEntityBaseURL(); + String entityAlias = getEntityAlias(); + + validateRequiredAttributes(entityId, entityBaseURL); + + if (id == null) { + // Use entityID cleaned as NCName for ID in case no value is provided + id = SAMLUtil.getNCNameString(entityId); + } + + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(EntityDescriptor.DEFAULT_ELEMENT_NAME); + EntityDescriptor descriptor = builder.buildObject(); + if (id != null) { + descriptor.setID(id); + } + descriptor.setEntityID(entityId); + + IDPSSODescriptor ssoDescriptor = buildIDPSSODescriptor(entityBaseURL, entityAlias, wantAuthnRequestSigned, + includedNameID); + if (ssoDescriptor != null) { + descriptor.getRoleDescriptors().add(ssoDescriptor); + } + + return descriptor; + } + + protected void validateRequiredAttributes(String entityId, String entityBaseURL) { + if (entityId == null || entityBaseURL == null) { + throw new RuntimeException("Required attributes entityId or entityBaseURL weren't set"); + } + } + + protected KeyInfo getServerKeyInfo(String alias) { + Credential serverCredential = keyManager.getCredential(alias); + if (serverCredential == null) { + throw new RuntimeException("Key for alias " + alias + " not found"); + } else if (serverCredential.getPrivateKey() == null) { + throw new RuntimeException("Key with alias " + alias + " doesn't have a private key"); + } + return generateKeyInfoForCredential(serverCredential); + } + + /** + * Generates extended metadata. Default extendedMetadata object is cloned if present and used for defaults. The + * following properties are always overriden from the properties of this bean: discoveryUrl, discoveryResponseUrl, + * signingKey, encryptionKey, entityAlias and tlsKey. Property local of the generated metadata is always set to + * true. + * + * @return generated extended metadata + */ + public IdpExtendedMetadata generateExtendedMetadata() { + + IdpExtendedMetadata metadata; + + if (extendedMetadata != null) { + metadata = extendedMetadata.clone(); + } else { + metadata = new IdpExtendedMetadata(); + } + + String entityBaseURL = getEntityBaseURL(); + String entityAlias = getEntityAlias(); + + if (isIncludeDiscovery()) { + metadata.setIdpDiscoveryURL(getDiscoveryURL(entityBaseURL, entityAlias)); + metadata.setIdpDiscoveryResponseURL(getDiscoveryResponseURL(entityBaseURL, entityAlias)); + } else { + metadata.setIdpDiscoveryURL(null); + metadata.setIdpDiscoveryResponseURL(null); + } + + metadata.setLocal(true); + metadata.setAssertionTimeToLiveSeconds(getAssertionTimeToLiveSeconds()); + metadata.setAssertionsSigned(isAssertionsSigned()); + return metadata; + + } + + protected KeyInfo generateKeyInfoForCredential(Credential credential) { + try { + String keyInfoGeneratorName = org.springframework.security.saml.SAMLConstants.SAML_METADATA_KEY_INFO_GENERATOR; + if (extendedMetadata != null && extendedMetadata.getKeyInfoGeneratorName() != null) { + keyInfoGeneratorName = extendedMetadata.getKeyInfoGeneratorName(); + } + KeyInfoGenerator keyInfoGenerator = SecurityHelper.getKeyInfoGenerator(credential, null, + keyInfoGeneratorName); + return keyInfoGenerator.generate(credential); + } catch (org.opensaml.xml.security.SecurityException e) { + log.error("Can't obtain key from the keystore or generate key info for credential: " + credential, e); + throw new SAMLRuntimeException("Can't obtain key from keystore or generate key info", e); + } + } + + protected IDPSSODescriptor buildIDPSSODescriptor(String entityBaseURL, String entityAlias, + boolean wantAuthnRequestSigned, Collection includedNameID) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + IDPSSODescriptor idpDescriptor = builder.buildObject(); + idpDescriptor.setWantAuthnRequestsSigned(wantAuthnRequestSigned); + idpDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + + // Name ID + idpDescriptor.getNameIDFormats().addAll(getNameIDFormat(includedNameID)); + + // Resolve alases + Collection bindingsSSO = mapAliases(getBindingsSSO()); + Collection bindingsSLO = mapAliases(getBindingsSLO()); + + // Assertion consumer MUST NOT be used with HTTP Redirect, Profiles 424, same applies to HoK profile + for (String binding : bindingsSSO) { + if (binding.equals(SAMLConstants.SAML2_ARTIFACT_BINDING_URI)) { + idpDescriptor.getSingleSignOnServices().add(getSingleSignOnService(entityBaseURL, entityAlias, + getSAMLWebSSOProcessingFilterPath(), SAMLConstants.SAML2_ARTIFACT_BINDING_URI)); + } + if (binding.equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + idpDescriptor.getSingleSignOnServices().add(getSingleSignOnService(entityBaseURL, entityAlias, + getSAMLWebSSOProcessingFilterPath(), SAMLConstants.SAML2_POST_BINDING_URI)); + } + if (binding.equals(SAMLConstants.SAML2_PAOS_BINDING_URI)) { + idpDescriptor.getSingleSignOnServices().add(getSingleSignOnService(entityBaseURL, entityAlias, + getSAMLWebSSOProcessingFilterPath(), SAMLConstants.SAML2_PAOS_BINDING_URI)); + } + } + + for (String binding : bindingsSLO) { + if (binding.equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + idpDescriptor.getSingleLogoutServices() + .add(getSingleLogoutService(entityBaseURL, entityAlias, SAMLConstants.SAML2_POST_BINDING_URI)); + } + if (binding.equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + idpDescriptor.getSingleLogoutServices().add( + getSingleLogoutService(entityBaseURL, entityAlias, SAMLConstants.SAML2_REDIRECT_BINDING_URI)); + } + if (binding.equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) { + idpDescriptor.getSingleLogoutServices().add( + getSingleLogoutService(entityBaseURL, entityAlias, SAMLConstants.SAML2_SOAP11_BINDING_URI)); + } + } + + // Build extensions + Extensions extensions = buildExtensions(entityBaseURL, entityAlias); + if (extensions != null) { + idpDescriptor.setExtensions(extensions); + } + + // Populate key aliases + String signingKey = getSigningKey(); + String encryptionKey = getEncryptionKey(); + String tlsKey = getTLSKey(); + + // Generate key info + if (signingKey != null) { + idpDescriptor.getKeyDescriptors().add(getKeyDescriptor(UsageType.SIGNING, getServerKeyInfo(signingKey))); + } else { + log.info( + "Generating metadata without signing key, KeyStore doesn't contain any default private key, or the signingKey specified in ExtendedMetadata cannot be found"); + } + if (encryptionKey != null) { + idpDescriptor.getKeyDescriptors() + .add(getKeyDescriptor(UsageType.ENCRYPTION, getServerKeyInfo(encryptionKey))); + } else { + log.info( + "Generating metadata without encryption key, KeyStore doesn't contain any default private key, or the encryptionKey specified in ExtendedMetadata cannot be found"); + } + + // Include TLS key with unspecified usage in case it differs from the singing and encryption keys + if (tlsKey != null && !(tlsKey.equals(encryptionKey)) && !(tlsKey.equals(signingKey))) { + idpDescriptor.getKeyDescriptors().add(getKeyDescriptor(UsageType.UNSPECIFIED, getServerKeyInfo(tlsKey))); + } + + return idpDescriptor; + } + + /** + * Method iterates all values in the input, for each tries to resolve correct alias. When alias value is found, it + * is entered into the return collection, otherwise warning is logged. Values are returned in order of input with + * all duplicities removed. + * + * @param values + * input collection + * @return result with resolved aliases + */ + protected Collection mapAliases(Collection values) { + LinkedHashSet result = new LinkedHashSet(); + for (String value : values) { + String alias = aliases.get(value); + if (alias != null) { + result.add(alias); + } else { + log.warn("Unsupported value " + value + " found"); + } + } + return result; + } + + protected Extensions buildExtensions(String entityBaseURL, String entityAlias) { + + boolean include = false; + Extensions extensions = new ExtensionsBuilder().buildObject(); + + // Add discovery + if (isIncludeDiscoveryExtension()) { + DiscoveryResponse discoveryService = getDiscoveryService(entityBaseURL, entityAlias); + extensions.getUnknownXMLObjects().add(discoveryService); + include = true; + } + + if (include) { + return extensions; + } else { + return null; + } + + } + + protected KeyDescriptor getKeyDescriptor(UsageType type, KeyInfo key) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) Configuration.getBuilderFactory() + .getBuilder(KeyDescriptor.DEFAULT_ELEMENT_NAME); + KeyDescriptor descriptor = builder.buildObject(); + descriptor.setUse(type); + descriptor.setKeyInfo(key); + return descriptor; + } + + protected Collection getNameIDFormat(Collection includedNameID) { + + // Resolve alases + includedNameID = mapAliases(includedNameID); + Collection formats = new LinkedList(); + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(NameIDFormat.DEFAULT_ELEMENT_NAME); + + // Populate nameIDs + for (String nameIDValue : includedNameID) { + + if (nameIDValue.equals(NameIDType.EMAIL)) { + NameIDFormat nameID = builder.buildObject(); + nameID.setFormat(NameIDType.EMAIL); + formats.add(nameID); + } + + if (nameIDValue.equals(NameIDType.TRANSIENT)) { + NameIDFormat nameID = builder.buildObject(); + nameID.setFormat(NameIDType.TRANSIENT); + formats.add(nameID); + } + + if (nameIDValue.equals(NameIDType.PERSISTENT)) { + NameIDFormat nameID = builder.buildObject(); + nameID.setFormat(NameIDType.PERSISTENT); + formats.add(nameID); + } + + if (nameIDValue.equals(NameIDType.UNSPECIFIED)) { + NameIDFormat nameID = builder.buildObject(); + nameID.setFormat(NameIDType.UNSPECIFIED); + formats.add(nameID); + } + + if (nameIDValue.equals(NameIDType.X509_SUBJECT)) { + NameIDFormat nameID = builder.buildObject(); + nameID.setFormat(NameIDType.X509_SUBJECT); + formats.add(nameID); + } + + } + + return formats; + + } + + protected SingleSignOnService getSingleSignOnService(String entityBaseURL, String entityAlias, String filterURL, + String binding) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(SingleSignOnService.DEFAULT_ELEMENT_NAME); + SingleSignOnService sso = builder.buildObject(); + sso.setLocation(getServerURL(entityBaseURL, entityAlias, filterURL)); + sso.setBinding(binding); + return sso; + } + + protected SingleSignOnService getHoKSingleSignOnService(String entityBaseURL, String entityAlias, String filterURL, + String binding) { + SingleSignOnService hokSso = getSingleSignOnService(entityBaseURL, entityAlias, filterURL, + org.springframework.security.saml.SAMLConstants.SAML2_HOK_WEBSSO_PROFILE_URI); + QName consumerName = new QName(org.springframework.security.saml.SAMLConstants.SAML2_HOK_WEBSSO_PROFILE_URI, + AuthnRequest.PROTOCOL_BINDING_ATTRIB_NAME, "hoksso"); + hokSso.getUnknownAttributes().put(consumerName, binding); + return hokSso; + } + + protected DiscoveryResponse getDiscoveryService(String entityBaseURL, String entityAlias) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(DiscoveryResponse.DEFAULT_ELEMENT_NAME); + DiscoveryResponse discovery = builder.buildObject(DiscoveryResponse.DEFAULT_ELEMENT_NAME); + discovery.setBinding(DiscoveryResponse.IDP_DISCO_NS); + discovery.setLocation(getDiscoveryResponseURL(entityBaseURL, entityAlias)); + return discovery; + } + + protected SingleLogoutService getSingleLogoutService(String entityBaseURL, String entityAlias, String binding) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory + .getBuilder(SingleLogoutService.DEFAULT_ELEMENT_NAME); + SingleLogoutService logoutService = builder.buildObject(); + logoutService.setLocation(getServerURL(entityBaseURL, entityAlias, getSAMLLogoutFilterPath())); + logoutService.setBinding(binding); + return logoutService; + } + + /** + * Creates URL at which the local server is capable of accepting incoming SAML messages. + * + * @param entityBaseURL + * entity ID + * @param processingURL + * local context at which processing filter is waiting + * @return URL of local server + */ + private String getServerURL(String entityBaseURL, String entityAlias, String processingURL) { + + return getServerURL(entityBaseURL, entityAlias, processingURL, null); + + } + + /** + * Creates URL at which the local server is capable of accepting incoming SAML messages. + * + * @param entityBaseURL + * entity ID + * @param processingURL + * local context at which processing filter is waiting + * @param parameters + * key - value pairs to be included as query part of the generated url, can be null + * @return URL of local server + */ + private String getServerURL(String entityBaseURL, String entityAlias, String processingURL, + Map parameters) { + + StringBuilder result = new StringBuilder(); + result.append(entityBaseURL); + if (!processingURL.startsWith("/")) { + result.append("/"); + } + result.append(processingURL); + + if (entityAlias != null) { + if (!processingURL.endsWith("/")) { + result.append("/"); + } + result.append("alias/"); + result.append(entityAlias); + result.append("/idp"); + } + + String resultString = result.toString(); + + if (parameters == null || parameters.size() == 0) { + + return resultString; + + } else { + + // Add parameters + URLBuilder returnUrlBuilder = new URLBuilder(resultString); + for (Map.Entry entry : parameters.entrySet()) { + returnUrlBuilder.getQueryParams().add(new Pair(entry.getKey(), entry.getValue())); + } + return returnUrlBuilder.buildURL(); + + } + + } + + private String getSAMLWebSSOProcessingFilterPath() { + if (samlWebSSOFilter != null) { + return samlWebSSOFilter.getFilterProcessesUrl(); + } else { + return SAMLProcessingFilter.FILTER_URL; + } + } + + private String getSAMLEntryPointPath() { + if (samlEntryPoint != null) { + return samlEntryPoint.getFilterProcessesUrl(); + } else { + return SAMLEntryPoint.FILTER_URL; + } + } + + private String getSAMLDiscoveryPath() { + if (samlDiscovery != null) { + return samlDiscovery.getFilterProcessesUrl(); + } else { + return SAMLDiscovery.FILTER_URL; + } + } + + private String getSAMLLogoutFilterPath() { + if (samlLogoutProcessingFilter != null) { + return samlLogoutProcessingFilter.getFilterProcessesUrl(); + } else { + return SAMLLogoutProcessingFilter.FILTER_URL; + } + } + + @Autowired(required = false) + @Qualifier("samlWebSSOProcessingFilter") + public void setSamlWebSSOFilter(SAMLProcessingFilter samlWebSSOFilter) { + this.samlWebSSOFilter = samlWebSSOFilter; + } + + @Autowired(required = false) + @Qualifier("samlWebSSOHoKProcessingFilter") + public void setSamlWebSSOHoKFilter(SAMLWebSSOHoKProcessingFilter samlWebSSOHoKFilter) { + this.samlWebSSOHoKFilter = samlWebSSOHoKFilter; + } + + @Autowired(required = false) + public void setSamlLogoutProcessingFilter(SAMLLogoutProcessingFilter samlLogoutProcessingFilter) { + this.samlLogoutProcessingFilter = samlLogoutProcessingFilter; + } + + @Autowired(required = false) + public void setSamlEntryPoint(SAMLEntryPoint samlEntryPoint) { + this.samlEntryPoint = samlEntryPoint; + } + + public boolean isWantAuthnRequestSigned() { + return wantAuthnRequestSigned; + } + + public void setWantAuthnRequestSigned(boolean wantAuthnRequestSigned) { + this.wantAuthnRequestSigned = wantAuthnRequestSigned; + } + + public Collection getNameID() { + return nameID == null ? defaultNameID : nameID; + } + + public void setNameID(Collection nameID) { + this.nameID = nameID; + } + + public String getEntityBaseURL() { + return entityBaseURL; + } + + public void setEntityBaseURL(String entityBaseURL) { + this.entityBaseURL = entityBaseURL; + } + + @Autowired + public void setKeyManager(KeyManager keyManager) { + this.keyManager = keyManager; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public String getEntityId() { + return entityId; + } + + public Collection getBindingsSSO() { + return bindingsSSO; + } + + /** + * List of bindings to be included in the generated metadata for Web Single Sign-On. Ordering of bindings affects + * inclusion in the generated metadata. + * + * Supported values are: "artifact" (or "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"), "post" (or + * "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") and "paos" (or "urn:oasis:names:tc:SAML:2.0:bindings:PAOS"). + * + * The following bindings are included by default: "artifact", "post" + * + * @param bindingsSSO + * bindings for web single sign-on + */ + public void setBindingsSSO(Collection bindingsSSO) { + if (bindingsSSO == null) { + this.bindingsSSO = Collections.emptyList(); + } else { + this.bindingsSSO = bindingsSSO; + } + } + + public Collection getBindingsSLO() { + return bindingsSLO; + } + + /** + * List of bindings to be included in the generated metadata for Single Logout. Ordering of bindings affects + * inclusion in the generated metadata. + * + * Supported values are: "post" (or "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") and "redirect" (or + * "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"). + * + * The following bindings are included by default: "post", "redirect" + * + * @param bindingsSLO + * bindings for single logout + */ + public void setBindingsSLO(Collection bindingsSLO) { + if (bindingsSLO == null) { + this.bindingsSLO = Collections.emptyList(); + } else { + this.bindingsSLO = bindingsSLO; + } + } + + public Collection getBindingsHoKSSO() { + return bindingsHoKSSO; + } + + /** + * List of bindings to be included in the generated metadata for Web Single Sign-On Holder of Key. Ordering of + * bindings affects inclusion in the generated metadata. + * + * Supported values are: "artifact" (or "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact") and "post" (or + * "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"). + * + * By default there are no included bindings for the profile. + * + * @param bindingsHoKSSO + * bindings for web single sign-on holder-of-key + */ + public void setBindingsHoKSSO(Collection bindingsHoKSSO) { + if (bindingsHoKSSO == null) { + this.bindingsHoKSSO = Collections.emptyList(); + } else { + this.bindingsHoKSSO = bindingsHoKSSO; + } + } + + public boolean isIncludeDiscoveryExtension() { + return includeDiscoveryExtension; + } + + /** + * When true discovery profile extension metadata pointing to the default SAMLEntryPoint will be generated and + * stored in the generated metadata document. + * + * @param includeDiscoveryExtension + * flag indicating whether IDP discovery should be enabled + */ + public void setIncludeDiscoveryExtension(boolean includeDiscoveryExtension) { + this.includeDiscoveryExtension = includeDiscoveryExtension; + } + + public int getAssertionConsumerIndex() { + return assertionConsumerIndex; + } + + /** + * Generated assertion consumer service with the index equaling set value will be marked as default. Use negative + * value to skip the default attribute altogether. + * + * @param assertionConsumerIndex + * assertion consumer index of service to mark as default + */ + public void setAssertionConsumerIndex(int assertionConsumerIndex) { + this.assertionConsumerIndex = assertionConsumerIndex; + } + + /** + * True when IDP discovery is enabled either on local property includeDiscovery or property idpDiscoveryEnabled in + * the extended metadata. + * + * @return true when discovery is enabled + */ + protected boolean isIncludeDiscovery() { + return extendedMetadata != null && extendedMetadata.isIdpDiscoveryEnabled(); + } + + /** + * Provides set discovery request url or generates a default when none was provided. Primarily value set on + * extenedMetadata property idpDiscoveryURL is used, when empty local property customDiscoveryURL is used, when + * empty URL is automatically generated. + * + * @param entityBaseURL + * base URL for generation of endpoints + * @param entityAlias + * alias of entity, or null when there's no alias required + * @return URL to use for IDP discovery request + */ + protected String getDiscoveryURL(String entityBaseURL, String entityAlias) { + if (extendedMetadata != null && extendedMetadata.getIdpDiscoveryURL() != null + && extendedMetadata.getIdpDiscoveryURL().length() > 0) { + return extendedMetadata.getIdpDiscoveryURL(); + } else { + return getServerURL(entityBaseURL, entityAlias, getSAMLDiscoveryPath()); + } + } + + /** + * Provides set discovery response url or generates a default when none was provided. Primarily value set on + * extenedMetadata property idpDiscoveryResponseURL is used, when empty local property customDiscoveryResponseURL + * is used, when empty URL is automatically generated. + * + * @param entityBaseURL + * base URL for generation of endpoints + * @param entityAlias + * alias of entity, or null when there's no alias required + * @return URL to use for IDP discovery response + */ + protected String getDiscoveryResponseURL(String entityBaseURL, String entityAlias) { + if (extendedMetadata != null && extendedMetadata.getIdpDiscoveryResponseURL() != null + && extendedMetadata.getIdpDiscoveryResponseURL().length() > 0) { + return extendedMetadata.getIdpDiscoveryResponseURL(); + } else { + Map params = new HashMap(); + params.put(SAMLEntryPoint.DISCOVERY_RESPONSE_PARAMETER, "true"); + return getServerURL(entityBaseURL, entityAlias, getSAMLEntryPointPath(), params); + } + } + + /** + * Provides key used for signing from extended metadata. Uses default key when key is not specified. + * + * @return signing key + */ + protected String getSigningKey() { + if (extendedMetadata != null && extendedMetadata.getSigningKey() != null) { + return extendedMetadata.getSigningKey(); + } else { + return keyManager.getDefaultCredentialName(); + } + } + + /** + * Provides key used for encryption from extended metadata. Uses default when key is not specified. + * + * @return encryption key + */ + protected String getEncryptionKey() { + if (extendedMetadata != null && extendedMetadata.getEncryptionKey() != null) { + return extendedMetadata.getEncryptionKey(); + } else { + return keyManager.getDefaultCredentialName(); + } + } + + /** + * Provides key used for SSL/TLS from extended metadata. Uses null when key is not specified. + * + * @return tls key + */ + protected String getTLSKey() { + if (extendedMetadata != null && extendedMetadata.getTlsKey() != null) { + return extendedMetadata.getTlsKey(); + } else { + return null; + } + } + + /** + * Provides entity alias from extended metadata, or null when metadata isn't specified or contains null. + * + * @return entity alias + */ + protected String getEntityAlias() { + if (extendedMetadata != null) { + return extendedMetadata.getAlias(); + } else { + return null; + } + } + + public boolean isAssertionsSigned() { + if (extendedMetadata != null) { + return extendedMetadata.isAssertionsSigned(); + } else { + return true; + } + } + + public int getAssertionTimeToLiveSeconds() { + if (extendedMetadata != null) { + return extendedMetadata.getAssertionTimeToLiveSeconds(); + } else { + return 600; + } + } + + /** + * Extended metadata which contains details on configuration of the generated service provider metadata. + * + * @return extended metadata + */ + public ExtendedMetadata getExtendedMetadata() { + return extendedMetadata; + } + + /** + * Default value for generation of extended metadata. Value is cloned upon each request to generate new + * ExtendedMetadata object. + * + * @param extendedMetadata + * default extended metadata or null + */ + public void setExtendedMetadata(IdpExtendedMetadata extendedMetadata) { + this.extendedMetadata = extendedMetadata; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java new file mode 100644 index 00000000000..6caf6d40abf --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java @@ -0,0 +1,229 @@ +/* Copyright 2009 Vladimir Schäfer +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.util.SimpleURLCanonicalizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml.metadata.MetadataDisplayFilter; +import org.springframework.security.saml.metadata.MetadataMemoryProvider; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +/** + * The filter expects calls on configured URL and presents user with SAML2 metadata representing this application + * deployment. In case the application is configured to automatically generate metadata, the generation occurs upon + * first invocation of this filter (first request made to the server). + * + * This class is based on org.springframework.security.saml.metadata.MetadataGeneratorFilter. + */ +public class IdpMetadataGeneratorFilter extends GenericFilterBean { + + /** + * Class logger. + */ + protected final static Logger log = LoggerFactory.getLogger(IdpMetadataGeneratorFilter.class); + + /** + * Class storing all SAML metadata documents + */ + protected IdpMetadataManager manager; + + /** + * Class capable of generating new metadata. + */ + protected IdpMetadataGenerator generator; + + /** + * Metadata display filter. + */ + protected MetadataDisplayFilter displayFilter; + + /** + * Flag indicates that in case generated base url is used (when value is not provided in the MetadataGenerator) it + * should be normalized. Normalization includes lower-casing of scheme and server name and removing standar ports + * of 80 for http and 443 for https schemes. + */ + protected boolean normalizeBaseUrl; + + /** + * Default constructor. + * + * @param generator + * generator + */ + public IdpMetadataGeneratorFilter(IdpMetadataGenerator generator) { + this.generator = generator; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + processMetadataInitialization((HttpServletRequest) request); + chain.doFilter(request, response); + } + + /** + * Verifies whether generation is needed and if so the metadata document is created and stored in metadata + * manager. + * + * @param request + * request + * @throws javax.servlet.ServletException + * error + */ + protected void processMetadataInitialization(HttpServletRequest request) throws ServletException { + + // In case the hosted IdP metadata weren't initialized, let's do it now + if (manager.getHostedIdpName() == null) { + + synchronized (IdpMetadataManager.class) { + + if (manager.getHostedIdpName() == null) { + + try { + + log.info( + "No default metadata configured, generating with default values, please pre-configure metadata for production use"); + + // Defaults + String alias = generator.getEntityAlias(); + String baseURL = getDefaultBaseURL(request); + + // Use default baseURL if not set + if (generator.getEntityBaseURL() == null) { + log.warn( + "Generated default entity base URL {} based on values in the first server request. Please set property entityBaseURL on MetadataGenerator bean to fixate the value.", + baseURL); + generator.setEntityBaseURL(baseURL); + } else { + baseURL = generator.getEntityBaseURL(); + } + + // Use default entityID if not set + if (generator.getEntityId() == null) { + generator.setEntityId(getDefaultEntityID(baseURL, alias)); + } + + EntityDescriptor descriptor = generator.generateMetadata(); + ExtendedMetadata extendedMetadata = generator.generateExtendedMetadata(); + + log.info("Created default metadata for system with entityID: " + descriptor.getEntityID()); + MetadataMemoryProvider memoryProvider = new MetadataMemoryProvider(descriptor); + memoryProvider.initialize(); + MetadataProvider metadataProvider = new ExtendedMetadataDelegate(memoryProvider, + extendedMetadata); + + manager.addMetadataProvider(metadataProvider); + manager.setHostedIdpName(descriptor.getEntityID()); + manager.refreshMetadata(); + + } catch (MetadataProviderException e) { + log.error("Error generating system metadata", e); + throw new ServletException("Error generating system metadata", e); + } + + } + + } + + } + + } + + protected String getDefaultEntityID(String entityBaseUrl, String alias) { + + String displayFilterUrl = MetadataDisplayFilter.FILTER_URL; + if (displayFilter != null) { + displayFilterUrl = displayFilter.getFilterProcessesUrl(); + } + + StringBuilder sb = new StringBuilder(); + sb.append(entityBaseUrl); + sb.append(displayFilterUrl); + + if (StringUtils.hasLength(alias)) { + sb.append("/alias/"); + sb.append(alias); + } + + return sb.toString(); + + } + + protected String getDefaultBaseURL(HttpServletRequest request) { + StringBuilder sb = new StringBuilder(); + sb.append(request.getScheme()).append("://").append(request.getServerName()).append(":") + .append(request.getServerPort()); + sb.append(request.getContextPath()); + String url = sb.toString(); + if (isNormalizeBaseUrl()) { + return SimpleURLCanonicalizer.canonicalize(url); + } else { + return url; + } + } + + @Autowired(required = false) + public void setDisplayFilter(MetadataDisplayFilter displayFilter) { + this.displayFilter = displayFilter; + } + + @Autowired + public void setManager(IdpMetadataManager manager) { + this.manager = manager; + } + + public boolean isNormalizeBaseUrl() { + return normalizeBaseUrl; + } + + /** + * When true flag indicates that in case generated base url is used (when value is not provided in the + * MetadataGenerator) it should be normalized. Normalization includes lower-casing of scheme and server name and + * removing standar ports of 80 for http and 443 for https schemes. + * + * @param normalizeBaseUrl + * flag + */ + public void setNormalizeBaseUrl(boolean normalizeBaseUrl) { + this.normalizeBaseUrl = normalizeBaseUrl; + } + + /** + * Verifies that required entities were autowired or set. + */ + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + Assert.notNull(generator, "Metadata generator"); + Assert.notNull(manager, "MetadataManager must be set"); + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java new file mode 100644 index 00000000000..5da97faf9ec --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java @@ -0,0 +1,30 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.util.List; + +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.security.saml.metadata.MetadataManager; + +/** + * MetadataManager has a field that stores the entity id of the local SAML service provider. However, in order to + * support SAML identity provider funcationality we also need to store the entity id of the local SAML identity + * provider. That is what this class provides. + * + */ +public class IdpMetadataManager extends MetadataManager { + + private String hostedIdpName; + + public IdpMetadataManager(final List providers) throws MetadataProviderException { + super(providers); + } + + public String getHostedIdpName() { + return this.hostedIdpName; + } + + public void setHostedIdpName(final String hostedIdpName) { + this.hostedIdpName = hostedIdpName; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java new file mode 100644 index 00000000000..7177b806aa2 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java @@ -0,0 +1,83 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.util.Collection; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +/** + * This authentication object represents an SAML authentication requests that was authenticated using an openId login. + * In other words, when the local SAML identity provider receives an authentication request from an external SAML + * service provider, it authenticates the user using the UAA spring openId login page. UAA stores the result of that + * authentication in an instance of this object. As such this object consists of a holder that contains both a + * SamlAuthenticationToken, which provides the SAML context, and an OpenIdAuthenticationToken, which provides the + * authentication details of the authenticated user. + * + */ +public class IdpSamlAuthentication implements Authentication { + + /** + * Generated serialization id. + */ + private static final long serialVersionUID = -4895486519411522514L; + + private final IdpSamlCredentialsHolder credentials; + + public IdpSamlAuthentication(IdpSamlCredentialsHolder credentials) { + this.credentials = credentials; + } + + @Override + public String getName() { + return credentials.getLoginAuthenticationToken().getName(); + } + + @Override + public Collection getAuthorities() { + return credentials.getLoginAuthenticationToken().getAuthorities(); + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getDetails() { + return credentials.getLoginAuthenticationToken().getDetails(); + } + + @Override + public Object getPrincipal() { + return credentials.getLoginAuthenticationToken().getPrincipal(); + } + + @Override + public boolean isAuthenticated() { + return credentials.getLoginAuthenticationToken().isAuthenticated(); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + // Do nothing. + } + + public static class IdpSamlCredentialsHolder { + + private final Authentication samlAuthenticationToken; + private final Authentication loginAuthenticationToken; + + public IdpSamlCredentialsHolder(Authentication samlAuthenticationToken, Authentication loginAuthenticationToken) { + this.samlAuthenticationToken = samlAuthenticationToken; + this.loginAuthenticationToken = loginAuthenticationToken; + } + + public Authentication getSamlAuthenticationToken() { + return samlAuthenticationToken; + } + + public Authentication getLoginAuthenticationToken() { + return loginAuthenticationToken; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java new file mode 100644 index 00000000000..a9e5c795c75 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.cloudfoundry.identity.uaa.provider.saml.idp.IdpSamlAuthentication.IdpSamlCredentialsHolder; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.saml.SAMLAuthenticationToken; + +/** + * This authentication provider produces a composite authentication object that contains the SamlAuthenticationToken, + * which contains the SAML context, and the OpenIdAuthenticationToken, which contains information about the + * authenticated user. + */ +public class IdpSamlAuthenticationProvider implements AuthenticationProvider { + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication loginAuthenticationToken = securityContext.getAuthentication(); + + IdpSamlCredentialsHolder credentials = new IdpSamlCredentialsHolder(authentication, loginAuthenticationToken); + + return new IdpSamlAuthentication(credentials); + } + + @Override + public boolean supports(Class authentication) { + return SAMLAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java new file mode 100644 index 00000000000..af884b1c6e9 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java @@ -0,0 +1,125 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; + +import org.cloudfoundry.identity.uaa.provider.saml.idp.IdpSamlAuthentication.IdpSamlCredentialsHolder; +import org.opensaml.common.SAMLException; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.RoleDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.signature.SignatureException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml.SAMLAuthenticationToken; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.MetadataManager; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.util.Assert; + +/** + * Use this class in conjunction with + * org.springframework.security.saml.SAMLProcessingFilter to create a SAML + * Response after SAMLProcessingFilter successfully processes a SAML + * Authentication Request. + */ +public class IdpSamlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(IdpSamlAuthenticationSuccessHandler.class); + + private IdpWebSsoProfile idpWebSsoProfile; + private MetadataManager metadataManager; + + public IdpSamlAuthenticationSuccessHandler() { + super(); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + IdpSamlCredentialsHolder credentials = (IdpSamlCredentialsHolder) authentication.getCredentials(); + Authentication loginAuthentication = credentials.getLoginAuthenticationToken(); + SAMLAuthenticationToken token = (SAMLAuthenticationToken) credentials.getSamlAuthenticationToken(); + SAMLMessageContext context = token.getCredentials(); + + IdpExtendedMetadata extendedMetadata = null; + try { + extendedMetadata = (IdpExtendedMetadata) metadataManager.getExtendedMetadata(context.getLocalEntityId()); + } catch (MetadataProviderException e) { + throw new ServletException("Failed to obtain local SAML IdP extended metadata.", e); + } + + try { + populatePeerContext(context); + } catch (MetadataProviderException e) { + throw new ServletException("Failed to populate peer SAML SP context.", e); + } + + try { + IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); + options.setAssertionsSigned(extendedMetadata.isAssertionsSigned()); + options.setAssertionTimeToLiveSeconds(extendedMetadata.getAssertionTimeToLiveSeconds()); + idpWebSsoProfile.sendResponse(loginAuthentication, context, options); + } catch (SAMLException e) { + LOGGER.debug("Incoming SAML message is invalid.", e); + throw new AuthenticationServiceException("Incoming SAML message is invalid.", e); + } catch (MetadataProviderException e) { + LOGGER.debug("Error determining metadata contracts.", e); + throw new AuthenticationServiceException("Error determining metadata contracts.", e); + } catch (MessageEncodingException e) { + LOGGER.debug("Error decoding incoming SAML message.", e); + throw new AuthenticationServiceException("Error encoding outgoing SAML message.", e); + } catch (MarshallingException | SecurityException | SignatureException e) { + LOGGER.debug("Error signing SAML assertion.", e); + throw new AuthenticationServiceException("Error signing SAML assertion.", e); + } + } + + protected void populatePeerContext(SAMLMessageContext samlContext) throws MetadataProviderException { + + String peerEntityId = samlContext.getPeerEntityId(); + QName peerEntityRole = samlContext.getPeerEntityRole(); + + if (peerEntityId == null) { + throw new MetadataProviderException("Peer entity ID wasn't specified, but is requested"); + } + + EntityDescriptor entityDescriptor = metadataManager.getEntityDescriptor(peerEntityId); + RoleDescriptor roleDescriptor = metadataManager.getRole(peerEntityId, peerEntityRole, SAMLConstants.SAML20P_NS); + ExtendedMetadata extendedMetadata = metadataManager.getExtendedMetadata(peerEntityId); + + if (entityDescriptor == null || roleDescriptor == null) { + throw new MetadataProviderException( + "Metadata for entity " + peerEntityId + " and role " + peerEntityRole + " wasn't found"); + } + + samlContext.setPeerEntityMetadata(entityDescriptor); + samlContext.setPeerEntityRoleMetadata(roleDescriptor); + samlContext.setPeerExtendedMetadata(extendedMetadata); + } + + @Autowired + public void setIdpWebSsoProfile(IdpWebSsoProfile idpWebSsoProfile) { + Assert.notNull(idpWebSsoProfile, "SAML Web SSO profile can't be null."); + this.idpWebSsoProfile = idpWebSsoProfile; + } + + @Autowired + public void setMetadataManager(MetadataManager metadataManager) { + Assert.notNull(metadataManager, "SAML metadata manager can't be null."); + this.metadataManager = metadataManager; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java new file mode 100644 index 00000000000..be1a2a76c04 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java @@ -0,0 +1,26 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.security.saml.context.SAMLContextProviderImpl; +import org.springframework.security.saml.context.SAMLMessageContext; + +/** + * Use this class in conjuction with + * org.springframework.security.saml.SAMLProcessingFilter to ensure that when + * SAMLProcessingFilter processes a SAML Authentication Request and builds a + * SAMLMessageContext it identifies the peer entity as a SAML SP. + */ +public class IdpSamlContextProviderImpl extends SAMLContextProviderImpl { + + @Override + public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServletResponse response) + throws MetadataProviderException { + SAMLMessageContext context = super.getLocalEntity(request, response); + context.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); + return context; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java new file mode 100644 index 00000000000..c306987eaab --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.springframework.security.saml.websso.WebSSOProfileOptions; + +/** + * DTO for SAML IdP Web SSO Profile options. + */ +public class IdpWebSSOProfileOptions extends WebSSOProfileOptions { + + /** + * Generated serialization id. + */ + private static final long serialVersionUID = 531377853538365709L; + + private boolean assertionsSigned; + private int assertionTimeToLiveSeconds; + + public boolean isAssertionsSigned() { + return assertionsSigned; + } + + public void setAssertionsSigned(boolean assertionsSigned) { + this.assertionsSigned = assertionsSigned; + } + + public int getAssertionTimeToLiveSeconds() { + return assertionTimeToLiveSeconds; + } + + public void setAssertionTimeToLiveSeconds(int assertionTimeToLiveSeconds) { + this.assertionTimeToLiveSeconds = assertionTimeToLiveSeconds; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java new file mode 100644 index 00000000000..421f962fccf --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java @@ -0,0 +1,20 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.opensaml.common.SAMLException; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.signature.SignatureException; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml.context.SAMLMessageContext; + +/** + * Interface for sending a SAML Response to SAML Service Provider during Web Single Sign-On profile. + */ +public interface IdpWebSsoProfile { + + void sendResponse(Authentication authentication, SAMLMessageContext context, IdpWebSSOProfileOptions options) + throws SAMLException, MetadataProviderException, MessageEncodingException, SecurityException, + MarshallingException, SignatureException; +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java new file mode 100644 index 00000000000..87d10ab0950 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java @@ -0,0 +1,300 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.common.SAMLException; +import org.opensaml.common.SAMLObjectBuilder; +import org.opensaml.common.SAMLVersion; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeStatement; +import org.opensaml.saml2.core.AttributeValue; +import org.opensaml.saml2.core.Audience; +import org.opensaml.saml2.core.AudienceRestriction; +import org.opensaml.saml2.core.AuthnContext; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.AuthnStatement; +import org.opensaml.saml2.core.Conditions; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDType; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.Status; +import org.opensaml.saml2.core.StatusCode; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.Endpoint; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.XMLObjectBuilder; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.schema.XSString; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.SecurityHelper; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.signature.Signature; +import org.opensaml.xml.signature.SignatureException; +import org.opensaml.xml.signature.Signer; +import org.opensaml.xml.signature.impl.SignatureBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.saml.websso.WebSSOProfileImpl; + +public class IdpWebSsoProfileImpl extends WebSSOProfileImpl implements IdpWebSsoProfile { + + @Override + public void sendResponse(Authentication authentication, SAMLMessageContext context, IdpWebSSOProfileOptions options) + throws SAMLException, MetadataProviderException, MessageEncodingException, SecurityException, + MarshallingException, SignatureException { + + buildResponse(authentication, context, options); + + sendMessage(context, false); + } + + @SuppressWarnings("unchecked") + protected void buildResponse(Authentication authentication, SAMLMessageContext context, + IdpWebSSOProfileOptions options) + throws MetadataProviderException, SecurityException, MarshallingException, SignatureException { + IDPSSODescriptor idpDescriptor = (IDPSSODescriptor) context.getLocalEntityRoleMetadata(); + SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getPeerEntityRoleMetadata(); + AuthnRequest authnRequest = (AuthnRequest) context.getInboundSAMLMessage(); + + AssertionConsumerService assertionConsumerService = getAssertionConsumerService(options, idpDescriptor, + spDescriptor); + + context.setPeerEntityEndpoint(assertionConsumerService); + + Assertion assertion = buildAssertion(authentication, authnRequest, options, context.getPeerEntityId(), + context.getLocalEntityId()); + if (options.isAssertionsSigned() || spDescriptor.getWantAssertionsSigned()) { + signAssertion(assertion, context.getLocalSigningCredential()); + } + Response samlResponse = createResponse(context, assertionConsumerService, assertion); + context.setOutboundMessage(samlResponse); + context.setOutboundSAMLMessage(samlResponse); + } + + private Response createResponse(SAMLMessageContext context, AssertionConsumerService assertionConsumerService, + Assertion assertion) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder responseBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Response.DEFAULT_ELEMENT_NAME); + Response response = responseBuilder.buildObject(); + + buildCommonAttributes(context.getLocalEntityId(), response, assertionConsumerService); + + response.getAssertions().add(assertion); + + buildStatusSuccess(response); + return response; + } + + private void buildCommonAttributes(String localEntityId, Response response, Endpoint service) { + + response.setID(generateID()); + response.setIssuer(getIssuer(localEntityId)); + response.setVersion(SAMLVersion.VERSION_20); + response.setIssueInstant(new DateTime()); + + if (service != null) { + response.setDestination(service.getLocation()); + } + } + + private Assertion buildAssertion(Authentication authentication, AuthnRequest authnRequest, + IdpWebSSOProfileOptions options, String audienceURI, String issuerEntityId) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Assertion.DEFAULT_ELEMENT_NAME); + Assertion assertion = assertionBuilder.buildObject(); + assertion.setID(generateID()); + assertion.setIssueInstant(new DateTime()); + assertion.setVersion(SAMLVersion.VERSION_20); + assertion.setIssuer(getIssuer(issuerEntityId)); + + buildAssertionAuthnStatement(assertion); + buildAssertionConditions(assertion, options.getAssertionTimeToLiveSeconds(), audienceURI); + buildAssertionSubject(assertion, authnRequest, options.getAssertionTimeToLiveSeconds(), + authentication.getName()); + buildAttributeStatement(assertion, authentication); + + return assertion; + } + + private void buildAssertionAuthnStatement(Assertion assertion) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder authnStatementBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME); + AuthnStatement authnStatement = authnStatementBuilder.buildObject(); + authnStatement.setAuthnInstant(new DateTime()); + authnStatement.setSessionIndex(generateID()); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder authnContextBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME); + AuthnContext authnContext = authnContextBuilder.buildObject(); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder authnContextClassRefBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); + AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(); + authnContextClassRef.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX); + authnContext.setAuthnContextClassRef(authnContextClassRef); + authnStatement.setAuthnContext(authnContext); + assertion.getAuthnStatements().add(authnStatement); + } + + private void buildAssertionConditions(Assertion assertion, int assertionTtlSeconds, String audienceURI) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder conditionsBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Conditions.DEFAULT_ELEMENT_NAME); + Conditions conditions = conditionsBuilder.buildObject(); + conditions.setNotBefore(new DateTime()); + conditions.setNotOnOrAfter(new DateTime().plusSeconds(assertionTtlSeconds)); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder audienceRestrictionBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME); + AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject(); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder audienceBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Audience.DEFAULT_ELEMENT_NAME); + Audience audience = audienceBuilder.buildObject(); + audience.setAudienceURI(audienceURI); + audienceRestriction.getAudiences().add(audience); + conditions.getAudienceRestrictions().add(audienceRestriction); + assertion.setConditions(conditions); + } + + private void buildAssertionSubject(Assertion assertion, AuthnRequest authnRequest, int assertionTtlSeconds, + String nameIdStr) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder subjectBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Subject.DEFAULT_ELEMENT_NAME); + Subject subject = subjectBuilder.buildObject(); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder nameIdBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(NameID.DEFAULT_ELEMENT_NAME); + NameID nameId = nameIdBuilder.buildObject(); + nameId.setValue(nameIdStr); + nameId.setFormat(NameIDType.UNSPECIFIED); + subject.setNameID(nameId); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder subjectConfirmationBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME); + SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject(); + subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder subjectConfirmationDataBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); + SubjectConfirmationData subjectConfirmationData = subjectConfirmationDataBuilder.buildObject(); + + subjectConfirmationData.setNotOnOrAfter(new DateTime().plusSeconds(assertionTtlSeconds)); + subjectConfirmationData.setInResponseTo(authnRequest.getID()); + subjectConfirmationData.setRecipient(authnRequest.getAssertionConsumerServiceURL()); + subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); + subject.getSubjectConfirmations().add(subjectConfirmation); + assertion.setSubject(subject); + } + + private void buildAttributeStatement(Assertion assertion, Authentication authentication) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder attributeStatementBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME); + AttributeStatement attributeStatement = attributeStatementBuilder.buildObject(); + + List authorities = new ArrayList<>(); + for (GrantedAuthority authority : authentication.getAuthorities()) { + authorities.add(authority.getAuthority()); + } + Attribute authoritiesAttribute = buildStringAttribute("authorities", authorities); + attributeStatement.getAttributes().add(authoritiesAttribute); + + UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); + Attribute emailAttribute = buildStringAttribute("email", Arrays.asList(new String[] { principal.getEmail() })); + attributeStatement.getAttributes().add(emailAttribute); + Attribute idAttribute = buildStringAttribute("email", Arrays.asList(new String[] { principal.getId() })); + attributeStatement.getAttributes().add(idAttribute); + Attribute nameAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getName() })); + attributeStatement.getAttributes().add(nameAttribute); + Attribute originAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getOrigin() })); + attributeStatement.getAttributes().add(originAttribute); + Attribute zoneAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getZoneId() })); + attributeStatement.getAttributes().add(zoneAttribute); + + assertion.getAttributeStatements().add(attributeStatement); + } + + public Attribute buildStringAttribute(String name, List values) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder attributeBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Attribute.DEFAULT_ELEMENT_NAME); + Attribute attribute = (Attribute) attributeBuilder.buildObject(); + attribute.setName(name); + + @SuppressWarnings("unchecked") + XMLObjectBuilder xsStringBuilder = (XMLObjectBuilder) builderFactory + .getBuilder(XSString.TYPE_NAME); + for (String value : values) { + // Set custom Attributes + XSString attributeValue = xsStringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSString.TYPE_NAME); + attributeValue.setValue(value); + attribute.getAttributeValues().add(attributeValue); + } + + return attribute; + } + + private void buildStatusSuccess(Response response) { + buildStatus(response, StatusCode.SUCCESS_URI); + } + + private void buildStatus(Response response, String statusCodeStr) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder statusCodeBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME); + StatusCode statusCode = statusCodeBuilder.buildObject(); + statusCode.setValue(statusCodeStr); + + @SuppressWarnings("unchecked") + SAMLObjectBuilder statusBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Status.DEFAULT_ELEMENT_NAME); + Status status = statusBuilder.buildObject(); + status.setStatusCode(statusCode); + response.setStatus(status); + } + + private void signAssertion(Assertion assertion, Credential credential) + throws SecurityException, MarshallingException, SignatureException { + SignatureBuilder signatureBuilder = (SignatureBuilder) builderFactory + .getBuilder(Signature.DEFAULT_ELEMENT_NAME); + Signature signature = signatureBuilder.buildObject(); + signature.setSigningCredential(credential); + + SecurityHelper.prepareSignatureParams(signature, credential, null, null); + assertion.setSignature(signature); + + Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(assertion); + marshaller.marshall(assertion); + + Signer.signObject(signature); + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java new file mode 100644 index 00000000000..5e13d23e991 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * 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.provider.saml.idp; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Rest-template-based data access for SAML Service Provider CRUD operations. + */ +public class JdbcSamlServiceProviderProvisioning implements SamlServiceProviderProvisioning, SamlServiceProviderDeletable { + + private static final Log LOGGER = LogFactory.getLog(JdbcIdentityProviderProvisioning.class); + + public static final String SERVICE_PROVIDER_FIELDS = "id,version,created,lastmodified,name,entity_id,config,identity_zone_id,active"; + + public static final String CREATE_SERVICE_PROVIDER_SQL = "insert into service_provider(" + SERVICE_PROVIDER_FIELDS + + ") values (?,?,?,?,?,?,?,?,?)"; + + public static final String DELETE_SERVICE_PROVIDER_SQL = "delete from service_provider where id=? and identity_zone_id=?"; + + public static final String DELETE_SERVICE_PROVIDER_BY_ENTITY_ID_SQL = "delete from service_provider where entity_id = ? and identity_zone_id=?"; + + public static final String DELETE_SERVICE_PROVIDER_BY_ZONE_SQL = "delete from service_provider where identity_zone_id=?"; + + public static final String SERVICE_PROVIDERS_QUERY = "select " + SERVICE_PROVIDER_FIELDS + + " from service_provider where identity_zone_id=?"; + + public static final String ACTIVE_SERVICE_PROVIDERS_QUERY = SERVICE_PROVIDERS_QUERY + " and active"; + + public static final String SERVICE_PROVIDER_UPDATE_FIELDS = "version,lastmodified,name,config,active".replace(",", + "=?,") + "=?"; + + public static final String UPDATE_SERVICE_PROVIDER_SQL = "update service_provider set " + + SERVICE_PROVIDER_UPDATE_FIELDS + " where id=? and identity_zone_id=?"; + + public static final String SERVICE_PROVIDER_BY_ID_QUERY = "select " + SERVICE_PROVIDER_FIELDS + + " from service_provider " + "where id=? and identity_zone_id=?"; + + public static final String SERVICE_PROVIDER_BY_ENTITY_ID_QUERY = "select " + SERVICE_PROVIDER_FIELDS + + " from service_provider " + "where entity_id=? and identity_zone_id=? "; + + protected final JdbcTemplate jdbcTemplate; + + private final RowMapper mapper = new SamlServiceProviderRowMapper(); + + public JdbcSamlServiceProviderProvisioning(JdbcTemplate jdbcTemplate) { + Assert.notNull(jdbcTemplate); + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public SamlServiceProvider retrieve(String id) { + SamlServiceProvider serviceProvider = jdbcTemplate.queryForObject(SERVICE_PROVIDER_BY_ID_QUERY, mapper, id, + IdentityZoneHolder.get().getId()); + return serviceProvider; + } + + @Override + public void delete(String id) { + jdbcTemplate.update(DELETE_SERVICE_PROVIDER_SQL, id, IdentityZoneHolder.get().getId()); + } + + @Override + public int deleteByEntityId(String entityId, String zoneId) { + return jdbcTemplate.update(DELETE_SERVICE_PROVIDER_BY_ENTITY_ID_SQL, entityId, zoneId); + } + + @Override + public int deleteByIdentityZone(String zoneId) { + return jdbcTemplate.update(DELETE_SERVICE_PROVIDER_BY_ZONE_SQL, zoneId); + } + + @Override + public List retrieveActive(String zoneId) { + return jdbcTemplate.query(ACTIVE_SERVICE_PROVIDERS_QUERY, mapper, zoneId); + } + + @Override + public List retrieveAll(boolean activeOnly, String zoneId) { + if (activeOnly) { + return retrieveActive(zoneId); + } else { + return jdbcTemplate.query(SERVICE_PROVIDERS_QUERY, mapper, zoneId); + } + } + + @Override + public SamlServiceProvider retrieveByEntityId(String entityId, String zoneId) { + SamlServiceProvider serviceProvider = jdbcTemplate.queryForObject(SERVICE_PROVIDER_BY_ENTITY_ID_QUERY, mapper, + entityId, zoneId); + return serviceProvider; + } + + @Override + public SamlServiceProvider create(final SamlServiceProvider serviceProvider) { + validate(serviceProvider); + final String id = UUID.randomUUID().toString(); + try { + jdbcTemplate.update(CREATE_SERVICE_PROVIDER_SQL, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + int pos = 1; + ps.setString(pos++, id); + ps.setInt(pos++, serviceProvider.getVersion()); + ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); + ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); + ps.setString(pos++, serviceProvider.getName()); + ps.setString(pos++, serviceProvider.getEntityId()); + ps.setString(pos++, JsonUtils.writeValueAsString(serviceProvider.getConfig())); + ps.setString(pos++, serviceProvider.getIdentityZoneId()); + ps.setBoolean(pos++, serviceProvider.isActive()); + } + }); + } catch (DuplicateKeyException e) { + throw new SamlSpAlreadyExistsException(e.getMostSpecificCause().getMessage()); + } + return retrieve(id); + } + + @Override + public SamlServiceProvider update(final SamlServiceProvider serviceProvider) { + validate(serviceProvider); + final String zoneId = IdentityZoneHolder.get().getId(); + jdbcTemplate.update(UPDATE_SERVICE_PROVIDER_SQL, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + int pos = 1; + ps.setInt(pos++, serviceProvider.getVersion() + 1); + ps.setTimestamp(pos++, new Timestamp(new Date().getTime())); + ps.setString(pos++, serviceProvider.getName()); + ps.setString(pos++, JsonUtils.writeValueAsString(serviceProvider.getConfig())); + ps.setBoolean(pos++, serviceProvider.isActive()); + ps.setString(pos++, serviceProvider.getId().trim()); + ps.setString(pos++, zoneId); + } + }); + return retrieve(serviceProvider.getId()); + } + + protected void validate(SamlServiceProvider provider) { + if (provider == null) { + throw new NullPointerException("SAML Service Provider can not be null."); + } + if (!StringUtils.hasText(provider.getIdentityZoneId())) { + throw new DataIntegrityViolationException("Identity zone ID must be set."); + } + + SamlServiceProviderDefinition saml = ObjectUtils.castInstance(provider.getConfig(), + SamlServiceProviderDefinition.class); + saml.setSpEntityId(provider.getEntityId()); + saml.setZoneId(provider.getIdentityZoneId()); + provider.setConfig(saml); + } + + private static final class SamlServiceProviderRowMapper implements RowMapper { + public SamlServiceProviderRowMapper() { + // Default constructor. + } + + @Override + public SamlServiceProvider mapRow(ResultSet rs, int rowNum) throws SQLException { + SamlServiceProvider samlServiceProvider = new SamlServiceProvider(); + int pos = 1; + samlServiceProvider.setId(rs.getString(pos++).trim()); + samlServiceProvider.setVersion(rs.getInt(pos++)); + samlServiceProvider.setCreated(rs.getTimestamp(pos++)); + samlServiceProvider.setLastModified(rs.getTimestamp(pos++)); + samlServiceProvider.setName(rs.getString(pos++)); + samlServiceProvider.setEntityId(rs.getString(pos++)); + String config = rs.getString(pos++); + SamlServiceProviderDefinition definition = JsonUtils.readValue(config, SamlServiceProviderDefinition.class); + samlServiceProvider.setConfig(definition); + samlServiceProvider.setIdentityZoneId(rs.getString(pos++)); + samlServiceProvider.setActive(rs.getBoolean(pos++)); + return samlServiceProvider; + } + } + + @Override + public Log getLogger() { + + return LOGGER; + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java new file mode 100644 index 00000000000..9b4bd4e55d8 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java @@ -0,0 +1,307 @@ +/******************************************************************************* + * 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.provider.saml.idp; + +import java.io.IOException; +import java.util.Date; + +import javax.validation.constraints.NotNull; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(using = SamlServiceProvider.SamlServiceProviderSerializer.class) +@JsonDeserialize(using = SamlServiceProvider.SamlServiceProviderDeserializer.class) +public class SamlServiceProvider { + + public static final String FIELD_ID = "id"; + public static final String FIELD_ENTITY_ID = "entityId"; + public static final String FIELD_NAME = "name"; + public static final String FIELD_VERSION = "version"; + public static final String FIELD_CREATED = "created"; + public static final String FIELD_LAST_MODIFIED = "lastModified"; + public static final String FIELD_ACTIVE = "active"; + public static final String FIELD_IDENTITY_ZONE_ID = "identityZoneId"; + public static final String FIELD_CONFIG = "config"; + + // see deserializer at the bottom + private String id; + @NotNull + private String entityId; + @NotNull + private String name; + private SamlServiceProviderDefinition config; + private int version = 0; + private Date created = new Date(); + private Date lastModified = new Date(); + private boolean active = true; + private String identityZoneId; + + public Date getCreated() { + return created; + } + + public SamlServiceProvider setCreated(Date created) { + this.created = created; + return this; + } + + public Date getLastModified() { + return lastModified; + } + + public SamlServiceProvider setLastModified(Date lastModified) { + this.lastModified = lastModified; + return this; + } + + public SamlServiceProvider setVersion(int version) { + this.version = version; + return this; + } + + public int getVersion() { + return version; + } + + public String getName() { + return name; + } + + public SamlServiceProvider setName(String name) { + this.name = name; + return this; + } + + public String getId() { + return id; + } + + public SamlServiceProvider setId(String id) { + this.id = id; + return this; + } + + public SamlServiceProviderDefinition getConfig() { + return config; + } + + public SamlServiceProvider setConfig(SamlServiceProviderDefinition config) { + + if (StringUtils.hasText(getEntityId())) { + config.setSpEntityId(getEntityId()); + } + if (StringUtils.hasText(getIdentityZoneId())) { + config.setZoneId(getIdentityZoneId()); + } + this.config = config; + return this; + } + + public String getEntityId() { + return entityId; + } + + public SamlServiceProvider setEntityId(String entityId) { + this.entityId = entityId; + if (config != null) { + config.setSpEntityId(entityId); + } + return this; + } + + public boolean isActive() { + return active; + } + + public SamlServiceProvider setActive(boolean active) { + this.active = active; + return this; + } + + public String getIdentityZoneId() { + return identityZoneId; + } + + public SamlServiceProvider setIdentityZoneId(String identityZoneId) { + this.identityZoneId = identityZoneId; + if (config != null) { + config.setZoneId(identityZoneId); + } + return this; + } + + public boolean configIsValid() { + // There may be need for this method in the fugure but for now it does nothing. + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((config == null) ? 0 : config.hashCode()); + result = prime * result + ((created == null) ? 0 : created.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((lastModified == null) ? 0 : lastModified.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((entityId == null) ? 0 : entityId.hashCode()); + result = prime * result + version; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SamlServiceProvider other = (SamlServiceProvider) obj; + if (config == null) { + if (other.config != null) + return false; + } else if (!config.equals(other.config)) + return false; + if (created == null) { + if (other.created != null) + return false; + } else if (!created.equals(other.created)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (lastModified == null) { + if (other.lastModified != null) + return false; + } else if (!lastModified.equals(other.lastModified)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (entityId == null) { + if (other.entityId != null) + return false; + } else if (!entityId.equals(other.entityId)) + return false; + if (version != other.version) + return false; + return true; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("SamlServiceProvider{"); + sb.append("id='").append(id).append('\''); + sb.append(", entityId='").append(entityId).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append(", active=").append(active); + sb.append('}'); + return sb.toString(); + } + + public static class SamlServiceProviderSerializer extends JsonSerializer { + @Override + public void serialize(SamlServiceProvider value, JsonGenerator gen, SerializerProvider serializers) + throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); + gen.writeStringField(FIELD_ID, value.getId()); + gen.writeStringField(FIELD_ENTITY_ID, value.getEntityId()); + gen.writeStringField(FIELD_NAME, value.getName()); + gen.writeNumberField(FIELD_VERSION, value.getVersion()); + writeDateField(FIELD_CREATED, value.getCreated(), gen); + writeDateField(FIELD_LAST_MODIFIED, value.getLastModified(), gen); + gen.writeBooleanField(FIELD_ACTIVE, value.isActive()); + gen.writeStringField(FIELD_IDENTITY_ZONE_ID, value.getIdentityZoneId()); + gen.writeEndObject(); + } + + public void writeDateField(String fieldName, Date value, JsonGenerator gen) throws IOException { + if (value != null) { + gen.writeNumberField(fieldName, value.getTime()); + } else { + gen.writeNullField(fieldName); + } + } + } + + public static class SamlServiceProviderDeserializer extends JsonDeserializer { + @Override + public SamlServiceProvider deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + SamlServiceProvider result = new SamlServiceProvider(); + // determine the type of IdentityProvider + JsonNode node = JsonUtils.readTree(jp); + // deserialize based on type + String config = getNodeAsString(node, FIELD_CONFIG, null); + SamlServiceProviderDefinition definition = null; + if (StringUtils.hasText(config)) { + definition = JsonUtils.readValue(config, SamlServiceProviderDefinition.class); + } + result.setConfig(definition); + + result.setId(getNodeAsString(node, FIELD_ID, null)); + result.setEntityId(getNodeAsString(node, FIELD_ENTITY_ID, null)); + result.setName(getNodeAsString(node, FIELD_NAME, null)); + result.setVersion(getNodeAsInt(node, FIELD_VERSION, 0)); + result.setCreated(getNodeAsDate(node, FIELD_CREATED)); + result.setLastModified(getNodeAsDate(node, FIELD_LAST_MODIFIED)); + result.setActive(getNodeAsBoolean(node, FIELD_ACTIVE, true)); + result.setIdentityZoneId(getNodeAsString(node, FIELD_IDENTITY_ZONE_ID, null)); + return result; + } + + protected String getNodeAsString(JsonNode node, String fieldName, String defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asText(defaultValue); + } + + protected int getNodeAsInt(JsonNode node, String fieldName, int defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asInt(defaultValue); + } + + protected boolean getNodeAsBoolean(JsonNode node, String fieldName, boolean defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asBoolean(defaultValue); + } + + protected Date getNodeAsDate(JsonNode node, String fieldName) { + JsonNode typeNode = node.get(fieldName); + long date = typeNode == null ? -1 : typeNode.asLong(-1); + if (date == -1) { + return null; + } else { + return new Date(date); + } + } + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java new file mode 100644 index 00000000000..0680e4101f2 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java @@ -0,0 +1,79 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] 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.provider.saml.idp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.event.ServiceProviderModifiedEvent; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.context.ApplicationListener; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; + +/** + * Listens to SAML service provider modified events from the RESTful service controller and updates the internal state and data persistence as necessary. + */ +public class SamlServiceProviderChangedListener implements ApplicationListener { + + private static final Log logger = LogFactory.getLog(SamlServiceProviderChangedListener.class); + private ZoneAwareIdpMetadataManager metadataManager = null; + private final SamlServiceProviderConfigurator configurator; + private final IdentityZoneProvisioning zoneProvisioning; + + public SamlServiceProviderChangedListener(SamlServiceProviderConfigurator configurator, + IdentityZoneProvisioning zoneProvisioning) { + this.configurator = configurator; + this.zoneProvisioning = zoneProvisioning; + } + + @Override + public void onApplicationEvent(ServiceProviderModifiedEvent event) { + if (metadataManager == null) { + return; + } + SamlServiceProvider changedSamlServiceProvider = (SamlServiceProvider) event.getSource(); + IdentityZone zone = zoneProvisioning.retrieve(changedSamlServiceProvider.getIdentityZoneId()); + ZoneAwareIdpMetadataManager.ExtensionMetadataManager manager = metadataManager.getManager(zone); + SamlServiceProviderDefinition definition = ObjectUtils.castInstance(changedSamlServiceProvider.getConfig(), + SamlServiceProviderDefinition.class); + try { + if (changedSamlServiceProvider.isActive()) { + ExtendedMetadataDelegate[] delegates = configurator.addSamlServiceProviderDefinition(definition); + if (delegates[1] != null) { + manager.removeMetadataProvider(delegates[1]); + } + manager.addMetadataProvider(delegates[0]); + } else { + ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProviderDefinition(definition); + if (delegate != null) { + manager.removeMetadataProvider(delegate); + } + } + for (MetadataProvider provider : manager.getProviders()) { + provider.getMetadata(); + } + manager.refreshMetadata(); + metadataManager.getManager(zone).refreshMetadata(); + } catch (MetadataProviderException e) { + logger.error("Unable to add new SAML service provider:" + definition, e); + } + } + + public void setMetadataManager(ZoneAwareIdpMetadataManager metadataManager) { + this.metadataManager = metadataManager; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java new file mode 100644 index 00000000000..4fa67ed43c2 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java @@ -0,0 +1,298 @@ +/******************************************************************************* + * 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.provider.saml.idp; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.SimpleHttpConnectionManager; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.http.client.utils.URIBuilder; +import org.cloudfoundry.identity.uaa.provider.saml.ConfigMetadataProvider; +import org.cloudfoundry.identity.uaa.provider.saml.FixedHttpMetaDataProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.xml.parse.BasicParserPool; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.util.StringUtils; + +/** + * Holds internal state of available SAML Service Providers. + */ +public class SamlServiceProviderConfigurator { + private Map serviceProviders = new HashMap<>(); + private HttpClientParams clientParams; + private BasicParserPool parserPool; + + private Timer dummyTimer = new Timer() { + + @Override + public void cancel() { + super.cancel(); + } + + @Override + public int purge() { + return 0; + } + + @Override + public void schedule(TimerTask task, long delay) { + // Do nothing. + } + + @Override + public void schedule(TimerTask task, long delay, long period) { + // Do nothing. + } + + @Override + public void schedule(TimerTask task, Date firstTime, long period) { + // Do nothing. + } + + @Override + public void schedule(TimerTask task, Date time) { + // Do nothing. + } + + @Override + public void scheduleAtFixedRate(TimerTask task, long delay, long period) { + // Do nothing. + } + + @Override + public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { + // Do nothing. + } + }; + + public SamlServiceProviderConfigurator() { + dummyTimer.cancel(); + } + + public List getSamlServiceProviderDefinitions() { + return Collections.unmodifiableList(new ArrayList<>(serviceProviders.keySet())); + } + + public List getSamlServiceProviderDefinitionsForZone(IdentityZone zone) { + List result = new LinkedList<>(); + for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { + if (zone.getId().equals(def.getZoneId())) { + result.add(def); + } + } + return result; + } + + public List getSamlServiceProviderDefinitions(List allowedSps, + IdentityZone zone) { + List spsInTheZone = getSamlServiceProviderDefinitionsForZone(zone); + if (allowedSps != null) { + List result = new LinkedList<>(); + for (SamlServiceProviderDefinition def : spsInTheZone) { + if (allowedSps.contains(def.getSpEntityId())) { + result.add(def); + } + } + return result; + } + return spsInTheZone; + } + + protected String getUniqueAlias(SamlServiceProviderDefinition def) { + return def.getUniqueAlias(); + } + + /** + * adds or replaces a SAML service provider + * + * @param providerDefinition + * - the provider to be added + * @return an array consisting of {provider-added, provider-deleted} where provider-deleted may be null + * @throws MetadataProviderException + * if the system fails to fetch meta data for this provider + */ + public synchronized ExtendedMetadataDelegate[] addSamlServiceProviderDefinition( + SamlServiceProviderDefinition providerDefinition) throws MetadataProviderException { + ExtendedMetadataDelegate added, deleted = null; + if (providerDefinition == null) { + throw new NullPointerException(); + } + if (!StringUtils.hasText(providerDefinition.getSpEntityId())) { + throw new NullPointerException("You must set the SAML SP Entity."); + } + if (!StringUtils.hasText(providerDefinition.getZoneId())) { + throw new NullPointerException("You must set the SAML SP Identity Zone Id."); + } + for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { + if (getUniqueAlias(providerDefinition).equals(getUniqueAlias(def))) { + deleted = serviceProviders.remove(def); + break; + } + } + SamlServiceProviderDefinition clone = providerDefinition.clone(); + added = getExtendedMetadataDelegate(clone); + String entityIdToBeAdded = ((ConfigMetadataProvider) added.getDelegate()).getEntityID(); + boolean entityIDexists = false; + for (Map.Entry entry : serviceProviders.entrySet()) { + SamlServiceProviderDefinition definition = entry.getKey(); + if (clone.getZoneId().equals(definition.getZoneId())) { + ConfigMetadataProvider provider = (ConfigMetadataProvider) entry.getValue().getDelegate(); + if (entityIdToBeAdded.equals(provider.getEntityID())) { + entityIDexists = true; + break; + } + } + } + if (entityIDexists) { + throw new MetadataProviderException("Duplicate entity id:" + entityIdToBeAdded); + } + + serviceProviders.put(clone, added); + return new ExtendedMetadataDelegate[] { added, deleted }; + } + + public synchronized ExtendedMetadataDelegate removeSamlServiceProviderDefinition( + SamlServiceProviderDefinition providerDefinition) { + return serviceProviders.remove(providerDefinition); + } + + public List getSamlServiceProviders() { + return getSamlServiceProviders(null); + } + + public List getSamlServiceProviders(IdentityZone zone) { + List result = new LinkedList<>(); + for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { + if (zone == null || zone.getId().equals(def.getZoneId())) { + ExtendedMetadataDelegate metadata = serviceProviders.get(def); + if (metadata != null) { + result.add(metadata); + } + } + } + return result; + } + + public ExtendedMetadataDelegate getExtendedMetadataDelegateFromCache(SamlServiceProviderDefinition def) + throws MetadataProviderException { + return serviceProviders.get(def); + } + + public ExtendedMetadataDelegate getExtendedMetadataDelegate(SamlServiceProviderDefinition def) + throws MetadataProviderException { + ExtendedMetadataDelegate metadata; + switch (def.getType()) { + case DATA: { + metadata = configureXMLMetadata(def); + break; + } + case URL: { + metadata = configureURLMetadata(def); + break; + } + default: { + throw new MetadataProviderException( + "Invalid metadata type for alias[" + def.getSpEntityId() + "]:" + def.getMetaDataLocation()); + } + } + return metadata; + } + + protected ExtendedMetadataDelegate configureXMLMetadata(SamlServiceProviderDefinition def) { + ConfigMetadataProvider configMetadataProvider = new ConfigMetadataProvider(def.getZoneId(), def.getSpEntityId(), + def.getMetaDataLocation()); + configMetadataProvider.setParserPool(getParserPool()); + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setLocal(false); + extendedMetadata.setAlias(def.getSpEntityId()); + ExtendedMetadataDelegate delegate = new ExtendedMetadataDelegate(configMetadataProvider, extendedMetadata); + delegate.setMetadataTrustCheck(def.isMetadataTrustCheck()); + + return delegate; + } + + @SuppressWarnings("unchecked") + protected ExtendedMetadataDelegate configureURLMetadata(SamlServiceProviderDefinition def) + throws MetadataProviderException { + Class socketFactory = null; + try { + def = def.clone(); + socketFactory = (Class) Class.forName(def.getSocketFactoryClassName()); + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setAlias(def.getSpEntityId()); + SimpleHttpConnectionManager connectionManager = new SimpleHttpConnectionManager(true); + connectionManager.getParams().setDefaults(getClientParams()); + HttpClient client = new HttpClient(connectionManager); + FixedHttpMetaDataProvider fixedHttpMetaDataProvider = new FixedHttpMetaDataProvider(dummyTimer, client, + adjustURIForPort(def.getMetaDataLocation())); + fixedHttpMetaDataProvider.setSocketFactory(socketFactory.newInstance()); + byte[] metadata = fixedHttpMetaDataProvider.fetchMetadata(); + def.setMetaDataLocation(new String(metadata, StandardCharsets.UTF_8)); + return configureXMLMetadata(def); + } catch (URISyntaxException e) { + throw new MetadataProviderException("Invalid socket factory(invalid URI):" + def.getMetaDataLocation(), e); + } catch (ClassNotFoundException e) { + throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); + } catch (InstantiationException e) { + throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); + } catch (IllegalAccessException e) { + throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); + } + } + + protected String adjustURIForPort(String uri) throws URISyntaxException { + URI metadataURI = new URI(uri); + if (metadataURI.getPort() < 0) { + switch (metadataURI.getScheme()) { + case "https": + return new URIBuilder(uri).setPort(443).build().toString(); + case "http": + return new URIBuilder(uri).setPort(80).build().toString(); + default: + return uri; + } + } + return uri; + } + + public HttpClientParams getClientParams() { + return clientParams; + } + + public void setClientParams(HttpClientParams clientParams) { + this.clientParams = clientParams; + } + + public BasicParserPool getParserPool() { + return parserPool; + } + + public void setParserPool(BasicParserPool parserPool) { + this.parserPool = parserPool; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java new file mode 100644 index 00000000000..13fb302aced --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.provider.saml.idp; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class SamlServiceProviderDefinition { + + public static final String DEFAULT_HTTP_SOCKET_FACTORY = "org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory"; + public static final String DEFAULT_HTTPS_SOCKET_FACTORY = "org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory"; + + public enum MetadataLocation { + URL, + DATA, + UNKNOWN + } + + private String metaDataLocation; + private String spEntityId; + private String zoneId; + private String nameID; + private int singleSignOnServiceIndex; + private boolean metadataTrustCheck; + private String socketFactoryClassName; + + public SamlServiceProviderDefinition clone() { + return new SamlServiceProviderDefinition(metaDataLocation, + spEntityId, + nameID, + singleSignOnServiceIndex, + metadataTrustCheck, + zoneId); + } + + public SamlServiceProviderDefinition() {} + + public SamlServiceProviderDefinition(String metaDataLocation, + String spEntityAlias, + String nameID, + int singleSignOnServiceIndex, + boolean metadataTrustCheck, + String zoneId) { + this.metaDataLocation = metaDataLocation; + this.spEntityId = spEntityAlias; + this.nameID = nameID; + this.singleSignOnServiceIndex = singleSignOnServiceIndex; + this.metadataTrustCheck = metadataTrustCheck; + this.zoneId = zoneId; + } + + @JsonIgnore + public MetadataLocation getType() { + String trimmedLocation = metaDataLocation.trim(); + if (trimmedLocation.startsWith("0) { + return socketFactoryClassName; + } + if (getMetaDataLocation()==null || getMetaDataLocation().trim().length()==0) { + throw new IllegalStateException("Invalid meta data URL[" + getMetaDataLocation() + "] cannot determine socket factory."); + } + if (getMetaDataLocation().startsWith("https")) { + return DEFAULT_HTTPS_SOCKET_FACTORY; + } else { + return DEFAULT_HTTP_SOCKET_FACTORY; + } + } + + public void setSocketFactoryClassName(String socketFactoryClassName) { + this.socketFactoryClassName = socketFactoryClassName; + if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { + try { + Class.forName( + socketFactoryClassName, + true, + Thread.currentThread().getContextClassLoader() + ); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + } + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SamlServiceProviderDefinition that = (SamlServiceProviderDefinition) o; + + return Objects.equals(getUniqueAlias(), that.getUniqueAlias()); + } + + @Override + public int hashCode() { + String alias = getUniqueAlias(); + return alias==null ? 0 : alias.hashCode(); + } + + @JsonIgnore + protected String getUniqueAlias() { + return getSpEntityId()+"###"+getZoneId(); + } + + @Override + public String toString() { + return "SamlServiceProviderDefinition{" + + "spEntityAlias='" + spEntityId + '\'' + + ", metaDataLocation='" + metaDataLocation + '\'' + + ", nameID='" + nameID + '\'' + + ", singleSignOnServiceIndex=" + singleSignOnServiceIndex + + ", metadataTrustCheck=" + metadataTrustCheck + + ", socketFactoryClassName='" + socketFactoryClassName + '\'' + + ", zoneId='" + zoneId + '\'' + + '}'; + } + + public static class Builder { + + private String metaDataLocation; + private String spEntityId; + private String zoneId; + private String nameID; + private int singleSignOnServiceIndex; + private boolean metadataTrustCheck; + private String socketFactoryClassName; + + private Builder(){} + + public static Builder get() { + return new Builder(); + } + + public SamlServiceProviderDefinition build() { + SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); + + def.setMetaDataLocation(metaDataLocation); + def.setSpEntityId(spEntityId); + def.setZoneId(zoneId); + def.setNameID(nameID); + def.setSingleSignOnServiceIndex(singleSignOnServiceIndex); + def.setMetadataTrustCheck(metadataTrustCheck); + def.setSocketFactoryClassName(socketFactoryClassName); + return def; + } + + public Builder setMetaDataLocation(String metaDataLocation) { + this.metaDataLocation = metaDataLocation; + return this; + } + + public Builder setSpEntityId(String spEntityId) { + this.spEntityId = spEntityId; + return this; + } + + public Builder setZoneId(String zoneId) { + this.zoneId = zoneId; + return this; + } + + public Builder setNameID(String nameID) { + this.nameID = nameID; + return this; + } + + public Builder setSingleSignOnServiceIndex(int singleSignOnServiceIndex) { + this.singleSignOnServiceIndex = singleSignOnServiceIndex; + return this; + } + + public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { + this.metadataTrustCheck = metadataTrustCheck; + return this; + } + + public Builder setSocketFactoryClassName(String socketFactoryClassName) { + this.socketFactoryClassName = socketFactoryClassName; + return this; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java new file mode 100644 index 00000000000..8638f595548 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.apache.commons.logging.Log; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.springframework.context.ApplicationListener; + +/** + * Handles SAML service provider deleted events. + */ +public interface SamlServiceProviderDeletable extends ApplicationListener> { + default void onApplicationEvent(EntityDeletedEvent event) { + if (event==null || event.getDeleted()==null) { + return; + } else if (event.getDeleted() instanceof SamlServiceProvider) { + String entityId = ((SamlServiceProvider)event.getDeleted()).getEntityId(); + String zoneId = ((SamlServiceProvider)event.getDeleted()).getIdentityZoneId(); + deleteByEntityId(entityId, zoneId); + } else { + getLogger().debug("Unsupported deleted event for deletion of object:"+event.getDeleted()); + } + } + + default boolean isUaaZone(String zoneId) { + return IdentityZone.getUaa().getId().equals(zoneId); + } + + int deleteByEntityId(String entityId, String zoneId); + + int deleteByIdentityZone(String zoneId); + + Log getLogger(); +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java new file mode 100644 index 00000000000..afaaddd5e6c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java @@ -0,0 +1,20 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.util.List; + +public interface SamlServiceProviderProvisioning { + + SamlServiceProvider create(SamlServiceProvider identityProvider); + + void delete(String id); + + SamlServiceProvider update(SamlServiceProvider identityProvider); + + SamlServiceProvider retrieve(String id); + + List retrieveActive(String zoneId); + + List retrieveAll(boolean activeOnly, String zoneId); + + SamlServiceProvider retrieveByEntityId(String entityId, String zoneId); +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java new file mode 100644 index 00000000000..f8d3802d138 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java @@ -0,0 +1,15 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.cloudfoundry.identity.uaa.error.UaaException; + +public class SamlSpAlreadyExistsException extends UaaException { + + /** + * Serialization id + */ + private static final long serialVersionUID = -6544686748746941568L; + + public SamlSpAlreadyExistsException(String msg) { + super("sp_exists", msg, 409); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java new file mode 100644 index 00000000000..8a47ee410c5 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java @@ -0,0 +1,75 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.springframework.security.saml.util.SAMLUtil; + +public class ZoneAwareIdpMetadataGenerator extends IdpMetadataGenerator { + + @Override + public boolean isAssertionsSigned() { + if (!IdentityZoneHolder.isUaa()) { + return getZoneDefinition().getSamlConfig().isAssertionSigned(); + } + return super.isAssertionsSigned(); + } + + @Override + public int getAssertionTimeToLiveSeconds() { + if (!IdentityZoneHolder.isUaa()) { + return getZoneDefinition().getSamlConfig().getAssertionTimeToLiveSeconds(); + } + return super.getAssertionTimeToLiveSeconds(); + } + + @Override + public IdpExtendedMetadata generateExtendedMetadata() { + IdpExtendedMetadata metadata = super.generateExtendedMetadata(); + metadata.setAlias(UaaUrlUtils.getSubdomain() + metadata.getAlias()); + return metadata; + } + + @Override + public String getEntityId() { + String entityId = super.getEntityId(); + if (UaaUrlUtils.isUrl(entityId)) { + return UaaUrlUtils.addSubdomainToUrl(entityId); + } else { + return UaaUrlUtils.getSubdomain() + entityId; + } + } + + @Override + public String getEntityBaseURL() { + return UaaUrlUtils.addSubdomainToUrl(super.getEntityBaseURL()); + } + + @Override + protected String getEntityAlias() { + return UaaUrlUtils.getSubdomain() + super.getEntityAlias(); + } + + @Override + public boolean isWantAuthnRequestSigned() { + if (!IdentityZoneHolder.isUaa()) { + return getZoneDefinition().getSamlConfig().isWantAuthnRequestSigned(); + } + return super.isWantAuthnRequestSigned(); + } + + protected IdentityZoneConfiguration getZoneDefinition() { + IdentityZone zone = IdentityZoneHolder.get(); + IdentityZoneConfiguration definition = zone.getConfig(); + return definition != null ? definition : new IdentityZoneConfiguration(); + } + + @Override + public EntityDescriptor generateMetadata() { + EntityDescriptor result = super.generateMetadata(); + result.setID(SAMLUtil.getNCNameString(result.getEntityID())); + return result; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java new file mode 100644 index 00000000000..91a5e5ad61b --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java @@ -0,0 +1,738 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] 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.provider.saml.idp; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.provider.saml.ComparableProvider; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.RoleDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.security.x509.PKIXValidationInformationResolver; +import org.opensaml.xml.signature.SignatureTrustEngine; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.CachingMetadataManager; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml.metadata.ExtendedMetadataProvider; +import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer; + +public class ZoneAwareIdpMetadataManager extends IdpMetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean, BeanNameAware { + + private static final Log logger = LogFactory.getLog(ZoneAwareIdpMetadataManager.class); + private SamlServiceProviderProvisioning providerDao; + private IdentityZoneProvisioning zoneDao; + private SamlServiceProviderConfigurator configurator; + private KeyManager keyManager; + private Map metadataManagers; + private long refreshInterval = 30000l; + private long lastRefresh = 0; + private Timer timer; + private String beanName = ZoneAwareIdpMetadataManager.class.getName()+"-"+System.identityHashCode(this); + + public ZoneAwareIdpMetadataManager(SamlServiceProviderProvisioning providerDao, + IdentityZoneProvisioning zoneDao, + SamlServiceProviderConfigurator configurator, + KeyManager keyManager) throws MetadataProviderException { + super(Collections.emptyList()); + this.providerDao = providerDao; + this.zoneDao = zoneDao; + this.configurator = configurator; + this.keyManager = keyManager; + super.setKeyManager(keyManager); + //disable internal timer + super.setRefreshCheckInterval(0); + if (metadataManagers==null) { + metadataManagers = new ConcurrentHashMap<>(); + } + } + + private class RefreshTask extends TimerTask { + @Override + public void run() { + try { + refreshAllProviders(false); + }catch (Exception x) { + log.error("Unable to run SAML provider refresh task:", x); + } + } + } + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + @PostConstruct + public void checkAllProviders() throws MetadataProviderException { + for (Map.Entry entry : metadataManagers.entrySet()) { + entry.getValue().setKeyManager(keyManager); + } + refreshAllProviders(); + timer = new Timer("ZoneAwareMetadataManager.Refresh["+beanName+"]", true); + timer.schedule(new RefreshTask(),refreshInterval , refreshInterval); + } + + protected void refreshAllProviders() throws MetadataProviderException { + refreshAllProviders(true); + } + + protected String getThreadNameAndId() { + return Thread.currentThread().getName()+"-"+System.identityHashCode(Thread.currentThread()); + } + + protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProviderException { + logger.debug("Running SAML SP refresh[" + getThreadNameAndId() + "] - ignoreTimestamp=" + ignoreTimestamp); + for (IdentityZone zone : zoneDao.retrieveAll()) { + ExtensionMetadataManager manager = getManager(zone); + boolean hasChanges = false; + @SuppressWarnings({ "unchecked", "rawtypes" }) + List zoneDefinitions = new LinkedList( + configurator.getSamlServiceProviderDefinitionsForZone(zone)); + for (SamlServiceProvider provider : providerDao.retrieveAll(false, zone.getId())) { + zoneDefinitions.remove(provider.getConfig()); + if (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime()) { + try { + SamlServiceProviderDefinition definition = (SamlServiceProviderDefinition) provider.getConfig(); + try { + if (provider.isActive()) { + log.info("Adding SAML SP zone[" + zone.getId() + "] entityId[" + + definition.getSpEntityId() + "]"); + ExtendedMetadataDelegate[] delegates = configurator + .addSamlServiceProviderDefinition(definition); + if (delegates[1] != null) { + manager.removeMetadataProvider(delegates[1]); + } + manager.addMetadataProvider(delegates[0]); + } else { + removeSamlServiceProvider(zone, manager, definition); + } + hasChanges = true; + } catch (MetadataProviderException e) { + logger.error("Unable to refresh SAML Service Provider:" + definition, e); + } + } catch (JsonUtils.JsonUtilException x) { + logger.error("Unable to load SAML Service Provider:" + provider, x); + } + } + } + for (SamlServiceProviderDefinition definition : zoneDefinitions) { + removeSamlServiceProvider(zone, manager, definition); + hasChanges = true; + } + if (hasChanges) { + refreshZoneManager(manager); + } + } + lastRefresh = System.currentTimeMillis(); + } + + protected void removeSamlServiceProvider(IdentityZone zone, ExtensionMetadataManager manager, + SamlServiceProviderDefinition definition) { + log.info("Removing SAML SP zone[" + zone.getId() + "] entityId[" + definition.getSpEntityId() + "]"); + ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProviderDefinition(definition); + if (delegate != null) { + manager.removeMetadataProvider(delegate); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ExtensionMetadataManager getManager(IdentityZone zone) { + if (metadataManagers==null) { //called during super constructor + metadataManagers = new ConcurrentHashMap<>(); + } + ExtensionMetadataManager manager = metadataManagers.get(zone); + if (manager==null) { + try { + manager = new ExtensionMetadataManager(Collections.emptyList()); + } catch (MetadataProviderException e) { + throw new IllegalStateException(e); + } + manager.setKeyManager(keyManager); + ((ConcurrentHashMap)metadataManagers).putIfAbsent(zone, manager); + } + return metadataManagers.get(zone); + } + public ExtensionMetadataManager getManager() { + return getManager(IdentityZoneHolder.get()); + } + + @Override + public void setProviders(List newProviders) throws MetadataProviderException { + getManager().setProviders(newProviders); + } + + @Override + public void refreshMetadata() { + getManager().refreshMetadata(); + } + + @Override + public void addMetadataProvider(MetadataProvider newProvider) throws MetadataProviderException { + getManager().addMetadataProvider(newProvider); + } + + @Override + public void removeMetadataProvider(MetadataProvider provider) { + getManager().removeMetadataProvider(provider); + } + + @Override + public List getProviders() { + return getManager().getProviders(); + } + + @Override + public List getAvailableProviders() { + return getManager().getAvailableProviders(); + } + + @Override + protected void initializeProvider(ExtendedMetadataDelegate provider) throws MetadataProviderException { + getManager().initializeProvider(provider); + } + + @Override + protected void initializeProviderData(ExtendedMetadataDelegate provider) throws MetadataProviderException { + getManager().initializeProviderData(provider); + } + + @Override + protected void initializeProviderFilters(ExtendedMetadataDelegate provider) throws MetadataProviderException { + getManager().initializeProviderFilters(provider); + } + + @Override + protected SignatureTrustEngine getTrustEngine(MetadataProvider provider) { + return getManager().getTrustEngine(provider); + } + + @Override + protected PKIXValidationInformationResolver getPKIXResolver(MetadataProvider provider, Set trustedKeys, Set trustedNames) { + return getManager().getPKIXResolver(provider, trustedKeys, trustedNames); + } + + @Override + protected List parseProvider(MetadataProvider provider) throws MetadataProviderException { + return getManager().parseProvider(provider); + } + + @Override + public Set getIDPEntityNames() { + return getManager().getIDPEntityNames(); + } + + @Override + public Set getSPEntityNames() { + return getManager().getSPEntityNames(); + } + + @Override + public boolean isIDPValid(String idpID) { + return getManager().isIDPValid(idpID); + } + + @Override + public boolean isSPValid(String spID) { + return getManager().isSPValid(spID); + } + + @Override + public String getHostedIdpName() { + return getManager().getHostedIdpName(); + } + + @Override + public void setHostedIdpName(String hostedIdpName) { + getManager().setHostedIdpName(hostedIdpName); + } + + @Override + public String getHostedSPName() { + return getManager().getHostedSPName(); + } + + @Override + public void setHostedSPName(String hostedSPName) { + getManager().setHostedSPName(hostedSPName); + } + + @Override + public String getDefaultIDP() throws MetadataProviderException { + return getManager().getDefaultIDP(); + } + + @Override + public void setDefaultIDP(String defaultIDP) { + getManager().setDefaultIDP(defaultIDP); + } + + @Override + public EntityDescriptor getEntityDescriptor(byte[] hash) throws MetadataProviderException { + return getManager().getEntityDescriptor(hash); + } + + @Override + public String getEntityIdForAlias(String entityAlias) throws MetadataProviderException { + return getManager().getEntityIdForAlias(entityAlias); + } + + @Override + public ExtendedMetadata getDefaultExtendedMetadata() { + return getManager().getDefaultExtendedMetadata(); + } + + @Override + public void setDefaultExtendedMetadata(ExtendedMetadata defaultExtendedMetadata) { + getManager().setDefaultExtendedMetadata(defaultExtendedMetadata); + } + + @Override + public boolean isRefreshRequired() { + return getManager().isRefreshRequired(); + } + + @Override + public void setRefreshRequired(boolean refreshRequired) { + getManager().setRefreshRequired(refreshRequired); + } + + @Override + public void setRefreshCheckInterval(long refreshCheckInterval) { + this.refreshInterval = refreshCheckInterval; + } + + @Override + public void setKeyManager(KeyManager keyManager) { + getManager().setKeyManager(keyManager); + } + + @Override + public void setTLSConfigurer(TLSProtocolConfigurer configurer) { + getManager().setTLSConfigurer(configurer); + } + + @Override + protected void doAddMetadataProvider(MetadataProvider provider, List providerList) { + getManager().doAddMetadataProvider(provider, providerList); + } + + @Override + public void setRequireValidMetadata(boolean requireValidMetadata) { + getManager().setRequireValidMetadata(requireValidMetadata); + } + + @Override + public MetadataFilter getMetadataFilter() { + return getManager().getMetadataFilter(); + } + + @Override + public void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException { + getManager().setMetadataFilter(newFilter); + } + + @Override + public XMLObject getMetadata() throws MetadataProviderException { + return getManager().getMetadata(); + } + + @Override + public EntitiesDescriptor getEntitiesDescriptor(String name) throws MetadataProviderException { + return getManager().getEntitiesDescriptor(name); + } + + @Override + public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException { + return getManager().getEntityDescriptor(entityID); + } + + @Override + public List getRole(String entityID, QName roleName) throws MetadataProviderException { + return getManager().getRole(entityID, roleName); + } + + @Override + public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol) throws MetadataProviderException { + return getManager().getRole(entityID, roleName, supportedProtocol); + } + + @Override + public List getObservers() { + return getManager().getObservers(); + } + + @Override + protected void emitChangeEvent() { + getManager().emitChangeEvent(); + } + + @Override + public boolean requireValidMetadata() { + return getManager().requireValidMetadata(); + } + + @Override + public void destroy() { + if (timer != null) { + timer.cancel(); + timer.purge(); + timer = null; + } + for (Map.Entry manager : metadataManagers.entrySet()) { + manager.getValue().destroy(); + } + metadataManagers.clear(); + super.destroy(); + } + + @Override + public ExtendedMetadata getExtendedMetadata(String entityID) throws MetadataProviderException { + return super.getExtendedMetadata(entityID); + } + + protected Set refreshZoneManager(ExtensionMetadataManager manager) { + Set result = new HashSet<>(); + try { + + log.trace("Executing metadata refresh task"); + + // Invoking getMetadata performs a refresh in case it's needed + // Potentially expensive operation, but other threads can still load existing cached data + for (MetadataProvider provider : manager.getProviders()) { + provider.getMetadata(); + } + + // Refresh the metadataManager if needed + if (manager.isRefreshRequired()) { + manager.refreshMetadata(); + } + + + for (MetadataProvider provider : manager.getProviders()) { + if (provider instanceof ComparableProvider) { + result.add((ComparableProvider)provider); + } else if (provider instanceof ExtendedMetadataDelegate && + ((ExtendedMetadataDelegate)provider).getDelegate() instanceof ComparableProvider) { + result.add((ComparableProvider)((ExtendedMetadataDelegate)provider).getDelegate()); + } + } + + } catch (Throwable e) { + log.warn("Metadata refreshing has failed", e); + } + return result; + } + + //just so that we can override protected methods + public static class ExtensionMetadataManager extends CachingMetadataManager { + private String hostedIdpName; + + public ExtensionMetadataManager(List providers) throws MetadataProviderException { + super(providers); + //disable internal timers (they only get created when afterPropertiesSet) + setRefreshCheckInterval(0); + } + + @Override + public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException { + return super.getEntityDescriptor(entityID); + } + + @Override + public EntityDescriptor getEntityDescriptor(byte[] hash) throws MetadataProviderException { + return super.getEntityDescriptor(hash); + } + + @Override + public String getEntityIdForAlias(String entityAlias) throws MetadataProviderException { + return super.getEntityIdForAlias(entityAlias); + } + + @Override + public ExtendedMetadata getExtendedMetadata(String entityID) throws MetadataProviderException { + return super.getExtendedMetadata(entityID); + } + + @Override + public void refreshMetadata() { + super.refreshMetadata(); + } + + @Override + public void addMetadataProvider(MetadataProvider newProvider) throws MetadataProviderException { + ComparableProvider cp = null; + if (newProvider instanceof ExtendedMetadataDelegate && ((ExtendedMetadataDelegate)newProvider).getDelegate() instanceof ComparableProvider) { + cp = (ComparableProvider) ((ExtendedMetadataDelegate)newProvider).getDelegate(); + } else { + logger.warn("Adding Unknown SAML Provider type:"+(newProvider!=null?newProvider.getClass():null)+":"+newProvider); + } + + for (MetadataProvider provider : getAvailableProviders()) { + if (newProvider.equals(provider)) { + removeMetadataProvider(provider); + if (cp!=null) { + logger.debug("Found duplicate SAML provider, removing before readding zone["+cp.getZoneId()+"] alias["+cp.getAlias()+"]"); + } + } + } + super.addMetadataProvider(newProvider); + if (cp!=null) { + logger.debug("Added Metadata for SAML provider zone[" + cp.getZoneId() + "] alias[" + cp.getAlias() + "]"); + } + + } + + @Override + public void destroy() { + super.destroy(); + } + + @Override + public List getAvailableProviders() { + return super.getAvailableProviders(); + } + + @Override + public ExtendedMetadata getDefaultExtendedMetadata() { + return super.getDefaultExtendedMetadata(); + } + + @Override + public String getDefaultIDP() throws MetadataProviderException { + return super.getDefaultIDP(); + } + + public String getHostedIdpName() { + return hostedIdpName; + } + + @Override + public String getHostedSPName() { + return super.getHostedSPName(); + } + + @Override + public Set getIDPEntityNames() { + return super.getIDPEntityNames(); + } + + @Override + public PKIXValidationInformationResolver getPKIXResolver(MetadataProvider provider, Set trustedKeys, Set trustedNames) { + return super.getPKIXResolver(provider, trustedKeys, trustedNames); + } + + @Override + public List getProviders() { + return super.getProviders(); + } + + @Override + public Set getSPEntityNames() { + return super.getSPEntityNames(); + } + + @Override + public SignatureTrustEngine getTrustEngine(MetadataProvider provider) { + return super.getTrustEngine(provider); + } + + @Override + public void initializeProvider(ExtendedMetadataDelegate provider) throws MetadataProviderException { + super.initializeProvider(provider); + } + + @Override + public void initializeProviderData(ExtendedMetadataDelegate provider) throws MetadataProviderException { + super.initializeProviderData(provider); + } + + @Override + public void initializeProviderFilters(ExtendedMetadataDelegate provider) throws MetadataProviderException { + super.initializeProviderFilters(provider); + } + + @Override + public boolean isIDPValid(String idpID) { + return super.isIDPValid(idpID); + } + + @Override + public boolean isRefreshRequired() { + return super.isRefreshRequired(); + } + + @Override + public boolean isSPValid(String spID) { + return super.isSPValid(spID); + } + + @Override + public List parseProvider(MetadataProvider provider) throws MetadataProviderException { + return super.parseProvider(provider); + } + + @Override + public void removeMetadataProvider(MetadataProvider provider) { + + ComparableProvider cp = null; + if (provider instanceof ExtendedMetadataDelegate && ((ExtendedMetadataDelegate)provider).getDelegate() instanceof ComparableProvider) { + cp = (ComparableProvider) ((ExtendedMetadataDelegate)provider).getDelegate(); + } else { + logger.warn("Removing Unknown SAML Provider type:"+(provider!=null?provider.getClass():null)+":"+provider); + } + super.removeMetadataProvider(provider); + if (cp!=null) { + logger.debug("Removed Metadata for SAML provider zone[" + cp.getZoneId() + "] alias[" + cp.getAlias() + "]"); + } + } + + @Override + public void setDefaultExtendedMetadata(ExtendedMetadata defaultExtendedMetadata) { + super.setDefaultExtendedMetadata(defaultExtendedMetadata); + } + + @Override + public void setDefaultIDP(String defaultIDP) { + super.setDefaultIDP(defaultIDP); + } + + public void setHostedIdpName(String hostedIdpName) { + this.hostedIdpName = hostedIdpName; + } + + @Override + public void setHostedSPName(String hostedSPName) { + super.setHostedSPName(hostedSPName); + } + + @Override + public void setKeyManager(KeyManager keyManager) { + super.setKeyManager(keyManager); + } + + @Override + public void setProviders(List newProviders) throws MetadataProviderException { + super.setProviders(newProviders); + } + + @Override + public void setRefreshCheckInterval(long refreshCheckInterval) { + super.setRefreshCheckInterval(refreshCheckInterval); + } + + @Override + public void setRefreshRequired(boolean refreshRequired) { + super.setRefreshRequired(refreshRequired); + } + + @Override + public void setTLSConfigurer(TLSProtocolConfigurer configurer) { + super.setTLSConfigurer(configurer); + } + + @Override + public void doAddMetadataProvider(MetadataProvider provider, List providerList) { + super.doAddMetadataProvider(provider, providerList); + } + + @Override + public void emitChangeEvent() { + super.emitChangeEvent(); + } + + @Override + public EntitiesDescriptor getEntitiesDescriptor(String name) throws MetadataProviderException { + return super.getEntitiesDescriptor(name); + } + + @Override + public XMLObject getMetadata() throws MetadataProviderException { + return super.getMetadata(); + } + + @Override + public MetadataFilter getMetadataFilter() { + return super.getMetadataFilter(); + } + + @Override + public List getObservers() { + return super.getObservers(); + } + + @Override + public List getRole(String entityID, QName roleName) throws MetadataProviderException { + return super.getRole(entityID, roleName); + } + + @Override + public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol) throws MetadataProviderException { + return super.getRole(entityID, roleName, supportedProtocol); + } + + @Override + public void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException { + super.setMetadataFilter(newFilter); + } + + @Override + public void setRequireValidMetadata(boolean requireValidMetadata) { + super.setRequireValidMetadata(requireValidMetadata); + } + + @Override + public boolean requireValidMetadata() { + return super.requireValidMetadata(); + } + } + + public static class MetadataProviderObserver implements ObservableMetadataProvider.Observer { + private ExtensionMetadataManager manager; + + public MetadataProviderObserver(ExtensionMetadataManager manager) { + this.manager = manager; + } + + public void onEvent(MetadataProvider provider) { + manager.setRefreshRequired(true); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderEventPublisher.java new file mode 100644 index 00000000000..e3e52c5b07c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderEventPublisher.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.zone.event; + +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; + +public class ServiceProviderEventPublisher implements ApplicationEventPublisherAware { + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } + + public void spCreated(SamlServiceProvider serviceProvider) { + publish(ServiceProviderModifiedEvent.serviceProviderCreated(serviceProvider)); + } + + public void spModified(SamlServiceProvider serviceProvider) { + publish(ServiceProviderModifiedEvent.serviceProviderModified(serviceProvider)); + } + + public void publish(ApplicationEvent event) { + if (publisher!=null) { + publisher.publishEvent(event); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderModifiedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderModifiedEvent.java new file mode 100644 index 00000000000..296ce9e4752 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/ServiceProviderModifiedEvent.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.zone.event; + + +import org.cloudfoundry.identity.uaa.audit.AuditEvent; +import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.springframework.security.core.Authentication; + +public class ServiceProviderModifiedEvent extends AbstractUaaEvent { + + /** + * Generated serialization id. + */ + private static final long serialVersionUID = -204120790766086570L; + + private AuditEventType eventType; + + public ServiceProviderModifiedEvent(SamlServiceProvider serviceProvider, Authentication authentication, AuditEventType type) { + super(serviceProvider, authentication); + eventType = type; + } + + @Override + public AuditEvent getAuditEvent() { + return createAuditRecord(getSource().toString(), eventType, getOrigin(getAuthentication()), JsonUtils.writeValueAsString(source)); + } + + public static ServiceProviderModifiedEvent serviceProviderCreated(SamlServiceProvider serviceProvider) { + return new ServiceProviderModifiedEvent(serviceProvider, getContextAuthentication(), AuditEventType.ServiceProviderCreatedEvent); + } + + public static ServiceProviderModifiedEvent serviceProviderModified(SamlServiceProvider serviceProvider) { + return new ServiceProviderModifiedEvent(serviceProvider, getContextAuthentication(), AuditEventType.ServiceProviderModifiedEvent); + } + +} diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index 8be2b46c5a5..f656809172e 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -268,7 +268,9 @@ + + + + + + + + + @@ -303,7 +312,6 @@ - diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..58e15fdd8b0 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE service_provider ( + id CHAR(36) NOT NULL PRIMARY KEY, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + version BIGINT DEFAULT 0 NOT NULL, + identity_zone_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + entity_id varchar(36) NOT NULL, + config LONGVARCHAR, + active BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..5ca5ce697ab --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE `service_provider` ( + `id` VARCHAR(36) NOT NULL, + `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + `version` BIGINT DEFAULT 0 NOT NULL, + `identity_zone_id` VARCHAR(36) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `entity_id` VARCHAR(36) NOT NULL, + `config` LONGTEXT, + `active` BOOLEAN DEFAULT TRUE NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `entity_in_zone` (`identity_zone_id`, `entity_id`) +); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..9e225eeeff0 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE service_provider ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + version BIGINT DEFAULT 0, + identity_zone_id VARCHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + entity_id VARCHAR(36) NOT NULL, + config TEXT, + active BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java index 092a1cfad05..fef6e1dabbb 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java @@ -14,6 +14,6 @@ public void testAuditEventType() { assertEquals(type, AuditEventType.fromCode(count)); count++; } - assertEquals(33,count); + assertEquals(35,count); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java new file mode 100644 index 00000000000..22acbf2028c --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java @@ -0,0 +1,171 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.opensaml.common.SAMLException; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.ConfigurationException; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.signature.SignatureException; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.saml.metadata.MetadataManager; + +public class IdpSamlAuthenticationSuccessHandlerTest { + + private final SamlTestUtils samlTestUtils = new SamlTestUtils(); + + @Before + public void setup() throws ConfigurationException { + samlTestUtils.initalize(); + } + + @Test + public void testOnAuthenticationSuccess() throws IOException, ServletException, MetadataProviderException, + MessageEncodingException, SAMLException, SecurityException, MarshallingException, SignatureException { + IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); + + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + + IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); + idpExtendedMetaData.setAssertionsSigned(true); + + MetadataManager metadataManager = mock(MetadataManager.class); + when(metadataManager.getExtendedMetadata(context.getLocalEntityId())).thenReturn(idpExtendedMetaData); + when(metadataManager.getEntityDescriptor(context.getPeerEntityId())) + .thenReturn(context.getPeerEntityMetadata()); + when(metadataManager.getRole(context.getPeerEntityId(), context.getPeerEntityRole(), SAMLConstants.SAML20P_NS)) + .thenReturn(context.getPeerEntityRoleMetadata()); + successHandler.setMetadataManager(metadataManager); + + IdpWebSsoProfile profile = mock(IdpWebSsoProfile.class); + doNothing().when(profile).sendResponse(any(), any(), any()); + successHandler.setIdpWebSsoProfile(profile); + + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + successHandler.onAuthenticationSuccess(request, response, authentication); + } + + @Test(expected = ServletException.class) + public void testOnAuthenticationSuccessFailureIfIdpExtendedMetadataMissing() + throws IOException, ServletException, MetadataProviderException { + IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); + + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + + MetadataManager metadataManager = mock(MetadataManager.class); + when(metadataManager.getExtendedMetadata(context.getLocalEntityId())) + .thenThrow(new MetadataProviderException()); + successHandler.setMetadataManager(metadataManager); + + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + successHandler.onAuthenticationSuccess(request, response, authentication); + } + + @Test(expected = ServletException.class) + public void testOnAuthenticationSuccessFailureIfIdpPeerEntityIdNull() + throws IOException, ServletException, MetadataProviderException, MessageEncodingException, SAMLException, + SecurityException, MarshallingException, SignatureException { + IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); + + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + + IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); + idpExtendedMetaData.setAssertionsSigned(true); + + MetadataManager metadataManager = mock(MetadataManager.class); + when(metadataManager.getExtendedMetadata(context.getLocalEntityId())).thenReturn(idpExtendedMetaData); + when(metadataManager.getEntityDescriptor(context.getPeerEntityId())) + .thenReturn(context.getPeerEntityMetadata()); + when(metadataManager.getRole(context.getPeerEntityId(), context.getPeerEntityRole(), SAMLConstants.SAML20P_NS)) + .thenReturn(context.getPeerEntityRoleMetadata()); + successHandler.setMetadataManager(metadataManager); + + IdpWebSsoProfile profile = mock(IdpWebSsoProfile.class); + doNothing().when(profile).sendResponse(any(), any(), any()); + successHandler.setIdpWebSsoProfile(profile); + + context.setPeerEntityId(null); + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + successHandler.onAuthenticationSuccess(request, response, authentication); + } + + @Test(expected = ServletException.class) + public void testOnAuthenticationSuccessFailureIfIdpPeerEntityMetadataNull() + throws IOException, ServletException, MetadataProviderException, MessageEncodingException, SAMLException, + SecurityException, MarshallingException, SignatureException { + IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); + + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + + IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); + idpExtendedMetaData.setAssertionsSigned(true); + + MetadataManager metadataManager = mock(MetadataManager.class); + when(metadataManager.getExtendedMetadata(context.getLocalEntityId())).thenReturn(idpExtendedMetaData); + when(metadataManager.getEntityDescriptor(context.getPeerEntityId())).thenReturn(null); + when(metadataManager.getRole(context.getPeerEntityId(), context.getPeerEntityRole(), SAMLConstants.SAML20P_NS)) + .thenReturn(context.getPeerEntityRoleMetadata()); + successHandler.setMetadataManager(metadataManager); + + IdpWebSsoProfile profile = mock(IdpWebSsoProfile.class); + doNothing().when(profile).sendResponse(any(), any(), any()); + successHandler.setIdpWebSsoProfile(profile); + + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + successHandler.onAuthenticationSuccess(request, response, authentication); + } + + @Test(expected = ServletException.class) + public void testOnAuthenticationSuccessFailureIfIdpPeerRoleDescriptorNull() + throws IOException, ServletException, MetadataProviderException, MessageEncodingException, SAMLException, + SecurityException, MarshallingException, SignatureException { + IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); + + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + + IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); + idpExtendedMetaData.setAssertionsSigned(true); + + MetadataManager metadataManager = mock(MetadataManager.class); + when(metadataManager.getExtendedMetadata(context.getLocalEntityId())).thenReturn(idpExtendedMetaData); + when(metadataManager.getEntityDescriptor(context.getPeerEntityId())) + .thenReturn(context.getPeerEntityMetadata()); + when(metadataManager.getRole(context.getPeerEntityId(), context.getPeerEntityRole(), SAMLConstants.SAML20P_NS)) + .thenReturn(null); + successHandler.setMetadataManager(metadataManager); + + IdpWebSsoProfile profile = mock(IdpWebSsoProfile.class); + doNothing().when(profile).sendResponse(any(), any(), any()); + successHandler.setIdpWebSsoProfile(profile); + + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + successHandler.onAuthenticationSuccess(request, response, authentication); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java new file mode 100644 index 00000000000..29edbb418c0 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java @@ -0,0 +1,81 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; +import org.opensaml.common.SAMLException; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.ConfigurationException; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.signature.SignatureException; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml.context.SAMLMessageContext; + +public class IdpWebSsoProfileImplTest { + + private final SamlTestUtils samlTestUtils = new SamlTestUtils(); + + @Before + public void setup() throws ConfigurationException { + samlTestUtils.initalize(); + } + + @Test + public void testBuildResponse() throws MessageEncodingException, SAMLException, MetadataProviderException, + SecurityException, MarshallingException, SignatureException { + IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); + + Authentication authentication = samlTestUtils.mockAuthentication(); + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + + IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); + options.setAssertionsSigned(false); + profile.buildResponse(authentication, context, options); + + AuthnRequest request = (AuthnRequest) context.getInboundSAMLMessage(); + Response response = (Response) context.getOutboundSAMLMessage(); + Assertion assertion = response.getAssertions().get(0); + Subject subject = assertion.getSubject(); + assertEquals("marissa", subject.getNameID().getValue()); + + SubjectConfirmation subjectConfirmation = subject.getSubjectConfirmations().get(0); + SubjectConfirmationData subjectConfirmationData = subjectConfirmation.getSubjectConfirmationData(); + assertEquals(request.getID(), subjectConfirmationData.getInResponseTo()); + } + + @Test + public void testBuildResponseWithSignedAssertion() throws MessageEncodingException, SAMLException, + MetadataProviderException, SecurityException, MarshallingException, SignatureException { + IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); + + Authentication authentication = samlTestUtils.mockAuthentication(); + SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); + + IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); + options.setAssertionsSigned(true); + profile.buildResponse(authentication, context, options); + + AuthnRequest request = (AuthnRequest) context.getInboundSAMLMessage(); + Response response = (Response) context.getOutboundSAMLMessage(); + Assertion assertion = response.getAssertions().get(0); + Subject subject = assertion.getSubject(); + assertEquals("marissa", subject.getNameID().getValue()); + + SubjectConfirmation subjectConfirmation = subject.getSubjectConfirmations().get(0); + SubjectConfirmationData subjectConfirmationData = subjectConfirmation.getSubjectConfirmationData(); + assertEquals(request.getID(), subjectConfirmationData.getInResponseTo()); + + assertNotNull(assertion.getSignature()); + } + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java new file mode 100644 index 00000000000..526d0f1adc2 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java @@ -0,0 +1,222 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.test.JdbcTestBase; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; + +public class JdbcSamlServiceProviderProvisioningTest extends JdbcTestBase { + + private JdbcSamlServiceProviderProvisioning db; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + private Authentication authentication = mock(Authentication.class); + + + @Before + public void createDatasource() throws Exception { + db = new JdbcSamlServiceProviderProvisioning(jdbcTemplate); + } + + @After + public void cleanUp() { + IdentityZoneHolder.clear(); + } + + @Test + public void testCreateAndUpdateSamlServiceProviderInDefaultZone() throws Exception { + IdentityZoneHolder.set(IdentityZone.getUaa()); + String zoneId = IdentityZone.getUaa().getId(); + + SamlServiceProvider sp = createSamlServiceProvider(zoneId); + + SamlServiceProvider createdSp = db.create(sp); + Map rawCreatedSp = jdbcTemplate.queryForMap("select * from service_provider where id = ?", + createdSp.getId()); + + assertEquals(sp.getName(), createdSp.getName()); + assertEquals(sp.getConfig(), createdSp.getConfig()); + + assertEquals(sp.getName(), rawCreatedSp.get("name")); + assertEquals(sp.getConfig(), + JsonUtils.readValue((String) rawCreatedSp.get("config"), SamlServiceProviderDefinition.class)); + assertEquals(zoneId, rawCreatedSp.get("identity_zone_id").toString().trim()); + + sp.setId(createdSp.getId()); + sp.setLastModified(new Timestamp(System.currentTimeMillis())); + sp.setName("updated name"); + sp.setCreated(createdSp.getCreated()); + SamlServiceProviderDefinition updatedConfig = new SamlServiceProviderDefinition(); + updatedConfig.setMetaDataLocation(SamlTestUtils.UNSIGNED_SAML_SP_METADATA); + sp.setConfig(updatedConfig); + sp.setIdentityZoneId(zoneId); + createdSp = db.update(sp); + + assertEquals(sp.getName(), createdSp.getName()); + assertEquals(sp.getConfig(), createdSp.getConfig()); + assertEquals(sp.getLastModified().getTime() / 1000, createdSp.getLastModified().getTime() / 1000); + assertEquals(Integer.valueOf(rawCreatedSp.get("version").toString()) + 1, createdSp.getVersion()); + assertEquals(zoneId, createdSp.getIdentityZoneId()); + } + + private SamlServiceProvider createSamlServiceProvider(String zoneId) { + SamlServiceProvider sp = new SamlServiceProvider(); + sp.setActive(true); + SamlServiceProviderDefinition config = new SamlServiceProviderDefinition(); + config.setMetaDataLocation(SamlTestUtils.SAML_SP_METADATA); + sp.setConfig(config); + sp.setEntityId(SamlTestUtils.SP_ENTITY_ID); + sp.setIdentityZoneId(zoneId); + sp.setLastModified(new Date()); + sp.setName("Unit Test SAML SP"); + return sp; + } + + @Test + public void testCreateSamlServiceProviderInOtherZone() throws Exception { + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + + SamlServiceProvider sp = createSamlServiceProvider(zone.getId()); + + SamlServiceProvider createdSp = db.create(sp); + Map rawCreatedSp = jdbcTemplate.queryForMap("select * from service_provider where id = ?", + createdSp.getId()); + + assertEquals(sp.getName(), createdSp.getName()); + assertEquals(sp.getConfig(), createdSp.getConfig()); + + assertEquals(sp.getName(), rawCreatedSp.get("name")); + assertEquals(sp.getConfig(), + JsonUtils.readValue((String) rawCreatedSp.get("config"), SamlServiceProviderDefinition.class)); + assertEquals(zone.getId(), rawCreatedSp.get("identity_zone_id")); + } + + @Test(expected = EmptyResultDataAccessException.class) + public void testGetSamlServiceProviderForWrongZone() throws Exception { + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + + SamlServiceProvider sp = createSamlServiceProvider(zone.getId()); + db.create(sp); + + // The current zone is not where we are creating the zone. + IdentityZoneHolder.set(IdentityZone.getUaa()); + db.retrieve(sp.getId()); + } + + @Test(expected = EmptyResultDataAccessException.class) + public void testUpdateSamlServiceProviderInWrongZone() throws Exception { + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + + SamlServiceProvider sp = createSamlServiceProvider(zone.getId()); + + SamlServiceProvider createdSp = db.create(sp); + Map rawCreatedSp = jdbcTemplate.queryForMap("select * from service_provider where id = ?", + createdSp.getId()); + + assertEquals(sp.getName(), createdSp.getName()); + assertEquals(sp.getConfig(), createdSp.getConfig()); + + assertEquals(sp.getName(), rawCreatedSp.get("name")); + assertEquals(sp.getConfig(), + JsonUtils.readValue((String) rawCreatedSp.get("config"), SamlServiceProviderDefinition.class)); + assertEquals(zone.getId(), rawCreatedSp.get("identity_zone_id").toString().trim()); + + sp.setId(createdSp.getId()); + sp.setLastModified(new Timestamp(System.currentTimeMillis())); + sp.setName("updated name"); + sp.setCreated(createdSp.getCreated()); + SamlServiceProviderDefinition updatedConfig = new SamlServiceProviderDefinition(); + updatedConfig.setMetaDataLocation(SamlTestUtils.UNSIGNED_SAML_SP_METADATA); + sp.setConfig(updatedConfig); + sp.setIdentityZoneId(zone.getId()); + // Switch to a different zone before updating. + IdentityZoneHolder.set(IdentityZone.getUaa()); + db.update(sp); + } + + @Test(expected = SamlSpAlreadyExistsException.class) + public void testCreateSamlServiceProviderWithSameEntityIdInDefaultZone() throws Exception { + IdentityZoneHolder.set(IdentityZone.getUaa()); + String zoneId = IdentityZone.getUaa().getId(); + SamlServiceProvider sp = createSamlServiceProvider(zoneId); + db.create(sp); + db.create(sp); + } + + @Test(expected = SamlSpAlreadyExistsException.class) + public void testCreateSamlServiceProviderWithSameEntityIdInOtherZone() throws Exception { + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + SamlServiceProvider sp = createSamlServiceProvider(zone.getId()); + db.create(sp); + db.create(sp); + } + + @Test + public void testCreateSamlServiceProviderWithSameEntityIdInDifferentZones() throws Exception { + IdentityZoneHolder.set(IdentityZone.getUaa()); + String zoneId = IdentityZone.getUaa().getId(); + SamlServiceProvider sp = createSamlServiceProvider(zoneId); + db.create(sp); + + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + zoneId = zone.getId(); + sp.setIdentityZoneId(zoneId); + db.create(sp); + } + + @Test + public void testDeleteSamlServiceProvidersInUaaZone() { + IdentityZoneHolder.set(IdentityZone.getUaa()); + String zoneId = IdentityZone.getUaa().getId(); + + SamlServiceProvider sp = createSamlServiceProvider(zoneId); + SamlServiceProvider createdSp = db.create(sp); + + assertNotNull(createdSp); + assertThat(jdbcTemplate.queryForObject("select count(*) from service_provider where identity_zone_id=?", + new Object[] { IdentityZoneHolder.get().getId() }, Integer.class), is(1)); + db.onApplicationEvent(new EntityDeletedEvent<>(createdSp, authentication)); + assertThat(jdbcTemplate.queryForObject("select count(*) from service_provider where identity_zone_id=?", + new Object[] { IdentityZoneHolder.get().getId() }, Integer.class), is(0)); + } + + @Test + public void testDeleteSamlServiceProvidersInOtherZone() { + String zoneId = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(zoneId, zoneId); + IdentityZoneHolder.set(zone); + + SamlServiceProvider sp = createSamlServiceProvider(zoneId); + SamlServiceProvider createdSp = db.create(sp); + + assertNotNull(createdSp); + assertThat(jdbcTemplate.queryForObject("select count(*) from service_provider where identity_zone_id=?", + new Object[] { IdentityZoneHolder.get().getId() }, Integer.class), is(1)); + db.onApplicationEvent(new EntityDeletedEvent<>(createdSp, authentication)); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", + new Object[] { IdentityZoneHolder.get().getId() }, Integer.class), is(0)); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java new file mode 100644 index 00000000000..77a27a09dd7 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java @@ -0,0 +1,100 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.fail; + +import java.util.Timer; + +import org.cloudfoundry.identity.uaa.provider.saml.ComparableProvider; +import org.junit.Before; +import org.junit.Test; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.xml.parse.BasicParserPool; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; + +public class SamlServiceProviderConfiguratorTest { + + private static final String SINGLE_ADD_ENTITY_ID = "cloudfoundry-saml-login"; + private final SamlTestUtils samlTestUtils = new SamlTestUtils(); + + private SamlServiceProviderConfigurator conf = null; + private SamlServiceProviderDefinition singleAdd = null; + private SamlServiceProviderDefinition singleAddWithoutHeader = null; + + @Before + public void setup() throws Exception { + samlTestUtils.initalize(); + conf = new SamlServiceProviderConfigurator(); + conf.setParserPool(new BasicParserPool()); + singleAdd = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_ID, + new RandomValueStringGenerator().generate())) + .setSpEntityId(SINGLE_ADD_ENTITY_ID).setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).setZoneId("uaa").build(); + singleAddWithoutHeader = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_HEADER, + new RandomValueStringGenerator().generate())) + .setSpEntityId(SINGLE_ADD_ENTITY_ID).setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).setZoneId("uaa").build(); + } + + @Test + public void testCloneSamlServiceProviderDefinition() throws Exception { + SamlServiceProviderDefinition clone = singleAdd.clone(); + assertEquals(singleAdd, clone); + assertNotSame(singleAdd, clone); + } + + @Test + public void testAddAndUpdateAndRemoveSamlServiceProviderDefinition() throws Exception { + conf.addSamlServiceProviderDefinition(singleAdd); + assertEquals(1, conf.getSamlServiceProviders().size()); + conf.addSamlServiceProviderDefinition(singleAddWithoutHeader); + assertEquals(1, conf.getSamlServiceProviders().size()); + conf.removeSamlServiceProviderDefinition(singleAdd); + assertEquals(0, conf.getSamlServiceProviders().size()); + } + + @Test(expected = MetadataProviderException.class) + public void testAddSamlServiceProviderDefinitionWithConflictingEntityId() throws Exception { + conf.addSamlServiceProviderDefinition(singleAdd); + SamlServiceProviderDefinition duplicate = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_ID, + new RandomValueStringGenerator().generate())) + .setSpEntityId(SINGLE_ADD_ENTITY_ID + "_2").setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).setZoneId("uaa").build(); + conf.addSamlServiceProviderDefinition(duplicate); + } + + @Test(expected = NullPointerException.class) + public void testAddNullSamlServiceProvider() throws Exception { + conf.addSamlServiceProviderDefinition(null); + } + + @Test(expected = NullPointerException.class) + public void testAddSamlServiceProviderWithNullEntityId() throws Exception { + singleAdd.setSpEntityId(null); + conf.addSamlServiceProviderDefinition(singleAdd); + } + + @Test + public void testGetEntityID() throws Exception { + Timer t = new Timer(); + conf.addSamlServiceProviderDefinition(singleAdd); + for (SamlServiceProviderDefinition def : conf.getSamlServiceProviderDefinitions()) { + switch (def.getSpEntityId()) { + case "cloudfoundry-saml-login": { + ComparableProvider provider = (ComparableProvider) conf.getExtendedMetadataDelegateFromCache(def) + .getDelegate(); + assertEquals("cloudfoundry-saml-login", provider.getEntityID()); + break; + } + default: + fail(String.format("Unknown provider %s", def.getSpEntityId())); + } + + } + t.cancel(); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java new file mode 100644 index 00000000000..bd2e209beb3 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java @@ -0,0 +1,90 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition.MetadataLocation.DATA; +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition.MetadataLocation.UNKNOWN; +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition.MetadataLocation.URL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.apache.commons.httpclient.contrib.ssl.StrictSSLProtocolSocketFactory; +import org.junit.Before; +import org.junit.Test; + +public class SamlServiceProviderDefinitionTest { + + SamlServiceProviderDefinition definition; + + @Before + public void createDefinition() { + definition = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation("location") + .setSpEntityId("alias") + .setNameID("nameID") + .setMetadataTrustCheck(true) + .setZoneId("zoneId") + .build(); + } + + @Test + public void testXmlWithDoctypeFails() { + definition.setMetaDataLocation(SamlTestUtils.SAML_SP_METADATA.replace("", "\n")); + assertEquals(UNKNOWN, definition.getType()); + } + + @Test + public void testGetFileTypeFailsAndIsNoLongerSupported() throws Exception { + definition.setMetaDataLocation(System.getProperty("user.home")); + assertEquals(UNKNOWN, definition.getType()); + } + + @Test + public void testGetUrlTypeMustBeValidUrl() throws Exception { + definition.setMetaDataLocation("http"); + assertEquals(UNKNOWN, definition.getType()); + } + + @Test + public void testGetUrlWhenValid() throws Exception { + definition.setMetaDataLocation("http://login.identity.cf-app.com/saml/idp/metadata"); + assertEquals(URL, definition.getType()); + } + + @Test + public void testGetDataTypeIsValid() throws Exception { + definition.setMetaDataLocation(" builder = (SAMLObjectBuilder) builderFactory + .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); + AuthnRequest request = builder.buildObject(); + request.setVersion(SAMLVersion.VERSION_20); + request.setID(generateID()); + request.setIssuer(getIssuer(SP_ENTITY_ID)); + request.setVersion(SAMLVersion.VERSION_20); + request.setIssueInstant(new DateTime()); + return request; + } + + public String generateID() { + Random r = new Random(); + return 'a' + Long.toString(Math.abs(r.nextLong()), 20) + Long.toString(Math.abs(r.nextLong()), 20); + } + + public Issuer getIssuer(String localEntityId) { + @SuppressWarnings("unchecked") + SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory + .getBuilder(Issuer.DEFAULT_ELEMENT_NAME); + Issuer issuer = issuerBuilder.buildObject(); + issuer.setValue(localEntityId); + return issuer; + } + + public Authentication mockIdpSamlAuthentication(SAMLMessageContext context) { + + Authentication samlAuthenticationToken = new SAMLAuthenticationToken(context); + Authentication loginAuthenticationToken = mockAuthentication(); + IdpSamlCredentialsHolder credentials = new IdpSamlCredentialsHolder(samlAuthenticationToken, + loginAuthenticationToken); + IdpSamlAuthentication authentication = new IdpSamlAuthentication(credentials); + return authentication; + } + + public Authentication mockAuthentication() { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn("marissa"); + + UaaPrincipal principal = new UaaPrincipal(UUID.randomUUID().toString(), "marissa", "marissa@testing.org", + "http://localhost:8080/uaa/oauth/token", "marissa", "uaa"); + when(authentication.getPrincipal()).thenReturn(principal); + + Collection authorities = new ArrayList<>(); + authorities.add(UaaAuthority.UAA_USER); + doReturn(authorities).when(authentication).getAuthorities(); + + return authentication; + } + + public static final String SAML_SP_METADATA = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "mPb/c/Gb/PN61JNRptMgHbK9L08=" + + "" + + "" + + "Ra6mE3hjN68Jwk6D3DktVrOu0BXJCSPTMr0YTgQyII8fv7j93BhuGMoZHw48tww6N9zkUDEuy+uRp9vd4gepxs8+XiL+kvoclMAStmzJ62/2fGuI3hCvht2lBXIuFBpZab3iuqxBhwceLnsvvsM5y4nfYDXuBS1XGRzrygLbldM=" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" + + "" + + "" + + "" + + ""; + + public static final String UNSIGNED_SAML_SP_METADATA = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" + + "" + + "" + + "" + + ""; + + public static final String UNSIGNED_SAML_SP_METADATA_WITHOUT_ID = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" + + "" + + "" + + "" + + ""; + + public static final String UNSIGNED_SAML_SP_METADATA_WITHOUT_HEADER = UNSIGNED_SAML_SP_METADATA_WITHOUT_ID.replace("", ""); + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGeneratorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGeneratorTest.java new file mode 100644 index 00000000000..0dd5f6cfa29 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGeneratorTest.java @@ -0,0 +1,53 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ZoneAwareIdpMetadataGeneratorTest { + + public static final String ZONE_ID = "zone-id"; + private ZoneAwareIdpMetadataGenerator generator; + private IdentityZone otherZone; + private IdentityZoneConfiguration otherZoneDefinition; + + @Before + public void setup() { + otherZone = new IdentityZone(); + otherZone.setId(ZONE_ID); + otherZone.setName(ZONE_ID); + otherZone.setSubdomain(ZONE_ID); + otherZone.setConfig(new IdentityZoneConfiguration()); + otherZoneDefinition = otherZone.getConfig(); + otherZoneDefinition.getSamlConfig().setRequestSigned(true); + otherZoneDefinition.getSamlConfig().setWantAssertionSigned(true); + + otherZone.setConfig(otherZoneDefinition); + + generator = new ZoneAwareIdpMetadataGenerator(); + } + + @After + public void clear() { + IdentityZoneHolder.clear(); + } + + @Test + public void testWantRequestSigned() { + generator.setWantAuthnRequestSigned(false); + assertFalse(generator.isWantAuthnRequestSigned()); + + generator.setWantAuthnRequestSigned(true); + assertTrue(generator.isWantAuthnRequestSigned()); + + IdentityZoneHolder.set(otherZone); + + assertFalse(generator.isWantAuthnRequestSigned()); + } +} diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 281cae232d1..8a7ed7949dc 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -332,6 +332,7 @@ + diff --git a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml index 641d1dda0b6..7eb81b4e115 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml @@ -143,4 +143,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml new file mode 100644 index 00000000000..c3f3c4c3d85 --- /dev/null +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index f5c901d5df1..55d269cfc84 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -32,6 +32,7 @@ + @@ -109,6 +110,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -128,6 +130,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -199,6 +206,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -225,6 +233,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -251,23 +260,35 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + + + + - + + + + + + - + + + + @@ -292,6 +313,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -332,6 +354,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + @@ -345,5 +368,4 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java new file mode 100644 index 00000000000..fac08b8c093 --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java @@ -0,0 +1,561 @@ +/******************************************************************************* + * 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.integration.feature; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeTrue; + +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; +import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; +import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.test.TestAccounts; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.core.type.TypeReference; + +@RunWith(LoginServerClassRunner.class) +@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) +public class SamlLoginWithLocalIdpIT { + + @Autowired + @Rule + public IntegrationTestRule integrationTestRule; + + @Autowired + RestOperations restOperations; + + @Autowired + WebDriver webDriver; + + @Value("${integration.test.base_url}") + String baseUrl; + + @Autowired + TestAccounts testAccounts; + + @Autowired + TestClient testClient; + + ServerRunning serverRunning = ServerRunning.isRunning(); + + @Before + public void clearWebDriverOfCookies() throws Exception { + webDriver.get(baseUrl + "/logout.do"); + webDriver.manage().deleteAllCookies(); + webDriver.get(baseUrl.replace("localhost", "testzone1.localhost") + "/logout.do"); + webDriver.manage().deleteAllCookies(); + webDriver.get(baseUrl.replace("localhost", "testzone2.localhost") + "/logout.do"); + webDriver.manage().deleteAllCookies(); + webDriver.get("http://simplesamlphp.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout"); + webDriver.get("http://simplesamlphp2.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout"); + } + + /** + * Test that can UAA generate it's own SAML identity provider metadata. + */ + @Test + public void testDownloadSamlIdpMetadata() { + String entityId = "unit-test-idp"; + SamlIdentityProviderDefinition idpDefinition = createLocalSamlIdpDefinition(entityId, "uaa"); + Assert.assertTrue(idpDefinition.getMetaDataLocation().contains(IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME)); + Assert.assertTrue(idpDefinition.getMetaDataLocation().contains("entityID=\"" + entityId + "\"")); + } + + /** + * Test that we can create an identity provider in UAA using UAA's own SAML identity provider metadata. + */ + @Test + public void testCreateSamlIdp() throws Exception { + SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition("unit-test-idp", OriginKeys.UAA); + IntegrationTestUtils.createIdentityProvider("Local SAML IdP", "unit-test-idp", true, this.baseUrl, + this.serverRunning, idpDef); + } + + public static SamlIdentityProviderDefinition createLocalSamlIdpDefinition(String alias, String zoneId) { + + String url; + if (StringUtils.isNotEmpty(zoneId) && !zoneId.equals("uaa")) { + url = "http://" + zoneId + ".localhost:8080/uaa/saml/idp/metadata/alias/" + zoneId + "." + alias + "/idp"; + } else { + url = "http://localhost:8080/uaa/saml/idp/metadata/alias/" + alias + "/idp"; + } + + RestTemplate client = new RestTemplate(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Accept", "application/samlmetadata+xml"); + headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); + HttpEntity getHeaders = new HttpEntity(headers); + ResponseEntity metadataResponse = client.exchange(url, HttpMethod.GET, getHeaders, String.class); + + String idpMetaData = metadataResponse.getBody(); + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + def.setZoneId(zoneId); + def.setMetaDataLocation(idpMetaData); + def.setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); + def.setAssertionConsumerIndex(0); + def.setMetadataTrustCheck(false); + def.setShowSamlLink(true); + if (StringUtils.isNotEmpty(zoneId) && !zoneId.equals(OriginKeys.UAA)) { + def.setIdpEntityAlias(zoneId + "." + alias); + def.setLinkText("Login with Local SAML IdP(" + zoneId + "." + alias + ")"); + } else { + def.setIdpEntityAlias(alias); + def.setLinkText("Login with Local SAML IdP(" + alias + ")"); + } + return def; + } + + @Test + public void testCreateSamlSp() throws Exception { + SamlServiceProviderDefinition spDef = createLocalSamlSpDefinition("cloudfoundry-saml-login", "uaa"); + createSamlServiceProvider("Local SAML SP", "unit-test-sp", baseUrl, serverRunning, spDef); + } + + public static SamlServiceProviderDefinition createLocalSamlSpDefinition(String alias, String zoneId) { + + String url; + if (StringUtils.isNotEmpty(zoneId) && !zoneId.equals("uaa")) { + url = "http://" + zoneId + ".localhost:8080/uaa/saml/metadata/alias/" + zoneId + "." + alias; + } else { + url = "http://localhost:8080/uaa/saml/metadata/alias/" + alias; + } + + RestTemplate client = new RestTemplate(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Accept", "application/samlmetadata+xml"); + headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); + HttpEntity getHeaders = new HttpEntity(headers); + ResponseEntity metadataResponse = client.exchange(url, HttpMethod.GET, getHeaders, String.class); + + String spMetaData = metadataResponse.getBody(); + SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); + def.setZoneId(zoneId); + if (StringUtils.isNotEmpty(zoneId) && !zoneId.equals("uaa")) { + def.setSpEntityId(zoneId + "." + alias); + } else { + def.setSpEntityId(alias); + } + def.setMetaDataLocation(spMetaData); + def.setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); + def.setSingleSignOnServiceIndex(0); + def.setMetadataTrustCheck(false); + return def; + } + + public static SamlServiceProviderDefinition createZone1SamlSpDefinition(String alias) { + return createLocalSamlSpDefinition(alias, "testzone1"); + } + + public static SamlServiceProviderDefinition createZone2SamlSpDefinition(String alias) { + return createLocalSamlSpDefinition(alias, "testzone2"); + } + + public static SamlServiceProvider createSamlServiceProvider(String name, String entityId, String baseUrl, + ServerRunning serverRunning, SamlServiceProviderDefinition samlServiceProviderDefinition) throws Exception { + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate(IntegrationTestUtils + .getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret")); + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret")); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, + true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); + + String zoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), "identity", "identitysecret", email, "secr3T"); + + SamlServiceProvider provider = new SamlServiceProvider(); + provider.setConfig(samlServiceProviderDefinition); + provider.setIdentityZoneId(OriginKeys.UAA); + provider.setActive(true); + provider.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + provider.setName(name); + provider = createOrUpdateSamlServiceProvider(zoneAdminToken, baseUrl, provider); + assertNotNull(provider.getId()); + return provider; + } + + public static SamlServiceProvider createOrUpdateSamlServiceProvider(String accessToken, String url, + SamlServiceProvider provider) { + RestTemplate client = new RestTemplate(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); + headers.add("Authorization", "bearer " + accessToken); + headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE); + headers.add(IdentityZoneSwitchingFilter.HEADER, provider.getIdentityZoneId()); + List existing = getSamlServiceProviders(accessToken, url, provider.getIdentityZoneId()); + if (existing != null) { + for (SamlServiceProvider p : existing) { + if (p.getEntityId().equals(provider.getEntityId()) + && p.getIdentityZoneId().equals(provider.getIdentityZoneId())) { + provider.setId(p.getId()); + HttpEntity putHeaders = new HttpEntity(provider, headers); + ResponseEntity providerPut = client.exchange(url + "/service-providers/{id}", + HttpMethod.PUT, putHeaders, String.class, provider.getId()); + if (providerPut.getStatusCode() == HttpStatus.OK) { + return JsonUtils.readValue(providerPut.getBody(), SamlServiceProvider.class); + } + } + } + } + + HttpEntity postHeaders = new HttpEntity(provider, headers); + ResponseEntity providerPost = client.exchange(url + "/service-providers/{id}", HttpMethod.POST, + postHeaders, String.class, provider.getId()); + if (providerPost.getStatusCode() == HttpStatus.CREATED) { + return JsonUtils.readValue(providerPost.getBody(), SamlServiceProvider.class); + } + throw new IllegalStateException( + "Invalid result code returned, unable to create identity provider:" + providerPost.getStatusCode()); + } + + public static List getSamlServiceProviders(String zoneAdminToken, String url, String zoneId) { + RestTemplate client = new RestTemplate(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); + headers.add("Authorization", "bearer " + zoneAdminToken); + headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE); + headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); + HttpEntity getHeaders = new HttpEntity(headers); + ResponseEntity providerGet = client.exchange(url + "/service-providers", HttpMethod.GET, getHeaders, + String.class); + if (providerGet != null && providerGet.getStatusCode() == HttpStatus.OK) { + return JsonUtils.readValue(providerGet.getBody(), new TypeReference>() { + // Do nothing. + }); + } + return null; + } + + @Test + public void testLocalSamlIdpLogin() throws Exception { + ScimUser user = IntegrationTestUtils.createRandomUser(this.baseUrl); + testLocalSamlIdpLogin("/login", "Where to?", user.getPrimaryEmail(), "secr3T"); + } + + private void testLocalSamlIdpLogin(String firstUrl, String lookfor, String username, String password) + throws Exception { + SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition("unit-test-idp", "uaa"); + @SuppressWarnings("unchecked") + IdentityProvider provider = IntegrationTestUtils.createIdentityProvider( + "Local SAML IdP", "unit-test-idp", true, this.baseUrl, this.serverRunning, idpDef); + + SamlServiceProviderDefinition spDef = createLocalSamlSpDefinition("cloudfoundry-saml-login", "uaa"); + createSamlServiceProvider("Local SAML SP", "cloudfoundry-saml-login", baseUrl, serverRunning, spDef); + + // tells us that we are on travis + assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + + webDriver.get(baseUrl + firstUrl); + Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); + webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); + webDriver.findElement(By.xpath("//h1[contains(text(), 'Welcome!')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(username); + webDriver.findElement(By.name("password")).sendKeys(password); + webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString(lookfor)); + + provider.setActive(false); + IntegrationTestUtils.updateIdentityProvider(this.baseUrl, this.serverRunning, provider); + } + + @SuppressWarnings("unchecked") + @Test + public void testLocalSamlIdpLoginInTestZone1Works() throws Exception { + assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + String zoneId = "testzone1"; + + RestTemplate identityClient = IntegrationTestUtils + .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(baseUrl, + new String[] { "zones.write", "zones.read", "scim.zones" }, "identity", "identitysecret")); + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret")); + IdentityZone zone = IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, + true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), zoneId); + + String zoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), "identity", "identitysecret", email, "secr3T"); + + String testZone1Url = baseUrl.replace("localhost", zoneId + ".localhost"); + String zoneAdminClientId = new RandomValueStringGenerator().generate() + "-" + zoneId + "-admin"; + BaseClientDetails clientDetails = new BaseClientDetails(zoneAdminClientId, null, "uaa.none", + "client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", testZone1Url); + clientDetails.setClientSecret("secret"); + IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); + + RestTemplate zoneAdminClient = IntegrationTestUtils.getClientCredentialsTemplate(IntegrationTestUtils + .getClientCredentialsResource(testZone1Url, new String[0], zoneAdminClientId, "secret")); + String zoneUserEmail = new RandomValueStringGenerator().generate() + "@samltesting.org"; + IntegrationTestUtils.createUser(zoneAdminClient, testZone1Url, zoneUserEmail, "Dana", "Scully", zoneUserEmail, + true); + + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition("unit-test-idp"); + IdentityProvider provider = new IdentityProvider<>(); + provider.setIdentityZoneId(zoneId); + provider.setType(OriginKeys.SAML); + provider.setActive(true); + provider.setConfig(samlIdentityProviderDefinition); + provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); + provider.setName("Local SAML IdP for testzone1"); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertNotNull(provider.getId()); + + + SamlServiceProviderDefinition samlServiceProviderDefinition = createZone1SamlSpDefinition("cloudfoundry-saml-login"); + SamlServiceProvider sp = new SamlServiceProvider(); + sp.setIdentityZoneId(zoneId); + sp.setActive(true); + sp.setConfig(samlServiceProviderDefinition); + sp.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + sp.setName("Local SAML SP for testzone1"); + sp = createOrUpdateSamlServiceProvider(zoneAdminToken, baseUrl, sp); + + webDriver.get(baseUrl + "/logout.do"); + webDriver.get(testZone1Url + "/logout.do"); + webDriver.get(testZone1Url + "/login"); + Assert.assertEquals(zone.getName(), webDriver.getTitle()); + + List elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(1, elements.size()); + + WebElement element = elements.get(0); + assertNotNull(element); + element.click(); + webDriver.findElement(By.xpath("//h1[contains(text(), 'Welcome to The Twiglet Zone[" + zoneId + "]!')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(zoneUserEmail); + webDriver.findElement(By.name("password")).sendKeys("secr3T"); + webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + + webDriver.get(baseUrl + "/logout.do"); + webDriver.get(testZone1Url + "/logout.do"); + + // disable the provider + provider.setActive(false); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertNotNull(provider.getId()); + webDriver.get(testZone1Url + "/login"); + Assert.assertEquals(zone.getName(), webDriver.getTitle()); + elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(0, elements.size()); + + // enable the provider + provider.setActive(true); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertNotNull(provider.getId()); + webDriver.get(testZone1Url + "/login"); + Assert.assertEquals(zone.getName(), webDriver.getTitle()); + elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(1, elements.size()); + } + + /** + * In this test testzone1 acts as the SAML IdP and testzone2 acts as the SAML SP. + */ + @SuppressWarnings("unchecked") + @Test + public void testCrossZoneSamlIntegration() throws Exception { + assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + String idpZoneId = "testzone1"; + String spZoneId = "testzone2"; + + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret")); + RestTemplate identityClient = IntegrationTestUtils + .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(baseUrl, + new String[] { "zones.write", "zones.read", "scim.zones" }, "identity", "identitysecret")); + + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, idpZoneId, idpZoneId); + String idpZoneAdminEmail = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser idpZoneAdminUser = IntegrationTestUtils.createUser(adminClient, baseUrl, idpZoneAdminEmail, "firstname", "lastname", idpZoneAdminEmail, + true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, idpZoneAdminUser.getId(), idpZoneId); + String idpZoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), "identity", "identitysecret", idpZoneAdminEmail, "secr3T"); + + String idpZoneUserEmail = new RandomValueStringGenerator().generate() + "@samltesting.org"; + String idpZoneUrl = baseUrl.replace("localhost", idpZoneId + ".localhost"); + createZoneUser(idpZoneId, idpZoneAdminToken, idpZoneUserEmail, idpZoneUrl); + + SamlConfig samlConfig = new SamlConfig(); + samlConfig.setWantAssertionSigned(true); + IdentityZoneConfiguration config = new IdentityZoneConfiguration(); + config.setSamlConfig(samlConfig); + IdentityZone spZone = IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, spZoneId, spZoneId, config ); + String spZoneAdminEmail = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser spZoneAdminUser = IntegrationTestUtils.createUser(adminClient, baseUrl, spZoneAdminEmail, "firstname", "lastname", spZoneAdminEmail, + true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, spZoneAdminUser.getId(), spZoneId); + String spZoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), "identity", "identitysecret", spZoneAdminEmail, "secr3T"); + String spZoneUrl = baseUrl.replace("localhost", spZoneId + ".localhost"); + + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition("unit-test-idp"); + IdentityProvider idp = new IdentityProvider<>(); + idp.setIdentityZoneId(spZoneId); + idp.setType(OriginKeys.SAML); + idp.setActive(true); + idp.setConfig(samlIdentityProviderDefinition); + idp.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); + idp.setName("Local SAML IdP for testzone1"); + idp = IntegrationTestUtils.createOrUpdateProvider(spZoneAdminToken, baseUrl, idp); + assertNotNull(idp.getId()); + + SamlServiceProviderDefinition samlServiceProviderDefinition = createZone2SamlSpDefinition("cloudfoundry-saml-login"); + SamlServiceProvider sp = new SamlServiceProvider(); + sp.setIdentityZoneId(idpZoneId); + sp.setActive(true); + sp.setConfig(samlServiceProviderDefinition); + sp.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + sp.setName("Local SAML SP for testzone2"); + sp = createOrUpdateSamlServiceProvider(idpZoneAdminToken, baseUrl, sp); + + webDriver.get(baseUrl + "/logout.do"); + webDriver.get(spZoneUrl + "/logout.do"); + webDriver.get(spZoneUrl + "/login"); + Assert.assertEquals(spZone.getName(), webDriver.getTitle()); + + List elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(1, elements.size()); + + WebElement element = elements.get(0); + assertNotNull(element); + element.click(); + webDriver.findElement(By.xpath("//h1[contains(text(), 'Welcome to The Twiglet Zone[" + idpZoneId + "]!')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys(idpZoneUserEmail); + webDriver.findElement(By.name("password")).sendKeys("secr3T"); + webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + + webDriver.get(baseUrl + "/logout.do"); + webDriver.get(spZoneUrl + "/logout.do"); + + // disable the provider + idp.setActive(false); + idp = IntegrationTestUtils.createOrUpdateProvider(spZoneAdminToken, baseUrl, idp); + assertNotNull(idp.getId()); + webDriver.get(spZoneUrl + "/login"); + Assert.assertEquals(spZone.getName(), webDriver.getTitle()); + elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(0, elements.size()); + + // enable the provider + idp.setActive(true); + idp = IntegrationTestUtils.createOrUpdateProvider(spZoneAdminToken, baseUrl, idp); + assertNotNull(idp.getId()); + webDriver.get(spZoneUrl + "/login"); + Assert.assertEquals(spZone.getName(), webDriver.getTitle()); + elements = webDriver + .findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertNotNull(elements); + assertEquals(1, elements.size()); + } + + private void createZoneUser(String idpZoneId, String zoneAdminToken, String zoneUserEmail, String zoneUrl) throws Exception { + String zoneAdminClientId = new RandomValueStringGenerator().generate() + "-" + idpZoneId + "-admin"; + BaseClientDetails clientDetails = new BaseClientDetails(zoneAdminClientId, null, "uaa.none", + "client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", zoneUrl); + clientDetails.setClientSecret("secret"); + IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, idpZoneId, clientDetails); + + RestTemplate zoneAdminClient = IntegrationTestUtils.getClientCredentialsTemplate(IntegrationTestUtils + .getClientCredentialsResource(zoneUrl, new String[0], zoneAdminClientId, "secret")); + IntegrationTestUtils.createUser(zoneAdminClient, zoneUrl, zoneUserEmail, "Dana", "Scully", zoneUserEmail, + true); + } + + protected boolean doesSupportZoneDNS() { + try { + return Arrays.equals(Inet4Address.getByName("testzone1.localhost").getAddress(), + new byte[] { 127, 0, 0, 1 }) + && Arrays.equals(Inet4Address.getByName("testzone2.localhost").getAddress(), + new byte[] { 127, 0, 0, 1 }) + && Arrays.equals(Inet4Address.getByName("testzone3.localhost").getAddress(), + new byte[] { 127, 0, 0, 1 }); + } catch (UnknownHostException e) { + return false; + } + } + + public SamlIdentityProviderDefinition createZone1IdpDefinition(String alias) { + return createLocalSamlIdpDefinition(alias, "testzone1"); + } + + public SamlIdentityProviderDefinition createZone2IdpDefinition(String alias) { + return createLocalSamlIdpDefinition(alias, "testzone2"); + } + + public SamlIdentityProviderDefinition createZone3IdpDefinition(String alias) { + return createLocalSamlIdpDefinition(alias, "testzone3"); + } + +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index c3805eb8ca0..acd186045c5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; @@ -442,6 +443,25 @@ public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, return zone.getBody(); } + public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, + String url, + String id, + String subdomain, + IdentityZoneConfiguration config) { + + ResponseEntity zoneGet = client.getForEntity(url + "/identity-zones/{id}", String.class, id); + if (zoneGet.getStatusCode()==HttpStatus.OK) { + IdentityZone existing = JsonUtils.readValue(zoneGet.getBody(), IdentityZone.class); + existing.setSubdomain(subdomain); + existing.setConfig(config); + client.put(url + "/identity-zones/{id}", existing, id); + return existing; + } + IdentityZone identityZone = fixtureIdentityZone(id, subdomain, config); + ResponseEntity zone = client.postForEntity(url + "/identity-zones", identityZone, IdentityZone.class); + return zone.getBody(); + } + public static void makeZoneAdmin(RestTemplate client, String url, String userId, @@ -595,8 +615,19 @@ public static List getProviders(String zoneAdminToken, */ public static IdentityProvider createIdentityProvider(String originKey, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning) throws Exception { String zoneAdminToken = getZoneAdminToken(baseUrl, serverRunning); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(originKey, OriginKeys.UAA); + return createIdentityProvider("simplesamlphp for uaa", originKey, addShadowUserOnLogin, baseUrl, serverRunning, samlIdentityProviderDefinition); + } + + /** + * @param originKey The unique identifier used to reference the identity provider in UAA. + * @param addShadowUserOnLogin Specifies whether UAA should automatically create shadow users upon successful SAML authentication. + * @return An object representation of an identity provider. + * @throws Exception on error + */ + public static IdentityProvider createIdentityProvider(String name, String originKey, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning, SamlIdentityProviderDefinition samlIdentityProviderDefinition) throws Exception { + String zoneAdminToken = getZoneAdminToken(baseUrl, serverRunning); + samlIdentityProviderDefinition.setAddShadowUserOnLogin(addShadowUserOnLogin); IdentityProvider provider = new IdentityProvider(); provider.setIdentityZoneId(OriginKeys.UAA); @@ -604,7 +635,7 @@ public static IdentityProvider createIdentityProvider(String originKey, boolean provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); - provider.setName("simplesamlphp for uaa"); + provider.setName(name); provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); assertNotNull(provider.getId()); return provider; @@ -629,6 +660,40 @@ public static String getZoneAdminToken(String baseUrl, ServerRunning serverRunni "secr3T"); } + public static ScimUser createRandomUser(String baseUrl) throws Exception { + + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + return IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + } + + public static IdentityProvider updateIdentityProvider( + String baseUrl, ServerRunning serverRunning, IdentityProvider provider) throws Exception { + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret") + ); + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); + + String zoneAdminToken = + IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); + + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertNotNull(provider.getId()); + return provider; + } + public static SamlIdentityProviderDefinition createSimplePHPSamlIDP(String alias, String zoneId) { if (!("simplesamlphp".equals(alias) || "simplesamlphp2".equals(alias))) { throw new IllegalArgumentException("Only valid origins are: simplesamlphp,simplesamlphp2"); @@ -720,11 +785,17 @@ public static IdentityProvider createOrUpdateProvider(String accessToken, } public static IdentityZone fixtureIdentityZone(String id, String subdomain) { + + return fixtureIdentityZone(id, subdomain, null); + } + + public static IdentityZone fixtureIdentityZone(String id, String subdomain, IdentityZoneConfiguration config) { IdentityZone identityZone = new IdentityZone(); identityZone.setId(id); identityZone.setSubdomain(subdomain); identityZone.setName("The Twiglet Zone[" + id + "]"); identityZone.setDescription("Like the Twilight Zone but tastier[" + id + "]."); + identityZone.setConfig(config); return identityZone; } From f7e51778fbd3f74e423d052e8707b277061c686b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 1 Feb 2016 08:54:49 -0700 Subject: [PATCH 48/87] Rename DB migrations to not be skipped entity_id can be a URL - increase to 255 characters Move data objects to the model --- .../saml/idp/SamlServiceProvider.java | 0 .../idp/SamlServiceProviderDefinition.java | 29 +++++++++---------- .../SamlServiceProviderEndpoints.java | 2 +- .../saml/idp/IdpExtendedMetadata.java | 5 ---- ...ent.sql => V3_0_3__SAML_SP_Management.sql} | 2 +- .../db/mysql/V2_7_6__SAML_SP_Management.sql | 13 --------- .../db/mysql/V3_0_3__SAML_SP_Management.sql | 13 +++++++++ ...ent.sql => V3_0_3__SAML_SP_Management.sql} | 2 +- .../WEB-INF/spring/multitenant-endpoints.xml | 2 +- .../feature/SamlLoginWithLocalIdpIT.java | 27 +++++++++-------- 10 files changed, 43 insertions(+), 52 deletions(-) rename {server => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java (100%) rename {server => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java (94%) rename server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/{V2_7_6__SAML_SP_Management.sql => V3_0_3__SAML_SP_Management.sql} (92%) delete mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql rename server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/{V2_7_6__SAML_SP_Management.sql => V3_0_3__SAML_SP_Management.sql} (92%) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java similarity index 100% rename from server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java similarity index 94% rename from server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java index 13fb302aced..e55595814c2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java @@ -12,22 +12,19 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.util.Objects; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import com.fasterxml.jackson.annotation.JsonIgnore; - public class SamlServiceProviderDefinition { public static final String DEFAULT_HTTP_SOCKET_FACTORY = "org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory"; @@ -81,7 +78,7 @@ public MetadataLocation getType() { try { validateXml(trimmedLocation); return MetadataLocation.DATA; - } catch (MetadataProviderException x) { + } catch (IllegalArgumentException x) { //invalid XML } } else if (trimmedLocation.startsWith("http")) { @@ -97,20 +94,20 @@ public MetadataLocation getType() { return MetadataLocation.UNKNOWN; } - protected void validateXml(String xml) throws MetadataProviderException { + protected void validateXml(String xml) throws IllegalArgumentException { if (xml==null || xml.toUpperCase().contains(" - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java index fac08b8c093..dfbbb70454e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java @@ -12,16 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeTrue; - -import java.net.Inet4Address; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.List; - +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang.StringUtils; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -64,7 +55,15 @@ import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; -import com.fasterxml.jackson.core.type.TypeReference; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeTrue; @RunWith(LoginServerClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @@ -243,7 +242,7 @@ public static SamlServiceProvider createOrUpdateSamlServiceProvider(String acces && p.getIdentityZoneId().equals(provider.getIdentityZoneId())) { provider.setId(p.getId()); HttpEntity putHeaders = new HttpEntity(provider, headers); - ResponseEntity providerPut = client.exchange(url + "/service-providers/{id}", + ResponseEntity providerPut = client.exchange(url + "/saml/service-providers/{id}", HttpMethod.PUT, putHeaders, String.class, provider.getId()); if (providerPut.getStatusCode() == HttpStatus.OK) { return JsonUtils.readValue(providerPut.getBody(), SamlServiceProvider.class); @@ -253,7 +252,7 @@ public static SamlServiceProvider createOrUpdateSamlServiceProvider(String acces } HttpEntity postHeaders = new HttpEntity(provider, headers); - ResponseEntity providerPost = client.exchange(url + "/service-providers/{id}", HttpMethod.POST, + ResponseEntity providerPost = client.exchange(url + "/saml/service-providers/{id}", HttpMethod.POST, postHeaders, String.class, provider.getId()); if (providerPost.getStatusCode() == HttpStatus.CREATED) { return JsonUtils.readValue(providerPost.getBody(), SamlServiceProvider.class); @@ -270,7 +269,7 @@ public static List getSamlServiceProviders(String zoneAdmin headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE); headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); HttpEntity getHeaders = new HttpEntity(headers); - ResponseEntity providerGet = client.exchange(url + "/service-providers", HttpMethod.GET, getHeaders, + ResponseEntity providerGet = client.exchange(url + "/saml/service-providers", HttpMethod.GET, getHeaders, String.class); if (providerGet != null && providerGet.getStatusCode() == HttpStatus.OK) { return JsonUtils.readValue(providerGet.getBody(), new TypeReference>() { From 3d04f82baefc04e3c2522f702d69b7f1bc5c5480 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 1 Feb 2016 13:49:54 -0700 Subject: [PATCH 49/87] Fix mysql table script Caused by: java.sql.SQLException: Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause --- .../identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql index 248b818490e..01d9602c4e7 100644 --- a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_3__SAML_SP_Management.sql @@ -1,7 +1,7 @@ CREATE TABLE service_provider ( id VARCHAR(36) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP NULL, version BIGINT DEFAULT 0 NOT NULL, identity_zone_id VARCHAR(36) NOT NULL, name VARCHAR(255) NOT NULL, From 5225d69395e830866832c47f5e6988d6edae40ee Mon Sep 17 00:00:00 2001 From: amiri Date: Fri, 5 Feb 2016 11:21:18 -0800 Subject: [PATCH 50/87] Enhanced unit tests to verify assertion attributes. --- .../saml/idp/IdpWebSsoProfileImpl.java | 6 +-- .../saml/idp/IdpWebSsoProfileImplTest.java | 42 ++++++++++++++++++- .../uaa/provider/saml/idp/SamlTestUtils.java | 6 ++- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java index 87d10ab0950..46e49a1aed3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java @@ -229,13 +229,13 @@ private void buildAttributeStatement(Assertion assertion, Authentication authent UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); Attribute emailAttribute = buildStringAttribute("email", Arrays.asList(new String[] { principal.getEmail() })); attributeStatement.getAttributes().add(emailAttribute); - Attribute idAttribute = buildStringAttribute("email", Arrays.asList(new String[] { principal.getId() })); + Attribute idAttribute = buildStringAttribute("id", Arrays.asList(new String[] { principal.getId() })); attributeStatement.getAttributes().add(idAttribute); Attribute nameAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getName() })); attributeStatement.getAttributes().add(nameAttribute); - Attribute originAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getOrigin() })); + Attribute originAttribute = buildStringAttribute("origin", Arrays.asList(new String[] { principal.getOrigin() })); attributeStatement.getAttributes().add(originAttribute); - Attribute zoneAttribute = buildStringAttribute("name", Arrays.asList(new String[] { principal.getZoneId() })); + Attribute zoneAttribute = buildStringAttribute("zoneId", Arrays.asList(new String[] { principal.getZoneId() })); attributeStatement.getAttributes().add(zoneAttribute); assertion.getAttributeStatements().add(attributeStatement); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java index 29edbb418c0..22e68f0ef79 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java @@ -3,10 +3,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.List; +import java.util.UUID; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.opensaml.common.SAMLException; import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.Subject; @@ -16,6 +21,7 @@ import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.schema.XSString; import org.opensaml.xml.security.SecurityException; import org.opensaml.xml.signature.SignatureException; import org.springframework.security.core.Authentication; @@ -35,7 +41,8 @@ public void testBuildResponse() throws MessageEncodingException, SAMLException, SecurityException, MarshallingException, SignatureException { IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); - Authentication authentication = samlTestUtils.mockAuthentication(); + String authenticationId = UUID.randomUUID().toString(); + Authentication authentication = samlTestUtils.mockAuthentication(authenticationId); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); @@ -51,6 +58,34 @@ public void testBuildResponse() throws MessageEncodingException, SAMLException, SubjectConfirmation subjectConfirmation = subject.getSubjectConfirmations().get(0); SubjectConfirmationData subjectConfirmationData = subjectConfirmation.getSubjectConfirmationData(); assertEquals(request.getID(), subjectConfirmationData.getInResponseTo()); + + verifyAssertionAttributes(authenticationId, assertion); + } + + private void verifyAssertionAttributes(String authenticationId, Assertion assertion) { + List attributes = assertion.getAttributeStatements().get(0).getAttributes(); + assertAttributeValue(attributes, "email", "marissa@testing.org"); + assertAttributeValue(attributes, "id", authenticationId); + assertAttributeValue(attributes, "name", "marissa"); + assertAttributeValue(attributes, "origin", "http://localhost:8080/uaa/oauth/token"); + assertAttributeValue(attributes, "zoneId", "uaa"); + } + + private void assertAttributeValue(List attributeList, String name, String expectedValue) { + + for (Attribute attribute : attributeList) { + if (attribute.getName().equals(name)) { + if (1 != attribute.getAttributeValues().size()) { + Assert.fail(String.format("More than one attribute value with name of '%s'.", name)); + } + XSString xsString = (XSString) attribute.getAttributeValues().get(0); + Assert.assertEquals(String.format("Attribute mismatch for '%s'.", name), expectedValue, + xsString.getValue()); + return; + } + } + + Assert.fail(String.format("No attribute value with name of '%s'.", name)); } @Test @@ -58,7 +93,8 @@ public void testBuildResponseWithSignedAssertion() throws MessageEncodingExcepti MetadataProviderException, SecurityException, MarshallingException, SignatureException { IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); - Authentication authentication = samlTestUtils.mockAuthentication(); + String authenticationId = UUID.randomUUID().toString(); + Authentication authentication = samlTestUtils.mockAuthentication(authenticationId); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); @@ -75,6 +111,8 @@ public void testBuildResponseWithSignedAssertion() throws MessageEncodingExcepti SubjectConfirmationData subjectConfirmationData = subjectConfirmation.getSubjectConfirmationData(); assertEquals(request.getID(), subjectConfirmationData.getInResponseTo()); + verifyAssertionAttributes(authenticationId, assertion); + assertNotNull(assertion.getSignature()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index 82726cb176b..15abbbe5b8c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -176,10 +176,14 @@ public Authentication mockIdpSamlAuthentication(SAMLMessageContext context) { } public Authentication mockAuthentication() { + return mockAuthentication(UUID.randomUUID().toString()); + } + + public Authentication mockAuthentication(String id) { Authentication authentication = mock(Authentication.class); when(authentication.getName()).thenReturn("marissa"); - UaaPrincipal principal = new UaaPrincipal(UUID.randomUUID().toString(), "marissa", "marissa@testing.org", + UaaPrincipal principal = new UaaPrincipal(id, "marissa", "marissa@testing.org", "http://localhost:8080/uaa/oauth/token", "marissa", "uaa"); when(authentication.getPrincipal()).thenReturn(principal); From 28b763c9aa0609e9c0148356812be1c34dec0780 Mon Sep 17 00:00:00 2001 From: sanjeev Date: Tue, 9 Feb 2016 11:13:35 -0800 Subject: [PATCH 51/87] Extended UaaAuthentcation to include SAMLMessageContext. --- .../uaa/authentication/UaaAuthentication.java | 12 +++ .../saml/idp/IdpSamlAuthentication.java | 83 ------------------- .../idp/IdpSamlAuthenticationProvider.java | 17 ++-- .../IdpSamlAuthenticationSuccessHandler.java | 10 +-- ...pSamlAuthenticationSuccessHandlerTest.java | 10 +-- .../saml/idp/IdpWebSsoProfileImplTest.java | 4 +- .../uaa/provider/saml/idp/SamlTestUtils.java | 24 ++---- 7 files changed, 38 insertions(+), 122 deletions(-) delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index c82c8e7547b..8b3afc42406 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -45,6 +46,9 @@ public class UaaAuthentication implements Authentication, Serializable { private Set externalGroups; private Map> userAttributes; + //This is used when UAA acts as a SAML IdP + private SAMLMessageContext samlMessageContext; + /** * Creates a token with the supplied array of authorities. * @@ -195,4 +199,12 @@ public void setUserAttributes(MultiValueMap userAttributes) { } } + public SAMLMessageContext getSamlMessageContext() { + return samlMessageContext; + } + + public void setSamlMessageContext(SAMLMessageContext samlMessageContext) { + this.samlMessageContext = samlMessageContext; + } + } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java deleted file mode 100644 index 7177b806aa2..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml.idp; - -import java.util.Collection; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * This authentication object represents an SAML authentication requests that was authenticated using an openId login. - * In other words, when the local SAML identity provider receives an authentication request from an external SAML - * service provider, it authenticates the user using the UAA spring openId login page. UAA stores the result of that - * authentication in an instance of this object. As such this object consists of a holder that contains both a - * SamlAuthenticationToken, which provides the SAML context, and an OpenIdAuthenticationToken, which provides the - * authentication details of the authenticated user. - * - */ -public class IdpSamlAuthentication implements Authentication { - - /** - * Generated serialization id. - */ - private static final long serialVersionUID = -4895486519411522514L; - - private final IdpSamlCredentialsHolder credentials; - - public IdpSamlAuthentication(IdpSamlCredentialsHolder credentials) { - this.credentials = credentials; - } - - @Override - public String getName() { - return credentials.getLoginAuthenticationToken().getName(); - } - - @Override - public Collection getAuthorities() { - return credentials.getLoginAuthenticationToken().getAuthorities(); - } - - @Override - public Object getCredentials() { - return credentials; - } - - @Override - public Object getDetails() { - return credentials.getLoginAuthenticationToken().getDetails(); - } - - @Override - public Object getPrincipal() { - return credentials.getLoginAuthenticationToken().getPrincipal(); - } - - @Override - public boolean isAuthenticated() { - return credentials.getLoginAuthenticationToken().isAuthenticated(); - } - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - // Do nothing. - } - - public static class IdpSamlCredentialsHolder { - - private final Authentication samlAuthenticationToken; - private final Authentication loginAuthenticationToken; - - public IdpSamlCredentialsHolder(Authentication samlAuthenticationToken, Authentication loginAuthenticationToken) { - this.samlAuthenticationToken = samlAuthenticationToken; - this.loginAuthenticationToken = loginAuthenticationToken; - } - - public Authentication getSamlAuthenticationToken() { - return samlAuthenticationToken; - } - - public Authentication getLoginAuthenticationToken() { - return loginAuthenticationToken; - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java index a9e5c795c75..5f4f5ff395d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java @@ -1,29 +1,26 @@ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import org.cloudfoundry.identity.uaa.provider.saml.idp.IdpSamlAuthentication.IdpSamlCredentialsHolder; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.saml.SAMLAuthenticationToken; +import org.springframework.security.saml.context.SAMLMessageContext; /** - * This authentication provider produces a composite authentication object that contains the SamlAuthenticationToken, - * which contains the SAML context, and the OpenIdAuthenticationToken, which contains information about the - * authenticated user. + * This authentication provider attaches the SAMLMessageContext to an existing UaaAuthentication. */ public class IdpSamlAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - SecurityContext securityContext = SecurityContextHolder.getContext(); - Authentication loginAuthenticationToken = securityContext.getAuthentication(); - - IdpSamlCredentialsHolder credentials = new IdpSamlCredentialsHolder(authentication, loginAuthenticationToken); - - return new IdpSamlAuthentication(credentials); + UaaAuthentication uaaAuthentication = (UaaAuthentication) securityContext.getAuthentication(); + SAMLMessageContext samlMessageContext = ((SAMLAuthenticationToken) authentication).getCredentials(); + uaaAuthentication.setSamlMessageContext(samlMessageContext); + return uaaAuthentication; } @Override diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java index af884b1c6e9..8693ec465a7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java @@ -7,7 +7,7 @@ import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; -import org.cloudfoundry.identity.uaa.provider.saml.idp.IdpSamlAuthentication.IdpSamlCredentialsHolder; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.opensaml.common.SAMLException; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.metadata.EntityDescriptor; @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; -import org.springframework.security.saml.SAMLAuthenticationToken; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.metadata.MetadataManager; @@ -50,10 +49,7 @@ public IdpSamlAuthenticationSuccessHandler() { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - IdpSamlCredentialsHolder credentials = (IdpSamlCredentialsHolder) authentication.getCredentials(); - Authentication loginAuthentication = credentials.getLoginAuthenticationToken(); - SAMLAuthenticationToken token = (SAMLAuthenticationToken) credentials.getSamlAuthenticationToken(); - SAMLMessageContext context = token.getCredentials(); + SAMLMessageContext context = ((UaaAuthentication) authentication).getSamlMessageContext(); IdpExtendedMetadata extendedMetadata = null; try { @@ -72,7 +68,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); options.setAssertionsSigned(extendedMetadata.isAssertionsSigned()); options.setAssertionTimeToLiveSeconds(extendedMetadata.getAssertionTimeToLiveSeconds()); - idpWebSsoProfile.sendResponse(loginAuthentication, context, options); + idpWebSsoProfile.sendResponse(authentication, context, options); } catch (SAMLException e) { LOGGER.debug("Incoming SAML message is invalid.", e); throw new AuthenticationServiceException("Incoming SAML message is invalid.", e); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java index 22acbf2028c..513452f509a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandlerTest.java @@ -42,7 +42,7 @@ public void testOnAuthenticationSuccess() throws IOException, ServletException, IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); - Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + Authentication authentication = samlTestUtils.mockUaaAuthenticationWithSamlMessageContext(context); IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); idpExtendedMetaData.setAssertionsSigned(true); @@ -70,7 +70,7 @@ public void testOnAuthenticationSuccessFailureIfIdpExtendedMetadataMissing() IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); - Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + Authentication authentication = samlTestUtils.mockUaaAuthenticationWithSamlMessageContext(context); MetadataManager metadataManager = mock(MetadataManager.class); when(metadataManager.getExtendedMetadata(context.getLocalEntityId())) @@ -89,7 +89,7 @@ public void testOnAuthenticationSuccessFailureIfIdpPeerEntityIdNull() IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); - Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + Authentication authentication = samlTestUtils.mockUaaAuthenticationWithSamlMessageContext(context); IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); idpExtendedMetaData.setAssertionsSigned(true); @@ -119,7 +119,7 @@ public void testOnAuthenticationSuccessFailureIfIdpPeerEntityMetadataNull() IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); - Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + Authentication authentication = samlTestUtils.mockUaaAuthenticationWithSamlMessageContext(context); IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); idpExtendedMetaData.setAssertionsSigned(true); @@ -147,7 +147,7 @@ public void testOnAuthenticationSuccessFailureIfIdpPeerRoleDescriptorNull() IdpSamlAuthenticationSuccessHandler successHandler = new IdpSamlAuthenticationSuccessHandler(); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); - Authentication authentication = samlTestUtils.mockIdpSamlAuthentication(context); + Authentication authentication = samlTestUtils.mockUaaAuthenticationWithSamlMessageContext(context); IdpExtendedMetadata idpExtendedMetaData = new IdpExtendedMetadata(); idpExtendedMetaData.setAssertionsSigned(true); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java index 22e68f0ef79..67315d516d3 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java @@ -42,7 +42,7 @@ public void testBuildResponse() throws MessageEncodingException, SAMLException, IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); String authenticationId = UUID.randomUUID().toString(); - Authentication authentication = samlTestUtils.mockAuthentication(authenticationId); + Authentication authentication = samlTestUtils.mockUaaAuthentication(authenticationId); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); @@ -94,7 +94,7 @@ public void testBuildResponseWithSignedAssertion() throws MessageEncodingExcepti IdpWebSsoProfileImpl profile = new IdpWebSsoProfileImpl(); String authenticationId = UUID.randomUUID().toString(); - Authentication authentication = samlTestUtils.mockAuthentication(authenticationId); + Authentication authentication = samlTestUtils.mockUaaAuthentication(authenticationId); SAMLMessageContext context = samlTestUtils.mockSamlMessageContext(); IdpWebSSOProfileOptions options = new IdpWebSSOProfileOptions(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index 15abbbe5b8c..ccfd126a82d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -10,9 +10,9 @@ import java.util.Random; import java.util.UUID; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.provider.saml.SamlLoginServerKeyManager; -import org.cloudfoundry.identity.uaa.provider.saml.idp.IdpSamlAuthentication.IdpSamlCredentialsHolder; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.joda.time.DateTime; import org.opensaml.Configuration; @@ -26,9 +26,7 @@ import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObjectBuilderFactory; -import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.saml.SAMLAuthenticationToken; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; import org.springframework.security.saml.metadata.ExtendedMetadata; @@ -165,22 +163,18 @@ public Issuer getIssuer(String localEntityId) { return issuer; } - public Authentication mockIdpSamlAuthentication(SAMLMessageContext context) { - - Authentication samlAuthenticationToken = new SAMLAuthenticationToken(context); - Authentication loginAuthenticationToken = mockAuthentication(); - IdpSamlCredentialsHolder credentials = new IdpSamlCredentialsHolder(samlAuthenticationToken, - loginAuthenticationToken); - IdpSamlAuthentication authentication = new IdpSamlAuthentication(credentials); - return authentication; + public UaaAuthentication mockUaaAuthenticationWithSamlMessageContext(SAMLMessageContext context) { + UaaAuthentication uaaAuthentication = mockUaaAuthentication(); + when(uaaAuthentication.getSamlMessageContext()).thenReturn(context); + return uaaAuthentication; } - public Authentication mockAuthentication() { - return mockAuthentication(UUID.randomUUID().toString()); + public UaaAuthentication mockUaaAuthentication() { + return mockUaaAuthentication(UUID.randomUUID().toString()); } - public Authentication mockAuthentication(String id) { - Authentication authentication = mock(Authentication.class); + public UaaAuthentication mockUaaAuthentication(String id) { + UaaAuthentication authentication = mock(UaaAuthentication.class); when(authentication.getName()).thenReturn("marissa"); UaaPrincipal principal = new UaaPrincipal(id, "marissa", "marissa@testing.org", From 056fa4c1e6ccdbe719ca3741da6aefaad21cd523 Mon Sep 17 00:00:00 2001 From: sanjeev Date: Tue, 9 Feb 2016 16:26:45 -0800 Subject: [PATCH 52/87] Simplified SAML IdP URLs. Consolidated SAML SP and IdP entityID configuration. Signed-off-by: Dario --- .../saml/idp/IdpMetadataGenerator.java | 5 +- .../saml/idp/IdpSamlContextProviderImpl.java | 73 +++++++++++++++++++ .../main/webapp/WEB-INF/spring/saml-idp.xml | 14 +--- .../feature/SamlLoginWithLocalIdpIT.java | 20 ++--- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java index 1d56a014975..d84c68fa536 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java @@ -105,12 +105,12 @@ public class IdpMetadataGenerator { /** * Bindings for single sign-on holder of key */ - private Collection bindingsHoKSSO = Arrays.asList(); + private Collection bindingsHoKSSO = Collections.emptyList(); /** * Bindings for single logout */ - private Collection bindingsSLO = Arrays.asList("post", "redirect"); + private Collection bindingsSLO = Collections.emptyList(); /** * Flag indicates whether to include extension with discovery endpoints in metadata. @@ -522,7 +522,6 @@ private String getServerURL(String entityBaseURL, String entityAlias, String pro } result.append("alias/"); result.append(entityAlias); - result.append("/idp"); } String resultString = result.toString(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java index be1a2a76c04..0aedbcc0f8e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java @@ -2,9 +2,12 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; +import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.ws.transport.http.HTTPInTransport; import org.springframework.security.saml.context.SAMLContextProviderImpl; import org.springframework.security.saml.context.SAMLMessageContext; @@ -23,4 +26,74 @@ public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServlet context.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); return context; } + + /** + * Method tries to load localEntityAlias and localEntityRole from the request path. Path is supposed to be in format: + * https(s)://server:port/application/saml/filterName/alias/aliasName/idp|sp. In case alias is missing from + * the path defaults are used. Otherwise localEntityId and sp or idp localEntityRole is entered into the context. + *

    + * In case alias entity id isn't found an exception is raised. + * + * @param context context to populate fields localEntityId and localEntityRole for + * @param requestURI context path to parse entityId and entityRole from + * @throws MetadataProviderException in case entityId can't be populated + */ + @Override + protected void populateLocalEntityId(SAMLMessageContext context, String requestURI) throws MetadataProviderException { + + String entityId; + HTTPInTransport inTransport = (HTTPInTransport) context.getInboundMessageTransport(); + + // Pre-configured entity Id + entityId = (String) inTransport.getAttribute(org.springframework.security.saml.SAMLConstants.LOCAL_ENTITY_ID); + if (entityId != null) { + logger.debug("Using protocol specified IdP {}", entityId); + context.setLocalEntityId(entityId); + context.setLocalEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + return; + } + + if (requestURI == null) { + requestURI = ""; + } + + int filterIndex = requestURI.indexOf("/alias/"); + if (filterIndex != -1) { // EntityId from URL alias + + String localAlias = requestURI.substring(filterIndex + 7); + QName localEntityRole; + + int entityTypePosition = localAlias.lastIndexOf('/'); + if (entityTypePosition != -1) { + String entityRole = localAlias.substring(entityTypePosition + 1); + if ("sp".equalsIgnoreCase(entityRole)) { + localEntityRole = SPSSODescriptor.DEFAULT_ELEMENT_NAME; + } else { + localEntityRole = IDPSSODescriptor.DEFAULT_ELEMENT_NAME; + } + localAlias = localAlias.substring(0, entityTypePosition); + } else { + localEntityRole = IDPSSODescriptor.DEFAULT_ELEMENT_NAME; + } + + + // Populate entityId + entityId = metadata.getEntityIdForAlias(localAlias); + + if (entityId == null) { + throw new MetadataProviderException("No local entity found for alias " + localAlias + ", verify your configuration."); + } else { + logger.debug("Using IdP {} specified in request with alias {}", entityId, localAlias); + } + + context.setLocalEntityId(entityId); + context.setLocalEntityRole(localEntityRole); + + } else { // Defaults + context.setLocalEntityId(((IdpMetadataManager)metadata).getHostedIdpName()); + context.setLocalEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + } + + } + } diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml index c3f3c4c3d85..4c4fda46bc9 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-idp.xml @@ -13,15 +13,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> - - @@ -121,13 +112,14 @@ + + value="${login.saml.idp.entityIDAlias:${login.idp.entityID:${login.saml.entityIDAlias:${login.entityID:unit-test-idp}}}}" /> - + provider = IntegrationTestUtils.createIdentityProvider( - "Local SAML IdP", "unit-test-idp", true, this.baseUrl, this.serverRunning, idpDef); + "Local SAML IdP", IDP_ENTITY_ID, true, this.baseUrl, this.serverRunning, idpDef); SamlServiceProviderDefinition spDef = createLocalSamlSpDefinition("cloudfoundry-saml-login", "uaa"); createSamlServiceProvider("Local SAML SP", "cloudfoundry-saml-login", baseUrl, serverRunning, spDef); @@ -345,7 +347,7 @@ public void testLocalSamlIdpLoginInTestZone1Works() throws Exception { IntegrationTestUtils.createUser(zoneAdminClient, testZone1Url, zoneUserEmail, "Dana", "Scully", zoneUserEmail, true); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition("unit-test-idp"); + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition(IDP_ENTITY_ID); IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.SAML); @@ -453,7 +455,7 @@ public void testCrossZoneSamlIntegration() throws Exception { UaaTestAccounts.standard(serverRunning), "identity", "identitysecret", spZoneAdminEmail, "secr3T"); String spZoneUrl = baseUrl.replace("localhost", spZoneId + ".localhost"); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition("unit-test-idp"); + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createZone1IdpDefinition(IDP_ENTITY_ID); IdentityProvider idp = new IdentityProvider<>(); idp.setIdentityZoneId(spZoneId); idp.setType(OriginKeys.SAML); From 6f385c9cea11ac615e8af5d729fa691647e7c103 Mon Sep 17 00:00:00 2001 From: Dario Date: Wed, 10 Feb 2016 10:48:13 -0800 Subject: [PATCH 53/87] Fixed origin key in mock. Signed-off-by: Sanjeev --- .../uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java | 3 ++- .../identity/uaa/provider/saml/idp/SamlTestUtils.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java index 67315d516d3..7ae736d0895 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImplTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.UUID; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -67,7 +68,7 @@ private void verifyAssertionAttributes(String authenticationId, Assertion assert assertAttributeValue(attributes, "email", "marissa@testing.org"); assertAttributeValue(attributes, "id", authenticationId); assertAttributeValue(attributes, "name", "marissa"); - assertAttributeValue(attributes, "origin", "http://localhost:8080/uaa/oauth/token"); + assertAttributeValue(attributes, "origin", OriginKeys.UAA); assertAttributeValue(attributes, "zoneId", "uaa"); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index ccfd126a82d..2c802ead1dd 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -12,6 +12,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.saml.SamlLoginServerKeyManager; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.joda.time.DateTime; @@ -178,7 +179,7 @@ public UaaAuthentication mockUaaAuthentication(String id) { when(authentication.getName()).thenReturn("marissa"); UaaPrincipal principal = new UaaPrincipal(id, "marissa", "marissa@testing.org", - "http://localhost:8080/uaa/oauth/token", "marissa", "uaa"); + OriginKeys.UAA, "marissa", "uaa"); when(authentication.getPrincipal()).thenReturn(principal); Collection authorities = new ArrayList<>(); From 3dfc7678ee4e9bd7f809cd261fbf82eabce7891a Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 11 Feb 2016 12:35:59 -0700 Subject: [PATCH 54/87] Make test compile --- .../saml/idp/JdbcSamlServiceProviderProvisioningTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java index 526d0f1adc2..cbaef82fa14 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioningTest.java @@ -30,7 +30,6 @@ public class JdbcSamlServiceProviderProvisioningTest extends JdbcTestBase { private RandomValueStringGenerator generator = new RandomValueStringGenerator(); private Authentication authentication = mock(Authentication.class); - @Before public void createDatasource() throws Exception { db = new JdbcSamlServiceProviderProvisioning(jdbcTemplate); From a277ed69c8b96a49cd070c9417da81c117778d04 Mon Sep 17 00:00:00 2001 From: amiri Date: Sun, 31 Jan 2016 19:22:39 -0800 Subject: [PATCH 55/87] Added feature that turns UAA into a SAML Identity Provider. --- .../identity/uaa/audit/AuditEventType.java | 1 - .../saml/idp/IdpExtendedMetadata.java | 5 + .../saml/idp/IdpSamlAuthentication.java | 83 +++++ .../saml/idp/SamlServiceProvider.java | 307 ++++++++++++++++++ .../idp/SamlServiceProviderDefinition.java | 294 +++++++++++++++++ .../db/hsqldb/V2_7_6__SAML_SP_Management.sql | 13 + .../db/mysql/V2_7_6__SAML_SP_Management.sql | 13 + .../postgresql/V2_7_6__SAML_SP_Management.sql | 13 + .../feature/SamlLoginWithLocalIdpIT.java | 1 + 9 files changed, 729 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java index 0dc123f0950..962f401a9dd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java @@ -57,7 +57,6 @@ public enum AuditEventType { ServiceProviderCreatedEvent(33), ServiceProviderModifiedEvent(34); - private final int code; private AuditEventType(int code) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java index 39c935927a7..99efe0f0bfc 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java @@ -8,6 +8,11 @@ */ public class IdpExtendedMetadata extends ExtendedMetadata { + /** + * Generated serialization id. + */ + private static final long serialVersionUID = -7933870052729540864L; + private boolean assertionsSigned = true; private int assertionTimeToLiveSeconds = 500; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java new file mode 100644 index 00000000000..7177b806aa2 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java @@ -0,0 +1,83 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import java.util.Collection; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +/** + * This authentication object represents an SAML authentication requests that was authenticated using an openId login. + * In other words, when the local SAML identity provider receives an authentication request from an external SAML + * service provider, it authenticates the user using the UAA spring openId login page. UAA stores the result of that + * authentication in an instance of this object. As such this object consists of a holder that contains both a + * SamlAuthenticationToken, which provides the SAML context, and an OpenIdAuthenticationToken, which provides the + * authentication details of the authenticated user. + * + */ +public class IdpSamlAuthentication implements Authentication { + + /** + * Generated serialization id. + */ + private static final long serialVersionUID = -4895486519411522514L; + + private final IdpSamlCredentialsHolder credentials; + + public IdpSamlAuthentication(IdpSamlCredentialsHolder credentials) { + this.credentials = credentials; + } + + @Override + public String getName() { + return credentials.getLoginAuthenticationToken().getName(); + } + + @Override + public Collection getAuthorities() { + return credentials.getLoginAuthenticationToken().getAuthorities(); + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getDetails() { + return credentials.getLoginAuthenticationToken().getDetails(); + } + + @Override + public Object getPrincipal() { + return credentials.getLoginAuthenticationToken().getPrincipal(); + } + + @Override + public boolean isAuthenticated() { + return credentials.getLoginAuthenticationToken().isAuthenticated(); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + // Do nothing. + } + + public static class IdpSamlCredentialsHolder { + + private final Authentication samlAuthenticationToken; + private final Authentication loginAuthenticationToken; + + public IdpSamlCredentialsHolder(Authentication samlAuthenticationToken, Authentication loginAuthenticationToken) { + this.samlAuthenticationToken = samlAuthenticationToken; + this.loginAuthenticationToken = loginAuthenticationToken; + } + + public Authentication getSamlAuthenticationToken() { + return samlAuthenticationToken; + } + + public Authentication getLoginAuthenticationToken() { + return loginAuthenticationToken; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java new file mode 100644 index 00000000000..9b4bd4e55d8 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java @@ -0,0 +1,307 @@ +/******************************************************************************* + * 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.provider.saml.idp; + +import java.io.IOException; +import java.util.Date; + +import javax.validation.constraints.NotNull; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(using = SamlServiceProvider.SamlServiceProviderSerializer.class) +@JsonDeserialize(using = SamlServiceProvider.SamlServiceProviderDeserializer.class) +public class SamlServiceProvider { + + public static final String FIELD_ID = "id"; + public static final String FIELD_ENTITY_ID = "entityId"; + public static final String FIELD_NAME = "name"; + public static final String FIELD_VERSION = "version"; + public static final String FIELD_CREATED = "created"; + public static final String FIELD_LAST_MODIFIED = "lastModified"; + public static final String FIELD_ACTIVE = "active"; + public static final String FIELD_IDENTITY_ZONE_ID = "identityZoneId"; + public static final String FIELD_CONFIG = "config"; + + // see deserializer at the bottom + private String id; + @NotNull + private String entityId; + @NotNull + private String name; + private SamlServiceProviderDefinition config; + private int version = 0; + private Date created = new Date(); + private Date lastModified = new Date(); + private boolean active = true; + private String identityZoneId; + + public Date getCreated() { + return created; + } + + public SamlServiceProvider setCreated(Date created) { + this.created = created; + return this; + } + + public Date getLastModified() { + return lastModified; + } + + public SamlServiceProvider setLastModified(Date lastModified) { + this.lastModified = lastModified; + return this; + } + + public SamlServiceProvider setVersion(int version) { + this.version = version; + return this; + } + + public int getVersion() { + return version; + } + + public String getName() { + return name; + } + + public SamlServiceProvider setName(String name) { + this.name = name; + return this; + } + + public String getId() { + return id; + } + + public SamlServiceProvider setId(String id) { + this.id = id; + return this; + } + + public SamlServiceProviderDefinition getConfig() { + return config; + } + + public SamlServiceProvider setConfig(SamlServiceProviderDefinition config) { + + if (StringUtils.hasText(getEntityId())) { + config.setSpEntityId(getEntityId()); + } + if (StringUtils.hasText(getIdentityZoneId())) { + config.setZoneId(getIdentityZoneId()); + } + this.config = config; + return this; + } + + public String getEntityId() { + return entityId; + } + + public SamlServiceProvider setEntityId(String entityId) { + this.entityId = entityId; + if (config != null) { + config.setSpEntityId(entityId); + } + return this; + } + + public boolean isActive() { + return active; + } + + public SamlServiceProvider setActive(boolean active) { + this.active = active; + return this; + } + + public String getIdentityZoneId() { + return identityZoneId; + } + + public SamlServiceProvider setIdentityZoneId(String identityZoneId) { + this.identityZoneId = identityZoneId; + if (config != null) { + config.setZoneId(identityZoneId); + } + return this; + } + + public boolean configIsValid() { + // There may be need for this method in the fugure but for now it does nothing. + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((config == null) ? 0 : config.hashCode()); + result = prime * result + ((created == null) ? 0 : created.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((lastModified == null) ? 0 : lastModified.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((entityId == null) ? 0 : entityId.hashCode()); + result = prime * result + version; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SamlServiceProvider other = (SamlServiceProvider) obj; + if (config == null) { + if (other.config != null) + return false; + } else if (!config.equals(other.config)) + return false; + if (created == null) { + if (other.created != null) + return false; + } else if (!created.equals(other.created)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (lastModified == null) { + if (other.lastModified != null) + return false; + } else if (!lastModified.equals(other.lastModified)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (entityId == null) { + if (other.entityId != null) + return false; + } else if (!entityId.equals(other.entityId)) + return false; + if (version != other.version) + return false; + return true; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("SamlServiceProvider{"); + sb.append("id='").append(id).append('\''); + sb.append(", entityId='").append(entityId).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append(", active=").append(active); + sb.append('}'); + return sb.toString(); + } + + public static class SamlServiceProviderSerializer extends JsonSerializer { + @Override + public void serialize(SamlServiceProvider value, JsonGenerator gen, SerializerProvider serializers) + throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); + gen.writeStringField(FIELD_ID, value.getId()); + gen.writeStringField(FIELD_ENTITY_ID, value.getEntityId()); + gen.writeStringField(FIELD_NAME, value.getName()); + gen.writeNumberField(FIELD_VERSION, value.getVersion()); + writeDateField(FIELD_CREATED, value.getCreated(), gen); + writeDateField(FIELD_LAST_MODIFIED, value.getLastModified(), gen); + gen.writeBooleanField(FIELD_ACTIVE, value.isActive()); + gen.writeStringField(FIELD_IDENTITY_ZONE_ID, value.getIdentityZoneId()); + gen.writeEndObject(); + } + + public void writeDateField(String fieldName, Date value, JsonGenerator gen) throws IOException { + if (value != null) { + gen.writeNumberField(fieldName, value.getTime()); + } else { + gen.writeNullField(fieldName); + } + } + } + + public static class SamlServiceProviderDeserializer extends JsonDeserializer { + @Override + public SamlServiceProvider deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + SamlServiceProvider result = new SamlServiceProvider(); + // determine the type of IdentityProvider + JsonNode node = JsonUtils.readTree(jp); + // deserialize based on type + String config = getNodeAsString(node, FIELD_CONFIG, null); + SamlServiceProviderDefinition definition = null; + if (StringUtils.hasText(config)) { + definition = JsonUtils.readValue(config, SamlServiceProviderDefinition.class); + } + result.setConfig(definition); + + result.setId(getNodeAsString(node, FIELD_ID, null)); + result.setEntityId(getNodeAsString(node, FIELD_ENTITY_ID, null)); + result.setName(getNodeAsString(node, FIELD_NAME, null)); + result.setVersion(getNodeAsInt(node, FIELD_VERSION, 0)); + result.setCreated(getNodeAsDate(node, FIELD_CREATED)); + result.setLastModified(getNodeAsDate(node, FIELD_LAST_MODIFIED)); + result.setActive(getNodeAsBoolean(node, FIELD_ACTIVE, true)); + result.setIdentityZoneId(getNodeAsString(node, FIELD_IDENTITY_ZONE_ID, null)); + return result; + } + + protected String getNodeAsString(JsonNode node, String fieldName, String defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asText(defaultValue); + } + + protected int getNodeAsInt(JsonNode node, String fieldName, int defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asInt(defaultValue); + } + + protected boolean getNodeAsBoolean(JsonNode node, String fieldName, boolean defaultValue) { + JsonNode typeNode = node.get(fieldName); + return typeNode == null ? defaultValue : typeNode.asBoolean(defaultValue); + } + + protected Date getNodeAsDate(JsonNode node, String fieldName) { + JsonNode typeNode = node.get(fieldName); + long date = typeNode == null ? -1 : typeNode.asLong(-1); + if (date == -1) { + return null; + } else { + return new Date(date); + } + } + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java new file mode 100644 index 00000000000..13fb302aced --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.provider.saml.idp; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class SamlServiceProviderDefinition { + + public static final String DEFAULT_HTTP_SOCKET_FACTORY = "org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory"; + public static final String DEFAULT_HTTPS_SOCKET_FACTORY = "org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory"; + + public enum MetadataLocation { + URL, + DATA, + UNKNOWN + } + + private String metaDataLocation; + private String spEntityId; + private String zoneId; + private String nameID; + private int singleSignOnServiceIndex; + private boolean metadataTrustCheck; + private String socketFactoryClassName; + + public SamlServiceProviderDefinition clone() { + return new SamlServiceProviderDefinition(metaDataLocation, + spEntityId, + nameID, + singleSignOnServiceIndex, + metadataTrustCheck, + zoneId); + } + + public SamlServiceProviderDefinition() {} + + public SamlServiceProviderDefinition(String metaDataLocation, + String spEntityAlias, + String nameID, + int singleSignOnServiceIndex, + boolean metadataTrustCheck, + String zoneId) { + this.metaDataLocation = metaDataLocation; + this.spEntityId = spEntityAlias; + this.nameID = nameID; + this.singleSignOnServiceIndex = singleSignOnServiceIndex; + this.metadataTrustCheck = metadataTrustCheck; + this.zoneId = zoneId; + } + + @JsonIgnore + public MetadataLocation getType() { + String trimmedLocation = metaDataLocation.trim(); + if (trimmedLocation.startsWith("0) { + return socketFactoryClassName; + } + if (getMetaDataLocation()==null || getMetaDataLocation().trim().length()==0) { + throw new IllegalStateException("Invalid meta data URL[" + getMetaDataLocation() + "] cannot determine socket factory."); + } + if (getMetaDataLocation().startsWith("https")) { + return DEFAULT_HTTPS_SOCKET_FACTORY; + } else { + return DEFAULT_HTTP_SOCKET_FACTORY; + } + } + + public void setSocketFactoryClassName(String socketFactoryClassName) { + this.socketFactoryClassName = socketFactoryClassName; + if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { + try { + Class.forName( + socketFactoryClassName, + true, + Thread.currentThread().getContextClassLoader() + ); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + } + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SamlServiceProviderDefinition that = (SamlServiceProviderDefinition) o; + + return Objects.equals(getUniqueAlias(), that.getUniqueAlias()); + } + + @Override + public int hashCode() { + String alias = getUniqueAlias(); + return alias==null ? 0 : alias.hashCode(); + } + + @JsonIgnore + protected String getUniqueAlias() { + return getSpEntityId()+"###"+getZoneId(); + } + + @Override + public String toString() { + return "SamlServiceProviderDefinition{" + + "spEntityAlias='" + spEntityId + '\'' + + ", metaDataLocation='" + metaDataLocation + '\'' + + ", nameID='" + nameID + '\'' + + ", singleSignOnServiceIndex=" + singleSignOnServiceIndex + + ", metadataTrustCheck=" + metadataTrustCheck + + ", socketFactoryClassName='" + socketFactoryClassName + '\'' + + ", zoneId='" + zoneId + '\'' + + '}'; + } + + public static class Builder { + + private String metaDataLocation; + private String spEntityId; + private String zoneId; + private String nameID; + private int singleSignOnServiceIndex; + private boolean metadataTrustCheck; + private String socketFactoryClassName; + + private Builder(){} + + public static Builder get() { + return new Builder(); + } + + public SamlServiceProviderDefinition build() { + SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); + + def.setMetaDataLocation(metaDataLocation); + def.setSpEntityId(spEntityId); + def.setZoneId(zoneId); + def.setNameID(nameID); + def.setSingleSignOnServiceIndex(singleSignOnServiceIndex); + def.setMetadataTrustCheck(metadataTrustCheck); + def.setSocketFactoryClassName(socketFactoryClassName); + return def; + } + + public Builder setMetaDataLocation(String metaDataLocation) { + this.metaDataLocation = metaDataLocation; + return this; + } + + public Builder setSpEntityId(String spEntityId) { + this.spEntityId = spEntityId; + return this; + } + + public Builder setZoneId(String zoneId) { + this.zoneId = zoneId; + return this; + } + + public Builder setNameID(String nameID) { + this.nameID = nameID; + return this; + } + + public Builder setSingleSignOnServiceIndex(int singleSignOnServiceIndex) { + this.singleSignOnServiceIndex = singleSignOnServiceIndex; + return this; + } + + public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { + this.metadataTrustCheck = metadataTrustCheck; + return this; + } + + public Builder setSocketFactoryClassName(String socketFactoryClassName) { + this.socketFactoryClassName = socketFactoryClassName; + return this; + } + } +} diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..58e15fdd8b0 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE service_provider ( + id CHAR(36) NOT NULL PRIMARY KEY, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + version BIGINT DEFAULT 0 NOT NULL, + identity_zone_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + entity_id varchar(36) NOT NULL, + config LONGVARCHAR, + active BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..5ca5ce697ab --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE `service_provider` ( + `id` VARCHAR(36) NOT NULL, + `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + `version` BIGINT DEFAULT 0 NOT NULL, + `identity_zone_id` VARCHAR(36) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `entity_id` VARCHAR(36) NOT NULL, + `config` LONGTEXT, + `active` BOOLEAN DEFAULT TRUE NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `entity_in_zone` (`identity_zone_id`, `entity_id`) +); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql new file mode 100644 index 00000000000..9e225eeeff0 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql @@ -0,0 +1,13 @@ +CREATE TABLE service_provider ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + version BIGINT DEFAULT 0, + identity_zone_id VARCHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + entity_id VARCHAR(36) NOT NULL, + config TEXT, + active BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java index 762f67725ea..8746dc98b97 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.integration.feature; import com.fasterxml.jackson.core.type.TypeReference; + import org.apache.commons.lang.StringUtils; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; From ba77fd1f1ea1a2d9de2201761aa02dbe7606fe02 Mon Sep 17 00:00:00 2001 From: Dario Date: Thu, 11 Feb 2016 16:43:56 -0800 Subject: [PATCH 56/87] Refactored SAML SP persistence and in-memory tracking. --- .../saml/idp/SamlServiceProvider.java | 12 - .../idp/SamlServiceProviderDefinition.java | 151 +++------ .../SamlServiceProviderEndpoints.java | 17 +- .../saml/idp/IdpSamlAuthentication.java | 83 ----- .../JdbcSamlServiceProviderProvisioning.java | 7 - .../saml/idp/SamlServiceProvider.java | 307 ------------------ .../SamlServiceProviderChangedListener.java | 9 +- .../idp/SamlServiceProviderConfigurator.java | 208 ++++++------ .../idp/SamlServiceProviderDefinition.java | 294 ----------------- .../saml/idp/SamlServiceProviderHolder.java | 56 ++++ .../saml/idp/ZoneAwareIdpMetadataManager.java | 29 +- .../db/hsqldb/V2_7_6__SAML_SP_Management.sql | 13 - .../db/mysql/V2_7_6__SAML_SP_Management.sql | 13 - .../postgresql/V2_7_6__SAML_SP_Management.sql | 13 - .../SamlServiceProviderConfiguratorTest.java | 115 ++++--- .../SamlServiceProviderDefinitionTest.java | 24 -- .../uaa/provider/saml/idp/SamlTestUtils.java | 93 ++++++ .../idp/ZoneAwareIdpMetadataManagerTest.java | 94 ++++++ .../feature/SamlLoginWithLocalIdpIT.java | 14 +- 19 files changed, 482 insertions(+), 1070 deletions(-) delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java delete mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql delete mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql delete mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManagerTest.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java index 9b4bd4e55d8..dd6c643d77e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java @@ -109,12 +109,6 @@ public SamlServiceProviderDefinition getConfig() { public SamlServiceProvider setConfig(SamlServiceProviderDefinition config) { - if (StringUtils.hasText(getEntityId())) { - config.setSpEntityId(getEntityId()); - } - if (StringUtils.hasText(getIdentityZoneId())) { - config.setZoneId(getIdentityZoneId()); - } this.config = config; return this; } @@ -125,9 +119,6 @@ public String getEntityId() { public SamlServiceProvider setEntityId(String entityId) { this.entityId = entityId; - if (config != null) { - config.setSpEntityId(entityId); - } return this; } @@ -146,9 +137,6 @@ public String getIdentityZoneId() { public SamlServiceProvider setIdentityZoneId(String identityZoneId) { this.identityZoneId = identityZoneId; - if (config != null) { - config.setZoneId(identityZoneId); - } return this; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java index e55595814c2..e4af76bc98c 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java @@ -12,23 +12,21 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; -import java.util.Objects; -public class SamlServiceProviderDefinition { +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.annotation.JsonIgnore; - public static final String DEFAULT_HTTP_SOCKET_FACTORY = "org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory"; - public static final String DEFAULT_HTTPS_SOCKET_FACTORY = "org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory"; +public class SamlServiceProviderDefinition { public enum MetadataLocation { URL, @@ -37,36 +35,27 @@ public enum MetadataLocation { } private String metaDataLocation; - private String spEntityId; - private String zoneId; private String nameID; private int singleSignOnServiceIndex; private boolean metadataTrustCheck; - private String socketFactoryClassName; public SamlServiceProviderDefinition clone() { return new SamlServiceProviderDefinition(metaDataLocation, - spEntityId, nameID, singleSignOnServiceIndex, - metadataTrustCheck, - zoneId); + metadataTrustCheck); } public SamlServiceProviderDefinition() {} public SamlServiceProviderDefinition(String metaDataLocation, - String spEntityAlias, String nameID, int singleSignOnServiceIndex, - boolean metadataTrustCheck, - String zoneId) { + boolean metadataTrustCheck) { this.metaDataLocation = metaDataLocation; - this.spEntityId = spEntityAlias; this.nameID = nameID; this.singleSignOnServiceIndex = singleSignOnServiceIndex; this.metadataTrustCheck = metadataTrustCheck; - this.zoneId = zoneId; } @JsonIgnore @@ -119,14 +108,6 @@ public void setMetaDataLocation(String metaDataLocation) { this.metaDataLocation = metaDataLocation; } - public String getSpEntityId() { - return spEntityId; - } - - public void setSpEntityId(String spEntityId) { - this.spEntityId = spEntityId; - } - public String getNameID() { return nameID; } @@ -151,88 +132,59 @@ public void setMetadataTrustCheck(boolean metadataTrustCheck) { this.metadataTrustCheck = metadataTrustCheck; } - public String getSocketFactoryClassName() { - if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { - return socketFactoryClassName; - } - if (getMetaDataLocation()==null || getMetaDataLocation().trim().length()==0) { - throw new IllegalStateException("Invalid meta data URL[" + getMetaDataLocation() + "] cannot determine socket factory."); - } - if (getMetaDataLocation().startsWith("https")) { - return DEFAULT_HTTPS_SOCKET_FACTORY; - } else { - return DEFAULT_HTTP_SOCKET_FACTORY; - } - } - - public void setSocketFactoryClassName(String socketFactoryClassName) { - this.socketFactoryClassName = socketFactoryClassName; - if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { - try { - Class.forName( - socketFactoryClassName, - true, - Thread.currentThread().getContextClassLoader() - ); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - } - } - - public String getZoneId() { - return zoneId; - } - - public void setZoneId(String zoneId) { - this.zoneId = zoneId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SamlServiceProviderDefinition that = (SamlServiceProviderDefinition) o; - - return Objects.equals(getUniqueAlias(), that.getUniqueAlias()); - } - @Override public int hashCode() { - String alias = getUniqueAlias(); - return alias==null ? 0 : alias.hashCode(); + final int prime = 31; + int result = 1; + result = prime * result + ((metaDataLocation == null) ? 0 : metaDataLocation.hashCode()); + result = prime * result + (metadataTrustCheck ? 1231 : 1237); + result = prime * result + ((nameID == null) ? 0 : nameID.hashCode()); + result = prime * result + singleSignOnServiceIndex; + return result; } - @JsonIgnore - protected String getUniqueAlias() { - return getSpEntityId()+"###"+getZoneId(); + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SamlServiceProviderDefinition other = (SamlServiceProviderDefinition) obj; + if (metaDataLocation == null) { + if (other.metaDataLocation != null) + return false; + } else if (!metaDataLocation.equals(other.metaDataLocation)) + return false; + if (metadataTrustCheck != other.metadataTrustCheck) + return false; + if (nameID == null) { + if (other.nameID != null) + return false; + } else if (!nameID.equals(other.nameID)) + return false; + if (singleSignOnServiceIndex != other.singleSignOnServiceIndex) + return false; + return true; } @Override public String toString() { return "SamlServiceProviderDefinition{" + - "spEntityAlias='" + spEntityId + '\'' + ", metaDataLocation='" + metaDataLocation + '\'' + ", nameID='" + nameID + '\'' + ", singleSignOnServiceIndex=" + singleSignOnServiceIndex + ", metadataTrustCheck=" + metadataTrustCheck + - ", socketFactoryClassName='" + socketFactoryClassName + '\'' + - ", zoneId='" + zoneId + '\'' + '}'; } public static class Builder { private String metaDataLocation; - private String spEntityId; - private String zoneId; private String nameID; private int singleSignOnServiceIndex; private boolean metadataTrustCheck; - private String socketFactoryClassName; private Builder(){} @@ -242,14 +194,10 @@ public static Builder get() { public SamlServiceProviderDefinition build() { SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); - def.setMetaDataLocation(metaDataLocation); - def.setSpEntityId(spEntityId); - def.setZoneId(zoneId); def.setNameID(nameID); def.setSingleSignOnServiceIndex(singleSignOnServiceIndex); def.setMetadataTrustCheck(metadataTrustCheck); - def.setSocketFactoryClassName(socketFactoryClassName); return def; } @@ -258,16 +206,6 @@ public Builder setMetaDataLocation(String metaDataLocation) { return this; } - public Builder setSpEntityId(String spEntityId) { - this.spEntityId = spEntityId; - return this; - } - - public Builder setZoneId(String zoneId) { - this.zoneId = zoneId; - return this; - } - public Builder setNameID(String nameID) { this.nameID = nameID; return this; @@ -282,10 +220,5 @@ public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { this.metadataTrustCheck = metadataTrustCheck; return this; } - - public Builder setSocketFactoryClassName(String socketFactoryClassName) { - this.socketFactoryClassName = socketFactoryClassName; - return this; - } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java index 1a57773ad1f..3b3841f50f8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java @@ -27,10 +27,8 @@ import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderConfigurator; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.dao.EmptyResultDataAccessException; @@ -66,12 +64,7 @@ public ResponseEntity createServiceProvider(@RequestBody Sa String zoneId = IdentityZoneHolder.get().getId(); body.setIdentityZoneId(zoneId); - SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), - SamlServiceProviderDefinition.class); - definition.setZoneId(zoneId); - definition.setSpEntityId(body.getEntityId()); - samlConfigurator.addSamlServiceProviderDefinition(definition); - body.setConfig(definition); + samlConfigurator.addSamlServiceProvider(body); SamlServiceProvider createdSp = serviceProviderProvisioning.create(body); return new ResponseEntity<>(createdSp, HttpStatus.CREATED); @@ -88,12 +81,8 @@ public ResponseEntity updateServiceProvider(@PathVariable S return new ResponseEntity<>(UNPROCESSABLE_ENTITY); } body.setEntityId(existing.getEntityId()); - SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), - SamlServiceProviderDefinition.class); - definition.setZoneId(zoneId); - definition.setSpEntityId(body.getEntityId()); - samlConfigurator.addSamlServiceProviderDefinition(definition); - body.setConfig(definition); + + samlConfigurator.addSamlServiceProvider(body); SamlServiceProvider updatedSp = serviceProviderProvisioning.update(body); return new ResponseEntity<>(updatedSp, OK); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java deleted file mode 100644 index 7177b806aa2..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthentication.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml.idp; - -import java.util.Collection; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * This authentication object represents an SAML authentication requests that was authenticated using an openId login. - * In other words, when the local SAML identity provider receives an authentication request from an external SAML - * service provider, it authenticates the user using the UAA spring openId login page. UAA stores the result of that - * authentication in an instance of this object. As such this object consists of a holder that contains both a - * SamlAuthenticationToken, which provides the SAML context, and an OpenIdAuthenticationToken, which provides the - * authentication details of the authenticated user. - * - */ -public class IdpSamlAuthentication implements Authentication { - - /** - * Generated serialization id. - */ - private static final long serialVersionUID = -4895486519411522514L; - - private final IdpSamlCredentialsHolder credentials; - - public IdpSamlAuthentication(IdpSamlCredentialsHolder credentials) { - this.credentials = credentials; - } - - @Override - public String getName() { - return credentials.getLoginAuthenticationToken().getName(); - } - - @Override - public Collection getAuthorities() { - return credentials.getLoginAuthenticationToken().getAuthorities(); - } - - @Override - public Object getCredentials() { - return credentials; - } - - @Override - public Object getDetails() { - return credentials.getLoginAuthenticationToken().getDetails(); - } - - @Override - public Object getPrincipal() { - return credentials.getLoginAuthenticationToken().getPrincipal(); - } - - @Override - public boolean isAuthenticated() { - return credentials.getLoginAuthenticationToken().isAuthenticated(); - } - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - // Do nothing. - } - - public static class IdpSamlCredentialsHolder { - - private final Authentication samlAuthenticationToken; - private final Authentication loginAuthenticationToken; - - public IdpSamlCredentialsHolder(Authentication samlAuthenticationToken, Authentication loginAuthenticationToken) { - this.samlAuthenticationToken = samlAuthenticationToken; - this.loginAuthenticationToken = loginAuthenticationToken; - } - - public Authentication getSamlAuthenticationToken() { - return samlAuthenticationToken; - } - - public Authentication getLoginAuthenticationToken() { - return loginAuthenticationToken; - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java index 5e13d23e991..b988a598052 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java @@ -24,7 +24,6 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; @@ -174,12 +173,6 @@ protected void validate(SamlServiceProvider provider) { if (!StringUtils.hasText(provider.getIdentityZoneId())) { throw new DataIntegrityViolationException("Identity zone ID must be set."); } - - SamlServiceProviderDefinition saml = ObjectUtils.castInstance(provider.getConfig(), - SamlServiceProviderDefinition.class); - saml.setSpEntityId(provider.getEntityId()); - saml.setZoneId(provider.getIdentityZoneId()); - provider.setConfig(saml); } private static final class SamlServiceProviderRowMapper implements RowMapper { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java deleted file mode 100644 index 9b4bd4e55d8..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProvider.java +++ /dev/null @@ -1,307 +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.provider.saml.idp; - -import java.io.IOException; -import java.util.Date; - -import javax.validation.constraints.NotNull; - -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.springframework.util.StringUtils; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -@JsonSerialize(using = SamlServiceProvider.SamlServiceProviderSerializer.class) -@JsonDeserialize(using = SamlServiceProvider.SamlServiceProviderDeserializer.class) -public class SamlServiceProvider { - - public static final String FIELD_ID = "id"; - public static final String FIELD_ENTITY_ID = "entityId"; - public static final String FIELD_NAME = "name"; - public static final String FIELD_VERSION = "version"; - public static final String FIELD_CREATED = "created"; - public static final String FIELD_LAST_MODIFIED = "lastModified"; - public static final String FIELD_ACTIVE = "active"; - public static final String FIELD_IDENTITY_ZONE_ID = "identityZoneId"; - public static final String FIELD_CONFIG = "config"; - - // see deserializer at the bottom - private String id; - @NotNull - private String entityId; - @NotNull - private String name; - private SamlServiceProviderDefinition config; - private int version = 0; - private Date created = new Date(); - private Date lastModified = new Date(); - private boolean active = true; - private String identityZoneId; - - public Date getCreated() { - return created; - } - - public SamlServiceProvider setCreated(Date created) { - this.created = created; - return this; - } - - public Date getLastModified() { - return lastModified; - } - - public SamlServiceProvider setLastModified(Date lastModified) { - this.lastModified = lastModified; - return this; - } - - public SamlServiceProvider setVersion(int version) { - this.version = version; - return this; - } - - public int getVersion() { - return version; - } - - public String getName() { - return name; - } - - public SamlServiceProvider setName(String name) { - this.name = name; - return this; - } - - public String getId() { - return id; - } - - public SamlServiceProvider setId(String id) { - this.id = id; - return this; - } - - public SamlServiceProviderDefinition getConfig() { - return config; - } - - public SamlServiceProvider setConfig(SamlServiceProviderDefinition config) { - - if (StringUtils.hasText(getEntityId())) { - config.setSpEntityId(getEntityId()); - } - if (StringUtils.hasText(getIdentityZoneId())) { - config.setZoneId(getIdentityZoneId()); - } - this.config = config; - return this; - } - - public String getEntityId() { - return entityId; - } - - public SamlServiceProvider setEntityId(String entityId) { - this.entityId = entityId; - if (config != null) { - config.setSpEntityId(entityId); - } - return this; - } - - public boolean isActive() { - return active; - } - - public SamlServiceProvider setActive(boolean active) { - this.active = active; - return this; - } - - public String getIdentityZoneId() { - return identityZoneId; - } - - public SamlServiceProvider setIdentityZoneId(String identityZoneId) { - this.identityZoneId = identityZoneId; - if (config != null) { - config.setZoneId(identityZoneId); - } - return this; - } - - public boolean configIsValid() { - // There may be need for this method in the fugure but for now it does nothing. - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((config == null) ? 0 : config.hashCode()); - result = prime * result + ((created == null) ? 0 : created.hashCode()); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((lastModified == null) ? 0 : lastModified.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((entityId == null) ? 0 : entityId.hashCode()); - result = prime * result + version; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SamlServiceProvider other = (SamlServiceProvider) obj; - if (config == null) { - if (other.config != null) - return false; - } else if (!config.equals(other.config)) - return false; - if (created == null) { - if (other.created != null) - return false; - } else if (!created.equals(other.created)) - return false; - if (id == null) { - if (other.id != null) - return false; - } else if (!id.equals(other.id)) - return false; - if (lastModified == null) { - if (other.lastModified != null) - return false; - } else if (!lastModified.equals(other.lastModified)) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (entityId == null) { - if (other.entityId != null) - return false; - } else if (!entityId.equals(other.entityId)) - return false; - if (version != other.version) - return false; - return true; - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("SamlServiceProvider{"); - sb.append("id='").append(id).append('\''); - sb.append(", entityId='").append(entityId).append('\''); - sb.append(", name='").append(name).append('\''); - sb.append(", active=").append(active); - sb.append('}'); - return sb.toString(); - } - - public static class SamlServiceProviderSerializer extends JsonSerializer { - @Override - public void serialize(SamlServiceProvider value, JsonGenerator gen, SerializerProvider serializers) - throws IOException, JsonProcessingException { - gen.writeStartObject(); - gen.writeStringField(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); - gen.writeStringField(FIELD_ID, value.getId()); - gen.writeStringField(FIELD_ENTITY_ID, value.getEntityId()); - gen.writeStringField(FIELD_NAME, value.getName()); - gen.writeNumberField(FIELD_VERSION, value.getVersion()); - writeDateField(FIELD_CREATED, value.getCreated(), gen); - writeDateField(FIELD_LAST_MODIFIED, value.getLastModified(), gen); - gen.writeBooleanField(FIELD_ACTIVE, value.isActive()); - gen.writeStringField(FIELD_IDENTITY_ZONE_ID, value.getIdentityZoneId()); - gen.writeEndObject(); - } - - public void writeDateField(String fieldName, Date value, JsonGenerator gen) throws IOException { - if (value != null) { - gen.writeNumberField(fieldName, value.getTime()); - } else { - gen.writeNullField(fieldName); - } - } - } - - public static class SamlServiceProviderDeserializer extends JsonDeserializer { - @Override - public SamlServiceProvider deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException { - SamlServiceProvider result = new SamlServiceProvider(); - // determine the type of IdentityProvider - JsonNode node = JsonUtils.readTree(jp); - // deserialize based on type - String config = getNodeAsString(node, FIELD_CONFIG, null); - SamlServiceProviderDefinition definition = null; - if (StringUtils.hasText(config)) { - definition = JsonUtils.readValue(config, SamlServiceProviderDefinition.class); - } - result.setConfig(definition); - - result.setId(getNodeAsString(node, FIELD_ID, null)); - result.setEntityId(getNodeAsString(node, FIELD_ENTITY_ID, null)); - result.setName(getNodeAsString(node, FIELD_NAME, null)); - result.setVersion(getNodeAsInt(node, FIELD_VERSION, 0)); - result.setCreated(getNodeAsDate(node, FIELD_CREATED)); - result.setLastModified(getNodeAsDate(node, FIELD_LAST_MODIFIED)); - result.setActive(getNodeAsBoolean(node, FIELD_ACTIVE, true)); - result.setIdentityZoneId(getNodeAsString(node, FIELD_IDENTITY_ZONE_ID, null)); - return result; - } - - protected String getNodeAsString(JsonNode node, String fieldName, String defaultValue) { - JsonNode typeNode = node.get(fieldName); - return typeNode == null ? defaultValue : typeNode.asText(defaultValue); - } - - protected int getNodeAsInt(JsonNode node, String fieldName, int defaultValue) { - JsonNode typeNode = node.get(fieldName); - return typeNode == null ? defaultValue : typeNode.asInt(defaultValue); - } - - protected boolean getNodeAsBoolean(JsonNode node, String fieldName, boolean defaultValue) { - JsonNode typeNode = node.get(fieldName); - return typeNode == null ? defaultValue : typeNode.asBoolean(defaultValue); - } - - protected Date getNodeAsDate(JsonNode node, String fieldName) { - JsonNode typeNode = node.get(fieldName); - long date = typeNode == null ? -1 : typeNode.asLong(-1); - if (date == -1) { - return null; - } else { - return new Date(date); - } - } - } - -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java index 0680e4101f2..c07bf5f5799 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java @@ -15,7 +15,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.event.ServiceProviderModifiedEvent; @@ -48,17 +47,15 @@ public void onApplicationEvent(ServiceProviderModifiedEvent event) { SamlServiceProvider changedSamlServiceProvider = (SamlServiceProvider) event.getSource(); IdentityZone zone = zoneProvisioning.retrieve(changedSamlServiceProvider.getIdentityZoneId()); ZoneAwareIdpMetadataManager.ExtensionMetadataManager manager = metadataManager.getManager(zone); - SamlServiceProviderDefinition definition = ObjectUtils.castInstance(changedSamlServiceProvider.getConfig(), - SamlServiceProviderDefinition.class); try { if (changedSamlServiceProvider.isActive()) { - ExtendedMetadataDelegate[] delegates = configurator.addSamlServiceProviderDefinition(definition); + ExtendedMetadataDelegate[] delegates = configurator.addSamlServiceProvider(changedSamlServiceProvider); if (delegates[1] != null) { manager.removeMetadataProvider(delegates[1]); } manager.addMetadataProvider(delegates[0]); } else { - ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProviderDefinition(definition); + ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProvider(changedSamlServiceProvider.getEntityId()); if (delegate != null) { manager.removeMetadataProvider(delegate); } @@ -69,7 +66,7 @@ public void onApplicationEvent(ServiceProviderModifiedEvent event) { manager.refreshMetadata(); metadataManager.getManager(zone).refreshMetadata(); } catch (MetadataProviderException e) { - logger.error("Unable to add new SAML service provider:" + definition, e); + logger.error("Unable to add new SAML service provider: " + changedSamlServiceProvider, e); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java index 4fa67ed43c2..6d5e2ffd59d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java @@ -12,14 +12,15 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Timer; @@ -27,12 +28,15 @@ import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.SimpleHttpConnectionManager; +import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.http.client.utils.URIBuilder; import org.cloudfoundry.identity.uaa.provider.saml.ConfigMetadataProvider; import org.cloudfoundry.identity.uaa.provider.saml.FixedHttpMetaDataProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.xml.parse.BasicParserPool; import org.springframework.security.saml.metadata.ExtendedMetadata; @@ -43,7 +47,8 @@ * Holds internal state of available SAML Service Providers. */ public class SamlServiceProviderConfigurator { - private Map serviceProviders = new HashMap<>(); + + private final Map> zoneServiceProviders = new HashMap<>(); private HttpClientParams clientParams; private BasicParserPool parserPool; @@ -94,175 +99,158 @@ public SamlServiceProviderConfigurator() { dummyTimer.cancel(); } - public List getSamlServiceProviderDefinitions() { - return Collections.unmodifiableList(new ArrayList<>(serviceProviders.keySet())); + public List getSamlServiceProviders() { + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone( + IdentityZoneHolder.get()); + return Collections.unmodifiableList(new ArrayList<>(serviceProviders.values())); } - public List getSamlServiceProviderDefinitionsForZone(IdentityZone zone) { - List result = new LinkedList<>(); - for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { - if (zone.getId().equals(def.getZoneId())) { - result.add(def); - } - } - return result; + public List getSamlServiceProvidersForZone(IdentityZone zone) { + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone(zone); + return Collections.unmodifiableList(new ArrayList<>(serviceProviders.values())); } - public List getSamlServiceProviderDefinitions(List allowedSps, - IdentityZone zone) { - List spsInTheZone = getSamlServiceProviderDefinitionsForZone(zone); - if (allowedSps != null) { - List result = new LinkedList<>(); - for (SamlServiceProviderDefinition def : spsInTheZone) { - if (allowedSps.contains(def.getSpEntityId())) { - result.add(def); + public Map getSamlServiceProviderMapForZone(IdentityZone zone) { + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone(zone); + return Collections.unmodifiableMap(serviceProviders); + } + + private Map getOrCreateSamlServiceProviderMapForZone(IdentityZone zone) { + Map serviceProviders = zoneServiceProviders.get(zone); + if (serviceProviders == null) { + synchronized (zoneServiceProviders) { + serviceProviders = zoneServiceProviders.get(zone); + if (serviceProviders == null) { + serviceProviders = new HashMap<>(); + zoneServiceProviders.put(IdentityZoneHolder.get(), serviceProviders); } } - return result; } - return spsInTheZone; - } - - protected String getUniqueAlias(SamlServiceProviderDefinition def) { - return def.getUniqueAlias(); + return serviceProviders; } /** * adds or replaces a SAML service provider * - * @param providerDefinition + * @param provider * - the provider to be added - * @return an array consisting of {provider-added, provider-deleted} where provider-deleted may be null + * @return an array consisting of {provider-added, provider-deleted} where + * provider-deleted may be null * @throws MetadataProviderException * if the system fails to fetch meta data for this provider */ - public synchronized ExtendedMetadataDelegate[] addSamlServiceProviderDefinition( - SamlServiceProviderDefinition providerDefinition) throws MetadataProviderException { - ExtendedMetadataDelegate added, deleted = null; - if (providerDefinition == null) { + public synchronized ExtendedMetadataDelegate[] addSamlServiceProvider(SamlServiceProvider provider) + throws MetadataProviderException { + + if (provider == null) { throw new NullPointerException(); } - if (!StringUtils.hasText(providerDefinition.getSpEntityId())) { + if (!StringUtils.hasText(provider.getEntityId())) { throw new NullPointerException("You must set the SAML SP Entity."); } - if (!StringUtils.hasText(providerDefinition.getZoneId())) { + if (!StringUtils.hasText(provider.getIdentityZoneId())) { throw new NullPointerException("You must set the SAML SP Identity Zone Id."); } - for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { - if (getUniqueAlias(providerDefinition).equals(getUniqueAlias(def))) { - deleted = serviceProviders.remove(def); - break; - } + if (!IdentityZoneHolder.get().getId().equals(provider.getIdentityZoneId())) { + throw new IllegalArgumentException("The SAML SP Identity Zone Id does not match the curent zone."); } - SamlServiceProviderDefinition clone = providerDefinition.clone(); - added = getExtendedMetadataDelegate(clone); - String entityIdToBeAdded = ((ConfigMetadataProvider) added.getDelegate()).getEntityID(); - boolean entityIDexists = false; - for (Map.Entry entry : serviceProviders.entrySet()) { - SamlServiceProviderDefinition definition = entry.getKey(); - if (clone.getZoneId().equals(definition.getZoneId())) { - ConfigMetadataProvider provider = (ConfigMetadataProvider) entry.getValue().getDelegate(); - if (entityIdToBeAdded.equals(provider.getEntityID())) { - entityIDexists = true; - break; - } - } - } - if (entityIDexists) { - throw new MetadataProviderException("Duplicate entity id:" + entityIdToBeAdded); + + ExtendedMetadataDelegate added = getExtendedMetadataDelegate(provider); + // Extract the entityId directly from the SAML metadata. + String metadataEntityId = ((ConfigMetadataProvider) added.getDelegate()).getEntityID(); + if (!provider.getEntityId().equals(metadataEntityId)) { + throw new MetadataProviderException( + "Metadata entity id does not match SAML SP entity id: " + provider.getEntityId()); } - serviceProviders.put(clone, added); - return new ExtendedMetadataDelegate[] { added, deleted }; - } + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone( + IdentityZoneHolder.get()); - public synchronized ExtendedMetadataDelegate removeSamlServiceProviderDefinition( - SamlServiceProviderDefinition providerDefinition) { - return serviceProviders.remove(providerDefinition); - } + ExtendedMetadataDelegate deleted = null; + if (serviceProviders.containsKey(provider.getEntityId())) { + deleted = serviceProviders.remove(provider.getEntityId()).getExtendedMetadataDelegate(); + } - public List getSamlServiceProviders() { - return getSamlServiceProviders(null); + SamlServiceProviderHolder holder = new SamlServiceProviderHolder(added, provider); + serviceProviders.put(provider.getEntityId(), holder); + return new ExtendedMetadataDelegate[] { added, deleted }; } - public List getSamlServiceProviders(IdentityZone zone) { - List result = new LinkedList<>(); - for (SamlServiceProviderDefinition def : getSamlServiceProviderDefinitions()) { - if (zone == null || zone.getId().equals(def.getZoneId())) { - ExtendedMetadataDelegate metadata = serviceProviders.get(def); - if (metadata != null) { - result.add(metadata); - } - } - } - return result; + public synchronized ExtendedMetadataDelegate removeSamlServiceProvider(String entityId) { + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone( + IdentityZoneHolder.get()); + return serviceProviders.remove(entityId).getExtendedMetadataDelegate(); } - public ExtendedMetadataDelegate getExtendedMetadataDelegateFromCache(SamlServiceProviderDefinition def) + public ExtendedMetadataDelegate getExtendedMetadataDelegateFromCache(String entityId) throws MetadataProviderException { - return serviceProviders.get(def); + Map serviceProviders = getOrCreateSamlServiceProviderMapForZone( + IdentityZoneHolder.get()); + return serviceProviders.get(entityId).getExtendedMetadataDelegate(); } - public ExtendedMetadataDelegate getExtendedMetadataDelegate(SamlServiceProviderDefinition def) + public ExtendedMetadataDelegate getExtendedMetadataDelegate(SamlServiceProvider provider) throws MetadataProviderException { ExtendedMetadataDelegate metadata; - switch (def.getType()) { + switch (provider.getConfig().getType()) { case DATA: { - metadata = configureXMLMetadata(def); + metadata = configureXMLMetadata(provider); break; } case URL: { - metadata = configureURLMetadata(def); + metadata = configureURLMetadata(provider); break; } default: { - throw new MetadataProviderException( - "Invalid metadata type for alias[" + def.getSpEntityId() + "]:" + def.getMetaDataLocation()); + throw new MetadataProviderException("Invalid metadata type for alias[" + provider.getEntityId() + "]:" + + provider.getConfig().getMetaDataLocation()); } } return metadata; } - protected ExtendedMetadataDelegate configureXMLMetadata(SamlServiceProviderDefinition def) { - ConfigMetadataProvider configMetadataProvider = new ConfigMetadataProvider(def.getZoneId(), def.getSpEntityId(), - def.getMetaDataLocation()); + protected ExtendedMetadataDelegate configureXMLMetadata(SamlServiceProvider provider) { + ConfigMetadataProvider configMetadataProvider = new ConfigMetadataProvider(provider.getIdentityZoneId(), + provider.getEntityId(), provider.getConfig().getMetaDataLocation()); configMetadataProvider.setParserPool(getParserPool()); ExtendedMetadata extendedMetadata = new ExtendedMetadata(); extendedMetadata.setLocal(false); - extendedMetadata.setAlias(def.getSpEntityId()); + extendedMetadata.setAlias(provider.getEntityId()); ExtendedMetadataDelegate delegate = new ExtendedMetadataDelegate(configMetadataProvider, extendedMetadata); - delegate.setMetadataTrustCheck(def.isMetadataTrustCheck()); + delegate.setMetadataTrustCheck(provider.getConfig().isMetadataTrustCheck()); return delegate; } - @SuppressWarnings("unchecked") - protected ExtendedMetadataDelegate configureURLMetadata(SamlServiceProviderDefinition def) + protected ExtendedMetadataDelegate configureURLMetadata(SamlServiceProvider provider) throws MetadataProviderException { - Class socketFactory = null; + ProtocolSocketFactory socketFactory = null; + SamlServiceProviderDefinition def = provider.getConfig().clone(); + if (def.getMetaDataLocation().startsWith("https")) { + try { + socketFactory = new EasySSLProtocolSocketFactory(); + } catch (GeneralSecurityException | IOException e) { + throw new MetadataProviderException("Error instantiating SSL/TLS socket factory.", e); + } + } else { + socketFactory = new DefaultProtocolSocketFactory(); + } + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setAlias(provider.getEntityId()); + SimpleHttpConnectionManager connectionManager = new SimpleHttpConnectionManager(true); + connectionManager.getParams().setDefaults(getClientParams()); + HttpClient client = new HttpClient(connectionManager); + FixedHttpMetaDataProvider fixedHttpMetaDataProvider; try { - def = def.clone(); - socketFactory = (Class) Class.forName(def.getSocketFactoryClassName()); - ExtendedMetadata extendedMetadata = new ExtendedMetadata(); - extendedMetadata.setAlias(def.getSpEntityId()); - SimpleHttpConnectionManager connectionManager = new SimpleHttpConnectionManager(true); - connectionManager.getParams().setDefaults(getClientParams()); - HttpClient client = new HttpClient(connectionManager); - FixedHttpMetaDataProvider fixedHttpMetaDataProvider = new FixedHttpMetaDataProvider(dummyTimer, client, + fixedHttpMetaDataProvider = new FixedHttpMetaDataProvider(dummyTimer, client, adjustURIForPort(def.getMetaDataLocation())); - fixedHttpMetaDataProvider.setSocketFactory(socketFactory.newInstance()); - byte[] metadata = fixedHttpMetaDataProvider.fetchMetadata(); - def.setMetaDataLocation(new String(metadata, StandardCharsets.UTF_8)); - return configureXMLMetadata(def); } catch (URISyntaxException e) { - throw new MetadataProviderException("Invalid socket factory(invalid URI):" + def.getMetaDataLocation(), e); - } catch (ClassNotFoundException e) { - throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); - } catch (InstantiationException e) { - throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); - } catch (IllegalAccessException e) { - throw new MetadataProviderException("Invalid socket factory:" + def.getSocketFactoryClassName(), e); + throw new MetadataProviderException("Invalid metadata URI: " + def.getMetaDataLocation(), e); } + fixedHttpMetaDataProvider.setSocketFactory(socketFactory); + byte[] metadata = fixedHttpMetaDataProvider.fetchMetadata(); + def.setMetaDataLocation(new String(metadata, StandardCharsets.UTF_8)); + return configureXMLMetadata(provider); } protected String adjustURIForPort(String uri) throws URISyntaxException { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java deleted file mode 100644 index 13fb302aced..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinition.java +++ /dev/null @@ -1,294 +0,0 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] 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.provider.saml.idp; - -import java.io.IOException; -import java.io.StringReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Objects; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -public class SamlServiceProviderDefinition { - - public static final String DEFAULT_HTTP_SOCKET_FACTORY = "org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory"; - public static final String DEFAULT_HTTPS_SOCKET_FACTORY = "org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory"; - - public enum MetadataLocation { - URL, - DATA, - UNKNOWN - } - - private String metaDataLocation; - private String spEntityId; - private String zoneId; - private String nameID; - private int singleSignOnServiceIndex; - private boolean metadataTrustCheck; - private String socketFactoryClassName; - - public SamlServiceProviderDefinition clone() { - return new SamlServiceProviderDefinition(metaDataLocation, - spEntityId, - nameID, - singleSignOnServiceIndex, - metadataTrustCheck, - zoneId); - } - - public SamlServiceProviderDefinition() {} - - public SamlServiceProviderDefinition(String metaDataLocation, - String spEntityAlias, - String nameID, - int singleSignOnServiceIndex, - boolean metadataTrustCheck, - String zoneId) { - this.metaDataLocation = metaDataLocation; - this.spEntityId = spEntityAlias; - this.nameID = nameID; - this.singleSignOnServiceIndex = singleSignOnServiceIndex; - this.metadataTrustCheck = metadataTrustCheck; - this.zoneId = zoneId; - } - - @JsonIgnore - public MetadataLocation getType() { - String trimmedLocation = metaDataLocation.trim(); - if (trimmedLocation.startsWith("0) { - return socketFactoryClassName; - } - if (getMetaDataLocation()==null || getMetaDataLocation().trim().length()==0) { - throw new IllegalStateException("Invalid meta data URL[" + getMetaDataLocation() + "] cannot determine socket factory."); - } - if (getMetaDataLocation().startsWith("https")) { - return DEFAULT_HTTPS_SOCKET_FACTORY; - } else { - return DEFAULT_HTTP_SOCKET_FACTORY; - } - } - - public void setSocketFactoryClassName(String socketFactoryClassName) { - this.socketFactoryClassName = socketFactoryClassName; - if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { - try { - Class.forName( - socketFactoryClassName, - true, - Thread.currentThread().getContextClassLoader() - ); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - } - } - - public String getZoneId() { - return zoneId; - } - - public void setZoneId(String zoneId) { - this.zoneId = zoneId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SamlServiceProviderDefinition that = (SamlServiceProviderDefinition) o; - - return Objects.equals(getUniqueAlias(), that.getUniqueAlias()); - } - - @Override - public int hashCode() { - String alias = getUniqueAlias(); - return alias==null ? 0 : alias.hashCode(); - } - - @JsonIgnore - protected String getUniqueAlias() { - return getSpEntityId()+"###"+getZoneId(); - } - - @Override - public String toString() { - return "SamlServiceProviderDefinition{" + - "spEntityAlias='" + spEntityId + '\'' + - ", metaDataLocation='" + metaDataLocation + '\'' + - ", nameID='" + nameID + '\'' + - ", singleSignOnServiceIndex=" + singleSignOnServiceIndex + - ", metadataTrustCheck=" + metadataTrustCheck + - ", socketFactoryClassName='" + socketFactoryClassName + '\'' + - ", zoneId='" + zoneId + '\'' + - '}'; - } - - public static class Builder { - - private String metaDataLocation; - private String spEntityId; - private String zoneId; - private String nameID; - private int singleSignOnServiceIndex; - private boolean metadataTrustCheck; - private String socketFactoryClassName; - - private Builder(){} - - public static Builder get() { - return new Builder(); - } - - public SamlServiceProviderDefinition build() { - SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); - - def.setMetaDataLocation(metaDataLocation); - def.setSpEntityId(spEntityId); - def.setZoneId(zoneId); - def.setNameID(nameID); - def.setSingleSignOnServiceIndex(singleSignOnServiceIndex); - def.setMetadataTrustCheck(metadataTrustCheck); - def.setSocketFactoryClassName(socketFactoryClassName); - return def; - } - - public Builder setMetaDataLocation(String metaDataLocation) { - this.metaDataLocation = metaDataLocation; - return this; - } - - public Builder setSpEntityId(String spEntityId) { - this.spEntityId = spEntityId; - return this; - } - - public Builder setZoneId(String zoneId) { - this.zoneId = zoneId; - return this; - } - - public Builder setNameID(String nameID) { - this.nameID = nameID; - return this; - } - - public Builder setSingleSignOnServiceIndex(int singleSignOnServiceIndex) { - this.singleSignOnServiceIndex = singleSignOnServiceIndex; - return this; - } - - public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { - this.metadataTrustCheck = metadataTrustCheck; - return this; - } - - public Builder setSocketFactoryClassName(String socketFactoryClassName) { - this.socketFactoryClassName = socketFactoryClassName; - return this; - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java new file mode 100644 index 00000000000..98044d1b3a5 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java @@ -0,0 +1,56 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; + +public class SamlServiceProviderHolder { + + private final ExtendedMetadataDelegate extendedMetadataDelegate; + private final SamlServiceProvider samlServiceProvider; + + public SamlServiceProviderHolder(ExtendedMetadataDelegate extendedMetadataDelegate, + SamlServiceProvider samlServiceProvider) { + + this.extendedMetadataDelegate = extendedMetadataDelegate; + this.samlServiceProvider = samlServiceProvider; + } + + public ExtendedMetadataDelegate getExtendedMetadataDelegate() { + return extendedMetadataDelegate; + } + + public SamlServiceProvider getSamlServiceProvider() { + return samlServiceProvider; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((samlServiceProvider.getIdentityZoneId() == null) ? 0 : samlServiceProvider.getIdentityZoneId().hashCode()); + result = prime * result + ((samlServiceProvider.getEntityId() == null) ? 0 : samlServiceProvider.getEntityId().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SamlServiceProviderHolder other = (SamlServiceProviderHolder) obj; + if (samlServiceProvider.getIdentityZoneId() == null) { + if (other.samlServiceProvider.getIdentityZoneId() != null) + return false; + } else if (!samlServiceProvider.getIdentityZoneId().equals(other.samlServiceProvider.getIdentityZoneId())) + return false; + if (samlServiceProvider.getEntityId() == null) { + if (other.samlServiceProvider.getEntityId() != null) + return false; + } else if (!samlServiceProvider.getEntityId().equals(other.samlServiceProvider.getEntityId())) + return false; + return true; + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java index 91a5e5ad61b..e21294c91f0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java @@ -15,8 +15,8 @@ package org.cloudfoundry.identity.uaa.provider.saml.idp; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -123,38 +123,37 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi for (IdentityZone zone : zoneDao.retrieveAll()) { ExtensionMetadataManager manager = getManager(zone); boolean hasChanges = false; - @SuppressWarnings({ "unchecked", "rawtypes" }) - List zoneDefinitions = new LinkedList( - configurator.getSamlServiceProviderDefinitionsForZone(zone)); + Map zoneProviderMap = + new HashMap(configurator.getSamlServiceProviderMapForZone(zone)); for (SamlServiceProvider provider : providerDao.retrieveAll(false, zone.getId())) { - zoneDefinitions.remove(provider.getConfig()); + zoneProviderMap.remove(provider.getEntityId()); if (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime()) { try { - SamlServiceProviderDefinition definition = (SamlServiceProviderDefinition) provider.getConfig(); try { if (provider.isActive()) { log.info("Adding SAML SP zone[" + zone.getId() + "] entityId[" - + definition.getSpEntityId() + "]"); + + provider.getEntityId() + "]"); ExtendedMetadataDelegate[] delegates = configurator - .addSamlServiceProviderDefinition(definition); + .addSamlServiceProvider(provider); if (delegates[1] != null) { manager.removeMetadataProvider(delegates[1]); } manager.addMetadataProvider(delegates[0]); } else { - removeSamlServiceProvider(zone, manager, definition); + removeSamlServiceProvider(zone, manager, provider); } hasChanges = true; } catch (MetadataProviderException e) { - logger.error("Unable to refresh SAML Service Provider:" + definition, e); + logger.error("Unable to refresh SAML Service Provider: " + provider, e); } } catch (JsonUtils.JsonUtilException x) { logger.error("Unable to load SAML Service Provider:" + provider, x); } } } - for (SamlServiceProviderDefinition definition : zoneDefinitions) { - removeSamlServiceProvider(zone, manager, definition); + // Remove anything that we did not find in persistent storage. + for (SamlServiceProviderHolder holder : zoneProviderMap.values()) { + removeSamlServiceProvider(zone, manager, holder.getSamlServiceProvider()); hasChanges = true; } if (hasChanges) { @@ -165,9 +164,9 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi } protected void removeSamlServiceProvider(IdentityZone zone, ExtensionMetadataManager manager, - SamlServiceProviderDefinition definition) { - log.info("Removing SAML SP zone[" + zone.getId() + "] entityId[" + definition.getSpEntityId() + "]"); - ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProviderDefinition(definition); + SamlServiceProvider provider) { + log.info("Removing SAML SP zone[" + zone.getId() + "] entityId[" + provider.getEntityId() + "]"); + ExtendedMetadataDelegate delegate = configurator.removeSamlServiceProvider(provider.getEntityId()); if (delegate != null) { manager.removeMetadataProvider(delegate); } diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql deleted file mode 100644 index 58e15fdd8b0..00000000000 --- a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_6__SAML_SP_Management.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE service_provider ( - id CHAR(36) NOT NULL PRIMARY KEY, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - version BIGINT DEFAULT 0 NOT NULL, - identity_zone_id varchar(36) NOT NULL, - name varchar(255) NOT NULL, - entity_id varchar(36) NOT NULL, - config LONGVARCHAR, - active BOOLEAN DEFAULT TRUE NOT NULL -); - -CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql deleted file mode 100644 index 5ca5ce697ab..00000000000 --- a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_6__SAML_SP_Management.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE `service_provider` ( - `id` VARCHAR(36) NOT NULL, - `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - `version` BIGINT DEFAULT 0 NOT NULL, - `identity_zone_id` VARCHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `entity_id` VARCHAR(36) NOT NULL, - `config` LONGTEXT, - `active` BOOLEAN DEFAULT TRUE NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `entity_in_zone` (`identity_zone_id`, `entity_id`) -); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql deleted file mode 100644 index 9e225eeeff0..00000000000 --- a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_6__SAML_SP_Management.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE service_provider ( - id VARCHAR(36) NOT NULL PRIMARY KEY, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - version BIGINT DEFAULT 0, - identity_zone_id VARCHAR(36) NOT NULL, - name VARCHAR(255) NOT NULL, - entity_id VARCHAR(36) NOT NULL, - config TEXT, - active BOOLEAN DEFAULT TRUE NOT NULL -); - -CREATE UNIQUE INDEX entity_in_zone ON service_provider (identity_zone_id,entity_id); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java index 77a27a09dd7..315dc7169b3 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfiguratorTest.java @@ -1,12 +1,18 @@ package org.cloudfoundry.identity.uaa.provider.saml.idp; +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.MOCK_SP_ENTITY_ID; +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.mockSamlServiceProvider; +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.mockSamlServiceProviderWithoutXmlHeaderInMetadata; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; import static org.junit.Assert.fail; +import java.util.Map; import java.util.Timer; +import java.util.UUID; import org.cloudfoundry.identity.uaa.provider.saml.ComparableProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; import org.opensaml.saml2.metadata.provider.MetadataProviderException; @@ -15,86 +21,115 @@ public class SamlServiceProviderConfiguratorTest { - private static final String SINGLE_ADD_ENTITY_ID = "cloudfoundry-saml-login"; private final SamlTestUtils samlTestUtils = new SamlTestUtils(); private SamlServiceProviderConfigurator conf = null; - private SamlServiceProviderDefinition singleAdd = null; - private SamlServiceProviderDefinition singleAddWithoutHeader = null; @Before public void setup() throws Exception { samlTestUtils.initalize(); conf = new SamlServiceProviderConfigurator(); conf.setParserPool(new BasicParserPool()); - singleAdd = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_ID, - new RandomValueStringGenerator().generate())) - .setSpEntityId(SINGLE_ADD_ENTITY_ID).setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(true).setZoneId("uaa").build(); - singleAddWithoutHeader = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_HEADER, - new RandomValueStringGenerator().generate())) - .setSpEntityId(SINGLE_ADD_ENTITY_ID).setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(true).setZoneId("uaa").build(); } @Test - public void testCloneSamlServiceProviderDefinition() throws Exception { - SamlServiceProviderDefinition clone = singleAdd.clone(); - assertEquals(singleAdd, clone); - assertNotSame(singleAdd, clone); - } + public void testAddAndUpdateAndRemoveSamlServiceProvider() throws Exception { + SamlServiceProvider sp = mockSamlServiceProvider(); + SamlServiceProvider spNoHeader = mockSamlServiceProviderWithoutXmlHeaderInMetadata(); - @Test - public void testAddAndUpdateAndRemoveSamlServiceProviderDefinition() throws Exception { - conf.addSamlServiceProviderDefinition(singleAdd); + conf.addSamlServiceProvider(sp); assertEquals(1, conf.getSamlServiceProviders().size()); - conf.addSamlServiceProviderDefinition(singleAddWithoutHeader); + conf.addSamlServiceProvider(spNoHeader); assertEquals(1, conf.getSamlServiceProviders().size()); - conf.removeSamlServiceProviderDefinition(singleAdd); + conf.removeSamlServiceProvider(sp.getEntityId()); assertEquals(0, conf.getSamlServiceProviders().size()); } + @Test(expected = IllegalArgumentException.class) + public void testAddSamlServiceProviderToWrongZone() throws Exception { + SamlServiceProvider sp = mockSamlServiceProvider(); + sp.setIdentityZoneId(UUID.randomUUID().toString()); + conf.addSamlServiceProvider(sp); + } + + @Test + public void testGetSamlServiceProviderMapForZone() throws Exception { + try { + String zoneId = UUID.randomUUID().toString(); + SamlServiceProvider sp = mockSamlServiceProvider(); + sp.setIdentityZoneId(zoneId); + IdentityZoneHolder.set(new IdentityZone().setId(zoneId)); + conf.addSamlServiceProvider(sp); + + String unwantedZoneId = UUID.randomUUID().toString(); + SamlServiceProvider unwantedSp = mockSamlServiceProvider(); + unwantedSp.setIdentityZoneId(unwantedZoneId); + IdentityZoneHolder.set(new IdentityZone().setId(unwantedZoneId)); + conf.addSamlServiceProvider(unwantedSp); + + IdentityZone zone = new IdentityZone().setId(zoneId); + Map spMap = conf.getSamlServiceProviderMapForZone(zone); + assertEquals(1, spMap.entrySet().size()); + assertEquals(sp, spMap.get(sp.getEntityId()).getSamlServiceProvider()); + } + finally { + IdentityZoneHolder.set(IdentityZone.getUaa()); + } + } + @Test(expected = MetadataProviderException.class) - public void testAddSamlServiceProviderDefinitionWithConflictingEntityId() throws Exception { - conf.addSamlServiceProviderDefinition(singleAdd); - SamlServiceProviderDefinition duplicate = SamlServiceProviderDefinition.Builder.get() + public void testAddSamlServiceProviderWithConflictingEntityId() throws Exception { + SamlServiceProvider sp = mockSamlServiceProvider(); + + conf.addSamlServiceProvider(sp); + SamlServiceProviderDefinition duplicateDef = SamlServiceProviderDefinition.Builder.get() .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_ID, new RandomValueStringGenerator().generate())) - .setSpEntityId(SINGLE_ADD_ENTITY_ID + "_2").setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(true).setZoneId("uaa").build(); - conf.addSamlServiceProviderDefinition(duplicate); + .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).build(); + SamlServiceProvider duplicate = new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID + "_2").setIdentityZoneId("uaa") + .setConfig(duplicateDef); + conf.addSamlServiceProvider(duplicate); } @Test(expected = NullPointerException.class) public void testAddNullSamlServiceProvider() throws Exception { - conf.addSamlServiceProviderDefinition(null); + conf.addSamlServiceProvider(null); } @Test(expected = NullPointerException.class) public void testAddSamlServiceProviderWithNullEntityId() throws Exception { - singleAdd.setSpEntityId(null); - conf.addSamlServiceProviderDefinition(singleAdd); + SamlServiceProvider sp = mockSamlServiceProvider(); + sp.setEntityId(null); + conf.addSamlServiceProvider(sp); + } + + @Test(expected = NullPointerException.class) + public void testAddSamlServiceProviderWithNullIdentityZoneId() throws Exception { + SamlServiceProvider sp = mockSamlServiceProvider(); + sp.setIdentityZoneId(null); + conf.addSamlServiceProvider(sp); } @Test - public void testGetEntityID() throws Exception { + public void testGetEntityId() throws Exception { Timer t = new Timer(); - conf.addSamlServiceProviderDefinition(singleAdd); - for (SamlServiceProviderDefinition def : conf.getSamlServiceProviderDefinitions()) { - switch (def.getSpEntityId()) { + conf.addSamlServiceProvider(mockSamlServiceProvider()); + for (SamlServiceProviderHolder holder : conf.getSamlServiceProviders()) { + SamlServiceProvider provider = holder.getSamlServiceProvider(); + switch (provider.getEntityId()) { case "cloudfoundry-saml-login": { - ComparableProvider provider = (ComparableProvider) conf.getExtendedMetadataDelegateFromCache(def) + ComparableProvider compProvider = (ComparableProvider) conf.getExtendedMetadataDelegateFromCache(provider.getEntityId()) .getDelegate(); - assertEquals("cloudfoundry-saml-login", provider.getEntityID()); + assertEquals("cloudfoundry-saml-login", compProvider.getEntityID()); break; } default: - fail(String.format("Unknown provider %s", def.getSpEntityId())); + fail(String.format("Unknown provider %s", provider.getEntityId())); } } t.cancel(); } + } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java index bd2e209beb3..13e38ace98c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDefinitionTest.java @@ -4,9 +4,7 @@ import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition.MetadataLocation.UNKNOWN; import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition.MetadataLocation.URL; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import org.apache.commons.httpclient.contrib.ssl.StrictSSLProtocolSocketFactory; import org.junit.Before; import org.junit.Test; @@ -18,10 +16,8 @@ public class SamlServiceProviderDefinitionTest { public void createDefinition() { definition = SamlServiceProviderDefinition.Builder.get() .setMetaDataLocation("location") - .setSpEntityId("alias") .setNameID("nameID") .setMetadataTrustCheck(true) - .setZoneId("zoneId") .build(); } @@ -67,24 +63,4 @@ public void testGetDataTypeWhenValid() throws Exception { assertEquals(DATA, definition.getType()); } - @Test - public void testGetSocketFactoryClassName() throws Exception { - SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); - def.setMetaDataLocation("https://dadas.dadas.dadas/sdada"); - assertEquals("org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory", def.getSocketFactoryClassName()); - def.setMetaDataLocation("http://dadas.dadas.dadas/sdada"); - assertEquals("org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory", def.getSocketFactoryClassName()); - def.setSocketFactoryClassName(""); - assertEquals("org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory", def.getSocketFactoryClassName()); - def.setSocketFactoryClassName(null); - assertEquals("org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory", def.getSocketFactoryClassName()); - try { - def.setSocketFactoryClassName("test.class.that.DoesntExist"); - fail("ClassNotFound is expected here"); - } catch (IllegalArgumentException x) { - assertEquals(ClassNotFoundException.class, x.getCause().getClass()); - } - def.setSocketFactoryClassName(StrictSSLProtocolSocketFactory.class.getName()); - assertEquals(StrictSSLProtocolSocketFactory.class.getName(), def.getSocketFactoryClassName()); - } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index 2c802ead1dd..4d4204b9c61 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -28,6 +28,7 @@ import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObjectBuilderFactory; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; import org.springframework.security.saml.metadata.ExtendedMetadata; @@ -408,6 +409,98 @@ public UaaAuthentication mockUaaAuthentication(String id) { + "" + ""; + public static final String UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" + + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" + + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" + + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" + + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" + + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" + + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" + + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" + + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" + + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" + + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" + + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" + + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" + + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" + + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + + "" + + "" + + "" + + "" + + "" + + "" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" + + "" + + "" + + "" + + ""; + public static final String UNSIGNED_SAML_SP_METADATA_WITHOUT_HEADER = UNSIGNED_SAML_SP_METADATA_WITHOUT_ID.replace("", ""); + public static final String MOCK_SP_ENTITY_ID = "cloudfoundry-saml-login"; + + public static SamlServiceProvider mockSamlServiceProvider() { + SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_ID, + new RandomValueStringGenerator().generate())) + .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).build(); + return new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID).setIdentityZoneId("uaa") + .setConfig(singleAddDef); + } + + public static SamlServiceProvider mockSamlServiceProvider(String entityId) { + SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID, + new RandomValueStringGenerator().generate(), entityId)) + .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).build(); + return new SamlServiceProvider().setEntityId(entityId).setIdentityZoneId("uaa") + .setConfig(singleAddDef); + } + + public static SamlServiceProvider mockSamlServiceProviderWithoutXmlHeaderInMetadata() { + SamlServiceProviderDefinition singleAddWithoutHeaderDef = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_WITHOUT_HEADER, + new RandomValueStringGenerator().generate())) + .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) + .setMetadataTrustCheck(true).build(); + return new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID).setIdentityZoneId("uaa") + .setConfig(singleAddWithoutHeaderDef); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManagerTest.java new file mode 100644 index 00000000000..b1c7b1046ab --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManagerTest.java @@ -0,0 +1,94 @@ +package org.cloudfoundry.identity.uaa.provider.saml.idp; + +import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.mockSamlServiceProvider; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.junit.Before; +import org.junit.Test; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.xml.parse.BasicParserPool; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; + +public class ZoneAwareIdpMetadataManagerTest { + + private final SamlTestUtils samlTestUtils = new SamlTestUtils(); + private SamlServiceProviderConfigurator configurator; + private ZoneAwareIdpMetadataManager metadataManager; + SamlServiceProviderProvisioning providerDao; + IdentityZoneProvisioning zoneDao; + KeyManager keyManager; + + @Before + public void setup() throws Exception { + samlTestUtils.initalize(); + configurator = new SamlServiceProviderConfigurator(); + configurator.setParserPool(new BasicParserPool()); + providerDao = mock(SamlServiceProviderProvisioning.class); + zoneDao = mock(IdentityZoneProvisioning.class); + metadataManager = new ZoneAwareIdpMetadataManager(providerDao, zoneDao, configurator, keyManager); + } + + @Test + public void testRefreshAllProviders() throws Exception { + configurator.addSamlServiceProvider(mockSamlServiceProvider()); + when(providerDao.retrieveAll(false, IdentityZone.getUaa().getId())) + .thenReturn(Arrays.asList(new SamlServiceProvider[] { mockSamlServiceProvider() })); + when(zoneDao.retrieveAll()).thenReturn(Arrays.asList(new IdentityZone[] { IdentityZone.getUaa() })); + this.metadataManager.refreshAllProviders(); + + assertEquals(1, configurator.getSamlServiceProvidersForZone(IdentityZoneHolder.get()).size()); + assertEquals(1, this.metadataManager.getManager(IdentityZoneHolder.get()).getAvailableProviders().size()); + + SamlServiceProvider confProvider = configurator.getSamlServiceProvidersForZone(IdentityZoneHolder.get()).get(0) + .getSamlServiceProvider(); + ExtendedMetadataDelegate metadataProvider = this.metadataManager.getManager(IdentityZoneHolder.get()) + .getAvailableProviders().get(0); + metadataProvider.initialize(); + EntityDescriptor entity = metadataProvider.getEntityDescriptor(confProvider.getEntityId()); + assertNotNull(entity); + assertEquals(confProvider.getEntityId(), entity.getEntityID()); + } + + @Test + public void testRefreshAllProvidersRemovesNonPersistedProvidersInConfigurator() throws Exception { + configurator.addSamlServiceProvider(mockSamlServiceProvider()); + configurator.addSamlServiceProvider(mockSamlServiceProvider("non-persisted-saml-sp")); + when(providerDao.retrieveAll(false, IdentityZone.getUaa().getId())) + .thenReturn(Arrays.asList(new SamlServiceProvider[] { mockSamlServiceProvider() })); + when(zoneDao.retrieveAll()).thenReturn(Arrays.asList(new IdentityZone[] { IdentityZone.getUaa() })); + this.metadataManager.refreshAllProviders(); + + assertEquals(1, configurator.getSamlServiceProvidersForZone(IdentityZoneHolder.get()).size()); + assertEquals(1, this.metadataManager.getManager(IdentityZoneHolder.get()).getAvailableProviders().size()); + + SamlServiceProvider confProvider = configurator.getSamlServiceProvidersForZone(IdentityZoneHolder.get()).get(0) + .getSamlServiceProvider(); + ExtendedMetadataDelegate metadataProvider = this.metadataManager.getManager(IdentityZoneHolder.get()) + .getAvailableProviders().get(0); + metadataProvider.initialize(); + EntityDescriptor entity = metadataProvider.getEntityDescriptor(confProvider.getEntityId()); + assertNotNull(entity); + assertEquals(confProvider.getEntityId(), entity.getEntityID()); + } + + @Test + public void testRefreshAllProvidersRemovesInactiveProvidersInConfigurator() throws Exception { + configurator.addSamlServiceProvider(mockSamlServiceProvider()); + when(providerDao.retrieveAll(false, IdentityZone.getUaa().getId())) + .thenReturn(Arrays.asList(new SamlServiceProvider[] { mockSamlServiceProvider().setActive(false) })); + when(zoneDao.retrieveAll()).thenReturn(Arrays.asList(new IdentityZone[] { IdentityZone.getUaa() })); + this.metadataManager.refreshAllProviders(); + + assertEquals(0, configurator.getSamlServiceProvidersForZone(IdentityZoneHolder.get()).size()); + assertEquals(0, this.metadataManager.getManager(IdentityZoneHolder.get()).getAvailableProviders().size()); + } +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java index 8746dc98b97..bbd264624f2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginWithLocalIdpIT.java @@ -163,7 +163,7 @@ public static SamlIdentityProviderDefinition createLocalSamlIdpDefinition(String @Test public void testCreateSamlSp() throws Exception { SamlServiceProviderDefinition spDef = createLocalSamlSpDefinition("cloudfoundry-saml-login", "uaa"); - createSamlServiceProvider("Local SAML SP", "unit-test-sp", baseUrl, serverRunning, spDef); + createSamlServiceProvider("Local SAML SP", "cloudfoundry-saml-login", baseUrl, serverRunning, spDef); } public static SamlServiceProviderDefinition createLocalSamlSpDefinition(String alias, String zoneId) { @@ -184,12 +184,6 @@ public static SamlServiceProviderDefinition createLocalSamlSpDefinition(String a String spMetaData = metadataResponse.getBody(); SamlServiceProviderDefinition def = new SamlServiceProviderDefinition(); - def.setZoneId(zoneId); - if (StringUtils.isNotEmpty(zoneId) && !zoneId.equals("uaa")) { - def.setSpEntityId(zoneId + "." + alias); - } else { - def.setSpEntityId(alias); - } def.setMetaDataLocation(spMetaData); def.setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); def.setSingleSignOnServiceIndex(0); @@ -223,7 +217,7 @@ public static SamlServiceProvider createSamlServiceProvider(String name, String provider.setConfig(samlServiceProviderDefinition); provider.setIdentityZoneId(OriginKeys.UAA); provider.setActive(true); - provider.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + provider.setEntityId(entityId); provider.setName(name); provider = createOrUpdateSamlServiceProvider(zoneAdminToken, baseUrl, provider); assertNotNull(provider.getId()); @@ -365,7 +359,7 @@ public void testLocalSamlIdpLoginInTestZone1Works() throws Exception { sp.setIdentityZoneId(zoneId); sp.setActive(true); sp.setConfig(samlServiceProviderDefinition); - sp.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + sp.setEntityId("testzone1.cloudfoundry-saml-login"); sp.setName("Local SAML SP for testzone1"); sp = createOrUpdateSamlServiceProvider(zoneAdminToken, baseUrl, sp); @@ -472,7 +466,7 @@ public void testCrossZoneSamlIntegration() throws Exception { sp.setIdentityZoneId(idpZoneId); sp.setActive(true); sp.setConfig(samlServiceProviderDefinition); - sp.setEntityId(samlServiceProviderDefinition.getSpEntityId()); + sp.setEntityId("testzone2.cloudfoundry-saml-login"); sp.setName("Local SAML SP for testzone2"); sp = createOrUpdateSamlServiceProvider(idpZoneAdminToken, baseUrl, sp); From c762180999456b631738768c53ad8e84ba4e1c6e Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 17 Feb 2016 10:29:45 -0700 Subject: [PATCH 57/87] Fix contextProvider invalid reference --- uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index 55d269cfc84..5cbd0ad47a4 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -158,7 +158,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + From 2803c2b6687cf1bafb88dc41e93293b63503082d Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 17 Feb 2016 10:59:55 -0700 Subject: [PATCH 58/87] simplify code --- .../identity/uaa/integration/feature/SamlLoginIT.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 88eb42fbbea..d94826395aa 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -311,10 +311,12 @@ public void testSingleLogoutWithLogoutRedirect() throws Exception { RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); + + IdentityZoneConfiguration config = new IdentityZoneConfiguration(); + config.getLinks().getLogout().setDisableRedirectParameter(false); //create the zone - IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config -> { - config.getLinks().getLogout().setDisableRedirectParameter(false); - }); + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); + //create a zone admin user String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; From ea5ab6a7f2f67aae960ca138a58ea5590f6010f8 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 17 Feb 2016 11:15:24 -0700 Subject: [PATCH 59/87] Add default zone configuration --- .../identity/uaa/integration/util/IntegrationTestUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index acd186045c5..d67b17f58b3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -436,7 +436,7 @@ public static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, client.put(url + "/identity-zones/{id}", existing, id); return existing; } - IdentityZone identityZone = fixtureIdentityZone(id, subdomain); + IdentityZone identityZone = fixtureIdentityZone(id, subdomain, new IdentityZoneConfiguration()); configureZone.accept(identityZone.getConfig()); ResponseEntity zone = client.postForEntity(url + "/identity-zones", identityZone, IdentityZone.class); From 5fe438ac46f14999ab76d2987aeac844bbe6b5ab Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 17 Feb 2016 12:20:51 -0700 Subject: [PATCH 60/87] Ignore attribute that is not supported for serialization --- .../identity/uaa/authentication/UaaAuthentication.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index 8b3afc42406..9d1326f2902 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.security.core.Authentication; @@ -47,6 +48,7 @@ public class UaaAuthentication implements Authentication, Serializable { private Map> userAttributes; //This is used when UAA acts as a SAML IdP + @JsonIgnore private SAMLMessageContext samlMessageContext; /** @@ -199,10 +201,12 @@ public void setUserAttributes(MultiValueMap userAttributes) { } } + @JsonIgnore public SAMLMessageContext getSamlMessageContext() { return samlMessageContext; } + @JsonIgnore public void setSamlMessageContext(SAMLMessageContext samlMessageContext) { this.samlMessageContext = samlMessageContext; } From 5fee87de7ceddc996f642cbcd56b6900fe4b640c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 17 Feb 2016 12:57:48 -0700 Subject: [PATCH 61/87] remove no op filters --- server/src/main/resources/login-ui.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index f656809172e..83362de6589 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -296,15 +296,6 @@ - - - - - - - - - From 4205992f4ae784c007e3f5260754372edc7581e1 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 18 Feb 2016 09:52:36 -0700 Subject: [PATCH 62/87] copyright notice update remove unmappable characters --- .../provider/IdentityProviderEndpoints.java | 23 ++++--- .../IdentityProviderProvisioning.java | 23 ++++--- .../SamlServiceProviderEndpoints.java | 69 ++++++++----------- .../provider/ldap/ProcessLdapProperties.java | 2 +- .../uaa/provider/saml/ComparableProvider.java | 2 +- .../MetadataProviderNotFoundException.java | 2 +- .../saml/ProviderChangedListener.java | 2 +- .../uaa/provider/saml/SamlRedirectUtils.java | 2 +- .../saml/ZoneAwareMetadataGenerator.java | 3 +- .../saml/ZoneAwareMetadataManager.java | 27 ++++---- .../saml/idp/IdpExtendedMetadata.java | 17 +++-- .../saml/idp/IdpMetadataGenerator.java | 50 +++++++++----- .../saml/idp/IdpMetadataGeneratorFilter.java | 43 ++++++------ .../provider/saml/idp/IdpMetadataManager.java | 16 ++++- .../idp/IdpSamlAuthenticationProvider.java | 12 ++++ .../IdpSamlAuthenticationSuccessHandler.java | 12 ++++ .../saml/idp/IdpSamlContextProviderImpl.java | 20 ++++-- .../saml/idp/IdpWebSSOProfileOptions.java | 12 ++++ .../provider/saml/idp/IdpWebSsoProfile.java | 12 ++++ .../saml/idp/IdpWebSsoProfileImpl.java | 20 ++++-- .../JdbcSamlServiceProviderProvisioning.java | 18 ++--- .../SamlServiceProviderChangedListener.java | 2 +- .../idp/SamlServiceProviderConfigurator.java | 32 ++++----- .../idp/SamlServiceProviderDeletable.java | 12 ++++ .../saml/idp/SamlServiceProviderHolder.java | 12 ++++ .../idp/SamlServiceProviderProvisioning.java | 12 ++++ .../idp/SamlSpAlreadyExistsException.java | 12 ++++ .../idp/ZoneAwareIdpMetadataGenerator.java | 12 ++++ .../saml/idp/ZoneAwareIdpMetadataManager.java | 27 ++++---- 29 files changed, 327 insertions(+), 181 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java index b8072c07941..fefe252b97a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java @@ -1,15 +1,16 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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 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. - *******************************************************************************/ + * 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.provider; import org.apache.commons.logging.Log; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java index 69bf7f31dee..7d4c027f072 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java @@ -1,15 +1,16 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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 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. - *******************************************************************************/ + * 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.provider; import java.util.List; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java index 3b3841f50f8..6dc7e00a68f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlServiceProviderEndpoints.java @@ -1,30 +1,20 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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 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. - *******************************************************************************/ + * 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.provider; -import static org.springframework.http.HttpStatus.OK; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; -import static org.springframework.web.bind.annotation.RequestMethod.GET; -import static org.springframework.web.bind.annotation.RequestMethod.POST; -import static org.springframework.web.bind.annotation.RequestMethod.PUT; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderProvisioning; @@ -34,8 +24,6 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -43,6 +31,14 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + @RequestMapping("/saml/service-providers") @RestController public class SamlServiceProviderEndpoints { @@ -53,14 +49,14 @@ public class SamlServiceProviderEndpoints { private final SamlServiceProviderConfigurator samlConfigurator; public SamlServiceProviderEndpoints(SamlServiceProviderProvisioning serviceProviderProvisioning, - SamlServiceProviderConfigurator samlConfigurator) { + SamlServiceProviderConfigurator samlConfigurator) { this.serviceProviderProvisioning = serviceProviderProvisioning; this.samlConfigurator = samlConfigurator; } @RequestMapping(method = POST) public ResponseEntity createServiceProvider(@RequestBody SamlServiceProvider body) - throws MetadataProviderException { + throws MetadataProviderException { String zoneId = IdentityZoneHolder.get().getId(); body.setIdentityZoneId(zoneId); @@ -72,7 +68,7 @@ public ResponseEntity createServiceProvider(@RequestBody Sa @RequestMapping(value = "{id}", method = PUT) public ResponseEntity updateServiceProvider(@PathVariable String id, - @RequestBody SamlServiceProvider body) throws MetadataProviderException { + @RequestBody SamlServiceProvider body) throws MetadataProviderException { SamlServiceProvider existing = serviceProviderProvisioning.retrieve(id); String zoneId = IdentityZoneHolder.get().getId(); body.setId(id); @@ -90,10 +86,11 @@ public ResponseEntity updateServiceProvider(@PathVariable S @RequestMapping(method = GET) public ResponseEntity> retrieveServiceProviders( - @RequestParam(value = "active_only", required = false) String activeOnly) { + @RequestParam(value = "active_only", required = false) String activeOnly) { Boolean retrieveActiveOnly = Boolean.valueOf(activeOnly); - List serviceProviderList = serviceProviderProvisioning.retrieveAll(retrieveActiveOnly, - IdentityZoneHolder.get().getId()); + List serviceProviderList = + serviceProviderProvisioning.retrieveAll(retrieveActiveOnly, + IdentityZoneHolder.get().getId()); return new ResponseEntity<>(serviceProviderList, OK); } @@ -122,16 +119,4 @@ public ResponseEntity handleProviderNotFoundException() { return new ResponseEntity<>("Provider not found.", HttpStatus.NOT_FOUND); } - protected String getExceptionString(Exception x) { - StringWriter writer = new StringWriter(); - x.printStackTrace(new PrintWriter(writer)); - return writer.getBuffer().toString(); - } - - protected static class NoOpLdapLoginAuthenticationManager extends LdapLoginAuthenticationManager { - @Override - public Authentication authenticate(Authentication request) throws AuthenticationException { - return request; - } - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java index f273de6ffaa..a2846d63118 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java index 33bd83ce738..7a93dc0891a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java index ce6d66dce3a..fd9f94c3636 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java index d14c1e92e07..55f607f3b10 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java index 1095d310926..bd766cd347b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java index 746977d7cb7..68cd0340cad 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * @@ -11,7 +11,6 @@ * subcomponent's license, as noted in the LICENSE file. * ***************************************************************************** */ - package org.cloudfoundry.identity.uaa.provider.saml; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java index bd4deade0ff..b6049493acb 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * @@ -14,19 +14,6 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; - -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -58,6 +45,18 @@ import org.springframework.security.saml.metadata.MetadataManager; import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer; +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + public class ZoneAwareMetadataManager extends MetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean, BeanNameAware { private static final Log logger = LogFactory.getLog(ZoneAwareMetadataManager.class); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java index 99efe0f0bfc..8e3420aa468 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpExtendedMetadata.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.springframework.security.saml.metadata.ExtendedMetadata; @@ -8,11 +20,6 @@ */ public class IdpExtendedMetadata extends ExtendedMetadata { - /** - * Generated serialization id. - */ - private static final long serialVersionUID = -7933870052729540864L; - private boolean assertionsSigned = true; private int assertionTimeToLiveSeconds = 500; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java index d84c68fa536..29708d486fb 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGenerator.java @@ -1,17 +1,15 @@ -/* Copyright 2009 Vladimir Schäfer -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.opensaml.Configuration; @@ -22,7 +20,12 @@ import org.opensaml.saml2.common.impl.ExtensionsBuilder; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.NameIDType; -import org.opensaml.saml2.metadata.*; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml2.metadata.NameIDFormat; +import org.opensaml.saml2.metadata.SingleLogoutService; +import org.opensaml.saml2.metadata.SingleSignOnService; import org.opensaml.samlext.idpdisco.DiscoveryResponse; import org.opensaml.util.URLBuilder; import org.opensaml.xml.XMLObjectBuilderFactory; @@ -36,18 +39,29 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.saml.*; +import org.springframework.security.saml.SAMLDiscovery; +import org.springframework.security.saml.SAMLEntryPoint; +import org.springframework.security.saml.SAMLLogoutProcessingFilter; +import org.springframework.security.saml.SAMLProcessingFilter; +import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter; import org.springframework.security.saml.key.KeyManager; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.util.SAMLUtil; import javax.xml.namespace.QName; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeMap; /** * The class is responsible for generating the metadata that describes the identity provider in the current deployment * environment. All the URLs in the metadata derive from information provided by the ServletContext. - * + * * This code for this class is based on org.springframework.security.saml.metadata.MetadataGenerator. */ public class IdpMetadataGenerator { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java index 6caf6d40abf..82d4ca85ede 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataGeneratorFilter.java @@ -1,27 +1,17 @@ -/* Copyright 2009 Vladimir Schäfer -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; @@ -37,11 +27,18 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + /** * The filter expects calls on configured URL and presents user with SAML2 metadata representing this application * deployment. In case the application is configured to automatically generate metadata, the generation occurs upon * first invocation of this filter (first request made to the server). - * + * * This class is based on org.springframework.security.saml.metadata.MetadataGeneratorFilter. */ public class IdpMetadataGeneratorFilter extends GenericFilterBean { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java index 5da97faf9ec..94d2c53616e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpMetadataManager.java @@ -1,11 +1,23 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; -import java.util.List; - import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.security.saml.metadata.MetadataManager; +import java.util.List; + /** * MetadataManager has a field that stores the entity id of the local SAML service provider. However, in order to * support SAML identity provider funcationality we also need to store the entity id of the local SAML identity diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java index 5f4f5ff395d..77752c1f210 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationProvider.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java index 8693ec465a7..361fae903cb 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlAuthenticationSuccessHandler.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import java.io.IOException; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java index 0aedbcc0f8e..55049413806 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpSamlContextProviderImpl.java @@ -1,9 +1,17 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.namespace.QName; - import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.provider.MetadataProviderException; @@ -11,6 +19,10 @@ import org.springframework.security.saml.context.SAMLContextProviderImpl; import org.springframework.security.saml.context.SAMLMessageContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; + /** * Use this class in conjuction with * org.springframework.security.saml.SAMLProcessingFilter to ensure that when diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java index c306987eaab..986a045d8e6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSSOProfileOptions.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.springframework.security.saml.websso.WebSSOProfileOptions; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java index 421f962fccf..3ad7d754eb0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfile.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.opensaml.common.SAMLException; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java index 46e49a1aed3..59c07c81705 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/IdpWebSsoProfileImpl.java @@ -1,9 +1,17 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.joda.time.DateTime; import org.opensaml.Configuration; @@ -51,6 +59,10 @@ import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.websso.WebSSOProfileImpl; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class IdpWebSsoProfileImpl extends WebSSOProfileImpl implements IdpWebSsoProfile { @Override diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java index b988a598052..c603d609e48 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/JdbcSamlServiceProviderProvisioning.java @@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. @@ -12,14 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.Date; -import java.util.List; -import java.util.UUID; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; @@ -33,6 +25,14 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + /** * Rest-template-based data access for SAML Service Provider CRUD operations. */ diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java index c07bf5f5799..a38cdc16017 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderChangedListener.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java index 6d5e2ffd59d..6f9caf6aa3d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderConfigurator.java @@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. @@ -12,20 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.SimpleHttpConnectionManager; import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; @@ -43,6 +29,20 @@ import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; import org.springframework.util.StringUtils; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + /** * Holds internal state of available SAML Service Providers. */ @@ -131,7 +131,7 @@ private Map getOrCreateSamlServiceProviderMap /** * adds or replaces a SAML service provider - * + * * @param provider * - the provider to be added * @return an array consisting of {provider-added, provider-deleted} where diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java index 8638f595548..2b1b179bb29 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderDeletable.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.apache.commons.logging.Log; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java index 98044d1b3a5..924fd57da80 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderHolder.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java index afaaddd5e6c..0726cf8461c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlServiceProviderProvisioning.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import java.util.List; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java index f8d3802d138..3440df5b83a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlSpAlreadyExistsException.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.cloudfoundry.identity.uaa.error.UaaException; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java index 8a47ee410c5..f4f6dd5ef50 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataGenerator.java @@ -1,3 +1,15 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.provider.saml.idp; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java index e21294c91f0..14e769b6c97 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/idp/ZoneAwareIdpMetadataManager.java @@ -1,7 +1,7 @@ /* * ***************************************************************************** * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. * @@ -14,19 +14,6 @@ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; - -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.provider.saml.ComparableProvider; @@ -54,6 +41,18 @@ import org.springframework.security.saml.metadata.ExtendedMetadataProvider; import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer; +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + public class ZoneAwareIdpMetadataManager extends IdpMetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean, BeanNameAware { private static final Log logger = LogFactory.getLog(ZoneAwareIdpMetadataManager.class); From fef95ed3f30d470a4b0a988b4bb4b95890bbd612 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 17 Feb 2016 11:11:46 -0800 Subject: [PATCH 63/87] Remove key validation from model. [#111794828] https://www.pivotaltracker.com/story/show/111794828 --- model/build.gradle | 2 - .../identity/uaa/zone/SamlConfig.java | 43 +++--------------- .../identity/uaa/zone/SamlConfigTest.java | 44 +------------------ .../identity/uaa/util/KeyWithCert.java | 0 .../IdentityZoneEndpointsMockMvcTests.java | 3 +- .../saml/SamlIDPRefreshMockMvcTests.java | 4 +- 6 files changed, 12 insertions(+), 84 deletions(-) rename {model => server}/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java (100%) diff --git a/model/build.gradle b/model/build.gradle index 15c733c9e66..c39581b0e95 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -14,8 +14,6 @@ dependencies { compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: parent.springSecurityOAuthVersion) { exclude(module: 'jackson-mapper-asl') } - compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.47' - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.50' compile group: 'org.slf4j', name: 'slf4j-log4j12', version:parent.slf4jVersion compile group: 'org.slf4j', name: 'slf4j-api', version:parent.slf4jVersion diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index 024d73ab2c4..24c555d4c4e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -14,13 +14,6 @@ package org.cloudfoundry.identity.uaa.zone; -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.cloudfoundry.identity.uaa.util.KeyWithCert; -import org.springframework.util.StringUtils; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - public class SamlConfig { private boolean assertionSigned = true; private boolean requestSigned = true; @@ -31,9 +24,6 @@ public class SamlConfig { private String privateKey; private String privateKeyPassword; - @JsonIgnore - private KeyWithCert keyCert; - public boolean isAssertionSigned() { return assertionSigned; } @@ -58,12 +48,12 @@ public void setWantAssertionSigned(boolean wantAssertionSigned) { this.wantAssertionSigned = wantAssertionSigned; } - public void setCertificate(String certificate) throws CertificateException { + public void setCertificate(String certificate) { this.certificate = certificate; + } - if(StringUtils.hasText(privateKey)) { - validateCert(); - } + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; } public boolean isWantAuthnRequestSigned() { @@ -86,15 +76,6 @@ public String getCertificate() { return certificate; } - public void setPrivateKeyAndPassword(String privateKey, String privateKeyPassword) throws CertificateException { - this.privateKey = privateKey; - this.privateKeyPassword = privateKeyPassword; - - if(StringUtils.hasText(certificate)) { - validateCert(); - } - } - public String getPrivateKey() { return privateKey; } @@ -103,19 +84,7 @@ public String getPrivateKeyPassword() { return privateKeyPassword; } - @JsonIgnore - public java.security.KeyPair getKeyPair() { - if(keyCert != null) { return keyCert.getPkey(); } - else { return null; } - } - - @JsonIgnore - public X509Certificate getParsedCertificate() { - if(keyCert != null) { return keyCert.getCert(); } - else { return null; } - } - - private void validateCert() throws CertificateException { - keyCert = new KeyWithCert(privateKey, privateKeyPassword, certificate); + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; } } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index fe9e07df13b..bae2de0b913 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -91,50 +91,10 @@ public void testSetKeyAndCert() throws CertificateException { "hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\n" + "-----END CERTIFICATE-----\n"; - config.setPrivateKeyAndPassword(privateKey, passphrase); + config.setPrivateKey(privateKey); + config.setPrivateKeyPassword(passphrase); config.setCertificate(certificate); assertEquals(privateKey, config.getPrivateKey()); assertEquals(passphrase, config.getPrivateKeyPassword()); } - - @Test(expected = CertificateException.class) - public void testCertificateExceptionThrownWhenCertDoesNotMatchKey() throws CertificateException { - String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + - "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + - "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + - "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + - "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + - "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + - "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + - "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + - "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + - "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + - "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + - "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + - "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + - "-----END RSA PRIVATE KEY-----\n"; - String passphrase = "password"; - - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + - "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + - "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + - "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + - "ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY\n" + - "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + - "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + - "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + - "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + - "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + - "-----END CERTIFICATE-----\n"; - - try { - config.setPrivateKeyAndPassword(privateKey, passphrase); - config.setCertificate(certificate); - } catch(CertificateException ex) { - assertEquals("Certificate does not match private key.", ex.getMessage()); - throw ex; - } - } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java similarity index 100% rename from model/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 999afa6a0c2..134d9e40c5f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -477,7 +477,8 @@ public void testCreateZoneAndIdentityProvider() throws Exception { "-----END CERTIFICATE-----\n"; samlConfig.setCertificate(samlCertificate); - samlConfig.setPrivateKeyAndPassword(samlPrivateKey, samlKeyPassphrase); + samlConfig.setPrivateKey(samlPrivateKey); + samlConfig.setPrivateKeyPassword(samlKeyPassphrase); IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); identityZone.setConfig(definition.setSamlConfig(samlConfig)); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java index 6b1aec7fb10..f3bcccdf5ff 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java @@ -439,7 +439,8 @@ public void test_zone_saml_properties() throws Exception { SamlConfig config1 = new SamlConfig(); config1. setWantAssertionSigned(true); config1. setRequestSigned(true); - config1.setPrivateKeyAndPassword(serviceProviderKey, serviceProviderKeyPassword); + config1.setPrivateKey(serviceProviderKey); + config1.setPrivateKeyPassword(serviceProviderKeyPassword); config1.setCertificate(serviceProviderCertificate); IdentityZoneConfiguration zoneConfig1 = new IdentityZoneConfiguration(null); @@ -497,7 +498,6 @@ public IdentityProvider createSamlProvider(Strin return provider; } - public SamlIdentityProviderDefinition createSimplePHPSamlIDP(String zoneId, String metaData, String alias, String linkText) { SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setZoneId(zoneId); From 5d4c8981561904eb07d88ab7a442700b7a160e1d Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 17 Feb 2016 15:42:32 -0800 Subject: [PATCH 64/87] Validate key for identity zone config on create [#111794828] https://www.pivotaltracker.com/story/show/111794828 --- .../IdentityZoneConfigurationBootstrap.java | 13 ++- .../identity/uaa/util/KeyWithCert.java | 2 + ...ralIdentityZoneConfigurationValidator.java | 38 +++++++++ .../zone/GeneralIdentityZoneValidator.java | 43 ++++++++++ .../IdentityZoneConfigurationValidator.java | 21 +++++ .../uaa/zone/IdentityZoneEndpoints.java | 13 +++ .../uaa/zone/IdentityZoneValidator.java | 21 +++++ ...lidIdentityZoneConfigurationException.java | 21 +++++ .../InvalidIdentityZoneDetailsException.java | 19 +++++ .../login/SamlLoginServerKeyManagerTests.java | 4 +- .../main/webapp/WEB-INF/spring-servlet.xml | 2 + .../IdentityZoneEndpointsMockMvcTests.java | 81 +++++++++++++++++++ 12 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfigurationValidator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneValidator.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneConfigurationException.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneDetailsException.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index b8301be3735..f3db29bff79 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -16,8 +16,11 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneValidator; +import org.cloudfoundry.identity.uaa.zone.InvalidIdentityZoneDetailsException; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; import java.util.List; import java.util.Map; @@ -38,13 +41,19 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private boolean logoutDisableRedirectParameter = true; private List prompts; + @Autowired + private IdentityZoneValidator validator = (config, mode) -> config; + + public void setValidator(IdentityZoneValidator validator) { + this.validator = validator; + } public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { this.provisioning = provisioning; } @Override - public void afterPropertiesSet() { + public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException { IdentityZone identityZone = provisioning.retrieve(IdentityZone.getUaa().getId()); IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); definition.getLinks().getSelfService().setSelfServiceLinksEnabled(selfServiceLinksEnabled); @@ -74,6 +83,8 @@ public void afterPropertiesSet() { } identityZone.setConfig(definition); + + identityZone = validator.validate(identityZone, IdentityZoneValidator.Mode.MODIFY); provisioning.update(identityZone); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java index 2ed639f9394..a25f48cfc16 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java @@ -24,6 +24,8 @@ public KeyWithCert(String key, String passphrase, String certificate) throws Cer pkey = (KeyPair) reader.readObject(); } catch (IOException ex) { throw new CertificateException("Failed to read private key or certificate.", ex); + } catch(Exception ex) { + throw ex; } if (!cert.getPublicKey().equals(pkey.getPublic())) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java new file mode 100644 index 00000000000..9fedc0e300f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.identity.uaa.zone; + +import org.cloudfoundry.identity.uaa.util.KeyWithCert; + +import java.security.GeneralSecurityException; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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. + *******************************************************************************/ +public class GeneralIdentityZoneConfigurationValidator implements IdentityZoneConfigurationValidator { + @Override + public IdentityZoneConfiguration validate(IdentityZoneConfiguration config, Mode mode) throws InvalidIdentityZoneConfigurationException { + SamlConfig samlConfig; + if((mode == Mode.CREATE || mode == Mode.MODIFY) && (samlConfig = config.getSamlConfig()) != null) { + try { + String samlSpCert = samlConfig.getCertificate(); + String samlSpKey = samlConfig.getPrivateKey(); + String samlSpkeyPassphrase = samlConfig.getPrivateKeyPassword(); + if(samlSpKey != null && samlSpCert != null) { + KeyWithCert keyWithCert = new KeyWithCert(samlSpKey, samlSpkeyPassphrase, samlSpCert); + } + } catch(GeneralSecurityException ex) { + throw new InvalidIdentityZoneConfigurationException("There is a security problem with the SAML SP configuration.", ex); + } + } + + return config; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java new file mode 100644 index 00000000000..a486a832c9a --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java @@ -0,0 +1,43 @@ +package org.cloudfoundry.identity.uaa.zone; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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. + *******************************************************************************/ +public class GeneralIdentityZoneValidator implements IdentityZoneValidator { + private final IdentityZoneConfigurationValidator configValidator; + + public GeneralIdentityZoneValidator() { + this(new GeneralIdentityZoneConfigurationValidator()); + } + + public GeneralIdentityZoneValidator(IdentityZoneConfigurationValidator configValidator) { + this.configValidator = configValidator; + } + + @Override + public IdentityZone validate(IdentityZone identityZone, Mode mode) throws InvalidIdentityZoneDetailsException { + try { + IdentityZoneConfigurationValidator.Mode configValidateMode = null; + if (mode == Mode.CREATE) { + configValidateMode = IdentityZoneConfigurationValidator.Mode.CREATE; + } else if (mode == Mode.MODIFY) { + configValidateMode = IdentityZoneConfigurationValidator.Mode.MODIFY; + } else if (mode == Mode.DELETE) { + configValidateMode = IdentityZoneConfigurationValidator.Mode.DELETE; + } + identityZone.setConfig(configValidator.validate(identityZone.getConfig(), configValidateMode)); + } catch (InvalidIdentityZoneConfigurationException ex) { + throw new InvalidIdentityZoneDetailsException("The zone configuration is invalid.", ex); + } + return identityZone; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfigurationValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfigurationValidator.java new file mode 100644 index 00000000000..a8861f7b4f0 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfigurationValidator.java @@ -0,0 +1,21 @@ +package org.cloudfoundry.identity.uaa.zone; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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. + *******************************************************************************/ +public interface IdentityZoneConfigurationValidator { + IdentityZoneConfiguration validate(IdentityZoneConfiguration config, Mode mode) throws InvalidIdentityZoneConfigurationException; + + enum Mode { + CREATE, MODIFY, DELETE + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index a526d29c38f..e5de84765e2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -77,6 +77,9 @@ public class IdentityZoneEndpoints implements ApplicationEventPublisherAware { private final IdentityProviderProvisioning idpDao; private final IdentityZoneEndpointClientRegistrationService clientRegistrationService; + @Autowired + private IdentityZoneValidator validator; + public IdentityZoneEndpoints(IdentityZoneProvisioning zoneDao, IdentityProviderProvisioning idpDao, IdentityZoneEndpointClientRegistrationService clientRegistrationService) { super(); @@ -130,6 +133,12 @@ public ResponseEntity createIdentityZone(@RequestBody @Valid Ident throw new AccessDeniedException("Zones can only be created by being authenticated in the default zone."); } + try { + body = validator.validate(body, IdentityZoneValidator.Mode.CREATE); + } catch (InvalidIdentityZoneDetailsException ex) { + throw new UnprocessableEntityException("The identity zone details are invalid.", ex); + } + if (!StringUtils.hasText(body.getId())) { body.setId(UUID.randomUUID().toString()); } @@ -316,5 +325,9 @@ private class UnprocessableEntityException extends UaaException { public UnprocessableEntityException(String message) { super("invalid_identity_zone", message, 422); } + + public UnprocessableEntityException(String message, Throwable cause) { + super(cause, "invalid_identity_zone", message, 422); + } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneValidator.java new file mode 100644 index 00000000000..79ce67cba40 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneValidator.java @@ -0,0 +1,21 @@ +package org.cloudfoundry.identity.uaa.zone; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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. + *******************************************************************************/ +public interface IdentityZoneValidator { + IdentityZone validate(IdentityZone identityZone, Mode mode) throws InvalidIdentityZoneDetailsException; + + enum Mode { + CREATE, MODIFY, DELETE + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneConfigurationException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneConfigurationException.java new file mode 100644 index 00000000000..b26e47dce09 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneConfigurationException.java @@ -0,0 +1,21 @@ +package org.cloudfoundry.identity.uaa.zone; + +import java.security.GeneralSecurityException; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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. + *******************************************************************************/ +public class InvalidIdentityZoneConfigurationException extends Exception { + public InvalidIdentityZoneConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneDetailsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneDetailsException.java new file mode 100644 index 00000000000..a3eacc3388f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/InvalidIdentityZoneDetailsException.java @@ -0,0 +1,19 @@ +package org.cloudfoundry.identity.uaa.zone; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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. + *******************************************************************************/ +public class InvalidIdentityZoneDetailsException extends Exception { + public InvalidIdentityZoneDetailsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java index 54e0e6ea683..9c494f858dc 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java @@ -264,7 +264,7 @@ public void testKeyPairValidated() throws Exception { "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----"; + "-----END RSA PRIVATE KEY-----\n"; String certificate = "-----BEGIN CERTIFICATE-----\n" + "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + @@ -290,7 +290,7 @@ public void testKeyPairValidated() throws Exception { "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + - "-----END CERTIFICATE-----"; + "-----END CERTIFICATE-----\n"; String password = "password"; diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 8a7ed7949dc..8a9db4b0803 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -31,6 +31,8 @@ + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 134d9e40c5f..9e30715536d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -514,6 +514,87 @@ public void testCreateZoneAndIdentityProvider() throws Exception { assertEquals(samlPrivateKey, createdZone.getConfig().getSamlConfig().getPrivateKey()); } + @Test + public void testCreateZoneWithInvalidSamlKeyCertPair() throws Exception { + + String id = UUID.randomUUID().toString(); + IdentityZone identityZone = getIdentityZone(id); + TokenPolicy tokenPolicy = new TokenPolicy(3600, 7200); + Map keyPairs = new HashMap<>(); + KeyPair pair = new KeyPair(); + pair.setSigningKey("secret_key_1"); + pair.setVerificationKey("public_key_1"); + keyPairs.put("key_id_1", pair); + KeyPair pair2 = new KeyPair(); + pair.setSigningKey("secret_key_2"); + pair.setVerificationKey("public_key_2"); + keyPairs.put("key_id_2", pair2); + tokenPolicy.setKeys(keyPairs); + + SamlConfig samlConfig = new SamlConfig(); + + String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "Proc-Type: 4,ENCRYPTED\n" + + "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + + "\n" + + "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + + "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + + "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + + "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + + "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + + "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + + "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + + "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + + "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + + "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + + "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + + "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + + "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + + "-----END RSA PRIVATE KEY-----\n"; + String samlKeyPassphrase = "password"; + + String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + + "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + + "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + + "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + + "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + + "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + + "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + + "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + + "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + + "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + + "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + + "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + + "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + + "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + + "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + + "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + + "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + + "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + + "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + + "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + + "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + + "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + + "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + + "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + + "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + + "-----END CERTIFICATE-----\n"; + + samlConfig.setCertificate(samlCertificate); + samlConfig.setPrivateKey(samlPrivateKey); + samlConfig.setPrivateKeyPassword(samlKeyPassphrase); + + IdentityZoneConfiguration definition = new IdentityZoneConfiguration(tokenPolicy); + identityZone.setConfig(definition.setSamlConfig(samlConfig)); + + getMockMvc().perform( + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void test_delete_zone_cleans_db() throws Exception { IdentityProviderProvisioning idpp = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); From d9acfc285bce9dd584ddcc44141f8e1aeec345d1 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 17 Feb 2016 16:06:27 -0800 Subject: [PATCH 65/87] Validate SAML SP key/cert pair on update of zone [#111794828] https://www.pivotaltracker.com/story/show/111794828 --- .../uaa/zone/IdentityZoneEndpoints.java | 7 +++ .../IdentityZoneEndpointsMockMvcTests.java | 60 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index e5de84765e2..cb7f82be562 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -180,6 +180,13 @@ public ResponseEntity updateIdentityZone( if (!IdentityZoneHolder.isUaa() && !id.equals(IdentityZoneHolder.get().getId()) ) { throw new AccessDeniedException("Zone admins can only update their own zone."); } + + try { + body = validator.validate(body, IdentityZoneValidator.Mode.MODIFY); + } catch(InvalidIdentityZoneDetailsException ex) { + throw new UnprocessableEntityException("The identity zone details are invalid.", ex); + } + IdentityZone previous = IdentityZoneHolder.get(); try { logger.debug("Zone - updating id["+id+"] subdomain["+body.getSubdomain()+"]"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 9e30715536d..293f0d36d3c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -372,6 +372,66 @@ public void testUpdateWithDifferentDataReturns200() throws Exception { checkZoneAuditEventInUaa(2, AuditEventType.IdentityZoneModifiedEvent); } + @Test + public void testUpdateWithInvalidSamlKeyCertPair() throws Exception { + String id = generator.generate(); + + IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken); + + String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "Proc-Type: 4,ENCRYPTED\n" + + "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + + "\n" + + "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + + "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + + "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + + "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + + "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + + "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + + "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + + "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + + "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + + "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + + "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + + "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + + "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + + "-----END RSA PRIVATE KEY-----\n"; + String samlKeyPassphrase = "password"; + + String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + + "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + + "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + + "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + + "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + + "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + + "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + + "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + + "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + + "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + + "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + + "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + + "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + + "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + + "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + + "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + + "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + + "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + + "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + + "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + + "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + + "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + + "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + + "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + + "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + + "-----END CERTIFICATE-----\n"; + + SamlConfig samlConfig = created.getConfig().getSamlConfig(); + samlConfig.setPrivateKey(samlPrivateKey); + samlConfig.setPrivateKeyPassword(samlKeyPassphrase); + samlConfig.setCertificate(samlCertificate); + updateZone(created, HttpStatus.UNPROCESSABLE_ENTITY, identityClientToken); + } + @Test public void testUpdateZoneWithExistingSubdomain() throws Exception { String id1 = generator.generate(); From 90595558ed40e05316c893d397cf6ffcfc958ffb Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 18 Feb 2016 09:42:35 -0800 Subject: [PATCH 66/87] Remove erroneous test setup [#111794828] https://www.pivotaltracker.com/story/show/111794828 Signed-off-by: Priyata Agrawal --- .../org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index bae2de0b913..55fbdf0eec4 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -17,7 +17,6 @@ import org.junit.Before; import org.junit.Test; -import java.security.Security; import java.security.cert.CertificateException; import static org.junit.Assert.assertEquals; @@ -28,10 +27,6 @@ public class SamlConfigTest { SamlConfig config; - public SamlConfigTest() { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); - } - @Before public void setUp() { config = new SamlConfig(); From 8df77d6584c2b00386ed7228a0bbc1efb3b33eba Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Thu, 18 Feb 2016 11:10:51 -0800 Subject: [PATCH 67/87] UaaUrlUtils has no fields, so make all of its methods static. Signed-off-by: Jeremy Coffield --- .../uaa/account/EmailChangeEmailService.java | 6 +-- .../uaa/account/ResetPasswordController.java | 7 +--- .../identity/uaa/util/UaaUrlUtils.java | 8 ++-- server/src/main/resources/login-ui.xml | 5 --- .../login/EmailChangeEmailServiceTest.java | 6 +-- .../login/ResetPasswordControllerTest.java | 6 +-- .../identity/uaa/util/UaaUrlUtilsTest.java | 38 ++++++------------- 7 files changed, 23 insertions(+), 53 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java index 7ee5b53e860..9419a0c2575 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java @@ -50,14 +50,12 @@ public class EmailChangeEmailService implements ChangeEmailService { private final ClientDetailsService clientDetailsService; private static final int EMAIL_CHANGE_LIFETIME = 30 * 60 * 1000; public static final String CHANGE_EMAIL_REDIRECT_URL = "change_email_redirect_url"; - private final UaaUrlUtils uaaUrlUtils; private final String companyName; - public EmailChangeEmailService(TemplateEngine templateEngine, MessageService messageService, ScimUserProvisioning scimUserProvisioning, UaaUrlUtils uaaUrlUtils, String companyName, ExpiringCodeStore codeStore, ClientDetailsService clientDetailsService) { + public EmailChangeEmailService(TemplateEngine templateEngine, MessageService messageService, ScimUserProvisioning scimUserProvisioning, String companyName, ExpiringCodeStore codeStore, ClientDetailsService clientDetailsService) { this.templateEngine = templateEngine; this.messageService = messageService; this.scimUserProvisioning = scimUserProvisioning; - this.uaaUrlUtils = uaaUrlUtils; this.companyName = companyName; this.codeStore = codeStore; this.clientDetailsService = clientDetailsService; @@ -149,7 +147,7 @@ private String getSubjectText() { } private String getEmailChangeEmailHtml(String email, String newEmail, String code) { - String verifyUrl = uaaUrlUtils.getUaaUrl("/verify_email"); + String verifyUrl = UaaUrlUtils.getUaaUrl("/verify_email"); final Context ctx = new Context(); if (IdentityZoneHolder.get().equals(IdentityZone.getUaa())) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index 7d4b712bedb..1f8af9a648a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -60,7 +60,6 @@ public class ResetPasswordController { private final ResetPasswordService resetPasswordService; private final MessageService messageService; private final TemplateEngine templateEngine; - private final UaaUrlUtils uaaUrlUtils; private final String companyName; private final Pattern emailPattern; private final ExpiringCodeStore codeStore; @@ -69,14 +68,12 @@ public class ResetPasswordController { public ResetPasswordController(ResetPasswordService resetPasswordService, MessageService messageService, TemplateEngine templateEngine, - UaaUrlUtils uaaUrlUtils, String companyName, ExpiringCodeStore codeStore, UaaUserDatabase userDatabase) { this.resetPasswordService = resetPasswordService; this.messageService = messageService; this.templateEngine = templateEngine; - this.uaaUrlUtils = uaaUrlUtils; this.companyName = companyName; emailPattern = Pattern.compile("^\\S+@\\S+\\.\\S+$"); this.codeStore = codeStore; @@ -133,7 +130,7 @@ private String getSubjectText() { } private String getCodeSentEmailHtml(String code, String email) { - String resetUrl = uaaUrlUtils.getUaaUrl("/reset_password"); + String resetUrl = UaaUrlUtils.getUaaUrl("/reset_password"); final Context ctx = new Context(); ctx.setVariable("serviceName", getServiceName()); @@ -144,7 +141,7 @@ private String getCodeSentEmailHtml(String code, String email) { } private String getResetUnavailableEmailHtml(String email) { - String hostname = uaaUrlUtils.getUaaHost(); + String hostname = UaaUrlUtils.getUaaHost(); final Context ctx = new Context(); ctx.setVariable("serviceName", getServiceName()); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java index 00573a3a3bb..506f6dafbb4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java @@ -26,11 +26,9 @@ import java.util.Set; import java.util.regex.Pattern; -public class UaaUrlUtils { +public abstract class UaaUrlUtils { - public UaaUrlUtils() {} - - public String getUaaUrl() { + public static String getUaaUrl() { return getUaaUrl(""); } @@ -38,7 +36,7 @@ public static String getUaaUrl(String path) { return getURIBuilder(path).build().toUriString(); } - public String getUaaHost() { + public static String getUaaHost() { return getURIBuilder("").build().getHost(); } diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index 83362de6589..d75bb5f94ca 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -556,13 +556,9 @@ - - - - @@ -573,7 +569,6 @@ - diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java index d1df87b84af..21cba2fbd09 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java @@ -74,7 +74,6 @@ public class EmailChangeEmailServiceTest { private ExpiringCodeStore codeStore; private MessageService messageService; private MockHttpServletRequest request; - private UaaUrlUtils uaaUrlUtils; private ClientDetailsService clientDetailsService; private String companyName; @@ -96,8 +95,7 @@ public void setUp() throws Exception { codeStore = mock(ExpiringCodeStore.class); clientDetailsService = mock(ClientDetailsService.class); messageService = mock(EmailService.class); - uaaUrlUtils = new UaaUrlUtils(); - emailChangeEmailService = new EmailChangeEmailService(templateEngine, messageService, scimUserProvisioning, uaaUrlUtils, "", codeStore, clientDetailsService); + emailChangeEmailService = new EmailChangeEmailService(templateEngine, messageService, scimUserProvisioning, "", codeStore, clientDetailsService); request = new MockHttpServletRequest(); request.setProtocol("http"); @@ -129,7 +127,7 @@ public void beginEmailChangeWithUsernameConflict() throws Exception { @Test public void testBeginEmailChangeWithCompanyNameConfigured() throws Exception { - emailChangeEmailService = new EmailChangeEmailService(templateEngine, messageService, scimUserProvisioning, uaaUrlUtils, "Best Company", codeStore, clientDetailsService); + emailChangeEmailService = new EmailChangeEmailService(templateEngine, messageService, scimUserProvisioning, "Best Company", codeStore, clientDetailsService); setUpForBeginEmailChange(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index a0e82cc390c..0d1768a34c1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -97,7 +97,7 @@ public void setUp() throws Exception { codeStore = mock(ExpiringCodeStore.class); userDatabase = mock(UaaUserDatabase.class); when(userDatabase.retrieveUserById(anyString())).thenReturn(new UaaUser("username","password","email","givenname","familyname")); - ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, new UaaUrlUtils(), companyName, codeStore, userDatabase); + ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, companyName, codeStore, userDatabase); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/jsp"); @@ -138,7 +138,7 @@ public void forgotPassword_ConflictInOtherZone_SendsEmailWithUnavailableEmailHtm } private void forgotPasswordWithConflict(String zoneDomain, String companyName) throws Exception { - new ResetPasswordController(resetPasswordService, messageService, templateEngine, new UaaUrlUtils(), companyName, codeStore, userDatabase); + new ResetPasswordController(resetPasswordService, messageService, templateEngine, companyName, codeStore, userDatabase); String domain = zoneDomain == null ? "localhost" : zoneDomain + ".localhost"; when(resetPasswordService.forgotPassword("user@example.com", "", "")).thenThrow(new ConflictException("abcd")); MockHttpServletRequestBuilder post = post("/forgot_password.do") @@ -188,7 +188,7 @@ public void forgotPassword_Successful() throws Exception { @Test public void forgotPassword_SuccessfulDefaultCompanyName() throws Exception { - ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, new UaaUrlUtils(), "", codeStore, userDatabase); + ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, "", codeStore, userDatabase); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/jsp"); viewResolver.setSuffix(".jsp"); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java index f1c42a547de..0caba74cdc3 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java @@ -12,34 +12,23 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.util; -import java.net.URLDecoder; -import java.net.URLEncoder; - -import org.apache.commons.httpclient.util.URIUtil; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.UriUtils; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.Assert.*; public class UaaUrlUtilsTest { - private UaaUrlUtils uaaURLUtils; - @Before public void setUp() throws Exception { - uaaURLUtils = new UaaUrlUtils(); - MockHttpServletRequest request = new MockHttpServletRequest(); ServletRequestAttributes attrs = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(attrs); @@ -52,12 +41,12 @@ public void tearDown() throws Exception { @Test public void testGetUaaUrl() throws Exception { - assertEquals("http://localhost", uaaURLUtils.getUaaUrl()); + assertEquals("http://localhost", UaaUrlUtils.getUaaUrl()); } @Test public void testGetUaaUrlWithPath() throws Exception { - assertEquals("http://localhost/login", uaaURLUtils.getUaaUrl("/login")); + assertEquals("http://localhost/login", UaaUrlUtils.getUaaUrl("/login")); } @Test @@ -72,7 +61,7 @@ public void testGetUaaUrlWithZone() throws Exception { RequestContextHolder.setRequestAttributes(attrs); - assertEquals("http://zone1.localhost", uaaURLUtils.getUaaUrl()); + assertEquals("http://zone1.localhost", UaaUrlUtils.getUaaUrl()); } @Test @@ -87,12 +76,12 @@ public void testGetUaaUrlWithZoneAndPath() throws Exception { RequestContextHolder.setRequestAttributes(attrs); - assertEquals("http://zone1.localhost/login", uaaURLUtils.getUaaUrl("/login")); + assertEquals("http://zone1.localhost/login", UaaUrlUtils.getUaaUrl("/login")); } @Test public void testGetHost() throws Exception { - assertEquals("localhost", uaaURLUtils.getUaaHost()); + assertEquals("localhost", UaaUrlUtils.getUaaHost()); } @Test @@ -106,7 +95,7 @@ public void testGetHostWithZone() throws Exception { ServletRequestAttributes attrs = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(attrs); - assertEquals("zone1.localhost", uaaURLUtils.getUaaHost()); + assertEquals("zone1.localhost", UaaUrlUtils.getUaaHost()); } @Test @@ -121,8 +110,7 @@ public void testLocalhostPortAndContextPathUrl() { RequestContextHolder.setRequestAttributes(attrs); - UaaUrlUtils urlUtils = new UaaUrlUtils(); - String url = urlUtils.getUaaUrl("/something"); + String url = UaaUrlUtils.getUaaUrl("/something"); assertThat(url, is("http://localhost:8080/uaa/something")); } @@ -137,8 +125,7 @@ public void testSecurityProtocol() { RequestContextHolder.setRequestAttributes(attrs); - UaaUrlUtils urlUtils = new UaaUrlUtils(); - String url = urlUtils.getUaaUrl("/something"); + String url = UaaUrlUtils.getUaaUrl("/something"); assertThat(url, is("https://localhost:8443/something")); } @@ -152,8 +139,7 @@ public void testMultiDomainUrls() { RequestContextHolder.setRequestAttributes(attrs); - UaaUrlUtils urlUtils = new UaaUrlUtils(); - String url = urlUtils.getUaaUrl("/something"); + String url = UaaUrlUtils.getUaaUrl("/something"); assertThat(url, is("http://login.localhost/something")); } @@ -169,8 +155,7 @@ public void testZonedAndMultiDomainUrls() { RequestContextHolder.setRequestAttributes(attrs); - UaaUrlUtils urlUtils = new UaaUrlUtils(); - String url = urlUtils.getUaaUrl("/something"); + String url = UaaUrlUtils.getUaaUrl("/something"); assertThat(url, is("http://testzone1.login.localhost/something")); } @@ -185,8 +170,7 @@ public void testXForwardedPrefixUrls() { RequestContextHolder.setRequestAttributes(attrs); - UaaUrlUtils urlUtils = new UaaUrlUtils(); - String url = urlUtils.getUaaUrl("/something"); + String url = UaaUrlUtils.getUaaUrl("/something"); assertThat(url, is("http://login.localhost/prefix/something")); } From 4a6b0b1dd82360c0cecd5e3392cb08d040428ee6 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 19 Feb 2016 10:34:16 -0700 Subject: [PATCH 68/87] Implement an example of creating an identity zone without the API https://www.pivotaltracker.com/story/show/113862631 [#113862631] --- .../ClientAPITokenIntegrationTest.java | 3 +- .../ClientIntegrationTestUtilities.java | 4 + .../SampleSimpleCreateIdentityZoneTest.java | 79 +++++++++++++++++++ .../identity/uaa/zone/IdentityZone.java | 3 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 client-lib/src/test/java/org/cloudfoundry/identity/client/integration/SampleSimpleCreateIdentityZoneTest.java diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index eb43210d407..dfaf31feb8f 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -30,6 +30,7 @@ import java.util.Collection; import java.util.Collections; +import static org.cloudfoundry.identity.client.integration.ClientIntegrationTestUtilities.UAA_URI; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; @@ -40,7 +41,7 @@ public class ClientAPITokenIntegrationTest { - public static String uaaURI = "http://localhost:8080/uaa"; + public static String uaaURI = UAA_URI; private UaaContextFactory factory; diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java index 4c94e45fca0..28d70bd4f06 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java @@ -19,6 +19,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -38,6 +39,9 @@ public class ClientIntegrationTestUtilities { public static final String DEFAULT_CSRF_COOKIE_NAME = "X-Uaa-Csrf"; + public static String UAA_URI = "http://localhost:8080/uaa"; + + public static RandomValueStringGenerator GENERATOR = new RandomValueStringGenerator(); public static String extractCookieCsrf(String body) { String pattern = "\\ created = context.getRestTemplate().exchange( + UAA_URI+"/identity-zones", + POST, + new HttpEntity<>(zone), + IdentityZone.class + ); + + Assert.assertEquals(HttpStatus.CREATED, created.getStatusCode()); + + + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 8cd97992b1b..9be29422a3c 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -147,8 +147,9 @@ public boolean equals(Object obj) { return true; } - public void setConfig(IdentityZoneConfiguration config) { + public IdentityZone setConfig(IdentityZoneConfiguration config) { this.config = config; + return this; } public IdentityZoneConfiguration getConfig() { From 02061865c83169042c8f825cabe7ff02308b6d3d Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 19 Feb 2016 15:23:41 -0700 Subject: [PATCH 69/87] Add more test cases around zone logout. https://www.pivotaltracker.com/story/show/114096343 [#114096343] --- ...tityZoneConfigurationIntegrationTest.java} | 3 +- .../identity/uaa/login/LoginMockMvcTests.java | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) rename client-lib/src/test/java/org/cloudfoundry/identity/client/integration/{SampleSimpleCreateIdentityZoneTest.java => IdentityZoneConfigurationIntegrationTest.java} (98%) diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/SampleSimpleCreateIdentityZoneTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IdentityZoneConfigurationIntegrationTest.java similarity index 98% rename from client-lib/src/test/java/org/cloudfoundry/identity/client/integration/SampleSimpleCreateIdentityZoneTest.java rename to client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IdentityZoneConfigurationIntegrationTest.java index b7c75fdac19..9364c393388 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/SampleSimpleCreateIdentityZoneTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/IdentityZoneConfigurationIntegrationTest.java @@ -34,7 +34,7 @@ import static org.cloudfoundry.identity.client.integration.ClientIntegrationTestUtilities.UAA_URI; import static org.springframework.http.HttpMethod.POST; -public class SampleSimpleCreateIdentityZoneTest { +public class IdentityZoneConfigurationIntegrationTest { private UaaContextFactory factory; @@ -74,6 +74,5 @@ public void create_zone_without_client_api() throws Exception { Assert.assertEquals(HttpStatus.CREATED, created.getStatusCode()); - } } 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 41b896f23c1..9604a6dd219 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 @@ -38,6 +38,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.Links; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -446,7 +447,7 @@ public void testLogOutEmptyWhitelistedRedirectParameter() throws Exception { .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); } finally { - setLogout(logout); + setLogout(original); } } @@ -506,6 +507,65 @@ public void testLogOutWithClientRedirect() throws Exception { } } + @Test + public void testLogOut_Config_For_Zone() throws Exception { + String zoneId = new RandomValueStringGenerator().generate(); + IdentityZoneProvisioning zoneProvisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); + IdentityZone zone = MultitenancyFixture.identityZone(zoneId, zoneId); + zone.setName(zoneId).setConfig(new IdentityZoneConfiguration()); + zone.getConfig().getLinks().getLogout() + .setRedirectUrl("http://test.redirect.com") + .setDisableRedirectParameter(true) + .setRedirectParameterName("redirect"); + zone = zoneProvisioning.create(zone); + + //default zone + getMockMvc().perform(get("/logout.do")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login")); + + //other zone + getMockMvc().perform(get("/logout.do") + .header("Host", zoneId+".localhost")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://test.redirect.com")); + + getMockMvc().perform(get("/logout.do") + .header("Host", zoneId+".localhost") + .param("redirect", "http://google.com") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://test.redirect.com")); + + zone.getConfig().getLinks().getLogout().setDisableRedirectParameter(false); + zone = zoneProvisioning.update(zone); + + getMockMvc().perform(get("/logout.do") + .header("Host", zoneId+".localhost") + .param("redirect", "http://google.com") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://google.com")); + + zone.getConfig().getLinks().getLogout().setWhitelist(Arrays.asList("http://yahoo.com")); + zone = zoneProvisioning.update(zone); + + getMockMvc().perform(get("/logout.do") + .header("Host", zoneId+".localhost") + .param("redirect", "http://google.com") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://test.redirect.com")); + + getMockMvc().perform(get("/logout.do") + .header("Host", zoneId+".localhost") + .param("redirect", "http://yahoo.com") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://yahoo.com")); + + } + @Test public void testLoginWithAnalytics() throws Exception { mockEnvironment.setProperty("analytics.code", "secret_code"); From 0354a35333c6d012a4ed060cc786793e2f13c10e Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Mon, 22 Feb 2016 10:44:56 -0800 Subject: [PATCH 70/87] Add doc and fix handling of /check_token scope check [#79786766] https://www.pivotaltracker.com/story/show/79786766 Signed-off-by: Jonathan Lo --- docs/UAA-APIs.rst | 23 +++++++++++++++++++ .../uaa/oauth/CheckTokenEndpoint.java | 8 ++++++- .../uaa/oauth/CheckTokenEndpointTests.java | 10 +++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index c8b7ef03d2b..2ae3008e446 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -639,6 +639,29 @@ This endpoint mirrors the OpenID Connect ``/check_id`` endpoint, so not very RES "client_id":"cf" } +Checking for scopes: +The caller can specify a list of ``scopes`` in the request in order to validate that the token has those scopes. + +* Request:: + + POST /check_token HTTP/1.1 + Host: server.example.com + Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + Content-Type: application/x-www-form-encoded + + token=eyJ0eXAiOiJKV1QiL + scopes=read,disallowed_scope + +* Response:: + + HTTP/1.1 400 Bad Request + Content-Type: application/json + + { + "error":"invalid_scope", + "error_description":"Some requested scopes are missing: disallowed_scope" + } + Notes: * The ``user_name`` is the same as you get from the `OpenID Connect`_ ``/userinfo`` endpoint. The ``user_id`` field is the same as you would use to get the full user profile from ``/Users``. diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java index 2fc04941de0..44efaff48a1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java @@ -92,7 +92,7 @@ public Claims checkToken(@RequestParam("token") String value, @RequestParam(name } if (!missingScopes.isEmpty()) { - throw new InvalidScopeException(String.join(",", missingScopes)); + throw new InvalidScopeException("Some requested scopes are missing: " + String.join(",", missingScopes)); } return response; @@ -133,4 +133,10 @@ public int getHttpErrorCode() { return exceptionTranslator.translate(e400); } + @ExceptionHandler(InvalidScopeException.class) + public ResponseEntity handleInvalidScopeException(Exception e) throws Exception { + logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + return exceptionTranslator.translate(e); + } + } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 005c9302b70..bb10b2e4bac 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -400,6 +400,10 @@ public void testRejectClientPasswordChange() throws Exception { endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } + private static String missingScopeMessage(String ... scopes) { + return "Some requested scopes are missing: " + String.join(",", scopes); + } + @Test(expected = InvalidScopeException.class) public void testValidateScopesNotPresent() { try { @@ -409,7 +413,7 @@ public void testValidateScopesNotPresent() { endpoint.checkToken(accessToken.getValue(), Collections.singletonList("scim.write")); } catch(InvalidScopeException ex) { - assertEquals("scim.write", ex.getMessage()); + assertEquals(missingScopeMessage("scim.write"), ex.getMessage()); throw ex; } } @@ -423,7 +427,7 @@ public void testValidateScopesMultipleNotPresent() { endpoint.checkToken(accessToken.getValue(), Arrays.asList("scim.write", "scim.read")); } catch(InvalidScopeException ex) { - assertEquals("scim.write,scim.read", ex.getMessage()); + assertEquals(missingScopeMessage("scim.write","scim.read"), ex.getMessage()); throw ex; } } @@ -455,7 +459,7 @@ public void testValidateScopesSomeNotPresent() { endpoint.checkToken(accessToken.getValue(), Arrays.asList("scim.read", "ponies.ride")); } catch(InvalidScopeException ex) { - assertEquals("ponies.ride", ex.getMessage()); + assertEquals(missingScopeMessage("ponies.ride"), ex.getMessage()); throw ex; } } From 27f569b34cf3f2245c4e48383bda47738bc7d35f Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 22 Feb 2016 13:55:59 -0800 Subject: [PATCH 71/87] No SAML redirects for JSON responses. [#114133605] https://www.pivotaltracker.com/story/show/114133605 Signed-off-by: Priyata Agrawal --- .../identity/uaa/login/LoginInfoEndpoint.java | 2 +- .../identity/uaa/login/LoginMockMvcTests.java | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 45e1f02e543..c4aee5ff0d4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -235,7 +235,7 @@ private String login(Model model, Principal principal, List excludedProm } } - if(!fieldUsernameShow) { + if(!fieldUsernameShow && !jsonResponse) { if (idps != null && idps.size() == 1) { String url = SamlRedirectUtils.getIdpRedirectUrl(idps.get(0), entityID); return "redirect:" + url; 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 9604a6dd219..97d99709321 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 @@ -863,11 +863,38 @@ public String[] getParameterValues(String name) { }; session.setAttribute("SPRING_SECURITY_SAVED_REQUEST", savedRequest); - getMockMvc().perform(get("/login").accept(TEXT_HTML).with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) + getMockMvc().perform(get("/login") + .accept(TEXT_HTML) .session(session) .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) - .andExpect(redirectedUrl("saml/discovery?returnIDParam=idp&entityID=" + identityZone.getSubdomain() + ".cloudfoundry-saml-login&idp="+alias+"&isPassive=true")); + .andExpect(redirectedUrl("saml/discovery?returnIDParam=idp&entityID=" + identityZone.getSubdomain() + ".cloudfoundry-saml-login&idp=" + alias + "&isPassive=true")); + + getMockMvc().perform(get("/login") + .accept(APPLICATION_JSON) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .andExpect(status().isOk()); + + IdentityProviderProvisioning provisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); + IdentityProvider uaaProvider = provisioning.retrieveByOrigin(OriginKeys.UAA, identityZone.getId()); + try { + IdentityZoneHolder.set(identityZone); + uaaProvider.setActive(false); + provisioning.update(uaaProvider); + getMockMvc().perform(get("/login") + .accept(APPLICATION_JSON) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .andExpect(status().isOk()); + }finally { + IdentityZoneHolder.set(identityZone); + uaaProvider.setActive(true); + provisioning.update(uaaProvider); + IdentityZoneHolder.clear(); + } + + } @Test From 4017f9c6424a045b874a8bf301c414f9bd752989 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 22 Feb 2016 09:43:25 -0800 Subject: [PATCH 72/87] Bootstrap legacy jwt keys as part of keypair list for default zone config [#114128075] https://www.pivotaltracker.com/story/show/114128075 Signed-off-by: Priyata Agrawal --- .../identity/uaa/zone/KeyPairsMap.java | 1 - .../IdentityZoneConfigurationBootstrap.java | 1 + .../uaa/impl/config/KeyPairsFactoryBean.java | 38 +++++++++++++++++++ .../webapp/WEB-INF/spring/oauth-endpoints.xml | 12 ++++++ .../identity/uaa/login/BootstrapTests.java | 31 +++++++++++++-- 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java index 91ed7bf365e..831d22de3be 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java @@ -28,7 +28,6 @@ public class KeyPairsMap { public KeyPairsMap(Map> unparsedMap) { keys = new HashMap<>(); - for (String kid : unparsedMap.keySet()) { Map keys = unparsedMap.get(kid); KeyPair keyPair = new KeyPair(keys.get(SIGNING_KEY), keys.get(VERIFICATION_KEY), keys.get(SIGNING_KEY_PASSWORD)); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index f3db29bff79..d9b3b26f538 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -18,6 +18,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneValidator; import org.cloudfoundry.identity.uaa.zone.InvalidIdentityZoneDetailsException; +import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java new file mode 100644 index 00000000000..e7e8ab5d064 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.identity.uaa.impl.config; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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. + *******************************************************************************/ +public class KeyPairsFactoryBean { + private Map> keyPairsMap; + + public KeyPairsFactoryBean(Map> map, Map legacyKeyPair) throws NoSuchAlgorithmException { + Map> keys = new HashMap<>(); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(legacyKeyPair.get("signingKey").getBytes()); + BigInteger number = new BigInteger(1, digest); + String keyId = number.toString(); + keys.put(keyId, legacyKeyPair); + keys.putAll(map); + this.keyPairsMap = keys; + } + + public Map> getKeyPairsMap() { + return keyPairsMap; + } +} diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index ec9e074046f..9bfa0cb7979 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -330,13 +330,25 @@ + + + + + + + + + + + + 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 c0ca81fc1b1..2cf3aa4885d 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 @@ -18,6 +18,7 @@ import org.cloudfoundry.identity.uaa.account.ResetPasswordController; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.impl.config.KeyPairsFactoryBean; import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.message.EmailService; import org.cloudfoundry.identity.uaa.message.NotificationsService; @@ -75,6 +76,9 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -390,6 +394,27 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { assertEquals("One Time Code ( Get one at https://login.some.test.domain.com:555/uaa/passcode )", passcode.getDetails()[1]); } + @Test + public void legacyJwtKeys_getBootstrappedAlongWithListOfKeys() throws Exception { + System.setProperty("jwt.token.verification-key", "my-old-key"); + System.setProperty("jwt.token.signing-key", "my-old-key"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest("my-old-key".getBytes()); + BigInteger number = new BigInteger(1, digest); + String keyId = number.toString(); + + context = getServletContext(null, "login.yml", "uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + IdentityZoneProvisioning identityZoneProvisioning = context.getBean("identityZoneProvisioning", IdentityZoneProvisioning.class); + + IdentityZone identityZone = identityZoneProvisioning.retrieve(IdentityZone.getUaa().getId()); + assertThat(identityZone.getConfig().getTokenPolicy().getKeys().get(keyId).getSigningKey(), equalTo("my-old-key")); + assertThat(identityZone.getConfig().getTokenPolicy().getKeys().get(keyId).getVerificationKey(), equalTo("my-old-key")); + + System.clearProperty("jwt.token.verification-key"); + System.clearProperty("jwt.token.signing-key"); + } + @Test public void testDefaultInternalHostnamesAndNoDBSettings_and_Cookie_isSecure() throws Exception { try { @@ -509,13 +534,13 @@ public void testBootstrappedIdps_and_ExcludedClaims_and_CorsConfig() throws Exce } @Test - public void bootstrap_map_of_signing_and_verification_keys_in_default_zone() { + public void bootstrap_map_of_signing_and_verification_keys_in_default_zone() throws NoSuchAlgorithmException { context = getServletContext("ldap,default", true, "test/bootstrap/login.yml,login.yml", "test/bootstrap/uaa.yml,uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); TokenPolicy uaaTokenPolicy = context.getBean("uaaTokenPolicy", TokenPolicy.class); assertThat(uaaTokenPolicy, is(notNullValue())); - assertThat(uaaTokenPolicy.getKeys().size(), comparesEqualTo(1)); + assertThat(uaaTokenPolicy.getKeys().size(), comparesEqualTo(2)); //legacy keys also bootstrapped Map keys = uaaTokenPolicy.getKeys(); - assertThat(keys.keySet(), contains("key-id-1")); + assertTrue(keys.keySet().contains("key-id-1")); KeyPair key = keys.get("key-id-1"); assertThat(key.getSigningKey(), containsString("test-signing-key")); assertThat(key.getVerificationKey(), containsString("test-verification-key")); From 343cb42189283030e49446cb08f1fed6076edba3 Mon Sep 17 00:00:00 2001 From: CF-Identity Date: Mon, 22 Feb 2016 14:29:42 -0800 Subject: [PATCH 73/87] By default - zone configuration will disallow the redirect parameter on /logout.do https://www.pivotaltracker.com/story/show/114096343 [#114096343] --- .../org/cloudfoundry/identity/uaa/zone/Links.java | 2 +- .../identity/uaa/login/LoginMockMvcTests.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java index d01bacd6e66..2665b6d1e3b 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/Links.java @@ -57,7 +57,7 @@ public Links setHomeRedirect(String homeRedirect) { public static class Logout { private String redirectUrl = "/login"; private String redirectParameterName = "redirect"; - private boolean disableRedirectParameter = false; + private boolean disableRedirectParameter = true; private List whitelist = null; public boolean isDisableRedirectParameter() { 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 97d99709321..bf77bf9bf6a 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 @@ -86,7 +86,9 @@ import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +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; import static org.springframework.http.MediaType.TEXT_HTML; import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; @@ -451,9 +453,20 @@ public void testLogOutEmptyWhitelistedRedirectParameter() throws Exception { } } + @Test + public void testLogoutRedirectIsDisabledInZone() throws Exception { + String zoneId = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(zoneId, zoneId); + zone.setConfig(new IdentityZoneConfiguration()); + IdentityZoneProvisioning provisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); + zone = provisioning.create(zone); + assertTrue(zone.getConfig().getLinks().getLogout().isDisableRedirectParameter()); + } + @Test public void testLogOutChangeUrlValue() throws Exception { Links.Logout original = getLogout(); + assertTrue(original.isDisableRedirectParameter()); Links.Logout logout = getLogout(); logout.setRedirectUrl("https://www.google.com"); setLogout(logout); From 6a06b05bde46e16981ef5a376af561646cfbbc5f Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Mon, 22 Feb 2016 17:29:32 -0800 Subject: [PATCH 74/87] Create revocable tokens storage [#112688251] https://www.pivotaltracker.com/story/show/112688251 Signed-off-by: Filip Hanik --- .../uaa/db/hsqldb/V3_1_1__RevocableToken.sql | 31 ++++ .../uaa/db/mysql/V3_1_1__RevocableToken.sql | 31 ++++ .../db/postgresql/V3_1_1__RevocableToken.sql | 31 ++++ .../uaa/db/RevocableTokenTableTest.java | 159 ++++++++++++++++++ .../identity/uaa/test/JdbcTestBase.java | 3 - 5 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_1_1__RevocableToken.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_1_1__RevocableToken.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_1_1__RevocableToken.sql create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_1_1__RevocableToken.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_1_1__RevocableToken.sql new file mode 100644 index 00000000000..e2891e424bf --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_1_1__RevocableToken.sql @@ -0,0 +1,31 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] 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. +-- + +CREATE TABLE revocable_tokens ( + + token_id VARCHAR(36) NOT NULL PRIMARY KEY, + client_id VARCHAR(255) NOT NULL, + user_id VARCHAR(36), + format VARCHAR(255), + response_type VARCHAR(25) NOT NULL, + issued_at BIGINT NOT NULL, + expires_at BIGINT NOT NULL, + scope VARCHAR(1000), + data LONGVARCHAR NOT NULL +); + +CREATE INDEX idx_revocable_token_client_id ON revocable_tokens(client_id); + +CREATE INDEX idx_revocable_token_user_id ON revocable_tokens(user_id); + +CREATE INDEX idx_revocable_token_expires_at ON revocable_tokens(expires_at); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_1_1__RevocableToken.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_1_1__RevocableToken.sql new file mode 100644 index 00000000000..2f18c293bb1 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_1_1__RevocableToken.sql @@ -0,0 +1,31 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] 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. +-- + +CREATE TABLE revocable_tokens ( + + token_id VARCHAR(36) NOT NULL PRIMARY KEY, + client_id VARCHAR(255) NOT NULL, + user_id VARCHAR(36), + format VARCHAR(255), + response_type VARCHAR(25) NOT NULL, + issued_at BIGINT NOT NULL, + expires_at BIGINT NOT NULL, + scope VARCHAR(1000), + data MEDIUMTEXT NOT NULL +); + +CREATE INDEX idx_revocable_token_client_id ON revocable_tokens(client_id); + +CREATE INDEX idx_revocable_token_user_id ON revocable_tokens(user_id); + +CREATE INDEX idx_revocable_token_expires_at ON revocable_tokens(expires_at); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_1_1__RevocableToken.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_1_1__RevocableToken.sql new file mode 100644 index 00000000000..97c5d5b9a81 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_1_1__RevocableToken.sql @@ -0,0 +1,31 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] 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. +-- + +CREATE TABLE revocable_tokens ( + + token_id VARCHAR(36) NOT NULL PRIMARY KEY, + client_id VARCHAR(255) NOT NULL, + user_id VARCHAR(36), + format VARCHAR(255), + response_type VARCHAR(25) NOT NULL, + issued_at BIGINT NOT NULL, + expires_at BIGINT NOT NULL, + scope VARCHAR(1000), + data TEXT NOT NULL +); + +CREATE INDEX idx_revocable_token_client_id ON revocable_tokens(client_id); + +CREATE INDEX idx_revocable_token_user_id ON revocable_tokens(user_id); + +CREATE INDEX idx_revocable_token_expires_at ON revocable_tokens(expires_at); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java new file mode 100644 index 00000000000..dfdb5ba09be --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2016] 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.db; + +import org.cloudfoundry.identity.uaa.test.JdbcTestBase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.springframework.mock.env.MockEnvironment; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(Parameterized.class) +public class RevocableTokenTableTest extends JdbcTestBase { + + private String springProfile; + private String tableName = "revocable_tokens"; + + private List TEST_COLUMNS = Arrays.asList( + new TestColumn("token_id", "varchar", 36), + new TestColumn("client_id", "varchar", 255), + new TestColumn("user_id", "varchar", 36), + new TestColumn("format", "varchar", 255), + new TestColumn("response_type", "varchar", 25), + new TestColumn("issued_at", "bigint/int8", 64), + new TestColumn("expires_at", "bigint/int8", 64), + new TestColumn("scope", "varchar", 1000), + new TestColumn("data", "longvarchar/mediumtext", 0) + ); + + private List TEST_INDEX = Arrays.asList( + new TestColumn("idx_revocable_token_client_id", "", 0), + new TestColumn("idx_revocable_token_user_id", "", 0), + new TestColumn("idx_revocable_token_expires_at", "", 0) + + ); + + public RevocableTokenTableTest(String springProfile, String tableName) { + this.springProfile = springProfile; + this.tableName = tableName; + } + + @Parameterized.Parameters(name = "{index}: org.cloudfoundry.identity.uaa.db[{0}]; table[{1}]") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"hsqldb", "revocable_tokens"}, + {"postgresql", "revocable_tokens"}, + {"mysql", "revocable_tokens"} + }); + } + + @Override + public void setUp() throws Exception { + MockEnvironment environment = new MockEnvironment(); + if ( springProfile!=null ) { + environment.setActiveProfiles(springProfile); + } + setUp(environment); + } + + public boolean testColumn(String name, String type, int size) { + return testColumn(TEST_COLUMNS, name, type, size); + } + public boolean testColumn(List columns, String name, String type, int size) { + for (TestColumn c : columns) { + if (c.name.equalsIgnoreCase(name)) { + return "varchar".equalsIgnoreCase(type) && !"data".equalsIgnoreCase(name) ? + c.type.toLowerCase().contains(type.toLowerCase()) && c.size == size : + c.type.toLowerCase().contains(type.toLowerCase()); + } + } + return false; + } + + + @Test + public void validate_table() throws Exception { + Connection connection = dataSource.getConnection(); + try { + DatabaseMetaData meta = connection.getMetaData(); + boolean foundTable = false; + int foundColumn = 0; + ResultSet rs = meta.getColumns(connection.getCatalog(), null, null, null); + while (rs.next()) { + String rstableName = rs.getString("TABLE_NAME"); + String rscolumnName = rs.getString("COLUMN_NAME"); + int columnSize = rs.getInt("COLUMN_SIZE"); + if (tableName.equalsIgnoreCase(rstableName)) { + assertTrue("Testing column:"+rscolumnName, testColumn(rscolumnName, rs.getString("TYPE_NAME"), columnSize)); + foundTable = true; + foundColumn++; + } + } + rs.close(); + assertTrue("Table " + tableName + " not found!", foundTable); + assertEquals("Table " + tableName + " is missing columns!", TEST_COLUMNS.size(), foundColumn); + + + rs = meta.getIndexInfo(connection.getCatalog(), null, tableName, false, false); + if (!rs.next()) { + rs = meta.getIndexInfo(connection.getCatalog(), null, tableName.toUpperCase(), false, false); + assertTrue(rs.next()); + } + int indexCount = 0; + do { + String indexName = rs.getString("INDEX_NAME"); + Short indexType = rs.getShort("TYPE"); + if (shouldCompareIndex(indexName)) { + assertTrue("Testing index: "+ indexName, testColumn(TEST_INDEX, indexName, "", indexType)); + indexCount++; + + } + } while (rs.next()); + assertEquals("One or more indices are missing", TEST_INDEX.size(), indexCount); + } finally{ + connection.close(); + } + } + + public boolean shouldCompareIndex(String indexName) { + for (TestColumn c : TEST_INDEX) { + if (c.name.equalsIgnoreCase(indexName)) { + return true; + } + } + return false; + } + + public static class TestColumn { + public final String name; + public final String type; + public final int size; + + public TestColumn(String name, String type, int size) { + this.name = name; + this.type = type; + this.size = size; + } + } + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java index 008fba57bf0..d388a6868f5 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java @@ -24,9 +24,6 @@ import javax.sql.DataSource; -/** - * Created by fhanik on 12/9/14. - */ public class JdbcTestBase extends TestClassNullifier { protected XmlWebApplicationContext webApplicationContext; From 45bf5eba253f69f03cbf61f456d8b24a561bcf87 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 23 Feb 2016 11:17:17 -0800 Subject: [PATCH 75/87] Cookie config should survive application context restarts --- .../uaa/web/UaaSessionCookieConfig.java | 53 ++++++++++--------- .../uaa/web/UaaSessionCookieConfigTest.java | 39 ++++++++++++++ 2 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfigTest.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java index 82208b99e5a..8cce5622c49 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfig.java @@ -40,30 +40,35 @@ public class UaaSessionCookieConfig implements SessionCookieConfig, ServletConte @Override public void setServletContext(ServletContext servletContext) { logger.debug("Configuring session cookie."); - SessionCookieConfig config = servletContext.getSessionCookieConfig(); - if (hasText(getComment())) { - logger.debug(String.format("Configuring session cookie - Comment: %s", getComment())); - config.setComment(getComment()); - } - if (hasText(getDomain())) { - logger.debug(String.format("Configuring session cookie - Domain: %s", getDomain())); - config.setDomain(getDomain()); - } - if (getMaxAge()>Integer.MIN_VALUE) { - logger.debug(String.format("Configuring session cookie - MaxAge: %s", getMaxAge())); - config.setMaxAge(getMaxAge()); - } - if (getPath()!=null) { - logger.debug(String.format("Configuring session cookie - Path: %s", getPath())); - config.setPath(getPath()); - } - logger.debug(String.format("Configuring session cookie - HttpOnly: %s", isHttpOnly())); - config.setHttpOnly(isHttpOnly()); - logger.debug(String.format("Configuring session cookie - Secure: %s", isSecure())); - config.setSecure(isSecure()); - if (hasText(getName())) { - logger.debug(String.format("Configuring session cookie - Name: %s", getName())); - config.setName(getName()); + + try { + SessionCookieConfig config = servletContext.getSessionCookieConfig(); + if (hasText(getComment())) { + logger.debug(String.format("Configuring session cookie - Comment: %s", getComment())); + config.setComment(getComment()); + } + if (hasText(getDomain())) { + logger.debug(String.format("Configuring session cookie - Domain: %s", getDomain())); + config.setDomain(getDomain()); + } + if (getMaxAge()>Integer.MIN_VALUE) { + logger.debug(String.format("Configuring session cookie - MaxAge: %s", getMaxAge())); + config.setMaxAge(getMaxAge()); + } + if (getPath()!=null) { + logger.debug(String.format("Configuring session cookie - Path: %s", getPath())); + config.setPath(getPath()); + } + logger.debug(String.format("Configuring session cookie - HttpOnly: %s", isHttpOnly())); + config.setHttpOnly(isHttpOnly()); + logger.debug(String.format("Configuring session cookie - Secure: %s", isSecure())); + config.setSecure(isSecure()); + if (hasText(getName())) { + logger.debug(String.format("Configuring session cookie - Name: %s", getName())); + config.setName(getName()); + } + } catch (Exception e) { + logger.error("Ignoring session cookie config - unable to configure UAA session cookie", e); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfigTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfigTest.java new file mode 100644 index 00000000000..eeea25b278e --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSessionCookieConfigTest.java @@ -0,0 +1,39 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] 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.web; + +import org.junit.Test; + +import javax.servlet.ServletContext; +import javax.servlet.SessionCookieConfig; + +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UaaSessionCookieConfigTest { + + @Test + public void testSetServletContext() throws Exception { + ServletContext context = mock(ServletContext.class); + UaaSessionCookieConfig config = new UaaSessionCookieConfig(); + SessionCookieConfig cookie = mock(SessionCookieConfig.class); + when(context.getSessionCookieConfig()).thenReturn(cookie); + doThrow(new IllegalStateException()).when(cookie).setHttpOnly(anyBoolean()); + config.setServletContext(context); + } +} \ No newline at end of file From 49097f9b229f5cab9268456f069ce1221c99bb68 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 23 Feb 2016 16:19:53 -0800 Subject: [PATCH 76/87] Generate JWT verification key based on the signing key. [#114128253] https://www.pivotaltracker.com/story/show/114128253 Signed-off-by: Filip Hanik --- .../identity/uaa/zone/KeyPair.java | 20 +- .../identity/uaa/zone/KeyPairsMap.java | 3 +- .../uaa/impl/config/KeyPairsFactoryBean.java | 19 +- .../identity/uaa/oauth/SignerProvider.java | 191 ++++++++++++------ ...entityZoneConfigurationBootstrapTests.java | 3 +- .../uaa/oauth/CheckTokenEndpointTests.java | 5 - .../uaa/oauth/token/SignerProviderTests.java | 79 +++----- .../oauth/token/TokenKeyEndpointTests.java | 6 +- .../webapp/WEB-INF/spring/oauth-endpoints.xml | 3 +- .../identity/uaa/login/BootstrapTests.java | 2 - .../token/TokenKeyEndpointMockMvcTests.java | 6 +- .../IdentityZoneEndpointsMockMvcTests.java | 4 - 12 files changed, 172 insertions(+), 169 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java index 67456a9efb6..ec234a0f991 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java @@ -23,11 +23,9 @@ public class KeyPair { public static final String SIGNING_KEY = "signingKey"; public static final String SIGNING_KEY_PASSWORD = "signingKeyPassword"; - public static final String VERIFICATION_KEY = "verificationKey"; private UUID id; - private String verificationKey = new RandomValueStringGenerator().generate(); - private String signingKey = verificationKey; + private String signingKey = new RandomValueStringGenerator().generate(); private String signingKeyPassword; public KeyPair() { @@ -36,18 +34,12 @@ public KeyPair() { public KeyPair(HashMap keymap) { this( keymap.get(SIGNING_KEY), - keymap.get(VERIFICATION_KEY), keymap.get(SIGNING_KEY_PASSWORD) ); } - public KeyPair(String signingKey, String verificationKey) { - this(signingKey, verificationKey, null); - } - - public KeyPair(String signingKey, String verificationKey, String signingKeyPassword) { + public KeyPair(String signingKey, String signingKeyPassword) { this.signingKey = signingKey; - this.verificationKey = verificationKey; this.signingKeyPassword = signingKeyPassword; } @@ -63,14 +55,6 @@ public void setSigningKey(String signingKey) { this.signingKey = signingKey; } - public String getVerificationKey() { - return verificationKey; - } - - public void setVerificationKey(String verificationKey) { - this.verificationKey = verificationKey; - } - public String getSigningKeyPassword() { return signingKeyPassword; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java index 831d22de3be..075db956652 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java @@ -19,7 +19,6 @@ import static org.cloudfoundry.identity.uaa.zone.KeyPair.SIGNING_KEY; import static org.cloudfoundry.identity.uaa.zone.KeyPair.SIGNING_KEY_PASSWORD; -import static org.cloudfoundry.identity.uaa.zone.KeyPair.VERIFICATION_KEY; public class KeyPairsMap { @@ -30,7 +29,7 @@ public KeyPairsMap(Map> unparsedMap) { keys = new HashMap<>(); for (String kid : unparsedMap.keySet()) { Map keys = unparsedMap.get(kid); - KeyPair keyPair = new KeyPair(keys.get(SIGNING_KEY), keys.get(VERIFICATION_KEY), keys.get(SIGNING_KEY_PASSWORD)); + KeyPair keyPair = new KeyPair(keys.get(SIGNING_KEY), keys.get(SIGNING_KEY_PASSWORD)); this.keys.put(kid, keyPair); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java index e7e8ab5d064..356502ff40f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/KeyPairsFactoryBean.java @@ -1,14 +1,6 @@ -package org.cloudfoundry.identity.uaa.impl.config; - -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; - /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] 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. @@ -18,6 +10,15 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ +package org.cloudfoundry.identity.uaa.impl.config; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + + public class KeyPairsFactoryBean { private Map> keyPairsMap; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java index 73a094a3949..35dff289531 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java @@ -12,8 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; +import org.apache.commons.lang.ObjectUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.ASN1Sequence; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.jwt.crypto.sign.InvalidSignatureException; import org.springframework.security.jwt.crypto.sign.MacSigner; @@ -23,45 +25,51 @@ import org.springframework.security.jwt.crypto.sign.Signer; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.springframework.security.jwt.codec.Codecs.b64Decode; +import static org.springframework.security.jwt.codec.Codecs.utf8Encode; +import static org.springframework.util.StringUtils.isEmpty; /** * A class that knows how to provide the signing and verification keys * * */ -public class SignerProvider implements InitializingBean { +public class SignerProvider { private final Log logger = LogFactory.getLog(getClass()); private String verifierKey = new RandomValueStringGenerator().generate(); private String signingKey = verifierKey; private Signer signer = new MacSigner(verifierKey); + private SignatureVerifier verifier = new MacSigner(signingKey); private String type = "MAC"; + private final Base64.Encoder base64encoder = Base64.getMimeEncoder(64, "\n".getBytes()); - @Override - public void afterPropertiesSet() throws Exception { - if (signer instanceof RsaSigner) { - type = "RSA"; - RsaVerifier verifier; - try { - verifier = new RsaVerifier(verifierKey); - } catch (Exception e) { - throw new RuntimeException("Unable to create an RSA verifier from verifierKey", e); - } + public SignerProvider() { + this(new RandomValueStringGenerator().generate()); + } - byte[] test = "test".getBytes(); - try { - verifier.verify(test, signer.sign(test)); - logger.debug("Signing and verification RSA keys match"); - } catch (InvalidSignatureException e) { - throw new RuntimeException("Signing and verification RSA keys do not match", e); - } - } - else { - Assert.state(this.signingKey == this.verifierKey, - "For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key"); + public SignerProvider(String signingKey) { + if (isEmpty(signingKey)) { + throw new IllegalArgumentException("Signing key cannot be empty"); } + setSigningKey(signingKey); } public Signer getSigner() { @@ -91,12 +99,7 @@ public boolean isPublic() { } public SignatureVerifier getVerifier() { - if (isAssymetricKey(signingKey)) { - return new RsaVerifier(verifierKey); - } - else { - return new MacSigner(verifierKey); - } + return verifier; } public String getRevocationHash(List salts) { @@ -109,29 +112,65 @@ public String getRevocationHash(List salts) { } /** - * Sets the JWT signing key. It can be either a simple MAC key or an RSA + * Sets the JWT signing key and corresponding key for verifying siugnatures produced by this class. + * + * The signing key can be either a simple MAC key or an RSA * key. RSA keys should be in OpenSSH format, * as produced by ssh-keygen. * - * @param key the key to be used for signing JWTs. + * @param signingKey the key to be used for signing JWTs. */ - public void setSigningKey(String key) { - Assert.hasText(key); - key = key.trim(); + public void setSigningKey(String signingKey) { + Assert.hasText(signingKey); + signingKey = signingKey.trim(); + + this.signingKey = signingKey; + + + if (isAssymetricKey(signingKey)) { + KeyPair keyPair = parseKeyPair(signingKey); + signer = new RsaSigner(signingKey); - this.signingKey = key; + pemEncodePublicKey(keyPair); - if (isAssymetricKey(key)) { - signer = new RsaSigner(key); logger.debug("Configured with RSA signing key"); + try { + verifier = new RsaVerifier(verifierKey); + } catch (Exception e) { + throw new RuntimeException("Unable to create an RSA verifier from verifierKey", e); + } + + byte[] test = "test".getBytes(); + try { + verifier.verify(test, signer.sign(test)); + logger.debug("Signing and verification RSA keys match"); + } catch (InvalidSignatureException e) { + throw new RuntimeException("Signing and verification RSA keys do not match", e); + } + type = "RSA"; } else { // Assume it's an HMAC key - this.verifierKey = key; - signer = new MacSigner(key); + this.verifierKey = signingKey; + MacSigner macSigner = new MacSigner(signingKey); + signer = macSigner; + verifier = macSigner; + + Assert.state(this.verifierKey == null || this.signingKey == this.verifierKey, + "For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key"); + type = "MAC"; } } + protected void pemEncodePublicKey(KeyPair keyPair) { + String begin = "-----BEGIN PUBLIC KEY-----\n"; + String end = "\n-----END PUBLIC KEY-----"; + byte[] data = keyPair.getPublic().getEncoded(); + String base64encoded = new String(base64encoder.encode(data)); + + verifierKey = begin + base64encoded + end; + } + /** * @return true if the key has a public verifier */ @@ -139,33 +178,6 @@ private boolean isAssymetricKey(String key) { return key.startsWith("-----BEGIN"); } - /** - * The key used for verifying signatures produced by this class. This is not - * used but is returned from the endpoint - * to allow resource servers to obtain the key. - * - * For an HMAC key it will be the same value as the signing key and does not - * need to be set. For and RSA key, it - * should be set to the String representation of the public key, in a - * standard format (e.g. OpenSSH keys) - * - * @param verifierKey the signature verification key (typically an RSA - * public key) - */ - public void setVerifierKey(String verifierKey) { - boolean valid = false; - try { - new RsaSigner(verifierKey); - } catch (Exception expected) { - // Expected - valid = true; - } - if (!valid) { - throw new IllegalArgumentException("Private key cannot be set as verifierKey property"); - } - this.verifierKey = verifierKey; - } - /** * This code is public domain. * @@ -226,5 +238,54 @@ public static int murmurhash3x8632(byte[] data, int offset, int len, int seed) { } + private static Pattern PEM_DATA = Pattern.compile("-----BEGIN (.*)-----(.*)-----END (.*)-----", Pattern.DOTALL); + + static KeyPair parseKeyPair(String pemData) { + Matcher m = PEM_DATA.matcher(pemData.trim()); + + if (!m.matches()) { + throw new IllegalArgumentException("String is not PEM encoded data"); + } + + String type = m.group(1); + final byte[] content = b64Decode(utf8Encode(m.group(2))); + + PublicKey publicKey; + PrivateKey privateKey = null; + + try { + KeyFactory fact = KeyFactory.getInstance("RSA"); + if (type.equals("RSA PRIVATE KEY")) { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + if (seq.size() != 9) { + throw new IllegalArgumentException("Invalid RSA Private Key ASN1 sequence."); + } + org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(), + key.getPrivateExponent(), key.getPrime1(), key.getPrime2(), key.getExponent1(), key.getExponent2(), + key.getCoefficient()); + publicKey = fact.generatePublic(pubSpec); + privateKey = fact.generatePrivate(privSpec); + } else if (type.equals("PUBLIC KEY")) { + KeySpec keySpec = new X509EncodedKeySpec(content); + publicKey = fact.generatePublic(keySpec); + } else if (type.equals("RSA PUBLIC KEY")) { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + org.bouncycastle.asn1.pkcs.RSAPublicKey key = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + publicKey = fact.generatePublic(pubSpec); + } else { + throw new IllegalArgumentException(type + " is not a supported format"); + } + return new KeyPair(publicKey, privateKey); + } + catch (InvalidKeySpecException e) { + throw new RuntimeException(e); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 91f67ef9f66..eeb0cbbc8e0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -75,7 +75,7 @@ public void configureProvisioning() { @Test public void tokenPolicy_configured_fromValuesInYaml() throws Exception { TokenPolicy tokenPolicy = new TokenPolicy(); - KeyPair key = new KeyPair(PRIVATE_KEY, PUBLIC_KEY, PASSWORD); + KeyPair key = new KeyPair(PRIVATE_KEY, PASSWORD); Map keys = new HashMap<>(); keys.put(ID, key); tokenPolicy.setKeys(keys); @@ -87,7 +87,6 @@ public void tokenPolicy_configured_fromValuesInYaml() throws Exception { IdentityZoneConfiguration definition = zone.getConfig(); assertEquals(3600, definition.getTokenPolicy().getAccessTokenValidity()); assertEquals(PASSWORD, definition.getTokenPolicy().getKeys().get(ID).getSigningKeyPassword()); - assertEquals(PUBLIC_KEY, definition.getTokenPolicy().getKeys().get(ID).getVerificationKey()); assertEquals(PRIVATE_KEY, definition.getTokenPolicy().getKeys().get(ID).getSigningKey()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index bb10b2e4bac..de91055541a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -221,7 +221,6 @@ public void setUp() { signerProvider = new SignerProvider(); signerProvider.setSigningKey(signerKey); - signerProvider.setVerifierKey(verifierKey); tokenServices.setSignerProvider(signerProvider); endpoint.setTokenServices(tokenServices); Date oneSecondAgo = new Date(System.currentTimeMillis() - 1000); @@ -292,8 +291,6 @@ public void testRejectInvalidIssuer() { @Test(expected = InvalidTokenException.class) public void testRejectInvalidVerifier() throws Exception { signerProvider.setSigningKey(alternateSignerKey); - signerProvider.setVerifierKey(alternateVerifierKey); - signerProvider.afterPropertiesSet(); endpoint.checkToken(accessToken.getValue(), Collections.emptyList()); } @@ -500,8 +497,6 @@ public void revokingAuthoritiesFromClients_invalidatesToken() throws Exception { @Test public void testSwitchVerifierKey() throws Exception { signerProvider.setSigningKey(alternateSignerKey); - signerProvider.setVerifierKey(alternateVerifierKey); - signerProvider.afterPropertiesSet(); OAuth2AccessToken alternateToken = tokenServices.createAccessToken(authentication); endpoint.checkToken(alternateToken.getValue(), Collections.emptyList()); try { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java index 6f950483767..a7b41f53427 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java @@ -36,7 +36,6 @@ public class SignerProviderTests { @Test public void testSignedProviderSymmetricKeys() { signerProvider.setSigningKey("testkey"); - signerProvider.setVerifierKey("testkey"); assertNotNull(signerProvider.getSigner()); assertNotNull(signerProvider.getVerifier()); @@ -45,46 +44,25 @@ public void testSignedProviderSymmetricKeys() { signerProvider.getVerifier().verify("joel".getBytes(), signedValue); } - @Test(expected = IllegalArgumentException.class) - public void accidentallySetPrivateKeyAsVerifier() throws Exception { - String rsaKey = "-----BEGIN RSA PRIVATE KEY-----\n" - + "MIIBywIBAAJhAOTeb4AZ+NwOtPh+ynIgGqa6UWNVe6JyJi+loPmPZdpHtzoqubnC \n" - + "wEs6JSiSZ3rButEAw8ymgLV6iBY02hdjsl3h5Z0NWaxx8dzMZfXe4EpfB04ISoqq\n" - + "hZCxchvuSDP4eQIDAQABAmEAqUuYsuuDWFRQrZgsbGsvC7G6zn3HLIy/jnM4NiJK\n" - + "t0JhWNeN9skGsR7bqb1Sak2uWqW8ZqnqgAC32gxFRYHTavJEk6LTaHWovwDEhPqc\n" - + "Zs+vXd6tZojJQ35chR/slUEBAjEA/sAd1oFLWb6PHkaz7r2NllwUBTvXL4VcMWTS\n" - + "pN+5cU41i9fsZcHw6yZEl+ZCicDxAjEA5f3R+Bj42htNI7eylebew1+sUnFv1xT8\n" - + "jlzxSzwVkoZo+vef7OD6OcFLeInAHzAJAjEAs6izolK+3ETa1CRSwz0lPHQlnmdM\n" - + "Y/QuR5tuPt6U/saEVuJpkn4LNRtg5qt6I4JRAjAgFRYTG7irBB/wmZFp47izXEc3\n" - + "gOdvA1hvq3tlWU5REDrYt24xpviA0fvrJpwMPbECMAKDKdiDi6Q4/iBkkzNMefA8\n" - + "7HX27b9LR33don/1u/yvzMUo+lrRdKAFJ+9GPE9XFA== \n" + "-----END RSA PRIVATE KEY-----"; - signerProvider.setVerifierKey(rsaKey); - } - @Test public void testSignedProviderAsymmetricKeys() throws Exception { SignerProvider signerProvider = new SignerProvider(); - signerProvider.setSigningKey("-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + - "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + - "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + - "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + - "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + - "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + - "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + - "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + - "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + - "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + - "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + - "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + - "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + - "-----END RSA PRIVATE KEY-----"); - signerProvider.setVerifierKey("-----BEGIN RSA PUBLIC KEY-----\n" + - "MIGJAoGBAMStmxljvRABrtZ0MPp46/dEsEDgjknTNk6JczOgUHnKHrirSyYRI21X\n" + - "ilrI5gTlOcfFaMyjTLuAOwaMjWiYAbrCB/Knrcj1ZwtfsUMvJ57jd8bn5v2uih+i\n" + - "wv47MlJcRJK6WxP1jVfFIUUzlEy7gh724zj+LMosKwAqKCAyGcCZAgMBAAE=\n" + - "-----END RSA PUBLIC KEY-----"); - signerProvider.afterPropertiesSet(); + String signingKey = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + + "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + + "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + + "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + + "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + + "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + + "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + + "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + + "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + + "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + + "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + + "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + + "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + + "-----END RSA PRIVATE KEY-----"; + signerProvider.setSigningKey(signingKey); assertNotNull(signerProvider.getSigner()); assertNotNull(signerProvider.getVerifier()); @@ -92,6 +70,16 @@ public void testSignedProviderAsymmetricKeys() throws Exception { signerProvider.getVerifier().verify("joel".getBytes(), signedValue); } + @Test(expected = IllegalArgumentException.class) + public void testNullSigningKey() { + new SignerProvider(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptySigningKey() { + new SignerProvider(null); + } + @Test public void testRevocationHash() throws Exception { List salts = new LinkedList<>(); @@ -104,19 +92,4 @@ public void testRevocationHash() throws Exception { assertFalse("Hash 2 should not be empty", StringUtils.isEmpty(hash2)); assertEquals(hash1, hash2); } - - @Test(expected = IllegalStateException.class) - public void keysNotMatchingWithMacSigner() throws Exception { - signerProvider.setSigningKey("aKey"); - signerProvider.setVerifierKey("someKey"); - signerProvider.afterPropertiesSet(); - } - - @Test(expected = IllegalStateException.class) - public void keysNotSameWithMacSigner() throws Exception { - signerProvider.setSigningKey("aKey"); - signerProvider.setVerifierKey(new String("aKey")); - signerProvider.afterPropertiesSet(); - } - } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java index d634c7884c4..fbff07f3ae8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java @@ -43,7 +43,7 @@ public void setUp() throws Exception { @Test public void sharedSecretIsReturnedFromTokenKeyEndpoint() throws Exception { - signerProvider.setVerifierKey("someKey"); + signerProvider.setSigningKey("someKey"); VerificationKeyResponse response = tokenEnhancer.getKey(new UsernamePasswordAuthenticationToken("foo", "bar")); assertEquals("HMACSHA256", response.getAlgorithm()); assertEquals("someKey", response.getKey()); @@ -53,7 +53,7 @@ public void sharedSecretIsReturnedFromTokenKeyEndpoint() throws Exception { @Test(expected = AccessDeniedException.class) public void sharedSecretCannotBeAnonymouslyRetrievedFromTokenKeyEndpoint() throws Exception { - signerProvider.setVerifierKey("someKey"); + signerProvider.setSigningKey("someKey"); assertEquals("{alg=HMACSHA256, value=someKey}", tokenEnhancer.getKey( new AnonymousAuthenticationToken("anon", "anonymousUser", AuthorityUtils @@ -62,7 +62,7 @@ public void sharedSecretCannotBeAnonymouslyRetrievedFromTokenKeyEndpoint() throw @Test public void responseIsBackwardCompatibleWithMap() { - signerProvider.setVerifierKey("someKey"); + signerProvider.setSigningKey("someKey"); VerificationKeyResponse response = tokenEnhancer.getKey(new UsernamePasswordAuthenticationToken("foo", "bar")); String serialized = JsonUtils.writeValueAsString(response); diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index 9bfa0cb7979..c77461b8277 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -320,8 +320,7 @@ - - + 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 2cf3aa4885d..efa7ea9b4cc 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 @@ -409,7 +409,6 @@ public void legacyJwtKeys_getBootstrappedAlongWithListOfKeys() throws Exception IdentityZone identityZone = identityZoneProvisioning.retrieve(IdentityZone.getUaa().getId()); assertThat(identityZone.getConfig().getTokenPolicy().getKeys().get(keyId).getSigningKey(), equalTo("my-old-key")); - assertThat(identityZone.getConfig().getTokenPolicy().getKeys().get(keyId).getVerificationKey(), equalTo("my-old-key")); System.clearProperty("jwt.token.verification-key"); System.clearProperty("jwt.token.signing-key"); @@ -543,7 +542,6 @@ public void bootstrap_map_of_signing_and_verification_keys_in_default_zone() thr assertTrue(keys.keySet().contains("key-id-1")); KeyPair key = keys.get("key-id-1"); assertThat(key.getSigningKey(), containsString("test-signing-key")); - assertThat(key.getVerificationKey(), containsString("test-verification-key")); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java index 7567b5d9845..fab2c6d2717 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; 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.status; public class TokenKeyEndpointMockMvcTests extends InjectedMockContextTest { @@ -91,16 +92,12 @@ public void setUp() throws Exception { originalSignKey = provider.getSigningKey(); originalVerifierKey = provider.getVerifierKey(); provider.setSigningKey(signKey); - provider.setVerifierKey(verifyKey); - provider.afterPropertiesSet(); } @After public void resetKeys() throws Exception { SignerProvider provider = getWebApplicationContext().getBean(SignerProvider.class); provider.setSigningKey(originalSignKey); - provider.setVerifierKey(originalVerifierKey); - provider.afterPropertiesSet(); } @Test @@ -155,6 +152,7 @@ public void checkTokenKeysValuesAnonymous() throws Exception { get("/token_keys") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) + .andDo(print()) .andReturn(); Map keys = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 293f0d36d3c..76c5a863d83 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -485,11 +485,9 @@ public void testCreateZoneAndIdentityProvider() throws Exception { Map keyPairs = new HashMap<>(); KeyPair pair = new KeyPair(); pair.setSigningKey("secret_key_1"); - pair.setVerificationKey("public_key_1"); keyPairs.put("key_id_1", pair); KeyPair pair2 = new KeyPair(); pair.setSigningKey("secret_key_2"); - pair.setVerificationKey("public_key_2"); keyPairs.put("key_id_2", pair2); tokenPolicy.setKeys(keyPairs); @@ -583,11 +581,9 @@ public void testCreateZoneWithInvalidSamlKeyCertPair() throws Exception { Map keyPairs = new HashMap<>(); KeyPair pair = new KeyPair(); pair.setSigningKey("secret_key_1"); - pair.setVerificationKey("public_key_1"); keyPairs.put("key_id_1", pair); KeyPair pair2 = new KeyPair(); pair.setSigningKey("secret_key_2"); - pair.setVerificationKey("public_key_2"); keyPairs.put("key_id_2", pair2); tokenPolicy.setKeys(keyPairs); From 8c28092f76e83500621c40226629fa4df6db3e74 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 25 Feb 2016 12:05:54 -0700 Subject: [PATCH 77/87] Remove the parameters from the table check test. that is intended for developers to test multiple DBs --- .../identity/uaa/db/RevocableTokenTableTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java index dfdb5ba09be..d6fc7d461ed 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/db/RevocableTokenTableTest.java @@ -14,7 +14,6 @@ import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.junit.Test; -import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.springframework.mock.env.MockEnvironment; @@ -28,7 +27,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -@RunWith(Parameterized.class) +//@RunWith(Parameterized.class) public class RevocableTokenTableTest extends JdbcTestBase { private String springProfile; @@ -53,10 +52,10 @@ public class RevocableTokenTableTest extends JdbcTestBase { ); - public RevocableTokenTableTest(String springProfile, String tableName) { - this.springProfile = springProfile; - this.tableName = tableName; - } +// public RevocableTokenTableTest(String springProfile, String tableName) { +// this.springProfile = springProfile; +// this.tableName = tableName; +// } @Parameterized.Parameters(name = "{index}: org.cloudfoundry.identity.uaa.db[{0}]; table[{1}]") public static Collection data() { From e7b7d3bff623e83be15e94ea255e33d29714e4da Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 25 Feb 2016 11:12:31 -0800 Subject: [PATCH 78/87] Test the failure to parse PEM object throws CertificateException [#111794828] https://www.pivotaltracker.com/story/show/111794828 Signed-off-by: Priyata Agrawal --- .../identity/uaa/util/KeyWithCert.java | 21 ++++-- .../identity/uaa/util/KeyWithCertTest.java | 71 +++++++++++++++++++ 2 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java index a25f48cfc16..cafb6027be2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java @@ -16,16 +16,25 @@ public class KeyWithCert { public KeyWithCert(String key, String passphrase, String certificate) throws CertificateException { if(passphrase == null) { passphrase = ""; } - try { - PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certificate.getBytes()))); - cert = (X509Certificate) reader.readObject(); + PEMReader reader; + try { reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(key.getBytes())), passphrase::toCharArray); pkey = (KeyPair) reader.readObject(); + if(pkey == null) { + throw new CertificateException("Failed to read private key. The security provider could not parse it."); + } + } catch (IOException ex) { + throw new CertificateException("Failed to read private key.", ex); + } + try { + reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certificate.getBytes()))); + cert = (X509Certificate) reader.readObject(); + if(cert == null) { + throw new CertificateException("Failed to read certificate. The security provider could not parse it."); + } } catch (IOException ex) { - throw new CertificateException("Failed to read private key or certificate.", ex); - } catch(Exception ex) { - throw ex; + throw new CertificateException("Failed to read certificate.", ex); } if (!cert.getPublicKey().equals(pkey.getPublic())) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java new file mode 100644 index 00000000000..f418a038a64 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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.util; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.Test; + +import java.security.Security; +import java.security.cert.CertificateException; + + +public class KeyWithCertTest { + public static final String key = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXgIBAAKBgQDfTLadf6QgJeS2XXImEHMsa+1O7MmIt44xaL77N2K+J/JGpfV3\n" + + "AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vlyHFKytG7UEt60Fl88SbAEfsU\n" + + "JN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LMvGy153LHFVW2zrjhTwIDAQAB\n" + + "AoGBAJDh21LRcJITRBQ3CUs9PR1DYZPl+tUkE7RnPBMPWpf6ny3LnDp9dllJeHqz\n" + + "a3ACSgleDSEEeCGzOt6XHnrqjYCKa42Z+Opnjx/OOpjyX1NAaswRtnb039jwv4gb\n" + + "RlwT49Y17UAQpISOo7JFadCBoMG0ix8xr4ScY+zCSoG5v0BhAkEA8llNsiWBJF5r\n" + + "LWQ6uimfdU2y1IPlkcGAvjekYDkdkHiRie725Dn4qRiXyABeaqNm2bpnD620Okwr\n" + + "sf7LY+BMdwJBAOvgt/ZGwJrMOe/cHhbujtjBK/1CumJ4n2r5V1zPBFfLNXiKnpJ6\n" + + "J/sRwmjgg4u3Anu1ENF3YsxYabflBnvOP+kCQCQ8VBCp6OhOMcpErT8+j/gTGQUL\n" + + "f5zOiPhoC2zTvWbnkCNGlqXDQTnPUop1+6gILI2rgFNozoTU9MeVaEXTuLsCQQDC\n" + + "AGuNpReYucwVGYet+LuITyjs/krp3qfPhhByhtndk4cBA5H0i4ACodKyC6Zl7Tmf\n" + + "oYaZoYWi6DzbQQUaIsKxAkEA2rXQjQFsfnSm+w/9067ChWg46p4lq5Na2NpcpFgH\n" + + "waZKhM1W0oB8MX78M+0fG3xGUtywTx0D4N7pr1Tk2GTgNw==\n" + + "-----END RSA PRIVATE KEY-----"; + public static final String cert = "-----BEGIN CERTIFICATE-----\n" + + "FILIPMIIEJTCCA46gAwIBAgIJANIqfxWTfhpkMA0GCSqGSIb3DQEBBQUAMIG+MQswCQYD\n" + + "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" + + "aXNjbzEdMBsGA1UEChMUUGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Ns\n" + + "b3VkIEZvdW5kcnkgSWRlbnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2Yt\n" + + "YXBwLmNvbTEfMB0GCSqGSIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzAeFw0xNTA1\n" + + "MTQxNzE5MTBaFw0yNTA1MTExNzE5MTBaMIG+MQswCQYDVQQGEwJVUzETMBEGA1UE\n" + + "CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMU\n" + + "UGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Nsb3VkIEZvdW5kcnkgSWRl\n" + + "bnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2YtYXBwLmNvbTEfMB0GCSqG\n" + + "SIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\n" + + "gYkCgYEA30y2nX+kICXktl1yJhBzLGvtTuzJiLeOMWi++zdivifyRqX1dwJ5MgdO\n" + + "sBWdNrASwe4ZKONiyLFRDsk7lAYq3f975chxSsrRu1BLetBZfPEmwBH7FCTdYtWk\n" + + "lJbpz0vzQs/gSsMChT/UrN6zSJhPVHNizLxstedyxxVVts644U8CAwEAAaOCAScw\n" + + "ggEjMB0GA1UdDgQWBBSvWY/TyHysYGxKvII95wD/CzE1AzCB8wYDVR0jBIHrMIHo\n" + + "gBSvWY/TyHysYGxKvII95wD/CzE1A6GBxKSBwTCBvjELMAkGA1UEBhMCVVMxEzAR\n" + + "BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNV\n" + + "BAoTFFBpdm90YWwgU29mdHdhcmUgSW5jMSQwIgYDVQQLExtDbG91ZCBGb3VuZHJ5\n" + + "IElkZW50aXR5IFRlYW0xHDAaBgNVBAMTE2lkZW50aXR5LmNmLWFwcC5jb20xHzAd\n" + + "BgkqhkiG9w0BCQEWEG1hcmlzc2FAdGVzdC5vcmeCCQDSKn8Vk34aZDAMBgNVHRME\n" + + "BTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL5j1JCN5EoXMOOBSBUL8KeVZFQD3Nfy\n" + + "YkYKBatFEKdBFlAKLBdG+5KzE7sTYesn7EzBISHXFz3DhdK2tg+IF1DeSFVmFl2n\n" + + "iVxQ1sYjo4kCugHBsWo+MpFH9VBLFzsMlP3eIDuVKe8aPXFKYCGhctZEJdQTKlja\n" + + "lshe50nayKrT\n" + + "-----END CERTIFICATE-----\n"; + public static final String password = "password"; + + @Test(expected = CertificateException.class) + public void test_invalid_cert() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + new KeyWithCert(key, password, cert); + + } +} \ No newline at end of file From 084685dfbb273e1c6036088d998496410d197237 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 25 Feb 2016 13:30:06 -0700 Subject: [PATCH 79/87] Fix error message --- .../uaa/provider/saml/LoginSamlAuthenticationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java index fa239d831ca..8bd6f76c49f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java @@ -137,7 +137,7 @@ public Authentication authenticate(Authentication authentication) throws Authent throw new ProviderNotFoundException("Identity Provider has been disabled by administrator."); } } catch (EmptyResultDataAccessException x) { - throw new ProviderNotFoundException("Not identity provider found in zone."); + throw new ProviderNotFoundException("No SAML identity provider found in zone."); } ExpiringUsernameAuthenticationToken result = getExpiringUsernameAuthenticationToken(authentication); UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, result.getName(), result.getName(), alias, result.getName(), zone.getId()); From eebc265bdd8ac5554ba86fd072997dc06179a7dd Mon Sep 17 00:00:00 2001 From: Priyata Agrawal Date: Thu, 25 Feb 2016 12:12:06 -0800 Subject: [PATCH 80/87] Use single-quotes instead of double-quotes in SCIM filters [finishes #114512691] https://www.pivotaltracker.com/story/show/114512691 Signed-off-by: Jeremy Coffield --- .../org/cloudfoundry/identity/uaa/oauth/AccessController.java | 2 +- .../uaa/scim/jdbc/JdbcScimGroupMembershipManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java index 6eca6035df0..b808602f317 100755 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java @@ -237,7 +237,7 @@ private List> getScopes(ArrayList scopes) { String code = SCOPE_PREFIX + scope; map.put("code", code); - Optional group = groupProvisioning.query(String.format("displayName eq '%s'", scope)).stream().findFirst(); + Optional group = groupProvisioning.query(String.format("displayName eq \"%s\"", scope)).stream().findFirst(); group.ifPresent(g -> { String description = g.getDescription(); if (StringUtils.hasText(description)) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index 27b6f5f472b..0f9fd646434 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -215,10 +215,10 @@ public List getMembers(final String groupId, String filter, boo if (StringUtils.hasText(filter)) { // validate filter syntax getQueryConverter().convert(filter, "member_id", true); - scopedFilter = String.format("group_id eq '%s' and (%s)", groupId, filter); + scopedFilter = String.format("group_id eq \"%s\" and (%s)", groupId, filter); } else { - scopedFilter = String.format("group_id eq '%s'", groupId); + scopedFilter = String.format("group_id eq \"%s\"", groupId); } List result = query(scopedFilter, "member_id", true); From 3a6f5feacd697294b0578bf257ff1c552c1a9f8c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 26 Feb 2016 07:58:40 -0700 Subject: [PATCH 81/87] SAML error messages should specify alias(origin) that is invalid --- .../uaa/provider/saml/LoginSamlAuthenticationProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java index 8bd6f76c49f..07c3a8f365c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java @@ -134,10 +134,10 @@ public Authentication authenticate(Authentication authentication) throws Authent samlConfig = idp.getConfig(); addNew = samlConfig.isAddShadowUserOnLogin(); if (!idp.isActive()) { - throw new ProviderNotFoundException("Identity Provider has been disabled by administrator."); + throw new ProviderNotFoundException("Identity Provider has been disabled by administrator for alias:"+alias); } } catch (EmptyResultDataAccessException x) { - throw new ProviderNotFoundException("No SAML identity provider found in zone."); + throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:"+alias); } ExpiringUsernameAuthenticationToken result = getExpiringUsernameAuthenticationToken(authentication); UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, result.getName(), result.getName(), alias, result.getName(), zone.getId()); From 08ed3b5ee5e41b71a00de996d25f45f236716371 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 26 Feb 2016 09:40:03 -0700 Subject: [PATCH 82/87] Support group names to be other types than XSString https://www.pivotaltracker.com/story/show/114508167 [#114508167] --- .../saml/LoginSamlAuthenticationProvider.java | 4 ++-- .../saml/LoginSamlAuthenticationProviderTests.java | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java index 07c3a8f365c..b088b062594 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java @@ -195,7 +195,7 @@ public Collection retrieveSamlAuthorities(SamlIdenti if ((groupNames.contains(attribute.getName())) || (groupNames.contains(attribute.getFriendlyName()))) { if (attribute.getAttributeValues() != null && attribute.getAttributeValues().size() > 0) { for (XMLObject group : attribute.getAttributeValues()) { - authorities.add(new SamlUserAuthority(((XSString) group).getValue())); + authorities.add(new SamlUserAuthority(getStringValue(attribute.getName(),definition,group))); } } } @@ -253,7 +253,7 @@ protected String getStringValue(String key, SamlIdentityProviderDefinition defin logger.debug(String.format("Found SAML user attribute %s of value %s [zone:%s, origin:%s]", key, value, definition.getZoneId(), definition.getIdpEntityAlias())); return value; } else if (xmlObject !=null){ - logger.debug(String.format("SAML user attribute %s at is not of type XSString, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(),definition.getZoneId(), definition.getIdpEntityAlias())); + logger.debug(String.format("SAML user attribute %s at is not of type XSString or other recognizable type, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(),definition.getZoneId(), definition.getIdpEntityAlias())); } return null; } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index e0f10de0840..3e192ce7d85 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -168,10 +168,16 @@ public List getAttributes(final String name, Object value) { impl.setValue((String)value); xmlObjects.add(impl); } else if (value instanceof List) { - for (String s : (List)value) { - AttributedStringImpl impl = new AttributedStringImpl("", "", ""); - impl.setValue(s); - xmlObjects.add(impl); + for (String s : (List) value) { + if (SAML_USER.equals(s)) { + XSAnyImpl impl = new XSAnyImpl("","","") {}; + impl.setTextContent(s); + xmlObjects.add(impl); + } else { + AttributedStringImpl impl = new AttributedStringImpl("", "", ""); + impl.setValue(s); + xmlObjects.add(impl); + } } } else { AttributedStringImpl impl = new AttributedStringImpl("", "", ""); From 5cb2ece877380c018f97da10e204ad169316e995 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 26 Feb 2016 09:55:20 -0700 Subject: [PATCH 83/87] Add TDD information to the contributor section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a6856510167..3f9f82a1d8d 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,9 @@ Here are some ways for you to get involved in the community: want to contribute code this way, please reference an existing issue if there is one as well covering the specific issue you are addressing. Always submit pull requests to the "develop" branch. + We strictly adhere to test driven development. We kindly ask that + pull requests are accompanied with test cases that would be failing + if ran separately from the pull request. * Watch for upcoming articles on Cloud Foundry by [subscribing](http://blog.cloudfoundry.org) to the cloudfoundry.org blog From a2065bf147a855159bbaf0544af4ffe1cc1ae345 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 2 Mar 2016 16:19:25 -0700 Subject: [PATCH 84/87] If only scope is zones..read, remove SamlConfig and TokenPolicy objects [#114894053] https://www.pivotaltracker.com/story/show/114894053 --- .../uaa/zone/IdentityZoneEndpoints.java | 32 ++++- .../identity/uaa/mock/util/MockMvcUtils.java | 6 + .../IdentityZoneEndpointsMockMvcTests.java | 124 ++++++++++++++---- 3 files changed, 134 insertions(+), 28 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index cb7f82be562..815ed68372d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -28,6 +28,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; import org.springframework.security.oauth2.provider.ClientDetails; @@ -116,12 +118,40 @@ protected List filterForCurrentZone(List zones) { List result = new LinkedList<>(); for (IdentityZone zone : zones) { if (currentId.equals(zone.getId())) { - result.add(zone); + result.add(filterForZonesDotRead(zone)); + break; } } + return result; } + protected IdentityZone filterForZonesDotRead(IdentityZone zone) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth!=null && hasReadOnlyAuthority(zone.getId(), auth)) { + zone.getConfig().setSamlConfig(null); + zone.getConfig().setTokenPolicy(null); + } + return zone; + } + + protected boolean hasReadOnlyAuthority(String zoneId, Authentication authentication) { + boolean hasRead = false; + boolean doesNotHaveAdmin = true; + String adminScope = ZoneManagementScopes.ZONES_ZONE_ID_PREFIX + zoneId + ".admin"; + String readScope = ZoneManagementScopes.ZONES_ZONE_ID_PREFIX + zoneId + ".read"; + for (GrantedAuthority a : authentication.getAuthorities()) { + if (adminScope.equals(a.getAuthority())) { + doesNotHaveAdmin = false; + } else if (readScope.equals(a.getAuthority())) { + hasRead = true; + } + } + return hasRead && doesNotHaveAdmin; + } + + + @RequestMapping(method = POST) public ResponseEntity createIdentityZone(@RequestBody @Valid IdentityZone body, BindingResult result) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 2006b673a5f..3dee9c5ad02 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -813,6 +813,9 @@ public String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, .param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); + if (StringUtils.hasText(scope)) { + authRequest.param(OAuth2Utils.SCOPE, scope); + } MvcResult result = mockMvc.perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); @@ -827,6 +830,9 @@ public String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, .param("code", code) .param(OAuth2Utils.CLIENT_ID, clientId) .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); + if (StringUtils.hasText(scope)) { + authRequest.param(OAuth2Utils.SCOPE, scope); + } result = mockMvc.perform(authRequest).andExpect(status().is2xxSuccessful()).andReturn(); TestClient.OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), TestClient.OAuthToken.class); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 76c5a863d83..2a5cd1162eb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -2,20 +2,19 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.scim.event.GroupModifiedEvent; -import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientDeleteEvent; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; -import org.cloudfoundry.identity.uaa.approval.Approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; -import org.cloudfoundry.identity.uaa.client.event.ClientDeleteEvent; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; @@ -24,12 +23,13 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.event.GroupModifiedEvent; +import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -82,6 +82,48 @@ public class IdentityZoneEndpointsMockMvcTests extends InjectedMockContextTest { public static final List BASE_URLS = Arrays.asList("/identity-zones", "/identity-zones/"); + + private final String serviceProviderKey = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5\n" + + "L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA\n" + + "fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB\n" + + "AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges\n" + + "7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu\n" + + "lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp\n" + + "ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX\n" + + "kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL\n" + + "gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK\n" + + "vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe\n" + + "A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS\n" + + "N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB\n" + + "qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/\n" + + "-----END RSA PRIVATE KEY-----"; + + private final String serviceProviderKeyPassword = "password"; + + private final String serviceProviderCertificate = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO\n" + + "MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO\n" + + "MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h\n" + + "cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx\n" + + "CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM\n" + + "BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb\n" + + "BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + + "ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W\n" + + "qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw\n" + + "znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha\n" + + "MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc\n" + + "gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD\n" + + "VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD\n" + + "VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh\n" + + "QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ\n" + + "0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC\n" + + "KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK\n" + + "RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + + "-----END CERTIFICATE-----\n"; + private String identityClientToken = null; private String identityClientZonesReadToken = null; private String identityClientZonesWriteToken = null; @@ -943,14 +985,13 @@ public void testZoneAdminTokenAgainstZoneEndpoints() throws Exception { MvcResult result = getMockMvc().perform( get("/identity-zones") .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getSubdomain()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); //test read your own zone only - List zones = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { - }); + List zones = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() {}); assertEquals(1, zones.size()); assertEquals(zone1, zones.get(0).getSubdomain()); @@ -958,7 +999,7 @@ public void testZoneAdminTokenAgainstZoneEndpoints() throws Exception { getMockMvc().perform( put("/identity-zones/" + result1.getIdentityZone().getId()) .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getSubdomain()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(result1.getIdentityZone()))) @@ -968,7 +1009,7 @@ public void testZoneAdminTokenAgainstZoneEndpoints() throws Exception { getMockMvc().perform( put("/identity-zones/" + result2.getIdentityZone().getId()) .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getSubdomain()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) @@ -978,7 +1019,7 @@ public void testZoneAdminTokenAgainstZoneEndpoints() throws Exception { getMockMvc().perform( post("/identity-zones") .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getSubdomain()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) @@ -1105,31 +1146,51 @@ public void userCanReadAZone_withZoneZoneIdReadToken() throws Exception { String id = generator.generate().toLowerCase(); IdentityZone identityZone = createZone(id, HttpStatus.CREATED, identityClientToken); - ScimGroup group = new ScimGroup(); - String zoneReadScope = "zones." + identityZone.getId() + ".read"; - group.setDisplayName(zoneReadScope); - group.setMembers(Collections.singletonList(new ScimGroupMember(user.getId()))); - getMockMvc().perform( - post("/Groups/zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group))) - .andExpect(status().isCreated()); + for (String displayName : Arrays.asList("read","admin")) { + ScimGroup group = new ScimGroup(); + String zoneReadScope = "zones." + identityZone.getId() + "." + displayName; + group.setDisplayName(zoneReadScope); + group.setMembers(Collections.singletonList(new ScimGroupMember(user.getId()))); + getMockMvc().perform( + post("/Groups/zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group))) + .andExpect(status().isCreated()); + } String userAccessToken = mockMvcUtils.getUserOAuthAccessTokenAuthCode(getMockMvc(), "identity", "identitysecret", user.getId(), user.getUserName(), user.getPassword(), "zones." + identityZone.getId() + ".read"); MvcResult result = getMockMvc().perform( get("/identity-zones/" + identityZone.getId()) .header("Authorization", "Bearer " + userAccessToken) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getSubdomain()) + .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); - IdentityZone zoneResult = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference() { - }); + IdentityZone zoneResult = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference() {}); + assertEquals(identityZone, zoneResult); + assertNull(zoneResult.getConfig().getSamlConfig().getPrivateKey()); + assertNull(zoneResult.getConfig().getTokenPolicy().getKeys()); + + + String userAccessTokenReadAndAdmin = mockMvcUtils.getUserOAuthAccessTokenAuthCode(getMockMvc(), "identity", "identitysecret", user.getId(), user.getUserName(), user.getPassword(), "zones." + identityZone.getId() + ".read "+"zones." + identityZone.getId() + ".admin "); + + result = getMockMvc().perform( + get("/identity-zones/" + identityZone.getId()) + .header("Authorization", "Bearer " + userAccessTokenReadAndAdmin) + .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + zoneResult = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference() {}); assertEquals(identityZone, zoneResult); + assertNotNull(zoneResult.getConfig().getSamlConfig().getPrivateKey()); + assertNotNull(zoneResult.getConfig().getTokenPolicy().getKeys()); + } private IdentityZone getIdentityZone(String id, HttpStatus expect, String token) throws Exception { @@ -1147,6 +1208,15 @@ private IdentityZone getIdentityZone(String id, HttpStatus expect, String token) private IdentityZone createZone(String id, HttpStatus expect, String token) throws Exception { IdentityZone identityZone = getIdentityZone(id); + identityZone.setConfig(new IdentityZoneConfiguration()); + identityZone.getConfig().getSamlConfig().setPrivateKey(serviceProviderKey); + identityZone.getConfig().getSamlConfig().setPrivateKeyPassword(serviceProviderKeyPassword); + identityZone.getConfig().getSamlConfig().setCertificate(serviceProviderCertificate); + KeyPair tokenKey = new KeyPair("key","key"); + Map keys = new HashMap<>(); + keys.put("kid", tokenKey); + identityZone.getConfig().getTokenPolicy().setKeys(keys); + MvcResult result = getMockMvc().perform( post("/identity-zones") .header("Authorization", "Bearer " + token) From bb1a5333cdff68fef24bb0215730ee735722540c Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 3 Mar 2016 14:20:38 -0800 Subject: [PATCH 85/87] Ignore unknown json properties in KeyPair - for backwards compatibility with json containing verificationKey [#114993951] https://www.pivotaltracker.com/story/show/114993951 Signed-off-by: Madhura Bhave --- .../identity/uaa/zone/KeyPair.java | 2 ++ .../identity/uaa/zone/TokenPolicy.java | 2 +- .../identity/uaa/zone/TokenPolicyTest.java | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 model/src/test/java/org/cloudfoundry/identity/uaa/zone/TokenPolicyTest.java diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java index ec234a0f991..b2987a6d8e9 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java @@ -14,11 +14,13 @@ package org.cloudfoundry.identity.uaa.zone; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.util.HashMap; import java.util.UUID; +@JsonIgnoreProperties(ignoreUnknown = true) public class KeyPair { public static final String SIGNING_KEY = "signingKey"; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java index 68b0fd23474..8b100c2feb3 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.zone; -import java.util.Map; + import java.util.Map; /******************************************************************************* * Cloud Foundry diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/TokenPolicyTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/TokenPolicyTest.java new file mode 100644 index 00000000000..b8490bb4059 --- /dev/null +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/TokenPolicyTest.java @@ -0,0 +1,30 @@ +package org.cloudfoundry.identity.uaa.zone; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] 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. + *******************************************************************************/ + +public class TokenPolicyTest { + + @Test + public void deserializationOfTokenPolicyWithVerificationKey_doesNotFail() { + String jsonTokenPolicy = "{\"keys\":{\"key-id-1\":{\"verificationKey\":\"some-verification-key-1\",\"signingKey\":\"some-signing-key-1\"}}}"; + TokenPolicy tokenPolicy = JsonUtils.readValue(jsonTokenPolicy, TokenPolicy.class); + assertEquals(tokenPolicy.getKeys().get("key-id-1").getSigningKey(), "some-signing-key-1"); + } + +} \ No newline at end of file From 2cccbe5f861d66bca08062d4a1c06db8f4e27c19 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Fri, 4 Mar 2016 09:01:37 -0700 Subject: [PATCH 86/87] favicon.ico should not be protected (and saved as a request) [#115006981] https://www.pivotaltracker.com/story/show/115006981 --- uaa/src/main/webapp/WEB-INF/spring-servlet.xml | 5 ++--- .../identity/uaa/integration/feature/SamlLoginIT.java | 10 +++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 8a9db4b0803..6967a6d8c77 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -15,11 +15,9 @@ @@ -89,6 +87,7 @@ + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index d94826395aa..8f74ad5a6e8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -12,9 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; @@ -75,7 +73,6 @@ import java.util.UUID; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; -import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; @@ -398,6 +395,13 @@ public void testGroupIntegration() throws Exception { testSimpleSamlLogin("/login", "Where to?", "marissa4", "saml2"); } + @Test + public void testFavicon_Should_Not_Save() throws Exception { + webDriver.get(baseUrl + "/favicon.ico"); + testSimpleSamlLogin("/login", "Where to?", "marissa4", "saml2"); + } + + private void testSimpleSamlLogin(String firstUrl, String lookfor) throws Exception { testSimpleSamlLogin(firstUrl, lookfor, testAccounts.getUserName(), testAccounts.getPassword()); } From d690c67a72b200261f88460933dce5c952c73ffb Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 7 Mar 2016 13:23:40 -0700 Subject: [PATCH 87/87] Bump release version to 3.2.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d72697bd6c5..aeeae20d263 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.1.1-SNAPSHOT +version=3.2.0