-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Support to enforce LoA in authentication flow (Step-up) #16897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...c/main/java/org/keycloak/authentication/authenticators/browser/ForceLoAAuthenticator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package org.keycloak.authentication.authenticators.browser; | ||
|
|
||
| import org.keycloak.authentication.AuthenticationFlowContext; | ||
| import org.keycloak.authentication.Authenticator; | ||
| import org.keycloak.authentication.authenticators.util.AcrStore; | ||
| import org.keycloak.models.ClientModel; | ||
| import org.keycloak.models.Constants; | ||
| import org.keycloak.models.KeycloakSession; | ||
| import org.keycloak.models.RealmModel; | ||
| import org.keycloak.models.UserModel; | ||
| import org.keycloak.protocol.oidc.utils.AcrUtils; | ||
| import org.keycloak.sessions.AuthenticationSessionModel; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import static org.keycloak.models.Constants.NO_LOA; | ||
|
|
||
| public class ForceLoAAuthenticator implements Authenticator { | ||
|
|
||
| @Override | ||
| public void authenticate(AuthenticationFlowContext authenticationFlowContext) { | ||
| int configuredMinLoa = new ForceLoAAuthenticatorConfig( | ||
| authenticationFlowContext.getAuthenticatorConfig()).levelOfAuthentication(); | ||
|
|
||
| ClientModel client = authenticationFlowContext.getAuthenticationSession().getClient(); | ||
|
|
||
| int maxDefaultLoa = getMaxDefaultLoa(client); | ||
| int enforcedLoA = Math.max(configuredMinLoa, maxDefaultLoa); | ||
|
|
||
| AuthenticationSessionModel authenticationSession = authenticationFlowContext.getAuthenticationSession(); | ||
| AcrStore acrStore = new AcrStore(authenticationSession); | ||
| int requestedLevelOfAuthentication = acrStore.getRequestedLevelOfAuthentication(); | ||
| if (requestedLevelOfAuthentication < enforcedLoA) { | ||
| authenticationSession.setClientNote(Constants.REQUESTED_LEVEL_OF_AUTHENTICATION, | ||
| String.valueOf(enforcedLoA)); | ||
| authenticationSession.setClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION, Boolean.TRUE.toString()); | ||
| } | ||
| authenticationFlowContext.success(); | ||
| } | ||
|
|
||
| private int getMaxDefaultLoa(ClientModel client) { | ||
| int defaultLoa = NO_LOA; | ||
| List<String> defaultAcrValues = AcrUtils.getDefaultAcrValues(client); | ||
| Map<String, Integer> acrToLoaMap = AcrUtils.getAcrLoaMap(client); | ||
| if (acrToLoaMap.isEmpty()) { | ||
| acrToLoaMap = AcrUtils.getAcrLoaMap(client.getRealm()); | ||
| } | ||
| for (String configuredAcr : defaultAcrValues) { | ||
| int loa; | ||
| if (acrToLoaMap.containsKey(configuredAcr)) { | ||
| loa = acrToLoaMap.get(configuredAcr); | ||
| } else { | ||
| try { | ||
| loa = Integer.parseInt(configuredAcr); | ||
| } catch(NumberFormatException ex) { | ||
| loa = NO_LOA; | ||
| } | ||
| } | ||
| defaultLoa = Math.max(defaultLoa, loa); | ||
| } | ||
| return defaultLoa; | ||
| } | ||
|
|
||
| @Override | ||
| public void action(AuthenticationFlowContext authenticationFlowContext) { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean requiresUser() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean configuredFor(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) { | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
|
|
||
| } | ||
| } | ||
33 changes: 33 additions & 0 deletions
33
.../java/org/keycloak/authentication/authenticators/browser/ForceLoAAuthenticatorConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package org.keycloak.authentication.authenticators.browser; | ||
|
|
||
| import org.keycloak.models.AuthenticatorConfigModel; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Optional; | ||
|
|
||
| import static com.google.common.base.Strings.emptyToNull; | ||
| import static org.keycloak.models.Constants.NO_LOA; | ||
|
|
||
| final class ForceLoAAuthenticatorConfig { | ||
|
|
||
| static final String MIN_LOA = "minLoA"; | ||
|
|
||
| private final AuthenticatorConfigModel authenticatorConfigModel; | ||
|
|
||
| ForceLoAAuthenticatorConfig(AuthenticatorConfigModel configModel) { | ||
| this.authenticatorConfigModel = configModel; | ||
| } | ||
|
|
||
| int levelOfAuthentication() { | ||
| return getConfigMap() | ||
| .map(config -> config.getOrDefault(MIN_LOA, String.valueOf(NO_LOA))) | ||
| .map(str -> Optional.ofNullable(emptyToNull(str)).orElse(String.valueOf(NO_LOA))) | ||
| .map(Integer::parseInt) | ||
| .orElse(NO_LOA); | ||
| } | ||
|
|
||
| private Optional<Map<String, String>> getConfigMap() { | ||
| return Optional.ofNullable(authenticatorConfigModel) | ||
| .map(AuthenticatorConfigModel::getConfig); | ||
| } | ||
| } |
26 changes: 26 additions & 0 deletions
26
...keycloak/authentication/authenticators/browser/ForceLoAAuthenticatorConfigProperties.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package org.keycloak.authentication.authenticators.browser; | ||
|
|
||
| import org.keycloak.provider.ProviderConfigProperty; | ||
| import org.keycloak.provider.ProviderConfigurationBuilder; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static java.util.Collections.emptyList; | ||
| import static org.keycloak.authentication.authenticators.browser.ForceLoAAuthenticatorConfig.MIN_LOA; | ||
| import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE; | ||
|
|
||
| final class ForceLoAAuthenticatorConfigProperties { | ||
|
|
||
| private static final ProviderConfigProperty MIN_LOA_PROPERTY = new ProviderConfigProperty( | ||
| MIN_LOA, | ||
| "Minimum Level of Authenticaton (LoA)", | ||
| "The minimum level of authentication enforced by this authenticator. If a client request a LoA greater than this value, the requested LoA will be used. Otherwise, this configured value will be used.", | ||
| STRING_TYPE, | ||
| emptyList(), | ||
| false); | ||
|
|
||
| static final List<ProviderConfigProperty> CONFIG_PROPERTIES = ProviderConfigurationBuilder.create() | ||
| .property(MIN_LOA_PROPERTY) | ||
| .build(); | ||
|
|
||
| } |
79 changes: 79 additions & 0 deletions
79
...java/org/keycloak/authentication/authenticators/browser/ForceLoAAuthenticatorFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package org.keycloak.authentication.authenticators.browser; | ||
|
|
||
| import org.keycloak.Config; | ||
| import org.keycloak.authentication.Authenticator; | ||
| import org.keycloak.authentication.AuthenticatorFactory; | ||
| import org.keycloak.models.AuthenticationExecutionModel; | ||
| import org.keycloak.models.KeycloakSession; | ||
| import org.keycloak.models.KeycloakSessionFactory; | ||
| import org.keycloak.provider.ProviderConfigProperty; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static org.keycloak.authentication.authenticators.browser.ForceLoAAuthenticatorConfigProperties.CONFIG_PROPERTIES; | ||
| import static org.keycloak.models.AuthenticationExecutionModel.Requirement.DISABLED; | ||
| import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED; | ||
|
|
||
| public class ForceLoAAuthenticatorFactory implements AuthenticatorFactory { | ||
|
|
||
| private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{REQUIRED, DISABLED}; | ||
|
|
||
| private static final String PROVIDER_ID = "auth-force-loa"; | ||
|
|
||
| @Override | ||
| public String getDisplayType() { | ||
| return "Force Level of Authentication (LoA)"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getReferenceCategory() { | ||
| return "Authorization"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isConfigurable() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { | ||
| return REQUIREMENT_CHOICES; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isUserSetupAllowed() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public String getHelpText() { | ||
| return "This authenticator forces the minimum level of authentication (LoA) if a client does not request a LoA or the requested LoA is less than the configured LoA."; | ||
| } | ||
|
|
||
| @Override | ||
| public List<ProviderConfigProperty> getConfigProperties() { | ||
| return CONFIG_PROPERTIES; | ||
| } | ||
|
|
||
| @Override | ||
| public Authenticator create(KeycloakSession keycloakSession) { | ||
| return new ForceLoAAuthenticator(); | ||
| } | ||
|
|
||
| @Override | ||
| public void init(Config.Scope scope) { | ||
| } | ||
|
|
||
| @Override | ||
| public void postInit(KeycloakSessionFactory keycloakSessionFactory) { | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| } | ||
|
|
||
| @Override | ||
| public String getId() { | ||
| return PROVIDER_ID; | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
.../keycloak/authentication/authenticators/conditional/ConditionalClientIdAuthenticator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package org.keycloak.authentication.authenticators.conditional; | ||
|
|
||
| import org.keycloak.authentication.AuthenticationFlowContext; | ||
| import org.keycloak.models.ClientModel; | ||
| import org.keycloak.models.KeycloakSession; | ||
| import org.keycloak.models.RealmModel; | ||
| import org.keycloak.models.UserModel; | ||
|
|
||
| final class ConditionalClientIdAuthenticator implements ConditionalAuthenticator { | ||
|
|
||
| ConditionalClientIdAuthenticator() { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean matchCondition(AuthenticationFlowContext authenticationFlowContext) { | ||
| ClientModel client = authenticationFlowContext.getAuthenticationSession().getClient(); | ||
| ConditionalClientIdConfig config = new ConditionalClientIdConfig( | ||
| authenticationFlowContext.getAuthenticatorConfig()); | ||
| String clientId = client.getClientId(); | ||
| boolean matches = config.getClientIds().contains(clientId); | ||
| return config.isNegateOutput() != matches; | ||
| } | ||
|
|
||
| @Override | ||
| public void action(AuthenticationFlowContext authenticationFlowContext) { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean requiresUser() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| } | ||
|
|
||
| } |
82 changes: 82 additions & 0 deletions
82
...ak/authentication/authenticators/conditional/ConditionalClientIdAuthenticatorFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package org.keycloak.authentication.authenticators.conditional; | ||
|
|
||
| import org.jboss.logging.Logger; | ||
| import org.keycloak.Config; | ||
| import org.keycloak.authentication.Authenticator; | ||
| import org.keycloak.authentication.AuthenticatorFactory; | ||
| import org.keycloak.models.AuthenticationExecutionModel; | ||
| import org.keycloak.models.KeycloakSession; | ||
| import org.keycloak.models.KeycloakSessionFactory; | ||
| import org.keycloak.provider.ProviderConfigProperty; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static org.keycloak.models.AuthenticationExecutionModel.Requirement.DISABLED; | ||
| import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED; | ||
|
|
||
| public final class ConditionalClientIdAuthenticatorFactory implements AuthenticatorFactory { | ||
|
|
||
| private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{REQUIRED, DISABLED}; | ||
|
|
||
| private static final String PROVIDER_ID = "conditional-client-id"; | ||
|
|
||
| private Config.Scope config; | ||
|
|
||
| @Override | ||
| public String getDisplayType() { | ||
| return "Condition - Client id"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getReferenceCategory() { | ||
| return "Authorization"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isConfigurable() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { | ||
| return REQUIREMENT_CHOICES; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isUserSetupAllowed() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public String getHelpText() { | ||
| return "Flow is executed only if client id matches configured ids"; | ||
| } | ||
|
|
||
| @Override | ||
| public List<ProviderConfigProperty> getConfigProperties() { | ||
| return ConditionalClientIdConfigProperties.CONFIG_PROPERTIES; | ||
| } | ||
|
|
||
| @Override | ||
| public Authenticator create(KeycloakSession session) { | ||
| return new ConditionalClientIdAuthenticator(); | ||
| } | ||
|
|
||
| @Override | ||
| public void init(Config.Scope config) { | ||
| this.config = config; | ||
| } | ||
|
|
||
| @Override | ||
| public void postInit(KeycloakSessionFactory factory) { | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| } | ||
|
|
||
| @Override | ||
| public String getId() { | ||
| return PROVIDER_ID; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Is it possible to add util method to
AcrUtilslike:and make the code in lines 50-59 to use that util method? For that case, it will be also good to update method
AcrUtils.mapLoaToAcr(lines around 132-141) to use your new method. Just trying to avoid using same/similar logic on multiple places around codebase.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sure. Will do.