diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java index c485760637..e07bfb284c 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java @@ -166,4 +166,5 @@ public abstract class AbstractIdentityProvider public IdentityProviderDataMarshaller getMarshaller() { return new DefaultDataMarshaller(); } + } diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java index 10e27e09d8..739aec1678 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/IdentityProvider.java @@ -29,6 +29,9 @@ import org.keycloak.sessions.AuthenticationSessionModel; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; +import java.util.Arrays; +import java.util.List; + /** * @author Pedro Igor */ @@ -74,6 +77,8 @@ public interface IdentityProvider extends Provi Response error(String message); } + C getConfig(); + void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context); void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context); @@ -132,4 +137,13 @@ public interface IdentityProvider extends Provi */ IdentityProviderDataMarshaller getMarshaller(); + /** + * Checks whether a mapper is supported for this Identity Provider. + */ + default boolean isMapperSupported(IdentityProviderMapper mapper) { + List compatibleIdps = Arrays.asList(mapper.getCompatibleProviders()); + return compatibleIdps.contains(IdentityProviderMapper.ANY_PROVIDER) + || compatibleIdps.contains(getConfig().getProviderId()); + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java index e5ac4e0b13..98e4f72fc2 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java @@ -64,9 +64,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -243,13 +241,14 @@ public class IdentityProviderResource { } - private IdentityProviderFactory getIdentityProviderFactory() { + private IdentityProviderFactory getIdentityProviderFactory() { + String providerId = identityProviderModel.getProviderId(); return Stream.concat(session.getKeycloakSessionFactory().getProviderFactoriesStream(IdentityProvider.class), session.getKeycloakSessionFactory().getProviderFactoriesStream(SocialIdentityProvider.class)) - .filter(providerFactory -> Objects.equals(providerFactory.getId(), identityProviderModel.getProviderId())) + .filter(providerFactory -> Objects.equals(providerFactory.getId(), providerId)) .map(IdentityProviderFactory.class::cast) .findFirst() - .orElse(null); + .orElseThrow(() -> new IllegalStateException("IDP not found by Provider ID: " + providerId)); } /** @@ -271,13 +270,17 @@ public class IdentityProviderResource { } try { - IdentityProviderFactory factory = getIdentityProviderFactory(); - return factory.create(session, identityProviderModel).export(session.getContext().getUri(), realm, format); + return createIdentityProviderInstance().export(session.getContext().getUri(), realm, format); } catch (Exception e) { throw ErrorResponse.error("Could not export public broker configuration for identity provider [" + identityProviderModel.getProviderId() + "].", Response.Status.NOT_FOUND); } } + private IdentityProvider createIdentityProviderInstance() { + IdentityProviderFactory factory = getIdentityProviderFactory(); + return factory.create(session, identityProviderModel); + } + /** * Get mapper types for identity provider */ @@ -293,26 +296,22 @@ public class IdentityProviderResource { throw new jakarta.ws.rs.NotFoundException(); } + IdentityProvider identityProviderInstance = createIdentityProviderInstance(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); return sessionFactory.getProviderFactoriesStream(IdentityProviderMapper.class) .map(IdentityProviderMapper.class::cast) - .map(mapper -> Arrays.stream(mapper.getCompatibleProviders()) - .filter(type -> Objects.equals(IdentityProviderMapper.ANY_PROVIDER, type) || - Objects.equals(identityProviderModel.getProviderId(), type)) - .map(type -> { - IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation(); - rep.setId(mapper.getId()); - rep.setCategory(mapper.getDisplayCategory()); - rep.setName(mapper.getDisplayType()); - rep.setHelpText(mapper.getHelpText()); - rep.setProperties(mapper.getConfigProperties().stream() - .map(ModelToRepresentation::toRepresentation) - .collect(Collectors.toList())); - return rep; - }) - .findFirst() - .orElse(null)) - .filter(Objects::nonNull) + .filter(identityProviderInstance::isMapperSupported) + .map(mapper -> { + IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation(); + rep.setId(mapper.getId()); + rep.setCategory(mapper.getDisplayCategory()); + rep.setName(mapper.getDisplayType()); + rep.setHelpText(mapper.getHelpText()); + rep.setProperties(mapper.getConfigProperties().stream() + .map(ModelToRepresentation::toRepresentation) + .collect(Collectors.toList())); + return rep; + }) .collect(Collectors.toMap(IdentityProviderMapperTypeRepresentation::getId, Function.identity())); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProvider.java new file mode 100644 index 0000000000..1cec299bfd --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProvider.java @@ -0,0 +1,30 @@ +package org.keycloak.testsuite.broker.oidc; + +import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider; +import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.models.KeycloakSession; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Daniel Fesenmeyer + */ +public class OverwrittenMappersTestIdentityProvider extends KeycloakOIDCIdentityProvider { + + public OverwrittenMappersTestIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) { + super(session, config); + } + + @Override + public boolean isMapperSupported(IdentityProviderMapper mapper) { + List compatibleIdps = Arrays.asList(mapper.getCompatibleProviders()); + + // provide the same mappers as are available for the parent provider (Keycloak-OIDC) + return compatibleIdps.contains(IdentityProviderMapper.ANY_PROVIDER) + || compatibleIdps.contains(KeycloakOIDCIdentityProviderFactory.PROVIDER_ID); + } + +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProviderFactory.java new file mode 100644 index 0000000000..2b4cd13ebe --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/broker/oidc/OverwrittenMappersTestIdentityProviderFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.keycloak.testsuite.broker.oidc; + +import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; + +/** + * @author Daniel Fesenmeyer + */ +public class OverwrittenMappersTestIdentityProviderFactory extends OIDCIdentityProviderFactory { + + public static final String PROVIDER_ID = "overwritten-mappers-test-id-idp"; + + @Override + public String getName() { + return PROVIDER_ID; + } + + @Override + public KeycloakOIDCIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { + return new OverwrittenMappersTestIdentityProvider(session, new OIDCIdentityProviderConfig(model)); + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory index 8f3523dbbe..875514d424 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory @@ -16,4 +16,5 @@ # org.keycloak.testsuite.broker.oidc.LegacyIdIdentityProviderFactory +org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProviderFactory org.keycloak.testsuite.broker.oidc.TestKeycloakOidcIdentityProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index b216cca24c..b1edfe110f 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -52,6 +52,7 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser; import org.keycloak.saml.processing.core.util.XMLSignatureUtil; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep; +import org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProviderFactory; import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.KeyUtils; @@ -603,6 +604,27 @@ public class IdentityProviderTest extends AbstractAdminTest { assertMapperTypes(mapperTypes, "saml-user-attribute-idp-mapper", "saml-role-idp-mapper", "saml-username-idp-mapper", "saml-advanced-role-idp-mapper", "saml-advanced-group-idp-mapper", "saml-xpath-attribute-idp-mapper"); } + @Test + public void mapperTypesCanBeOverwritten() { + String kcOidcProviderId = "keycloak-oidc"; + create(createRep(kcOidcProviderId, kcOidcProviderId)); + + String testProviderId = OverwrittenMappersTestIdentityProviderFactory.PROVIDER_ID; + create(createRep(testProviderId, testProviderId)); + + /* + * in the test provider, we have overwritten the mapper types to be the same as supported by "keycloak-oidc", so + * the "keycloak-oidc" mappers are the expected mappers for the test provider + */ + IdentityProviderResource kcOidcProvider = realm.identityProviders().get(kcOidcProviderId); + Set expectedMapperTypes = kcOidcProvider.getMapperTypes().keySet(); + + IdentityProviderResource testProvider = realm.identityProviders().get(testProviderId); + Set actualMapperTypes = testProvider.getMapperTypes().keySet(); + + assertThat(actualMapperTypes, equalTo(expectedMapperTypes)); + } + private void assertMapperTypes(Map mapperTypes, String ... mapperIds) { Set expected = new HashSet<>(); expected.add("hardcoded-user-session-attribute-idp-mapper");