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() {
|
public IdentityProviderDataMarshaller getMarshaller() {
|
||||||
return new DefaultDataMarshaller();
|
return new DefaultDataMarshaller();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,9 @@ import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
|
@ -74,6 +77,8 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
||||||
Response error(String message);
|
Response error(String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
C getConfig();
|
||||||
|
|
||||||
|
|
||||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
|
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
|
||||||
void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
|
void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
|
||||||
|
@ -132,4 +137,13 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
||||||
*/
|
*/
|
||||||
IdentityProviderDataMarshaller getMarshaller();
|
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.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
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),
|
return Stream.concat(session.getKeycloakSessionFactory().getProviderFactoriesStream(IdentityProvider.class),
|
||||||
session.getKeycloakSessionFactory().getProviderFactoriesStream(SocialIdentityProvider.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)
|
.map(IdentityProviderFactory.class::cast)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElseThrow(() -> new IllegalStateException("IDP not found by Provider ID: " + providerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,13 +270,17 @@ public class IdentityProviderResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
IdentityProviderFactory factory = getIdentityProviderFactory();
|
return createIdentityProviderInstance().export(session.getContext().getUri(), realm, format);
|
||||||
return factory.create(session, identityProviderModel).export(session.getContext().getUri(), realm, format);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ErrorResponse.error("Could not export public broker configuration for identity provider [" + identityProviderModel.getProviderId() + "].", Response.Status.NOT_FOUND);
|
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
|
* Get mapper types for identity provider
|
||||||
*/
|
*/
|
||||||
|
@ -293,13 +296,12 @@ public class IdentityProviderResource {
|
||||||
throw new jakarta.ws.rs.NotFoundException();
|
throw new jakarta.ws.rs.NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IdentityProvider<?> identityProviderInstance = createIdentityProviderInstance();
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
return sessionFactory.getProviderFactoriesStream(IdentityProviderMapper.class)
|
return sessionFactory.getProviderFactoriesStream(IdentityProviderMapper.class)
|
||||||
.map(IdentityProviderMapper.class::cast)
|
.map(IdentityProviderMapper.class::cast)
|
||||||
.map(mapper -> Arrays.stream(mapper.getCompatibleProviders())
|
.filter(identityProviderInstance::isMapperSupported)
|
||||||
.filter(type -> Objects.equals(IdentityProviderMapper.ANY_PROVIDER, type) ||
|
.map(mapper -> {
|
||||||
Objects.equals(identityProviderModel.getProviderId(), type))
|
|
||||||
.map(type -> {
|
|
||||||
IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation();
|
IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation();
|
||||||
rep.setId(mapper.getId());
|
rep.setId(mapper.getId());
|
||||||
rep.setCategory(mapper.getDisplayCategory());
|
rep.setCategory(mapper.getDisplayCategory());
|
||||||
|
@ -310,9 +312,6 @@ public class IdentityProviderResource {
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
return rep;
|
return rep;
|
||||||
})
|
})
|
||||||
.findFirst()
|
|
||||||
.orElse(null))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toMap(IdentityProviderMapperTypeRepresentation::getId, Function.identity()));
|
.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.LegacyIdIdentityProviderFactory
|
||||||
|
org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProviderFactory
|
||||||
org.keycloak.testsuite.broker.oidc.TestKeycloakOidcIdentityProviderFactory
|
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.saml.processing.core.util.XMLSignatureUtil;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep;
|
import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep;
|
||||||
|
import org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProviderFactory;
|
||||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
import org.keycloak.testsuite.util.KeyUtils;
|
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");
|
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) {
|
private void assertMapperTypes(Map<String, IdentityProviderMapperTypeRepresentation> mapperTypes, String ... mapperIds) {
|
||||||
Set<String> expected = new HashSet<>();
|
Set<String> expected = new HashSet<>();
|
||||||
expected.add("hardcoded-user-session-attribute-idp-mapper");
|
expected.add("hardcoded-user-session-attribute-idp-mapper");
|
||||||
|
|
Loading…
Reference in a new issue