Make WebAuthn required actions enabled by default

Closes #12723
This commit is contained in:
Martin Bartoš 2022-06-29 13:46:31 +02:00 committed by Marek Posolda
parent 8238467c49
commit 07ab29378b
4 changed files with 62 additions and 41 deletions

View file

@ -99,6 +99,8 @@ public class DefaultRequiredActions {
addUpdateLocaleAction(realm); addUpdateLocaleAction(realm);
addDeleteAccountAction(realm); addDeleteAccountAction(realm);
addUpdateEmailAction(realm); addUpdateEmailAction(realm);
addWebAuthnRegisterAction(realm);
addWebAuthnPasswordlessRegisterAction(realm);
} }
public static void addDeleteAccountAction(RealmModel realm) { public static void addDeleteAccountAction(RealmModel realm) {
@ -140,4 +142,40 @@ public class DefaultRequiredActions {
realm.addRequiredActionProvider(updateEmail); realm.addRequiredActionProvider(updateEmail);
} }
} }
public static void addWebAuthnRegisterAction(RealmModel realm) {
final String PROVIDER_ID = "webauthn-register";
final boolean isWebAuthnFeatureEnabled = Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
final boolean isRequiredActionActive = realm.getRequiredActionProviderByAlias(PROVIDER_ID) != null;
if (isWebAuthnFeatureEnabled && !isRequiredActionActive) {
final RequiredActionProviderModel webauthnRegister = new RequiredActionProviderModel();
webauthnRegister.setEnabled(true);
webauthnRegister.setAlias(PROVIDER_ID);
webauthnRegister.setName("Webauthn Register");
webauthnRegister.setProviderId(PROVIDER_ID);
webauthnRegister.setDefaultAction(false);
webauthnRegister.setPriority(70);
realm.addRequiredActionProvider(webauthnRegister);
}
}
public static void addWebAuthnPasswordlessRegisterAction(RealmModel realm) {
final String PROVIDER_ID = "webauthn-register-passwordless";
final boolean isWebAuthnFeatureEnabled = Profile.isFeatureEnabled(Profile.Feature.WEB_AUTHN);
final boolean isRequiredActionActive = realm.getRequiredActionProviderByAlias(PROVIDER_ID) != null;
if (isWebAuthnFeatureEnabled && !isRequiredActionActive) {
final RequiredActionProviderModel webauthnRegister = new RequiredActionProviderModel();
webauthnRegister.setEnabled(true);
webauthnRegister.setAlias(PROVIDER_ID);
webauthnRegister.setName("Webauthn Register Passwordless");
webauthnRegister.setProviderId(PROVIDER_ID);
webauthnRegister.setDefaultAction(false);
webauthnRegister.setPriority(80);
realm.addRequiredActionProvider(webauthnRegister);
}
}
} }

View file

@ -68,6 +68,7 @@ import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.validate.validators.EmailValidator; import org.keycloak.validate.validators.EmailValidator;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -76,12 +77,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -533,14 +536,28 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
requiredAction.setId("12345"); requiredAction.setId("12345");
requiredAction.setName(WebAuthnRegisterFactory.PROVIDER_ID); requiredAction.setName(WebAuthnRegisterFactory.PROVIDER_ID);
requiredAction.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID); requiredAction.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID);
testRealm().flows().registerRequiredAction(requiredAction);
try {
testRealm().flows().registerRequiredAction(requiredAction);
} catch (ClientErrorException e) {
assertThat(e.getResponse(), notNullValue());
assertThat(e.getResponse().getStatus(), is(409));
}
getCleanup().addRequiredAction(requiredAction.getProviderId()); getCleanup().addRequiredAction(requiredAction.getProviderId());
requiredAction = new RequiredActionProviderSimpleRepresentation(); requiredAction = new RequiredActionProviderSimpleRepresentation();
requiredAction.setId("6789"); requiredAction.setId("6789");
requiredAction.setName(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID); requiredAction.setName(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
requiredAction.setProviderId(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID); requiredAction.setProviderId(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
testRealm().flows().registerRequiredAction(requiredAction);
try {
testRealm().flows().registerRequiredAction(requiredAction);
} catch (ClientErrorException e) {
assertThat(e.getResponse(), notNullValue());
assertThat(e.getResponse().getStatus(), is(409));
}
getCleanup().addRequiredAction(requiredAction.getProviderId()); getCleanup().addRequiredAction(requiredAction.getProviderId());
List<AccountCredentialResource.CredentialContainer> credentials = getCredentials(); List<AccountCredentialResource.CredentialContainer> credentials = getCredentials();

View file

@ -52,6 +52,8 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
addRequiredAction(expected, "delete_account", "Delete Account", false, false, null); addRequiredAction(expected, "delete_account", "Delete Account", false, false, null);
addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null); addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null);
addRequiredAction(expected, "update_user_locale", "Update User Locale", true, false, null); addRequiredAction(expected, "update_user_locale", "Update User Locale", true, false, null);
addRequiredAction(expected, "webauthn-register", "Webauthn Register", true, false, null);
addRequiredAction(expected, "webauthn-register-passwordless", "Webauthn Register Passwordless", true, false, null);
compareRequiredActions(expected, sort(result)); compareRequiredActions(expected, sort(result));
@ -82,7 +84,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
// Dummy RequiredAction is not registered in the realm and WebAuthn actions // Dummy RequiredAction is not registered in the realm and WebAuthn actions
List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions(); List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions();
Assert.assertEquals(4, result.size()); Assert.assertEquals(2, result.size());
RequiredActionProviderSimpleRepresentation action = result.stream().filter( RequiredActionProviderSimpleRepresentation action = result.stream().filter(
a -> a.getProviderId().equals(DummyRequiredActionFactory.PROVIDER_ID) a -> a.getProviderId().equals(DummyRequiredActionFactory.PROVIDER_ID)
).findFirst().get(); ).findFirst().get();

View file

@ -959,43 +959,16 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
); );
} }
/**
* This test checks that if a REQUIRED authentication execution which has isUserSetupAllowed -> true
* has its requiredActionProvider in a not registered state, then it will not try to create the required action,
* and will instead raise an credential setup required error.
* NOTE: webauthn currently isn't configured by default in the realm. When this changes, this test will need to be adapted
*/
@Test
@AuthServerContainerExclude(REMOTE)
public void testLoginWithWithNoWebAuthnCredentialAndNoRequiredActionProviderRegistered(){
String newFlowAlias = "browser - copy 1";
configureBrowserFlowWithRequiredWebAuthn(newFlowAlias);
try {
provideUsernamePassword("test-user@localhost");
// Assert that the login evaluates to an error, as all required elements to not validate to successful
errorPage.assertCurrent();
} finally {
revertFlows("browser - copy 1");
}
}
/** /**
* This test checks that if a REQUIRED authentication execution which has isUserSetupAllowed -> true * This test checks that if a REQUIRED authentication execution which has isUserSetupAllowed -> true
* has its requiredActionProvider disabled, then it will not try to create the required action, * has its requiredActionProvider disabled, then it will not try to create the required action,
* and will instead raise an credential setup required error. * and will instead raise an credential setup required error.
* NOTE: webauthn currently isn't configured by default in the realm. When this changes, this test will need to be adapted
*/ */
@Test @Test
@AuthServerContainerExclude(REMOTE) @AuthServerContainerExclude(REMOTE)
public void testLoginWithWithNoWebAuthnCredentialAndRequiredActionProviderDisabled(){ public void testLoginWithWithNoWebAuthnCredentialAndRequiredActionProviderDisabled(){
String newFlowAlias = "browser - copy 1"; String newFlowAlias = "browser - copy 1";
configureBrowserFlowWithRequiredWebAuthn(newFlowAlias); configureBrowserFlowWithRequiredWebAuthn(newFlowAlias);
RequiredActionProviderSimpleRepresentation requiredActionRepresentation = new RequiredActionProviderSimpleRepresentation();
requiredActionRepresentation.setName("WebAuthn Required Action");
requiredActionRepresentation.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID);
testRealm().flows().registerRequiredAction(requiredActionRepresentation);
RequiredActionProviderRepresentation rapr = testRealm().flows().getRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID); RequiredActionProviderRepresentation rapr = testRealm().flows().getRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
rapr.setEnabled(false); rapr.setEnabled(false);
testRealm().flows().updateRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID, rapr); testRealm().flows().updateRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID, rapr);
@ -1007,14 +980,12 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
} finally { } finally {
revertFlows("browser - copy 1"); revertFlows("browser - copy 1");
testRealm().flows().removeRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
} }
} }
/** /**
* This test checks that if a REQUIRED authentication execution which has isUserSetupAllowed -> true * This test checks that if a REQUIRED authentication execution which has isUserSetupAllowed -> true
* has its requiredActionProvider enabled, than it will login and show the otpSetup page. * has its requiredActionProvider enabled, then it will login and show the WebAuthn registration page.
* NOTE: webauthn currently isn't configured by default in the realm. When this changes, this test will need to be adapted
*/ */
@Test @Test
@AuthServerContainerExclude(REMOTE) @AuthServerContainerExclude(REMOTE)
@ -1022,20 +993,13 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
String newFlowAlias = "browser - copy 1"; String newFlowAlias = "browser - copy 1";
configureBrowserFlowWithRequiredWebAuthn(newFlowAlias); configureBrowserFlowWithRequiredWebAuthn(newFlowAlias);
RequiredActionProviderSimpleRepresentation requiredActionRepresentation = new RequiredActionProviderSimpleRepresentation();
requiredActionRepresentation.setName("WebAuthn Required Action");
requiredActionRepresentation.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID);
testRealm().flows().registerRequiredAction(requiredActionRepresentation);
try { try {
provideUsernamePassword("test-user@localhost"); provideUsernamePassword("test-user@localhost");
// Assert that in this case you arrive to an webauthn setup // Assert that in this case you arrive to an webauthn setup
Assert.assertTrue(driver.getCurrentUrl().contains("required-action?execution=" + WebAuthnRegisterFactory.PROVIDER_ID)); Assert.assertTrue(driver.getCurrentUrl().contains("required-action?execution=" + WebAuthnRegisterFactory.PROVIDER_ID));
} finally { } finally {
revertFlows("browser - copy 1"); revertFlows("browser - copy 1");
testRealm().flows().removeRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
UserRepresentation user = testRealm().users().search("test-user@localhost").get(0); UserRepresentation user = testRealm().users().search("test-user@localhost").get(0);
user.setRequiredActions(Collections.emptyList()); user.setRequiredActions(Collections.emptyList());
testRealm().users().get(user.getId()).update(user); testRealm().users().get(user.getId()).update(user);