diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java index 9d077ae91d7..e8e06f5c811 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java @@ -3,6 +3,8 @@ import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.ZoneAware; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -15,7 +17,8 @@ import java.util.function.Function; @Slf4j -public class ConfiguratorRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { +public class ConfiguratorRelyingPartyRegistrationRepository + implements RelyingPartyRegistrationRepository, ZoneAware { private final SamlIdentityProviderConfigurator configurator; private final KeyWithCert keyWithCert; @@ -54,7 +57,7 @@ public RelyingPartyRegistration findByRegistrationId(String registrationId) { return buildRelyingPartyRegistration(registrationId, identityProviderDefinition); } } - return null; + return buildDefaultRelyingPartyRegistration(); } private RelyingPartyRegistration buildRelyingPartyRegistration(String registrationId, SamlIdentityProviderDefinition def) { @@ -78,4 +81,41 @@ private RelyingPartyRegistration buildRelyingPartyRegistration(String registrati ) .build(); } + + private RelyingPartyRegistration buildDefaultRelyingPartyRegistration() { + String samlEntityID, samlServiceUri; + IdentityZone zone = retrieveZone(); + if (zone.isUaa()) { + samlEntityID = this.samlEntityID; + samlServiceUri = this.samlEntityID; + } + else if (zone.getConfig() != null && zone.getConfig().getSamlConfig() != null) { + + samlEntityID = zone.getConfig().getSamlConfig().getEntityID(); + samlServiceUri = zone.getSubdomain() + "." + this.samlEntityID; + } + else { + return null; + } + + return RelyingPartyRegistrations + .fromMetadataLocation("dummy-saml-idp-metadata.xml") + .entityId(samlEntityID) +// .nameIdFormat(def.getNameID()) +// .registrationId(registrationId) + .assertionConsumerServiceLocation(assertionConsumerServiceLocationFunction.apply(samlServiceUri)) + .singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(samlServiceUri)) + // TODO: Verify that we should accept both POST and REDIRECT bindings + .singleLogoutServiceBindings(c -> {c.add(Saml2MessageBinding.REDIRECT); c.add(Saml2MessageBinding.POST);}) + .assertingPartyDetails(details -> details + .wantAuthnRequestsSigned(samlSignRequest) + ) + .signingX509Credentials(cred -> cred + .add(Saml2X509Credential.signing(keyWithCert.getPrivateKey(), keyWithCert.getCertificate())) + ) + .decryptionX509Credentials(cred -> cred + .add(Saml2X509Credential.decryption(keyWithCert.getPrivateKey(), keyWithCert.getCertificate())) + ) + .build(); + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java index 754ea1385fa..ec71e1c0e86 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java @@ -1,5 +1,7 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.ZoneAware; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.util.Assert; @@ -34,9 +36,10 @@ public DelegatingRelyingPartyRegistrationRepository(RelyingPartyRegistrationRepo */ @Override public RelyingPartyRegistration findByRegistrationId(String registrationId) { + boolean isDefaultZone = IdentityZoneHolder.isUaa(); for (RelyingPartyRegistrationRepository repository : this.delegates) { RelyingPartyRegistration registration = repository.findByRegistrationId(registrationId); - if (registration != null) { + if (registration != null && (isDefaultZone || repository instanceof ZoneAware)) { return registration; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java new file mode 100644 index 00000000000..d4b39605d8e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java @@ -0,0 +1,7 @@ +package org.cloudfoundry.identity.uaa.zone; + +public interface ZoneAware { + default IdentityZone retrieveZone() { + return IdentityZoneHolder.get(); + } +} 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 7d8d1a3d8d1..de5b69c08c2 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 @@ -220,6 +220,47 @@ void samlSPMetadata() { .contains("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); } + @Test + void samlSPMetadataForZone() { + String zoneId = "testzone1"; + 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") + ); + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + + //create the zone + IdentityZoneConfiguration config = new IdentityZoneConfiguration(); + config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); + config.getSamlConfig().setEntityID(zoneId + "-saml-login"); + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); + + RestTemplate request = new RestTemplate(); + ResponseEntity response = request.getForEntity( + zoneUrl + "/saml/metadata", String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + String metadataXml = response.getBody(); + + // The SAML SP metadata should match the following UAA configs: + // login.entityID + assertThat(metadataXml).contains("entityID=\"" + zoneId + "-saml-login\"") + // TODO: Are DigestMethod and SignatureMethod needed? + // login.saml.signatureAlgorithm + //.contains("") + //.contains("") + // login.saml.signRequest + .contains("AuthnRequestsSigned=\"true\"") + // login.saml.wantAssertionSigned + .contains("WantAssertionsSigned=\"true\"") + // login.saml.nameID +// .contains("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); + .contains("/saml/SSO/alias/" + zoneId + ".cloudfoundry-saml-login"); // TODO: Improve this check + } + @Test void contentTypes() { String loginUrl = baseUrl + "/login";