Fixing UriValidator
closes #26792 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
4ff4c3f897
commit
b4d289c562
4 changed files with 126 additions and 12 deletions
|
@ -6,6 +6,10 @@ import { HelpItem } from "ui-shared";
|
|||
import { MultiLineInput } from "../multi-line-input/MultiLineInput";
|
||||
import { convertToName } from "./DynamicComponents";
|
||||
|
||||
function convertDefaultValue(formValue?: any): string[] {
|
||||
return formValue && Array.isArray(formValue) ? formValue : [formValue];
|
||||
}
|
||||
|
||||
export const MultiValuedStringComponent = ({
|
||||
name,
|
||||
label,
|
||||
|
@ -29,7 +33,7 @@ export const MultiValuedStringComponent = ({
|
|||
aria-label={t(label!)}
|
||||
name={fieldName}
|
||||
isDisabled={isDisabled}
|
||||
defaultValue={[defaultValue]}
|
||||
defaultValue={convertDefaultValue(defaultValue)}
|
||||
addButtonLabel={t("addMultivaluedLabel", {
|
||||
fieldLabel: t(label!).toLowerCase(),
|
||||
})}
|
||||
|
|
|
@ -18,7 +18,7 @@ package org.keycloak.validate.validators;
|
|||
|
||||
import org.keycloak.provider.ConfiguredProvider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.validate.SimpleValidator;
|
||||
import org.keycloak.validate.AbstractSimpleValidator;
|
||||
import org.keycloak.validate.ValidationContext;
|
||||
import org.keycloak.validate.ValidationError;
|
||||
import org.keycloak.validate.ValidatorConfig;
|
||||
|
@ -27,6 +27,7 @@ import java.net.MalformedURLException;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -37,7 +38,7 @@ import java.util.Set;
|
|||
* URI validation - accepts {@link URI}, {@link URL} and single String. Null input is valid, use other validators (like
|
||||
* {@link NotBlankValidator} or {@link NotEmptyValidator} to force field as required.
|
||||
*/
|
||||
public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
||||
public class UriValidator extends AbstractSimpleValidator implements ConfiguredProvider {
|
||||
|
||||
public static final UriValidator INSTANCE = new UriValidator();
|
||||
|
||||
|
@ -45,10 +46,10 @@ public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
|||
public static final String KEY_ALLOW_FRAGMENT = "allowFragment";
|
||||
public static final String KEY_REQUIRE_VALID_URL = "requireValidUrl";
|
||||
|
||||
public static final Set<String> DEFAULT_ALLOWED_SCHEMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
public static final List<String> DEFAULT_ALLOWED_SCHEMES = Collections.unmodifiableList(Arrays.asList(
|
||||
"http",
|
||||
"https"
|
||||
)));
|
||||
));
|
||||
public static final String MESSAGE_INVALID_URI = "error-invalid-uri";
|
||||
public static final String MESSAGE_INVALID_SCHEME = "error-invalid-uri-scheme";
|
||||
public static final String MESSAGE_INVALID_FRAGMENT = "error-invalid-uri-fragment";
|
||||
|
@ -59,16 +60,50 @@ public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
|||
|
||||
public static final String ID = "uri";
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(KEY_ALLOWED_SCHEMES);
|
||||
property.setLabel("Allowed schemes");
|
||||
property.setHelpText("Allowed URL schemes. Defaults to 'http' and 'https' as only allowed schemes");
|
||||
property.setType(ProviderConfigProperty.MULTIVALUED_STRING_TYPE);
|
||||
property.setDefaultValue(DEFAULT_ALLOWED_SCHEMES);
|
||||
configProperties.add(property);
|
||||
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(KEY_ALLOW_FRAGMENT);
|
||||
property.setLabel("Allow fragment");
|
||||
property.setHelpText("Specify if allow URL with the URI fragment. It is true by default");
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue(DEFAULT_ALLOW_FRAGMENT);
|
||||
configProperties.add(property);
|
||||
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(KEY_REQUIRE_VALID_URL);
|
||||
property.setLabel("Require Valid URL");
|
||||
property.setHelpText("Checks if the specified URL is valid URL. It is true by default");
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue(DEFAULT_REQUIRE_VALID_URL);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationContext validate(Object input, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||
protected boolean skipValidation(Object value, ValidatorConfig config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doValidate(Object input, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||
|
||||
if(input == null || (input instanceof String && ((String) input).isEmpty())) {
|
||||
return context;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -77,7 +112,7 @@ public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
|||
if (uri == null) {
|
||||
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_URI, input));
|
||||
} else {
|
||||
Set<String> allowedSchemes = config.getStringSetOrDefault(KEY_ALLOWED_SCHEMES, DEFAULT_ALLOWED_SCHEMES);
|
||||
Set<String> allowedSchemes = new HashSet<>(config.getStringListOrDefault(KEY_ALLOWED_SCHEMES, DEFAULT_ALLOWED_SCHEMES));
|
||||
boolean allowFragment = config.getBooleanOrDefault(KEY_ALLOW_FRAGMENT, DEFAULT_ALLOW_FRAGMENT);
|
||||
boolean requireValidUrl = config.getBooleanOrDefault(KEY_REQUIRE_VALID_URL, DEFAULT_REQUIRE_VALID_URL);
|
||||
|
||||
|
@ -86,8 +121,6 @@ public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
|||
} catch (MalformedURLException | IllegalArgumentException | URISyntaxException e) {
|
||||
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_URI, input, e.getMessage()));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private URI toUri(Object input) throws URISyntaxException {
|
||||
|
@ -144,6 +177,6 @@ public class UriValidator implements SimpleValidator, ConfiguredProvider {
|
|||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
return configProperties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import static org.junit.Assert.assertTrue;
|
|||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.parseSystemDefaultConfig;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.ArrayList;
|
||||
|
@ -85,6 +84,7 @@ import org.keycloak.userprofile.validator.UsernameIDNHomographValidator;
|
|||
import org.keycloak.validate.ValidationError;
|
||||
import org.keycloak.validate.validators.EmailValidator;
|
||||
import org.keycloak.validate.validators.LengthValidator;
|
||||
import org.keycloak.validate.validators.UriValidator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -1111,6 +1111,74 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ModelTest(realmName = "test")
|
||||
public void testUriValidator(KeycloakSession session) {
|
||||
UserProfileProvider provider = getUserProfileProvider(session);
|
||||
UPConfig config = UPConfigUtils.parseSystemDefaultConfig();
|
||||
|
||||
UPAttribute attribute = new UPAttribute("picture-url");
|
||||
attribute.addValidation(UriValidator.ID, new HashMap<>());
|
||||
config.addOrReplaceAttribute(attribute);
|
||||
provider.setConfiguration(config);
|
||||
|
||||
try {
|
||||
// Should fail with the default error message
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put(UserModel.USERNAME, "abc");
|
||||
attributes.put("picture-url", "some-invalid-url");
|
||||
|
||||
UserProfile profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
try {
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.hasError(UriValidator.MESSAGE_INVALID_URI));
|
||||
}
|
||||
|
||||
// URL with fragment should be OK by default
|
||||
attributes.put("picture-url", "https://somehost/somepath?param=foo#frg=bar");
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
profile.validate();
|
||||
|
||||
// Not allow fragment
|
||||
attribute.addValidation(UriValidator.ID, Map.of(UriValidator.KEY_ALLOW_FRAGMENT, false));
|
||||
config.addOrReplaceAttribute(attribute);
|
||||
provider.setConfiguration(config);
|
||||
|
||||
attributes.put("picture-url", "https://somehost/somepath?param=foo#frg=bar");
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
try {
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.hasError(UriValidator.MESSAGE_INVALID_FRAGMENT));
|
||||
}
|
||||
|
||||
// not allow file URL by default
|
||||
attributes.put("picture-url", "file:///somefile.txt");
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
try {
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.hasError(UriValidator.MESSAGE_INVALID_SCHEME));
|
||||
}
|
||||
|
||||
// Allow file scheme and check it works
|
||||
attribute.addValidation(UriValidator.ID, Map.of(UriValidator.KEY_ALLOWED_SCHEMES, Arrays.asList("https", "http", "file")));
|
||||
config.addOrReplaceAttribute(attribute);
|
||||
provider.setConfiguration(config);
|
||||
|
||||
attributes.put("picture-url", "file:///somefile.txt");
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
profile.validate();
|
||||
} finally {
|
||||
config.removeAttribute("picture-url");
|
||||
provider.setConfiguration(config);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomAttributeRequired() {
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testCustomAttributeRequired);
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.keycloak.testsuite.validation;
|
|||
import static org.keycloak.validate.ValidatorConfig.configFromMap;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -500,10 +501,18 @@ public class BuiltinValidatorsTest extends AbstractKeycloakTest {
|
|||
Assert.assertTrue(validator.validate("http://localhost:3000/", "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate("https://localhost:3000/", "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate("https://localhost:3000/#someFragment", "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate(new URL("https://localhost:3000/#someFragment"), "baseUrl").isValid());
|
||||
|
||||
// Collections
|
||||
Assert.assertTrue(validator.validate(Arrays.asList("https://localhost:3000/#someFragment", "https://localhost:3000"), "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate(Arrays.asList("https://localhost:3000/#someFragment"), "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate(Arrays.asList(new URL("https://localhost:3000/#someFragment")), "baseUrl").isValid());
|
||||
Assert.assertTrue(validator.validate(Arrays.asList(""), "baseUrl").isValid());
|
||||
|
||||
Assert.assertFalse(validator.validate(" ", "baseUrl").isValid());
|
||||
Assert.assertFalse(validator.validate("file:///somefile.txt", "baseUrl").isValid());
|
||||
Assert.assertFalse(validator.validate("invalidUrl++@23", "invalidUri").isValid());
|
||||
Assert.assertFalse(validator.validate(Arrays.asList("https://localhost:3000/#someFragment", "file:///somefile.txt"), "baseUrl").isValid());
|
||||
|
||||
ValidatorConfig config = configFromMap(ImmutableMap.of(UriValidator.KEY_ALLOW_FRAGMENT, false));
|
||||
Assert.assertFalse(validator.validate("https://localhost:3000/#someFragment", "baseUrl", config).isValid());
|
||||
|
|
Loading…
Reference in a new issue