Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ public <CLAIM_TYPE> ClaimValue<CLAIM_TYPE> getClaimValue(String claimName, Claim
}
}

public void addIdTokenClaim(String claimName, ClaimValue<String> claimValue) {
idTokenClaims.put(claimName, claimValue);
}

public enum ClaimContext {
ID_TOKEN, USERINFO
}
Expand Down
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)) {
Copy link
Copy Markdown
Contributor

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 AcrUtils like:

Integer getMappedLoad(String loa, Map<String, Integer> acrLoaMap);

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure. Will do.

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() {

}
}
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);
}
}
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();

}
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;
}
}
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() {
}

}
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;
}
}
Loading