| | |
| | | import java.net.URI; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.TreeSet; |
| | | import java.util.regex.Pattern; |
| | |
| | | |
| | | // any access to parent folder /../ is unsafe with or without encoding |
| | | private final static Pattern UNSAFE_PATH_PATTERN = Pattern.compile( |
| | | "(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}(/|%2[fF]|%5[cC]|\\\\)|(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}$"); |
| | | "(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}(/|%2[fF]|%5[cC]|\\\\|;)|(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}$"); |
| | | |
| | | private static boolean areWildcardsAllowed(URI redirectUri) { |
| | | // wildcars are only allowed if no user-info and no unsafe pattern in path |
| | |
| | | if ("*".equals(validRedirect)) { |
| | | // the valid redirect * is a full wildcard for http(s) even if the redirect URI does not allow wildcards |
| | | return validRedirect; |
| | | } else if (validRedirect.endsWith("*") && !validRedirect.contains("?") && allowWildcards) { |
| | | // strip off the query or fragment components - we don't check them when wildcards are effective |
| | | int idx = redirect.indexOf('?'); |
| | | if (idx == -1) { |
| | | idx = redirect.indexOf('#'); |
| | | } else { |
| | | String validRedirectWildcard = allowWildcards ? checkValidRedirectWildcard(validRedirect) : null; |
| | | if (validRedirectWildcard != null) { |
| | | // strip off the query or fragment components - we don't check them when wildcards are effective |
| | | int idx = redirect.indexOf('?'); |
| | | if (idx == -1) { |
| | | idx = redirect.indexOf('#'); |
| | | } |
| | | String r = idx == -1 ? redirect : redirect.substring(0, idx); |
| | | // strip off * |
| | | int length = validRedirectWildcard.length() - 1; |
| | | validRedirectWildcard = validRedirectWildcard.substring(0, length); |
| | | if (r.startsWith(validRedirectWildcard)) { |
| | | return validRedirectWildcard; |
| | | } |
| | | // strip off trailing '/' |
| | | if (length - 1 > 0 && validRedirectWildcard.charAt(length - 1) == '/') { |
| | | length--; |
| | | } |
| | | validRedirectWildcard = validRedirectWildcard.substring(0, length); |
| | | if (validRedirectWildcard.equals(r)) { |
| | | return validRedirectWildcard; |
| | | } |
| | | } else if (validRedirect.equals(redirect)) { |
| | | return validRedirect; |
| | | } |
| | | String r = idx == -1 ? redirect : redirect.substring(0, idx); |
| | | // strip off * |
| | | int length = validRedirect.length() - 1; |
| | | validRedirect = validRedirect.substring(0, length); |
| | | if (r.startsWith(validRedirect)) return validRedirect; |
| | | // strip off trailing '/' |
| | | if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--; |
| | | validRedirect = validRedirect.substring(0, length); |
| | | if (validRedirect.equals(r)) return validRedirect; |
| | | } else if (validRedirect.equals(redirect)) return validRedirect; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String checkValidRedirectWildcard(String validRedirect) { |
| | | if (!validRedirect.endsWith("*") || validRedirect.contains("?") || validRedirect.contains("#")) { |
| | | return null; // no wildcard as before |
| | | } |
| | | KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(validRedirect, false); |
| | | if (uriBuilder.getPath() != null) { |
| | | return validRedirect; // wildcard valid on path |
| | | } |
| | | if (uriBuilder.getAuthority() != null) { |
| | | if (uriBuilder.getAuthority().equals("*") || uriBuilder.getAuthority().endsWith(":*")) { |
| | | return validRedirect; // on authority just full wildcard or on port |
| | | } else { |
| | | // treat the wildcard after the authority |
| | | validRedirect = validRedirect.substring(0, validRedirect.length() - 1); |
| | | validRedirect = validRedirect + "/*"; |
| | | return validRedirect; |
| | | } |
| | | } |
| | | if (uriBuilder.getSsp() != null) { |
| | | return validRedirect; // wildcard valid on SSP |
| | | } |
| | | return null; |
| | | } |
| | |
| | | } |
| | | return redirectUri; |
| | | } |
| | | |
| | | public static Set<String> resolveUrlsWithRedirects(KeycloakSession session, List<String> origUrls, |
| | | String rootUrl, List<String> redirectUris, boolean returnAsOrigins) { |
| | | |
| | | Set<String> refactoredUrls = (origUrls != null) ? new HashSet<>(origUrls) : new HashSet<>(); |
| | | if (refactoredUrls.contains(Constants.INCLUDE_REDIRECTS)) { |
| | | refactoredUrls.remove(Constants.INCLUDE_REDIRECTS); |
| | | |
| | | Set<String> redirectsToProcess = (redirectUris != null) ? new HashSet<>(redirectUris) : Collections.emptySet(); |
| | | for (String redirectUri : resolveValidRedirects(session, rootUrl, redirectsToProcess)) { |
| | | if (isValidScheme(redirectUri)) { |
| | | if (returnAsOrigins) { |
| | | refactoredUrls.add(UriUtils.getOrigin(redirectUri)); |
| | | } else { |
| | | refactoredUrls.add(redirectUri); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return refactoredUrls; |
| | | } |
| | | |
| | | private static boolean isValidScheme(String url) { |
| | | return url != null && (url.startsWith("http://") || url.startsWith("https://")); |
| | | } |
| | | } |