keycloak-uncached

Details

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