Fixes for UsernameIDNHomographValidator

closes #26564

Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
mposolda 2024-01-29 19:02:18 +01:00 committed by Marek Posolda
parent 5373f3c97a
commit 1213556eff
2 changed files with 70 additions and 6 deletions

View file

@ -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 <velias@redhat.com>
*
*/
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<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}

View file

@ -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<String, Object> 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);