KEYCLOAK-15511 OTP registration during login with LDAP read-only
When LDAP user federation is configured in read-only mode, it is not possible to set required actions for users from LDAP. Keycloak credential model allows for registering OTP devices when LDAP ist configured with "Import Users" flag enabled. Registering OTP devices needs to be done via the account management console and works as expecetd. However, it fails, if a user has to register aN OTP device during login (i.e. within the authentication flow), because the OTP Form Authenticator tries to enforce OTP registration via setting the corresponding required action for the user. That fails, because the user is read-only. To work around this, the required action is set on the authentication session instead.
This commit is contained in:
parent
a965025be8
commit
850d3e7fef
4 changed files with 36 additions and 6 deletions
|
@ -36,6 +36,7 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.credential.OTPCredentialModel;
|
import org.keycloak.models.credential.OTPCredentialModel;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -129,8 +130,9 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
if (!user.getRequiredActions().contains(UserModel.RequiredAction.CONFIGURE_TOTP.name())) {
|
AuthenticationSessionModel authenticationSession = session.getContext().getAuthenticationSession();
|
||||||
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP.name());
|
if (!authenticationSession.getRequiredActions().contains(UserModel.RequiredAction.CONFIGURE_TOTP.name())) {
|
||||||
|
authenticationSession.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.WebAuthnPolicy;
|
import org.keycloak.models.WebAuthnPolicy;
|
||||||
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -234,8 +235,9 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
|
||||||
|
|
||||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
// ask the user to do required action to register webauthn authenticator
|
// ask the user to do required action to register webauthn authenticator
|
||||||
if (!user.getRequiredActions().contains(WebAuthnRegisterFactory.PROVIDER_ID)) {
|
AuthenticationSessionModel authenticationSession = session.getContext().getAuthenticationSession();
|
||||||
user.addRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
|
if (!authenticationSession.getRequiredActions().contains(WebAuthnRegisterFactory.PROVIDER_ID)) {
|
||||||
|
authenticationSession.addRequiredAction(WebAuthnRegisterFactory.PROVIDER_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.WebAuthnPolicy;
|
import org.keycloak.models.WebAuthnPolicy;
|
||||||
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
import org.keycloak.models.credential.WebAuthnCredentialModel;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticator for WebAuthn authentication with passwordless credential. This class is temporary and will be likely
|
* Authenticator for WebAuthn authentication with passwordless credential. This class is temporary and will be likely
|
||||||
|
@ -57,8 +58,9 @@ public class WebAuthnPasswordlessAuthenticator extends WebAuthnAuthenticator {
|
||||||
@Override
|
@Override
|
||||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
// ask the user to do required action to register webauthn authenticator
|
// ask the user to do required action to register webauthn authenticator
|
||||||
if (!user.getRequiredActions().contains(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID)) {
|
AuthenticationSessionModel authenticationSession = session.getContext().getAuthenticationSession();
|
||||||
user.addRequiredAction(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
|
if (!authenticationSession.getRequiredActions().contains(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID)) {
|
||||||
|
authenticationSession.addRequiredAction(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
@ -329,6 +330,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
||||||
@Test
|
@Test
|
||||||
public void setupTotpExisting() {
|
public void setupTotpExisting() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
totpPage.assertCurrent();
|
totpPage.assertCurrent();
|
||||||
|
@ -358,7 +360,29 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//KEYCLOAK-15511
|
||||||
|
@Test
|
||||||
|
public void setupTotpEnforcedBySessionNotForUserInGeneral() {
|
||||||
|
String username = "test-user@localhost";
|
||||||
|
String configureTotp = UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||||
|
|
||||||
|
// Remove required action from the user
|
||||||
|
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), username);
|
||||||
|
UserRepresentation userRepresentation = user.toRepresentation();
|
||||||
|
userRepresentation.getRequiredActions().remove(configureTotp);
|
||||||
|
user.update(userRepresentation);
|
||||||
|
|
||||||
|
// login
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login(username, "password");
|
||||||
|
|
||||||
|
// ensure TOTP configuration is enforced for current authentication session
|
||||||
|
totpPage.assertCurrent();
|
||||||
|
|
||||||
|
// ensure TOTP configuration it is not enforced for the user in general
|
||||||
|
userRepresentation = user.toRepresentation();
|
||||||
|
assertFalse(userRepresentation.getRequiredActions().contains(configureTotp));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setupTotpRegisteredAfterTotpRemoval() {
|
public void setupTotpRegisteredAfterTotpRemoval() {
|
||||||
|
|
Loading…
Reference in a new issue