Skip to content

Commit

Permalink
wip: zoned metadata fixes and zoned login
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Chen <peter-h.chen@broadcom.com>
  • Loading branch information
peterhaochen47 committed Jul 12, 2024
1 parent 748f5f2 commit d5abae0
Show file tree
Hide file tree
Showing 14 changed files with 539 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package org.cloudfoundry.identity.uaa.provider.saml;

import lombok.extern.slf4j.Slf4j;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
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.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Optional;

@Slf4j
public class ConfiguratorRelyingPartyRegistrationRepository
Expand All @@ -20,13 +21,17 @@ public class ConfiguratorRelyingPartyRegistrationRepository
private final KeyWithCert keyWithCert;
private final String samlEntityID;

public ConfiguratorRelyingPartyRegistrationRepository(@Qualifier("samlEntityID") String samlEntityID,
private final String samlEntityIDAlias;

public ConfiguratorRelyingPartyRegistrationRepository(String samlEntityID,
String samlEntityIDAlias,
KeyWithCert keyWithCert,
SamlIdentityProviderConfigurator configurator) {
Assert.notNull(configurator, "configurator cannot be null");
this.configurator = configurator;
this.keyWithCert = keyWithCert;
this.samlEntityID = samlEntityID;
this.samlEntityIDAlias = samlEntityIDAlias;
}

/**
Expand All @@ -38,39 +43,49 @@ public ConfiguratorRelyingPartyRegistrationRepository(@Qualifier("samlEntityID")
*/
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
IdentityZone currentZone = retrieveZone();
String currentZoneId = Optional.ofNullable(currentZone.getId()).orElse(OriginKeys.UAA);

// TODO: possibly call getIdentityProviderDefinitionsForZone(currentZone), and don't have to check zoneId in the if statement
List<SamlIdentityProviderDefinition> identityProviderDefinitions = configurator.getIdentityProviderDefinitions();
for (SamlIdentityProviderDefinition identityProviderDefinition : identityProviderDefinitions) {
if (identityProviderDefinition.getIdpEntityAlias().equals(registrationId)) {
if (identityProviderDefinition.getIdpEntityAlias().equals(registrationId) && currentZoneId.equals(identityProviderDefinition.getZoneId())) {

String zonedSamlEntityID = getZoneEntityId(currentZone);
String zonedSamlEntityAlias = getZoneEntityAlias(currentZone);
boolean requestSigned = currentZone.getConfig().getSamlConfig().isRequestSigned();

IdentityZone zone = retrieveZone();
return RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
samlEntityID, identityProviderDefinition.getNameID(),
zonedSamlEntityID, identityProviderDefinition.getNameID(),
keyWithCert, identityProviderDefinition.getMetaDataLocation(),
registrationId, zone.getConfig().getSamlConfig().isRequestSigned());
registrationId, zonedSamlEntityAlias, requestSigned);
}
}
return buildDefaultRelyingPartyRegistration();
return null;
}

private RelyingPartyRegistration buildDefaultRelyingPartyRegistration() {
String samlEntityID, samlServiceUri;
IdentityZone zone = retrieveZone();
if (zone.isUaa()) {
samlEntityID = this.samlEntityID;
samlServiceUri = this.samlEntityID;
private String getZoneEntityId(IdentityZone currentZone) {
String id = currentZone.getConfig().getSamlConfig().getEntityID();
if (id == null) {
id = samlEntityID;
}
if (currentZone.isUaa()) {
return id;
}
else if (zone.getConfig() != null && zone.getConfig().getSamlConfig() != null) {
return "%s.%s".formatted(currentZone.getSubdomain(), id);
}

samlEntityID = zone.getConfig().getSamlConfig().getEntityID();
samlServiceUri = zone.getSubdomain() + "." + this.samlEntityID;
private String getZoneEntityAlias(IdentityZone currentZone) {
String alias = currentZone.getConfig().getSamlConfig().getEntityID();
if (alias == null) {
alias = samlEntityIDAlias;
if (alias == null) {
alias = samlEntityID;
}
}
else {
return null;
if (currentZone.isUaa()) {
return alias;
}

return RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
samlEntityID, null,
keyWithCert, "dummy-saml-idp-metadata.xml", null,
samlServiceUri, zone.getConfig().getSamlConfig().isRequestSigned());
return "%s.%s".formatted(currentZone.getSubdomain(), alias);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.cloudfoundry.identity.uaa.provider.saml;

import org.cloudfoundry.identity.uaa.util.KeyWithCert;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
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;

/**
* A {@link RelyingPartyRegistrationRepository} that always returns a default {@link RelyingPartyRegistrationRepository}.
*/
public class DefaultRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository, ZoneAware {
public static final String CLASSPATH_DUMMY_SAML_IDP_METADATA_XML = "classpath:dummy-saml-idp-metadata.xml";

private final KeyWithCert keyWithCert;
private final String samlEntityID;

private final String samlEntityIDAlias; // TODO consider renaming this to indicate UAA wide

public DefaultRelyingPartyRegistrationRepository(String samlEntityID,
String samlEntityIDAlias,
KeyWithCert keyWithCert) {
this.keyWithCert = keyWithCert;
this.samlEntityID = samlEntityID;
this.samlEntityIDAlias = samlEntityIDAlias;
}

/**
* Returns the relying party registration identified by the provided
* {@code registrationId}, or {@code null} if not found.
*
* @param registrationId the registration identifier
* @return the {@link RelyingPartyRegistration} if found, otherwise {@code null}
*/
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
IdentityZone zone = retrieveZone();

boolean requestSigned = true;
if (zone.getConfig() != null && zone.getConfig().getSamlConfig() != null) {
requestSigned = zone.getConfig().getSamlConfig().isRequestSigned();
}

String zonedSamlEntityID;
if (!zone.isUaa() && zone.getConfig() != null && zone.getConfig().getSamlConfig() != null && zone.getConfig().getSamlConfig().getEntityID() != null) {
zonedSamlEntityID = zone.getConfig().getSamlConfig().getEntityID();
} else {
zonedSamlEntityID = this.samlEntityID;
}

// TODO is this repeating code?
String zonedSamlEntityIDAlias;
if (zone.isUaa()) { // default zone
zonedSamlEntityIDAlias = samlEntityIDAlias;
} else { // non-default zone
zonedSamlEntityIDAlias = "%s.%s".formatted(zone.getSubdomain(), samlEntityIDAlias);
}

return RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
zonedSamlEntityID, null,
keyWithCert, CLASSPATH_DUMMY_SAML_IDP_METADATA_XML, registrationId,
zonedSamlEntityIDAlias, requestSigned);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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;
Expand All @@ -13,7 +12,7 @@
* A {@link RelyingPartyRegistrationRepository} that delegates to a list of other {@link RelyingPartyRegistrationRepository}
* instances.
*/
public class DelegatingRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
public class DelegatingRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository, ZoneAware {

private final List<RelyingPartyRegistrationRepository> delegates;

Expand All @@ -36,11 +35,13 @@ public DelegatingRelyingPartyRegistrationRepository(RelyingPartyRegistrationRepo
*/
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
boolean isDefaultZone = IdentityZoneHolder.isUaa();
boolean isDefaultZone = retrieveZone().isUaa();
for (RelyingPartyRegistrationRepository repository : this.delegates) {
RelyingPartyRegistration registration = repository.findByRegistrationId(registrationId);
if (registration != null && (isDefaultZone || repository instanceof ZoneAware)) {
return registration;
if (isDefaultZone || repository instanceof ZoneAware) {
RelyingPartyRegistration registration = repository.findByRegistrationId(registrationId);
if (registration != null) {
return registration;
}
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,12 @@ private RelyingPartyRegistrationBuilder() {
throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

public static RelyingPartyRegistration buildRelyingPartyRegistration(
String samlEntityID, String samlSpNameId,
KeyWithCert keyWithCert,
String metadataLocation, String rpRegstrationId, boolean requestSigned) {
return buildRelyingPartyRegistration(samlEntityID, samlSpNameId,
keyWithCert, metadataLocation, rpRegstrationId,
samlEntityID, requestSigned);
}

public static RelyingPartyRegistration buildRelyingPartyRegistration(
String samlEntityID, String samlSpNameId,
KeyWithCert keyWithCert, String metadataLocation,
String rpRegstrationId, String samlServiceUri, boolean requestSigned) {
SamlIdentityProviderDefinition.MetadataLocation type = SamlIdentityProviderDefinition.getType(metadataLocation);
String rpRegstrationId, String samlSpAlias, boolean requestSigned) {

SamlIdentityProviderDefinition.MetadataLocation type = SamlIdentityProviderDefinition.getType(metadataLocation);
RelyingPartyRegistration.Builder builder;
if (type == SamlIdentityProviderDefinition.MetadataLocation.DATA) {
try (InputStream stringInputStream = new ByteArrayInputStream(metadataLocation.getBytes())) {
Expand All @@ -51,14 +42,17 @@ public static RelyingPartyRegistration buildRelyingPartyRegistration(
builder = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation);
}

// fallback to entityId if alias is not provided TODO has the falling back already happened?
samlSpAlias = samlSpAlias == null ? samlEntityID : samlSpAlias;

builder.entityId(samlEntityID);
if (samlSpNameId != null) builder.nameIdFormat(samlSpNameId);
if (rpRegstrationId != null) builder.registrationId(rpRegstrationId);
return builder
.assertionConsumerServiceLocation(assertionConsumerServiceLocationFunction.apply(samlServiceUri))
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(samlServiceUri))
.singleLogoutServiceLocation(singleLogoutServiceLocationFunction.apply(samlServiceUri))
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(samlServiceUri))
.assertionConsumerServiceLocation(assertionConsumerServiceLocationFunction.apply(samlSpAlias))
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(samlSpAlias))
.singleLogoutServiceLocation(singleLogoutServiceLocationFunction.apply(samlSpAlias))
.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(samlSpAlias))
// Accept both POST and REDIRECT bindings
.singleLogoutServiceBindings(c -> {
c.add(Saml2MessageBinding.REDIRECT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public class SamlConfigProps {

private String activeKeyId;

private String entityIDAlias;

private Map<String, SamlKey> keys;

private Boolean wantAssertionSigned = true;

private Boolean signRequest = true;

public SamlKey getActiveSamlKey() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(SamlIdenti

List<RelyingPartyRegistration> relyingPartyRegistrations = new ArrayList<>();

String uaaWideSamlEntityIDAlias = samlConfigProps.getEntityIDAlias() != null ? samlConfigProps.getEntityIDAlias() : samlEntityID;

@SuppressWarnings("java:S125")
// Spring Security requires at least one relyingPartyRegistration before SAML SP metadata generation;
// and each relyingPartyRegistration needs to contain the SAML IDP metadata.
Expand All @@ -64,23 +66,25 @@ RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(SamlIdenti
// here to ensure that the SAML SP metadata will always be present,
// even when there are no SAML IDPs configured.
// See relevant issue: https://github.com/spring-projects/spring-security/issues/11369
RelyingPartyRegistration defaultRelyingPartyRegistration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
samlEntityID, samlSpNameID, keyWithCert, CLASSPATH_DUMMY_SAML_IDP_METADATA_XML, DEFAULT_REGISTRATION_ID, samlConfigProps.getSignRequest());
relyingPartyRegistrations.add(defaultRelyingPartyRegistration);
RelyingPartyRegistration exampleRelyingPartyRegistration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
samlEntityID, samlSpNameID, keyWithCert, CLASSPATH_DUMMY_SAML_IDP_METADATA_XML, DEFAULT_REGISTRATION_ID, uaaWideSamlEntityIDAlias, samlConfigProps.getSignRequest());
relyingPartyRegistrations.add(exampleRelyingPartyRegistration);

for (SamlIdentityProviderDefinition samlIdentityProviderDefinition : bootstrapSamlIdentityProviderData.getIdentityProviderDefinitions()) {
relyingPartyRegistrations.add(
RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(
samlEntityID, samlSpNameID, keyWithCert,
samlIdentityProviderDefinition.getMetaDataLocation(),
samlIdentityProviderDefinition.getIdpEntityAlias(),
uaaWideSamlEntityIDAlias,
samlConfigProps.getSignRequest())
);
}

InMemoryRelyingPartyRegistrationRepository bootstrapRepo = new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistrations);
ConfiguratorRelyingPartyRegistrationRepository configuratorRepo = new ConfiguratorRelyingPartyRegistrationRepository(samlEntityID, keyWithCert, samlIdentityProviderConfigurator);
return new DelegatingRelyingPartyRegistrationRepository(bootstrapRepo, configuratorRepo);
ConfiguratorRelyingPartyRegistrationRepository configuratorRepo = new ConfiguratorRelyingPartyRegistrationRepository(samlEntityID, uaaWideSamlEntityIDAlias, keyWithCert, samlIdentityProviderConfigurator);
DefaultRelyingPartyRegistrationRepository defaultRepo = new DefaultRelyingPartyRegistrationRepository(samlEntityID, uaaWideSamlEntityIDAlias, keyWithCert);
return new DelegatingRelyingPartyRegistrationRepository(bootstrapRepo, configuratorRepo, defaultRepo);
}

@Autowired
Expand Down
Loading

0 comments on commit d5abae0

Please sign in to comment.