diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java index 02aaa01a9f6..fc716d4c245 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java @@ -37,8 +37,9 @@ import static org.keycloak.models.utils.KeycloakModelUtils.hasRole; *

*

*

Role

- * A role can be used to control the OTP authentication. If the user has the specified role the OTP authentication is forced. - * Otherwise if no role is selected the setting is ignored. + * A role can be used to control the OTP authentication. If the user has the specified skip OTP role then OTP authentication is skipped for the user. + * If the user has the specified force OTP role, then the OTP authentication is required for the user. + * If not configured, e.g. if no role is selected, then this setting is ignored. *

*

*

@@ -67,6 +68,8 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator { public static final String OTP_CONTROL_USER_ATTRIBUTE = "otpControlAttribute"; + public static final String SKIP_OTP_ROLE = "skipOtpRole"; + public static final String FORCE_OTP_ROLE = "forceOtpRole"; public static final String NO_OTP_REQUIRED_FOR_HTTP_HEADER = "noOtpRequiredForHeaderPattern"; @@ -88,7 +91,7 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator { return; } - if (tryConcludeBasedOn(voteForUserForceOtpRole(context, config), context)) { + if (tryConcludeBasedOn(voteForUserRole(context, config), context)) { return; } @@ -216,19 +219,32 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator { return false; } - private OtpDecision voteForUserForceOtpRole(AuthenticationFlowContext context, Map config) { + private OtpDecision voteForUserRole(AuthenticationFlowContext context, Map config) { - if (!config.containsKey(FORCE_OTP_ROLE)) { + if (!config.containsKey(SKIP_OTP_ROLE) && !config.containsKey(FORCE_OTP_ROLE)) { return ABSTAIN; } - RoleModel forceOtpRole = getRoleFromString(context.getRealm(), config.get(FORCE_OTP_ROLE)); - UserModel user = context.getUser(); + if (userHasRole(context, config.get(SKIP_OTP_ROLE))) { + return SKIP_OTP; + } - if (hasRole(user.getRoleMappings(), forceOtpRole)) { + if (userHasRole(context, config.get(FORCE_OTP_ROLE))) { return SHOW_OTP; } return ABSTAIN; } + + private boolean userHasRole(AuthenticationFlowContext context, String roleName) { + + if (roleName == null) { + return false; + } + + RoleModel role = getRoleFromString(context.getRealm(), roleName); + UserModel user = context.getUser(); + + return hasRole(user.getRoleMappings(), role); + } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticatorFactory.java index 17c9620e340..9fb15cb3016 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticatorFactory.java @@ -98,6 +98,12 @@ public class ConditionalOtpFormAuthenticatorFactory implements AuthenticatorFact "If attribute value is 'force' then OTP is always required. " + "If value is 'skip' the OTP auth is skipped. Otherwise this check is ignored."); + ProviderConfigProperty skipOtpRole = new ProviderConfigProperty(); + skipOtpRole.setType(ROLE_TYPE); + skipOtpRole.setName(SKIP_OTP_ROLE); + skipOtpRole.setLabel("Skip OTP for Role"); + skipOtpRole.setHelpText("OTP is always skipped if user has the given Role."); + ProviderConfigProperty forceOtpRole = new ProviderConfigProperty(); forceOtpRole.setType(ROLE_TYPE); forceOtpRole.setName(FORCE_OTP_ROLE); @@ -127,6 +133,6 @@ public class ConditionalOtpFormAuthenticatorFactory implements AuthenticatorFact defaultOutcome.setDefaultValue(asList(SKIP, FORCE)); defaultOutcome.setHelpText("What to do in case of every check abstains. Defaults to force OTP authentication."); - return asList(forceOtpUserAttribute, forceOtpRole, noOtpRequiredForHttpHeader, forceOtpForHttpHeader, defaultOutcome); + return asList(forceOtpUserAttribute, skipOtpRole, forceOtpRole, noOtpRequiredForHttpHeader, forceOtpForHttpHeader, defaultOutcome); } }