diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index 8a325dc0a00..761548b64e6 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -57,6 +57,9 @@ public enum ExternalGroupMappingMode { private boolean skipSslValidation = false; private List authnContext; + @JsonIgnore + private String idpEntityId; + public SamlIdentityProviderDefinition() {} public SamlIdentityProviderDefinition clone() { @@ -67,6 +70,7 @@ public SamlIdentityProviderDefinition clone() { SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setMetaDataLocation(metaDataLocation); def.setIdpEntityAlias(idpEntityAlias); + def.setIdpEntityId(idpEntityId); def.setZoneId(zoneId); def.setNameID(nameID); def.setAssertionConsumerIndex(assertionConsumerIndex); @@ -108,6 +112,16 @@ public MetadataLocation getType() { return MetadataLocation.UNKNOWN; } + @JsonIgnore + public String getIdpEntityId() { + return this.idpEntityId; + } + + @JsonIgnore + public void setIdpEntityId(final String idpEntityId) { + this.idpEntityId = idpEntityId; + } + private boolean validateXml(String xml) { if (xml==null || xml.toUpperCase().contains(" getDefaultGroups() { return defaultGroups; } @@ -78,4 +80,12 @@ public boolean isCheckOriginEnabled() { public void setCheckOriginEnabled(boolean checkOriginEnabled) { this.checkOriginEnabled = checkOriginEnabled; } + + public boolean isAllowAllOrigins() { + return this.allowAllOrigins; + } + + public void setAllowAllOrigins(final boolean allowAllOrigins) { + this.allowAllOrigins = allowAllOrigins; + } } 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 240ff723407..6c01aefed77 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 @@ -137,7 +137,7 @@ public ResponseEntity createIdentityProvider(@RequestBody Iden SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), SamlIdentityProviderDefinition.class); definition.setZoneId(zoneId); definition.setIdpEntityAlias(body.getOriginKey()); - samlConfigurator.validateSamlIdentityProviderDefinition(definition); + definition.setIdpEntityId(samlConfigurator.validateSamlIdentityProviderDefinition(definition)); body.setConfig(definition); } 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 0349fe729f6..2686d764468 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 @@ -29,6 +29,8 @@ public interface IdentityProviderProvisioning { IdentityProvider retrieveByOrigin(String origin, String zoneId); + IdentityProvider retrieveByExternId(String externId, String type, String zoneId); + default IdentityProvider retrieveByOriginIgnoreActiveFlag(String origin, String zoneId) { return retrieveByOrigin(origin, zoneId); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java index 89f8eedf1de..0295131013d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java @@ -11,7 +11,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.sql.ResultSet; @@ -26,15 +25,15 @@ public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisi private static Logger logger = LoggerFactory.getLogger(JdbcIdentityProviderProvisioning.class); - public static final String ID_PROVIDER_FIELDS = "id,version,created,lastmodified,name,origin_key,type,config,identity_zone_id,active,alias_id,alias_zid"; + public static final String ID_PROVIDER_FIELDS = "id,version,created,lastmodified,name,origin_key,type,config,identity_zone_id,active,alias_id,alias_zid,external_key"; - public static final String CREATE_IDENTITY_PROVIDER_SQL = "insert into identity_provider(" + ID_PROVIDER_FIELDS + ") values (?,?,?,?,?,?,?,?,?,?,?,?)"; + public static final String CREATE_IDENTITY_PROVIDER_SQL = "insert into identity_provider(" + ID_PROVIDER_FIELDS + ") values (?,?,?,?,?,?,?,?,?,?,?,?,?)"; public static final String IDENTITY_PROVIDERS_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider where identity_zone_id=?"; public static final String IDENTITY_ACTIVE_PROVIDERS_QUERY = IDENTITY_PROVIDERS_QUERY + " and active=?"; - public static final String ID_PROVIDER_UPDATE_FIELDS = "version,lastmodified,name,type,config,active,alias_id,alias_zid".replace(",", "=?,") + "=?"; + public static final String ID_PROVIDER_UPDATE_FIELDS = "version,lastmodified,name,type,config,active,alias_id,alias_zid,external_key".replace(",", "=?,") + "=?"; public static final String UPDATE_IDENTITY_PROVIDER_SQL = "update identity_provider set " + ID_PROVIDER_UPDATE_FIELDS + " where id=? and identity_zone_id=?"; @@ -48,6 +47,8 @@ public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisi public static final String IDENTITY_PROVIDER_BY_ORIGIN_QUERY_ACTIVE = IDENTITY_PROVIDER_BY_ORIGIN_QUERY + " and active = ? "; + public static final String IDENTITY_PROVIDER_BY_EXTERNAL_QUERY = IDENTITY_PROVIDERS_QUERY + " and type=? and external_key=?"; + protected final JdbcTemplate jdbcTemplate; private final RowMapper mapper = new IdentityProviderRowMapper(); @@ -85,9 +86,14 @@ public IdentityProvider retrieveByOriginIgnoreActiveFlag(String origin, String z return jdbcTemplate.queryForObject(IDENTITY_PROVIDER_BY_ORIGIN_QUERY, mapper, origin, zoneId); } + @Override + public IdentityProvider retrieveByExternId(String externId, String type, String zoneId) { + return jdbcTemplate.queryForObject(IDENTITY_PROVIDER_BY_EXTERNAL_QUERY, mapper, zoneId, type, externId); + } + @Override public IdentityProvider create(final IdentityProvider identityProvider, String zoneId) { - validate(identityProvider); + String externId = validate(identityProvider); final String id = UUID.randomUUID().toString(); try { jdbcTemplate.update(CREATE_IDENTITY_PROVIDER_SQL, ps -> { @@ -103,7 +109,8 @@ public IdentityProvider create(final IdentityProvider identityProvider, String z ps.setString(pos++, zoneId); ps.setBoolean(pos++, identityProvider.isActive()); ps.setString(pos++, identityProvider.getAliasId()); - ps.setString(pos, identityProvider.getAliasZid()); + ps.setString(pos++, identityProvider.getAliasZid()); + ps.setString(pos, externId); }); } catch (DuplicateKeyException e) { throw new IdpAlreadyExistsException(e.getMostSpecificCause().getMessage()); @@ -113,7 +120,7 @@ public IdentityProvider create(final IdentityProvider identityProvider, String z @Override public IdentityProvider update(final IdentityProvider identityProvider, String zoneId) { - validate(identityProvider); + String externId = validate(identityProvider); jdbcTemplate.update(UPDATE_IDENTITY_PROVIDER_SQL, ps -> { int pos = 1; @@ -126,6 +133,7 @@ public IdentityProvider update(final IdentityProvider identityProvider, String z ps.setBoolean(pos++, identityProvider.isActive()); ps.setString(pos++, identityProvider.getAliasId()); ps.setString(pos++, identityProvider.getAliasZid()); + ps.setString(pos++, externId); // placeholders in WHERE ps.setString(pos++, identityProvider.getId().trim()); @@ -134,20 +142,25 @@ public IdentityProvider update(final IdentityProvider identityProvider, String z return retrieve(identityProvider.getId(), zoneId); } - protected void validate(IdentityProvider provider) { + private String validate(IdentityProvider provider) { if (provider == null) { throw new NullPointerException("Provider can not be null."); } if (!StringUtils.hasText(provider.getIdentityZoneId())) { throw new DataIntegrityViolationException("Identity zone ID must be set."); } + String externId = null; //ensure that SAML IDPs have redundant fields synchronized if (OriginKeys.SAML.equals(provider.getType()) && provider.getConfig() != null) { SamlIdentityProviderDefinition saml = ObjectUtils.castInstance(provider.getConfig(), SamlIdentityProviderDefinition.class); saml.setIdpEntityAlias(provider.getOriginKey()); saml.setZoneId(provider.getIdentityZoneId()); provider.setConfig(saml); + externId = saml.getIdpEntityId(); + } else if (provider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition externalOAuthIdentityProviderDefinition) { + externId = externalOAuthIdentityProviderDefinition.getIssuer(); } + return externId; } /** @@ -181,17 +194,25 @@ public IdentityProvider mapRow(ResultSet rs, int rowNum) throws SQLException { identityProvider.setOriginKey(rs.getString(pos++)); identityProvider.setType(rs.getString(pos++)); String config = rs.getString(pos++); + identityProvider.setIdentityZoneId(rs.getString(pos++)); + identityProvider.setActive(rs.getBoolean(pos++)); + identityProvider.setAliasId(rs.getString(pos++)); + identityProvider.setAliasZid(rs.getString(pos++)); + String externId = rs.getString(pos); if (StringUtils.hasText(config)) { AbstractIdentityProviderDefinition definition; switch (identityProvider.getType()) { case OriginKeys.SAML: definition = JsonUtils.readValue(config, SamlIdentityProviderDefinition.class); + ((SamlIdentityProviderDefinition) definition).setIdpEntityId(externId); break; case OriginKeys.OAUTH20: definition = JsonUtils.readValue(config, RawExternalOAuthIdentityProviderDefinition.class); + ((RawExternalOAuthIdentityProviderDefinition) definition).setIssuer(externId); break; case OriginKeys.OIDC10: definition = JsonUtils.readValue(config, OIDCIdentityProviderDefinition.class); + ((OIDCIdentityProviderDefinition) definition).setIssuer(externId); break; case OriginKeys.UAA: definition = JsonUtils.readValue(config, UaaIdentityProviderDefinition.class); @@ -210,10 +231,6 @@ public IdentityProvider mapRow(ResultSet rs, int rowNum) throws SQLException { identityProvider.setConfig(definition); } } - identityProvider.setIdentityZoneId(rs.getString(pos++)); - identityProvider.setActive(rs.getBoolean(pos++)); - identityProvider.setAliasId(rs.getString(pos++)); - identityProvider.setAliasZid(rs.getString(pos)); return identityProvider; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfigurator.java index 7ae1f6ec846..8cbb33ddb4e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfigurator.java @@ -8,9 +8,14 @@ import org.cloudfoundry.identity.uaa.util.SessionUtils; import org.cloudfoundry.identity.uaa.util.UaaRandomStringUtil; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.UserConfig; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.util.CollectionUtils; @@ -23,7 +28,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -33,19 +38,26 @@ public class ExternalOAuthProviderConfigurator implements IdentityProviderProvisioning { - private static Logger LOGGER = LoggerFactory.getLogger(ExternalOAuthProviderConfigurator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ExternalOAuthProviderConfigurator.class); private final IdentityProviderProvisioning providerProvisioning; private final OidcMetadataFetcher oidcMetadataFetcher; private final UaaRandomStringUtil uaaRandomStringUtil; + private final IdentityZoneProvisioning identityZoneProvisioning; + private final IdentityZoneManager identityZoneManager; public ExternalOAuthProviderConfigurator( final @Qualifier("identityProviderProvisioning") IdentityProviderProvisioning providerProvisioning, final OidcMetadataFetcher oidcMetadataFetcher, - final UaaRandomStringUtil uaaRandomStringUtil) { + final UaaRandomStringUtil uaaRandomStringUtil, + final @Qualifier("identityZoneProvisioning") IdentityZoneProvisioning identityZoneProvisioning, + final IdentityZoneManager identityZoneManager + ) { this.providerProvisioning = providerProvisioning; this.oidcMetadataFetcher = oidcMetadataFetcher; this.uaaRandomStringUtil = uaaRandomStringUtil; + this.identityZoneProvisioning = identityZoneProvisioning; + this.identityZoneManager = identityZoneManager; } protected OIDCIdentityProviderDefinition overlay(OIDCIdentityProviderDefinition definition) { @@ -119,12 +131,25 @@ private String getCallbackUrlForIdp(String idpOriginKey, String uaaBaseUrl) { } private String getIdpUrlBase(final AbstractExternalOAuthIdentityProviderDefinition definition) { - if (definition instanceof OIDCIdentityProviderDefinition) { - return overlay((OIDCIdentityProviderDefinition) definition).getAuthUrl().toString(); + if (definition instanceof OIDCIdentityProviderDefinition oidcIdentityProviderDefinition) { + return overlay(oidcIdentityProviderDefinition).getAuthUrl().toString(); } return definition.getAuthUrl().toString(); } + private boolean isOriginLoopAllowed(String zoneId, boolean allowed) { + if (!allowed) { + return false; + } + IdentityZoneConfiguration idzConfig; + if (identityZoneManager.getCurrentIdentityZoneId().equals(zoneId)) { + idzConfig = identityZoneManager.getCurrentIdentityZone().getConfig(); + } else { + idzConfig = identityZoneProvisioning.retrieve(zoneId).getConfig(); + } + return idzConfig == null || Optional.of(idzConfig.getUserConfig()).map(UserConfig::isAllowAllOrigins).orElse(true); + } + @Override public IdentityProvider create(IdentityProvider identityProvider, String zoneId) { return providerProvisioning.create(identityProvider, zoneId); @@ -150,11 +175,29 @@ public List retrieveActive(String zoneId) { } public IdentityProvider retrieveByIssuer(String issuer, String zoneId) throws IncorrectResultSizeDataAccessException { + IdentityProvider issuedProvider = null; + boolean originLoopAllowed = true; + try { + issuedProvider = retrieveByExternId(issuer, OIDC10, zoneId); + if (issuedProvider != null && issuedProvider.isActive() + && issuedProvider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition oAuthIdentityProviderDefinition + && oAuthIdentityProviderDefinition.getIssuer().equals(issuer)) { + return issuedProvider; + } + } catch (EmptyResultDataAccessException e) { + originLoopAllowed = isOriginLoopAllowed(zoneId, true); + if (!isOriginLoopAllowed(zoneId, originLoopAllowed)) { + throw new IncorrectResultSizeDataAccessException(String.format("No provider with unique issuer[%s] found", issuer), 1, 0, e); + } + } + if (!isOriginLoopAllowed(zoneId, originLoopAllowed) && issuedProvider == null) { + throw new IncorrectResultSizeDataAccessException(String.format("Active provider with unique issuer[%s] not found", issuer), 1); + } List providers = retrieveAll(true, zoneId) .stream() .filter(p -> OIDC10.equals(p.getType()) && issuer.equals(((OIDCIdentityProviderDefinition) p.getConfig()).getIssuer())) - .collect(Collectors.toList()); + .toList(); if (providers.isEmpty()) { throw new IncorrectResultSizeDataAccessException(String.format("Active provider with issuer[%s] not found", issuer), 1); } else if (providers.size() > 1) { @@ -194,6 +237,15 @@ public IdentityProvider retrieveByOrigin(String origin, String zoneId) { return p; } + @Override + public IdentityProvider retrieveByExternId(String externId, String type, String zoneId) { + IdentityProvider p = providerProvisioning.retrieveByExternId(externId, type, zoneId); + if (p != null && OIDC10.equals(type)) { + p.setConfig(overlay((OIDCIdentityProviderDefinition) p.getConfig())); + } + return p; + } + @Override public IdentityProvider retrieveByOriginIgnoreActiveFlag(String origin, String zoneId) { IdentityProvider p = providerProvisioning.retrieveByOriginIgnoreActiveFlag(origin, zoneId); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java index 1cedd620cfc..856ce3f3fef 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java @@ -4,13 +4,13 @@ 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.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; 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.beans.factory.annotation.Qualifier; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; import org.springframework.stereotype.Component; @@ -73,7 +73,7 @@ public List getIdentityProviderDefinitions(List< * @param providerDefinition - the provider to be added * @throws MetadataProviderException if the system fails to fetch meta data for this provider */ - public synchronized void validateSamlIdentityProviderDefinition(SamlIdentityProviderDefinition providerDefinition) throws MetadataProviderException { + public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityProviderDefinition providerDefinition) throws MetadataProviderException { ExtendedMetadataDelegate added, deleted = null; if (providerDefinition == null) { throw new NullPointerException(); @@ -91,26 +91,36 @@ public synchronized void validateSamlIdentityProviderDefinition(SamlIdentityProv throw new MetadataProviderException("Emtpy entityID for SAML provider with zoneId:" + providerDefinition.getZoneId() + " and origin:" + providerDefinition.getIdpEntityAlias()); } - boolean entityIDexists = false; + boolean entityIDexists = entityIdExists(entityIDToBeAdded, providerDefinition.getZoneId()); - for (SamlIdentityProviderDefinition existing : getIdentityProviderDefinitions()) { - ConfigMetadataProvider existingProvider = (ConfigMetadataProvider) getExtendedMetadataDelegate(existing).getDelegate(); - if (entityIDToBeAdded.equals(existingProvider.getEntityID()) && - !(existing.getUniqueAlias().equals(clone.getUniqueAlias()))) { - entityIDexists = true; - break; + if (!entityIDexists) { + for (SamlIdentityProviderDefinition existing : getIdentityProviderDefinitions()) { + ConfigMetadataProvider existingProvider = (ConfigMetadataProvider) getExtendedMetadataDelegate(existing).getDelegate(); + if (entityIDToBeAdded.equals(existingProvider.getEntityID()) && !(existing.getUniqueAlias().equals(clone.getUniqueAlias()))) { + entityIDexists = true; + break; + } } } if (entityIDexists) { throw new MetadataProviderException("Duplicate entity ID:" + entityIDToBeAdded); } + return entityIDToBeAdded; } public ExtendedMetadataDelegate getExtendedMetadataDelegateFromCache(SamlIdentityProviderDefinition def) throws MetadataProviderException { return getExtendedMetadataDelegate(def); } + private boolean entityIdExists(String entityId, String zoneId) { + try { + return providerProvisioning.retrieveByExternId(entityId, OriginKeys.SAML, zoneId) != null; + } catch (EmptyResultDataAccessException e) { + return false; + } + } + public ExtendedMetadataDelegate getExtendedMetadataDelegate(SamlIdentityProviderDefinition def) throws MetadataProviderException { ExtendedMetadataDelegate metadata; switch (def.getType()) { diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_110__Add_ExternalKey_To_Identity_Provider.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_110__Add_ExternalKey_To_Identity_Provider.sql new file mode 100644 index 00000000000..990b30d9338 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_110__Add_ExternalKey_To_Identity_Provider.sql @@ -0,0 +1,3 @@ +-- add column external_key for oauth2,oidc,saml2 IdPs +ALTER TABLE identity_provider ADD COLUMN external_key CLOB DEFAULT NULL; +CREATE UNIQUE INDEX identity_provider_ext_key_zid__idx on identity_provider (identity_zone_id,type,external_key); \ No newline at end of file diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_110__Add_ExternalKey_To_Identity_Provider.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_110__Add_ExternalKey_To_Identity_Provider.sql new file mode 100644 index 00000000000..cc1d1ac5363 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_110__Add_ExternalKey_To_Identity_Provider.sql @@ -0,0 +1,3 @@ +-- add column external_key for oauth2,oidc,saml2 IdPs +ALTER TABLE identity_provider ADD COLUMN external_key TEXT DEFAULT NULL; +CREATE UNIQUE INDEX identity_provider_ext_key_zid__idx on identity_provider (identity_zone_id,type,external_key); \ No newline at end of file diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_110__Add_ExternalKey_To_Identity_Provider.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_110__Add_ExternalKey_To_Identity_Provider.sql new file mode 100644 index 00000000000..cc1d1ac5363 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_110__Add_ExternalKey_To_Identity_Provider.sql @@ -0,0 +1,3 @@ +-- add column external_key for oauth2,oidc,saml2 IdPs +ALTER TABLE identity_provider ADD COLUMN external_key TEXT DEFAULT NULL; +CREATE UNIQUE INDEX identity_provider_ext_key_zid__idx on identity_provider (identity_zone_id,type,external_key); \ No newline at end of file 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 f6fca6c0c53..6022834edf3 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -25,9 +25,12 @@ 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.Links; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -104,6 +107,8 @@ class LoginInfoEndpointTests { private IdentityProviderProvisioning mockIdentityProviderProvisioning; private IdentityProvider uaaIdentityProvider; private IdentityZoneConfiguration originalConfiguration; + private IdentityZoneProvisioning identityZoneProvisioning; + private IdentityZoneManager identityZoneManager; private ExternalOAuthProviderConfigurator configurator; @BeforeEach @@ -119,6 +124,8 @@ void setUp() { when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitionsForZone(any())).thenReturn(emptyList()); mockIdentityProviderProvisioning = mock(IdentityProviderProvisioning.class); uaaIdentityProvider = new IdentityProvider(); + identityZoneProvisioning = mock(IdentityZoneProvisioning.class); + identityZoneManager = new IdentityZoneManagerImpl(); when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(eq(OriginKeys.UAA), anyString())).thenReturn(uaaIdentityProvider); when(mockIdentityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.LDAP), anyString())).thenReturn(new IdentityProvider()); idps = getIdps(); @@ -127,7 +134,7 @@ void setUp() { IdentityZoneHolder.get().setConfig(new IdentityZoneConfiguration()); UaaRandomStringUtil randomStringUtil = mock(UaaRandomStringUtil.class); when(randomStringUtil.getSecureRandom(anyInt())).thenReturn("01234567890123456789012345678901234567890123456789"); - configurator = new ExternalOAuthProviderConfigurator(mockIdentityProviderProvisioning, mockOidcMetadataFetcher, randomStringUtil); + configurator = new ExternalOAuthProviderConfigurator(mockIdentityProviderProvisioning, mockOidcMetadataFetcher, randomStringUtil, identityZoneProvisioning, identityZoneManager); extendedModelMap = new ExtendedModelMap(); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java index 6600d4e69af..63a501a3d01 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java @@ -43,7 +43,10 @@ import org.cloudfoundry.identity.uaa.util.UaaRandomStringUtil; import org.cloudfoundry.identity.uaa.util.UaaTokenUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -141,6 +144,8 @@ class ExternalOAuthAuthenticationManagerIT { private InMemoryUaaUserDatabase userDatabase; private ExternalOAuthCodeToken xCodeToken; private ApplicationEventPublisher publisher; + private IdentityZoneProvisioning identityZoneProvisioning; + private IdentityZoneManager identityZoneManager; private static final String CODE = "the_code"; private static final String ORIGIN = "the_origin"; @@ -195,6 +200,8 @@ void setUp() throws Exception { IdentityZoneHolder.get().getConfig().getTokenPolicy().setKeys(Collections.singletonMap(keyName, PRIVATE_KEY)); provisioning = mock(IdentityProviderProvisioning.class); + identityZoneProvisioning = mock(IdentityZoneProvisioning.class); + identityZoneManager = new IdentityZoneManagerImpl(); ScimGroupExternalMembershipManager externalMembershipManager = mock(ScimGroupExternalMembershipManager.class); for (String scope : SCOPES_LIST) { @@ -218,7 +225,9 @@ void setUp() throws Exception { new ExternalOAuthProviderConfigurator( provisioning, oidcMetadataFetcher, - mock(UaaRandomStringUtil.class)) + mock(UaaRandomStringUtil.class), + identityZoneProvisioning, + identityZoneManager) ); externalOAuthAuthenticationManager = spy(new ExternalOAuthAuthenticationManager(externalOAuthProviderConfigurator, trustingRestTemplate, nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_URL), oidcMetadataFetcher)); externalOAuthAuthenticationManager.setUserDatabase(userDatabase); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfiguratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfiguratorTests.java index e9624285b05..00a9d3ce09b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfiguratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfiguratorTests.java @@ -8,6 +8,8 @@ import org.cloudfoundry.identity.uaa.provider.RawExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.UaaRandomStringUtil; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,6 +71,10 @@ class ExternalOAuthProviderConfiguratorTests { private IdentityProviderProvisioning mockIdentityProviderProvisioning; @Mock private UaaRandomStringUtil mockUaaRandomStringUtil; + @Mock + private IdentityZoneProvisioning identityZoneProvisioning; + @Mock + private IdentityZoneManager identityZoneManager; private OIDCIdentityProviderDefinition config; private IdentityProvider oidcProvider; @@ -96,7 +102,9 @@ void setup() throws MalformedURLException { configurator = spy(new ExternalOAuthProviderConfigurator( mockIdentityProviderProvisioning, mockOidcMetadataFetcher, - mockUaaRandomStringUtil)); + mockUaaRandomStringUtil, + identityZoneProvisioning, + identityZoneManager)); config = new OIDCIdentityProviderDefinition(); config.setDiscoveryUrl(new URL("https://accounts.google.com/.well-known/openid-configuration")); @@ -146,6 +154,8 @@ void retrieve_by_issuer() throws Exception { when(mockIdentityProviderProvisioning.retrieveAll(eq(true), anyString())).thenReturn(Arrays.asList(oidcProvider, oauthProvider, new IdentityProvider<>().setType(LDAP))); String issuer = "https://accounts.google.com"; + when(identityZoneManager.getCurrentIdentityZoneId()).thenReturn(IdentityZone.getUaaZoneId()); + when(identityZoneManager.getCurrentIdentityZone()).thenReturn(IdentityZone.getUaa()); doAnswer(invocation -> { OIDCIdentityProviderDefinition definition = invocation.getArgument(0); definition.setIssuer(issuer); @@ -164,6 +174,8 @@ void retrieve_by_issuer() throws Exception { void issuer_not_found() { String issuer = "https://accounts.google.com"; when(mockIdentityProviderProvisioning.retrieveAll(eq(true), anyString())).thenReturn(Arrays.asList(oauthProvider, new IdentityProvider<>().setType(LDAP))); + when(identityZoneManager.getCurrentIdentityZoneId()).thenReturn(IdentityZone.getUaaZoneId()); + when(identityZoneManager.getCurrentIdentityZone()).thenReturn(IdentityZone.getUaa()); assertThrowsWithMessageThat( IncorrectResultSizeDataAccessException.class, () -> configurator.retrieveByIssuer(issuer, IdentityZone.getUaaZoneId()), @@ -175,6 +187,8 @@ void issuer_not_found() { void duplicate_issuer_found() throws Exception { String issuer = "https://accounts.google.com"; when(mockIdentityProviderProvisioning.retrieveAll(eq(true), anyString())).thenReturn(Arrays.asList(oidcProvider, oidcProvider, oauthProvider, new IdentityProvider<>().setType(LDAP))); + when(identityZoneManager.getCurrentIdentityZoneId()).thenReturn(IdentityZone.getUaaZoneId()); + when(identityZoneManager.getCurrentIdentityZone()).thenReturn(IdentityZone.getUaa()); doAnswer(invocation -> { OIDCIdentityProviderDefinition definition = invocation.getArgument(0); definition.setIssuer(issuer); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java index 36348db6f0d..13ac9ef15c7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java @@ -92,7 +92,7 @@ private static void seedUaaZoneSimilarToHowTheRealFlywayMigrationDoesIt(JdbcTemp for (String origin : origins) { String identityProviderId = UUID.randomUUID().toString(); originMap.put(origin, identityProviderId); - jdbcTemplate.update("insert into identity_provider VALUES (?,?,?,0,?,?,?,?,null,?,null,null)",identityProviderId, t, t, uaa.getId(),origin,origin,origin,true); + jdbcTemplate.update("insert into identity_provider VALUES (?,?,?,0,?,?,?,?,null,?,null,null,null)",identityProviderId, t, t, uaa.getId(),origin,origin,origin,true); } jdbcTemplate.update("update oauth_client_details set identity_zone_id = ?",uaa.getId()); List clientIds = jdbcTemplate.queryForList("SELECT client_id from oauth_client_details", String.class);