keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 106(+56 -50)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java 15(+6 -9)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java 142(+79 -63)
authz/client/src/main/java/org/keycloak/authorization/client/representation/ResourceRepresentation.java 218(+0 -218)
authz/client/src/main/java/org/keycloak/authorization/client/representation/ScopeRepresentation.java 98(+0 -98)
authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java 39(+32 -7)
core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java 54(+41 -13)
core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java 39(+34 -5)
services/src/main/java/org/keycloak/authorization/protection/resource/RegistrationResponse.java 50(+0 -50)
services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaResourceRepresentation.java 176(+0 -176)
services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaScopeRepresentation.java 98(+0 -98)
services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java 119(+31 -88)
services/src/main/java/org/keycloak/authorization/protection/resource/UmaResourceRepresentation.java 72(+72 -0)
services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/keycloak-lazy-load-path-authz-service.json 78(+78 -0)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 4(+2 -2)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json 10(+10 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-lazy-load-authz-service.json 15(+15 -0)
testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/src/main/webapp/WEB-INF/keycloak.json 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java 14(+10 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractBaseServletAuthzAdapterTest.java 211(+211 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPermissiveModeAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java 12(+1 -11)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleLazyLoadPathsAdapterTest.java 88(+88 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleNoLazyLoadPathsAdapterTest.java 42(+16 -26)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java 193(+8 -185)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java 10(+1 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzLazyLoadPathsAdapterTest.java 58(+58 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletPolicyEnforcerTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ResourceManagementWithAuthzClientTest.java 73(+61 -12)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleLazyLoadPathsAdapterTest.java 29(+29 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 96fbe5d..452583b 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -19,7 +19,6 @@ package org.keycloak.adapters.authorization;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
@@ -30,10 +29,12 @@ import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessToken.Authorization;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
import org.keycloak.representations.idm.authorization.Permission;
/**
@@ -42,31 +43,23 @@ import org.keycloak.representations.idm.authorization.Permission;
public abstract class AbstractPolicyEnforcer {
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
- private final PolicyEnforcerConfig enforcerConfig;
- private final PolicyEnforcer policyEnforcer;
+ private static final String HTTP_METHOD_DELETE = "DELETE";
- private Map<String, PathConfig> paths;
- private AuthzClient authzClient;
- private PathMatcher pathMatcher;
+ private final PolicyEnforcer policyEnforcer;
- public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
+ protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
- this.enforcerConfig = policyEnforcer.getEnforcerConfig();
- this.authzClient = policyEnforcer.getClient();
- this.pathMatcher = policyEnforcer.getPathMatcher();
- this.paths = policyEnforcer.getPaths();
}
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
- EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
+ EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
return createEmptyAuthorizationContext(true);
}
Request request = httpFacade.getRequest();
- String path = getPath(request);
- PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
+ PathConfig pathConfig = getPathConfig(request);
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
if (securityContext == null) {
@@ -79,16 +72,20 @@ public abstract class AbstractPolicyEnforcer {
AccessToken accessToken = securityContext.getToken();
if (accessToken != null) {
- LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
+ }
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
}
- LOGGER.debugf("Could not find a configuration for path [%s]", path);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
+ }
- if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
+ if (isDefaultAccessDeniedUri(request)) {
return createAuthorizationContext(accessToken, null);
}
@@ -111,10 +108,18 @@ public abstract class AbstractPolicyEnforcer {
}
}
- LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
+ if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
+ return createEmptyAuthorizationContext(true);
+ }
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
+ }
if (!challenge(pathConfig, methodConfig, httpFacade)) {
- LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
+ }
handleAccessDenied(httpFacade);
}
}
@@ -126,22 +131,21 @@ public abstract class AbstractPolicyEnforcer {
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest();
- PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
- if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
+ if (isDefaultAccessDeniedUri(request)) {
return true;
}
- AccessToken.Authorization authorization = accessToken.getAuthorization();
+ Authorization authorization = accessToken.getAuthorization();
if (authorization == null) {
return false;
}
- List<Permission> permissions = authorization.getPermissions();
boolean hasPermission = false;
+ List<Permission> grantedPermissions = authorization.getPermissions();
- for (Permission permission : permissions) {
+ for (Permission permission : grantedPermissions) {
if (permission.getResourceId() != null) {
if (isResourcePermission(actualPathConfig, permission)) {
hasPermission = true;
@@ -151,9 +155,11 @@ public abstract class AbstractPolicyEnforcer {
}
if (hasResourceScopePermission(methodConfig, permission)) {
- LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
- if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
- this.paths.remove(actualPathConfig);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
+ }
+ if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
+ policyEnforcer.getPaths().remove(actualPathConfig);
}
return true;
}
@@ -170,7 +176,9 @@ public abstract class AbstractPolicyEnforcer {
return true;
}
- LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, permissions);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
+ }
return false;
}
@@ -179,15 +187,21 @@ public abstract class AbstractPolicyEnforcer {
httpFacade.getResponse().sendError(403);
}
- private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
- String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
+ protected AuthzClient getAuthzClient() {
+ return policyEnforcer.getClient();
+ }
- if (accessDeniedPath != null) {
- if (request.getURI().contains(accessDeniedPath)) {
- return true;
- }
- }
- return false;
+ protected PolicyEnforcerConfig getEnforcerConfig() {
+ return policyEnforcer.getEnforcerConfig();
+ }
+
+ protected PolicyEnforcer getPolicyEnforcer() {
+ return policyEnforcer;
+ }
+
+ private boolean isDefaultAccessDeniedUri(Request request) {
+ String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
+ return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
}
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
@@ -215,20 +229,8 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes.isEmpty();
}
- protected AuthzClient getAuthzClient() {
- return this.authzClient;
- }
-
- protected PolicyEnforcerConfig getEnforcerConfig() {
- return enforcerConfig;
- }
-
- protected PolicyEnforcer getPolicyEnforcer() {
- return policyEnforcer;
- }
-
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
- return new ClientAuthorizationContext(authzClient) {
+ return new ClientAuthorizationContext(getAuthzClient()) {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@@ -279,7 +281,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
- return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
+ return new ClientAuthorizationContext(accessToken, pathConfig, policyEnforcer.getPaths(), getAuthzClient());
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
@@ -297,4 +299,8 @@ public abstract class AbstractPolicyEnforcer {
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
return permission.getResourceId().equals(actualPathConfig.getId());
}
+
+ private PathConfig getPathConfig(Request request) {
+ return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
+ }
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
index e699203..cf8815c 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java
@@ -24,6 +24,8 @@ import java.util.concurrent.locks.LockSupport;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
+ * A simple LRU cache implementation supporting expiration and maximum number of entries.
+ *
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PathCache {
@@ -43,15 +45,6 @@ public class PathCache {
* Creates a new instance.
*
* @param maxEntries the maximum number of entries to keep in the cache
- */
- public PathCache(int maxEntries) {
- this(maxEntries, -1);
- }
-
- /**
- * Creates a new instance.
- *
- * @param maxEntries the maximum number of entries to keep in the cache
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
*/
public PathCache(final int maxEntries, long maxAge) {
@@ -80,6 +73,10 @@ public class PathCache {
}
}
+ public boolean containsKey(String uri) {
+ return cache.containsKey(uri);
+ }
+
public PathConfig get(String uri) {
if (parkForReadAndCheckInterrupt()) {
return null;
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
index fe8aa1a..2d5f0cc 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
@@ -17,11 +17,11 @@
*/
package org.keycloak.adapters.authorization;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -34,13 +34,13 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
-import org.keycloak.authorization.client.representation.ResourceRepresentation;
-import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
+import org.keycloak.common.util.PathMatcher;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -69,8 +69,9 @@ public class PolicyEnforcer {
}
}
});
- this.pathMatcher = new PathMatcher(this.authzClient);
+
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
+ this.pathMatcher = createPathMatcher(authzClient);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:");
@@ -104,11 +105,11 @@ public class PolicyEnforcer {
return context;
}
- PolicyEnforcerConfig getEnforcerConfig() {
+ public PolicyEnforcerConfig getEnforcerConfig() {
return enforcerConfig;
}
- AuthzClient getClient() {
+ public AuthzClient getClient() {
return authzClient;
}
@@ -116,11 +117,11 @@ public class PolicyEnforcer {
return paths;
}
- void addPath(PathConfig pathConfig) {
- paths.put(pathConfig.getPath(), pathConfig);
+ public PathMatcher<PathConfig> getPathMatcher() {
+ return pathMatcher;
}
- KeycloakDeployment getDeployment() {
+ public KeycloakDeployment getDeployment() {
return deployment;
}
@@ -144,7 +145,7 @@ public class PolicyEnforcer {
}
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
- Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
+ Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
ResourceRepresentation resource;
@@ -168,36 +169,11 @@ public class PolicyEnforcer {
}
if (resource == null) {
- if (enforcerConfig.isCreateResources()) {
- LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
- ResourceRepresentation representation = new ResourceRepresentation();
-
- representation.setName(resourceName);
- representation.setType(pathConfig.getType());
- representation.setUri(path);
-
- HashSet<ScopeRepresentation> scopes = new HashSet<>();
-
- for (String scopeName : pathConfig.getScopes()) {
- ScopeRepresentation scope = new ScopeRepresentation();
-
- scope.setName(scopeName);
-
- scopes.add(scope);
- }
-
- representation.setScopes(scopes);
-
- ResourceRepresentation registrationResponse = protectedResource.create(representation);
-
- pathConfig.setId(registrationResponse.getId());
- } else {
- throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
- }
- } else {
- pathConfig.setId(resource.getId());
+ throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
}
+ pathConfig.setId(resource.getId());
+
PathConfig existingPath = null;
for (PathConfig current : paths.values()) {
@@ -222,45 +198,85 @@ public class PolicyEnforcer {
LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
- for (String id : protectedResource.findAll()) {
- ResourceRepresentation resourceDescription = protectedResource.findById(id);
+ if (!enforcerConfig.getLazyLoadPaths()) {
+ for (String id : protectedResource.findAll()) {
+ ResourceRepresentation resourceDescription = protectedResource.findById(id);
- if (resourceDescription.getUri() != null) {
- PathConfig pathConfig = createPathConfig(resourceDescription);
- paths.put(pathConfig.getPath(), pathConfig);
+ if (resourceDescription.getUri() != null) {
+ PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
+ paths.put(pathConfig.getPath(), pathConfig);
+ }
}
}
return paths;
}
- static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
- PathConfig pathConfig = new PathConfig();
+ private PathMatcher<PathConfig> createPathMatcher(final AuthzClient authzClient) {
+ final PathCache pathCache = new PathCache(100, 30000);
- pathConfig.setId(resourceDescription.getId());
- pathConfig.setName(resourceDescription.getName());
+ return new PathMatcher<PathConfig>() {
+ @Override
+ public PathConfig matches(String targetUri) {
+ PathConfig pathConfig = pathCache.get(targetUri);
- String uri = resourceDescription.getUri();
+ if (pathCache.containsKey(targetUri) || pathConfig != null) {
+ return pathConfig;
+ }
- if (uri == null || "".equals(uri.trim())) {
- throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
- }
+ pathConfig = super.matches(targetUri);
- pathConfig.setPath(uri);
+ if (enforcerConfig.getLazyLoadPaths() && (pathConfig == null || pathConfig.getPath().contains("*"))) {
+ try {
+ List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
- List<String> scopeNames = new ArrayList<>();
+ if (!matchingResources.isEmpty()) {
+ pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
+ paths.put(pathConfig.getPath(), pathConfig);
+ }
+ } catch (Exception cause) {
+ LOGGER.errorf(cause, "Could not lazy load paths from server");
+ return null;
+ }
+ }
- for (ScopeRepresentation scope : resourceDescription.getScopes()) {
- scopeNames.add(scope.getName());
- }
+ pathCache.put(targetUri, pathConfig);
- pathConfig.setScopes(scopeNames);
- pathConfig.setType(resourceDescription.getType());
+ return pathConfig;
+ }
- return pathConfig;
- }
+ @Override
+ protected String getPath(PathConfig entry) {
+ return entry.getPath();
+ }
- public PathMatcher getPathMatcher() {
- return pathMatcher;
+ @Override
+ protected Collection<PathConfig> getPaths() {
+ return paths.values();
+ }
+
+ @Override
+ protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
+ if (originalConfig.hasPattern()) {
+ ProtectedResource resource = authzClient.protection().resource();
+ List<ResourceRepresentation> search = resource.findByUri(path);
+
+ if (!search.isEmpty()) {
+ // resource does exist on the server, cache it
+ ResourceRepresentation targetResource = search.get(0);
+ PathConfig config = PathConfig.createPathConfig(targetResource);
+
+ config.setScopes(originalConfig.getScopes());
+ config.setMethods(originalConfig.getMethods());
+ config.setParentConfig(originalConfig);
+ config.setEnforcementMode(originalConfig.getEnforcementMode());
+
+ return config;
+ }
+ }
+
+ return null;
+ }
+ };
}
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java
index cf2e91a..80c9e42 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java
@@ -23,11 +23,11 @@ import java.util.List;
import java.util.concurrent.Callable;
import org.keycloak.authorization.client.Configuration;
-import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ServerConfiguration;
import org.keycloak.authorization.client.util.Http;
import org.keycloak.authorization.client.util.Throwables;
import org.keycloak.authorization.client.util.TokenCallable;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.util.JsonSerialization;
/**
@@ -124,11 +124,11 @@ public class ProtectedResource {
/**
* Query the server for a resource given its <code>name</code> where the owner is the resource server itself.
*
- * @param id the resource name
+ * @param name the resource name
* @return a {@link ResourceRepresentation}
*/
public ResourceRepresentation findByName(String name) {
- String[] representations = find(null, name, null, configuration.getResource(), null, null, null, null);
+ String[] representations = find(null, name, null, configuration.getResource(), null, null, false, null, null);
if (representations.length == 0) {
return null;
@@ -145,7 +145,7 @@ public class ProtectedResource {
* @return a {@link ResourceRepresentation}
*/
public ResourceRepresentation findByName(String name, String ownerId) {
- String[] representations = find(null, name, null, ownerId, null, null, null, null);
+ String[] representations = find(null, name, null, ownerId, null, null, false, null, null);
if (representations.length == 0) {
return null;
@@ -163,11 +163,12 @@ public class ProtectedResource {
* @param owner the resource owner
* @param type the resource type
* @param scope the resource scope
+ * @param matchingUri the resource uri. Use this parameter to lookup a resource that best match the given uri
* @param firstResult the position of the first resource to retrieve
* @param maxResult the maximum number of resources to retrieve
* @return an array of strings with the resource ids
*/
- public String[] find(final String id, final String name, final String uri, final String owner, final String type, final String scope, final Integer firstResult, final Integer maxResult) {
+ public String[] find(final String id, final String name, final String uri, final String owner, final String type, final String scope, final boolean matchingUri, final Integer firstResult, final Integer maxResult) {
Callable<String[]> callable = new Callable<String[]>() {
@Override
public String[] call() throws Exception {
@@ -179,6 +180,7 @@ public class ProtectedResource {
.param("owner", owner)
.param("type", type)
.param("scope", scope)
+ .param("matchingUri", Boolean.valueOf(matchingUri).toString())
.param("deep", Boolean.FALSE.toString())
.param("first", firstResult != null ? firstResult.toString() : null)
.param("max", maxResult != null ? maxResult.toString() : null)
@@ -199,7 +201,7 @@ public class ProtectedResource {
*/
public String[] findAll() {
try {
- return find(null,null , null, null, null, null, null, null);
+ return find(null,null , null, null, null, null, false, null, null);
} catch (Exception cause) {
throw Throwables.handleWrapException("Could not find resource", cause);
}
@@ -233,7 +235,30 @@ public class ProtectedResource {
* @param uri the resource uri
*/
public List<ResourceRepresentation> findByUri(String uri) {
- String[] ids = find(null, null, uri, null, null, null, null, null);
+ String[] ids = find(null, null, uri, null, null, null, false, null, null);
+
+ if (ids.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List<ResourceRepresentation> representations = new ArrayList<>();
+
+ for (String id : ids) {
+ representations.add(findById(id));
+ }
+
+ return representations;
+ }
+
+ /**
+ * Returns a list of resources that best matches the given {@code uri}. This method queries the server for resources whose
+ * {@link ResourceRepresentation#uri} best matches the given {@code uri}.
+ *
+ * @param uri the resource uri to match
+ * @return a list of resources
+ */
+ public List<ResourceRepresentation> findByMatchingUri(String uri) {
+ String[] ids = find(null, null, uri, null, null, null, true, null, null);
if (ids.length == 0) {
return Collections.emptyList();
diff --git a/core/src/main/java/org/keycloak/AuthorizationContext.java b/core/src/main/java/org/keycloak/AuthorizationContext.java
index 0a9b332..538a70f 100644
--- a/core/src/main/java/org/keycloak/AuthorizationContext.java
+++ b/core/src/main/java/org/keycloak/AuthorizationContext.java
@@ -59,53 +59,31 @@ public class AuthorizationContext {
return false;
}
- if (current != null) {
- if (current.getName().equals(resourceName)) {
- return true;
- }
- }
+ for (Permission permission : authorization.getPermissions()) {
+ if (resourceName.equalsIgnoreCase(permission.getResourceName()) || resourceName.equalsIgnoreCase(permission.getResourceId())) {
+ if (scopeName == null) {
+ return true;
+ }
- if (hasResourcePermission(resourceName)) {
- for (Permission permission : authorization.getPermissions()) {
- for (PathConfig pathHolder : paths.values()) {
- if (pathHolder.getId().equals(permission.getResourceId())) {
- if (permission.getScopes().contains(scopeName)) {
- return true;
- }
- }
+ if (permission.getScopes().contains(scopeName)) {
+ return true;
}
}
}
- return false;
- }
-
- public boolean hasResourcePermission(String resourceName) {
- if (this.authzToken == null) {
- return false;
- }
-
- Authorization authorization = this.authzToken.getAuthorization();
-
- if (authorization == null) {
- return false;
- }
-
if (current != null) {
if (current.getName().equals(resourceName)) {
return true;
}
}
- for (Permission permission : authorization.getPermissions()) {
- if (permission.getResourceName().equals(resourceName) || permission.getResourceId().equals(resourceName)) {
- return true;
- }
- }
-
return false;
}
+ public boolean hasResourcePermission(String resourceName) {
+ return hasPermission(resourceName, null);
+ }
+
public boolean hasScopePermission(String scopeName) {
if (this.authzToken == null) {
return false;
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index 89dadbf..71b44a4 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -23,16 +23,14 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PolicyEnforcerConfig {
- @JsonProperty("create-resources")
- @JsonInclude(JsonInclude.Include.NON_NULL)
- private Boolean createResources = Boolean.FALSE;
-
@JsonProperty("enforcement-mode")
private EnforcementMode enforcementMode = EnforcementMode.ENFORCING;
@@ -40,6 +38,9 @@ public class PolicyEnforcerConfig {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PathConfig> paths = new ArrayList<>();
+ @JsonProperty("lazy-load-paths")
+ private Boolean lazyLoadPaths = Boolean.FALSE;
+
@JsonProperty("on-deny-redirect-to")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String onDenyRedirectTo;
@@ -48,14 +49,18 @@ public class PolicyEnforcerConfig {
@JsonInclude(JsonInclude.Include.NON_NULL)
private UserManagedAccessConfig userManagedAccess;
- public Boolean isCreateResources() {
- return this.createResources;
- }
-
public List<PathConfig> getPaths() {
return this.paths;
}
+ public Boolean getLazyLoadPaths() {
+ return lazyLoadPaths;
+ }
+
+ public void setLazyLoadPaths(Boolean lazyLoadPaths) {
+ this.lazyLoadPaths = lazyLoadPaths;
+ }
+
public EnforcementMode getEnforcementMode() {
return this.enforcementMode;
}
@@ -68,10 +73,6 @@ public class PolicyEnforcerConfig {
return this.userManagedAccess;
}
- public void setCreateResources(Boolean createResources) {
- this.createResources = createResources;
- }
-
public void setPaths(List<PathConfig> paths) {
this.paths = paths;
}
@@ -90,6 +91,32 @@ public class PolicyEnforcerConfig {
public static class PathConfig {
+ public static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
+ PathConfig pathConfig = new PathConfig();
+
+ pathConfig.setId(resourceDescription.getId());
+ pathConfig.setName(resourceDescription.getName());
+
+ String uri = resourceDescription.getUri();
+
+ if (uri == null || "".equals(uri.trim())) {
+ throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
+ }
+
+ pathConfig.setPath(uri);
+
+ List<String> scopeNames = new ArrayList<>();
+
+ for (ScopeRepresentation scope : resourceDescription.getScopes()) {
+ scopeNames.add(scope.getName());
+ }
+
+ pathConfig.setScopes(scopeNames);
+ pathConfig.setType(resourceDescription.getType());
+
+ return pathConfig;
+ }
+
private String name;
private String type;
private String path;
@@ -231,7 +258,8 @@ public class PolicyEnforcerConfig {
public enum ScopeEnforcementMode {
ALL,
- ANY
+ ANY,
+ DISABLED
}
public static class UserManagedAccessConfig {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
index 071bc32..92f4717 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java
@@ -24,8 +24,10 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.keycloak.json.StringListMapDeserializer;
@@ -45,6 +47,7 @@ public class ResourceRepresentation {
private String uri;
private String type;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonProperty("scopes")
private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
@@ -52,9 +55,6 @@ public class ResourceRepresentation {
private ResourceOwnerRepresentation owner;
private Boolean ownerManagedAccess;
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- private List<PolicyRepresentation> policies;
-
private String displayName;
@JsonDeserialize(using = StringListMapDeserializer.class)
@@ -162,17 +162,31 @@ public class ResourceRepresentation {
}
public void setUri(String uri) {
- this.uri = uri;
+ if (uri != null && !"".equalsIgnoreCase(uri.trim())) {
+ this.uri = uri;
+ }
}
public void setType(String type) {
- this.type = type;
+ if (type != null && !"".equalsIgnoreCase(type.trim())) {
+ this.type = type;
+ }
}
public void setScopes(Set<ScopeRepresentation> scopes) {
this.scopes = scopes;
}
+ /**
+ * TODO: This is a workaround to allow deserialization of UMA resource representation. Jackson 2.19+ support aliases, once we upgrade, change this.
+ *
+ * @param scopes
+ */
+ @JsonSetter("resource_scopes")
+ private void setScopesUma(Set<ScopeRepresentation> scopes) {
+ this.scopes = scopes;
+ }
+
public void setIconUri(String iconUri) {
this.iconUri = iconUri;
}
@@ -181,10 +195,25 @@ public class ResourceRepresentation {
return this.owner;
}
+ @JsonProperty
public void setOwner(ResourceOwnerRepresentation owner) {
this.owner = owner;
}
+ @JsonIgnore
+ public void setOwner(String ownerId) {
+ if (ownerId == null) {
+ owner = null;
+ return;
+ }
+
+ if (owner == null) {
+ owner = new ResourceOwnerRepresentation();
+ }
+
+ owner.setId(ownerId);
+ }
+
public Boolean getOwnerManagedAccess() {
return ownerManagedAccess;
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index 0c82dc0..b9cecf7 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -181,6 +181,10 @@ public class JPAResourceStore implements ResourceStore {
predicates.add(root.join("scopes").get("id").in(value));
} else if ("ownerManagedAccess".equals(name)) {
predicates.add(builder.equal(root.get(name), Boolean.valueOf(value[0])));
+ } else if ("uri".equals(name)) {
+ predicates.add(builder.equal(builder.lower(root.get(name)), value[0].toLowerCase()));
+ } else if ("uri_not_null".equals(name)) {
+ predicates.add(builder.isNotNull(root.get("uri")));
} else {
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 538dcef..06545a2 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -2314,7 +2314,7 @@ public class RepresentationToModel {
String ownerId = owner.getId();
if (ownerId == null) {
- throw new RuntimeException("No owner specified for resource [" + resource.getName() + "].");
+ ownerId = resourceServer.getId();
}
if (!resourceServer.getId().equals(ownerId)) {
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
index b69d991..edde9cc 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
@@ -21,6 +21,8 @@ import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
import static org.keycloak.models.utils.RepresentationToModel.toModel;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -46,6 +48,7 @@ import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@@ -54,6 +57,7 @@ import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.common.util.PathMatcher;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
@@ -64,7 +68,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
-import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
@@ -90,32 +94,18 @@ public class ResourceSetService {
@Consumes("application/json")
@Produces("application/json")
public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
- return create(uriInfo, resource, (Function<Resource, ResourceRepresentation>) resource1 -> {
- ResourceRepresentation representation = new ResourceRepresentation();
-
- representation.setId(resource1.getId());
+ if (resource == null) {
+ return Response.status(Status.BAD_REQUEST).build();
+ }
- return representation;
- });
- }
+ ResourceRepresentation newResource = create(resource);
- public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource, Function<Resource, ?> toRepresentation) {
- Response response = create(resource, toRepresentation);
audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
- return response;
- }
-
- public Response create(ResourceRepresentation resource) {
- return create(resource, (Function<Resource, ResourceRepresentation>) resource1 -> {
- ResourceRepresentation representation = new ResourceRepresentation();
-
- representation.setId(resource1.getId());
- return representation;
- });
+ return Response.status(Status.CREATED).entity(newResource).build();
}
- public Response create(ResourceRepresentation resource, Function<Resource, ?> toRepresentation) {
+ public ResourceRepresentation create(ResourceRepresentation resource) {
requireManage();
StoreFactory storeFactory = this.authorization.getStoreFactory();
ResourceOwnerRepresentation owner = resource.getOwner();
@@ -123,21 +113,22 @@ public class ResourceSetService {
if (owner == null) {
owner = new ResourceOwnerRepresentation();
owner.setId(resourceServer.getId());
+ resource.setOwner(owner);
}
String ownerId = owner.getId();
if (ownerId == null) {
- return ErrorResponse.error("You must specify the resource owner.", Status.BAD_REQUEST);
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "You must specify the resource owner.", Status.BAD_REQUEST);
}
Resource existingResource = storeFactory.getResourceStore().findByName(resource.getName(), ownerId, this.resourceServer.getId());
if (existingResource != null) {
- return ErrorResponse.exists("Resource with name [" + resource.getName() + "] already exists.");
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Resource with name [" + resource.getName() + "] already exists.", Status.CONFLICT);
}
- return Response.status(Status.CREATED).entity(toRepresentation.apply(toModel(resource, this.resourceServer, authorization))).build();
+ return toRepresentation(toModel(resource, this.resourceServer, authorization), resourceServer, authorization);
}
@Path("{id}")
@@ -198,10 +189,10 @@ public class ResourceSetService {
@NoCache
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
- return findById(id, (Function<Resource, ResourceRepresentation>) resource -> toRepresentation(resource, resourceServer, authorization, true));
+ return findById(id, resource -> toRepresentation(resource, resourceServer, authorization, true));
}
- public Response findById(@PathParam("id") String id, Function<Resource, ?> toRepresentation) {
+ public Response findById(String id, Function<Resource, ? extends ResourceRepresentation> toRepresentation) {
requireView();
StoreFactory storeFactory = authorization.getStoreFactory();
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
@@ -340,10 +331,11 @@ public class ResourceSetService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
+ @QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
- return find(id, name, uri, owner, type, scope, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, ResourceRepresentation>) (resource, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1));
+ return find(id, name, uri, owner, type, scope, matchingUri, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, ResourceRepresentation>) (resource, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1));
}
public Response find(@QueryParam("_id") String id,
@@ -352,6 +344,7 @@ public class ResourceSetService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
+ @QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult,
@@ -413,9 +406,38 @@ public class ResourceSetService {
search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new));
}
+ List<Resource> resources = storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS);
+
+ if (matchingUri != null && matchingUri && resources.isEmpty()) {
+ HashMap<String, String[]> attributes = new HashMap<>();
+
+ attributes.put("uri_not_null", new String[] {"true"});
+ attributes.put("owner", new String[] {resourceServer.getId()});
+
+ List<Resource> serverResources = storeFactory.getResourceStore().findByResourceServer(attributes, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS);
+ PathMatcher<Resource> pathMatcher = new PathMatcher<Resource>() {
+ @Override
+ protected String getPath(Resource entry) {
+ return entry.getUri();
+ }
+
+ @Override
+ protected Collection<Resource> getPaths() {
+ return serverResources;
+ }
+ };
+
+ Resource matches = pathMatcher.matches(uri);
+
+ if (matches != null) {
+ resources = Arrays.asList(matches);
+ }
+ }
+
Boolean finalDeep = deep;
+
return Response.ok(
- storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream()
+ resources.stream()
.map(resource -> toRepresentation.apply(resource, finalDeep))
.collect(Collectors.toList()))
.build();
@@ -437,7 +459,7 @@ public class ResourceSetService {
audit(uriInfo, resource, null, operation);
}
- private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
+ public void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
if (authorization.getRealm().isAdminEventsEnabled()) {
if (id != null) {
adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index 8a811f1..80226cf 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -65,7 +65,7 @@ public class ProtectionService {
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
- ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
+ ResourceService resource = new ResourceService(resourceServer, identity, resourceManager);
ResteasyProviderFactory.getInstance().injectProperties(resource);
diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
index 21fd27c..1fbe5f9 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
@@ -18,8 +18,6 @@
package org.keycloak.authorization.protection.resource;
import java.util.function.BiFunction;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -36,16 +34,13 @@ import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
-import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.ResourceSetService;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
-import org.keycloak.authorization.protection.resource.representation.UmaResourceRepresentation;
-import org.keycloak.authorization.protection.resource.representation.UmaScopeRepresentation;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
-import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
/**
@@ -56,39 +51,51 @@ public class ResourceService {
private final ResourceServer resourceServer;
private final ResourceSetService resourceManager;
private final Identity identity;
- private final AuthorizationProvider authorization;
- public ResourceService(ResourceServer resourceServer, Identity identity, ResourceSetService resourceManager, AuthorizationProvider authorization) {
+ public ResourceService(ResourceServer resourceServer, Identity identity, ResourceSetService resourceManager) {
this.identity = identity;
this.resourceServer = resourceServer;
this.resourceManager = resourceManager;
- this.authorization = authorization;
}
@POST
@Consumes("application/json")
@Produces("application/json")
- public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation umaResource) {
+ public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation resource) {
checkResourceServerSettings();
- if (umaResource == null) {
+
+ if (resource == null) {
return Response.status(Status.BAD_REQUEST).build();
}
- return this.resourceManager.create(uriInfo, toResourceRepresentation(umaResource), (Function<Resource, UmaResourceRepresentation>) this::toUmaRepresentation);
+
+ ResourceOwnerRepresentation owner = resource.getOwner();
+
+ if (owner == null) {
+ owner = new ResourceOwnerRepresentation();
+ resource.setOwner(owner);
+ }
+
+ String ownerId = owner.getId();
+
+ if (ownerId == null) {
+ ownerId = this.identity.getId();
+ }
+
+ owner.setId(ownerId);
+
+ ResourceRepresentation newResource = resourceManager.create(resource);
+
+ resourceManager.audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
+
+ return Response.status(Status.CREATED).entity(new UmaResourceRepresentation(newResource)).build();
}
@Path("{id}")
@PUT
@Consumes("application/json")
@Produces("application/json")
- public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) {
- ResourceRepresentation resource = toResourceRepresentation(representation);
- Response response = this.resourceManager.update(uriInfo, id, resource);
-
- if (response.getEntity() instanceof ResourceRepresentation) {
- return Response.noContent().build();
- }
-
- return response;
+ public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) {
+ return this.resourceManager.update(uriInfo, id, resource);
}
@Path("/{id}")
@@ -102,7 +109,7 @@ public class ResourceService {
@GET
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
- return this.resourceManager.findById(id, (Function<Resource, UmaResourceRepresentation>) resource -> toUmaRepresentation(resource));
+ return this.resourceManager.findById(id, UmaResourceRepresentation::new);
}
@GET
@@ -114,75 +121,11 @@ public class ResourceService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
+ @QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
- return resourceManager.find(id, name, uri, owner, type, scope, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, String>) (resource, deep1) -> resource.getId());
- }
-
- private ResourceRepresentation toResourceRepresentation(UmaResourceRepresentation umaResource) {
- ResourceRepresentation resource = new ResourceRepresentation();
-
- resource.setId(umaResource.getId());
- resource.setIconUri(umaResource.getIconUri());
- resource.setName(umaResource.getName());
- resource.setUri(umaResource.getUri());
- resource.setType(umaResource.getType());
- resource.setOwnerManagedAccess(umaResource.getOwnerManagedAccess());
-
- ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
- String ownerId = umaResource.getOwner();
-
- if (ownerId == null) {
- ownerId = this.identity.getId();
- }
-
- owner.setId(ownerId);
- resource.setOwner(owner);
-
- resource.setScopes(umaResource.getScopes().stream().map(representation -> {
- ScopeRepresentation scopeRepresentation = new ScopeRepresentation();
-
- scopeRepresentation.setId(representation.getId());
- scopeRepresentation.setName(representation.getName());
- scopeRepresentation.setIconUri(representation.getIconUri());
-
- return scopeRepresentation;
- }).collect(Collectors.toSet()));
-
- resource.setAttributes(umaResource.getAttributes());
-
- return resource;
- }
-
- private UmaResourceRepresentation toUmaRepresentation(Resource model) {
- if (model == null) {
- return null;
- }
-
- UmaResourceRepresentation resource = new UmaResourceRepresentation();
-
- resource.setId(model.getId());
- resource.setIconUri(model.getIconUri());
- resource.setName(model.getName());
- resource.setUri(model.getUri());
- resource.setType(model.getType());
-
- if (model.getOwner() != null) {
- resource.setOwner(model.getOwner());
- }
-
- resource.setScopes(model.getScopes().stream().map(scopeRepresentation -> {
- UmaScopeRepresentation umaScopeRep = new UmaScopeRepresentation();
- umaScopeRep.setId(scopeRepresentation.getId());
- umaScopeRep.setName(scopeRepresentation.getName());
- umaScopeRep.setIconUri(scopeRepresentation.getIconUri());
- return umaScopeRep;
- }).collect(Collectors.toSet()));
-
- resource.setAttributes(model.getAttributes());
-
- return resource;
+ return resourceManager.find(id, name, uri, owner, type, scope, matchingUri, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, String>) (resource, deep1) -> resource.getId());
}
private void checkResourceServerSettings() {
diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/UmaResourceRepresentation.java b/services/src/main/java/org/keycloak/authorization/protection/resource/UmaResourceRepresentation.java
new file mode 100644
index 0000000..302dd96
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/protection/resource/UmaResourceRepresentation.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.authorization.protection.resource;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class UmaResourceRepresentation extends ResourceRepresentation {
+
+ public UmaResourceRepresentation() {
+
+ }
+
+ public UmaResourceRepresentation(ResourceRepresentation resource) {
+ setId(resource.getId());
+ setName(resource.getName());
+ setType(resource.getType());
+ setUri(resource.getUri());
+ setIconUri(resource.getIconUri());
+ setOwner(resource.getOwner());
+ setScopes(resource.getScopes());
+ setDisplayName(resource.getDisplayName());
+ setOwnerManagedAccess(resource.getOwnerManagedAccess());
+ }
+
+ public UmaResourceRepresentation(Resource resource) {
+ setId(resource.getId());
+ setName(resource.getName());
+ setType(resource.getType());
+ setUri(resource.getUri());
+ setIconUri(resource.getIconUri());
+ setOwner(resource.getOwner());
+ setScopes(resource.getScopes().stream().map(scope -> new ScopeRepresentation(scope.getName())).collect(Collectors.toSet()));
+ setDisplayName(resource.getDisplayName());
+ setOwnerManagedAccess(resource.isOwnerManagedAccess());
+ setAttributes(resource.getAttributes());
+ }
+
+ @JsonProperty("resource_scopes")
+ @Override
+ public Set<ScopeRepresentation> getScopes() {
+ return super.getScopes();
+ }
+
+ @JsonProperty("resource_scopes")
+ @Override
+ public void setScopes(Set<ScopeRepresentation> scopes) {
+ super.setScopes(scopes);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index f6beac5..a84765b 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -68,6 +68,7 @@ import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@@ -315,7 +316,7 @@ public class ExportUtils {
ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization);
if (rep.getOwner().getId().equals(settingsModel.getId())) {
- rep.setOwner(null);
+ rep.setOwner((ResourceOwnerRepresentation) null);
} else {
rep.getOwner().setId(null);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index 60970d8..8dadaaf 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -155,7 +155,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
enforcerConfig.setEnforcementMode(null);
- enforcerConfig.setCreateResources(null);
+ enforcerConfig.setLazyLoadPaths(null);
rep.setEnforcerConfig(enforcerConfig);
diff --git a/testsuite/integration-arquillian/test-apps/photoz/keycloak-lazy-load-path-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/keycloak-lazy-load-path-authz-service.json
new file mode 100644
index 0000000..47437dc
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/photoz/keycloak-lazy-load-path-authz-service.json
@@ -0,0 +1,78 @@
+{
+ "realm": "photoz",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "http://localhost:8180/auth",
+ "ssl-required": "external",
+ "resource": "photoz-restful-api",
+ "bearer-only" : true,
+ "credentials": {
+ "jwt": {
+ "client-key-password": "password",
+ "client-keystore-file": "classpath:keystore.jks",
+ "client-keystore-password": "password",
+ "client-key-alias": "secure-portal",
+ "token-timeout": 10,
+ "client-keystore-type": "jks"
+ }
+ },
+ "policy-enforcer": {
+ "enforcement-mode": "PERMISSIVE",
+ "user-managed-access": {},
+ "lazy-load-paths": true,
+ "paths": [
+ {
+ "name" : "Album Resource",
+ "path" : "/album",
+ "methods" : [
+ {
+ "method": "GET",
+ "scopes-enforcement-mode" : "DISABLED"
+ }
+ ]
+ },
+ {
+ "name" : "Album Resource",
+ "path" : "/album/{id}",
+ "methods" : [
+ {
+ "method": "DELETE",
+ "scopes" : ["album:delete"]
+ },
+ {
+ "method": "GET",
+ "scopes" : ["album:view"]
+ }
+ ]
+ },
+ {
+ "path" : "/profile"
+ },
+ {
+ "name" : "Admin Resources",
+ "path" : "/admin/*"
+ },
+ {
+ "name" : "Scope Protected Resource",
+ "path" : "/scope-any",
+ "methods": [
+ {
+ "method": "GET",
+ "scopes": ["scope-a", "scope-b"],
+ "scopes-enforcement-mode": "ANY"
+ }
+ ]
+ },
+ {
+ "name" : "Scope Protected Resource",
+ "path" : "/scope-all",
+ "methods": [
+ {
+ "method": "GET",
+ "scopes": ["scope-a", "scope-b"],
+ "scopes-enforcement-mode": "ALL"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 94feb72..40b1242 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -3,8 +3,8 @@ package org.keycloak.example.photoz.album;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
-import org.keycloak.authorization.client.representation.ResourceRepresentation;
-import org.keycloak.authorization.client.representation.ScopeRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.util.Transaction;
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index a0f8711..dd0cab1 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -21,6 +21,16 @@
"paths": [
{
"name" : "Album Resource",
+ "path" : "/album",
+ "methods" : [
+ {
+ "method": "GET",
+ "scopes-enforcement-mode" : "DISABLED"
+ }
+ ]
+ },
+ {
+ "name" : "Album Resource",
"path" : "/album/{id}",
"methods" : [
{
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-lazy-load-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-lazy-load-authz-service.json
new file mode 100644
index 0000000..35f76d2
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-lazy-load-authz-service.json
@@ -0,0 +1,15 @@
+{
+ "realm": "servlet-authz",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url" : "http://localhost:8180/auth",
+ "ssl-required" : "external",
+ "resource" : "servlet-authz-app",
+ "public-client" : false,
+ "credentials": {
+ "secret": "secret"
+ },
+ "policy-enforcer": {
+ "on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
+ "lazy-load-paths": true
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/src/main/webapp/WEB-INF/keycloak.json
index 0dd6a14..d6e7f00 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/src/main/webapp/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/src/main/webapp/WEB-INF/keycloak.json
@@ -8,6 +8,7 @@
},
"policy-enforcer": {
"on-deny-redirect-to": "/servlet-policy-enforcer/denied.jsp",
+ "lazy-load-paths": false,
"paths": [
{
"name": "Welcome Resource",
@@ -31,7 +32,7 @@
},
{
"name": "Pattern 5",
- "path": "/resource/{pattern}/resource-d"
+ "path": "/a/{pattern}/resource-d"
},
{
"name": "Pattern 6",
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index f87d481..90a6692 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -203,10 +203,16 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
this.loginPage.form().login(username, password);
waitForPageToLoad();//guess
-
- // simple check if we are at the consent page, if so just click 'Yes'
- if (this.consentPage.isCurrent()) {
- consentPage.confirm();
+
+ try {
+ if (!isCurrent()) {
+ // simple check if we are at the consent page, if so just click 'Yes'
+ if (this.consentPage.isCurrent()) {
+ consentPage.confirm();
+ }
+ }
+ } catch (Exception ignore) {
+ // ignore errors when checking consent page, if an error tests will also fail
}
pause(WAIT_AFTER_OPERATION);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractBaseServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractBaseServletAuthzAdapterTest.java
new file mode 100644
index 0000000..7f47bb9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractBaseServletAuthzAdapterTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.keycloak.testsuite.util.IOUtil.loadJson;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployer;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.junit.BeforeClass;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
+import org.keycloak.testsuite.util.WaitUtils;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractBaseServletAuthzAdapterTest extends AbstractExampleAdapterTest {
+
+ protected static final String REALM_NAME = "servlet-authz";
+ protected static final String RESOURCE_SERVER_ID = "servlet-authz-app";
+
+ @BeforeClass
+ public static void enabled() { ProfileAssume.assumePreview(); }
+
+ @ArquillianResource
+ private Deployer deployer;
+
+ @Override
+ public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
+ testRealms.add(
+ loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
+ }
+
+ protected void performTests(ExceptionRunnable assertion) {
+ performTests(() -> importResourceServerSettings(), assertion);
+ }
+
+ protected void performTests(ExceptionRunnable beforeDeploy, ExceptionRunnable assertion) {
+ try {
+ beforeDeploy.run();
+ deployer.deploy(RESOURCE_SERVER_ID);
+ assertion.run();
+ } catch (FileNotFoundException cause) {
+ throw new RuntimeException("Failed to import authorization settings", cause);
+ } catch (Exception cause) {
+ throw new RuntimeException("Error while executing tests", cause);
+ } finally {
+ deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ protected boolean hasLink(String text) {
+ return getLink(text) != null;
+ }
+
+ protected boolean hasText(String text) {
+ return this.driver.getPageSource().contains(text);
+ }
+
+ private WebElement getLink(String text) {
+ return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
+ }
+
+ protected void importResourceServerSettings() throws FileNotFoundException {
+ getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
+ }
+
+ protected AuthorizationResource getAuthorizationResource() {
+ return getClientResource(RESOURCE_SERVER_ID).authorization();
+ }
+
+ protected ClientResource getClientResource(String clientId) {
+ ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
+ ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
+ return clients.get(resourceServer.getId());
+ }
+
+ private void logOut() {
+ navigateTo();
+ By by = By.xpath("//a[text() = 'Sign Out']");
+ WaitUtils.waitUntilElement(by);
+ this.driver.findElement(by).click();
+ pause(500);
+ }
+
+ protected void login(String username, String password) {
+ try {
+ navigateTo();
+ Thread.sleep(2000);
+ if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
+ Thread.sleep(2000);
+ logOut();
+ navigateTo();
+ }
+
+ Thread.sleep(2000);
+
+ this.loginPage.form().login(username, password);
+ } catch (Exception cause) {
+ throw new RuntimeException("Login failed", cause);
+ }
+ }
+
+ private void navigateTo() {
+ this.driver.navigate().to(getResourceServerUrl());
+ WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
+ }
+
+ protected boolean wasDenied() {
+ return this.driver.getPageSource().contains("You can not access this resource.");
+ }
+
+ protected URL getResourceServerUrl() {
+ try {
+ return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Could not obtain resource server url.", e);
+ }
+ }
+
+ protected void navigateToDynamicMenuPage() {
+ navigateTo();
+ getLink("Dynamic Menu").click();
+ }
+
+ protected void navigateToUserPremiumPage() {
+ navigateTo();
+ getLink("User Premium").click();
+ }
+
+ protected void navigateToAdminPage() {
+ navigateTo();
+ getLink("Administration").click();
+ }
+
+ protected void updatePermissionPolicies(String permissionName, String... policyNames) {
+ for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+ if (permissionName.equalsIgnoreCase(policy.getName())) {
+ StringBuilder policies = new StringBuilder("[");
+
+ for (String policyName : policyNames) {
+ if (policies.length() > 1) {
+ policies.append(",");
+ }
+ policies.append("\"").append(policyName).append("\"");
+
+ }
+
+ policies.append("]");
+
+ policy.getConfig().put("applyPolicies", policies.toString());
+ getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+ }
+ }
+ }
+
+ protected void createUserPolicy(String name, String... userNames) {
+ UserPolicyRepresentation policy = new UserPolicyRepresentation();
+
+ policy.setName(name);
+
+ for (String userName : userNames) {
+ policy.addUser(userName);
+ }
+
+ assertFalse(policy.getUsers().isEmpty());
+
+ Response response = getAuthorizationResource().policies().user().create(policy);
+ response.close();
+ }
+
+ protected interface ExceptionRunnable {
+ void run() throws Exception;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPermissiveModeAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPermissiveModeAdapterTest.java
index e1ad409..052150c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPermissiveModeAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPermissiveModeAdapterTest.java
@@ -28,7 +28,7 @@ import org.junit.Test;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public abstract class AbstractPermissiveModeAdapterTest extends AbstractServletAuthzAdapterTest {
+public abstract class AbstractPermissiveModeAdapterTest extends AbstractBaseServletAuthzAdapterTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() throws IOException {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 4cc9d4a..80ddd05 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -75,7 +75,7 @@ import org.keycloak.util.JsonSerialization;
public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAdapterTest {
private static final String REALM_NAME = "photoz";
- private static final String RESOURCE_SERVER_ID = "photoz-restful-api";
+ protected static final String RESOURCE_SERVER_ID = "photoz-restful-api";
private static final int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
@ArquillianResource
@@ -118,16 +118,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
testRealms.add(realm);
}
- @Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
- public static WebArchive deploymentClient() throws IOException {
- return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
- }
-
- @Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
- public static WebArchive deploymentResourceServer() throws IOException {
- return exampleDeployment(RESOURCE_SERVER_ID);
- }
-
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleLazyLoadPathsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleLazyLoadPathsAdapterTest.java
new file mode 100644
index 0000000..3e35a33
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleLazyLoadPathsAdapterTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.util.IOUtil.loadJson;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.LaxRedirectStrategy;
+import org.jboss.arquillian.container.test.api.Deployer;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.ResourcesResource;
+import org.keycloak.admin.client.resource.RoleResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
+import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractPhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
+
+ @Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
+ public static WebArchive deploymentClient() throws IOException {
+ return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
+ }
+
+ @Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
+ public static WebArchive deploymentResourceServer() throws IOException {
+ return exampleDeployment(RESOURCE_SERVER_ID)
+ .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json");
+ }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
index f6a8bb2..71297a9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,196 +16,19 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
-import static org.junit.Assert.assertFalse;
-import static org.keycloak.testsuite.util.IOUtil.loadJson;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.WaitUtils.pause;
+import java.io.IOException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-
-import javax.ws.rs.core.Response;
-
-import org.jboss.arquillian.container.test.api.Deployer;
-import org.jboss.arquillian.test.api.ArquillianResource;
-import org.junit.BeforeClass;
-import org.keycloak.admin.client.resource.AuthorizationResource;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.ClientsResource;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.authorization.PolicyRepresentation;
-import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
-import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
-import org.keycloak.testsuite.ProfileAssume;
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import org.keycloak.testsuite.util.WaitUtils;
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebElement;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest {
-
- protected static final String REALM_NAME = "servlet-authz";
- protected static final String RESOURCE_SERVER_ID = "servlet-authz-app";
-
- @BeforeClass
- public static void enabled() { ProfileAssume.assumePreview(); }
-
- @ArquillianResource
- private Deployer deployer;
-
- @Override
- public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
- testRealms.add(
- loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
- }
-
- protected void performTests(ExceptionRunnable assertion) {
- performTests(() -> importResourceServerSettings(), assertion);
- }
-
- protected void performTests(ExceptionRunnable beforeDeploy, ExceptionRunnable assertion) {
- try {
- beforeDeploy.run();
- deployer.deploy(RESOURCE_SERVER_ID);
- assertion.run();
- } catch (FileNotFoundException cause) {
- throw new RuntimeException("Failed to import authorization settings", cause);
- } catch (Exception cause) {
- throw new RuntimeException("Error while executing tests", cause);
- } finally {
- deployer.undeploy(RESOURCE_SERVER_ID);
- }
- }
-
- protected boolean hasLink(String text) {
- return getLink(text) != null;
- }
-
- protected boolean hasText(String text) {
- return this.driver.getPageSource().contains(text);
- }
-
- private WebElement getLink(String text) {
- return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
- }
-
- protected void importResourceServerSettings() throws FileNotFoundException {
- getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
- }
-
- protected AuthorizationResource getAuthorizationResource() {
- return getClientResource(RESOURCE_SERVER_ID).authorization();
- }
-
- protected ClientResource getClientResource(String clientId) {
- ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
- ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
- return clients.get(resourceServer.getId());
- }
-
- private void logOut() {
- navigateTo();
- By by = By.xpath("//a[text() = 'Sign Out']");
- WaitUtils.waitUntilElement(by);
- this.driver.findElement(by).click();
- pause(500);
- }
-
- protected void login(String username, String password) {
- try {
- navigateTo();
- Thread.sleep(2000);
- if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
- Thread.sleep(2000);
- logOut();
- navigateTo();
- }
+public abstract class AbstractServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
- Thread.sleep(2000);
-
- this.loginPage.form().login(username, password);
- } catch (Exception cause) {
- throw new RuntimeException("Login failed", cause);
- }
- }
-
- private void navigateTo() {
- this.driver.navigate().to(getResourceServerUrl());
- WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
- }
-
- protected boolean wasDenied() {
- return this.driver.getPageSource().contains("You can not access this resource.");
- }
-
- protected URL getResourceServerUrl() {
- try {
- return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
- } catch (MalformedURLException e) {
- throw new RuntimeException("Could not obtain resource server url.", e);
- }
- }
-
- protected void navigateToDynamicMenuPage() {
- navigateTo();
- getLink("Dynamic Menu").click();
- }
-
- protected void navigateToUserPremiumPage() {
- navigateTo();
- getLink("User Premium").click();
- }
-
- protected void navigateToAdminPage() {
- navigateTo();
- getLink("Administration").click();
+ @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+ public static WebArchive deployment() throws IOException {
+ return exampleDeployment(RESOURCE_SERVER_ID);
}
- protected void updatePermissionPolicies(String permissionName, String... policyNames) {
- for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
- if (permissionName.equalsIgnoreCase(policy.getName())) {
- StringBuilder policies = new StringBuilder("[");
-
- for (String policyName : policyNames) {
- if (policies.length() > 1) {
- policies.append(",");
- }
- policies.append("\"").append(policyName).append("\"");
-
- }
-
- policies.append("]");
-
- policy.getConfig().put("applyPolicies", policies.toString());
- getAuthorizationResource().policies().policy(policy.getId()).update(policy);
- }
- }
- }
-
- protected void createUserPolicy(String name, String... userNames) {
- UserPolicyRepresentation policy = new UserPolicyRepresentation();
-
- policy.setName(name);
-
- for (String userName : userNames) {
- policy.addUser(userName);
- }
-
- assertFalse(policy.getUsers().isEmpty());
-
- Response response = getAuthorizationResource().policies().user().create(policy);
- response.close();
- }
-
- protected interface ExceptionRunnable {
- void run() throws Exception;
- }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
index 63852d0..35c936b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
@@ -19,14 +19,11 @@ package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.core.Response;
-import org.jboss.arquillian.container.test.api.Deployment;
-import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientPoliciesResource;
import org.keycloak.admin.client.resource.RealmResource;
@@ -46,12 +43,7 @@ import org.keycloak.testsuite.util.WaitUtils;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractServletAuthzAdapterTest {
-
- @Deployment(name = RESOURCE_SERVER_ID, managed = false)
- public static WebArchive deployment() throws IOException {
- return exampleDeployment(RESOURCE_SERVER_ID);
- }
+public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractBaseServletAuthzAdapterTest {
@Test
public void testCanNotAccessWhenEnforcing() throws Exception {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzLazyLoadPathsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzLazyLoadPathsAdapterTest.java
new file mode 100644
index 0000000..0989aa5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzLazyLoadPathsAdapterTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientPoliciesResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.ResourcesResource;
+import org.keycloak.admin.client.resource.RolePoliciesResource;
+import org.keycloak.admin.client.resource.RoleScopeResource;
+import org.keycloak.admin.client.resource.RolesResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.testsuite.util.WaitUtils;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractServletAuthzLazyLoadPathsAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+
+ @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+ public static WebArchive deployment() throws IOException {
+ return exampleDeployment(RESOURCE_SERVER_ID)
+ .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-lazy-load-authz-service.json"), "keycloak.json");
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletPolicyEnforcerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletPolicyEnforcerTest.java
index 5c6b0eb..9afed01 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletPolicyEnforcerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletPolicyEnforcerTest.java
@@ -177,21 +177,21 @@ public abstract class AbstractServletPolicyEnforcerTest extends AbstractExampleA
performTests(() -> {
login("alice", "alice");
- navigateTo("/resource/a/resource-d");
+ navigateTo("/a/a/resource-d");
assertFalse(wasDenied());
navigateTo("/resource/b/resource-d");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Deny Policy");
login("alice", "alice");
- navigateTo("/resource/a/resource-d");
+ navigateTo("/a/a/resource-d");
assertTrue(wasDenied());
- navigateTo("/resource/b/resource-d");
+ navigateTo("/a/b/resource-d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Default Policy");
login("alice", "alice");
- navigateTo("/resource/b/resource-d");
+ navigateTo("/a/b/resource-d");
assertFalse(wasDenied());
});
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ResourceManagementWithAuthzClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ResourceManagementWithAuthzClientTest.java
index 32865ec..bbff687 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ResourceManagementWithAuthzClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ResourceManagementWithAuthzClientTest.java
@@ -17,12 +17,17 @@
package org.keycloak.testsuite.admin.client.authorization;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
import java.util.stream.Collectors;
+import org.junit.Test;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
-import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -35,12 +40,60 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
private AuthzClient authzClient;
+ @Test
+ public void testFindMatchingUri() {
+ doCreateResource(new ResourceRepresentation("/*", Collections.emptySet(), "/*", null));
+ doCreateResource(new ResourceRepresentation("/resources/*", Collections.emptySet(), "/resources/*", null));
+ doCreateResource(new ResourceRepresentation("/resources/{pattern}/*", Collections.emptySet(), "/resources/{pattern}/*", null));
+ doCreateResource(new ResourceRepresentation("/resources/{pattern}/{pattern}/*", Collections.emptySet(), "/resources/{pattern}/{pattern}/*", null));
+ doCreateResource(new ResourceRepresentation("/resources/{pattern}/sub-resources/{pattern}/*", Collections.emptySet(), "/resources/{pattern}/sub-resources/{pattern}/*", null));
+ doCreateResource(new ResourceRepresentation("/resources/{pattern}/sub-resource", Collections.emptySet(), "/resources/{pattern}/sub-resources/{pattern}/*", null));
+
+ AuthzClient authzClient = getAuthzClient();
+
+ List<ResourceRepresentation> resources = authzClient.protection().resource().findByMatchingUri("/test");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/*", resources.get(0).getUri());
+
+ resources = authzClient.protection().resource().findByMatchingUri("/resources/test");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/resources/*", resources.get(0).getUri());
+
+ resources = authzClient.protection().resource().findByMatchingUri("/resources");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/resources/*", resources.get(0).getUri());
+
+ resources = authzClient.protection().resource().findByMatchingUri("/resources/a/b");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/resources/{pattern}/*", resources.get(0).getUri());
+
+ resources = authzClient.protection().resource().findByMatchingUri("/resources/a/b/c");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/resources/{pattern}/{pattern}/*", resources.get(0).getUri());
+
+ resources = authzClient.protection().resource().findByMatchingUri("/resources/a/sub-resources/c/d");
+
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ assertEquals("/resources/{pattern}/sub-resources/{pattern}/*", resources.get(0).getUri());
+ }
+
@Override
protected ResourceRepresentation doCreateResource(ResourceRepresentation newResource) {
- org.keycloak.authorization.client.representation.ResourceRepresentation resource = toResourceRepresentation(newResource);
+ ResourceRepresentation resource = toResourceRepresentation(newResource);
AuthzClient authzClient = getAuthzClient();
- org.keycloak.authorization.client.representation.ResourceRepresentation response = authzClient.protection().resource().create(resource);
+ ResourceRepresentation response = authzClient.protection().resource().create(resource);
return toResourceRepresentation(authzClient, response.getId());
}
@@ -60,7 +113,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
}
private ResourceRepresentation toResourceRepresentation(AuthzClient authzClient, String id) {
- org.keycloak.authorization.client.representation.ResourceRepresentation created = authzClient.protection().resource().findById(id);
+ ResourceRepresentation created = authzClient.protection().resource().findById(id);
ResourceRepresentation resourceRepresentation = new ResourceRepresentation();
resourceRepresentation.setId(created.getId());
@@ -68,11 +121,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
resourceRepresentation.setIconUri(created.getIconUri());
resourceRepresentation.setUri(created.getUri());
resourceRepresentation.setType(created.getType());
- ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
-
- owner.setId(created.getOwner());
-
- resourceRepresentation.setOwner(owner);
+ resourceRepresentation.setOwner(created.getOwner());
resourceRepresentation.setScopes(created.getScopes().stream().map(scopeRepresentation -> {
ScopeRepresentation scope = new ScopeRepresentation();
@@ -88,8 +137,8 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
return resourceRepresentation;
}
- private org.keycloak.authorization.client.representation.ResourceRepresentation toResourceRepresentation(ResourceRepresentation newResource) {
- org.keycloak.authorization.client.representation.ResourceRepresentation resource = new org.keycloak.authorization.client.representation.ResourceRepresentation();
+ private ResourceRepresentation toResourceRepresentation(ResourceRepresentation newResource) {
+ ResourceRepresentation resource = new ResourceRepresentation();
resource.setId(newResource.getId());
resource.setName(newResource.getName());
@@ -102,7 +151,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
}
resource.setScopes(newResource.getScopes().stream().map(scopeRepresentation -> {
- org.keycloak.authorization.client.representation.ScopeRepresentation scope = new org.keycloak.authorization.client.representation.ScopeRepresentation();
+ ScopeRepresentation scope = new ScopeRepresentation();
scope.setName(scopeRepresentation.getName());
scope.setIconUri(scopeRepresentation.getIconUri());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
index 7fece47..63726b5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
@@ -42,7 +42,6 @@ import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
-import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.jose.jws.JWSInput;
@@ -56,6 +55,7 @@ import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
index b55f348..7393a94 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
@@ -38,8 +38,6 @@ import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
-import org.keycloak.authorization.client.representation.ResourceRepresentation;
-import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
@@ -48,7 +46,9 @@ import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java
index 42cde44..6139c22 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleAdapterTest.java
@@ -24,6 +24,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
*/
@AppServerContainer("app-server-wildfly")
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
-public class WildflyPhotozExampleAdapterTest extends AbstractPhotozExampleAdapterTest {
+public class WildflyPhotozExampleAdapterTest extends AbstractPhotozExampleNoLazyLoadPathsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleLazyLoadPathsAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleLazyLoadPathsAdapterTest.java
new file mode 100644
index 0000000..a23ba15
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleLazyLoadPathsAdapterTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyPhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleLazyLoadPathsAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
index 13a444f..7a7223d 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzAdapterTest.java
@@ -26,6 +26,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
@RunAsClient
@AppServerContainer("app-server-wildfly")
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
-public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzLazyLoadPathsAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzLazyLoadPathsAdapterTest.java
new file mode 100644
index 0000000..7964736
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzLazyLoadPathsAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletAuthzLazyLoadPathsAdapterTest extends AbstractServletAuthzLazyLoadPathsAdapterTest {
+
+}