diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java
index 8865892..d90a4fd 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java
@@ -17,93 +17,206 @@
*/
package org.keycloak.adapters.authorization;
-import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
-
+import java.util.Arrays;
import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.representation.ResourceRepresentation;
+import org.keycloak.authorization.client.resource.ProtectedResource;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class PathMatcher {
- private static final String ANY_RESOURCE_PATTERN = "/*";
+ private static final char WILDCARD = '*';
+ private final AuthzClient authzClient;
+ // TODO: make this configurable
+ private PathCache cache = new PathCache(100, 30000);
- PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
- PathConfig pathConfig = paths.get(requestedUri);
+ public PathMatcher(AuthzClient authzClient) {
+ this.authzClient = authzClient;
+ }
+
+ public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
+ PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
if (pathConfig != null) {
return pathConfig;
}
- PathConfig actualConfig = null;
+ PathConfig matchingAnyPath = null;
+ PathConfig matchingAnySuffixPath = null;
+ PathConfig matchingPath = null;
for (PathConfig entry : paths.values()) {
- String protectedUri = entry.getPath();
- String selectedUri = null;
+ String expectedUri = entry.getPath();
+ String matchingUri = null;
- if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
- selectedUri = protectedUri;
+ if (exactMatch(expectedUri, targetUri, expectedUri)) {
+ matchingUri = expectedUri;
}
- int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
-
- if (suffixIndex != -1) {
- String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
+ if (isTemplate(expectedUri)) {
+ String templateUri = buildUriFromTemplate(expectedUri, targetUri);
- if (requestedUri.endsWith(protectedSuffix)) {
- selectedUri = protectedUri;
+ if (templateUri != null) {
+ if (exactMatch(expectedUri, targetUri, templateUri)) {
+ matchingUri = templateUri;
+ entry = resolvePathConfig(entry, targetUri);
+ }
}
}
- if (protectedUri.equals(requestedUri)) {
- selectedUri = protectedUri;
- }
+ if (matchingUri != null) {
+ StringBuilder path = new StringBuilder(expectedUri);
+ int patternIndex = path.indexOf("/" + WILDCARD);
+
+ if (patternIndex != -1) {
+ path.delete(patternIndex, path.length());
+ }
- if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
- String formattedPattern = removeWildCardsFromUri(protectedUri);
+ patternIndex = path.indexOf("{");
- if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
- selectedUri = protectedUri;
+ if (patternIndex != -1) {
+ path.delete(patternIndex, path.length());
}
- if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
- selectedUri = protectedUri;
+ String pathString = path.toString();
+
+ if ("".equals(pathString)) {
+ pathString = "/";
}
- }
- int startRegex = protectedUri.indexOf('{');
+ if (matchingUri.equals(targetUri)) {
+ cache.put(targetUri, entry);
+ return entry;
+ }
- if (startRegex != -1) {
- String prefix = protectedUri.substring(0, startRegex);
+ if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
+ matchingAnyPath = entry;
+ } else {
+ int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
+
+ if (suffixIndex != -1) {
+ String protectedSuffix = expectedUri.substring(suffixIndex + 1);
- if (requestedUri.startsWith(prefix)) {
- selectedUri = protectedUri;
+ if (targetUri.endsWith(protectedSuffix)) {
+ matchingAnySuffixPath = entry;
+ }
+ }
}
}
+ }
- if (selectedUri != null) {
- selectedUri = protectedUri;
- }
+ if (matchingAnySuffixPath != null) {
+ cache.put(targetUri, matchingAnySuffixPath);
+ return matchingAnySuffixPath;
+ }
- if (selectedUri != null) {
- if (actualConfig == null) {
- actualConfig = entry;
- } else {
- if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
- actualConfig = entry;
+ if (matchingAnyPath != null) {
+ cache.put(targetUri, matchingAnyPath);
+ }
+
+ return matchingAnyPath;
+ }
+
+ private boolean exactMatch(String expectedUri, String targetUri, String value) {
+ if (targetUri.equals(value)) {
+ return value.equals(targetUri);
+ }
+
+ if (endsWithWildcard(expectedUri)) {
+ return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
+ }
+
+ return false;
+ }
+
+ public String buildUriFromTemplate(String expectedUri, String targetUri) {
+ int patternStartIndex = expectedUri.indexOf("{");
+
+ if (patternStartIndex >= targetUri.length()) {
+ return null;
+ }
+
+ char[] expectedUriChars = expectedUri.toCharArray();
+ char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
+
+ if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
+ int matchingLastIndex = matchingUri.length;
+ matchingUri = Arrays.copyOf(matchingUri, targetUri.length()); // +1 so we can add a slash at the end
+ int targetPatternStartIndex = patternStartIndex;
+
+ while (patternStartIndex != -1) {
+ int parameterStartIndex = -1;
+
+ for (int i = targetPatternStartIndex; i < targetUri.length(); i++) {
+ char c = targetUri.charAt(i);
+
+ if (c != '/') {
+ if (parameterStartIndex == -1) {
+ parameterStartIndex = matchingLastIndex;
+ }
+ matchingUri[matchingLastIndex] = c;
+ matchingLastIndex++;
}
- if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
- actualConfig = entry;
+ if (c == '/' || ((i + 1 == targetUri.length()))) {
+ if (matchingUri[matchingLastIndex - 1] != '/' && matchingLastIndex < matchingUri.length) {
+ matchingUri[matchingLastIndex] = '/';
+ matchingLastIndex++;
+ }
+
+ targetPatternStartIndex = targetUri.indexOf('/', i) + 1;
+ break;
}
}
+
+ if ((patternStartIndex = expectedUri.indexOf('{', patternStartIndex + 1)) == -1) {
+ break;
+ }
+
+ if ((targetPatternStartIndex == 0 || targetPatternStartIndex == targetUri.length()) && parameterStartIndex != -1) {
+ return null;
+ }
}
+
+ return String.valueOf(matchingUri);
}
- return actualConfig;
+ return null;
+ }
+
+ public boolean endsWithWildcard(String expectedUri) {
+ return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
+ }
+
+ private boolean isTemplate(String uri) {
+ return uri.indexOf("{") != -1;
}
- private String removeWildCardsFromUri(String protectedUri) {
- return protectedUri.replaceAll("/[*]", "/");
+ private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
+ if (originalConfig.hasPattern()) {
+ ProtectedResource resource = this.authzClient.protection().resource();
+ Set<String> search = resource.findByFilter("uri=" + path);
+
+ if (!search.isEmpty()) {
+ // resource does exist on the server, cache it
+ ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
+ PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
+
+ config.setScopes(originalConfig.getScopes());
+ config.setMethods(originalConfig.getMethods());
+ config.setParentConfig(originalConfig);
+ config.setEnforcementMode(originalConfig.getEnforcementMode());
+
+ return config;
+ }
+ }
+
+ return originalConfig;
}
}