RedirectUtils needs to use KeycloakUriBuilder with no parameter parsing

Closes https://github.com/keycloak/keycloak/issues/22424
This commit is contained in:
rmartinc 2023-08-16 11:18:51 +02:00 committed by Marek Posolda
parent 568590fcdd
commit b67ede2a30
5 changed files with 98 additions and 33 deletions

View file

@ -168,6 +168,15 @@ public class Encode
return encodeValue(value, queryStringEncoding); return encodeValue(value, queryStringEncoding);
} }
/**
* Keep encoded values "%..." but not the template parameters.
* @param value
* @return
*/
public static String encodeQueryStringNotTemplateParameters(String value) {
return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false));
}
/** /**
* Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact. * Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact.
*/ */
@ -192,6 +201,16 @@ public class Encode
return encodeValue(value, queryStringEncoding); return encodeValue(value, queryStringEncoding);
} }
/**
* Keep encoded values "%..." but not the template parameters.
* @param value
* @return
*/
public static String encodeFragmentNotTemplateParameters(String value)
{
return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false));
}
/** /**
* Keep encoded values "%..." and template parameters intact. * Keep encoded values "%..." and template parameters intact.
*/ */

View file

@ -51,7 +51,12 @@ public class KeycloakUriBuilder {
} }
public static KeycloakUriBuilder fromUri(String uriTemplate) { public static KeycloakUriBuilder fromUri(String uriTemplate) {
return new KeycloakUriBuilder().uri(uriTemplate); // default fromUri manages template params {}
return new KeycloakUriBuilder().uri(uriTemplate, true);
}
public static KeycloakUriBuilder fromUri(String uri, boolean template) {
return new KeycloakUriBuilder().uri(uri, template);
} }
public static KeycloakUriBuilder fromPath(String path) throws IllegalArgumentException { public static KeycloakUriBuilder fromPath(String path) throws IllegalArgumentException {
@ -131,28 +136,10 @@ public class KeycloakUriBuilder {
* @return * @return
*/ */
public KeycloakUriBuilder uriTemplate(String uriTemplate) { public KeycloakUriBuilder uriTemplate(String uriTemplate) {
if (uriTemplate == null) throw new IllegalArgumentException("uriTemplate parameter is null"); return uri(uriTemplate, true);
Matcher opaque = opaqueUri.matcher(uriTemplate);
if (opaque.matches()) {
this.authority = null;
this.host = null;
this.port = -1;
this.userInfo = null;
this.query = null;
this.scheme = opaque.group(1);
this.ssp = opaque.group(2);
return this;
} else {
Matcher match = hierarchicalUri.matcher(uriTemplate);
if (match.matches()) {
ssp = null;
return parseHierarchicalUri(uriTemplate, match);
}
}
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate);
} }
protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher match) { protected KeycloakUriBuilder parseHierarchicalUri(String uri, Matcher match, boolean template) {
boolean scheme = match.group(2) != null; boolean scheme = match.group(2) != null;
if (scheme) this.scheme = match.group(2); if (scheme) this.scheme = match.group(2);
String authority = match.group(4); String authority = match.group(4);
@ -171,7 +158,7 @@ public class KeycloakUriBuilder {
try { try {
this.port = Integer.parseInt(hostPortMatch.group(2)); this.port = Integer.parseInt(hostPortMatch.group(2));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate, e); throw new IllegalArgumentException("Illegal uri template: " + uri, e);
} }
} else { } else {
this.host = host; this.host = host;
@ -180,16 +167,39 @@ public class KeycloakUriBuilder {
if (match.group(5) != null) { if (match.group(5) != null) {
String group = match.group(5); String group = match.group(5);
if (!scheme && !"".equals(group) && !group.startsWith("/") && group.indexOf(':') > -1) if (!scheme && !"".equals(group) && !group.startsWith("/") && group.indexOf(':') > -1)
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate); throw new IllegalArgumentException("Illegal uri template: " + uri);
if (!"".equals(group)) replacePath(group); if (!"".equals(group)) replacePath(group, template);
} }
if (match.group(7) != null) replaceQuery(match.group(7)); if (match.group(7) != null) replaceQuery(match.group(7), template);
if (match.group(9) != null) fragment(match.group(9)); if (match.group(9) != null) fragment(match.group(9), template);
return this; return this;
} }
public KeycloakUriBuilder uri(String uriTemplate) throws IllegalArgumentException { public KeycloakUriBuilder uri(String uri) throws IllegalArgumentException {
return uriTemplate(uriTemplate); // default uri manages template params {}
return uri(uri, true);
}
public KeycloakUriBuilder uri(String uri, boolean template) throws IllegalArgumentException {
if (uri == null) throw new IllegalArgumentException("uri parameter is null");
Matcher opaque = opaqueUri.matcher(uri);
if (opaque.matches()) {
this.authority = null;
this.host = null;
this.port = -1;
this.userInfo = null;
this.query = null;
this.scheme = opaque.group(1);
this.ssp = opaque.group(2);
return this;
} else {
Matcher match = hierarchicalUri.matcher(uri);
if (match.matches()) {
ssp = null;
return parseHierarchicalUri(uri, match, template);
}
}
throw new IllegalArgumentException("Illegal uri template: " + uri);
} }
public KeycloakUriBuilder uri(URI uri) throws IllegalArgumentException { public KeycloakUriBuilder uri(URI uri) throws IllegalArgumentException {
@ -356,20 +366,30 @@ public class KeycloakUriBuilder {
} }
public KeycloakUriBuilder replaceQuery(String query) throws IllegalArgumentException { public KeycloakUriBuilder replaceQuery(String query) throws IllegalArgumentException {
// default replaceQuery manages template params {}
return replaceQuery(query, true);
}
public KeycloakUriBuilder replaceQuery(String query, boolean template) throws IllegalArgumentException {
if (query == null || query.length() == 0) { if (query == null || query.length() == 0) {
this.query = null; this.query = null;
return this; return this;
} }
this.query = Encode.encodeQueryString(query); this.query = template? Encode.encodeQueryString(query) : Encode.encodeQueryStringNotTemplateParameters(query);
return this; return this;
} }
public KeycloakUriBuilder fragment(String fragment) throws IllegalArgumentException { public KeycloakUriBuilder fragment(String fragment) throws IllegalArgumentException {
// default fragment manages template params {}
return fragment(fragment, true);
}
public KeycloakUriBuilder fragment(String fragment, boolean template) throws IllegalArgumentException {
if (fragment == null) { if (fragment == null) {
this.fragment = null; this.fragment = null;
return this; return this;
} }
this.fragment = Encode.encodeFragment(fragment); this.fragment = template? Encode.encodeFragment(fragment) : Encode.encodeFragmentNotTemplateParameters(fragment);
return this; return this;
} }
@ -709,11 +729,16 @@ public class KeycloakUriBuilder {
} }
public KeycloakUriBuilder replacePath(String path) { public KeycloakUriBuilder replacePath(String path) {
// default replacePath manages template expression {}
return replacePath(path, true);
}
public KeycloakUriBuilder replacePath(String path, boolean template) {
if (path == null) { if (path == null) {
this.path = null; this.path = null;
return this; return this;
} }
this.path = Encode.encodePath(path); this.path = template? Encode.encodePath(path) : Encode.encodePathSaveEncodings(path);
return this; return this;
} }

View file

@ -72,4 +72,12 @@ public class KeycloakUriBuilderTest {
Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").buildAsString()); Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").buildAsString());
Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").preserveDefaultPort().buildAsString()); Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").preserveDefaultPort().buildAsString());
} }
@Test
public void testTemplateAndNotTemplate() {
Assert.assertEquals("https://localhost:8443/path?key=query#fragment", KeycloakUriBuilder.fromUri(
"https://localhost:8443/{path}?key={query}#{fragment}").buildAsString("path", "query", "fragment"));
Assert.assertEquals("https://localhost:8443/%7Bpath%7D?key=%7Bquery%7D#%7Bfragment%7D", KeycloakUriBuilder.fromUri(
"https://localhost:8443/{path}?key={query}#{fragment}", false).buildAsString());
}
} }

View file

@ -196,7 +196,7 @@ public class RedirectUtils {
int MAX_DECODING_COUNT = 5; // Max count of attempts for decoding URL (in case it was encoded multiple times) int MAX_DECODING_COUNT = 5; // Max count of attempts for decoding URL (in case it was encoded multiple times)
try { try {
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri).preserveDefaultPort(); KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri, false).preserveDefaultPort();
String origQuery = uriBuilder.getQuery(); String origQuery = uriBuilder.getQuery();
String origFragment = uriBuilder.getFragment(); String origFragment = uriBuilder.getFragment();
String encodedRedirectUri = uriBuilder String encodedRedirectUri = uriBuilder
@ -209,7 +209,7 @@ public class RedirectUtils {
decodedRedirectUri = Encode.decode(encodedRedirectUri); decodedRedirectUri = Encode.decode(encodedRedirectUri);
if (decodedRedirectUri.equals(encodedRedirectUri)) { if (decodedRedirectUri.equals(encodedRedirectUri)) {
// URL is decoded. We can return it (after attach original query and fragment) // URL is decoded. We can return it (after attach original query and fragment)
return KeycloakUriBuilder.fromUri(decodedRedirectUri).preserveDefaultPort() return KeycloakUriBuilder.fromUri(decodedRedirectUri, false).preserveDefaultPort()
.replaceQuery(origQuery) .replaceQuery(origQuery)
.fragment(origFragment) .fragment(origFragment)
.buildAsString(); .buildAsString();

View file

@ -114,4 +114,17 @@ public class RedirectUtilsTest {
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "custom3:/test", set, false)); Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "custom3:/test", set, false));
} }
@Test
public void testverifyRedirectUriWithCurlyBrackets() {
Set<String> set = Stream.of(
"https://keycloak.org/%7B123%7D",
"https://keycloak.org/parent/*"
).collect(Collectors.toSet());
Assert.assertEquals("https://keycloak.org/%7B123%7D", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/%7B123%7D", set, false));
Assert.assertEquals("https://keycloak.org/parent/%7B123%7D", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/parent/%7B123%7D", set, false));
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/%7Babc%7D", set, false));
}
} }