diff --git a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java index 954da121e7..0ce32eb71b 100644 --- a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java +++ b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java @@ -35,6 +35,9 @@ import org.keycloak.services.util.ResolveRelative; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; + +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -235,13 +238,21 @@ public class DefaultClientValidationProvider implements ClientValidationProvider } } + private void checkUri(FieldMessages field, String url, ValidationContext context, boolean checkValidUrl, boolean checkFragment) { if (url == null || url.isEmpty()) { return; } try { - URI uri = new URI(url); + String urlToCheck=url; + if(field==FieldMessages.BACKCHANNEL_LOGOUT_URL){ + if(checkCurlyBracketsBalanced(url)) + // This allow user to set parametrized backchannel logout url in this format : http://{example}/{example2} + urlToCheck=url.replace("{","%7B").replace("}","%7D"); + else throw new MalformedURLException(); + } + URI uri = new URI(urlToCheck); boolean valid = true; if (uri.getScheme() != null && (uri.getScheme().equals("data") || uri.getScheme().equals("javascript"))) { @@ -262,11 +273,42 @@ public class DefaultClientValidationProvider implements ClientValidationProvider uri.toURL(); // throws an exception } } + catch (MalformedURLException | IllegalArgumentException | URISyntaxException e) { context.addError(field.getFieldId(), field.getInvalid(), field.getInvalidKey()); } } + /** + * Check if url has curly brackets in correct position ('{' before '}') + * @param url to check + * @return true if curly brackets are balanced, else false + */ + public static boolean checkCurlyBracketsBalanced(String url) + { + Deque stack + = new ArrayDeque<>(); + + for(char singleLetter:url.toCharArray()){ + if (singleLetter == '{') + { + // Push the element in the stack + stack.push(singleLetter); + continue; + } + if(stack.isEmpty() && (singleLetter=='}')) return false; + char check; + if(singleLetter=='}'){ + check=stack.pop(); + if(check!='{') return false; + } + + } + + + return stack.isEmpty(); + } + private void checkUriLogo(FieldMessages field, String url, ValidationContext context) { if (url == null || url.isEmpty()) { return; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/DefaultClientValidationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/DefaultClientValidationTest.java new file mode 100644 index 0000000000..5e0b7e39ae --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/DefaultClientValidationTest.java @@ -0,0 +1,26 @@ +package org.keycloak.testsuite.client; + +import org.junit.Test; +import org.keycloak.validation.DefaultClientValidationProvider; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class DefaultClientValidationTest { + @Test + public void that_checkCurlyBracketsBalanced_worksCorrectly() { + String urlWithCurlyBrackets1="http://{test}/prova123"; + String urlWithCurlyBrackets2="http://{test}/{prova123}"; + String urlWithCurlyBrackets3="http://{{test}/{prova123}}"; + assertTrue(DefaultClientValidationProvider.checkCurlyBracketsBalanced(urlWithCurlyBrackets1)); + assertTrue(DefaultClientValidationProvider.checkCurlyBracketsBalanced(urlWithCurlyBrackets2)); + assertTrue(DefaultClientValidationProvider.checkCurlyBracketsBalanced(urlWithCurlyBrackets3)); + } + @Test + public void that_checkCurlyBracketsBalanced_notWorksCorrectly() { + String urlWithImproperlyCurlyBrackets="http://}test}/prova123"; + String urlWithImproperlyCurlyBrackets1="http://{test}/prova123}"; + assertFalse(DefaultClientValidationProvider.checkCurlyBracketsBalanced(urlWithImproperlyCurlyBrackets)); + assertFalse(DefaultClientValidationProvider.checkCurlyBracketsBalanced(urlWithImproperlyCurlyBrackets1)); + } +}