Disable username prohibited chars validator when email as username is… (#31140)

* Disable username prohibited chars validator when email as the username is set

Closes #25339

Signed-off-by: Martin Kanis <mkanis@redhat.com>
Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Martin Kanis 2024-07-10 14:46:24 +02:00 committed by GitHub
parent d475833361
commit 922eaa9fc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 51 additions and 14 deletions

View file

@ -285,6 +285,7 @@ The list below provides a list of all the built-in validators:
|username-prohibited-characters
| Check if the value is a valid username as an additional barrier for attacks such as script injection. The validation is based on a default RegEx pattern that blocks characters not common in usernames.
When the realm setting `Email as username` is enabled, this validator is skipped to allow email values.
|
*error-message*: the key of the error message in i18n bundle. If not set a generic message is used.

View file

@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.validate.AbstractStringValidator;
@ -40,11 +42,11 @@ public class UsernameProhibitedCharactersValidator extends AbstractStringValidat
public static final UsernameProhibitedCharactersValidator INSTANCE = new UsernameProhibitedCharactersValidator();
protected static final Pattern PATTERN = Pattern.compile("^[^<>&\"'\\s\\v\\h$%!#?§,;:*~/\\\\|^=\\[\\]{}()`\\p{Cntrl}]+$");
public static final String MESSAGE_NO_MATCH = "error-username-invalid-character";
public static final String CFG_ERROR_MESSAGE = "error-message";
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
@ -64,12 +66,22 @@ public class UsernameProhibitedCharactersValidator extends AbstractStringValidat
@Override
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
KeycloakSession session = context.getSession();
if (session != null) {
RealmModel realm = session.getContext().getRealm();
if (realm.isRegistrationEmailAsUsername()) {
return;
}
}
if (!PATTERN.matcher(value).matches()) {
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, MESSAGE_NO_MATCH)));
}
}
@Override
public String getHelpText() {
return "Basic Username validator disallowing bunch of characters we really do not expect in username.";

View file

@ -321,6 +321,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
public void testValidation() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::failValidationWhenEmptyAttributes);
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testAttributeValidation);
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testEmailAsUsernameValidation);
}
private static void failValidationWhenEmptyAttributes(KeycloakSession session) {
@ -398,7 +399,30 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertFalse(profile.getAttributes().validate(UserModel.EMAIL, (Consumer<ValidationError>) errors::add));
assertTrue(containsErrorMessage(errors, EmailValidator.MESSAGE_INVALID_EMAIL));
}
private static void testEmailAsUsernameValidation(KeycloakSession session) {
Map<String, Object> attributes = new HashMap<>();
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
provider.setConfiguration(null);
UserProfile profile;
RealmModel realm = session.getContext().getRealm();
try {
realm.setRegistrationEmailAsUsername(true);
attributes.clear();
attributes.put(UserModel.FIRST_NAME, "Joe");
attributes.put(UserModel.LAST_NAME, "Doe");
// valid email but invalid as username
attributes.put(UserModel.EMAIL, "foo%bar@example.com");
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
profile.validate();
} catch (ValidationException ve) {
Assert.fail("Should be OK email as username");
} finally {
realm.setRegistrationEmailAsUsername(false);
}
}
private static boolean containsErrorMessage(List<ValidationError> errors, String message){
for(ValidationError err : errors) {
if(err.getMessage().equals(message)) {
@ -407,7 +431,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
}
return false;
}
@Test
public void testValidateComplianceWithUserProfile() {
@ -516,14 +540,14 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertThat(attributes.nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "address", "second"));
AttributeGroupMetadata companyAddressGroup = attributes.getMetadata("address").getAttributeGroupMetadata();
assertEquals("companyaddress", companyAddressGroup.getName());
assertEquals("header", companyAddressGroup.getDisplayHeader());
assertEquals("description", companyAddressGroup.getDisplayDescription());
assertNull(companyAddressGroup.getAnnotations());
AttributeGroupMetadata groupwithannoGroup = attributes.getMetadata("second").getAttributeGroupMetadata();
assertEquals("groupwithanno", groupwithannoGroup.getName());
assertNull(groupwithannoGroup.getDisplayHeader());
@ -572,9 +596,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributesUpdatedOldValues.put(UserModel.FIRST_NAME, "Joe");
attributesUpdatedOldValues.put(UserModel.LAST_NAME, "Doe");
attributesUpdatedOldValues.put(UserModel.EMAIL, "user@keycloak.org");
profile.update((attributeName, userModel, oldValue) -> {
assertTrue(attributesUpdated.add(attributeName));
assertTrue(attributesUpdated.add(attributeName));
assertEquals(attributesUpdatedOldValues.get(attributeName), getSingleValue(oldValue));
assertEquals(attributes.get(attributeName), userModel.getFirstAttribute(attributeName));
});
@ -593,13 +617,13 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertEquals("fixed-business-address", user.getFirstAttribute("business.address"));
}
private static String getSingleValue(List<String> vals) {
if(vals==null || vals.isEmpty())
return null;
return vals.get(0);
}
@Test
public void testReadonlyUpdates() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates);