Backchannel logout url with curly brackets

closes #30023

Signed-off-by: raff897 <85362193+raff897@users.noreply.github.com>
This commit is contained in:
raff897 2024-05-31 23:38:54 +02:00 committed by Marek Posolda
parent 0cd0d03c08
commit 6d6131cade
2 changed files with 69 additions and 1 deletions

View file

@ -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<ClientModel> 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<Character> 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<ClientModel> context) {
if (url == null || url.isEmpty()) {
return;

View file

@ -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));
}
}