From 4a425c2674cf968335fba4a50561f71dfcfb7a0b Mon Sep 17 00:00:00 2001 From: pedroigor Date: Thu, 22 Mar 2018 21:49:15 -0300 Subject: [PATCH] [KEYCLOAK-4102] - Support lazy loading of paths via policy enforcer config --- .../authorization/AbstractPolicyEnforcer.java | 106 +++++---- .../adapters/authorization/PathCache.java | 15 +- .../authorization/PolicyEnforcer.java | 111 ++++++--- .../ResourceRepresentation.java | 218 ------------------ .../representation/ScopeRepresentation.java | 98 -------- .../client/resource/ProtectedResource.java | 39 +++- .../keycloak/common/util}/PathMatcher.java | 121 +++++----- .../org/keycloak/AuthorizationContext.java | 48 ++-- .../adapters/config/PolicyEnforcerConfig.java | 42 +++- .../authorization/ResourceRepresentation.java | 39 +++- .../jpa/store/JPAResourceStore.java | 4 + .../models/utils/RepresentationToModel.java | 2 +- .../admin/ResourceSetService.java | 82 ++++--- .../protection/ProtectionService.java | 2 +- .../resource/RegistrationResponse.java | 50 ---- .../protection/resource/ResourceService.java | 119 +++------- .../resource/UmaResourceRepresentation.java | 72 ++++++ .../representation/RegistrationResponse.java | 50 ---- .../UmaResourceRepresentation.java | 176 -------------- .../UmaScopeRepresentation.java | 98 -------- .../exportimport/util/ExportUtils.java | 3 +- .../KeycloakOIDCClientInstallation.java | 1 + ...keycloak-lazy-load-path-authz-service.json | 78 +++++++ .../example/photoz/album/AlbumService.java | 4 +- .../src/main/webapp/WEB-INF/keycloak.json | 10 + .../keycloak-lazy-load-authz-service.json | 15 ++ .../src/main/webapp/WEB-INF/keycloak.json | 3 +- .../page/PhotozClientAuthzTestApp.java | 14 +- .../AbstractBaseServletAuthzAdapterTest.java | 211 +++++++++++++++++ .../AbstractPermissiveModeAdapterTest.java | 2 +- .../AbstractPhotozExampleAdapterTest.java | 12 +- ...PhotozExampleLazyLoadPathsAdapterTest.java | 88 +++++++ ...otozExampleNoLazyLoadPathsAdapterTest.java | 40 ++++ .../AbstractServletAuthzAdapterTest.java | 193 +--------------- ...ractServletAuthzFunctionalAdapterTest.java | 10 +- ...tServletAuthzLazyLoadPathsAdapterTest.java | 58 +++++ .../AbstractServletPolicyEnforcerTest.java | 8 +- ...ResourceManagementWithAuthzClientTest.java | 77 +++++-- .../authz/AuthzClientCredentialsTest.java | 2 +- .../authz/ConflictingScopePermissionTest.java | 4 +- .../WildflyPhotozExampleAdapterTest.java | 2 +- ...PhotozExampleLazyLoadPathsAdapterTest.java | 29 +++ .../WildflyServletAuthzAdapterTest.java | 2 +- ...yServletAuthzLazyLoadPathsAdapterTest.java | 31 +++ 44 files changed, 1132 insertions(+), 1257 deletions(-) delete mode 100644 authz/client/src/main/java/org/keycloak/authorization/client/representation/ResourceRepresentation.java delete mode 100644 authz/client/src/main/java/org/keycloak/authorization/client/representation/ScopeRepresentation.java rename {adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization => common/src/main/java/org/keycloak/common/util}/PathMatcher.java (63%) delete mode 100644 services/src/main/java/org/keycloak/authorization/protection/resource/RegistrationResponse.java create mode 100644 services/src/main/java/org/keycloak/authorization/protection/resource/UmaResourceRepresentation.java delete mode 100644 services/src/main/java/org/keycloak/authorization/protection/resource/representation/RegistrationResponse.java delete mode 100644 services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaResourceRepresentation.java delete mode 100644 services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaScopeRepresentation.java create mode 100644 testsuite/integration-arquillian/test-apps/photoz/keycloak-lazy-load-path-authz-service.json create mode 100644 testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-lazy-load-authz-service.json create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractBaseServletAuthzAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleLazyLoadPathsAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleNoLazyLoadPathsAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzLazyLoadPathsAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyPhotozExampleLazyLoadPathsAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzLazyLoadPathsAdapterTest.java 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 96fbe5d113..452583b393 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 static final String HTTP_METHOD_DELETE = "DELETE"; + private final PolicyEnforcer policyEnforcer; - private Map paths; - private AuthzClient authzClient; - private PathMatcher pathMatcher; - - 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 permissions = authorization.getPermissions(); boolean hasPermission = false; + List 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 e6992039c5..cf8815c6a9 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 Pedro Igor */ public class PathCache { @@ -39,15 +41,6 @@ public class PathCache { private final long maxAge; - /** - * 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. * @@ -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 ccd9452945..2d5f0cc37a 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 Pedro Igor @@ -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 getPathMatcher() { + return pathMatcher; } - KeycloakDeployment getDeployment() { + public KeycloakDeployment getDeployment() { return deployment; } @@ -144,7 +145,7 @@ public class PolicyEnforcer { } private Map configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) { - Map paths = Collections.synchronizedMap(new HashMap()); + Map paths = Collections.synchronizedMap(new LinkedHashMap()); for (PathConfig pathConfig : enforcerConfig.getPaths()) { ResourceRepresentation resource; @@ -197,45 +198,85 @@ public class PolicyEnforcer { LOGGER.info("Querying the server for all resources associated with this application."); Map paths = Collections.synchronizedMap(new HashMap()); - 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 createPathMatcher(final AuthzClient authzClient) { + final PathCache pathCache = new PathCache(100, 30000); - pathConfig.setId(resourceDescription.getId()); - pathConfig.setName(resourceDescription.getName()); + return new PathMatcher() { + @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 matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri); - List 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 getPaths() { + return paths.values(); + } + + @Override + protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) { + if (originalConfig.hasPattern()) { + ProtectedResource resource = authzClient.protection().resource(); + List 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/representation/ResourceRepresentation.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/ResourceRepresentation.java deleted file mode 100644 index fa2622e012..0000000000 --- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/ResourceRepresentation.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * JBoss, Home of Professional Open Source - * - * Copyright 2015 Red Hat, Inc. and/or its affiliates. - * - * 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.client.representation; - -import java.net.URI; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - *

One or more resources that the resource server manages as a set of protected resources. - * - *

For more details, OAuth-resource-reg. - * - * @author Pedro Igor - */ -public class ResourceRepresentation { - - @JsonProperty("_id") - private String id; - - private String name; - private String displayName; - private String uri; - private String type; - - @JsonProperty("resource_scopes") - private Set scopes; - - @JsonProperty("icon_uri") - private String iconUri; - private String owner; - private Boolean ownerManagedAccess; - private Map> attributes; - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param uri a {@link URI} that provides the network location for the resource set being registered - * @param type a string uniquely identifying the semantics of the resource set - * @param scopes the available scopes for this resource set - * @param iconUri a {@link URI} for a graphic icon representing the resource set - */ - public ResourceRepresentation(String name, Set scopes, String uri, String type, String iconUri) { - this.name = name; - this.scopes = scopes; - this.uri = uri; - this.type = type; - this.iconUri = iconUri; - } - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param uri a {@link URI} that provides the network location for the resource set being registered - * @param type a string uniquely identifying the semantics of the resource set - * @param scopes the available scopes for this resource set - */ - public ResourceRepresentation(String name, Set scopes, String uri, String type) { - this(name, scopes, uri, type, null); - } - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param serverUri a {@link URI} that identifies this resource server - * @param scopes the available scopes for this resource set - */ - public ResourceRepresentation(String name, Set scopes) { - this(name, scopes, null, null, null); - } - - /** - * Creates a new instance. - * - */ - public ResourceRepresentation() { - this(null, null, null, null, null); - } - - public void setId(String id) { - this.id = id; - } - - public String getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public String getDisplayName() { - return displayName; - } - - public String getUri() { - return this.uri; - } - - public String getType() { - return this.type; - } - - public Set getScopes() { - if (this.scopes == null) { - this.scopes = Collections.emptySet(); - } - return Collections.unmodifiableSet(this.scopes); - } - - public String getIconUri() { - return this.iconUri; - } - - public void setName(String name) { - this.name = name; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public void setUri(String uri) { - this.uri = uri; - } - - public void setType(String type) { - this.type = type; - } - - public void setScopes(Set scopes) { - this.scopes = scopes; - } - - public void setIconUri(String iconUri) { - this.iconUri = iconUri; - } - - public String getOwner() { - return owner; - } - - public void setOwner(String owner) { - this.owner = owner; - } - - public void setOwnerManagedAccess(Boolean ownerManagedAccess) { - this.ownerManagedAccess = ownerManagedAccess; - } - - public Boolean getOwnerManagedAccess() { - return ownerManagedAccess; - } - - public void addScope(ScopeRepresentation scopeRepresentation) { - if (this.scopes == null) { - this.scopes = new HashSet<>(); - } - this.scopes.add(scopeRepresentation); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ResourceRepresentation that = (ResourceRepresentation) o; - return Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public String toString() { - return "ResourceRepresentation{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", uri='" + uri + '\'' + - ", type='" + type + '\'' + - ", owner='" + owner + '\'' + - ", scopes=" + scopes + - '}'; - } - - public void setAttributes(Map> attributes) { - this.attributes = attributes; - } - - public Map> getAttributes() { - return attributes; - } -} diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/ScopeRepresentation.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/ScopeRepresentation.java deleted file mode 100644 index 4fcfc35908..0000000000 --- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/ScopeRepresentation.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * JBoss, Home of Professional Open Source - * - * Copyright 2015 Red Hat, Inc. and/or its affiliates. - * - * 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.client.representation; - -import java.net.URI; -import java.util.Objects; - -/** - *

A bounded extent of access that is possible to perform on a resource set. In authorization policy terminology, - * a scope is one of the potentially many "verbs" that can logically apply to a resource set ("object"). - * - *

For more details, OAuth-resource-reg. - * - * @author Pedro Igor - */ -public class ScopeRepresentation { - - private String id; - private String name; - private String iconUri; - - /** - * Creates an instance. - * - * @param name the a human-readable string describing some scope (extent) of access - * @param iconUri a {@link URI} for a graphic icon representing the scope - */ - public ScopeRepresentation(String name, String iconUri) { - this.name = name; - this.iconUri = iconUri; - } - - /** - * Creates an instance. - * - * @param name the a human-readable string describing some scope (extent) of access - */ - public ScopeRepresentation(String name) { - this(name, null); - } - - /** - * Creates an instance. - */ - public ScopeRepresentation() { - this(null, null); - } - - public String getName() { - return this.name; - } - - public String getIconUri() { - return this.iconUri; - } - - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ScopeRepresentation scope = (ScopeRepresentation) o; - return Objects.equals(getName(), scope.getName()); - } - - public int hashCode() { - return Objects.hash(getName()); - } - - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } - - public void setIconUri(String iconUri) { - this.iconUri = iconUri; - } -} \ No newline at end of file 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 cf2e91a5f6..80c9e42727 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 name 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 callable = new Callable() { @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 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 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 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/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java b/common/src/main/java/org/keycloak/common/util/PathMatcher.java similarity index 63% rename from adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java rename to common/src/main/java/org/keycloak/common/util/PathMatcher.java index 8bec8408fd..d56c408cb9 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java +++ b/common/src/main/java/org/keycloak/common/util/PathMatcher.java @@ -1,57 +1,44 @@ /* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. + * 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 + * 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. + * 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.adapters.authorization; +package org.keycloak.common.util; import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.keycloak.authorization.client.AuthzClient; -import org.keycloak.authorization.client.representation.ResourceRepresentation; -import org.keycloak.authorization.client.resource.ProtectedResource; -import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; +import java.util.Collection; /** * @author Pedro Igor */ -class PathMatcher { +public abstract class PathMatcher

{ private static final char WILDCARD = '*'; - private final AuthzClient authzClient; - // TODO: make this configurable - private PathCache cache = new PathCache(100, 30000); - public PathMatcher(AuthzClient authzClient) { - this.authzClient = authzClient; - } + public P matches(final String targetUri) { + int patternCount = 0; + P matchingPath = null; + P matchingAnyPath = null; + P matchingAnySuffixPath = null; - public PathConfig matches(final String targetUri, Map paths) { - PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri); + for (P entry : getPaths()) { + String expectedUri = getPath(entry); - if (pathConfig != null) { - return pathConfig; - } + if (expectedUri == null) { + continue; + } - PathConfig matchingAnyPath = null; - PathConfig matchingAnySuffixPath = null; - - for (PathConfig entry : paths.values()) { - String expectedUri = entry.getPath(); String matchingUri = null; if (exactMatch(expectedUri, targetUri, expectedUri)) { @@ -62,9 +49,17 @@ class PathMatcher { String templateUri = buildUriFromTemplate(expectedUri, targetUri); if (templateUri != null) { - if (exactMatch(expectedUri, targetUri, templateUri)) { + int length = expectedUri.split("\\/").length; + + if (exactMatch(expectedUri, targetUri, templateUri) && (patternCount == 0 || length > patternCount)) { matchingUri = templateUri; - entry = resolvePathConfig(entry, targetUri); + P resolved = resolvePathConfig(entry, targetUri); + + if (resolved != null) { + entry = resolved; + } + + patternCount = length; } } } @@ -90,12 +85,15 @@ class PathMatcher { } if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) { - cache.put(targetUri, entry); - return entry; + if (patternCount == 0) { + return entry; + } else { + matchingPath = entry; + } } if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) { - if (matchingAnyPath == null || matchingAnyPath.getPath().length() < matchingUri.length()) { + if (matchingAnyPath == null || getPath(matchingAnyPath).length() < matchingUri.length()) { matchingAnyPath = entry; } } else { @@ -112,18 +110,21 @@ class PathMatcher { } } - if (matchingAnySuffixPath != null) { - cache.put(targetUri, matchingAnySuffixPath); - return matchingAnySuffixPath; + if (matchingPath != null) { + return matchingPath; } - if (matchingAnyPath != null) { - cache.put(targetUri, matchingAnyPath); + if (matchingAnySuffixPath != null) { + return matchingAnySuffixPath; } return matchingAnyPath; } + protected abstract String getPath(P entry); + + protected abstract Collection

getPaths(); + private boolean exactMatch(String expectedUri, String targetUri, String value) { if (targetUri.equals(value)) { return value.equals(targetUri); @@ -213,32 +214,16 @@ class PathMatcher { } public boolean endsWithWildcard(String expectedUri) { - return WILDCARD == expectedUri.charAt(expectedUri.length() - 1); + int length = expectedUri.length(); + return length > 0 && WILDCARD == expectedUri.charAt(length - 1); } private boolean isTemplate(String uri) { return uri.indexOf("{") != -1; } - private PathConfig resolvePathConfig(PathConfig originalConfig, String path) { - if (originalConfig.hasPattern()) { - ProtectedResource resource = this.authzClient.protection().resource(); - List search = resource.findByUri(path); - - if (!search.isEmpty()) { - // resource does exist on the server, cache it - ResourceRepresentation targetResource = search.get(0); - PathConfig config = PolicyEnforcer.createPathConfig(targetResource); - - config.setScopes(originalConfig.getScopes()); - config.setMethods(originalConfig.getMethods()); - config.setParentConfig(originalConfig); - config.setEnforcementMode(originalConfig.getEnforcementMode()); - - return config; - } - } - - return originalConfig; + protected P resolvePathConfig(P entry, String path) { + return entry; } } + diff --git a/core/src/main/java/org/keycloak/AuthorizationContext.java b/core/src/main/java/org/keycloak/AuthorizationContext.java index 0a9b33259c..538a70f51b 100644 --- a/core/src/main/java/org/keycloak/AuthorizationContext.java +++ b/core/src/main/java/org/keycloak/AuthorizationContext.java @@ -59,21 +59,21 @@ 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 (permission.getScopes().contains(scopeName)) { + 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 (current != null) { + if (current.getName().equals(resourceName)) { + return true; } } @@ -81,29 +81,7 @@ public class AuthorizationContext { } 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; + return hasPermission(resourceName, null); } public boolean hasScopePermission(String scopeName) { 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 46189f6111..71b44a4cdb 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,6 +23,8 @@ 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 Pedro Igor @@ -36,6 +38,9 @@ public class PolicyEnforcerConfig { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List 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,6 +53,14 @@ public class PolicyEnforcerConfig { return this.paths; } + public Boolean getLazyLoadPaths() { + return lazyLoadPaths; + } + + public void setLazyLoadPaths(Boolean lazyLoadPaths) { + this.lazyLoadPaths = lazyLoadPaths; + } + public EnforcementMode getEnforcementMode() { return this.enforcementMode; } @@ -78,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 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; @@ -219,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 071bc324dc..92f471758c 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 scopes; @JsonProperty("icon_uri") @@ -52,9 +55,6 @@ public class ResourceRepresentation { private ResourceOwnerRepresentation owner; private Boolean ownerManagedAccess; - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List 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 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 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 0c82dc01de..b9cecf7056 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 538dcef4b7..06545a234c 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 b69d9910b0..edde9ccd36 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) resource1 -> { - ResourceRepresentation representation = new ResourceRepresentation(); + if (resource == null) { + return Response.status(Status.BAD_REQUEST).build(); + } - representation.setId(resource1.getId()); + ResourceRepresentation newResource = create(resource); - return representation; - }); - } - - public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource, Function toRepresentation) { - Response response = create(resource, toRepresentation); audit(uriInfo, resource, resource.getId(), OperationType.CREATE); - return response; + + return Response.status(Status.CREATED).entity(newResource).build(); } - public Response create(ResourceRepresentation resource) { - return create(resource, (Function) resource1 -> { - ResourceRepresentation representation = new ResourceRepresentation(); - - representation.setId(resource1.getId()); - - return representation; - }); - } - - public Response create(ResourceRepresentation resource, Function 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 -> toRepresentation(resource, resourceServer, authorization, true)); + return findById(id, resource -> toRepresentation(resource, resourceServer, authorization, true)); } - public Response findById(@PathParam("id") String id, Function toRepresentation) { + public Response findById(String id, Function 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, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1)); + return find(id, name, uri, owner, type, scope, matchingUri, deep, firstResult, maxResult, (BiFunction) (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 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 attributes = new HashMap<>(); + + attributes.put("uri_not_null", new String[] {"true"}); + attributes.put("owner", new String[] {resourceServer.getId()}); + + List serverResources = storeFactory.getResourceStore().findByResourceServer(attributes, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS); + PathMatcher pathMatcher = new PathMatcher() { + @Override + protected String getPath(Resource entry) { + return entry.getUri(); + } + + @Override + protected Collection 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 8a811f149c..80226cff5a 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/RegistrationResponse.java b/services/src/main/java/org/keycloak/authorization/protection/resource/RegistrationResponse.java deleted file mode 100644 index 4f82242951..0000000000 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/RegistrationResponse.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual 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 com.fasterxml.jackson.annotation.JsonUnwrapped; -import org.keycloak.authorization.protection.resource.representation.UmaResourceRepresentation; - -/** - * @author Pedro Igor - */ -public class RegistrationResponse { - - private final UmaResourceRepresentation resourceDescription; - - public RegistrationResponse(UmaResourceRepresentation resourceDescription) { - this.resourceDescription = resourceDescription; - } - - public RegistrationResponse() { - this(null); - } - - @JsonUnwrapped - public UmaResourceRepresentation getResourceDescription() { - return this.resourceDescription; - } - - public String getId() { - if (this.resourceDescription != null) { - return this.resourceDescription.getId(); - } - - return null; - } -} 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 21fd27ca41..1fbe5f9363 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) 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 -> 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, 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, 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 0000000000..302dd96b86 --- /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 Pedro Igor + */ +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 getScopes() { + return super.getScopes(); + } + + @JsonProperty("resource_scopes") + @Override + public void setScopes(Set scopes) { + super.setScopes(scopes); + } +} diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/RegistrationResponse.java b/services/src/main/java/org/keycloak/authorization/protection/resource/representation/RegistrationResponse.java deleted file mode 100644 index 6922d51f7b..0000000000 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/RegistrationResponse.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual 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.representation; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; - -/** - * @author Pedro Igor - */ -public class RegistrationResponse { - - private final UmaResourceRepresentation resourceDescription; - - public RegistrationResponse(UmaResourceRepresentation resourceDescription) { - this.resourceDescription = resourceDescription; - } - - public RegistrationResponse() { - this(null); - } - - @JsonUnwrapped - public UmaResourceRepresentation getResourceDescription() { - return this.resourceDescription; - } - - public String getId() { - if (this.resourceDescription != null) { - return this.resourceDescription.getId(); - } - - return null; - } -} diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaResourceRepresentation.java b/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaResourceRepresentation.java deleted file mode 100644 index 1ac4608768..0000000000 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaResourceRepresentation.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual 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.representation; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - *

One or more resources that the resource server manages as a set of protected resources. - * - *

For more details, OAuth-resource-reg. - * - * @author Pedro Igor - */ -public class UmaResourceRepresentation { - - @JsonProperty("_id") - private String id; - - private String name; - private String uri; - private String type; - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - @JsonProperty("resource_scopes") - private Set scopes; - - @JsonProperty("icon_uri") - private String iconUri; - private String owner; - private Boolean ownerManagedAccess; - - private Map> attributes; - - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param uri a {@link URI} that provides the network location for the resource set being registered - * @param type a string uniquely identifying the semantics of the resource set - * @param scopes the available scopes for this resource set - * @param iconUri a {@link URI} for a graphic icon representing the resource set - */ - public UmaResourceRepresentation(String name, Set scopes, String uri, String type, String iconUri) { - this.name = name; - this.scopes = scopes; - this.uri = uri; - this.type = type; - this.iconUri = iconUri; - } - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param uri a {@link URI} that provides the network location for the resource set being registered - * @param type a string uniquely identifying the semantics of the resource set - * @param scopes the available scopes for this resource set - */ - public UmaResourceRepresentation(String name, Set scopes, String uri, String type) { - this(name, scopes, uri, type, null); - } - - /** - * Creates a new instance. - * - * @param name a human-readable string describing a set of one or more resources - * @param serverUri a {@link URI} that identifies this resource server - * @param scopes the available scopes for this resource set - */ - public UmaResourceRepresentation(String name, Set scopes) { - this(name, scopes, null, null, null); - } - - /** - * Creates a new instance. - * - */ - public UmaResourceRepresentation() { - this(null, null, null, null, null); - } - - public void setId(String id) { - this.id = id; - } - - public String getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public String getUri() { - return this.uri; - } - - public String getType() { - return this.type; - } - - public Set getScopes() { - return Collections.unmodifiableSet(this.scopes); - } - - public String getIconUri() { - return this.iconUri; - } - - public void setName(String name) { - this.name = name; - } - - public void setUri(String uri) { - this.uri = uri; - } - - public void setType(String type) { - this.type = type; - } - - public void setScopes(Set scopes) { - this.scopes = scopes; - } - - public void setIconUri(String iconUri) { - this.iconUri = iconUri; - } - - public String getOwner() { - return owner; - } - - public void setOwner(String owner) { - this.owner = owner; - } - - public void setOwnerManagedAccess(Boolean ownerManagedAccess) { - this.ownerManagedAccess = ownerManagedAccess; - } - - public Boolean getOwnerManagedAccess() { - return ownerManagedAccess; - } - - public Map> getAttributes() { - return attributes; - } - - public void setAttributes(Map> attributes) { - this.attributes = attributes; - } -} diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaScopeRepresentation.java b/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaScopeRepresentation.java deleted file mode 100644 index 4a184d9dae..0000000000 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/representation/UmaScopeRepresentation.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual 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.representation; - -import java.net.URI; -import java.util.Objects; - -/** - *

A bounded extent of access that is possible to perform on a resource set. In authorization policy terminology, - * a scope is one of the potentially many "verbs" that can logically apply to a resource set ("object"). - * - *

For more details, OAuth-resource-reg. - * - * @author Pedro Igor - */ -public class UmaScopeRepresentation { - - private String id; - private String name; - private String iconUri; - - /** - * Creates an instance. - * - * @param name the a human-readable string describing some scope (extent) of access - * @param iconUri a {@link URI} for a graphic icon representing the scope - */ - public UmaScopeRepresentation(String name, String iconUri) { - this.name = name; - this.iconUri = iconUri; - } - - /** - * Creates an instance. - * - * @param name the a human-readable string describing some scope (extent) of access - */ - public UmaScopeRepresentation(String name) { - this(name, null); - } - - /** - * Creates an instance. - */ - public UmaScopeRepresentation() { - this(null, null); - } - - public String getName() { - return this.name; - } - - public String getIconUri() { - return this.iconUri; - } - - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UmaScopeRepresentation scope = (UmaScopeRepresentation) o; - return Objects.equals(getName(), scope.getName()); - } - - public int hashCode() { - return Objects.hash(getName()); - } - - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } - - public void setIconUri(String iconUri) { - this.iconUri = iconUri; - } -} \ No newline at end of file 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 f6beac505b..a84765b61e 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 d0b7de6d00..8dadaafc18 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,6 +155,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig(); enforcerConfig.setEnforcementMode(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 0000000000..47437dcbba --- /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 94feb72654..40b12422ca 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 a0f8711eea..dd0cab1abf 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 @@ -19,6 +19,16 @@ "enforcement-mode": "PERMISSIVE", "user-managed-access": {}, "paths": [ + { + "name" : "Album Resource", + "path" : "/album", + "methods" : [ + { + "method": "GET", + "scopes-enforcement-mode" : "DISABLED" + } + ] + }, { "name" : "Album Resource", "path" : "/album/{id}", 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 0000000000..35f76d2726 --- /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 0dd6a14f3a..d6e7f000de 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 f87d481a37..90a6692c4d 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 0000000000..7f47bb988a --- /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 Pedro Igor + */ +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 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 e1ad409350..052150cc8c 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 Pedro Igor */ -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 4cc9d4a606..80ddd057a8 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 0000000000..3e35a33c18 --- /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 Pedro Igor + */ +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/AbstractPhotozExampleNoLazyLoadPathsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleNoLazyLoadPathsAdapterTest.java new file mode 100644 index 0000000000..466029f688 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleNoLazyLoadPathsAdapterTest.java @@ -0,0 +1,40 @@ +/* + * 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 java.io.IOException; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp; + +/** + * @author Pedro Igor + */ +public abstract class AbstractPhotozExampleNoLazyLoadPathsAdapterTest 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); + } + +} \ 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 f6a8bb247d..71297a9391 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 Pedro Igor */ -public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest { +public abstract class AbstractServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest { - 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 testRealms) { - testRealms.add( - loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json"))); + @Deployment(name = RESOURCE_SERVER_ID, managed = false) + public static WebArchive deployment() throws IOException { + return exampleDeployment(RESOURCE_SERVER_ID); } - 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/AbstractServletAuthzFunctionalAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java index 63852d0f5e..35c936b804 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 Pedro Igor */ -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 0000000000..0989aa5faa --- /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 Pedro Igor + */ +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 5c6b0ebcc4..9afed016cc 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 32865ec30c..bbff687b9b 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; - @Override - protected ResourceRepresentation doCreateResource(ResourceRepresentation newResource) { - org.keycloak.authorization.client.representation.ResourceRepresentation resource = toResourceRepresentation(newResource); + @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(); - org.keycloak.authorization.client.representation.ResourceRepresentation response = authzClient.protection().resource().create(resource); + + List 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) { + ResourceRepresentation resource = toResourceRepresentation(newResource); + + AuthzClient authzClient = getAuthzClient(); + 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 7fece477c6..63726b50c2 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 b55f348270..7393a94ef4 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 42cde44ab3..6139c22f59 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 0000000000..a23ba15c31 --- /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 Pedro Igor + */ +@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 13a444f96e..7a7223db47 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 0000000000..7964736793 --- /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 { + +}