Support to define compatible mappers for (new) Identity Providers
- Also allows to use existing mappers for custom Identity Providers without having to change those mappers Closes #21154
This commit is contained in:
parent
442adfa495
commit
a68ad55a37
7 changed files with 138 additions and 24 deletions
|
@ -166,4 +166,5 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||
public IdentityProviderDataMarshaller getMarshaller() {
|
||||
return new DefaultDataMarshaller();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<C extends IdentityProviderModel> 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<C extends IdentityProviderModel> extends Provi
|
|||
*/
|
||||
IdentityProviderDataMarshaller getMarshaller();
|
||||
|
||||
/**
|
||||
* Checks whether a mapper is supported for this Identity Provider.
|
||||
*/
|
||||
default boolean isMapperSupported(IdentityProviderMapper mapper) {
|
||||
List<String> compatibleIdps = Arrays.asList(mapper.getCompatibleProviders());
|
||||
return compatibleIdps.contains(IdentityProviderMapper.ANY_PROVIDER)
|
||||
|| compatibleIdps.contains(getConfig().getProviderId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,13 +296,12 @@ 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 -> {
|
||||
.filter(identityProviderInstance::isMapperSupported)
|
||||
.map(mapper -> {
|
||||
IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation();
|
||||
rep.setId(mapper.getId());
|
||||
rep.setCategory(mapper.getDisplayCategory());
|
||||
|
@ -310,9 +312,6 @@ public class IdentityProviderResource {
|
|||
.collect(Collectors.toList()));
|
||||
return rep;
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(IdentityProviderMapperTypeRepresentation::getId, Function.identity()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <daniel.fesenmeyer@bosch.com>
|
||||
*/
|
||||
public class OverwrittenMappersTestIdentityProvider extends KeycloakOIDCIdentityProvider {
|
||||
|
||||
public OverwrittenMappersTestIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) {
|
||||
super(session, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMapperSupported(IdentityProviderMapper mapper) {
|
||||
List<String> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <daniel.fesenmeyer@bosch.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -16,4 +16,5 @@
|
|||
#
|
||||
|
||||
org.keycloak.testsuite.broker.oidc.LegacyIdIdentityProviderFactory
|
||||
org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProviderFactory
|
||||
org.keycloak.testsuite.broker.oidc.TestKeycloakOidcIdentityProviderFactory
|
|
@ -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<String> expectedMapperTypes = kcOidcProvider.getMapperTypes().keySet();
|
||||
|
||||
IdentityProviderResource testProvider = realm.identityProviders().get(testProviderId);
|
||||
Set<String> actualMapperTypes = testProvider.getMapperTypes().keySet();
|
||||
|
||||
assertThat(actualMapperTypes, equalTo(expectedMapperTypes));
|
||||
}
|
||||
|
||||
private void assertMapperTypes(Map<String, IdentityProviderMapperTypeRepresentation> mapperTypes, String ... mapperIds) {
|
||||
Set<String> expected = new HashSet<>();
|
||||
expected.add("hardcoded-user-session-attribute-idp-mapper");
|
||||
|
|
Loading…
Reference in a new issue