| | |
| | | if (validRedirect.startsWith("/")) { |
| | | validRedirect = relativeToAbsoluteURI(session, rootUrl, validRedirect); |
| | | logger.debugv("replacing relative valid redirect with: {0}", validRedirect); |
| | | resolveValidRedirects.add(validRedirect); |
| | | } else { |
| | | resolveValidRedirects.add(validRedirect); |
| | | } |
| | | resolveValidRedirects.add(lowerCaseHostname(validRedirect)); |
| | | } |
| | | return resolveValidRedirects; |
| | | } |
| | |
| | | // Make the validations against fully decoded and normalized redirect-url. This also allows wildcards (case when client configured "Valid redirect-urls" contain wildcards) |
| | | String decodedRedirectUri = decodeRedirectUri(redirectUri); |
| | | URI decodedRedirect = toUri(decodedRedirectUri); |
| | | decodedRedirectUri = getNormalizedRedirectUri(decodedRedirect, true); |
| | | decodedRedirectUri = getNormalizedRedirectUri(decodedRedirect); |
| | | if (decodedRedirectUri == null) return null; |
| | | |
| | | String r = decodedRedirectUri; |
| | |
| | | } |
| | | |
| | | // Return the original redirectUri, which can be partially encoded - for example http://localhost:8280/foo/bar%20bar%2092%2F72/3 . Just make sure it is normalized |
| | | redirectUri = getNormalizedRedirectUri(originalRedirect, false); |
| | | redirectUri = getNormalizedRedirectUri(originalRedirect); |
| | | |
| | | // We try to check validity also for original (encoded) redirectUrl, but just in case it exactly matches some "Valid Redirect URL" specified for client (not wildcards allowed) |
| | | if (valid == null) { |
| | | valid = matchesRedirects(resolveValidRedirects, redirectUri, false); |
| | | } |
| | | |
| | | if (valid != null && !originalRedirect.isAbsolute()) { |
| | | // return absolute if the original URI is relative |
| | | if (!redirectUri.startsWith("/")) { |
| | | redirectUri = "/" + redirectUri; |
| | | } |
| | | if (valid != null && redirectUri.startsWith("/")) { |
| | | redirectUri = relativeToAbsoluteURI(session, rootUrl, redirectUri); |
| | | } |
| | | |
| | | String scheme = decodedRedirect.getScheme(); |
| | | if (valid != null && scheme != null) { |
| | | // check the scheme is valid, it should be http(s) or explicitly allowed by the validation |
| | | if (!valid.startsWith(scheme.toLowerCase() + ":") && !"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { |
| | | if (!valid.startsWith(scheme + ":") && !"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { |
| | | logger.debugf("Invalid URI because scheme is not allowed: %s", redirectUri); |
| | | valid = null; |
| | | } |
| | |
| | | private static URI toUri(String redirectUri) { |
| | | URI uri = null; |
| | | if (redirectUri != null) { |
| | | try { |
| | | try { |
| | | uri = URI.create(redirectUri); |
| | | } catch (IllegalArgumentException cause) { |
| | | logger.debug("Invalid redirect uri", cause); |
| | |
| | | return uri; |
| | | } |
| | | |
| | | private static String getNormalizedRedirectUri(URI uri, boolean lower) { |
| | | private static String getNormalizedRedirectUri(URI uri) { |
| | | String redirectUri = null; |
| | | if (uri != null) { |
| | | redirectUri = uri.normalize().toString(); |
| | | if (lower) { |
| | | redirectUri = lowerCaseHostname(redirectUri); |
| | | } |
| | | redirectUri = lowerCaseHostname(redirectUri); |
| | | } |
| | | return redirectUri; |
| | | } |
| | |
| | | KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri, false).preserveDefaultPort(); |
| | | String origQuery = uriBuilder.getQuery(); |
| | | String origFragment = uriBuilder.getFragment(); |
| | | String origUserInfo = uriBuilder.getUserInfo(); |
| | | String encodedRedirectUri = uriBuilder |
| | | .replaceQuery(null) |
| | | .fragment(null) |
| | | .userInfo(null) |
| | | .buildAsString(); |
| | | String decodedRedirectUri = null; |
| | | |
| | |
| | | return KeycloakUriBuilder.fromUri(decodedRedirectUri, false).preserveDefaultPort() |
| | | .replaceQuery(origQuery) |
| | | .fragment(origFragment) |
| | | .userInfo(origUserInfo) |
| | | .buildAsString(); |
| | | } else { |
| | | // Next attempt |
| | |
| | | } |
| | | |
| | | private static String lowerCaseHostname(String redirectUri) { |
| | | // lower case host and scheme which are case-insensitive by spec |
| | | KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri, false).preserveDefaultPort(); |
| | | if (uriBuilder.getScheme() != null) { |
| | | uriBuilder.scheme(uriBuilder.getScheme().toLowerCase()); |
| | | int n = redirectUri.indexOf('/', 7); |
| | | if (n == -1) { |
| | | return redirectUri.toLowerCase(); |
| | | } else { |
| | | return redirectUri.substring(0, n).toLowerCase() + redirectUri.substring(n); |
| | | } |
| | | if (uriBuilder.getHost() != null) { |
| | | uriBuilder.host(uriBuilder.getHost().toLowerCase()); |
| | | } |
| | | return uriBuilder.buildAsString(); |
| | | } |
| | | |
| | | private static String relativeToAbsoluteURI(KeycloakSession session, String rootUrl, String relative) { |