Ability to override default/built-in providers with same providerId. Using ProviderFactory.order() for choosing priority providers
Closes #19867
This commit is contained in:
parent
78958ae434
commit
a3f2ebb193
21 changed files with 591 additions and 11 deletions
|
@ -47,6 +47,9 @@ public class MyThemeSelectorProviderFactory implements ThemeSelectorProviderFact
|
|||
}
|
||||
----
|
||||
|
||||
It is recommended that your provider factory implementation returns unique id by method `getId()`. However
|
||||
there can be some exceptions to this rule as mentioned below in the <<_override_builtin_providers,Overriding providers>> section.
|
||||
|
||||
NOTE: {project_name} creates a single instance of provider factories which makes it possible to store state for multiple requests.
|
||||
Provider instances are created by calling create on the factory for each request so these should be light-weight object.
|
||||
|
||||
|
@ -119,6 +122,34 @@ public class MyThemeSelectorProvider implements ThemeSelectorProvider {
|
|||
}
|
||||
----
|
||||
|
||||
[[_override_builtin_providers]]
|
||||
==== Override built-in providers
|
||||
|
||||
As mentioned above, it is recommended that your `ProviderFactory` implementations use unique ID. However at the same time, it can be useful to override one of the {project_name} built-in providers.
|
||||
The recommended way for this is still ProviderFactory implementation with unique ID and then for instance set the default provider as
|
||||
specified in the link:https://www.keycloak.org/server/configuration-provider[Configuring Providers] guide. On the other hand, this may not be always possible.
|
||||
|
||||
For instance when you need some customizations to default OpenID Connect protocol behaviour and you want to override
|
||||
default {project_name} implementation of `OIDCLoginProtocolFactory` you need to preserve same providerId. As for example admin console, OIDC protocol well-known endpoint and various other things rely on
|
||||
the ID of the protocol factory being `openid-connect`.
|
||||
|
||||
For this case, it is highly recommended to implement method `order()` of your custom implementation and make sure that it has higher order than the built-in implementation.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class CustomOIDCLoginProtocolFactory extends OIDCLoginProtocolFactory {
|
||||
|
||||
// Some customizations here
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In case of multiple implementations with same provider ID, only the one with highest order will be used by {project_name} runtime.
|
||||
|
||||
[[_providers_admin_console]]
|
||||
==== Show info from your SPI implementation in the Admin Console
|
||||
|
||||
|
|
|
@ -18,15 +18,14 @@ package org.keycloak.provider;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -89,24 +88,46 @@ public class ProviderManager {
|
|||
public synchronized List<ProviderFactory> load(Spi spi) {
|
||||
if (!cache.containsKey(spi.getProviderClass())) {
|
||||
|
||||
Set<String> loaded = new HashSet<>();
|
||||
Map<String, ProviderFactory> loaded = new HashMap<>();
|
||||
for (ProviderLoader loader : loaders) {
|
||||
List<ProviderFactory> f = loader.load(spi);
|
||||
if (f != null) {
|
||||
for (ProviderFactory pf: f) {
|
||||
String uniqueId = spi.getName() + "-" + pf.getId() + "-" + pf.getClass().getName();
|
||||
if (!loaded.contains(uniqueId)) {
|
||||
cache.add(spi.getProviderClass(), pf);
|
||||
loaded.add(uniqueId);
|
||||
String uniqueId = spi.getName() + "-" + pf.getId();
|
||||
if (!loaded.containsKey(uniqueId)) {
|
||||
loaded.put(uniqueId, pf);
|
||||
} else {
|
||||
ProviderFactory currentFactory = loaded.get(uniqueId);
|
||||
ProviderFactory factoryToUse = compareFactories(currentFactory, pf);
|
||||
loaded.put(uniqueId, factoryToUse);
|
||||
|
||||
logger.debugf("Found multiple provider factories of same provider ID implementing same SPI. SPI is '%s', providerFactory ID '%s'. Factories are '%s' and '%s'. Using provider factory '%s'.",
|
||||
spi.getName(), pf.getId(), currentFactory.getClass().getName(), pf.getClass().getName(), factoryToUse.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ProviderFactory providerFactory : loaded.values()) {
|
||||
cache.add(spi.getProviderClass(), providerFactory);
|
||||
}
|
||||
}
|
||||
List<ProviderFactory> rtn = cache.get(spi.getProviderClass());
|
||||
return rtn == null ? Collections.EMPTY_LIST : rtn;
|
||||
}
|
||||
|
||||
// Compare provider factories of same providerId. Just one of them needs to be chosen to be used in Keycloak
|
||||
public ProviderFactory compareFactories(ProviderFactory p1, ProviderFactory p2) {
|
||||
if (p1.order() != p2.order()) return (p1.order() > p2.order()) ? p1 : p2;
|
||||
|
||||
// Internal factory is supposed to be overriden by custom factory
|
||||
if (DefaultKeycloakSessionFactory.isInternal(p1) ^ DefaultKeycloakSessionFactory.isInternal(p2)) {
|
||||
return DefaultKeycloakSessionFactory.isInternal(p1) ? p2 : p1;
|
||||
}
|
||||
|
||||
return p1;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a copy of internal factories.
|
||||
*
|
||||
|
|
|
@ -405,7 +405,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean isInternal(ProviderFactory<?> factory) {
|
||||
public static boolean isInternal(ProviderFactory<?> factory) {
|
||||
String packageName = factory.getClass().getPackage().getName();
|
||||
return packageName.startsWith("org.keycloak") && !packageName.startsWith("org.keycloak.examples");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.account.freemarker.FreeMarkerAccountProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CustomFreemarkerAccountProvider1 extends FreeMarkerAccountProvider {
|
||||
|
||||
public CustomFreemarkerAccountProvider1(KeycloakSession session) {
|
||||
super(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.account.freemarker.FreeMarkerAccountProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CustomFreemarkerAccountProvider2 extends FreeMarkerAccountProvider {
|
||||
|
||||
public CustomFreemarkerAccountProvider2(KeycloakSession session) {
|
||||
super(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.account.AccountProvider;
|
||||
import org.keycloak.forms.account.freemarker.FreeMarkerAccountProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Won't be used due lower order than CustomFreemarkerAccountProviderFactory2
|
||||
*/
|
||||
public class CustomFreemarkerAccountProviderFactory1 extends FreeMarkerAccountProviderFactory {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider create(KeycloakSession session) {
|
||||
return new CustomFreemarkerAccountProvider1(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.account.AccountProvider;
|
||||
import org.keycloak.forms.account.freemarker.FreeMarkerAccountProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Test for order (This one should be called in favour of FreemarkerAccountProviderFactory and CustomFreemarkerAccountProviderFactory1 as it has highest order)
|
||||
*/
|
||||
public class CustomFreemarkerAccountProviderFactory2 extends FreeMarkerAccountProviderFactory {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider create(KeycloakSession session) {
|
||||
return new CustomFreemarkerAccountProvider2(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.login.freemarker.FreeMarkerLoginFormsProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CustomLoginFormsProvider extends FreeMarkerLoginFormsProvider {
|
||||
|
||||
public CustomLoginFormsProvider(KeycloakSession session) {
|
||||
super(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.forms.login.freemarker.FreeMarkerLoginFormsProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* This has same providerID like built-in ValidateUsername provider. But it should be called in favour of ValidateUsername even
|
||||
* if it doesn't have "order" set. Ass it is custom provider and it worked this way in previous versions
|
||||
*/
|
||||
public class CustomLoginFormsProviderFactory extends FreeMarkerLoginFormsProviderFactory {
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider create(KeycloakSession session) {
|
||||
return new CustomLoginFormsProvider(session);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateOTP;
|
||||
|
||||
/**
|
||||
* Overrides built-in, but should not be called due the different order
|
||||
*
|
||||
*/
|
||||
public class CustomValidateOTP extends ValidateOTP {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
|
||||
/**
|
||||
* Test for order (This one is not called due CustomValidatePassword2 has bigger order)
|
||||
*
|
||||
*/
|
||||
public class CustomValidatePassword1 extends ValidatePassword {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
|
||||
/**
|
||||
* Test for order (This one should be called in favour of ValidatePassword, CustomValidatePassword1, CustomValidatePassword3 as it has highest order)
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CustomValidatePassword2 extends ValidatePassword {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
|
||||
/**
|
||||
* Test for order (This one is not called due CustomValidatePassword2 has bigger order)
|
||||
*
|
||||
*/
|
||||
public class CustomValidatePassword3 extends ValidatePassword {
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2023 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.examples.providersoverride;
|
||||
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateUsername;
|
||||
|
||||
/**
|
||||
* This has same providerID like built-in ValidateUsername provider. But it should be called in favour of ValidateUsername even
|
||||
* if it doesn't have "order" set. Ass it is custom provider and it worked this way in previous versions
|
||||
*
|
||||
*/
|
||||
public class CustomValidateUsername extends ValidateUsername {
|
||||
}
|
|
@ -46,10 +46,8 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionSpi;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||
|
@ -1062,6 +1060,19 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
return ErrorPage.error(session, session.getContext().getAuthenticationSession(), Response.Status.BAD_REQUEST, message == null ? "" : message);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/get-provider-implementation-class")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String getProviderClassName(@QueryParam("providerClass") String providerClass, @QueryParam("providerId") String providerId) {
|
||||
try {
|
||||
Class<? extends Provider> providerClazz = (Class<? extends Provider>) Class.forName(providerClass);
|
||||
Provider provider = (providerId == null) ? session.getProvider(providerClazz) : session.getProvider(providerClazz, providerId);
|
||||
return provider.getClass().getName();
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new RuntimeException("Cannot find provider class: " + providerClass, cnfe);
|
||||
}
|
||||
}
|
||||
|
||||
private RealmModel getRealmByName(String realmName) {
|
||||
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
|
||||
RealmModel realm = realmProvider.getRealmByName(realmName);
|
||||
|
|
|
@ -23,4 +23,9 @@ org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
|||
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
||||
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
|
||||
org.keycloak.testsuite.authentication.SetUserAttributeAuthenticatorFactory
|
||||
org.keycloak.testsuite.authentication.CustomAuthenticationFlowCallbackFactory
|
||||
org.keycloak.testsuite.authentication.CustomAuthenticationFlowCallbackFactory
|
||||
org.keycloak.examples.providersoverride.CustomValidateUsername
|
||||
org.keycloak.examples.providersoverride.CustomValidatePassword1
|
||||
org.keycloak.examples.providersoverride.CustomValidatePassword2
|
||||
org.keycloak.examples.providersoverride.CustomValidatePassword3
|
||||
org.keycloak.examples.providersoverride.CustomValidateOTP
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Copyright 2023 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.
|
||||
#
|
||||
#
|
||||
|
||||
org.keycloak.examples.providersoverride.CustomFreemarkerAccountProviderFactory1
|
||||
org.keycloak.examples.providersoverride.CustomFreemarkerAccountProviderFactory2
|
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
# Copyright 2023 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.
|
||||
#
|
||||
#
|
||||
|
||||
org.keycloak.examples.providersoverride.CustomLoginFormsProviderFactory
|
|
@ -398,4 +398,14 @@ public interface TestingResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("/display-error-message")
|
||||
Response displayErrorMessage(@QueryParam("message") String message);
|
||||
|
||||
/**
|
||||
* @param providerClass Full name of class such as for example "org.keycloak.authentication.Authenticator"
|
||||
* @param providerId providerId referenced in particular provider factory. Can be null (in this case we're returning default provider for particular providerClass)
|
||||
* @return fullname of provider implementation class
|
||||
*/
|
||||
@GET
|
||||
@Path("/get-provider-implementation-class")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
String getProviderClassName(@QueryParam("providerClass") String providerClass, @QueryParam("providerId") String providerId);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2023 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.providers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateOTP;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateUsername;
|
||||
import org.keycloak.examples.providersoverride.CustomValidatePassword2;
|
||||
import org.keycloak.examples.providersoverride.CustomValidateUsername;
|
||||
import org.keycloak.forms.account.AccountProvider;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.examples.providersoverride.CustomFreemarkerAccountProvider2;
|
||||
import org.keycloak.examples.providersoverride.CustomLoginFormsProvider;
|
||||
|
||||
/**
|
||||
* Test for having multiple providerFactory of smae SPI with same providerId
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ProvidersOverrideTest extends AbstractKeycloakTest {
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuiltinAuthenticatorsOverride() {
|
||||
// The custom provider would be preferred over the internal ValidateUsername. Both has same order, so custom provider would be chosen (backwards compatibility with previous versions)
|
||||
testProviderImplementationClass(Authenticator.class, ValidateUsername.PROVIDER_ID, CustomValidateUsername.class);
|
||||
|
||||
// The provider with highest order is chosen
|
||||
testProviderImplementationClass(Authenticator.class, ValidatePassword.PROVIDER_ID, CustomValidatePassword2.class);
|
||||
|
||||
// The builtin ValidateOTP class is chosen as it has higher order than the CustomValidateOTP
|
||||
testProviderImplementationClass(Authenticator.class, ValidateOTP.PROVIDER_ID, ValidateOTP.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultProvidersOverride() {
|
||||
// The custom provider would be preferred over the internal FreemarkerLoginFormsProvider. Both has same order, so custom provider would be chosen (backwards compatibility with previous versions)
|
||||
testProviderImplementationClass(LoginFormsProvider.class, null, CustomLoginFormsProvider.class);
|
||||
|
||||
// The provider with highest order is chosen
|
||||
testProviderImplementationClass(AccountProvider.class, null, CustomFreemarkerAccountProvider2.class);
|
||||
}
|
||||
|
||||
private void testProviderImplementationClass(Class<? extends Provider> providerClass, String providerId, Class<? extends Provider> expectedProviderImplClass) {
|
||||
String providerImplClass = getTestingClient().testing().getProviderClassName(providerClass.getName(), providerId);
|
||||
Assert.assertEquals(expectedProviderImplClass.getName(), providerImplClass);
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ oauth,6
|
|||
oidc,6
|
||||
openshift,6
|
||||
policy,6
|
||||
providers,4
|
||||
runonserver,6
|
||||
saml,6
|
||||
script,6
|
||||
|
|
Loading…
Reference in a new issue