[KEYCLOAK-4102] - Support lazy loading of paths via policy enforcer config

This commit is contained in:
pedroigor 2018-03-22 21:49:15 -03:00
parent e9e376419d
commit 4a425c2674
44 changed files with 1132 additions and 1257 deletions

View file

@ -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<String, PathConfig> 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<Permission> permissions = authorization.getPermissions();
boolean hasPermission = false;
List<Permission> grantedPermissions = authorization.getPermissions();
for (Permission permission : permissions) {
for (Permission permission : grantedPermissions) {
if (permission.getResourceId() != null) {
if (isResourcePermission(actualPathConfig, permission)) {
hasPermission = true;
@ -151,9 +155,11 @@ public abstract class AbstractPolicyEnforcer {
}
if (hasResourceScopePermission(methodConfig, permission)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
this.paths.remove(actualPathConfig);
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
}
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
policyEnforcer.getPaths().remove(actualPathConfig);
}
return true;
}
@ -170,7 +176,9 @@ public abstract class AbstractPolicyEnforcer {
return true;
}
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, permissions);
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
}
return false;
}
@ -179,15 +187,21 @@ public abstract class AbstractPolicyEnforcer {
httpFacade.getResponse().sendError(403);
}
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
protected AuthzClient getAuthzClient() {
return policyEnforcer.getClient();
}
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
return false;
protected PolicyEnforcerConfig getEnforcerConfig() {
return policyEnforcer.getEnforcerConfig();
}
protected PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer;
}
private boolean isDefaultAccessDeniedUri(Request request) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
}
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
@ -215,20 +229,8 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes.isEmpty();
}
protected AuthzClient getAuthzClient() {
return this.authzClient;
}
protected PolicyEnforcerConfig getEnforcerConfig() {
return enforcerConfig;
}
protected PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer;
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(authzClient) {
return new ClientAuthorizationContext(getAuthzClient()) {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@ -279,7 +281,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
return new ClientAuthorizationContext(accessToken, pathConfig, policyEnforcer.getPaths(), getAuthzClient());
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
@ -297,4 +299,8 @@ public abstract class AbstractPolicyEnforcer {
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
return permission.getResourceId().equals(actualPathConfig.getId());
}
private PathConfig getPathConfig(Request request) {
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
}
}

View file

@ -24,6 +24,8 @@ import java.util.concurrent.locks.LockSupport;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
* A simple LRU cache implementation supporting expiration and maximum number of entries.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PathCache {
@ -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;

View file

@ -17,11 +17,11 @@
*/
package org.keycloak.adapters.authorization;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -34,13 +34,13 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.common.util.PathMatcher;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -69,8 +69,9 @@ public class PolicyEnforcer {
}
}
});
this.pathMatcher = new PathMatcher(this.authzClient);
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
this.pathMatcher = createPathMatcher(authzClient);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:");
@ -104,11 +105,11 @@ public class PolicyEnforcer {
return context;
}
PolicyEnforcerConfig getEnforcerConfig() {
public PolicyEnforcerConfig getEnforcerConfig() {
return enforcerConfig;
}
AuthzClient getClient() {
public AuthzClient getClient() {
return authzClient;
}
@ -116,11 +117,11 @@ public class PolicyEnforcer {
return paths;
}
void addPath(PathConfig pathConfig) {
paths.put(pathConfig.getPath(), pathConfig);
public PathMatcher<PathConfig> getPathMatcher() {
return pathMatcher;
}
KeycloakDeployment getDeployment() {
public KeycloakDeployment getDeployment() {
return deployment;
}
@ -144,7 +145,7 @@ public class PolicyEnforcer {
}
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
ResourceRepresentation resource;
@ -197,45 +198,85 @@ public class PolicyEnforcer {
LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
for (String id : protectedResource.findAll()) {
ResourceRepresentation resourceDescription = protectedResource.findById(id);
if (!enforcerConfig.getLazyLoadPaths()) {
for (String id : protectedResource.findAll()) {
ResourceRepresentation resourceDescription = protectedResource.findById(id);
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = createPathConfig(resourceDescription);
paths.put(pathConfig.getPath(), pathConfig);
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
paths.put(pathConfig.getPath(), pathConfig);
}
}
}
return paths;
}
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
PathConfig pathConfig = new PathConfig();
private PathMatcher<PathConfig> createPathMatcher(final AuthzClient authzClient) {
final PathCache pathCache = new PathCache(100, 30000);
pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName());
return new PathMatcher<PathConfig>() {
@Override
public PathConfig matches(String targetUri) {
PathConfig pathConfig = pathCache.get(targetUri);
String uri = resourceDescription.getUri();
if (pathCache.containsKey(targetUri) || pathConfig != null) {
return pathConfig;
}
if (uri == null || "".equals(uri.trim())) {
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
}
pathConfig = super.matches(targetUri);
pathConfig.setPath(uri);
if (enforcerConfig.getLazyLoadPaths() && (pathConfig == null || pathConfig.getPath().contains("*"))) {
try {
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
List<String> scopeNames = new ArrayList<>();
if (!matchingResources.isEmpty()) {
pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
paths.put(pathConfig.getPath(), pathConfig);
}
} catch (Exception cause) {
LOGGER.errorf(cause, "Could not lazy load paths from server");
return null;
}
}
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
scopeNames.add(scope.getName());
}
pathCache.put(targetUri, pathConfig);
pathConfig.setScopes(scopeNames);
pathConfig.setType(resourceDescription.getType());
return pathConfig;
}
return pathConfig;
}
@Override
protected String getPath(PathConfig entry) {
return entry.getPath();
}
public PathMatcher getPathMatcher() {
return pathMatcher;
@Override
protected Collection<PathConfig> getPaths() {
return paths.values();
}
@Override
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
if (originalConfig.hasPattern()) {
ProtectedResource resource = authzClient.protection().resource();
List<ResourceRepresentation> search = resource.findByUri(path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = search.get(0);
PathConfig config = PathConfig.createPathConfig(targetResource);
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
return config;
}
}
return null;
}
};
}
}

View file

@ -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;
/**
* <p>One or more resources that the resource server manages as a set of protected resources.
*
* <p>For more details, <a href="https://docs.kantarainitiative.org/uma/draft-oauth-resource-reg.html#rfc.section.2.2">OAuth-resource-reg</a>.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
private String iconUri;
private String owner;
private Boolean ownerManagedAccess;
private Map<String, List<String>> 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<ScopeRepresentation> 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<ScopeRepresentation> 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<ScopeRepresentation> 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<ScopeRepresentation> 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<ScopeRepresentation> 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<String, List<String>> attributes) {
this.attributes = attributes;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
}

View file

@ -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;
/**
* <p>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").
*
* <p>For more details, <a href="https://docs.kantarainitiative.org/uma/draft-oauth-resource-reg.html#rfc.section.2.1">OAuth-resource-reg</a>.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

@ -23,11 +23,11 @@ import java.util.List;
import java.util.concurrent.Callable;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ServerConfiguration;
import org.keycloak.authorization.client.util.Http;
import org.keycloak.authorization.client.util.Throwables;
import org.keycloak.authorization.client.util.TokenCallable;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.util.JsonSerialization;
/**
@ -124,11 +124,11 @@ public class ProtectedResource {
/**
* Query the server for a resource given its <code>name</code> where the owner is the resource server itself.
*
* @param id the resource name
* @param name the resource name
* @return a {@link ResourceRepresentation}
*/
public ResourceRepresentation findByName(String name) {
String[] representations = find(null, name, null, configuration.getResource(), null, null, null, null);
String[] representations = find(null, name, null, configuration.getResource(), null, null, false, null, null);
if (representations.length == 0) {
return null;
@ -145,7 +145,7 @@ public class ProtectedResource {
* @return a {@link ResourceRepresentation}
*/
public ResourceRepresentation findByName(String name, String ownerId) {
String[] representations = find(null, name, null, ownerId, null, null, null, null);
String[] representations = find(null, name, null, ownerId, null, null, false, null, null);
if (representations.length == 0) {
return null;
@ -163,11 +163,12 @@ public class ProtectedResource {
* @param owner the resource owner
* @param type the resource type
* @param scope the resource scope
* @param matchingUri the resource uri. Use this parameter to lookup a resource that best match the given uri
* @param firstResult the position of the first resource to retrieve
* @param maxResult the maximum number of resources to retrieve
* @return an array of strings with the resource ids
*/
public String[] find(final String id, final String name, final String uri, final String owner, final String type, final String scope, final Integer firstResult, final Integer maxResult) {
public String[] find(final String id, final String name, final String uri, final String owner, final String type, final String scope, final boolean matchingUri, final Integer firstResult, final Integer maxResult) {
Callable<String[]> callable = new Callable<String[]>() {
@Override
public String[] call() throws Exception {
@ -179,6 +180,7 @@ public class ProtectedResource {
.param("owner", owner)
.param("type", type)
.param("scope", scope)
.param("matchingUri", Boolean.valueOf(matchingUri).toString())
.param("deep", Boolean.FALSE.toString())
.param("first", firstResult != null ? firstResult.toString() : null)
.param("max", maxResult != null ? maxResult.toString() : null)
@ -199,7 +201,7 @@ public class ProtectedResource {
*/
public String[] findAll() {
try {
return find(null,null , null, null, null, null, null, null);
return find(null,null , null, null, null, null, false, null, null);
} catch (Exception cause) {
throw Throwables.handleWrapException("Could not find resource", cause);
}
@ -233,7 +235,30 @@ public class ProtectedResource {
* @param uri the resource uri
*/
public List<ResourceRepresentation> findByUri(String uri) {
String[] ids = find(null, null, uri, null, null, null, null, null);
String[] ids = find(null, null, uri, null, null, null, false, null, null);
if (ids.length == 0) {
return Collections.emptyList();
}
List<ResourceRepresentation> representations = new ArrayList<>();
for (String id : ids) {
representations.add(findById(id));
}
return representations;
}
/**
* Returns a list of resources that best matches the given {@code uri}. This method queries the server for resources whose
* {@link ResourceRepresentation#uri} best matches the given {@code uri}.
*
* @param uri the resource uri to match
* @return a list of resources
*/
public List<ResourceRepresentation> findByMatchingUri(String uri) {
String[] ids = find(null, null, uri, null, null, null, true, null, null);
if (ids.length == 0) {
return Collections.emptyList();

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class PathMatcher {
public abstract class PathMatcher<P> {
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<String, PathConfig> 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<P> 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<ResourceRepresentation> 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;
}
}

View file

@ -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) {

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -36,6 +38,9 @@ public class PolicyEnforcerConfig {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PathConfig> paths = new ArrayList<>();
@JsonProperty("lazy-load-paths")
private Boolean lazyLoadPaths = Boolean.FALSE;
@JsonProperty("on-deny-redirect-to")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String onDenyRedirectTo;
@ -48,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<String> scopeNames = new ArrayList<>();
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
scopeNames.add(scope.getName());
}
pathConfig.setScopes(scopeNames);
pathConfig.setType(resourceDescription.getType());
return pathConfig;
}
private String name;
private String type;
private String path;
@ -219,7 +258,8 @@ public class PolicyEnforcerConfig {
public enum ScopeEnforcementMode {
ALL,
ANY
ANY,
DISABLED
}
public static class UserManagedAccessConfig {

View file

@ -24,8 +24,10 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.keycloak.json.StringListMapDeserializer;
@ -45,6 +47,7 @@ public class ResourceRepresentation {
private String uri;
private String type;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonProperty("scopes")
private Set<ScopeRepresentation> scopes;
@JsonProperty("icon_uri")
@ -52,9 +55,6 @@ public class ResourceRepresentation {
private ResourceOwnerRepresentation owner;
private Boolean ownerManagedAccess;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<PolicyRepresentation> policies;
private String displayName;
@JsonDeserialize(using = StringListMapDeserializer.class)
@ -162,17 +162,31 @@ public class ResourceRepresentation {
}
public void setUri(String uri) {
this.uri = uri;
if (uri != null && !"".equalsIgnoreCase(uri.trim())) {
this.uri = uri;
}
}
public void setType(String type) {
this.type = type;
if (type != null && !"".equalsIgnoreCase(type.trim())) {
this.type = type;
}
}
public void setScopes(Set<ScopeRepresentation> scopes) {
this.scopes = scopes;
}
/**
* TODO: This is a workaround to allow deserialization of UMA resource representation. Jackson 2.19+ support aliases, once we upgrade, change this.
*
* @param scopes
*/
@JsonSetter("resource_scopes")
private void setScopesUma(Set<ScopeRepresentation> scopes) {
this.scopes = scopes;
}
public void setIconUri(String iconUri) {
this.iconUri = iconUri;
}
@ -181,10 +195,25 @@ public class ResourceRepresentation {
return this.owner;
}
@JsonProperty
public void setOwner(ResourceOwnerRepresentation owner) {
this.owner = owner;
}
@JsonIgnore
public void setOwner(String ownerId) {
if (ownerId == null) {
owner = null;
return;
}
if (owner == null) {
owner = new ResourceOwnerRepresentation();
}
owner.setId(ownerId);
}
public Boolean getOwnerManagedAccess() {
return ownerManagedAccess;
}

View file

@ -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() + "%"));
}

View file

@ -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)) {

View file

@ -21,6 +21,8 @@ import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
import static org.keycloak.models.utils.RepresentationToModel.toModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -46,6 +48,7 @@ import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@ -54,6 +57,7 @@ import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.util.PathMatcher;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
@ -64,7 +68,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
@ -90,32 +94,18 @@ public class ResourceSetService {
@Consumes("application/json")
@Produces("application/json")
public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
return create(uriInfo, resource, (Function<Resource, ResourceRepresentation>) resource1 -> {
ResourceRepresentation representation = new ResourceRepresentation();
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<Resource, ?> 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<Resource, ResourceRepresentation>) resource1 -> {
ResourceRepresentation representation = new ResourceRepresentation();
representation.setId(resource1.getId());
return representation;
});
}
public Response create(ResourceRepresentation resource, Function<Resource, ?> toRepresentation) {
public ResourceRepresentation create(ResourceRepresentation resource) {
requireManage();
StoreFactory storeFactory = this.authorization.getStoreFactory();
ResourceOwnerRepresentation owner = resource.getOwner();
@ -123,21 +113,22 @@ public class ResourceSetService {
if (owner == null) {
owner = new ResourceOwnerRepresentation();
owner.setId(resourceServer.getId());
resource.setOwner(owner);
}
String ownerId = owner.getId();
if (ownerId == null) {
return ErrorResponse.error("You must specify the resource owner.", Status.BAD_REQUEST);
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "You must specify the resource owner.", Status.BAD_REQUEST);
}
Resource existingResource = storeFactory.getResourceStore().findByName(resource.getName(), ownerId, this.resourceServer.getId());
if (existingResource != null) {
return ErrorResponse.exists("Resource with name [" + resource.getName() + "] already exists.");
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Resource with name [" + resource.getName() + "] already exists.", Status.CONFLICT);
}
return Response.status(Status.CREATED).entity(toRepresentation.apply(toModel(resource, this.resourceServer, authorization))).build();
return toRepresentation(toModel(resource, this.resourceServer, authorization), resourceServer, authorization);
}
@Path("{id}")
@ -198,10 +189,10 @@ public class ResourceSetService {
@NoCache
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
return findById(id, (Function<Resource, ResourceRepresentation>) resource -> toRepresentation(resource, resourceServer, authorization, true));
return findById(id, resource -> toRepresentation(resource, resourceServer, authorization, true));
}
public Response findById(@PathParam("id") String id, Function<Resource, ?> toRepresentation) {
public Response findById(String id, Function<Resource, ? extends ResourceRepresentation> toRepresentation) {
requireView();
StoreFactory storeFactory = authorization.getStoreFactory();
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
@ -340,10 +331,11 @@ public class ResourceSetService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
@QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
return find(id, name, uri, owner, type, scope, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, ResourceRepresentation>) (resource, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1));
return find(id, name, uri, owner, type, scope, matchingUri, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, ResourceRepresentation>) (resource, deep1) -> toRepresentation(resource, resourceServer, authorization, deep1));
}
public Response find(@QueryParam("_id") String id,
@ -352,6 +344,7 @@ public class ResourceSetService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
@QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult,
@ -413,9 +406,38 @@ public class ResourceSetService {
search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new));
}
List<Resource> resources = storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS);
if (matchingUri != null && matchingUri && resources.isEmpty()) {
HashMap<String, String[]> attributes = new HashMap<>();
attributes.put("uri_not_null", new String[] {"true"});
attributes.put("owner", new String[] {resourceServer.getId()});
List<Resource> serverResources = storeFactory.getResourceStore().findByResourceServer(attributes, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS);
PathMatcher<Resource> pathMatcher = new PathMatcher<Resource>() {
@Override
protected String getPath(Resource entry) {
return entry.getUri();
}
@Override
protected Collection<Resource> getPaths() {
return serverResources;
}
};
Resource matches = pathMatcher.matches(uri);
if (matches != null) {
resources = Arrays.asList(matches);
}
}
Boolean finalDeep = deep;
return Response.ok(
storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream()
resources.stream()
.map(resource -> toRepresentation.apply(resource, finalDeep))
.collect(Collectors.toList()))
.build();
@ -437,7 +459,7 @@ public class ResourceSetService {
audit(uriInfo, resource, null, operation);
}
private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
public void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
if (authorization.getRealm().isAdminEventsEnabled()) {
if (id != null) {
adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();

View file

@ -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);

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

@ -18,8 +18,6 @@
package org.keycloak.authorization.protection.resource;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@ -36,16 +34,13 @@ import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.ResourceSetService;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.protection.resource.representation.UmaResourceRepresentation;
import org.keycloak.authorization.protection.resource.representation.UmaScopeRepresentation;
import org.keycloak.events.admin.OperationType;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
/**
@ -56,39 +51,51 @@ public class ResourceService {
private final ResourceServer resourceServer;
private final ResourceSetService resourceManager;
private final Identity identity;
private final AuthorizationProvider authorization;
public ResourceService(ResourceServer resourceServer, Identity identity, ResourceSetService resourceManager, AuthorizationProvider authorization) {
public ResourceService(ResourceServer resourceServer, Identity identity, ResourceSetService resourceManager) {
this.identity = identity;
this.resourceServer = resourceServer;
this.resourceManager = resourceManager;
this.authorization = authorization;
}
@POST
@Consumes("application/json")
@Produces("application/json")
public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation umaResource) {
public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation resource) {
checkResourceServerSettings();
if (umaResource == null) {
if (resource == null) {
return Response.status(Status.BAD_REQUEST).build();
}
return this.resourceManager.create(uriInfo, toResourceRepresentation(umaResource), (Function<Resource, UmaResourceRepresentation>) this::toUmaRepresentation);
ResourceOwnerRepresentation owner = resource.getOwner();
if (owner == null) {
owner = new ResourceOwnerRepresentation();
resource.setOwner(owner);
}
String ownerId = owner.getId();
if (ownerId == null) {
ownerId = this.identity.getId();
}
owner.setId(ownerId);
ResourceRepresentation newResource = resourceManager.create(resource);
resourceManager.audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
return Response.status(Status.CREATED).entity(new UmaResourceRepresentation(newResource)).build();
}
@Path("{id}")
@PUT
@Consumes("application/json")
@Produces("application/json")
public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) {
ResourceRepresentation resource = toResourceRepresentation(representation);
Response response = this.resourceManager.update(uriInfo, id, resource);
if (response.getEntity() instanceof ResourceRepresentation) {
return Response.noContent().build();
}
return response;
public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) {
return this.resourceManager.update(uriInfo, id, resource);
}
@Path("/{id}")
@ -102,7 +109,7 @@ public class ResourceService {
@GET
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
return this.resourceManager.findById(id, (Function<Resource, UmaResourceRepresentation>) resource -> toUmaRepresentation(resource));
return this.resourceManager.findById(id, UmaResourceRepresentation::new);
}
@GET
@ -114,75 +121,11 @@ public class ResourceService {
@QueryParam("owner") String owner,
@QueryParam("type") String type,
@QueryParam("scope") String scope,
@QueryParam("matchingUri") Boolean matchingUri,
@QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
return resourceManager.find(id, name, uri, owner, type, scope, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, String>) (resource, deep1) -> resource.getId());
}
private ResourceRepresentation toResourceRepresentation(UmaResourceRepresentation umaResource) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setId(umaResource.getId());
resource.setIconUri(umaResource.getIconUri());
resource.setName(umaResource.getName());
resource.setUri(umaResource.getUri());
resource.setType(umaResource.getType());
resource.setOwnerManagedAccess(umaResource.getOwnerManagedAccess());
ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
String ownerId = umaResource.getOwner();
if (ownerId == null) {
ownerId = this.identity.getId();
}
owner.setId(ownerId);
resource.setOwner(owner);
resource.setScopes(umaResource.getScopes().stream().map(representation -> {
ScopeRepresentation scopeRepresentation = new ScopeRepresentation();
scopeRepresentation.setId(representation.getId());
scopeRepresentation.setName(representation.getName());
scopeRepresentation.setIconUri(representation.getIconUri());
return scopeRepresentation;
}).collect(Collectors.toSet()));
resource.setAttributes(umaResource.getAttributes());
return resource;
}
private UmaResourceRepresentation toUmaRepresentation(Resource model) {
if (model == null) {
return null;
}
UmaResourceRepresentation resource = new UmaResourceRepresentation();
resource.setId(model.getId());
resource.setIconUri(model.getIconUri());
resource.setName(model.getName());
resource.setUri(model.getUri());
resource.setType(model.getType());
if (model.getOwner() != null) {
resource.setOwner(model.getOwner());
}
resource.setScopes(model.getScopes().stream().map(scopeRepresentation -> {
UmaScopeRepresentation umaScopeRep = new UmaScopeRepresentation();
umaScopeRep.setId(scopeRepresentation.getId());
umaScopeRep.setName(scopeRepresentation.getName());
umaScopeRep.setIconUri(scopeRepresentation.getIconUri());
return umaScopeRep;
}).collect(Collectors.toSet()));
resource.setAttributes(model.getAttributes());
return resource;
return resourceManager.find(id, name, uri, owner, type, scope, matchingUri, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, String>) (resource, deep1) -> resource.getId());
}
private void checkResourceServerSettings() {

View file

@ -0,0 +1,72 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.protection.resource;
import java.util.Set;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.authorization.model.Resource;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UmaResourceRepresentation extends ResourceRepresentation {
public UmaResourceRepresentation() {
}
public UmaResourceRepresentation(ResourceRepresentation resource) {
setId(resource.getId());
setName(resource.getName());
setType(resource.getType());
setUri(resource.getUri());
setIconUri(resource.getIconUri());
setOwner(resource.getOwner());
setScopes(resource.getScopes());
setDisplayName(resource.getDisplayName());
setOwnerManagedAccess(resource.getOwnerManagedAccess());
}
public UmaResourceRepresentation(Resource resource) {
setId(resource.getId());
setName(resource.getName());
setType(resource.getType());
setUri(resource.getUri());
setIconUri(resource.getIconUri());
setOwner(resource.getOwner());
setScopes(resource.getScopes().stream().map(scope -> new ScopeRepresentation(scope.getName())).collect(Collectors.toSet()));
setDisplayName(resource.getDisplayName());
setOwnerManagedAccess(resource.isOwnerManagedAccess());
setAttributes(resource.getAttributes());
}
@JsonProperty("resource_scopes")
@Override
public Set<ScopeRepresentation> getScopes() {
return super.getScopes();
}
@JsonProperty("resource_scopes")
@Override
public void setScopes(Set<ScopeRepresentation> scopes) {
super.setScopes(scopes);
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

@ -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;
/**
* <p>One or more resources that the resource server manages as a set of protected resources.
*
* <p>For more details, <a href="https://docs.kantarainitiative.org/uma/draft-oauth-resource-reg.html#rfc.section.2.2">OAuth-resource-reg</a>.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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<UmaScopeRepresentation> scopes;
@JsonProperty("icon_uri")
private String iconUri;
private String owner;
private Boolean ownerManagedAccess;
private Map<String, List<String>> 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<UmaScopeRepresentation> 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<UmaScopeRepresentation> 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<UmaScopeRepresentation> 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<UmaScopeRepresentation> 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<UmaScopeRepresentation> 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<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.attributes = attributes;
}
}

View file

@ -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;
/**
* <p>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").
*
* <p>For more details, <a href="https://docs.kantarainitiative.org/uma/draft-oauth-resource-reg.html#rfc.section.2.1">OAuth-resource-reg</a>.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

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

View file

@ -155,6 +155,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
enforcerConfig.setEnforcementMode(null);
enforcerConfig.setLazyLoadPaths(null);
rep.setEnforcerConfig(enforcerConfig);

View file

@ -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"
}
]
}
]
}
}

View file

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

View file

@ -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}",

View file

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

View file

@ -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",

View file

@ -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);

View file

@ -0,0 +1,211 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.keycloak.testsuite.util.IOUtil.loadJson;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractBaseServletAuthzAdapterTest extends AbstractExampleAdapterTest {
protected static final String REALM_NAME = "servlet-authz";
protected static final String RESOURCE_SERVER_ID = "servlet-authz-app";
@BeforeClass
public static void enabled() { ProfileAssume.assumePreview(); }
@ArquillianResource
private Deployer deployer;
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
}
protected void performTests(ExceptionRunnable assertion) {
performTests(() -> importResourceServerSettings(), assertion);
}
protected void performTests(ExceptionRunnable beforeDeploy, ExceptionRunnable assertion) {
try {
beforeDeploy.run();
deployer.deploy(RESOURCE_SERVER_ID);
assertion.run();
} catch (FileNotFoundException cause) {
throw new RuntimeException("Failed to import authorization settings", cause);
} catch (Exception cause) {
throw new RuntimeException("Error while executing tests", cause);
} finally {
deployer.undeploy(RESOURCE_SERVER_ID);
}
}
protected boolean hasLink(String text) {
return getLink(text) != null;
}
protected boolean hasText(String text) {
return this.driver.getPageSource().contains(text);
}
private WebElement getLink(String text) {
return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
}
protected void importResourceServerSettings() throws FileNotFoundException {
getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
}
protected AuthorizationResource getAuthorizationResource() {
return getClientResource(RESOURCE_SERVER_ID).authorization();
}
protected ClientResource getClientResource(String clientId) {
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
return clients.get(resourceServer.getId());
}
private void logOut() {
navigateTo();
By by = By.xpath("//a[text() = 'Sign Out']");
WaitUtils.waitUntilElement(by);
this.driver.findElement(by).click();
pause(500);
}
protected void login(String username, String password) {
try {
navigateTo();
Thread.sleep(2000);
if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
Thread.sleep(2000);
logOut();
navigateTo();
}
Thread.sleep(2000);
this.loginPage.form().login(username, password);
} catch (Exception cause) {
throw new RuntimeException("Login failed", cause);
}
}
private void navigateTo() {
this.driver.navigate().to(getResourceServerUrl());
WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
}
protected boolean wasDenied() {
return this.driver.getPageSource().contains("You can not access this resource.");
}
protected URL getResourceServerUrl() {
try {
return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
} catch (MalformedURLException e) {
throw new RuntimeException("Could not obtain resource server url.", e);
}
}
protected void navigateToDynamicMenuPage() {
navigateTo();
getLink("Dynamic Menu").click();
}
protected void navigateToUserPremiumPage() {
navigateTo();
getLink("User Premium").click();
}
protected void navigateToAdminPage() {
navigateTo();
getLink("Administration").click();
}
protected void updatePermissionPolicies(String permissionName, String... policyNames) {
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if (permissionName.equalsIgnoreCase(policy.getName())) {
StringBuilder policies = new StringBuilder("[");
for (String policyName : policyNames) {
if (policies.length() > 1) {
policies.append(",");
}
policies.append("\"").append(policyName).append("\"");
}
policies.append("]");
policy.getConfig().put("applyPolicies", policies.toString());
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
}
protected void createUserPolicy(String name, String... userNames) {
UserPolicyRepresentation policy = new UserPolicyRepresentation();
policy.setName(name);
for (String userName : userNames) {
policy.addUser(userName);
}
assertFalse(policy.getUsers().isEmpty());
Response response = getAuthorizationResource().policies().user().create(policy);
response.close();
}
protected interface ExceptionRunnable {
void run() throws Exception;
}
}

View file

@ -28,7 +28,7 @@ import org.junit.Test;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractPermissiveModeAdapterTest extends AbstractServletAuthzAdapterTest {
public abstract class AbstractPermissiveModeAdapterTest extends AbstractBaseServletAuthzAdapterTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() throws IOException {

View file

@ -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();

View file

@ -0,0 +1,88 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.IOUtil.loadJson;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ResourcesResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractPhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID)
.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json");
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,196 +16,19 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.keycloak.testsuite.util.IOUtil.loadJson;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest {
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<RealmRepresentation> 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;
}
}

View file

@ -19,14 +19,11 @@ package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientPoliciesResource;
import org.keycloak.admin.client.resource.RealmResource;
@ -46,12 +43,7 @@ import org.keycloak.testsuite.util.WaitUtils;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractServletAuthzAdapterTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID);
}
public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractBaseServletAuthzAdapterTest {
@Test
public void testCanNotAccessWhenEnforcing() throws Exception {

View file

@ -0,0 +1,58 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientPoliciesResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.ResourcesResource;
import org.keycloak.admin.client.resource.RolePoliciesResource;
import org.keycloak.admin.client.resource.RoleScopeResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.testsuite.util.WaitUtils;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractServletAuthzLazyLoadPathsAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID)
.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-lazy-load-authz-service.json"), "keycloak.json");
}
}

View file

@ -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());
});
}

View file

@ -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<ResourceRepresentation> resources = authzClient.protection().resource().findByMatchingUri("/test");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/*", resources.get(0).getUri());
resources = authzClient.protection().resource().findByMatchingUri("/resources/test");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/resources/*", resources.get(0).getUri());
resources = authzClient.protection().resource().findByMatchingUri("/resources");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/resources/*", resources.get(0).getUri());
resources = authzClient.protection().resource().findByMatchingUri("/resources/a/b");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/resources/{pattern}/*", resources.get(0).getUri());
resources = authzClient.protection().resource().findByMatchingUri("/resources/a/b/c");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/resources/{pattern}/{pattern}/*", resources.get(0).getUri());
resources = authzClient.protection().resource().findByMatchingUri("/resources/a/sub-resources/c/d");
assertNotNull(resources);
assertEquals(1, resources.size());
assertEquals("/resources/{pattern}/sub-resources/{pattern}/*", resources.get(0).getUri());
}
@Override
protected ResourceRepresentation doCreateResource(ResourceRepresentation newResource) {
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());

View file

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

View file

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

View file

@ -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 {
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.example.authorization;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer("app-server-wildfly")
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
public class WildflyPhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleLazyLoadPathsAdapterTest {
}

View file

@ -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 {
}

View file

@ -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 {
}