From 1213556eff46e743684ab94f9cf18fd7ebfefddf Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 29 Jan 2024 19:02:18 +0100 Subject: [PATCH] Fixes for UsernameIDNHomographValidator closes #26564 Signed-off-by: mposolda --- .../UsernameIDNHomographValidator.java | 38 +++++++++++++++++-- .../user/profile/UserProfileTest.java | 38 +++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/org/keycloak/userprofile/validator/UsernameIDNHomographValidator.java b/services/src/main/java/org/keycloak/userprofile/validator/UsernameIDNHomographValidator.java index 9f907ebd51..89e8d60712 100644 --- a/services/src/main/java/org/keycloak/userprofile/validator/UsernameIDNHomographValidator.java +++ b/services/src/main/java/org/keycloak/userprofile/validator/UsernameIDNHomographValidator.java @@ -16,13 +16,15 @@ */ package org.keycloak.userprofile.validator; -import org.keycloak.services.messages.Messages; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.services.validation.Validation; import org.keycloak.validate.SimpleValidator; import org.keycloak.validate.ValidationContext; import org.keycloak.validate.ValidationError; import org.keycloak.validate.ValidatorConfig; +import java.util.ArrayList; import java.util.List; /** @@ -31,10 +33,26 @@ import java.util.List; * @author Vlastimil Elias * */ -public class UsernameIDNHomographValidator implements SimpleValidator { +public class UsernameIDNHomographValidator implements SimpleValidator, ConfiguredProvider { public static final String ID = "up-username-not-idn-homograph"; + public static final String CFG_ERROR_MESSAGE = "error-message"; + + public static final String MESSAGE_NO_MATCH = "error-username-invalid-character"; + + private static final List configProperties = new ArrayList<>(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(CFG_ERROR_MESSAGE); + property.setLabel("Error message key"); + property.setHelpText("Key of the error message in i18n bundle. Dafault message key is " + MESSAGE_NO_MATCH); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + } + @Override public String getId() { return ID; @@ -52,10 +70,24 @@ public class UsernameIDNHomographValidator implements SimpleValidator { } if (!Validation.isBlank(value) && !Validation.isUsernameValid(value)) { - context.addError(new ValidationError(ID, value, Messages.INVALID_USERNAME)); + context.addError(new ValidationError(ID, inputHint, getErrorMessageKey(inputHint, config))); } return context; } + protected String getErrorMessageKey(String inputHint, ValidatorConfig config) { + String cfg = config.getString(CFG_ERROR_MESSAGE); + return (cfg != null && !cfg.isBlank()) ? cfg : MESSAGE_NO_MATCH; + } + + @Override + public String getHelpText() { + return "The field can contain only latin characters and common unicode characters. Useful for the fields, which can be subject of IDN homograph attacks (typically username)."; + } + + @Override + public List getConfigProperties() { + return configProperties; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java index 87d076b793..4d9de6e67b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java @@ -44,7 +44,6 @@ import java.util.Set; import java.util.function.Consumer; import org.junit.Assert; -import org.junit.ClassRule; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.common.Profile.Feature; @@ -65,7 +64,6 @@ import org.keycloak.services.messages.Messages; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.ModelTest; import org.keycloak.testsuite.runonserver.RunOnServer; -import org.keycloak.testsuite.util.LDAPRule; import org.keycloak.userprofile.AttributeGroupMetadata; import org.keycloak.representations.userprofile.config.UPAttribute; import org.keycloak.representations.userprofile.config.UPAttributePermissions; @@ -943,7 +941,7 @@ public class UserProfileTest extends AbstractUserProfileTest { profile.validate(); fail("Should fail validation"); } catch (ValidationException ve) { - assertTrue(ve.hasError(Messages.INVALID_USERNAME)); + assertTrue(ve.hasError(UsernameIDNHomographValidator.MESSAGE_NO_MATCH)); } UPConfig config = provider.getConfiguration(); @@ -962,6 +960,40 @@ public class UserProfileTest extends AbstractUserProfileTest { profile.validate(); } + @Test + public void testHomographValidator() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testHomographValidator); + } + + private static void testHomographValidator(KeycloakSession session) { + UserProfileProvider provider = getUserProfileProvider(session); + UPConfig config = parseDefaultConfig(); + + UPAttribute attribute = config.getAttribute(UserModel.LAST_NAME); + attribute.addValidation(UsernameIDNHomographValidator.ID, Map.of(UsernameIDNHomographValidator.CFG_ERROR_MESSAGE, "error-something")); + provider.setConfiguration(config); + try { + Map attributes = new HashMap<>(); + + attributes.put(UserModel.USERNAME, "abc"); + attributes.put(UserModel.EMAIL, "test@keycloak.org"); + attributes.put(UserModel.FIRST_NAME, "Foo"); + attributes.put(UserModel.LAST_NAME, "你好世界"); + + UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes); + + try { + profile.validate(); + fail("Should fail validation"); + } catch (ValidationException ve) { + assertTrue(ve.hasError("error-something")); + } + } finally { + attribute.getValidations().remove(UsernameIDNHomographValidator.ID); + provider.setConfiguration(config); + } + } + @Test public void testOptionalAttributes() { getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testOptionalAttributes);