KEYCLOAK-12958 Preview feature profile for WebAuthn (#6780)
* KEYCLOAK-12958 Preview feature profile for WebAuthn * KEYCLOAK-12958 Ability to enable features having EnvironmentDependent providers without restart server * KEYCLOAK-12958 WebAuthn profile product/project Co-authored-by: Marek Posolda <mposolda@gmail.com>
This commit is contained in:
parent
8436a88075
commit
eaaff6e555
27 changed files with 364 additions and 43 deletions
|
@ -52,16 +52,31 @@ public class Profile {
|
|||
OPENSHIFT_INTEGRATION(Type.PREVIEW),
|
||||
SCRIPTS(Type.PREVIEW),
|
||||
TOKEN_EXCHANGE(Type.PREVIEW),
|
||||
UPLOAD_SCRIPTS(DEPRECATED);
|
||||
UPLOAD_SCRIPTS(DEPRECATED),
|
||||
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW);
|
||||
|
||||
private Type type;
|
||||
private Type typeProject;
|
||||
private Type typeProduct;
|
||||
|
||||
Feature(Type type) {
|
||||
this.type = type;
|
||||
this(type, type);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
Feature(Type typeProject, Type typeProduct) {
|
||||
this.typeProject = typeProject;
|
||||
this.typeProduct = typeProduct;
|
||||
}
|
||||
|
||||
public Type getTypeProject() {
|
||||
return typeProject;
|
||||
}
|
||||
|
||||
public Type getTypeProduct() {
|
||||
return typeProduct;
|
||||
}
|
||||
|
||||
public boolean hasDifferentProductType() {
|
||||
return typeProject != typeProduct;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,8 +110,9 @@ public class Profile {
|
|||
|
||||
for (Feature f : Feature.values()) {
|
||||
Boolean enabled = config.getConfig(f);
|
||||
Type type = product.equals(ProductValue.RHSSO) ? f.getTypeProduct() : f.getTypeProject();
|
||||
|
||||
switch (f.getType()) {
|
||||
switch (type) {
|
||||
case DEFAULT:
|
||||
if (enabled != null && !enabled) {
|
||||
disabledFeatures.add(f);
|
||||
|
@ -107,7 +123,7 @@ public class Profile {
|
|||
case DISABLED_BY_DEFAULT:
|
||||
if (enabled == null || !enabled) {
|
||||
disabledFeatures.add(f);
|
||||
} else if (DEPRECATED.equals(f.getType())) {
|
||||
} else if (DEPRECATED.equals(type)) {
|
||||
logger.warnf("Deprecated feature enabled: " + f.name().toLowerCase());
|
||||
if (Feature.UPLOAD_SCRIPTS.equals(f)) {
|
||||
previewFeatures.add(Feature.SCRIPTS);
|
||||
|
|
|
@ -19,12 +19,36 @@ public class ProfileTest {
|
|||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void checkDefaults() {
|
||||
public void checkDefaultsKeycloak() {
|
||||
Assert.assertEquals("community", Profile.getName());
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION);
|
||||
assertEquals(Profile.getExperimentalFeatures(), Profile.Feature.ACCOUNT2);
|
||||
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
|
||||
|
||||
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
||||
Assert.assertEquals(Profile.Feature.WEB_AUTHN.getTypeProject(), Profile.Type.DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDefaultsRH_SSO() {
|
||||
System.setProperty("keycloak.profile", "product");
|
||||
String backUpName = Version.NAME;
|
||||
Version.NAME = "rh-sso";
|
||||
Profile.init();
|
||||
|
||||
Assert.assertEquals("product", Profile.getName());
|
||||
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN);
|
||||
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN);
|
||||
assertEquals(Profile.getExperimentalFeatures(), Profile.Feature.ACCOUNT2);
|
||||
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
|
||||
|
||||
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
||||
Assert.assertEquals(Profile.Feature.WEB_AUTHN.getTypeProduct(), Profile.Type.PREVIEW);
|
||||
|
||||
System.setProperty("keycloak.profile", "community");
|
||||
Version.NAME = backUpName;
|
||||
Profile.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.keycloak.provider.Spi;
|
|||
*/
|
||||
public class AuthenticatorSpi implements Spi {
|
||||
|
||||
public static final String SPI_NAME = "authenticator";
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
|
@ -33,7 +35,7 @@ public class AuthenticatorSpi implements Spi {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "authenticator";
|
||||
return SPI_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,15 +19,17 @@ package org.keycloak.authentication.authenticators.browser;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WebAuthnAuthenticatorFactory implements AuthenticatorFactory {
|
||||
public class WebAuthnAuthenticatorFactory implements AuthenticatorFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "webauthn-authenticator";
|
||||
|
||||
|
@ -91,4 +93,9 @@ public class WebAuthnAuthenticatorFactory implements AuthenticatorFactory {
|
|||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,10 @@ import org.keycloak.Config.Scope;
|
|||
import org.keycloak.authentication.DisplayTypeRequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.truststore.TruststoreProvider;
|
||||
|
||||
import com.webauthn4j.anchor.KeyStoreTrustAnchorsProvider;
|
||||
|
@ -31,7 +33,7 @@ import com.webauthn4j.anchor.TrustAnchorsResolverImpl;
|
|||
import com.webauthn4j.validator.attestation.trustworthiness.certpath.NullCertPathTrustworthinessValidator;
|
||||
import com.webauthn4j.validator.attestation.trustworthiness.certpath.TrustAnchorCertPathTrustworthinessValidator;
|
||||
|
||||
public class WebAuthnRegisterFactory implements RequiredActionFactory, DisplayTypeRequiredActionFactory {
|
||||
public class WebAuthnRegisterFactory implements RequiredActionFactory, DisplayTypeRequiredActionFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "webauthn-register";
|
||||
|
||||
|
@ -88,4 +90,8 @@ public class WebAuthnRegisterFactory implements RequiredActionFactory, DisplayTy
|
|||
return "Webauthn Register";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package org.keycloak.credential;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import com.webauthn4j.converter.util.CborConverter;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
public class WebAuthnCredentialProviderFactory implements CredentialProviderFactory<WebAuthnCredentialProvider> {
|
||||
public class WebAuthnCredentialProviderFactory implements CredentialProviderFactory<WebAuthnCredentialProvider>, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-webauthn";
|
||||
|
||||
|
@ -35,4 +37,9 @@ public class WebAuthnCredentialProviderFactory implements CredentialProviderFact
|
|||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,14 @@
|
|||
package org.keycloak.credential;
|
||||
|
||||
import com.webauthn4j.converter.util.CborConverter;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class WebAuthnPasswordlessCredentialProviderFactory implements CredentialProviderFactory<WebAuthnPasswordlessCredentialProvider> {
|
||||
public class WebAuthnPasswordlessCredentialProviderFactory implements CredentialProviderFactory<WebAuthnPasswordlessCredentialProvider>, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-webauthn-passwordless";
|
||||
|
||||
|
@ -39,4 +41,9 @@ public class WebAuthnPasswordlessCredentialProviderFactory implements Credential
|
|||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
|
|||
import org.keycloak.testsuite.runonserver.FetchOnServer;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||
import org.keycloak.testsuite.runonserver.SerializationUtil;
|
||||
import org.keycloak.testsuite.util.FeatureDeployerUtil;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
@ -867,6 +868,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
if (Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.ok().build();
|
||||
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||
|
||||
System.setProperty("keycloak.profile.feature." + featureProfile.toString().toLowerCase(), "enabled");
|
||||
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
|
@ -877,6 +880,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
|
||||
Profile.init();
|
||||
|
||||
FeatureDeployerUtil.deployFactoriesAfterFeatureEnabled(featureProfile);
|
||||
|
||||
if (Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.ok().build();
|
||||
else
|
||||
|
@ -899,7 +904,9 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
if (!Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.ok().build();
|
||||
|
||||
System.getProperties().remove("keycloak.profile.feature." + featureProfile.toString().toLowerCase());
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(featureProfile);
|
||||
|
||||
disableFeatureProperties(featureProfile);
|
||||
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
||||
|
@ -909,12 +916,25 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
|
||||
Profile.init();
|
||||
|
||||
FeatureDeployerUtil.undeployFactoriesAfterFeatureDisabled(featureProfile);
|
||||
|
||||
if (!Profile.isFeatureEnabled(featureProfile))
|
||||
return Response.ok().build();
|
||||
else
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* KEYCLOAK-12958
|
||||
*/
|
||||
private void disableFeatureProperties(Profile.Feature feature) {
|
||||
Profile.Type type = Profile.getName().equals("product") ? feature.getTypeProduct() : feature.getTypeProject();
|
||||
if (type.equals(Profile.Type.DEFAULT)) {
|
||||
System.setProperty("keycloak.profile.feature." + feature.toString().toLowerCase(), "disabled");
|
||||
} else {
|
||||
System.getProperties().remove("keycloak.profile.feature." + feature.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send POST request to specified URL with specified form parameters. It's not easily possible to "trick" web driver to send POST
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 2019 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.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.provider.DefaultProviderLoader;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.KeycloakDeploymentInfo;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.ProviderManager;
|
||||
import org.keycloak.provider.ProviderManagerRegistry;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.services.DefaultKeycloakSession;
|
||||
|
||||
/**
|
||||
* Used to dynamically reload EnvironmentDependentProviderFactories after some feature is enabled/disabled
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class FeatureDeployerUtil {
|
||||
|
||||
private final static Map<Profile.Feature, Map<ProviderFactory, Spi>> initializer = new ConcurrentHashMap<>();
|
||||
|
||||
private final static Map<Profile.Feature, ProviderManager> deployersCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FeatureDeployerUtil.class);
|
||||
|
||||
public static void initBeforeChangeFeature(Profile.Feature feature) {
|
||||
if (deployersCache.containsKey(feature)) return;
|
||||
|
||||
// Compute which provider factories are enabled before feature is enabled (disabled)
|
||||
Map<ProviderFactory, Spi> factoriesBefore = loadEnabledEnvironmentFactories();
|
||||
initializer.put(feature, factoriesBefore);
|
||||
}
|
||||
|
||||
public static void deployFactoriesAfterFeatureEnabled(Profile.Feature feature) {
|
||||
ProviderManager manager = deployersCache.get(feature);
|
||||
if (manager == null) {
|
||||
// Need to figure which provider factories were enabled after feature was enabled. Create deployer based on it and save it to the cache
|
||||
Map<ProviderFactory, Spi> factoriesBeforeEnable = initializer.remove(feature);
|
||||
Map<ProviderFactory, Spi> factoriesAfterEnable = loadEnabledEnvironmentFactories();
|
||||
Map<ProviderFactory, Spi> factories = getFactoriesDependentOnFeature(factoriesBeforeEnable, factoriesAfterEnable);
|
||||
|
||||
logger.infof("New factories when enabling feature '%s': %s", feature, factories.keySet());
|
||||
|
||||
KeycloakDeploymentInfo di = createDeploymentInfo(factories);
|
||||
|
||||
manager = new ProviderManager(di, FeatureDeployerUtil.class.getClassLoader());
|
||||
deployersCache.put(feature, manager);
|
||||
}
|
||||
ProviderManagerRegistry.SINGLETON.deploy(manager);
|
||||
}
|
||||
|
||||
public static void undeployFactoriesAfterFeatureDisabled(Profile.Feature feature) {
|
||||
ProviderManager manager = deployersCache.get(feature);
|
||||
if (manager == null) {
|
||||
// This is used if some feature is enabled by default and then disabled
|
||||
// Need to figure which provider factories were enabled after feature was enabled. Create deployer based on it and save it to the cache
|
||||
Map<ProviderFactory, Spi> factoriesBeforeDisable = initializer.remove(feature);
|
||||
Map<ProviderFactory, Spi> factoriesAfterDisable = loadEnabledEnvironmentFactories();
|
||||
Map<ProviderFactory, Spi> factories = getFactoriesDependentOnFeature(factoriesAfterDisable, factoriesBeforeDisable);
|
||||
|
||||
KeycloakDeploymentInfo di = createDeploymentInfo(factories);
|
||||
|
||||
manager = new ProviderManager(di, FeatureDeployerUtil.class.getClassLoader());
|
||||
loadFactories(manager);
|
||||
deployersCache.put(feature, manager);
|
||||
}
|
||||
ProviderManagerRegistry.SINGLETON.undeploy(manager);
|
||||
}
|
||||
|
||||
private static Map<ProviderFactory, Spi> getFactoriesDependentOnFeature(Map<ProviderFactory, Spi> factoriesDisabled, Map<ProviderFactory, Spi> factoriesEnabled) {
|
||||
Set<Class<? extends ProviderFactory>> disabledFactoriesClasses = factoriesDisabled.keySet().stream()
|
||||
.map(ProviderFactory::getClass)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<Class<? extends ProviderFactory>> enabledFactoriesClasses = factoriesEnabled.keySet().stream()
|
||||
.map(ProviderFactory::getClass)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
enabledFactoriesClasses.removeAll(disabledFactoriesClasses);
|
||||
|
||||
Map<ProviderFactory, Spi> newFactories = factoriesEnabled.entrySet().stream()
|
||||
.filter(entry -> enabledFactoriesClasses.contains(entry.getKey().getClass()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
return newFactories;
|
||||
}
|
||||
|
||||
private static KeycloakDeploymentInfo createDeploymentInfo(Map<ProviderFactory, Spi> factories) {
|
||||
KeycloakDeploymentInfo di = KeycloakDeploymentInfo.create();
|
||||
for (Map.Entry<ProviderFactory, Spi> factory : factories.entrySet()) {
|
||||
ProviderFactory pf = factory.getKey();
|
||||
Class<? extends Spi> spiClass = factory.getValue().getClass();
|
||||
di.addProvider(spiClass, pf);
|
||||
}
|
||||
return di;
|
||||
}
|
||||
|
||||
private static Map<ProviderFactory, Spi> loadEnabledEnvironmentFactories() {
|
||||
KeycloakDeploymentInfo di = KeycloakDeploymentInfo.create().services();
|
||||
ClassLoader classLoader = DefaultKeycloakSession.class.getClassLoader();
|
||||
DefaultProviderLoader loader = new DefaultProviderLoader(di, classLoader);
|
||||
|
||||
Map<ProviderFactory, Spi> providerFactories = new HashMap<>();
|
||||
for (Spi spi : loader.loadSpis()) {
|
||||
List<ProviderFactory> currentFactories = loader.load(spi);
|
||||
for (ProviderFactory factory : currentFactories) {
|
||||
if (factory instanceof EnvironmentDependentProviderFactory) {
|
||||
if (((EnvironmentDependentProviderFactory) factory).isSupported()) {
|
||||
providerFactories.put(factory, spi);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return providerFactories;
|
||||
}
|
||||
|
||||
private static void loadFactories(ProviderManager pm) {
|
||||
KeycloakDeploymentInfo di = KeycloakDeploymentInfo.create().services();
|
||||
ClassLoader classLoader = DefaultKeycloakSession.class.getClassLoader();
|
||||
DefaultProviderLoader loader = new DefaultProviderLoader(di, classLoader);
|
||||
loader.loadSpis().forEach(pm::load);
|
||||
}
|
||||
}
|
|
@ -20,4 +20,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
public @interface DisableFeature {
|
||||
Profile.Feature value();
|
||||
boolean skipRestart() default false;
|
||||
boolean onlyForProduct() default false;
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
public @interface EnableFeature {
|
||||
Profile.Feature value();
|
||||
boolean skipRestart() default false;
|
||||
boolean onlyForProduct() default false;
|
||||
}
|
||||
|
|
|
@ -72,11 +72,13 @@ public class KeycloakContainerFeaturesController {
|
|||
private Profile.Feature feature;
|
||||
private boolean skipRestart;
|
||||
private FeatureAction action;
|
||||
private boolean onlyForProduct;
|
||||
|
||||
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action) {
|
||||
public UpdateFeature(Profile.Feature feature, boolean skipRestart, FeatureAction action, boolean onlyForProduct) {
|
||||
this.feature = feature;
|
||||
this.skipRestart = skipRestart;
|
||||
this.action = action;
|
||||
this.onlyForProduct = onlyForProduct;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,6 +122,10 @@ public class KeycloakContainerFeaturesController {
|
|||
}
|
||||
|
||||
private void updateFeatures(List<UpdateFeature> updateFeatures) throws Exception {
|
||||
updateFeatures = updateFeatures.stream()
|
||||
.filter(this::skipForProduct)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
updateFeatures.forEach(UpdateFeature::performAction);
|
||||
|
||||
if (updateFeatures.stream().anyMatch(updateFeature -> !updateFeature.skipRestart)) {
|
||||
|
@ -130,20 +136,25 @@ public class KeycloakContainerFeaturesController {
|
|||
updateFeatures.forEach(UpdateFeature::assertPerformed);
|
||||
}
|
||||
|
||||
// KEYCLOAK-12958 WebAuthn profile product/project
|
||||
private boolean skipForProduct(UpdateFeature feature) {
|
||||
return !feature.onlyForProduct || Profile.getName().equals("product");
|
||||
}
|
||||
|
||||
private void checkAnnotatedElementForFeatureAnnotations(AnnotatedElement annotatedElement, State state) throws Exception {
|
||||
List<UpdateFeature> updateFeatureList = new ArrayList<>(0);
|
||||
|
||||
if (annotatedElement.isAnnotationPresent(EnableFeatures.class) || annotatedElement.isAnnotationPresent(EnableFeature.class)) {
|
||||
updateFeatureList.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(EnableFeature.class))
|
||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
||||
state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE))
|
||||
state == State.BEFORE ? FeatureAction.ENABLE : FeatureAction.DISABLE, annotation.onlyForProduct()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (annotatedElement.isAnnotationPresent(DisableFeatures.class) || annotatedElement.isAnnotationPresent(DisableFeature.class)) {
|
||||
updateFeatureList.addAll(Arrays.stream(annotatedElement.getAnnotationsByType(DisableFeature.class))
|
||||
.map(annotation -> new UpdateFeature(annotation.value(), annotation.skipRestart(),
|
||||
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE))
|
||||
state == State.BEFORE ? FeatureAction.DISABLE : FeatureAction.ENABLE, annotation.onlyForProduct()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAu
|
|||
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.credential.CredentialTypeMetadata;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
@ -50,11 +51,16 @@ import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.account.AccountCredentialResource;
|
||||
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.TokenUtil;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -62,22 +68,25 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.*;
|
||||
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
@EnableFeature(value = ACCOUNT_API, skipRestart = true)
|
||||
public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||
|
||||
@Test
|
||||
public void testGetProfile() throws IOException {
|
||||
|
||||
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||
assertEquals("Tom", user.getFirstName());
|
||||
assertEquals("Brady", user.getLastName());
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
|
|||
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.challenge.NoCookieFlowRedirectAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
|
@ -31,6 +32,7 @@ import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
|||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||
|
||||
|
@ -320,6 +322,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
public void testRequirementsInExecution() {
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
|
|
|
@ -34,12 +34,14 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public class ProvidersTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
|
@ -136,9 +138,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
@Test
|
||||
public void testInitialAuthenticationProviders() {
|
||||
List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
|
||||
providers = sortProviders(providers);
|
||||
|
||||
compareProviders(sortProviders(expectedAuthProviders()), providers);
|
||||
compareProviders(expectedAuthProviders(), providers);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> expectedAuthProviders() {
|
||||
|
@ -234,7 +234,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
for (Map<String, Object> item: list) {
|
||||
result.add(new HashMap(item));
|
||||
}
|
||||
return result;
|
||||
return sortProviders(result);
|
||||
}
|
||||
|
||||
private void addProviderInfo(List<Map<String, Object>> list, String id, String displayName, String description) {
|
||||
|
|
|
@ -19,11 +19,13 @@ package org.keycloak.testsuite.admin.authentication;
|
|||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
||||
import org.keycloak.testsuite.actions.DummyRequiredActionFactory;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
|
@ -37,6 +39,7 @@ import java.util.Map;
|
|||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public class RequiredActionsTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorF
|
|||
import org.keycloak.authentication.authenticators.conditional.ConditionalRoleAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.conditional.ConditionalUserConfiguredAuthenticatorFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
||||
|
@ -33,6 +34,7 @@ import org.keycloak.testsuite.ActionURIUtils;
|
|||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.auth.page.login.OneTimeCode;
|
||||
import org.keycloak.testsuite.broker.SocialLoginTest;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
|
@ -61,6 +63,7 @@ import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.GITHUB;
|
|||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.GITLAB;
|
||||
import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.GOOGLE;
|
||||
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||
private static final String INVALID_AUTH_CODE = "Invalid authenticator code.";
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFa
|
|||
import org.keycloak.authentication.authenticators.browser.PasswordFormFactory;
|
||||
import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
|
||||
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
|
@ -40,6 +41,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -241,6 +243,7 @@ public class MultiFactorAuthenticationTest extends AbstractTestRealmKeycloakTest
|
|||
// Test for the case when user can authenticate either with: WebAuthn OR (Password AND OTP)
|
||||
// WebAuthn is not enabled for the user, so he needs to use password AND OTP
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public void testAlternativeMechanismsInDifferentSubflows_firstMechanismUnavailable() {
|
||||
final String newFlowAlias = "browser - alternative mechanisms";
|
||||
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
|
||||
|
|
|
@ -57,7 +57,7 @@ import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
|
|||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
@EnableFeature(OPENSHIFT_INTEGRATION)
|
||||
@EnableFeature(value = OPENSHIFT_INTEGRATION, skipRestart = true)
|
||||
public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static boolean flowConfigured;
|
||||
|
|
|
@ -67,7 +67,7 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
|||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
@EnableFeature(OPENSHIFT_INTEGRATION)
|
||||
@EnableFeature(value = OPENSHIFT_INTEGRATION, skipRestart = true)
|
||||
public final class OpenshiftClientStorageTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static Undertow OPENSHIFT_API_SERVER;
|
||||
|
|
|
@ -22,8 +22,11 @@ import org.junit.Before;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.WebAuthnConstants;
|
||||
import org.keycloak.authentication.AuthenticatorSpi;
|
||||
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.RandomString;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
|
@ -32,10 +35,14 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
|||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.info.ServerInfoRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.RegisterPage;
|
||||
import org.keycloak.testsuite.pages.webauthn.WebAuthnLoginPage;
|
||||
|
@ -48,10 +55,13 @@ import static org.junit.Assert.assertEquals;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.AUTH_SERVER_SSL_REQUIRED;
|
||||
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public class WebAuthnRegisterAndLoginTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Rule
|
||||
|
@ -281,6 +291,23 @@ public class WebAuthnRegisterAndLoginTest extends AbstractTestRealmKeycloakTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebAuthnEnabled() {
|
||||
testWebAuthnAvailability(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true)
|
||||
public void testWebAuthnDisabled() {
|
||||
testWebAuthnAvailability(false);
|
||||
}
|
||||
|
||||
private void testWebAuthnAvailability(boolean expectedAvailability) {
|
||||
ServerInfoRepresentation serverInfo = adminClient.serverInfo().getInfo();
|
||||
Set<String> authenticatorProviderIds = serverInfo.getProviders().get(AuthenticatorSpi.SPI_NAME).getProviders().keySet();
|
||||
Assert.assertEquals(expectedAvailability, authenticatorProviderIds.contains(WebAuthnAuthenticatorFactory.PROVIDER_ID));
|
||||
}
|
||||
|
||||
private void assertUserRegistered(String userId, String username, String email) {
|
||||
UserRepresentation user = getUser(userId);
|
||||
Assert.assertNotNull(user);
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorF
|
|||
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
|
||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
||||
|
@ -37,6 +38,7 @@ import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
||||
import org.keycloak.testsuite.WebAuthnAssume;
|
||||
import org.keycloak.testsuite.admin.Users;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.auth.page.login.OTPSetup;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||
import org.keycloak.testsuite.pages.webauthn.WebAuthnRegisterPage;
|
||||
|
@ -62,6 +64,9 @@ import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
|||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
@EnableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true)
|
||||
@EnableFeature(value = Profile.Feature.ACCOUNT_API, skipRestart = true)
|
||||
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
|
||||
public class SigningInTest extends BaseAccountPageTest {
|
||||
public static final String PASSWORD_LABEL = "My Password";
|
||||
public static final String WEBAUTHN_FLOW_ID = "75e2390e-f296-49e6-acf8-6d21071d7e10";
|
||||
|
|
|
@ -1993,8 +1993,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfo) {
|
||||
return ServerInfo.delay;
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmOtpPolicyCtrl'
|
||||
|
@ -2005,8 +2005,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfo) {
|
||||
return ServerInfo.delay;
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmWebAuthnPolicyCtrl'
|
||||
|
@ -2017,8 +2017,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfo) {
|
||||
return ServerInfo.delay;
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmWebAuthnPasswordlessPolicyCtrl'
|
||||
|
|
|
@ -398,30 +398,44 @@ module.controller('RealmOtpPolicyCtrl', function($scope, Current, Realm, realm,
|
|||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/otp-policy");
|
||||
});
|
||||
|
||||
module.controller('RealmWebAuthnPolicyCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) {
|
||||
module.controller('RealmWebAuthnPolicyCtrl', function ($scope, Current, Realm, realm, serverInfo, $http, $route, $location, Dialog, Notifications) {
|
||||
|
||||
$scope.deleteAcceptableAaguid = function(index) {
|
||||
$scope.realm.webAuthnPolicyAcceptableAaguids.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addAcceptableAaguid = function() {
|
||||
$scope.realm.webAuthnPolicyAcceptableAaguids.push($scope.newAcceptableAaguid);
|
||||
$scope.newAcceptableAaguid = "";
|
||||
};
|
||||
|
||||
// Just for case the user fill particular URL with disabled WebAuthn feature.
|
||||
$scope.redirectIfWebAuthnDisabled = function () {
|
||||
if (!serverInfo.featureEnabled('WEB_AUTHN')) {
|
||||
$location.url("/realms/" + $scope.realm.realm + "/authentication");
|
||||
}
|
||||
};
|
||||
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/webauthn-policy");
|
||||
});
|
||||
|
||||
module.controller('RealmWebAuthnPasswordlessPolicyCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) {
|
||||
module.controller('RealmWebAuthnPasswordlessPolicyCtrl', function ($scope, Current, Realm, realm, serverInfo, $http, $route, $location, Dialog, Notifications) {
|
||||
|
||||
$scope.deleteAcceptableAaguid = function(index) {
|
||||
$scope.realm.webAuthnPolicyPasswordlessAcceptableAaguids.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addAcceptableAaguid = function() {
|
||||
$scope.realm.webAuthnPolicyPasswordlessAcceptableAaguids.push($scope.newAcceptableAaguid);
|
||||
$scope.newAcceptableAaguid = "";
|
||||
};
|
||||
|
||||
// Just for case the user fill particular URL with disabled WebAuthn feature.
|
||||
$scope.redirectIfWebAuthnDisabled = function () {
|
||||
if (!serverInfo.featureEnabled('WEB_AUTHN')) {
|
||||
$location.url("/realms/" + $scope.realm.realm + "/authentication");
|
||||
}
|
||||
};
|
||||
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/webauthn-policy-passwordless");
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>{{:: 'authentication' | translate}}</h1>
|
||||
|
||||
<span data-ng-init="redirectIfWebAuthnDisabled()"></span>
|
||||
<kc-tabs-authentication></kc-tabs-authentication>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>{{:: 'authentication' | translate}}</h1>
|
||||
|
||||
<span data-ng-init="redirectIfWebAuthnDisabled()"></span>
|
||||
<kc-tabs-authentication></kc-tabs-authentication>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<li ng-class="{active: path[3] == 'required-actions'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/required-actions">{{:: 'required-actions' | translate}}</a></li>
|
||||
<li ng-class="{active: path[3] == 'password-policy'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/password-policy">{{:: 'password-policy' | translate}}</a></li>
|
||||
<li ng-class="{active: path[3] == 'otp-policy'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/otp-policy">{{:: 'otp-policy' | translate}}</a></li>
|
||||
<li ng-class="{active: path[3] == 'webauthn-policy'}" data-ng-show="access.viewRealm">
|
||||
<li ng-class="{active: path[3] == 'webauthn-policy'}" data-ng-show="access.viewRealm && serverInfo.featureEnabled('WEB_AUTHN')">
|
||||
<a href="#/realms/{{realm.realm}}/authentication/webauthn-policy">{{:: 'webauthn-policy' | translate}}</a>
|
||||
<kc-tooltip>{{:: 'webauthn-policy.tooltip' | translate}}</kc-tooltip>
|
||||
</li>
|
||||
<li ng-class="{active: path[3] == 'webauthn-policy-passwordless'}" data-ng-show="access.viewRealm">
|
||||
<li ng-class="{active: path[3] == 'webauthn-policy-passwordless'}" data-ng-show="access.viewRealm && serverInfo.featureEnabled('WEB_AUTHN')">
|
||||
<a href="#/realms/{{realm.realm}}/authentication/webauthn-policy-passwordless">{{:: 'webauthn-policy-passwordless' | translate}}</a>
|
||||
<kc-tooltip>{{:: 'webauthn-policy-passwordless.tooltip' | translate}}</kc-tooltip>
|
||||
</li>
|
||||
|
|
Loading…
Reference in a new issue