[KEYCLOAK-4102] - Support lazy loading of paths via policy enforcer config
This commit is contained in:
parent
e9e376419d
commit
4a425c2674
44 changed files with 1132 additions and 1257 deletions
|
@ -19,7 +19,6 @@ package org.keycloak.adapters.authorization;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.AuthzClient;
|
||||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||||
import org.keycloak.representations.AccessToken;
|
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;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,31 +43,23 @@ import org.keycloak.representations.idm.authorization.Permission;
|
||||||
public abstract class AbstractPolicyEnforcer {
|
public abstract class AbstractPolicyEnforcer {
|
||||||
|
|
||||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
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 final PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
private Map<String, PathConfig> paths;
|
protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
private AuthzClient authzClient;
|
|
||||||
private PathMatcher pathMatcher;
|
|
||||||
|
|
||||||
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
|
||||||
this.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) {
|
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||||
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
|
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
|
||||||
|
|
||||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||||
return createEmptyAuthorizationContext(true);
|
return createEmptyAuthorizationContext(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = httpFacade.getRequest();
|
Request request = httpFacade.getRequest();
|
||||||
String path = getPath(request);
|
PathConfig pathConfig = getPathConfig(request);
|
||||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
|
||||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||||
|
|
||||||
if (securityContext == null) {
|
if (securityContext == null) {
|
||||||
|
@ -79,16 +72,20 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
AccessToken accessToken = securityContext.getToken();
|
AccessToken accessToken = securityContext.getToken();
|
||||||
|
|
||||||
if (accessToken != null) {
|
if (accessToken != null) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||||
|
}
|
||||||
|
|
||||||
if (pathConfig == null) {
|
if (pathConfig == null) {
|
||||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||||
return createAuthorizationContext(accessToken, null);
|
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);
|
return createAuthorizationContext(accessToken, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +108,18 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||||
|
return createEmptyAuthorizationContext(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||||
|
}
|
||||||
|
|
||||||
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||||
|
}
|
||||||
handleAccessDenied(httpFacade);
|
handleAccessDenied(httpFacade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,22 +131,21 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
|
|
||||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||||
Request request = httpFacade.getRequest();
|
Request request = httpFacade.getRequest();
|
||||||
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
|
||||||
|
|
||||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
if (isDefaultAccessDeniedUri(request)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
Authorization authorization = accessToken.getAuthorization();
|
||||||
|
|
||||||
if (authorization == null) {
|
if (authorization == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Permission> permissions = authorization.getPermissions();
|
|
||||||
boolean hasPermission = false;
|
boolean hasPermission = false;
|
||||||
|
List<Permission> grantedPermissions = authorization.getPermissions();
|
||||||
|
|
||||||
for (Permission permission : permissions) {
|
for (Permission permission : grantedPermissions) {
|
||||||
if (permission.getResourceId() != null) {
|
if (permission.getResourceId() != null) {
|
||||||
if (isResourcePermission(actualPathConfig, permission)) {
|
if (isResourcePermission(actualPathConfig, permission)) {
|
||||||
hasPermission = true;
|
hasPermission = true;
|
||||||
|
@ -151,9 +155,11 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
if (LOGGER.isDebugEnabled()) {
|
||||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||||
this.paths.remove(actualPathConfig);
|
}
|
||||||
|
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||||
|
policyEnforcer.getPaths().remove(actualPathConfig);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -170,7 +176,9 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -179,15 +187,21 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
httpFacade.getResponse().sendError(403);
|
httpFacade.getResponse().sendError(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
protected AuthzClient getAuthzClient() {
|
||||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
return policyEnforcer.getClient();
|
||||||
|
}
|
||||||
|
|
||||||
if (accessDeniedPath != null) {
|
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||||
if (request.getURI().contains(accessDeniedPath)) {
|
return policyEnforcer.getEnforcerConfig();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PolicyEnforcer getPolicyEnforcer() {
|
||||||
|
return policyEnforcer;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
private boolean isDefaultAccessDeniedUri(Request request) {
|
||||||
|
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||||
|
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||||
|
@ -215,20 +229,8 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
return requiredScopes.isEmpty();
|
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) {
|
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||||
return new ClientAuthorizationContext(authzClient) {
|
return new ClientAuthorizationContext(getAuthzClient()) {
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(String resourceName, String scopeName) {
|
public boolean hasPermission(String resourceName, String scopeName) {
|
||||||
return granted;
|
return granted;
|
||||||
|
@ -279,7 +281,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
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) {
|
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||||
|
@ -297,4 +299,8 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PathConfig getPathConfig(Request request) {
|
||||||
|
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import java.util.concurrent.locks.LockSupport;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
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>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public class PathCache {
|
public class PathCache {
|
||||||
|
@ -39,15 +41,6 @@ public class PathCache {
|
||||||
|
|
||||||
private final long maxAge;
|
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.
|
* 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) {
|
public PathConfig get(String uri) {
|
||||||
if (parkForReadAndCheckInterrupt()) {
|
if (parkForReadAndCheckInterrupt()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.adapters.authorization;
|
package org.keycloak.adapters.authorization;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
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.AuthzClient;
|
||||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||||
import org.keycloak.authorization.client.Configuration;
|
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.authorization.client.resource.ProtectedResource;
|
||||||
|
import org.keycloak.common.util.PathMatcher;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @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.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||||
|
this.pathMatcher = createPathMatcher(authzClient);
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("Initialization complete. Path configurations:");
|
LOGGER.debug("Initialization complete. Path configurations:");
|
||||||
|
@ -104,11 +105,11 @@ public class PolicyEnforcer {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
PolicyEnforcerConfig getEnforcerConfig() {
|
public PolicyEnforcerConfig getEnforcerConfig() {
|
||||||
return enforcerConfig;
|
return enforcerConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthzClient getClient() {
|
public AuthzClient getClient() {
|
||||||
return authzClient;
|
return authzClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,11 +117,11 @@ public class PolicyEnforcer {
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPath(PathConfig pathConfig) {
|
public PathMatcher<PathConfig> getPathMatcher() {
|
||||||
paths.put(pathConfig.getPath(), pathConfig);
|
return pathMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeycloakDeployment getDeployment() {
|
public KeycloakDeployment getDeployment() {
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +145,7 @@ public class PolicyEnforcer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
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()) {
|
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||||
ResourceRepresentation resource;
|
ResourceRepresentation resource;
|
||||||
|
@ -197,45 +198,85 @@ public class PolicyEnforcer {
|
||||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||||
|
|
||||||
|
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||||
for (String id : protectedResource.findAll()) {
|
for (String id : protectedResource.findAll()) {
|
||||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||||
|
|
||||||
if (resourceDescription.getUri() != null) {
|
if (resourceDescription.getUri() != null) {
|
||||||
PathConfig pathConfig = createPathConfig(resourceDescription);
|
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
|
||||||
paths.put(pathConfig.getPath(), pathConfig);
|
paths.put(pathConfig.getPath(), pathConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
private PathMatcher<PathConfig> createPathMatcher(final AuthzClient authzClient) {
|
||||||
PathConfig pathConfig = new PathConfig();
|
final PathCache pathCache = new PathCache(100, 30000);
|
||||||
|
|
||||||
pathConfig.setId(resourceDescription.getId());
|
return new PathMatcher<PathConfig>() {
|
||||||
pathConfig.setName(resourceDescription.getName());
|
@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.setPath(uri);
|
pathConfig = super.matches(targetUri);
|
||||||
|
|
||||||
List<String> scopeNames = new ArrayList<>();
|
if (enforcerConfig.getLazyLoadPaths() && (pathConfig == null || pathConfig.getPath().contains("*"))) {
|
||||||
|
try {
|
||||||
|
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||||
|
|
||||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
if (!matchingResources.isEmpty()) {
|
||||||
scopeNames.add(scope.getName());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pathConfig.setScopes(scopeNames);
|
pathCache.put(targetUri, pathConfig);
|
||||||
pathConfig.setType(resourceDescription.getType());
|
|
||||||
|
|
||||||
return pathConfig;
|
return pathConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathMatcher getPathMatcher() {
|
@Override
|
||||||
return pathMatcher;
|
protected String getPath(PathConfig entry) {
|
||||||
|
return entry.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,11 +23,11 @@ import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import org.keycloak.authorization.client.Configuration;
|
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.representation.ServerConfiguration;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
import org.keycloak.authorization.client.util.Throwables;
|
import org.keycloak.authorization.client.util.Throwables;
|
||||||
import org.keycloak.authorization.client.util.TokenCallable;
|
import org.keycloak.authorization.client.util.TokenCallable;
|
||||||
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
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.
|
* 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}
|
* @return a {@link ResourceRepresentation}
|
||||||
*/
|
*/
|
||||||
public ResourceRepresentation findByName(String name) {
|
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) {
|
if (representations.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -145,7 +145,7 @@ public class ProtectedResource {
|
||||||
* @return a {@link ResourceRepresentation}
|
* @return a {@link ResourceRepresentation}
|
||||||
*/
|
*/
|
||||||
public ResourceRepresentation findByName(String name, String ownerId) {
|
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) {
|
if (representations.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -163,11 +163,12 @@ public class ProtectedResource {
|
||||||
* @param owner the resource owner
|
* @param owner the resource owner
|
||||||
* @param type the resource type
|
* @param type the resource type
|
||||||
* @param scope the resource scope
|
* @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 firstResult the position of the first resource to retrieve
|
||||||
* @param maxResult the maximum number of resources to retrieve
|
* @param maxResult the maximum number of resources to retrieve
|
||||||
* @return an array of strings with the resource ids
|
* @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[]>() {
|
Callable<String[]> callable = new Callable<String[]>() {
|
||||||
@Override
|
@Override
|
||||||
public String[] call() throws Exception {
|
public String[] call() throws Exception {
|
||||||
|
@ -179,6 +180,7 @@ public class ProtectedResource {
|
||||||
.param("owner", owner)
|
.param("owner", owner)
|
||||||
.param("type", type)
|
.param("type", type)
|
||||||
.param("scope", scope)
|
.param("scope", scope)
|
||||||
|
.param("matchingUri", Boolean.valueOf(matchingUri).toString())
|
||||||
.param("deep", Boolean.FALSE.toString())
|
.param("deep", Boolean.FALSE.toString())
|
||||||
.param("first", firstResult != null ? firstResult.toString() : null)
|
.param("first", firstResult != null ? firstResult.toString() : null)
|
||||||
.param("max", maxResult != null ? maxResult.toString() : null)
|
.param("max", maxResult != null ? maxResult.toString() : null)
|
||||||
|
@ -199,7 +201,7 @@ public class ProtectedResource {
|
||||||
*/
|
*/
|
||||||
public String[] findAll() {
|
public String[] findAll() {
|
||||||
try {
|
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) {
|
} catch (Exception cause) {
|
||||||
throw Throwables.handleWrapException("Could not find resource", cause);
|
throw Throwables.handleWrapException("Could not find resource", cause);
|
||||||
}
|
}
|
||||||
|
@ -233,7 +235,30 @@ public class ProtectedResource {
|
||||||
* @param uri the resource uri
|
* @param uri the resource uri
|
||||||
*/
|
*/
|
||||||
public List<ResourceRepresentation> findByUri(String 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) {
|
if (ids.length == 0) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
|
@ -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.
|
* and other contributors as indicated by the @author tags.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -13,45 +13,32 @@
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
package org.keycloak.adapters.authorization;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
class PathMatcher {
|
public abstract class PathMatcher<P> {
|
||||||
|
|
||||||
private static final char WILDCARD = '*';
|
private static final char WILDCARD = '*';
|
||||||
private final AuthzClient authzClient;
|
|
||||||
// TODO: make this configurable
|
|
||||||
private PathCache cache = new PathCache(100, 30000);
|
|
||||||
|
|
||||||
public PathMatcher(AuthzClient authzClient) {
|
public P matches(final String targetUri) {
|
||||||
this.authzClient = authzClient;
|
int patternCount = 0;
|
||||||
|
P matchingPath = null;
|
||||||
|
P matchingAnyPath = null;
|
||||||
|
P matchingAnySuffixPath = null;
|
||||||
|
|
||||||
|
for (P entry : getPaths()) {
|
||||||
|
String expectedUri = getPath(entry);
|
||||||
|
|
||||||
|
if (expectedUri == null) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
|
|
||||||
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
|
|
||||||
|
|
||||||
if (pathConfig != null) {
|
|
||||||
return pathConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
PathConfig matchingAnyPath = null;
|
|
||||||
PathConfig matchingAnySuffixPath = null;
|
|
||||||
|
|
||||||
for (PathConfig entry : paths.values()) {
|
|
||||||
String expectedUri = entry.getPath();
|
|
||||||
String matchingUri = null;
|
String matchingUri = null;
|
||||||
|
|
||||||
if (exactMatch(expectedUri, targetUri, expectedUri)) {
|
if (exactMatch(expectedUri, targetUri, expectedUri)) {
|
||||||
|
@ -62,9 +49,17 @@ class PathMatcher {
|
||||||
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
|
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
|
||||||
|
|
||||||
if (templateUri != null) {
|
if (templateUri != null) {
|
||||||
if (exactMatch(expectedUri, targetUri, templateUri)) {
|
int length = expectedUri.split("\\/").length;
|
||||||
|
|
||||||
|
if (exactMatch(expectedUri, targetUri, templateUri) && (patternCount == 0 || length > patternCount)) {
|
||||||
matchingUri = templateUri;
|
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)) {
|
if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) {
|
||||||
cache.put(targetUri, entry);
|
if (patternCount == 0) {
|
||||||
return entry;
|
return entry;
|
||||||
|
} else {
|
||||||
|
matchingPath = entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
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;
|
matchingAnyPath = entry;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,18 +110,21 @@ class PathMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingAnySuffixPath != null) {
|
if (matchingPath != null) {
|
||||||
cache.put(targetUri, matchingAnySuffixPath);
|
return matchingPath;
|
||||||
return matchingAnySuffixPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingAnyPath != null) {
|
if (matchingAnySuffixPath != null) {
|
||||||
cache.put(targetUri, matchingAnyPath);
|
return matchingAnySuffixPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchingAnyPath;
|
return matchingAnyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract String getPath(P entry);
|
||||||
|
|
||||||
|
protected abstract Collection<P> getPaths();
|
||||||
|
|
||||||
private boolean exactMatch(String expectedUri, String targetUri, String value) {
|
private boolean exactMatch(String expectedUri, String targetUri, String value) {
|
||||||
if (targetUri.equals(value)) {
|
if (targetUri.equals(value)) {
|
||||||
return value.equals(targetUri);
|
return value.equals(targetUri);
|
||||||
|
@ -213,32 +214,16 @@ class PathMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean endsWithWildcard(String expectedUri) {
|
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) {
|
private boolean isTemplate(String uri) {
|
||||||
return uri.indexOf("{") != -1;
|
return uri.indexOf("{") != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
protected P resolvePathConfig(P entry, String path) {
|
||||||
if (originalConfig.hasPattern()) {
|
return entry;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,21 +59,21 @@ public class AuthorizationContext {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current != null) {
|
for (Permission permission : authorization.getPermissions()) {
|
||||||
if (current.getName().equals(resourceName)) {
|
if (resourceName.equalsIgnoreCase(permission.getResourceName()) || resourceName.equalsIgnoreCase(permission.getResourceId())) {
|
||||||
|
if (scopeName == null) {
|
||||||
return true;
|
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)) {
|
if (permission.getScopes().contains(scopeName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
if (current.getName().equals(resourceName)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,29 +81,7 @@ public class AuthorizationContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasResourcePermission(String resourceName) {
|
public boolean hasResourcePermission(String resourceName) {
|
||||||
if (this.authzToken == null) {
|
return hasPermission(resourceName, null);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Authorization authorization = this.authzToken.getAuthorization();
|
|
||||||
|
|
||||||
if (authorization == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != null) {
|
|
||||||
if (current.getName().equals(resourceName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Permission permission : authorization.getPermissions()) {
|
|
||||||
if (permission.getResourceName().equals(resourceName) || permission.getResourceId().equals(resourceName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasScopePermission(String scopeName) {
|
public boolean hasScopePermission(String scopeName) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.util.List;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
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>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
@ -36,6 +38,9 @@ public class PolicyEnforcerConfig {
|
||||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
private List<PathConfig> paths = new ArrayList<>();
|
private List<PathConfig> paths = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonProperty("lazy-load-paths")
|
||||||
|
private Boolean lazyLoadPaths = Boolean.FALSE;
|
||||||
|
|
||||||
@JsonProperty("on-deny-redirect-to")
|
@JsonProperty("on-deny-redirect-to")
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private String onDenyRedirectTo;
|
private String onDenyRedirectTo;
|
||||||
|
@ -48,6 +53,14 @@ public class PolicyEnforcerConfig {
|
||||||
return this.paths;
|
return this.paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getLazyLoadPaths() {
|
||||||
|
return lazyLoadPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLazyLoadPaths(Boolean lazyLoadPaths) {
|
||||||
|
this.lazyLoadPaths = lazyLoadPaths;
|
||||||
|
}
|
||||||
|
|
||||||
public EnforcementMode getEnforcementMode() {
|
public EnforcementMode getEnforcementMode() {
|
||||||
return this.enforcementMode;
|
return this.enforcementMode;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +91,32 @@ public class PolicyEnforcerConfig {
|
||||||
|
|
||||||
public static class PathConfig {
|
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 name;
|
||||||
private String type;
|
private String type;
|
||||||
private String path;
|
private String path;
|
||||||
|
@ -219,7 +258,8 @@ public class PolicyEnforcerConfig {
|
||||||
|
|
||||||
public enum ScopeEnforcementMode {
|
public enum ScopeEnforcementMode {
|
||||||
ALL,
|
ALL,
|
||||||
ANY
|
ANY,
|
||||||
|
DISABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UserManagedAccessConfig {
|
public static class UserManagedAccessConfig {
|
||||||
|
|
|
@ -24,8 +24,10 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import org.keycloak.json.StringListMapDeserializer;
|
import org.keycloak.json.StringListMapDeserializer;
|
||||||
|
|
||||||
|
@ -45,6 +47,7 @@ public class ResourceRepresentation {
|
||||||
private String uri;
|
private String uri;
|
||||||
private String type;
|
private String type;
|
||||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@JsonProperty("scopes")
|
||||||
private Set<ScopeRepresentation> scopes;
|
private Set<ScopeRepresentation> scopes;
|
||||||
|
|
||||||
@JsonProperty("icon_uri")
|
@JsonProperty("icon_uri")
|
||||||
|
@ -52,9 +55,6 @@ public class ResourceRepresentation {
|
||||||
private ResourceOwnerRepresentation owner;
|
private ResourceOwnerRepresentation owner;
|
||||||
private Boolean ownerManagedAccess;
|
private Boolean ownerManagedAccess;
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
|
||||||
private List<PolicyRepresentation> policies;
|
|
||||||
|
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
@JsonDeserialize(using = StringListMapDeserializer.class)
|
@JsonDeserialize(using = StringListMapDeserializer.class)
|
||||||
|
@ -162,17 +162,31 @@ public class ResourceRepresentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUri(String uri) {
|
public void setUri(String uri) {
|
||||||
|
if (uri != null && !"".equalsIgnoreCase(uri.trim())) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setType(String type) {
|
public void setType(String type) {
|
||||||
|
if (type != null && !"".equalsIgnoreCase(type.trim())) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setScopes(Set<ScopeRepresentation> scopes) {
|
public void setScopes(Set<ScopeRepresentation> scopes) {
|
||||||
this.scopes = 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) {
|
public void setIconUri(String iconUri) {
|
||||||
this.iconUri = iconUri;
|
this.iconUri = iconUri;
|
||||||
}
|
}
|
||||||
|
@ -181,10 +195,25 @@ public class ResourceRepresentation {
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
public void setOwner(ResourceOwnerRepresentation owner) {
|
public void setOwner(ResourceOwnerRepresentation owner) {
|
||||||
this.owner = 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() {
|
public Boolean getOwnerManagedAccess() {
|
||||||
return ownerManagedAccess;
|
return ownerManagedAccess;
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,10 @@ public class JPAResourceStore implements ResourceStore {
|
||||||
predicates.add(root.join("scopes").get("id").in(value));
|
predicates.add(root.join("scopes").get("id").in(value));
|
||||||
} else if ("ownerManagedAccess".equals(name)) {
|
} else if ("ownerManagedAccess".equals(name)) {
|
||||||
predicates.add(builder.equal(root.get(name), Boolean.valueOf(value[0])));
|
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 {
|
} else {
|
||||||
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
|
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2314,7 +2314,7 @@ public class RepresentationToModel {
|
||||||
String ownerId = owner.getId();
|
String ownerId = owner.getId();
|
||||||
|
|
||||||
if (ownerId == null) {
|
if (ownerId == null) {
|
||||||
throw new RuntimeException("No owner specified for resource [" + resource.getName() + "].");
|
ownerId = resourceServer.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resourceServer.getId().equals(ownerId)) {
|
if (!resourceServer.getId().equals(ownerId)) {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
||||||
import static org.keycloak.models.utils.RepresentationToModel.toModel;
|
import static org.keycloak.models.utils.RepresentationToModel.toModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -46,6 +48,7 @@ import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
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.PolicyStore;
|
||||||
import org.keycloak.authorization.store.ResourceStore;
|
import org.keycloak.authorization.store.ResourceStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
|
import org.keycloak.common.util.PathMatcher;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.ClientModel;
|
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.ResourceOwnerRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
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.AdminEventBuilder;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
|
|
||||||
|
@ -90,32 +94,18 @@ public class ResourceSetService {
|
||||||
@Consumes("application/json")
|
@Consumes("application/json")
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
|
public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
|
||||||
return create(uriInfo, resource, (Function<Resource, ResourceRepresentation>) resource1 -> {
|
if (resource == null) {
|
||||||
ResourceRepresentation representation = new ResourceRepresentation();
|
return Response.status(Status.BAD_REQUEST).build();
|
||||||
|
|
||||||
representation.setId(resource1.getId());
|
|
||||||
|
|
||||||
return representation;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource, Function<Resource, ?> toRepresentation) {
|
ResourceRepresentation newResource = create(resource);
|
||||||
Response response = create(resource, toRepresentation);
|
|
||||||
audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
|
audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
|
||||||
return response;
|
|
||||||
|
return Response.status(Status.CREATED).entity(newResource).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response create(ResourceRepresentation resource) {
|
public ResourceRepresentation 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) {
|
|
||||||
requireManage();
|
requireManage();
|
||||||
StoreFactory storeFactory = this.authorization.getStoreFactory();
|
StoreFactory storeFactory = this.authorization.getStoreFactory();
|
||||||
ResourceOwnerRepresentation owner = resource.getOwner();
|
ResourceOwnerRepresentation owner = resource.getOwner();
|
||||||
|
@ -123,21 +113,22 @@ public class ResourceSetService {
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
owner = new ResourceOwnerRepresentation();
|
owner = new ResourceOwnerRepresentation();
|
||||||
owner.setId(resourceServer.getId());
|
owner.setId(resourceServer.getId());
|
||||||
|
resource.setOwner(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
String ownerId = owner.getId();
|
String ownerId = owner.getId();
|
||||||
|
|
||||||
if (ownerId == null) {
|
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());
|
Resource existingResource = storeFactory.getResourceStore().findByName(resource.getName(), ownerId, this.resourceServer.getId());
|
||||||
|
|
||||||
if (existingResource != null) {
|
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}")
|
@Path("{id}")
|
||||||
|
@ -198,10 +189,10 @@ public class ResourceSetService {
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response findById(@PathParam("id") String id) {
|
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();
|
requireView();
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
|
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
|
||||||
|
@ -340,10 +331,11 @@ public class ResourceSetService {
|
||||||
@QueryParam("owner") String owner,
|
@QueryParam("owner") String owner,
|
||||||
@QueryParam("type") String type,
|
@QueryParam("type") String type,
|
||||||
@QueryParam("scope") String scope,
|
@QueryParam("scope") String scope,
|
||||||
|
@QueryParam("matchingUri") Boolean matchingUri,
|
||||||
@QueryParam("deep") Boolean deep,
|
@QueryParam("deep") Boolean deep,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResult) {
|
@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,
|
public Response find(@QueryParam("_id") String id,
|
||||||
|
@ -352,6 +344,7 @@ public class ResourceSetService {
|
||||||
@QueryParam("owner") String owner,
|
@QueryParam("owner") String owner,
|
||||||
@QueryParam("type") String type,
|
@QueryParam("type") String type,
|
||||||
@QueryParam("scope") String scope,
|
@QueryParam("scope") String scope,
|
||||||
|
@QueryParam("matchingUri") Boolean matchingUri,
|
||||||
@QueryParam("deep") Boolean deep,
|
@QueryParam("deep") Boolean deep,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResult,
|
@QueryParam("max") Integer maxResult,
|
||||||
|
@ -413,9 +406,38 @@ public class ResourceSetService {
|
||||||
search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new));
|
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;
|
Boolean finalDeep = deep;
|
||||||
|
|
||||||
return Response.ok(
|
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))
|
.map(resource -> toRepresentation.apply(resource, finalDeep))
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
.build();
|
.build();
|
||||||
|
@ -437,7 +459,7 @@ public class ResourceSetService {
|
||||||
audit(uriInfo, resource, null, operation);
|
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 (authorization.getRealm().isAdminEventsEnabled()) {
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
|
adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class ProtectionService {
|
||||||
|
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
|
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
|
||||||
|
|
||||||
ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
|
ResourceService resource = new ResourceService(resourceServer, identity, resourceManager);
|
||||||
|
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,8 +18,6 @@
|
||||||
package org.keycloak.authorization.protection.resource;
|
package org.keycloak.authorization.protection.resource;
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
|
@ -36,16 +34,13 @@ import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
|
||||||
import org.keycloak.authorization.admin.ResourceSetService;
|
import org.keycloak.authorization.admin.ResourceSetService;
|
||||||
import org.keycloak.authorization.identity.Identity;
|
import org.keycloak.authorization.identity.Identity;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.protection.resource.representation.UmaResourceRepresentation;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.authorization.protection.resource.representation.UmaScopeRepresentation;
|
|
||||||
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,39 +51,51 @@ public class ResourceService {
|
||||||
private final ResourceServer resourceServer;
|
private final ResourceServer resourceServer;
|
||||||
private final ResourceSetService resourceManager;
|
private final ResourceSetService resourceManager;
|
||||||
private final Identity identity;
|
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.identity = identity;
|
||||||
this.resourceServer = resourceServer;
|
this.resourceServer = resourceServer;
|
||||||
this.resourceManager = resourceManager;
|
this.resourceManager = resourceManager;
|
||||||
this.authorization = authorization;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes("application/json")
|
@Consumes("application/json")
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation umaResource) {
|
public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation resource) {
|
||||||
checkResourceServerSettings();
|
checkResourceServerSettings();
|
||||||
if (umaResource == null) {
|
|
||||||
|
if (resource == null) {
|
||||||
return Response.status(Status.BAD_REQUEST).build();
|
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}")
|
@Path("{id}")
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes("application/json")
|
@Consumes("application/json")
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) {
|
public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) {
|
||||||
ResourceRepresentation resource = toResourceRepresentation(representation);
|
return this.resourceManager.update(uriInfo, id, resource);
|
||||||
Response response = this.resourceManager.update(uriInfo, id, resource);
|
|
||||||
|
|
||||||
if (response.getEntity() instanceof ResourceRepresentation) {
|
|
||||||
return Response.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/{id}")
|
@Path("/{id}")
|
||||||
|
@ -102,7 +109,7 @@ public class ResourceService {
|
||||||
@GET
|
@GET
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response findById(@PathParam("id") String id) {
|
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
|
@GET
|
||||||
|
@ -114,75 +121,11 @@ public class ResourceService {
|
||||||
@QueryParam("owner") String owner,
|
@QueryParam("owner") String owner,
|
||||||
@QueryParam("type") String type,
|
@QueryParam("type") String type,
|
||||||
@QueryParam("scope") String scope,
|
@QueryParam("scope") String scope,
|
||||||
|
@QueryParam("matchingUri") Boolean matchingUri,
|
||||||
@QueryParam("deep") Boolean deep,
|
@QueryParam("deep") Boolean deep,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResult) {
|
@QueryParam("max") Integer maxResult) {
|
||||||
return resourceManager.find(id, name, uri, owner, type, scope, deep, firstResult, maxResult, (BiFunction<Resource, Boolean, String>) (resource, deep1) -> resource.getId());
|
return resourceManager.find(id, name, uri, owner, type, scope, matchingUri, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkResourceServerSettings() {
|
private void checkResourceServerSettings() {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -68,6 +68,7 @@ import org.keycloak.representations.idm.ScopeMappingRepresentation;
|
||||||
import org.keycloak.representations.idm.UserConsentRepresentation;
|
import org.keycloak.representations.idm.UserConsentRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
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.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
|
@ -315,7 +316,7 @@ public class ExportUtils {
|
||||||
ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization);
|
ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization);
|
||||||
|
|
||||||
if (rep.getOwner().getId().equals(settingsModel.getId())) {
|
if (rep.getOwner().getId().equals(settingsModel.getId())) {
|
||||||
rep.setOwner(null);
|
rep.setOwner((ResourceOwnerRepresentation) null);
|
||||||
} else {
|
} else {
|
||||||
rep.getOwner().setId(null);
|
rep.getOwner().setId(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
|
||||||
PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
|
PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
|
||||||
|
|
||||||
enforcerConfig.setEnforcementMode(null);
|
enforcerConfig.setEnforcementMode(null);
|
||||||
|
enforcerConfig.setLazyLoadPaths(null);
|
||||||
|
|
||||||
rep.setEnforcerConfig(enforcerConfig);
|
rep.setEnforcerConfig(enforcerConfig);
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ package org.keycloak.example.photoz.album;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||||
import org.keycloak.example.photoz.entity.Album;
|
import org.keycloak.example.photoz.entity.Album;
|
||||||
import org.keycloak.example.photoz.util.Transaction;
|
import org.keycloak.example.photoz.util.Transaction;
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
"enforcement-mode": "PERMISSIVE",
|
"enforcement-mode": "PERMISSIVE",
|
||||||
"user-managed-access": {},
|
"user-managed-access": {},
|
||||||
"paths": [
|
"paths": [
|
||||||
|
{
|
||||||
|
"name" : "Album Resource",
|
||||||
|
"path" : "/album",
|
||||||
|
"methods" : [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"scopes-enforcement-mode" : "DISABLED"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name" : "Album Resource",
|
"name" : "Album Resource",
|
||||||
"path" : "/album/{id}",
|
"path" : "/album/{id}",
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
},
|
},
|
||||||
"policy-enforcer": {
|
"policy-enforcer": {
|
||||||
"on-deny-redirect-to": "/servlet-policy-enforcer/denied.jsp",
|
"on-deny-redirect-to": "/servlet-policy-enforcer/denied.jsp",
|
||||||
|
"lazy-load-paths": false,
|
||||||
"paths": [
|
"paths": [
|
||||||
{
|
{
|
||||||
"name": "Welcome Resource",
|
"name": "Welcome Resource",
|
||||||
|
@ -31,7 +32,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Pattern 5",
|
"name": "Pattern 5",
|
||||||
"path": "/resource/{pattern}/resource-d"
|
"path": "/a/{pattern}/resource-d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Pattern 6",
|
"name": "Pattern 6",
|
||||||
|
|
|
@ -204,10 +204,16 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
||||||
this.loginPage.form().login(username, password);
|
this.loginPage.form().login(username, password);
|
||||||
waitForPageToLoad();//guess
|
waitForPageToLoad();//guess
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isCurrent()) {
|
||||||
// simple check if we are at the consent page, if so just click 'Yes'
|
// simple check if we are at the consent page, if so just click 'Yes'
|
||||||
if (this.consentPage.isCurrent()) {
|
if (this.consentPage.isCurrent()) {
|
||||||
consentPage.confirm();
|
consentPage.confirm();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// ignore errors when checking consent page, if an error tests will also fail
|
||||||
|
}
|
||||||
|
|
||||||
pause(WAIT_AFTER_OPERATION);
|
pause(WAIT_AFTER_OPERATION);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ import org.junit.Test;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @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)
|
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
||||||
public static WebArchive deployment() throws IOException {
|
public static WebArchive deployment() throws IOException {
|
||||||
|
|
|
@ -75,7 +75,7 @@ import org.keycloak.util.JsonSerialization;
|
||||||
public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAdapterTest {
|
public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAdapterTest {
|
||||||
|
|
||||||
private static final String REALM_NAME = "photoz";
|
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
|
private static final int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
|
||||||
|
|
||||||
@ArquillianResource
|
@ArquillianResource
|
||||||
|
@ -118,16 +118,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
||||||
testRealms.add(realm);
|
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
|
@Override
|
||||||
public void beforeAbstractKeycloakTest() throws Exception {
|
public void beforeAbstractKeycloakTest() throws Exception {
|
||||||
super.beforeAbstractKeycloakTest();
|
super.beforeAbstractKeycloakTest();
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
* and other contributors as indicated by the @author tags.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -16,196 +16,19 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.adapter.example.authorization;
|
package org.keycloak.testsuite.adapter.example.authorization;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import java.io.IOException;
|
||||||
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 org.jboss.arquillian.container.test.api.Deployment;
|
||||||
import java.io.FileInputStream;
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
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>
|
* @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";
|
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
||||||
protected static final String RESOURCE_SERVER_ID = "servlet-authz-app";
|
public static WebArchive deployment() throws IOException {
|
||||||
|
return exampleDeployment(RESOURCE_SERVER_ID);
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,11 @@ package org.keycloak.testsuite.adapter.example.authorization;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
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.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientPoliciesResource;
|
import org.keycloak.admin.client.resource.ClientPoliciesResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
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>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractServletAuthzAdapterTest {
|
public abstract class AbstractServletAuthzFunctionalAdapterTest extends AbstractBaseServletAuthzAdapterTest {
|
||||||
|
|
||||||
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
|
||||||
public static WebArchive deployment() throws IOException {
|
|
||||||
return exampleDeployment(RESOURCE_SERVER_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCanNotAccessWhenEnforcing() throws Exception {
|
public void testCanNotAccessWhenEnforcing() throws Exception {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -177,21 +177,21 @@ public abstract class AbstractServletPolicyEnforcerTest extends AbstractExampleA
|
||||||
performTests(() -> {
|
performTests(() -> {
|
||||||
login("alice", "alice");
|
login("alice", "alice");
|
||||||
|
|
||||||
navigateTo("/resource/a/resource-d");
|
navigateTo("/a/a/resource-d");
|
||||||
assertFalse(wasDenied());
|
assertFalse(wasDenied());
|
||||||
navigateTo("/resource/b/resource-d");
|
navigateTo("/resource/b/resource-d");
|
||||||
assertFalse(wasDenied());
|
assertFalse(wasDenied());
|
||||||
|
|
||||||
updatePermissionPolicies("Pattern 5 Permission", "Deny Policy");
|
updatePermissionPolicies("Pattern 5 Permission", "Deny Policy");
|
||||||
login("alice", "alice");
|
login("alice", "alice");
|
||||||
navigateTo("/resource/a/resource-d");
|
navigateTo("/a/a/resource-d");
|
||||||
assertTrue(wasDenied());
|
assertTrue(wasDenied());
|
||||||
navigateTo("/resource/b/resource-d");
|
navigateTo("/a/b/resource-d");
|
||||||
assertTrue(wasDenied());
|
assertTrue(wasDenied());
|
||||||
|
|
||||||
updatePermissionPolicies("Pattern 5 Permission", "Default Policy");
|
updatePermissionPolicies("Pattern 5 Permission", "Default Policy");
|
||||||
login("alice", "alice");
|
login("alice", "alice");
|
||||||
navigateTo("/resource/b/resource-d");
|
navigateTo("/a/b/resource-d");
|
||||||
assertFalse(wasDenied());
|
assertFalse(wasDenied());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,17 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.admin.client.authorization;
|
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.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
import org.keycloak.authorization.client.Configuration;
|
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.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
@ -35,12 +40,60 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
|
|
||||||
private AuthzClient authzClient;
|
private AuthzClient authzClient;
|
||||||
|
|
||||||
@Override
|
@Test
|
||||||
protected ResourceRepresentation doCreateResource(ResourceRepresentation newResource) {
|
public void testFindMatchingUri() {
|
||||||
org.keycloak.authorization.client.representation.ResourceRepresentation resource = toResourceRepresentation(newResource);
|
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();
|
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());
|
return toResourceRepresentation(authzClient, response.getId());
|
||||||
}
|
}
|
||||||
|
@ -60,7 +113,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceRepresentation toResourceRepresentation(AuthzClient authzClient, String id) {
|
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 resourceRepresentation = new ResourceRepresentation();
|
||||||
|
|
||||||
resourceRepresentation.setId(created.getId());
|
resourceRepresentation.setId(created.getId());
|
||||||
|
@ -68,11 +121,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
resourceRepresentation.setIconUri(created.getIconUri());
|
resourceRepresentation.setIconUri(created.getIconUri());
|
||||||
resourceRepresentation.setUri(created.getUri());
|
resourceRepresentation.setUri(created.getUri());
|
||||||
resourceRepresentation.setType(created.getType());
|
resourceRepresentation.setType(created.getType());
|
||||||
ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
|
resourceRepresentation.setOwner(created.getOwner());
|
||||||
|
|
||||||
owner.setId(created.getOwner());
|
|
||||||
|
|
||||||
resourceRepresentation.setOwner(owner);
|
|
||||||
resourceRepresentation.setScopes(created.getScopes().stream().map(scopeRepresentation -> {
|
resourceRepresentation.setScopes(created.getScopes().stream().map(scopeRepresentation -> {
|
||||||
ScopeRepresentation scope = new ScopeRepresentation();
|
ScopeRepresentation scope = new ScopeRepresentation();
|
||||||
|
|
||||||
|
@ -88,8 +137,8 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
return resourceRepresentation;
|
return resourceRepresentation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private org.keycloak.authorization.client.representation.ResourceRepresentation toResourceRepresentation(ResourceRepresentation newResource) {
|
private ResourceRepresentation toResourceRepresentation(ResourceRepresentation newResource) {
|
||||||
org.keycloak.authorization.client.representation.ResourceRepresentation resource = new org.keycloak.authorization.client.representation.ResourceRepresentation();
|
ResourceRepresentation resource = new ResourceRepresentation();
|
||||||
|
|
||||||
resource.setId(newResource.getId());
|
resource.setId(newResource.getId());
|
||||||
resource.setName(newResource.getName());
|
resource.setName(newResource.getName());
|
||||||
|
@ -102,7 +151,7 @@ public class ResourceManagementWithAuthzClientTest extends ResourceManagementTes
|
||||||
}
|
}
|
||||||
|
|
||||||
resource.setScopes(newResource.getScopes().stream().map(scopeRepresentation -> {
|
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.setName(scopeRepresentation.getName());
|
||||||
scope.setIconUri(scopeRepresentation.getIconUri());
|
scope.setIconUri(scopeRepresentation.getIconUri());
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||||
import org.keycloak.authorization.client.Configuration;
|
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.resource.ProtectionResource;
|
||||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
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.Permission;
|
||||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||||
import org.keycloak.representations.idm.authorization.PermissionResponse;
|
import org.keycloak.representations.idm.authorization.PermissionResponse;
|
||||||
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||||
import org.keycloak.testsuite.util.ClientBuilder;
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
|
|
@ -38,8 +38,6 @@ import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
import org.keycloak.authorization.client.Configuration;
|
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.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.representations.AccessToken;
|
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.Permission;
|
||||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
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.ScopePermissionRepresentation;
|
||||||
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.testsuite.util.ClientBuilder;
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
|
@ -24,6 +24,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-wildfly")
|
@AppServerContainer("app-server-wildfly")
|
||||||
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
||||||
public class WildflyPhotozExampleAdapterTest extends AbstractPhotozExampleAdapterTest {
|
public class WildflyPhotozExampleAdapterTest extends AbstractPhotozExampleNoLazyLoadPathsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
@RunAsClient
|
@RunAsClient
|
||||||
@AppServerContainer("app-server-wildfly")
|
@AppServerContainer("app-server-wildfly")
|
||||||
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
||||||
public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
|
public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue