Allow identity provider configuration without defaults for user authentication (#43963)

Closes #43552

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-11-05 14:13:40 +01:00 committed by GitHub
parent a9a14bd346
commit b278dbbb3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 228 additions and 131 deletions

View File

@ -49,14 +49,14 @@ public class IdentityProviderRepresentation {
* @see #UPFLM_OFF
*/
@Deprecated
protected String updateProfileFirstLoginMode = UPFLM_ON;
protected String updateProfileFirstLoginMode;
protected boolean trustEmail;
protected boolean storeToken;
protected boolean addReadTokenRoleOnCreate;
protected boolean authenticateByDefault;
protected boolean linkOnly;
protected boolean hideOnLogin;
protected Boolean trustEmail;
protected Boolean storeToken;
protected Boolean addReadTokenRoleOnCreate;
protected Boolean authenticateByDefault;
protected Boolean linkOnly;
protected Boolean hideOnLogin;
protected String firstBrokerLoginFlowAlias;
protected String postBrokerLoginFlowAlias;
protected String organizationId;
@ -103,19 +103,19 @@ public class IdentityProviderRepresentation {
this.enabled = enabled;
}
public boolean isLinkOnly() {
public Boolean isLinkOnly() {
return linkOnly;
}
public void setLinkOnly(boolean linkOnly) {
public void setLinkOnly(Boolean linkOnly) {
this.linkOnly = linkOnly;
}
public boolean isHideOnLogin() {
public Boolean isHideOnLogin() {
return this.hideOnLogin;
}
public void setHideOnLogin(boolean hideOnLogin) {
public void setHideOnLogin(Boolean hideOnLogin) {
this.hideOnLogin = hideOnLogin;
}
@ -126,8 +126,8 @@ public class IdentityProviderRepresentation {
* @deprecated {@link #setUpdateProfileFirstLoginMode(String)}
*/
@Deprecated
public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
this.updateProfileFirstLoginMode = updateProfileFirstLogin ? UPFLM_ON : UPFLM_OFF;
public void setUpdateProfileFirstLogin(Boolean updateProfileFirstLogin) {
this.updateProfileFirstLoginMode = updateProfileFirstLogin == null ? null : (updateProfileFirstLogin ? UPFLM_ON : UPFLM_OFF);
}
/**
@ -150,12 +150,12 @@ public class IdentityProviderRepresentation {
* @deprecated Replaced by configuration option in identity provider authenticator
*/
@Deprecated
public boolean isAuthenticateByDefault() {
public Boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
public void setAuthenticateByDefault(Boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}
@ -175,27 +175,27 @@ public class IdentityProviderRepresentation {
this.postBrokerLoginFlowAlias = postBrokerLoginFlowAlias;
}
public boolean isStoreToken() {
public Boolean isStoreToken() {
return this.storeToken;
}
public void setStoreToken(boolean storeToken) {
public void setStoreToken(Boolean storeToken) {
this.storeToken = storeToken;
}
public boolean isAddReadTokenRoleOnCreate() {
public Boolean isAddReadTokenRoleOnCreate() {
return addReadTokenRoleOnCreate;
}
public void setAddReadTokenRoleOnCreate(boolean addReadTokenRoleOnCreate) {
public void setAddReadTokenRoleOnCreate(Boolean addReadTokenRoleOnCreate) {
this.addReadTokenRoleOnCreate = addReadTokenRoleOnCreate;
}
public boolean isTrustEmail() {
public Boolean isTrustEmail() {
return trustEmail;
}
public void setTrustEmail(boolean trustEmail) {
public void setTrustEmail(Boolean trustEmail) {
this.trustEmail = trustEmail;
}

View File

@ -0,0 +1,38 @@
package org.keycloak.util;
public class Booleans {
/**
* Checks if a boolean is true, including support for null values where null is considered false
*
* @param b the boolean to check
* @return true if non-null and true
*/
public static Boolean isTrue(Boolean b) {
return b != null && b;
}
/**
* Checks if a boolean is false, including support for null values where null is considered false
*
* @param b the boolean to check
* @return true if null and false
*/
public static Boolean isFalse(Boolean b) {
return b == null || !b;
}
/**
* Compares two boolean, including support for null values where null is considered false
*
* @param a the first boolean to compare
* @param b the second boolean to compare
* @return true if both values have resolves to the same value
*/
public static Boolean equals(Boolean a, Boolean b) {
a = a != null && a;
b = b != null && b;
return a.equals(b);
}
}

View File

@ -30,6 +30,10 @@ For anyone implementing a custom federated user authentication identity provider
by Keycloak or `AbstractIdentityProvider` you need to update your implementation to implement
the new `UserAuthenticationIdentityProvider` interface (all methods remain the same, they have just been moved).
Additionally, both `IdentityProviderModel` and `IdentityProviderRepresentation` are now using Boolean values to allow
configuration like `isHideOnLogin` to be null in order to not include these in Identity Provider types that do
not need these configurations.
=== Accepting only normalized paths in requests
Previously {project_name} accepted HTTP requests with paths containing double dots (`..`) or double slashes (`//`). When processing them, it normalized the path by collapsing double slashes and normalized the path according to RFC3986.

View File

@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session;
@ -252,10 +253,11 @@ public class JpaIdentityProviderStorageProvider implements IdentityProviderStora
case ENABLED:
case HIDE_ON_LOGIN:
case LINK_ONLY: {
Path<Boolean> path = idp.get(key);
if (Boolean.parseBoolean(value)) {
predicates.add(builder.isTrue(idp.get(key)));
predicates.add(builder.isTrue(path));
} else {
predicates.add(builder.isFalse(idp.get(key)));
predicates.add(builder.or(builder.isNull(path), builder.equal(path, Boolean.FALSE)));
}
break;
}

View File

@ -57,22 +57,22 @@ public class IdentityProviderEntity {
private boolean enabled;
@Column(name = "TRUST_EMAIL")
private boolean trustEmail;
private Boolean trustEmail;
@Column(name="STORE_TOKEN")
private boolean storeToken;
private Boolean storeToken;
@Column(name="LINK_ONLY")
private boolean linkOnly;
private Boolean linkOnly;
@Column(name="HIDE_ON_LOGIN")
private boolean hideOnLogin;
private Boolean hideOnLogin;
@Column(name="ADD_TOKEN_ROLE")
protected boolean addReadTokenRoleOnCreate;
protected Boolean addReadTokenRoleOnCreate;
@Column(name="AUTHENTICATE_BY_DEFAULT")
private boolean authenticateByDefault;
private Boolean authenticateByDefault;
@Column(name="FIRST_BROKER_LOGIN_FLOW_ID")
private String firstBrokerLoginFlowId;
@ -129,27 +129,27 @@ public class IdentityProviderEntity {
this.enabled = enabled;
}
public boolean isStoreToken() {
public Boolean isStoreToken() {
return this.storeToken;
}
public void setStoreToken(boolean storeToken) {
public void setStoreToken(Boolean storeToken) {
this.storeToken = storeToken;
}
public boolean isAuthenticateByDefault() {
public Boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
public void setAuthenticateByDefault(boolean authenticateByDefault) {
public void setAuthenticateByDefault(Boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}
public boolean isLinkOnly() {
public Boolean isLinkOnly() {
return linkOnly;
}
public void setLinkOnly(boolean linkOnly) {
public void setLinkOnly(Boolean linkOnly) {
this.linkOnly = linkOnly;
}
@ -177,11 +177,11 @@ public class IdentityProviderEntity {
this.organizationId = organizationId;
}
public boolean isHideOnLogin() {
public Boolean isHideOnLogin() {
return this.hideOnLogin;
}
public void setHideOnLogin(boolean hideOnLogin) {
public void setHideOnLogin(Boolean hideOnLogin) {
this.hideOnLogin = hideOnLogin;
}
@ -193,19 +193,19 @@ public class IdentityProviderEntity {
this.config = config;
}
public boolean isAddReadTokenRoleOnCreate() {
public Boolean isAddReadTokenRoleOnCreate() {
return addReadTokenRoleOnCreate;
}
public void setAddReadTokenRoleOnCreate(boolean addReadTokenRoleOnCreate) {
public void setAddReadTokenRoleOnCreate(Boolean addReadTokenRoleOnCreate) {
this.addReadTokenRoleOnCreate = addReadTokenRoleOnCreate;
}
public boolean isTrustEmail() {
public Boolean isTrustEmail() {
return trustEmail;
}
public void setTrustEmail(boolean trustEmail) {
public void setTrustEmail(Boolean trustEmail) {
this.trustEmail = trustEmail;
}

View File

@ -58,4 +58,19 @@
</modifySql>
</changeSet>
<changeSet author="keycloak" id="26.5.0-idp-config-allow-null">
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="TRUST_EMAIL"/>
<dropNotNullConstraint tableName="IDENTITY_PROVIDER" columnName="TRUST_EMAIL" columnDataType="BOOLEAN"/>
<dropNotNullConstraint tableName="IDENTITY_PROVIDER" columnName="STORE_TOKEN" columnDataType="BOOLEAN"/>
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="STORE_TOKEN"/>
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="ADD_TOKEN_ROLE"/>
<dropNotNullConstraint tableName="IDENTITY_PROVIDER" columnName="ADD_TOKEN_ROLE" columnDataType="BOOLEAN"/>
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="AUTHENTICATE_BY_DEFAULT"/>
<dropNotNullConstraint tableName="IDENTITY_PROVIDER" columnName="AUTHENTICATE_BY_DEFAULT" columnDataType="BOOLEAN"/>
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="LINK_ONLY"/>
<dropNotNullConstraint tableName="IDENTITY_PROVIDER" columnName="LINK_ONLY" columnDataType="BOOLEAN"/>
<!-- HIDE_ON_LOGIN did not have a non-null constraint -->
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="HIDE_ON_LOGIN"/>
</changeSet>
</databaseChangeLog>

View File

@ -110,6 +110,7 @@ import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.UserStorageUtil;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.util.Booleans;
import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.StringUtil;
import org.keycloak.validation.ValidationUtil;
@ -1514,7 +1515,7 @@ public class DefaultExportImportManager implements ExportImportManager {
String defaultProvider = null;
if (rep.getIdentityProviders() != null) {
for (IdentityProviderRepresentation i : rep.getIdentityProviders()) {
if (i.isEnabled() && i.isAuthenticateByDefault()) {
if (i.isEnabled() && Booleans.isTrue(i.isAuthenticateByDefault())) {
defaultProvider = i.getProviderId();
break;
}

View File

@ -28,6 +28,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@ -206,7 +207,7 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
if (isNewUser || federatedEmail != null && !federatedEmail.equalsIgnoreCase(localEmail)) {
IdentityProviderModel config = context.getIdpConfig();
boolean trustEmail = config.isTrustEmail();
boolean trustEmail = Booleans.isTrue(config.isTrustEmail());
if (logger.isTraceEnabled()) {
logger.tracef("Email %s verified automatically after updating user '%s' through Identity provider '%s' ", trustEmail ? "" : "not", user.getUsername(), config.getAlias());

View File

@ -13,7 +13,11 @@ public final class IdentityProviderMapperSyncModeDelegate {
protected static final Logger logger = Logger.getLogger(IdentityProviderMapperSyncModeDelegate.class);
public static void delegateUpdateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context, IdentityProviderMapper mapper) {
IdentityProviderSyncMode effectiveSyncMode = combineIdpAndMapperSyncMode(context.getIdpConfig().getSyncMode(), mapperModel.getSyncMode());
IdentityProviderSyncMode idpSyncMode = context.getIdpConfig().getSyncMode();
if (idpSyncMode == null) {
idpSyncMode = IdentityProviderSyncMode.LEGACY;
}
IdentityProviderSyncMode effectiveSyncMode = combineIdpAndMapperSyncMode(idpSyncMode, mapperModel.getSyncMode());
if (!mapper.supportsSyncMode(effectiveSyncMode)) {
logger.warnf("The mapper %s does not explicitly support sync mode %s. Please ensure that the SPI supports the sync mode correctly and update it to reflect this.", mapper.getDisplayType(), effectiveSyncMode);

View File

@ -890,11 +890,6 @@ public class ModelToRepresentation {
providerRep.setConfig(config);
providerRep.setAddReadTokenRoleOnCreate(identityProviderModel.isAddReadTokenRoleOnCreate());
String syncMode = config.get(IdentityProviderModel.SYNC_MODE);
if (syncMode == null) {
config.put(IdentityProviderModel.SYNC_MODE, "LEGACY");
}
String firstBrokerLoginFlowId = identityProviderModel.getFirstBrokerLoginFlowId();
if (firstBrokerLoginFlowId != null) {
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(firstBrokerLoginFlowId);

View File

@ -75,18 +75,18 @@ public class IdentityProviderModel implements Serializable {
private boolean enabled;
private boolean trustEmail;
private Boolean trustEmail;
private boolean storeToken;
private Boolean storeToken;
protected boolean addReadTokenRoleOnCreate;
protected Boolean addReadTokenRoleOnCreate;
protected boolean linkOnly;
protected Boolean linkOnly;
/**
* Specifies if particular provider should be used by default for authentication even before displaying login screen
*/
private boolean authenticateByDefault;
private Boolean authenticateByDefault;
private String firstBrokerLoginFlowId;
@ -98,7 +98,7 @@ public class IdentityProviderModel implements Serializable {
private String displayIconClasses;
private boolean hideOnLogin;
private Boolean hideOnLogin;
/**
* <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
@ -162,29 +162,29 @@ public class IdentityProviderModel implements Serializable {
this.enabled = enabled;
}
public boolean isStoreToken() {
public Boolean isStoreToken() {
return this.storeToken;
}
public void setStoreToken(boolean storeToken) {
public void setStoreToken(Boolean storeToken) {
this.storeToken = storeToken;
}
public boolean isLinkOnly() {
public Boolean isLinkOnly() {
return linkOnly;
}
public void setLinkOnly(boolean linkOnly) {
public void setLinkOnly(Boolean linkOnly) {
this.linkOnly = linkOnly;
}
@Deprecated
public boolean isAuthenticateByDefault() {
public Boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
public void setAuthenticateByDefault(Boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}
@ -212,19 +212,19 @@ public class IdentityProviderModel implements Serializable {
this.config = config;
}
public boolean isAddReadTokenRoleOnCreate() {
public Boolean isAddReadTokenRoleOnCreate() {
return addReadTokenRoleOnCreate;
}
public void setAddReadTokenRoleOnCreate(boolean addReadTokenRoleOnCreate) {
public void setAddReadTokenRoleOnCreate(Boolean addReadTokenRoleOnCreate) {
this.addReadTokenRoleOnCreate = addReadTokenRoleOnCreate;
}
public boolean isTrustEmail() {
public Boolean isTrustEmail() {
return trustEmail;
}
public void setTrustEmail(boolean trustEmail) {
public void setTrustEmail(Boolean trustEmail) {
this.trustEmail = trustEmail;
}
@ -259,35 +259,35 @@ public class IdentityProviderModel implements Serializable {
}
public IdentityProviderSyncMode getSyncMode() {
return IdentityProviderSyncMode.valueOf(getConfig().getOrDefault(SYNC_MODE, "LEGACY"));
String syncMode = getConfig().get(SYNC_MODE);
return syncMode != null ? IdentityProviderSyncMode.valueOf(syncMode) : null;
}
public void setSyncMode(IdentityProviderSyncMode syncMode) {
getConfig().put(SYNC_MODE, syncMode.toString());
getConfig().put(SYNC_MODE, syncMode != null ? syncMode.toString() : null);
}
public boolean isLoginHint() {
return Boolean.valueOf(getConfig().get(LOGIN_HINT));
return getBooleanConfig(LOGIN_HINT);
}
public void setLoginHint(boolean loginHint) {
getConfig().put(LOGIN_HINT, String.valueOf(loginHint));
public void setLoginHint(Boolean loginHint) {
setBooleanConfig(LOGIN_HINT, loginHint);
}
public boolean isPassMaxAge() {
return Boolean.valueOf(getConfig().get(PASS_MAX_AGE));
return getBooleanConfig(PASS_MAX_AGE);
}
public void setPassMaxAge(boolean passMaxAge) {
getConfig().put(PASS_MAX_AGE, String.valueOf(passMaxAge));
public void setPassMaxAge(Boolean passMaxAge) {
setBooleanConfig(PASS_MAX_AGE, passMaxAge);
}
public boolean isHideOnLogin() {
public Boolean isHideOnLogin() {
return this.hideOnLogin;
}
public void setHideOnLogin(boolean hideOnLogin) {
public void setHideOnLogin(Boolean hideOnLogin) {
this.hideOnLogin = hideOnLogin;
}
@ -297,23 +297,23 @@ public class IdentityProviderModel implements Serializable {
* @return
*/
public boolean isTransientUsers() {
return Profile.isFeatureEnabled(Feature.TRANSIENT_USERS) && Boolean.valueOf(getConfig().get(DO_NOT_STORE_USERS));
return Profile.isFeatureEnabled(Feature.TRANSIENT_USERS) && getBooleanConfig(DO_NOT_STORE_USERS);
}
/**
* Configures the IdP to not store users in Keycloak database. Default value: {@code false}.
* @return
*/
public void setTransientUsers(boolean transientUsers) {
getConfig().put(DO_NOT_STORE_USERS, String.valueOf(transientUsers));
public void setTransientUsers(Boolean transientUsers) {
setBooleanConfig(DO_NOT_STORE_USERS, transientUsers);
}
public boolean isFilteredByClaims() {
return Boolean.valueOf(getConfig().getOrDefault(FILTERED_BY_CLAIMS, Boolean.toString(false)));
return getBooleanConfig(FILTERED_BY_CLAIMS);
}
public void setFilteredByClaims(boolean filteredByClaims) {
getConfig().put(FILTERED_BY_CLAIMS, String.valueOf(filteredByClaims));
public void setFilteredByClaims(Boolean filteredByClaims) {
setBooleanConfig(FILTERED_BY_CLAIMS, filteredByClaims);
}
public String getClaimFilterName() {
@ -341,11 +341,11 @@ public class IdentityProviderModel implements Serializable {
}
public boolean isCaseSensitiveOriginalUsername() {
return Boolean.parseBoolean(getConfig().getOrDefault(CASE_SENSITIVE_ORIGINAL_USERNAME, Boolean.FALSE.toString()));
return getBooleanConfig(CASE_SENSITIVE_ORIGINAL_USERNAME);
}
public void setCaseSensitiveOriginalUsername(boolean caseSensitive) {
getConfig().put(CASE_SENSITIVE_ORIGINAL_USERNAME, Boolean.valueOf(caseSensitive).toString());
public void setCaseSensitiveOriginalUsername(Boolean caseSensitive) {
setBooleanConfig(CASE_SENSITIVE_ORIGINAL_USERNAME, caseSensitive);
}
public void setMinValidityToken(int minValidityToken) {
@ -386,4 +386,14 @@ public class IdentityProviderModel implements Serializable {
return Objects.equals(getInternalId(), ((IdentityProviderModel) obj).getInternalId()) &&
Objects.equals(getAlias(), ((IdentityProviderModel) obj).getAlias());
}
private boolean getBooleanConfig(String key) {
String value = getConfig().get(key);
return value != null ? Boolean.parseBoolean(value) : Boolean.FALSE;
}
private void setBooleanConfig(String key, Boolean value) {
getConfig().put(key, value != null ? String.valueOf(value) : null);
}
}

View File

@ -23,6 +23,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.util.Booleans;
import org.keycloak.provider.Provider;
/**
@ -236,9 +237,9 @@ public interface IdentityProviderStorageProvider extends Provider {
ENABLED(IdentityProviderModel.ENABLED, Boolean.TRUE.toString(), IdentityProviderModel::isEnabled),
LINK_ONLY(IdentityProviderModel.LINK_ONLY, Boolean.FALSE.toString(), Predicate.not(IdentityProviderModel::isLinkOnly)),
LINK_ONLY(IdentityProviderModel.LINK_ONLY, Boolean.FALSE.toString(), m -> Booleans.isFalse(m.isLinkOnly())),
HIDE_ON_LOGIN(IdentityProviderModel.HIDE_ON_LOGIN, Boolean.FALSE.toString(), Predicate.not(IdentityProviderModel::isHideOnLogin));
HIDE_ON_LOGIN(IdentityProviderModel.HIDE_ON_LOGIN, Boolean.FALSE.toString(), m -> Booleans.isFalse(m.isHideOnLogin()));
private final String key;
private final String value;

View File

@ -17,7 +17,7 @@ public class KubernetesIdentityProviderConfig extends OIDCIdentityProviderConfig
}
@Override
public boolean isHideOnLogin() {
public Boolean isHideOnLogin() {
return true;
}

View File

@ -90,6 +90,7 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.urls.UrlType;
import org.keycloak.util.Booleans;
import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.StringUtil;
import org.keycloak.vault.VaultStringSecret;
@ -357,7 +358,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
event.error(Errors.INVALID_REQUEST);
return exchangeUnsupportedRequiredType();
}
if (!getConfig().isStoreToken()) {
if (Booleans.isFalse(getConfig().isStoreToken())) {
// if token isn't stored, we need to see if this session has been linked
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
brokerId = brokerId == null ? tokenUserSession.getNote(UserAuthenticationIdentityProvider.EXTERNAL_IDENTITY_PROVIDER) : brokerId;
@ -474,7 +475,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
BrokeredIdentityContext context = doGetFederatedIdentity(accessToken);
if (getConfig().isStoreToken() && response.startsWith("{")) {
if (Booleans.isTrue(getConfig().isStoreToken()) && response.startsWith("{")) {
try {
OAuthResponse tokenResponse = JsonSerialization.readValue(response, OAuthResponse.class);
if (tokenResponse.getExpiresIn() != null && tokenResponse.getExpiresIn() > 0) {
@ -512,7 +513,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
AuthenticationSessionModel authenticationSession = request.getAuthenticationSession();
String loginHint = authenticationSession.getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
if (getConfig().isLoginHint() && loginHint != null) {
if (Booleans.isTrue(getConfig().isLoginHint()) && loginHint != null) {
uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
}
@ -745,7 +746,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
BrokeredIdentityContext federatedIdentity = provider.getFederatedIdentity(response);
if (providerConfig.isStoreToken()) {
if (Booleans.isTrue(providerConfig.isStoreToken())) {
// make sure that token wasn't already set by getFederatedIdentity();
// want to be able to allow provider to set the token itself.
if (federatedIdentity.getToken() == null)federatedIdentity.setToken(response);

View File

@ -80,6 +80,7 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.IdentityBrokerService;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.TokenUtil;
import org.keycloak.vault.VaultStringSecret;
@ -413,7 +414,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
JsonWebToken idToken = validateToken(encodedIdToken);
if (getConfig().isPassMaxAge()) {
if (Booleans.isTrue(getConfig().isPassMaxAge())) {
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
if (isAuthTimeExpired(idToken, authSession)) {
@ -464,7 +465,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
identity.getContextData().put(BROKER_NONCE_PARAM, idToken.getOtherClaims().get(OIDCLoginProtocol.NONCE_PARAM));
}
if (getConfig().isStoreToken()) {
if (Booleans.isTrue(getConfig().isStoreToken())) {
if (tokenResponse.getExpiresIn() > 0) {
long accessTokenExpiration = Time.currentTime() + tokenResponse.getExpiresIn();
tokenResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
@ -976,7 +977,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
String maxAge = request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.MAX_AGE_PARAM);
if (getConfig().isPassMaxAge() && maxAge != null) {
if (Booleans.isTrue(getConfig().isPassMaxAge()) && maxAge != null) {
uriBuilder.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
}
@ -1023,7 +1024,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
.orElseGet(() -> contextData.get(VALIDATED_ACCESS_TOKEN));
Boolean emailVerified = getEmailVerifiedClaim(token);
if (!config.isTrustEmail() || emailVerified == null) {
if (Booleans.isFalse(config.isTrustEmail()) || emailVerified == null) {
// fallback to the default behavior if trust is disabled or there is no email_verified claim
super.setEmailVerified(user, context);
return;

View File

@ -119,6 +119,7 @@ import org.keycloak.saml.validators.ConditionsValidator;
import org.keycloak.saml.validators.DestinationValidator;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
import org.keycloak.utils.StringUtil;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
@ -658,7 +659,7 @@ public class SAMLEndpoint {
identity.setEmail(subjectNameID.getValue());
}
if (config.isStoreToken()) {
if (Booleans.isTrue(config.isStoreToken())) {
identity.setToken(samlResponse);
}

View File

@ -84,6 +84,7 @@ import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.saml.validators.DestinationValidator;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
import org.keycloak.util.JsonSerialization;
import org.w3c.dom.Document;
@ -159,7 +160,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
Integer attributeConsumingServiceIndex = getConfig().getAttributeConsumingServiceIndex();
String loginHint = getConfig().isLoginHint() ? request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM) : null;
String loginHint = Booleans.isTrue(getConfig().isLoginHint()) ? request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM) : null;
Boolean allowCreate = null;
if (getConfig().getConfig().get(SAMLIdentityProviderConfig.ALLOW_CREATE) == null || getConfig().isAllowCreate())
allowCreate = Boolean.TRUE;

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jakarta.ws.rs.core.MultivaluedHashMap;
@ -66,6 +65,7 @@ import org.keycloak.organization.utils.Organizations;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
@ -429,7 +429,7 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
}
private boolean hasPublicBrokers(OrganizationModel organization) {
return organization.getIdentityProviders().anyMatch(Predicate.not(IdentityProviderModel::isHideOnLogin));
return organization.getIdentityProviders().anyMatch(i -> Booleans.isFalse(i.isHideOnLogin()));
}
private OrganizationProvider getOrganizationProvider() {

View File

@ -25,6 +25,7 @@ import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OrganizationModel;
import org.keycloak.organization.utils.Organizations;
import org.keycloak.util.Booleans;
import static org.keycloak.models.IdentityProviderStorageProvider.FetchMode.ALL;
import static org.keycloak.models.IdentityProviderStorageProvider.FetchMode.ORG_ONLY;
@ -64,7 +65,7 @@ public class OrganizationAwareIdentityProviderBean extends IdentityProviderBean
// we already have the organization, just fetch the organization's public enabled IDPs.
if (this.organization != null) {
return organization.getIdentityProviders()
.filter(idp -> idp.isEnabled() && !idp.isLinkOnly() && !idp.isHideOnLogin())
.filter(idp -> idp.isEnabled() && Booleans.isFalse(idp.isLinkOnly()) && Booleans.isFalse(idp.isHideOnLogin()))
.filter(idp -> !Objects.equals(existingIDP, idp.getAlias()))
.map(idp -> createIdentityProvider(super.realm, super.baseURI, idp))
.sorted(IDP_COMPARATOR_INSTANCE).toList();
@ -103,6 +104,6 @@ public class OrganizationAwareIdentityProviderBean extends IdentityProviderBean
if (organization != null && !Objects.equals(organization.getId(),idp.getOrganizationId())) {
return false;
}
return !idp.isHideOnLogin();
return Booleans.isFalse(idp.isHideOnLogin());
}
}

View File

@ -63,6 +63,7 @@ import org.keycloak.services.resources.admin.fgap.AdminPermissions;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.util.Booleans;
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
import static org.keycloak.models.IdentityProviderType.EXCHANGE_EXTERNAL_TOKEN;
@ -389,7 +390,7 @@ public abstract class AbstractTokenExchangeProvider implements TokenExchangeProv
target.importNewUser(session, realm, user, mapper, context);
}
if (context.getIdpConfig().isTrustEmail() && !Validation.isBlank(user.getEmail())) {
if (Booleans.isTrue(context.getIdpConfig().isTrustEmail()) && !Validation.isBlank(user.getEmail())) {
logger.debugf("Email verified automatically after registration of user '%s' through Identity provider '%s' ", user.getUsername(), context.getIdpConfig().getAlias());
user.setEmailVerified(true);
}

View File

@ -96,6 +96,7 @@ import org.keycloak.services.util.DefaultClientSessionContext;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.util.Booleans;
import org.keycloak.util.JsonSerialization;
import jakarta.ws.rs.GET;
@ -395,7 +396,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
if (identityProviderModel == null) {
throw new IdentityBrokerException("Identity Provider [" + providerAlias + "] not found.");
}
if (identityProviderModel.isLinkOnly()) {
if (Booleans.isTrue(identityProviderModel.isLinkOnly())) {
throw new IdentityBrokerException("Identity Provider [" + providerAlias + "] is not allowed to perform a login.");
}
if (clientSessionCode.getClientSession() != null && loginHint != null) {
@ -504,7 +505,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, providerAlias);
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerAlias);
if (identityProviderConfig.isStoreToken()) {
if (Booleans.isTrue(identityProviderConfig.isStoreToken())) {
FederatedIdentityModel identity = this.session.users().getFederatedIdentity(this.realmModel, authResult.user(), providerAlias);
if (identity == null) {
@ -553,7 +554,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
String providerAlias = identityProviderConfig.getAlias();
if (!identityProviderConfig.isStoreToken()) {
if (Booleans.isFalse(identityProviderConfig.isStoreToken())) {
if (isDebugEnabled()) {
logger.debugf("Token will not be stored for identity provider [%s].", providerAlias);
}
@ -731,7 +732,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
event.user(federatedUser);
event.detail(Details.USERNAME, federatedUser.getUsername());
if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
if (Booleans.isTrue(context.getIdpConfig().isAddReadTokenRoleOnCreate())) {
ClientModel brokerClient = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);
if (brokerClient == null) {
throw new IdentityBrokerException("Client 'broker' not available. Maybe realm has not migrated to support the broker token exchange service");
@ -1014,7 +1015,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
if (federatedUser != null) {
if (context.getIdpConfig().isStoreToken()) {
if (Booleans.isTrue(context.getIdpConfig().isStoreToken())) {
FederatedIdentityModel oldModel = this.session.users().getFederatedIdentity(this.realmModel, federatedUser, context.getIdpConfig().getAlias());
if (!ObjectUtil.isEqualOrBothNull(context.getToken(), oldModel.getToken())) {
this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, newModel);
@ -1155,7 +1156,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
}
private void updateToken(BrokeredIdentityContext context, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
if (Booleans.isTrue(context.getIdpConfig().isStoreToken()) && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
// like in OIDCIdentityProvider.exchangeStoredToken()
// we shouldn't override the refresh token if it is null in the context and not null in the DB
// as for google IDP it will be lost forever

View File

@ -129,12 +129,7 @@ public class IdentityProvidersResource {
String providerId = formDataMap.getFirst("providerId").asString();
String config = StreamUtil.readString(formDataMap.getFirst("file").asInputStream());
IdentityProviderFactory<?> providerFactory = getProviderFactoryById(providerId);
final Map<String, String> parsedConfig = providerFactory.parseConfig(session, config);
String syncMode = parsedConfig.get(IdentityProviderModel.SYNC_MODE);
if (syncMode == null) {
parsedConfig.put(IdentityProviderModel.SYNC_MODE, "LEGACY");
}
return parsedConfig;
return providerFactory.parseConfig(session, config);
}
/**

View File

@ -42,6 +42,7 @@ import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.util.Booleans;
import org.keycloak.vault.VaultStringSecret;
import twitter4j.AccessToken;
import twitter4j.OAuthAuthorization;
@ -132,7 +133,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
if (requestedType != null && !requestedType.equals(TWITTER_TOKEN_TYPE)) {
return exchangeUnsupportedRequiredType();
}
if (!getConfig().isStoreToken()) {
if (Booleans.isFalse(getConfig().isStoreToken())) {
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
@ -255,7 +256,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
tokenBuilder.append("\"user_id\":").append("\"").append(oAuthAccessToken.getUserId()).append("\"");
tokenBuilder.append("}");
String token = tokenBuilder.toString();
if (providerConfig.isStoreToken()) {
if (Booleans.isTrue(providerConfig.isStoreToken())) {
identity.setToken(token);
}
identity.getContextData().put(UserAuthenticationIdentityProvider.FEDERATED_ACCESS_TOKEN, token);

View File

@ -35,8 +35,11 @@ public class AbstractIdentityProviderTest {
String secret = idpRep.getConfig() != null ? idpRep.getConfig().get("clientSecret") : null;
idpRep = StripSecretsUtils.stripSecrets(null, idpRep);
// if legacy hide on login page attribute was used, the attr will be removed when converted to model
idpRep.setHideOnLogin(Boolean.parseBoolean(idpRep.getConfig().remove(IdentityProviderModel.LEGACY_HIDE_ON_LOGIN_ATTR)));
if ("true".equals(idpRep.getConfig().get(IdentityProviderModel.LEGACY_HIDE_ON_LOGIN_ATTR))) {
idpRep.setHideOnLogin(true);
idpRep.getConfig().remove(IdentityProviderModel.LEGACY_HIDE_ON_LOGIN_ATTR);
}
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.identityProviderPath(idpRep.getAlias()), idpRep, ResourceType.IDENTITY_PROVIDER);

View File

@ -93,8 +93,8 @@ public class IdentityProviderOidcTest extends AbstractIdentityProviderTest {
assertEquals("clientId", representation.getConfig().get("clientId"));
assertEquals(ComponentRepresentation.SECRET_VALUE, representation.getConfig().get("clientSecret"));
assertTrue(representation.isEnabled());
assertFalse(representation.isStoreToken());
assertFalse(representation.isTrustEmail());
assertNull(representation.isStoreToken());
assertNull(representation.isTrustEmail());
assertNull(representation.getFirstBrokerLoginFlowAlias());
assertEquals("some secret value", runOnServer.fetch(s -> s.identityProviders().getByAlias("new-identity-provider").getConfig().get("clientSecret"), String.class));
@ -211,8 +211,8 @@ public class IdentityProviderOidcTest extends AbstractIdentityProviderTest {
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, representation.getConfig().get("clientAuthMethod"));
assertTrue(representation.isEnabled());
assertFalse(representation.isStoreToken());
assertFalse(representation.isTrustEmail());
assertNull(representation.isStoreToken());
assertNull(representation.isTrustEmail());
assertEquals("some secret value", runOnServer.fetch(s -> s.identityProviders().getByAlias("new-identity-provider").getConfig().get("clientSecret"), String.class));
@ -249,8 +249,8 @@ public class IdentityProviderOidcTest extends AbstractIdentityProviderTest {
assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, representation.getConfig().get("clientAuthMethod"));
assertNull(representation.getConfig().get("jwtX509HeadersEnabled"));
assertTrue(representation.isEnabled());
assertFalse(representation.isStoreToken());
assertFalse(representation.isTrustEmail());
assertNull(representation.isStoreToken());
assertNull(representation.isTrustEmail());
managedRealm.cleanup().add(r -> r.identityProviders().get(id).remove());
}
@ -283,8 +283,8 @@ public class IdentityProviderOidcTest extends AbstractIdentityProviderTest {
assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, representation.getConfig().get("clientAuthMethod"));
assertEquals("true", representation.getConfig().get("jwtX509HeadersEnabled"));
assertTrue(representation.isEnabled());
assertFalse(representation.isStoreToken());
assertFalse(representation.isTrustEmail());
assertNull(representation.isStoreToken());
assertNull(representation.isTrustEmail());
managedRealm.cleanup().add(r -> r.identityProviders().get(id).remove());
}

View File

@ -417,7 +417,7 @@ public class IdentityProviderSamlTest extends AbstractIdentityProviderTest {
// import endpoint simply converts IDPSSODescriptor into key value pairs.
// check that saml-idp-metadata.xml was properly converted into key value pairs
//System.out.println(config);
List<String> keys = new ArrayList<>(List.of("syncMode",
List<String> keys = new ArrayList<>(List.of(
"validateSignature",
"singleLogoutServiceUrl",
"postBindingLogout",

View File

@ -2,6 +2,9 @@ package org.keycloak.tests.client.authentication.external;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.ws.rs.core.Response;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
@ -29,6 +32,7 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
import org.openqa.selenium.NoSuchElementException;
import java.io.IOException;
import java.util.Map;
@KeycloakIntegrationTest(config = SpiffeClientAuthTest.SpiffeServerConfig.class)
@TestMethodOrder(MethodOrderer.MethodName.class)
@ -58,6 +62,22 @@ public class SpiffeConfigTest {
IdentityProviderRepresentation rep = createConfig("testConfig", "spiffe://test", "https://localhost");
Assertions.assertEquals(201, idps.create(rep).getStatus());
IdentityProviderRepresentation createdRep = realm.admin().identityProviders().get(rep.getAlias()).toRepresentation();
Assertions.assertTrue(createdRep.isEnabled());
MatcherAssert.assertThat(createdRep.getConfig(), Matchers.equalTo(Map.of("bundleEndpoint", "https://localhost", "issuer", "spiffe://test")));
Assertions.assertNull(createdRep.getUpdateProfileFirstLoginMode());
Assertions.assertNull(createdRep.getFirstBrokerLoginFlowAlias());
Assertions.assertNull(createdRep.getPostBrokerLoginFlowAlias());
Assertions.assertNull(createdRep.getOrganizationId());
Assertions.assertNull(createdRep.isAddReadTokenRoleOnCreate());
Assertions.assertNull(createdRep.isAuthenticateByDefault());
Assertions.assertNull(createdRep.isHideOnLogin());
Assertions.assertNull(createdRep.isLinkOnly());
Assertions.assertNull(createdRep.isTrustEmail());
Assertions.assertNull(createdRep.isStoreToken());
checkNotDisplayOnLoginPages("testConfig");
checkNoIdpsInAccountConsole();
}

View File

@ -857,13 +857,13 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
}
private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource,
IdentityProviderSyncMode syncMode, boolean trustEmail) {
IdentityProviderSyncMode syncMode, Boolean trustEmail) {
assertThat(idProvider, Matchers.notNullValue());
assertThat(idProviderResource, Matchers.notNullValue());
assertThat(syncMode, Matchers.notNullValue());
if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name())
&& idProvider.isTrustEmail() == trustEmail) {
&& trustEmail.equals(idProvider.isTrustEmail())) {
return;
}

View File

@ -55,7 +55,7 @@ public class KcAdmCreateTest extends AbstractAdmCliTest {
}
// If the sync mode is not present on creating the idp, it will never be stored automatically. However, the model will always report behaviour as "LEGACY", so no errors should occur.
Assert.assertEquals("LEGACY", realmResource.identityProviders().get("idpAlias").toRepresentation().getConfig().get(IdentityProviderModel.SYNC_MODE));
Assert.assertNull(realmResource.identityProviders().get("idpAlias").toRepresentation().getConfig().get(IdentityProviderModel.SYNC_MODE));
}
@Test