Removing policy-enforcer from Keycloak repository
closes #32191 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
967893d3cf
commit
cd947ce3bc
54 changed files with 12 additions and 5092 deletions
|
@ -1,54 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-policy-enforcer-tests</artifactId>
|
||||
<name>Keycloak Authz: Policy Enforcer for tests</name>
|
||||
<description>Keycloak Policy Enforcer. This module is supposed to be used just in the Keycloak repository for the testsuite. It is NOT supposed to be used by the 3rd party applications.
|
||||
For the use by 3rd party applications, please use `org.keycloak:keycloak-policy-enforcer` module.</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client-tests</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Built-in Elytron/Jakarta Servlet integration -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.security</groupId>
|
||||
<artifactId>wildfly-elytron-http-oidc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,203 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.adapters.authorization;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* A simple LRU cache implementation supporting expiration and maximum number of entries.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathCache {
|
||||
|
||||
/**
|
||||
* The load factor.
|
||||
*/
|
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
private final Map<String, CacheEntry> cache;
|
||||
|
||||
private final AtomicBoolean writing = new AtomicBoolean(false);
|
||||
|
||||
private final long maxAge;
|
||||
private final boolean enabled;
|
||||
private final Map<String, PathConfig> paths;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param maxEntries the maximum number of entries to keep in the cache
|
||||
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
|
||||
* @param paths the pre-configured paths
|
||||
*/
|
||||
PathCache(final int maxEntries, long maxAge,
|
||||
Map<String, PathConfig> paths) {
|
||||
cache = Collections.synchronizedMap(new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return cache.size() > maxEntries;
|
||||
}
|
||||
});
|
||||
this.maxAge = maxAge;
|
||||
this.enabled = ! (maxAge < -1 || (maxAge > -1 && maxAge <= 0));
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
public void put(String uri, PathConfig newValue) {
|
||||
if (!enabled) {
|
||||
if (newValue != null) {
|
||||
// if disabled we also remove from the pre-defined paths map
|
||||
markForInvalidation(newValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CacheEntry cacheEntry = cache.get(uri);
|
||||
|
||||
if (cacheEntry == null) {
|
||||
cache.put(uri, new CacheEntry(uri, newValue, maxAge));
|
||||
}
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void markForInvalidation(PathConfig newValue) {
|
||||
PathConfig pathConfig = paths.get(newValue.getPath());
|
||||
|
||||
if (pathConfig != null && !pathConfig.isStatic()) {
|
||||
// invalidate the configuration so that the path config is reload based on latest changes on the server
|
||||
pathConfig.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsKey(String uri) {
|
||||
return cache.containsKey(uri);
|
||||
}
|
||||
|
||||
public PathConfig get(String uri) {
|
||||
if (parkForReadAndCheckInterrupt()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry cached = cache.get(uri);
|
||||
|
||||
if (cached != null) {
|
||||
return removeIfExpired(cached);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.remove(key);
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
private PathConfig removeIfExpired(CacheEntry cached) {
|
||||
if (cached == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PathConfig config = cached.value();
|
||||
|
||||
if (cached.isExpired()) {
|
||||
remove(cached.key());
|
||||
|
||||
if (config != null && config.getPath() != null) {
|
||||
// also remove from pre-defined paths map so that changes on the server are properly reflected
|
||||
markForInvalidation(config);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private boolean parkForWriteAndCheckInterrupt() {
|
||||
while (!writing.compareAndSet(false, true)) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean parkForReadAndCheckInterrupt() {
|
||||
while (writing.get()) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
private static final class CacheEntry {
|
||||
|
||||
final String key;
|
||||
final PathConfig value;
|
||||
final long expiration;
|
||||
|
||||
CacheEntry(String key, PathConfig value, long maxAge) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
if(maxAge == -1) {
|
||||
expiration = -1;
|
||||
} else {
|
||||
expiration = Time.currentTimeMillis() + maxAge;
|
||||
}
|
||||
}
|
||||
|
||||
String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
PathConfig value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return expiration != -1 ? Time.currentTimeMillis() > expiration : false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
PathConfigMatcher(PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
this.authzClient = authzClient;
|
||||
this.paths = configurePaths();
|
||||
this.pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan(), paths);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configuration:");
|
||||
for (PathConfig pathConfig : this.paths.values()) {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
||||
if (pathCache.containsKey(targetUri) || pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || pathConfig.isInvalidated() || pathConfig.getPath().contains("*"))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (matchingResources.isEmpty()) {
|
||||
// if this config is invalidated (e.g.: due to cache expiration) we remove and return null
|
||||
if (pathConfig != null && pathConfig.isInvalidated()) {
|
||||
paths.remove(targetUri);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Map<String, Map<String, Object>> cipConfig = null;
|
||||
PolicyEnforcerConfig.EnforcementMode enforcementMode = PolicyEnforcerConfig.EnforcementMode.ENFORCING;
|
||||
ResourceRepresentation targetResource = matchingResources.get(0);
|
||||
List<org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig> methodConfig = null;
|
||||
boolean isStatic = false;
|
||||
|
||||
if (pathConfig != null) {
|
||||
cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
enforcementMode = pathConfig.getEnforcementMode();
|
||||
methodConfig = pathConfig.getMethods();
|
||||
isStatic = pathConfig.isStatic();
|
||||
} else {
|
||||
for (PathConfig existingPath : paths.values()) {
|
||||
if (targetResource.getId().equals(existingPath.getId())
|
||||
&& existingPath.isStatic()
|
||||
&& !org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(existingPath.getEnforcementMode())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathConfig = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
if (cipConfig != null) {
|
||||
pathConfig.setClaimInformationPointConfig(cipConfig);
|
||||
}
|
||||
|
||||
if (methodConfig != null) {
|
||||
pathConfig.setMethods(methodConfig);
|
||||
}
|
||||
|
||||
pathConfig.setStatic(isStatic);
|
||||
pathConfig.setEnforcementMode(enforcementMode);
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPath(PathConfig entry) {
|
||||
return entry.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<PathConfig> getPaths() {
|
||||
return paths.values();
|
||||
}
|
||||
|
||||
public PathCache getPathCache() {
|
||||
return pathCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = authzClient.protection().resource();
|
||||
|
||||
// search by an exact match
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
// if exact match not found, try to obtain from current path the parent path.
|
||||
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
|
||||
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
|
||||
// so any permission granted to parent also applies to sub resources
|
||||
if (search.isEmpty()) {
|
||||
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
|
||||
}
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPathConfig() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths() {
|
||||
ProtectedResource protectedResource = this.authzClient.protection().resource();
|
||||
boolean loadPathsFromServer = !enforcerConfig.getLazyLoadPaths();
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
|
||||
|
||||
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
|
||||
|
||||
return paths;
|
||||
} else {
|
||||
LOGGER.info("Paths provided in configuration.");
|
||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
ResourceRepresentation resource;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
resources = protectedResource.findByMatchingUri(path);
|
||||
}
|
||||
|
||||
if (resources.size() == 1) {
|
||||
resource = resources.get(0);
|
||||
} else if (resources.size() > 1) {
|
||||
throw new RuntimeException("Multiple resources found with the same uri");
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (resource != null) {
|
||||
pathConfig.setId(resource.getId());
|
||||
// if the resource is statically bound to a resource it means the config can not be invalidated
|
||||
if (resourceName != null) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
pathConfig.setStatic(true);
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPath == null) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
} else {
|
||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
|
||||
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
}
|
|
@ -1,669 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.adapters.authorization;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.JsonUtils.asAccessToken;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* <p>A Policy Enforcement Point (PEP) that requests and enforces authorization decisions from Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
private static final String HTTP_METHOD_DELETE = "DELETE";
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private final AuthzClient authzClient;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathConfigMatcher pathMatcher;
|
||||
private final HttpClient httpClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
|
||||
|
||||
protected PolicyEnforcer(Builder builder) {
|
||||
enforcerConfig = builder.getEnforcerConfig();
|
||||
Configuration authzClientConfig = builder.authzClientConfig;
|
||||
|
||||
if (authzClientConfig.getRealm() == null) {
|
||||
authzClientConfig.setRealm(enforcerConfig.getRealm());
|
||||
}
|
||||
|
||||
if (authzClientConfig.getAuthServerUrl() == null) {
|
||||
authzClientConfig.setAuthServerUrl(enforcerConfig.getAuthServerUrl());
|
||||
}
|
||||
|
||||
if (authzClientConfig.getCredentials() == null || authzClientConfig.getCredentials().isEmpty()) {
|
||||
authzClientConfig.setCredentials(enforcerConfig.getCredentials());
|
||||
}
|
||||
|
||||
if (authzClientConfig.getResource() == null) {
|
||||
authzClientConfig.setResource(enforcerConfig.getResource());
|
||||
}
|
||||
|
||||
authzClient = AuthzClient.create(authzClientConfig);
|
||||
httpClient = authzClient.getConfiguration().getHttpClient();
|
||||
pathMatcher = new PathConfigMatcher(builder.getEnforcerConfig(), authzClient);
|
||||
paths = pathMatcher.getPathConfig();
|
||||
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(HttpRequest request, HttpResponse response) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", request.getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context = authorize(request, response);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", request.getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
LOGGER.debugv("Returning authorization context with permissions:");
|
||||
for (Permission permission : context.getPermissions()) {
|
||||
LOGGER.debug(permission);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public AuthzClient getAuthzClient() {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPaths() {
|
||||
return Collections.unmodifiableMap(paths);
|
||||
}
|
||||
|
||||
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
|
||||
return claimInformationPointProviderFactories;
|
||||
}
|
||||
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
private AuthorizationContext authorize(HttpRequest request, HttpResponse response) {
|
||||
EnforcementMode enforcementMode = enforcerConfig.getEnforcementMode();
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
boolean anonymous = principal == null || principal.getRawToken() == null;
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
if (anonymous) {
|
||||
response.sendError(401, "Invalid bearer");
|
||||
}
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
PathConfig pathConfig = getPathConfig(request);
|
||||
|
||||
if (anonymous) {
|
||||
if (!isDefaultAccessDeniedUri(request)) {
|
||||
if (pathConfig != null) {
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
} else {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), request, response);
|
||||
}
|
||||
} else {
|
||||
handleAccessDenied(response);
|
||||
}
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
AccessToken accessToken = principal.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
}
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
|
||||
}
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(response);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, request);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, request, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken original = accessToken;
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, request, claims);
|
||||
|
||||
if (accessToken != null) {
|
||||
AccessToken.Authorization authorization = original.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
authorization = new AccessToken.Authorization();
|
||||
authorization.setPermissions(new ArrayList<Permission>());
|
||||
}
|
||||
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
grantedPermissions.add(newPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, request, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
}
|
||||
|
||||
if (!challenge(pathConfig, methodConfig, request, response)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
}
|
||||
handleAccessDenied(response);
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, HttpRequest request, Map<String, List<String>> claims) {
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasPermission = false;
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
pathMatcher.removeFromCache(getPath(request));
|
||||
}
|
||||
|
||||
return hasValidClaims(permission, claims);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
resolveClaims(claims, enforcerConfig.getClaimInformationPointConfig(), request);
|
||||
resolveClaims(claims, pathConfig.getClaimInformationPointConfig(), request);
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, HttpRequest request, HttpResponse response) {
|
||||
if (isBearerAuthorization(request)) {
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, request);
|
||||
|
||||
if (ticket != null) {
|
||||
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
|
||||
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
|
||||
response.sendError(401);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending challenge");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleAccessDenied(response);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(HttpResponse response) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
response.setHeader("Location", accessDeniedPath);
|
||||
response.sendError(302);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasValidClaims(Permission permission, Map<String, List<String>> claims) {
|
||||
Map<String, Set<String>> grantedClaims = permission.getClaims();
|
||||
|
||||
if (grantedClaims != null) {
|
||||
if (claims.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
|
||||
List<String> requestClaims = claims.get(entry.getKey());
|
||||
|
||||
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(HttpRequest request) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||
List<String> requiredScopes = methodConfig.getScopes();
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (allowedScopes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
|
||||
return allowedScopes.containsAll(requiredScopes);
|
||||
}
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
|
||||
for (String requiredScope : requiredScopes) {
|
||||
if (allowedScopes.contains(requiredScope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requiredScopes.isEmpty();
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new ClientAuthorizationContext(authzClient) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Permission> getPermissions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getPath(HttpRequest request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
|
||||
private MethodConfig getRequiredScopes(PathConfig pathConfig, HttpRequest request) {
|
||||
String method = request.getMethod();
|
||||
|
||||
for (MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||
if (methodConfig.getMethod().equals(method)) {
|
||||
return methodConfig;
|
||||
}
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
|
||||
methodConfig.setMethod(request.getMethod());
|
||||
List scopes = new ArrayList<>();
|
||||
|
||||
if (Boolean.TRUE.equals(enforcerConfig.getHttpMethodAsScope())) {
|
||||
scopes.add(request.getMethod());
|
||||
} else {
|
||||
scopes.addAll(pathConfig.getScopes());
|
||||
}
|
||||
|
||||
methodConfig.setScopes(scopes);
|
||||
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
|
||||
|
||||
return methodConfig;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, authzClient);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
|
||||
private PathConfig getPathConfig(HttpRequest request) {
|
||||
return isDefaultAccessDeniedUri(request) ? null : pathMatcher.matches(getPath(request));
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, HttpRequest request, Map<String, List<String>> claims) {
|
||||
if (enforcerConfig.getUserManagedAccess() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
String accessTokenString = principal.getRawToken();
|
||||
AccessToken accessToken = principal.getToken();
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest();
|
||||
|
||||
if (isBearerAuthorization(request) || accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
|
||||
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse;
|
||||
|
||||
if (isBearerAuthorization(request)) {
|
||||
authzRequest.setSubjectToken(accessTokenString);
|
||||
authzResponse = authzClient.authorization().authorize(authzRequest);
|
||||
} else {
|
||||
authzResponse = authzClient.authorization(accessTokenString).authorize(authzRequest);
|
||||
}
|
||||
|
||||
if (authzResponse != null) {
|
||||
return asAccessToken(authzResponse.getToken());
|
||||
}
|
||||
} catch (AuthorizationDeniedException ignore) {
|
||||
LOGGER.debug("Authorization denied", ignore);
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Authorization failed", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, MethodConfig methodConfig, AuthzClient authzClient, HttpRequest httpFacade) {
|
||||
if (enforcerConfig.getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
permissionRequest.setClaims(claims);
|
||||
}
|
||||
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBearerAuthorization(HttpRequest request) {
|
||||
List<String> authHeaders = request.getHeaders("Authorization");
|
||||
|
||||
if (authHeaders != null) {
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return authzClient.getConfiguration().isBearerOnly();
|
||||
}
|
||||
|
||||
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
|
||||
for (ClaimInformationPointProviderFactory factory : loader) {
|
||||
factory.init(this);
|
||||
|
||||
claimInformationPointProviderFactories.put(factory.getName(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveClaims(Map<String, List<String>> claims, Map<String, Map<String, Object>> claimInformationPointConfig, HttpRequest request) {
|
||||
if (claimInformationPointConfig != null) {
|
||||
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = claimInformationPointProviderFactories.get(claimDef.getKey());
|
||||
|
||||
if (factory != null) {
|
||||
claims.putAll(factory.create(claimDef.getValue()).resolve(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
Configuration authzClientConfig = new Configuration();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder authServerUrl(String authServerUrl) {
|
||||
authzClientConfig.setAuthServerUrl(authServerUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder realm(String realm) {
|
||||
authzClientConfig.setRealm(realm);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientId(String clientId) {
|
||||
authzClientConfig.setResource(clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder bearerOnly(boolean bearerOnly) {
|
||||
authzClientConfig.setBearerOnly(bearerOnly);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder credentials(Map<String, Object> credentials) {
|
||||
authzClientConfig.setCredentials(credentials);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enforcerConfig(PolicyEnforcerConfig enforcerConfig) {
|
||||
authzClientConfig.setPolicyEnforcerConfig(enforcerConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enforcerConfig(InputStream is) {
|
||||
try {
|
||||
enforcerConfig(JsonSerialization.readValue(is, PolicyEnforcerConfig.class));
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to read configuration", cause);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder httpClient(HttpClient httpClient) {
|
||||
authzClientConfig.setHttpClient(httpClient);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder credentialProvider(ClientCredentialsProvider credentialsProvider) {
|
||||
authzClientConfig.setClientCredentialsProvider(credentialsProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyEnforcer build() {
|
||||
return new PolicyEnforcer(this);
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return authzClientConfig.getPolicyEnforcerConfig();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
/**
|
||||
* A {@link Principal} backed by a token representing the entity requesting permissions.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface TokenPrincipal extends Principal {
|
||||
|
||||
/**
|
||||
* The token in its raw format.
|
||||
*
|
||||
* @return the token in its raw format.
|
||||
*/
|
||||
String getRawToken();
|
||||
|
||||
/**
|
||||
* The {@link AccessToken} representation of {@link TokenPrincipal#getRawToken()}.
|
||||
*
|
||||
* @return the access token representation
|
||||
*/
|
||||
default AccessToken getToken() {
|
||||
return JsonUtils.asAccessToken(getRawToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the entity represented by the token.
|
||||
*
|
||||
* @return the name of the principal
|
||||
*/
|
||||
default String getName() {
|
||||
return getToken().getPreferredUsername();
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.cip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
|
||||
public ClaimsInformationPointProvider(Map<String, Object> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
for (Entry<String, Object> configEntry : config.entrySet()) {
|
||||
String claimName = configEntry.getKey();
|
||||
Object claimValue = configEntry.getValue();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (claimValue instanceof String) {
|
||||
values = getValues(claimValue.toString(), request);
|
||||
} else if (claimValue instanceof Collection) {
|
||||
|
||||
for (Object value : Collection.class.cast(claimValue)) {
|
||||
List<String> resolvedValues = getValues(value.toString(), request);
|
||||
|
||||
if (!resolvedValues.isEmpty()) {
|
||||
values.addAll(resolvedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.isEmpty()) {
|
||||
claims.put(claimName, values);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private List<String> getValues(String value, HttpRequest httpFacade) {
|
||||
return PlaceHolders.resolve(value, httpFacade);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProviderFactory implements ClaimInformationPointProviderFactory<ClaimsInformationPointProvider> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "claims";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClaimsInformationPointProvider create(Map<String, Object> config) {
|
||||
return new ClaimsInformationPointProvider(config);
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.cip;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpClaimInformationPointProvider(Map<String, Object> config, HttpClient httpClient) {
|
||||
this.config = config;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
try {
|
||||
InputStream responseStream = executeRequest(request);
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(responseStream)) {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
Map<String, Object> claimsDef = (Map<String, Object>) config.get("claims");
|
||||
|
||||
if (claimsDef == null) {
|
||||
Iterator<String> nodeNames = jsonNode.fieldNames();
|
||||
|
||||
while (nodeNames.hasNext()) {
|
||||
String nodeName = nodeNames.next();
|
||||
claims.put(nodeName, JsonUtils.getValues(jsonNode.get(nodeName)));
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Object> claimDef : claimsDef.entrySet()) {
|
||||
List<String> jsonPaths = new ArrayList<>();
|
||||
|
||||
if (claimDef.getValue() instanceof Collection) {
|
||||
jsonPaths.addAll(Collection.class.cast(claimDef.getValue()));
|
||||
} else {
|
||||
jsonPaths.add(claimDef.getValue().toString());
|
||||
}
|
||||
|
||||
List<String> claimValues = new ArrayList<>();
|
||||
|
||||
for (String path : jsonPaths) {
|
||||
claimValues.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
|
||||
claims.put(claimDef.getKey(), claimValues);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not obtain claims from http claim information point [" + config.get("url") + "] response", cause);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream executeRequest(HttpRequest request) {
|
||||
String method = config.get("method").toString();
|
||||
|
||||
if (method == null) {
|
||||
method = "GET";
|
||||
}
|
||||
|
||||
RequestBuilder builder = null;
|
||||
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
builder = RequestBuilder.get();
|
||||
} else {
|
||||
builder = RequestBuilder.post();
|
||||
}
|
||||
|
||||
builder.setUri(config.get("url").toString());
|
||||
|
||||
byte[] bytes = new byte[0];
|
||||
|
||||
try {
|
||||
setParameters(builder, request);
|
||||
|
||||
if (config.containsKey("headers")) {
|
||||
setHeaders(builder, request);
|
||||
}
|
||||
|
||||
HttpResponse response = httpClient.execute(builder.build());
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) {
|
||||
bytes = EntityUtils.toByteArray(entity);
|
||||
}
|
||||
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
int statusCode = statusLine.getStatusCode();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(bytes);
|
||||
} catch (Exception cause) {
|
||||
try {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]. Response : " + StreamUtil.readString(new ByteArrayInputStream(bytes), Charset.forName("UTF-8")), cause);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeaders(RequestBuilder builder, HttpRequest request) {
|
||||
Object headersDef = config.get("headers");
|
||||
|
||||
if (headersDef != null) {
|
||||
Map<String, Object> headers = Map.class.cast(headersDef);
|
||||
|
||||
for (Entry<String, Object> header : headers.entrySet()) {
|
||||
Object value = header.getValue();
|
||||
List<String> headerValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
|
||||
for (Object item : values) {
|
||||
headerValues.addAll(PlaceHolders.resolve(item.toString(), request));
|
||||
}
|
||||
} else {
|
||||
headerValues.addAll(PlaceHolders.resolve(value.toString(), request));
|
||||
}
|
||||
|
||||
for (String headerValue : headerValues) {
|
||||
builder.addHeader(header.getKey(), headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setParameters(RequestBuilder builder, HttpRequest request) {
|
||||
Object config = this.config.get("parameters");
|
||||
|
||||
if (config != null) {
|
||||
Map<String, Object> paramsDef = Map.class.cast(config);
|
||||
|
||||
for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
|
||||
Object value = paramDef.getValue();
|
||||
List<String> paramValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
|
||||
for (Object item : values) {
|
||||
paramValues.addAll(PlaceHolders.resolve(item.toString(), request));
|
||||
}
|
||||
} else {
|
||||
paramValues.addAll(PlaceHolders.resolve(value.toString(), request));
|
||||
}
|
||||
|
||||
for (String paramValue : paramValues) {
|
||||
builder.addParameter(paramDef.getKey(), paramValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<HttpClaimInformationPointProvider> {
|
||||
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClaimInformationPointProvider create(Map<String, Object> config) {
|
||||
return new HttpClaimInformationPointProvider(config, policyEnforcer.getHttpClient());
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.cip.spi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProvider {
|
||||
|
||||
Map<String, List<String>> resolve(HttpRequest request);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.cip.spi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProviderFactory<C extends ClaimInformationPointProvider> {
|
||||
|
||||
String getName();
|
||||
|
||||
default void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
C create(Map<String, Object> config);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package org.keycloak.adapters.authorization.integration.elytron;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.integration.jakarta.ServletPolicyEnforcerFilter;
|
||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.wildfly.security.http.oidc.OidcClientConfiguration;
|
||||
import org.wildfly.security.http.oidc.OidcPrincipal;
|
||||
import org.wildfly.security.http.oidc.RefreshableOidcSecurityContext;
|
||||
|
||||
public class ElytronPolicyEnforcerFilter extends ServletPolicyEnforcerFilter {
|
||||
|
||||
public ElytronPolicyEnforcerFilter(ConfigurationResolver configResolver) {
|
||||
super(configResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String extractBearerToken(HttpServletRequest request) {
|
||||
Principal principal = request.getUserPrincipal();
|
||||
|
||||
if (principal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OidcPrincipal oidcPrincipal = (OidcPrincipal) principal;
|
||||
RefreshableOidcSecurityContext securityContext = (RefreshableOidcSecurityContext) oidcPrincipal.getOidcSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return securityContext.getTokenString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PolicyEnforcer createPolicyEnforcer(HttpServletRequest servletRequest, PolicyEnforcerConfig enforcerConfig) {
|
||||
RefreshableOidcSecurityContext securityContext = (RefreshableOidcSecurityContext) ((OidcPrincipal) servletRequest.getUserPrincipal()).getOidcSecurityContext();
|
||||
OidcClientConfiguration configuration = securityContext.getOidcClientConfiguration();
|
||||
String authServerUrl = configuration.getAuthServerBaseUrl();
|
||||
|
||||
return PolicyEnforcer.builder()
|
||||
.authServerUrl(authServerUrl)
|
||||
.realm(configuration.getRealm())
|
||||
.clientId(configuration.getClientId())
|
||||
.credentials(configuration.getResourceCredentials())
|
||||
.bearerOnly(false)
|
||||
.enforcerConfig(enforcerConfig)
|
||||
.httpClient(configuration.getClient()).build();
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package org.keycloak.adapters.authorization.integration.elytron;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletContextEvent;
|
||||
import jakarta.servlet.ServletContextListener;
|
||||
import jakarta.servlet.annotation.WebListener;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* A {@link ServletContextListener} to programmatically configure the {@link ServletContext} in order to
|
||||
* enable the policy enforcer.</p>
|
||||
*
|
||||
* By default, the policy enforcer configuration is loaded from a file at {@code WEB-INF/policy-enforcer.json}.</p>
|
||||
*
|
||||
* Applications can also dynamically resolve the configuration by implementing the {@link ConfigurationResolver} SPI. For that,
|
||||
* make sure to create a {@link META-INF/services/org.keycloak.adapters.authorization.spi.ConfigurationResolver} to register
|
||||
* the implementation.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@WebListener
|
||||
public class PolicyEnforcerServletContextListener implements ServletContextListener {
|
||||
|
||||
private final Logger logger = Logger.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext servletContext = sce.getServletContext();
|
||||
Iterator<ConfigurationResolver> configResolvers = ServiceLoader.load(ConfigurationResolver.class).iterator();
|
||||
ConfigurationResolver configResolver;
|
||||
|
||||
if (configResolvers.hasNext()) {
|
||||
configResolver = configResolvers.next();
|
||||
|
||||
if (configResolvers.hasNext()) {
|
||||
throw new IllegalStateException("Multiple " + ConfigurationResolver.class.getName() + " implementations found");
|
||||
}
|
||||
|
||||
logger.debugf("Configuration resolver found from classpath: %s", configResolver);
|
||||
} else {
|
||||
String enforcerConfigLocation = "WEB-INF/policy-enforcer.json";
|
||||
InputStream config = servletContext.getResourceAsStream(enforcerConfigLocation);
|
||||
|
||||
if (config == null) {
|
||||
logger.debugf("Could not find the policy enforcer configuration file: %s", enforcerConfigLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
configResolver = createDefaultConfigurationResolver(JsonSerialization.readValue(config, PolicyEnforcerConfig.class));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to parse policy enforcer configuration: " + enforcerConfigLocation);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Policy enforcement filter is enabled.");
|
||||
|
||||
servletContext.addFilter("keycloak-policy-enforcer", new ElytronPolicyEnforcerFilter(configResolver))
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
}
|
||||
|
||||
private ConfigurationResolver createDefaultConfigurationResolver(PolicyEnforcerConfig enforcerConfig) {
|
||||
return new ConfigurationResolver() {
|
||||
@Override
|
||||
public PolicyEnforcerConfig resolve(HttpRequest request) {
|
||||
return enforcerConfig;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.integration.elytron;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ServletHttpRequest implements HttpRequest {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
private final TokenPrincipal tokenPrincipal;
|
||||
private InputStream inputStream;
|
||||
|
||||
public ServletHttpRequest(HttpServletRequest request, TokenPrincipal tokenPrincipal) {
|
||||
this.request = request;
|
||||
this.tokenPrincipal = tokenPrincipal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelativePath() {
|
||||
String uri = request.getRequestURI();
|
||||
String contextPath = request.getContextPath();
|
||||
String servletPath = uri.substring(uri.indexOf(contextPath) + contextPath.length());
|
||||
|
||||
if ("".equals(servletPath)) {
|
||||
servletPath = "/";
|
||||
}
|
||||
|
||||
return servletPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return request.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
return Collections.list(request.getHeaders(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String name) {
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
String[] values = parameters.get(name);
|
||||
|
||||
if (values == null || values.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return values[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCookieValue(String name) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName().equals(name)) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return request.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return request.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(boolean buffered) {
|
||||
if (inputStream != null) {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
if (buffered) {
|
||||
try {
|
||||
return inputStream = new BufferedInputStream(request.getInputStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return request.getInputStream();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenPrincipal getPrincipal() {
|
||||
return tokenPrincipal;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.integration.elytron;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ServletHttpResponse implements HttpResponse {
|
||||
|
||||
private HttpServletResponse response;
|
||||
|
||||
public ServletHttpResponse(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int status) {
|
||||
try {
|
||||
response.sendError(status);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int status, String reason) {
|
||||
try {
|
||||
response.sendError(status, reason);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
response.setHeader(name, value);
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package org.keycloak.adapters.authorization.integration.jakarta;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletContextAttributeListener;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpRequest;
|
||||
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpResponse;
|
||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.wildfly.security.http.oidc.OidcClientConfiguration;
|
||||
import org.wildfly.security.http.oidc.OidcPrincipal;
|
||||
import org.wildfly.security.http.oidc.RefreshableOidcSecurityContext;
|
||||
|
||||
/**
|
||||
* A Jakarta Servlet {@link Filter} acting as a policy enforcer. This filter does not enforce access for anonymous subjects.</p>
|
||||
*
|
||||
* For authenticated subjects, this filter delegates the access decision to the {@link PolicyEnforcer} and decide if
|
||||
* the request should continue.</p>
|
||||
*
|
||||
* If access is not granted, this filter aborts the request and relies on the {@link PolicyEnforcer} to properly
|
||||
* respond to client.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ServletPolicyEnforcerFilter implements Filter, ServletContextAttributeListener {
|
||||
|
||||
private final Logger logger = Logger.getLogger(getClass());
|
||||
private final Map<PolicyEnforcerConfig, PolicyEnforcer> policyEnforcer;
|
||||
private final ConfigurationResolver configResolver;
|
||||
|
||||
public ServletPolicyEnforcerFilter(ConfigurationResolver configResolver) {
|
||||
this.configResolver = configResolver;
|
||||
this.policyEnforcer = Collections.synchronizedMap(new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
// no-init
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
ServletHttpRequest httpRequest = new ServletHttpRequest(request, new TokenPrincipal() {
|
||||
@Override
|
||||
public String getRawToken() {
|
||||
return extractBearerToken(request);
|
||||
}
|
||||
});
|
||||
|
||||
PolicyEnforcer policyEnforcer = getOrCreatePolicyEnforcer(request, httpRequest);
|
||||
AuthorizationContext authzContext = policyEnforcer.enforce(httpRequest, new ServletHttpResponse(response));
|
||||
|
||||
request.setAttribute(AuthorizationContext.class.getName(), authzContext);
|
||||
|
||||
if (authzContext.isGranted()) {
|
||||
logger.debug("Request authorized, continuing the filter chain");
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
} else {
|
||||
logger.debugf("Unauthorized request to path [%s], aborting the filter chain", request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
protected String extractBearerToken(HttpServletRequest request) {
|
||||
Enumeration<String> authorizationHeaderValues = request.getHeaders("Authorization");
|
||||
|
||||
while (authorizationHeaderValues.hasMoreElements()) {
|
||||
String value = authorizationHeaderValues.nextElement();
|
||||
String[] parts = value.trim().split("\\s+");
|
||||
|
||||
if (parts.length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String bearer = parts[0];
|
||||
|
||||
if (bearer.equalsIgnoreCase("Bearer")) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private PolicyEnforcer getOrCreatePolicyEnforcer(HttpServletRequest servletRequest, HttpRequest request) {
|
||||
return policyEnforcer.computeIfAbsent(configResolver.resolve(request), new Function<PolicyEnforcerConfig, PolicyEnforcer>() {
|
||||
@Override
|
||||
public PolicyEnforcer apply(PolicyEnforcerConfig enforcerConfig) {
|
||||
return createPolicyEnforcer(servletRequest, enforcerConfig);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected PolicyEnforcer createPolicyEnforcer(HttpServletRequest servletRequest, PolicyEnforcerConfig enforcerConfig) {
|
||||
String authServerUrl = enforcerConfig.getAuthServerUrl();
|
||||
|
||||
return PolicyEnforcer.builder()
|
||||
.authServerUrl(authServerUrl)
|
||||
.realm(enforcerConfig.getRealm())
|
||||
.clientId(enforcerConfig.getResource())
|
||||
.credentials(enforcerConfig.getCredentials())
|
||||
.bearerOnly(false)
|
||||
.enforcerConfig(enforcerConfig).build();
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.spi;
|
||||
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
|
||||
/**
|
||||
* Resolves a {@link PolicyEnforcerConfig} based on the information from the {@link HttpRequest}.</p>
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ConfigurationResolver {
|
||||
|
||||
/**
|
||||
* Resolves a {@link PolicyEnforcerConfig} based on the information from the {@link HttpRequest}.
|
||||
*
|
||||
* @param request the request
|
||||
* @return the policy enforcer configuration for the given request
|
||||
*/
|
||||
PolicyEnforcerConfig resolve(HttpRequest request);
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.spi;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
|
||||
/**
|
||||
* Represents an incoming HTTP request and the contract to manipulate it.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface HttpRequest {
|
||||
|
||||
/**
|
||||
* Get the request path. This is the path relative to the context path.
|
||||
* E.g.: for a HTTP GET request to http://my.appserver.com/my-application/path/sub-path this method is going to return /path/sub-path.
|
||||
|
||||
* @return the relative path
|
||||
*/
|
||||
String getRelativePath();
|
||||
|
||||
/**
|
||||
* Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
|
||||
*
|
||||
* @return a {@code String} specifying the name of the method with which this request was made
|
||||
*/
|
||||
String getMethod();
|
||||
|
||||
/**
|
||||
* Get the URI representation for the current request.
|
||||
*
|
||||
* @return a {@code String} representation for the current request
|
||||
*/
|
||||
String getURI();
|
||||
|
||||
/**
|
||||
* Get a list of all of the values set for the specified header within the HTTP request.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return a list of the values set for this header, if the header is not set on the request then null should be returned
|
||||
*/
|
||||
List<String> getHeaders(String name);
|
||||
|
||||
/**
|
||||
* Get the first value for a parameter with the given {@code name}
|
||||
*
|
||||
* @param name the parameter name
|
||||
* @return the value of the parameter
|
||||
*/
|
||||
String getFirstParam(String name);
|
||||
|
||||
/**
|
||||
* Get the first value for a cookie with the given {@code name}.
|
||||
*
|
||||
* @param name the parameter name
|
||||
* @return the value of the cookie
|
||||
*/
|
||||
String getCookieValue(String name);
|
||||
|
||||
/**
|
||||
* Returns the client address.
|
||||
*
|
||||
* @return the client address.
|
||||
*/
|
||||
String getRemoteAddr();
|
||||
|
||||
/**
|
||||
* Indicates if the request is coming from a secure channel through HTTPS.
|
||||
*
|
||||
* @return {@code true} if the HTTP scheme is set to 'https'. Otherwise, {@code false}
|
||||
*/
|
||||
boolean isSecure();
|
||||
|
||||
/**
|
||||
* Get the first value for a HEADER with the given {@code name}.
|
||||
*
|
||||
* @param name the HEADER name
|
||||
* @return the value of the HEADER
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
/**
|
||||
* Returns the request input stream
|
||||
*
|
||||
* @param buffered if the input stream should be buffered and support for multiple reads
|
||||
* @return the request input stream
|
||||
*/
|
||||
InputStream getInputStream(boolean buffered);
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenPrincipal} associated with the request.
|
||||
*
|
||||
* @return the principal
|
||||
*/
|
||||
TokenPrincipal getPrincipal();
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.adapters.authorization.spi;
|
||||
|
||||
/**
|
||||
* Represents an outgoing HTTP response and the contract to manipulate it.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface HttpResponse {
|
||||
|
||||
/**
|
||||
* Send an error with the given {@code statusCode}.
|
||||
*
|
||||
* @param statusCode the status to set in the response
|
||||
*/
|
||||
void sendError(int statusCode);
|
||||
|
||||
/**
|
||||
* Send an error with the given {@code statusCode} and {@code reason} message.
|
||||
*
|
||||
* @param statusCode the status to set in the response
|
||||
*/
|
||||
void sendError(int statusCode, String reason);
|
||||
|
||||
/**
|
||||
* Set a header with the given {@code name} and {@code value}.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
void setHeader(String name, String value);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* Utility methods to manipulate JSON data
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode, String path) {
|
||||
return getValues(jsonNode.at(path));
|
||||
}
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode) {
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (jsonNode.isArray()) {
|
||||
for (JsonNode node : jsonNode) {
|
||||
String value;
|
||||
|
||||
if (node.isObject()) {
|
||||
try {
|
||||
value = JsonSerialization.writeValueAsString(node);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
value = node.asText();
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String value = jsonNode.asText();
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static AccessToken asAccessToken(String rawToken) {
|
||||
try {
|
||||
return new JWSInput(rawToken).readJsonContent(AccessToken.class);
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to decode token", cause);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
public static final String NAME = "keycloak";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpRequest request) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
TokenPrincipal principal = request.getPrincipal();
|
||||
|
||||
if (source.endsWith("access_token")) {
|
||||
return Arrays.asList(principal.getRawToken());
|
||||
}
|
||||
|
||||
JsonNode jsonNode;
|
||||
|
||||
if (source.startsWith("access_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(principal.getToken());
|
||||
} else {
|
||||
throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
|
||||
}
|
||||
|
||||
return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PlaceHolderResolver {
|
||||
|
||||
List<String> resolve(String placeHolder, HttpRequest httpFacade);
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PlaceHolders {
|
||||
|
||||
private static Map<String, PlaceHolderResolver> resolvers = new HashMap<>();
|
||||
|
||||
static {
|
||||
resolvers.put(RequestPlaceHolderResolver.NAME, new RequestPlaceHolderResolver());
|
||||
resolvers.put(KeycloakSecurityContextPlaceHolderResolver.NAME, new KeycloakSecurityContextPlaceHolderResolver());
|
||||
}
|
||||
|
||||
private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
|
||||
private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
|
||||
|
||||
public static List<String> resolve(String value, HttpRequest httpFacade) {
|
||||
Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
|
||||
|
||||
if (!placeHolders.isEmpty()) {
|
||||
value = formatPlaceHolder(value);
|
||||
|
||||
for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
|
||||
List<String> values = entry.getValue();
|
||||
|
||||
if (values.isEmpty() || values.size() > 1) {
|
||||
return values;
|
||||
}
|
||||
|
||||
value = value.replaceAll(entry.getKey(), values.get(0)).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
|
||||
static String getParameter(String source, String messageIfNotFound) {
|
||||
Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);
|
||||
|
||||
while (matcher.find()) {
|
||||
return matcher.group(1).replaceAll("'", "");
|
||||
}
|
||||
|
||||
if (messageIfNotFound != null) {
|
||||
throw new RuntimeException(messageIfNotFound);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> parsePlaceHolders(String value, HttpRequest httpFacade) {
|
||||
Map<String, List<String>> placeHolders = Collections.emptyMap();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
|
||||
boolean found = matcher.find();
|
||||
|
||||
if (found) {
|
||||
placeHolders = new HashMap<>();
|
||||
do {
|
||||
String placeHolder = matcher.group(1);
|
||||
int resolverNameIdx = placeHolder.indexOf('.');
|
||||
|
||||
if (resolverNameIdx == -1) {
|
||||
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
|
||||
}
|
||||
|
||||
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
|
||||
|
||||
if (resolver != null) {
|
||||
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
|
||||
|
||||
if (resolved != null) {
|
||||
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
|
||||
}
|
||||
}
|
||||
} while (matcher.find());
|
||||
}
|
||||
|
||||
return placeHolders;
|
||||
}
|
||||
|
||||
private static String formatPlaceHolder(String placeHolder) {
|
||||
return placeHolder.replaceAll("\\{", "").replace("}", "").replace("[", "").replace("]", "").replace("[", "").replace("]", "");
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* 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.adapters.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class RequestPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
static String NAME = "request";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpRequest request) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
|
||||
if (source.startsWith("parameter")) {
|
||||
String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
|
||||
String parameterValue = request.getFirstParam(parameterName);
|
||||
|
||||
if (parameterValue != null) {
|
||||
return Arrays.asList(parameterValue);
|
||||
}
|
||||
} else if (source.startsWith("header")) {
|
||||
String headerName = getParameter(source, "Could not obtain header name from placeholder [" + source + "]");
|
||||
List<String> headerValue = request.getHeaders(headerName);
|
||||
|
||||
if (headerValue != null) {
|
||||
return headerValue;
|
||||
}
|
||||
} else if (source.startsWith("cookie")) {
|
||||
String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
|
||||
String cookieValue = request.getCookieValue(cookieName);
|
||||
|
||||
if (cookieValue != null) {
|
||||
return Arrays.asList(cookieValue);
|
||||
}
|
||||
} else if (source.startsWith("remoteAddr")) {
|
||||
String value = request.getRemoteAddr();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("method")) {
|
||||
String value = request.getMethod();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("uri")) {
|
||||
String value = request.getURI();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("relativePath")) {
|
||||
String value = request.getRelativePath();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("secure")) {
|
||||
return Arrays.asList(String.valueOf(request.isSecure()));
|
||||
} else if (source.startsWith("body")) {
|
||||
String contentType = request.getHeader("Content-Type");
|
||||
|
||||
if (contentType == null) {
|
||||
contentType = "";
|
||||
} else if (contentType.indexOf(';') != -1){
|
||||
contentType = contentType.substring(0, contentType.indexOf(';')).trim();
|
||||
}
|
||||
|
||||
InputStream body = request.getInputStream(true);
|
||||
|
||||
try {
|
||||
if (body == null || body.available() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to check available bytes in request input stream", cause);
|
||||
}
|
||||
|
||||
if (body.markSupported()) {
|
||||
body.mark(0);
|
||||
}
|
||||
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
try {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
try {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(new BufferedInputStream(body) {
|
||||
@Override
|
||||
public void close() {
|
||||
// we can't close the stream because it may be used later by the application
|
||||
}
|
||||
});
|
||||
String path = getParameter(source, null);
|
||||
|
||||
if (path == null) {
|
||||
values.addAll(JsonUtils.getValues(jsonNode));
|
||||
} else {
|
||||
values.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request JSON body", cause);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
StringBuilder value = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(body));
|
||||
|
||||
try {
|
||||
int ch;
|
||||
|
||||
while ((ch = reader.read()) != -1) {
|
||||
value.append((char) ch);
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request body", cause);
|
||||
}
|
||||
|
||||
values.add(value.toString());
|
||||
}
|
||||
} finally {
|
||||
if (body.markSupported()) {
|
||||
try {
|
||||
body.reset();
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to reset request input stream", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#
|
||||
# * 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.
|
||||
#
|
||||
|
||||
org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory
|
||||
org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory
|
|
@ -20,6 +20,5 @@
|
|||
<modules>
|
||||
<module>policy</module>
|
||||
<module>client</module>
|
||||
<module>policy-enforcer</module>
|
||||
</modules>
|
||||
</project>
|
5
pom.xml
5
pom.xml
|
@ -1052,11 +1052,6 @@
|
|||
<artifactId>keycloak-authz-policy-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus -->
|
||||
<dependency>
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 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.util;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AuthzTestUtils {
|
||||
|
||||
private AuthzTestUtils() {
|
||||
}
|
||||
|
||||
public static InputStream httpsAwareConfigurationStream(InputStream input) throws IOException {
|
||||
if (!ServerURLs.AUTH_SERVER_SSL_REQUIRED) {
|
||||
return input;
|
||||
}
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try (PrintWriter pw = new PrintWriter(out);
|
||||
Scanner s = new Scanner(input)) {
|
||||
while (s.hasNextLine()) {
|
||||
String lineWithReplaces = s.nextLine().replace("http://localhost:8180/auth",
|
||||
ServerURLs.AUTH_SERVER_SCHEME + "://localhost:" + ServerURLs.AUTH_SERVER_PORT + "/auth");
|
||||
pw.println(lineWithReplaces);
|
||||
}
|
||||
}
|
||||
return new ByteArrayInputStream(out.toByteArray());
|
||||
}
|
||||
|
||||
public static InputStream getAdapterConfiguration(String fileName) {
|
||||
try {
|
||||
return httpsAwareConfigurationStream(AuthzTestUtils.class.getResourceAsStream("/authorization-test/" + fileName));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Could not load keycloak configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PolicyEnforcer createPolicyEnforcer(String resource, boolean bearerOnly) {
|
||||
try (InputStream is = getAdapterConfiguration(resource)) {
|
||||
return PolicyEnforcer.builder().enforcerConfig(is).bearerOnly(bearerOnly).build();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Invalid resource " + resource, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path) {
|
||||
return createHttpRequest(path, null, null, null, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path, String token) {
|
||||
return createHttpRequest(path, null, null, token, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path, String token, String method) {
|
||||
return createHttpRequest(path, null, method, token, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path, String token, Map<String, List<String>> parameters) {
|
||||
return createHttpRequest(path, null, null, token, Collections.emptyMap(), parameters, Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path, String method, String token, Map<String,
|
||||
List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
|
||||
return createHttpRequest(path, null, method, token, headers, parameters, Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public static HttpRequest createHttpRequest(String path, String relativePath, String method, String token, Map<String,
|
||||
List<String>> headers, Map<String, List<String>> parameters, Map<String, String> cookies, InputStream requestBody) {
|
||||
return new HttpRequest() {
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
@Override
|
||||
public String getRelativePath() {
|
||||
return relativePath != null? relativePath : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return method == null ? "GET" : method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
return headers.getOrDefault(name, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String name) {
|
||||
List<String> values = parameters.getOrDefault(name, Collections.emptyList());
|
||||
return values.isEmpty()? null : values.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCookieValue(String name) {
|
||||
return cookies.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return "user-remote-addr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
List<String> headers = getHeaders(name);
|
||||
return headers.isEmpty()? null : headers.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(boolean buffered) {
|
||||
if (requestBody == null) {
|
||||
return new ByteArrayInputStream(new byte[] {});
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
if (buffered) {
|
||||
return inputStream = new BufferedInputStream(requestBody);
|
||||
}
|
||||
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenPrincipal getPrincipal() {
|
||||
return () -> token;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class TestResponse implements HttpResponse {
|
||||
|
||||
private final Map<String, List<String>> headers;
|
||||
private int status;
|
||||
|
||||
public TestResponse() {
|
||||
this.headers = new HashMap<>();
|
||||
}
|
||||
|
||||
public TestResponse(Map<String, List<String>> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
headers.put(name, Arrays.asList(value));
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int code) {
|
||||
status = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int code, String message) {
|
||||
status = code;
|
||||
}
|
||||
|
||||
public TestResponse clear() {
|
||||
this.status = -1;
|
||||
this.headers.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,14 +23,15 @@ import static org.junit.Assert.assertNotNull;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.AuthorizationResource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
|
@ -38,6 +39,7 @@ import org.keycloak.admin.client.resource.ClientsResource;
|
|||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
@ -60,6 +62,7 @@ import org.keycloak.testsuite.util.ClientBuilder;
|
|||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RolesBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -346,9 +349,12 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
|
|||
}
|
||||
|
||||
private AuthzClient getAuthzClient(String adapterConfig) {
|
||||
PolicyEnforcer policyEnforcer = PolicyEnforcer.builder().enforcerConfig(getConfigurationStream(adapterConfig)).build();
|
||||
|
||||
return policyEnforcer.getAuthzClient();
|
||||
try {
|
||||
Configuration authzClientConfig = JsonSerialization.readValue(getConfigurationStream(adapterConfig), Configuration.class);
|
||||
return AuthzClient.create(authzClientConfig);
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getConfigurationStream(String adapterConfig) {
|
||||
|
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* 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.authz.admin;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
|
||||
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.TreeNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.handlers.form.FormData;
|
||||
import io.undertow.server.handlers.form.FormDataParser;
|
||||
import io.undertow.server.handlers.form.FormParserFactory;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.util.AuthzTestUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
private static Undertow httpService;
|
||||
|
||||
@BeforeClass
|
||||
public static void enabled() {
|
||||
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void onBeforeClass() {
|
||||
httpService = Undertow.builder().addHttpListener(8989, "localhost").setHandler(exchange -> {
|
||||
if (exchange.isInIoThread()) {
|
||||
try {
|
||||
if (exchange.getRelativePath().equals("/post-claim-information-provider")) {
|
||||
FormParserFactory parserFactory = FormParserFactory.builder().build();
|
||||
FormDataParser parser = parserFactory.createParser(exchange);
|
||||
FormData formData = parser.parseBlocking();
|
||||
|
||||
if (!("Bearer " + accessTokenString()).equals(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
|| !"post".equalsIgnoreCase(exchange.getRequestMethod().toString())
|
||||
|| !"application/x-www-form-urlencoded".equals(exchange.getRequestHeaders().getFirst("Content-Type"))
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value1")
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value2")
|
||||
|| !formData.get("param-a").getFirst().getValue().equals("param-a-value1")
|
||||
|| !formData.get("param-a").getLast().getValue().equals("param-a-value2")
|
||||
|| !formData.get("param-subject").getFirst().getValue().equals("sub")
|
||||
|| !formData.get("param-user-name").getFirst().getValue().equals("username")
|
||||
|| !formData.get("param-other-claims").getFirst().getValue().equals("param-other-claims-value1")
|
||||
|| !formData.get("param-other-claims").getLast().getValue().equals("param-other-claims-value2")) {
|
||||
exchange.setStatusCode(400);
|
||||
return;
|
||||
}
|
||||
|
||||
exchange.setStatusCode(200);
|
||||
} else if (exchange.getRelativePath().equals("/get-claim-information-provider")) {
|
||||
if (!("Bearer " + accessTokenString()).equals(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
|| !"get".equalsIgnoreCase(exchange.getRequestMethod().toString())
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value1")
|
||||
|| !exchange.getRequestHeaders().get("header-b").contains("header-b-value2")
|
||||
|| !exchange.getQueryParameters().get("param-a").contains("param-a-value1")
|
||||
|| !exchange.getQueryParameters().get("param-a").contains("param-a-value2")
|
||||
|| !exchange.getQueryParameters().get("param-subject").contains("sub")
|
||||
|| !exchange.getQueryParameters().get("param-user-name").contains("username")) {
|
||||
exchange.setStatusCode(400);
|
||||
return;
|
||||
}
|
||||
|
||||
exchange.setStatusCode(200);
|
||||
} else {
|
||||
exchange.setStatusCode(404);
|
||||
}
|
||||
} finally {
|
||||
if (exchange.getStatusCode() == 200) {
|
||||
try {
|
||||
ObjectMapper mapper = JsonSerialization.mapper;
|
||||
JsonParser jsonParser = mapper.getFactory().createParser("{\"a\": \"a-value1\", \"b\": \"b-value1\", \"d\": [\"d-value1\", \"d-value2\"]}");
|
||||
TreeNode treeNode = mapper.readTree(jsonParser);
|
||||
exchange.getResponseSender().send(treeNode.toString());
|
||||
} catch (Exception ignore) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
exchange.endExchange();
|
||||
}
|
||||
}
|
||||
}).build();
|
||||
|
||||
httpService.start();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void onAfterClass() {
|
||||
if (httpService != null) {
|
||||
httpService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = loadRealm(getClass().getResourceAsStream("/authorization-test/test-authz-realm.json"));
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
private ClaimInformationPointProvider getClaimInformationProviderForPath(String path, String providerName) {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-config-claims-provider.json", true);
|
||||
Map<String, ClaimInformationPointProviderFactory> providers = policyEnforcer.getClaimInformationPointProviderFactories();
|
||||
|
||||
PathConfig pathConfig = policyEnforcer.getPaths().get(path);
|
||||
|
||||
assertNotNull(pathConfig);
|
||||
|
||||
Map<String, Map<String, Object>> cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
|
||||
assertNotNull(cipConfig);
|
||||
|
||||
ClaimInformationPointProviderFactory factory = providers.get(providerName);
|
||||
|
||||
assertNotNull(factory);
|
||||
|
||||
Map<String, Object> claimsConfig = cipConfig.get(providerName);
|
||||
|
||||
return factory.create(claimsConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicClaimsInformationPoint() {
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims")
|
||||
.resolve(createHttpRequest());
|
||||
|
||||
assertEquals("parameter-a", claims.get("claim-from-request-parameter").get(0));
|
||||
assertEquals("header-b", claims.get("claim-from-header").get(0));
|
||||
assertEquals("cookie-c", claims.get("claim-from-cookie").get(0));
|
||||
assertEquals("user-remote-addr", claims.get("claim-from-remoteAddr").get(0));
|
||||
assertEquals("GET", claims.get("claim-from-method").get(0));
|
||||
assertEquals("/app/request-uri", claims.get("claim-from-uri").get(0));
|
||||
assertEquals("/request-relative-path", claims.get("claim-from-relativePath").get(0));
|
||||
assertEquals("true", claims.get("claim-from-secure").get(0));
|
||||
assertEquals("static value", claims.get("claim-from-static-value").get(0));
|
||||
assertEquals("static", claims.get("claim-from-multiple-static-value").get(0));
|
||||
assertEquals("value", claims.get("claim-from-multiple-static-value").get(1));
|
||||
assertEquals("Test param-other-claims-value1 and parameter-a", claims.get("param-replace-multiple-placeholder").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyJsonClaimsInformationPoint() throws Exception {
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
|
||||
headers.put("Content-Type", Arrays.asList("application/json"));
|
||||
|
||||
ObjectMapper mapper = JsonSerialization.mapper;
|
||||
JsonParser parser = mapper.getFactory().createParser("{\"a\": {\"b\": {\"c\": \"c-value\"}}, \"d\": [\"d-value1\", \"d-value2\"], \"e\": {\"number\": 123}}");
|
||||
TreeNode treeNode = mapper.readTree(parser);
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(
|
||||
createHttpRequest(headers, new ByteArrayInputStream(treeNode.toString().getBytes())));
|
||||
|
||||
assertEquals("c-value", claims.get("claim-from-json-body-object").get(0));
|
||||
assertEquals("d-value2", claims.get("claim-from-json-body-array").get(0));
|
||||
assertEquals("123", claims.get("claim-from-json-body-number").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyJsonObjectClaim() throws Exception {
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
|
||||
headers.put("Content-Type", Arrays.asList("application/json"));
|
||||
|
||||
ObjectMapper mapper = JsonSerialization.mapper;
|
||||
JsonParser parser = mapper.getFactory().createParser("{\"Individual\" : {\n"
|
||||
+ "\n"
|
||||
+ " \"Name\": \"John\",\n"
|
||||
+ "\n"
|
||||
+ " \"Lastname\": \"Doe\",\n"
|
||||
+ "\n"
|
||||
+ " \"individualRoles\" : [ {\n"
|
||||
+ "\n"
|
||||
+ " \"roleSpec\": 2342,\n"
|
||||
+ "\n"
|
||||
+ " \"roleId\": 4234},\n"
|
||||
+ "\n"
|
||||
+ "{\n"
|
||||
+ "\n"
|
||||
+ " \"roleSpec\": 4223,\n"
|
||||
+ "\n"
|
||||
+ " \"roleId\": 523\n"
|
||||
+ "\n"
|
||||
+ " }\n"
|
||||
+ "\n"
|
||||
+ " ]\n"
|
||||
+ "\n"
|
||||
+ "}}");
|
||||
TreeNode treeNode = mapper.readTree(parser);
|
||||
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims")
|
||||
.resolve(createHttpRequest(headers, new ByteArrayInputStream(treeNode.toString().getBytes())));
|
||||
|
||||
assertEquals(1, claims.size());
|
||||
assertEquals(2, claims.get("individualRoles").size());
|
||||
assertEquals("{\"roleSpec\":2342,\"roleId\":4234}", claims.get("individualRoles").get(0));
|
||||
assertEquals("{\"roleSpec\":4223,\"roleId\":523}", claims.get("individualRoles").get(1));
|
||||
|
||||
headers.put("Content-Type", Arrays.asList("application/json; charset=utf-8"));
|
||||
|
||||
claims = getClaimInformationProviderForPath("/claims-from-body-json-object", "claims")
|
||||
.resolve(createHttpRequest(headers, new ByteArrayInputStream(treeNode.toString().getBytes())));
|
||||
|
||||
assertEquals(1, claims.size());
|
||||
assertEquals(2, claims.get("individualRoles").size());
|
||||
assertEquals("{\"roleSpec\":2342,\"roleId\":4234}", claims.get("individualRoles").get(0));
|
||||
assertEquals("{\"roleSpec\":4223,\"roleId\":523}", claims.get("individualRoles").get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyClaimsInformationPoint() {
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims")
|
||||
.resolve(createHttpRequest(new HashMap<>(), new ByteArrayInputStream("raw-body-text".getBytes())));
|
||||
|
||||
assertEquals("raw-body-text", claims.get("claim-from-body").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpClaimInformationPointProviderWithoutClaims() {
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-get-claim-provider", "http")
|
||||
.resolve(createHttpRequest(new HashMap<>(), null));
|
||||
|
||||
assertEquals("a-value1", claims.get("a").get(0));
|
||||
assertEquals("b-value1", claims.get("b").get(0));
|
||||
assertEquals("d-value1", claims.get("d").get(0));
|
||||
assertEquals("d-value2", claims.get("d").get(1));
|
||||
|
||||
assertNull(claims.get("claim-a"));
|
||||
assertNull(claims.get("claim-d"));
|
||||
assertNull(claims.get("claim-d0"));
|
||||
assertNull(claims.get("claim-d-all"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpClaimInformationPointProviderWithClaims() {
|
||||
Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-post-claim-provider", "http")
|
||||
.resolve(createHttpRequest(new HashMap<>(), null));
|
||||
|
||||
assertEquals("a-value1", claims.get("claim-a").get(0));
|
||||
assertEquals("d-value1", claims.get("claim-d").get(0));
|
||||
assertEquals("d-value2", claims.get("claim-d").get(1));
|
||||
assertEquals("d-value1", claims.get("claim-d0").get(0));
|
||||
assertEquals("d-value1", claims.get("claim-d-all").get(0));
|
||||
assertEquals("d-value2", claims.get("claim-d-all").get(1));
|
||||
|
||||
assertNull(claims.get("a"));
|
||||
assertNull(claims.get("b"));
|
||||
assertNull(claims.get("d"));
|
||||
}
|
||||
|
||||
private static HttpRequest createHttpRequest() {
|
||||
return createHttpRequest(new HashMap<>(), null);
|
||||
}
|
||||
|
||||
private static HttpRequest createHttpRequest(Map<String, List<String>> headers, InputStream requestBody) {
|
||||
Map<String, List<String>> queryParameter = new HashMap<>();
|
||||
queryParameter.put("a", Arrays.asList("parameter-a"));
|
||||
headers.put("b", Arrays.asList("header-b"));
|
||||
Map<String, String> cookies = new HashMap<>();
|
||||
cookies.put("c", "cookie-c");
|
||||
return AuthzTestUtils.createHttpRequest("/app/request-uri", "/request-relative-path", "GET",
|
||||
accessTokenString(), headers, queryParameter, cookies, requestBody);
|
||||
}
|
||||
|
||||
private static AccessToken accessToken() {
|
||||
AccessToken token = new AccessToken();
|
||||
token.subject("sub");
|
||||
token.setPreferredUsername("username");
|
||||
token.getOtherClaims().put("custom_claim", Arrays.asList("param-other-claims-value1", "param-other-claims-value2"));
|
||||
return token;
|
||||
}
|
||||
|
||||
private static String accessTokenString() {
|
||||
return new JWSBuilder().jsonContent(accessToken()).none();
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.authz.admin;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.util.AuthzTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
|
||||
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
|
||||
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class EnforcerConfigTest extends AbstractKeycloakTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void enabled() {
|
||||
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = loadRealm(getClass().getResourceAsStream("/authorization-test/test-authz-realm.json"));
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplePathsWithSameName() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-config-paths-same-name.json", true);
|
||||
Map<String, PolicyEnforcerConfig.PathConfig> paths = policyEnforcer.getPaths();
|
||||
assertEquals(1, paths.size());
|
||||
assertEquals(4, paths.values().iterator().next().getMethods().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathConfigClaimInformationPoint() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-config-path-cip.json", true);
|
||||
Map<String, PolicyEnforcerConfig.PathConfig> paths = policyEnforcer.getPaths();
|
||||
|
||||
assertEquals(1, paths.size());
|
||||
|
||||
PathConfig pathConfig = paths.values().iterator().next();
|
||||
Map<String, Map<String, Object>> cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
|
||||
assertEquals(1, cipConfig.size());
|
||||
|
||||
Map<String, Object> claims = cipConfig.get("claims");
|
||||
|
||||
assertNotNull(claims);
|
||||
|
||||
assertEquals(3, claims.size());
|
||||
assertEquals("{request.parameter['a']}", claims.get("claim-a"));
|
||||
assertEquals("{request.header['b']}", claims.get("claim-b"));
|
||||
assertEquals("{request.cookie['c']}", claims.get("claim-c"));
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* 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.authz.admin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
|
||||
public class MyCustomCIPFactory implements ClaimInformationPointProviderFactory<MyCustomCIP> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "my-custom-cip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyCustomCIP create(Map<String, Object> config) {
|
||||
return new MyCustomCIP(config);
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomCIP implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
|
||||
MyCustomCIP(Map<String, Object> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpRequest request) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
claims.put("resolved-claim", Arrays.asList(config.get("claim-value").toString()));
|
||||
|
||||
return claims;
|
||||
}
|
||||
}
|
|
@ -1,386 +0,0 @@
|
|||
/*
|
||||
* 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.authz.admin;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.util.AuthzTestUtils;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RoleBuilder;
|
||||
import org.keycloak.testsuite.util.RolesBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcerClaimsTest extends AbstractKeycloakTest {
|
||||
|
||||
protected static final String REALM_NAME = "authz-test";
|
||||
|
||||
@BeforeClass
|
||||
public static void enabled() {
|
||||
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
testRealms.add(RealmBuilder.create().name(REALM_NAME)
|
||||
.roles(RolesBuilder.create()
|
||||
.realmRole(RoleBuilder.create().name("uma_authorization").build())
|
||||
.realmRole(RoleBuilder.create().name("uma_protection").build())
|
||||
)
|
||||
.user(UserBuilder.create().username("marta").password("password")
|
||||
.addRoles("uma_authorization", "uma_protection")
|
||||
.role("resource-server-test", "uma_protection"))
|
||||
.user(UserBuilder.create().username("kolo").password("password"))
|
||||
.client(ClientBuilder.create().clientId("resource-server-uma-test")
|
||||
.secret("secret")
|
||||
.authorizationServicesEnabled(true)
|
||||
.redirectUris("http://localhost/resource-server-uma-test")
|
||||
.defaultRoles("uma_protection")
|
||||
.directAccessGrants())
|
||||
.client(ClientBuilder.create().clientId("resource-server-test")
|
||||
.secret("secret")
|
||||
.authorizationServicesEnabled(true)
|
||||
.redirectUris("http://localhost/resource-server-test")
|
||||
.defaultRoles("uma_protection")
|
||||
.directAccessGrants())
|
||||
.client(ClientBuilder.create().clientId("public-client-test")
|
||||
.publicClient()
|
||||
.redirectUris("http://localhost:8180/auth/realms/master/app/auth/*", "https://localhost:8543/auth/realms/master/app/auth/*")
|
||||
.directAccessGrants())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
|
||||
initAuthorizationSettings(getClientResource("resource-server-uma-test"));
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-uma-claims-test.json", true);
|
||||
HashMap<String, List<String>> headers = new HashMap<>();
|
||||
HashMap<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
AuthzClient authzClient = policyEnforcer.getAuthzClient();
|
||||
String token = authzClient.obtainAccessToken("marta", "password").getToken();
|
||||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse);
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
AuthorizationRequest request = new AuthorizationRequest();
|
||||
|
||||
request.setTicket(extractTicket(testResponse.getHeaders()));
|
||||
|
||||
AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(request);
|
||||
token = response.getToken();
|
||||
|
||||
assertNotNull(token);
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
|
||||
request = new AuthorizationRequest();
|
||||
|
||||
request.setTicket(extractTicket(testResponse.getHeaders()));
|
||||
|
||||
response = authzClient.authorization("marta", "password").authorize(request);
|
||||
token = response.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "POST", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", "GET", token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
assertEquals(1, context.getPermissions().size());
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
|
||||
assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforceEntitlementAccessWithClaimsWithoutBearerToken() {
|
||||
initAuthorizationSettings(getClientResource("resource-server-test"));
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-entitlement-claims-test.json", false);
|
||||
HashMap<String, List<String>> headers = new HashMap<>();
|
||||
HashMap<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
AuthzClient authzClient = policyEnforcer.getAuthzClient();
|
||||
String token = authzClient.obtainAccessToken("marta", "password").getToken();
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse);
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
assertEquals(1, context.getPermissions().size());
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
assertEquals(1, context.getPermissions().size());
|
||||
permission = context.getPermissions().get(0);
|
||||
assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforceEntitlementAccessWithClaimsWithBearerToken() {
|
||||
initAuthorizationSettings(getClientResource("resource-server-test"));
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-entitlement-claims-test.json", false);
|
||||
HashMap<String, List<String>> headers = new HashMap<>();
|
||||
HashMap<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
AuthzClient authzClient = policyEnforcer.getAuthzClient();
|
||||
String token = authzClient.obtainAccessToken("marta", "password").getToken();
|
||||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse);
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforceEntitlementAccessWithClaimsWithBearerTokenFromPublicClient() {
|
||||
initAuthorizationSettings(getClientResource("resource-server-test"));
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-entitlement-claims-test.json", false);
|
||||
HashMap<String, List<String>> headers = new HashMap<>();
|
||||
HashMap<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
headers.put("Authorization", Arrays.asList("Bearer " + token));
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse);
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("200"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("50"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
parameters.put("withdrawal.amount", Arrays.asList("10"));
|
||||
|
||||
context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/bank/account/1/withdrawal", null, token, headers, parameters, null),
|
||||
testResponse.clear());
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
private String extractTicket(Map<String, List<String>> headers) {
|
||||
List<String> wwwAuthenticateHeader = headers.get("WWW-Authenticate");
|
||||
|
||||
assertNotNull(wwwAuthenticateHeader);
|
||||
assertFalse(wwwAuthenticateHeader.isEmpty());
|
||||
|
||||
String wwwAuthenticate = wwwAuthenticateHeader.get(0);
|
||||
return wwwAuthenticate.substring(wwwAuthenticate.indexOf("ticket=") + "ticket=\"".length(), wwwAuthenticate.lastIndexOf('"'));
|
||||
}
|
||||
|
||||
private void initAuthorizationSettings(ClientResource clientResource) {
|
||||
if (clientResource.authorization().resources().findByName("Bank Account").isEmpty()) {
|
||||
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
||||
|
||||
policy.setName("Withdrawal Limit Policy");
|
||||
policy.setType("script-scripts/enforce-withdraw-limit-policy.js");
|
||||
|
||||
clientResource.authorization().policies().js().create(policy).close();
|
||||
|
||||
createResource(clientResource, "Bank Account", "/api/bank/account/{id}/withdrawal", "withdrawal");
|
||||
|
||||
ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
|
||||
|
||||
permission.setName("Withdrawal Permission");
|
||||
permission.addScope("withdrawal");
|
||||
permission.addPolicy(policy.getName());
|
||||
|
||||
clientResource.authorization().permissions().scope().create(permission).close();
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceRepresentation createResource(ClientResource clientResource, String name, String uri, String... scopes) {
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
representation.setName(name);
|
||||
representation.setUri(uri);
|
||||
representation.setScopes(Arrays.asList(scopes).stream().map(ScopeRepresentation::new).collect(Collectors.toSet()));
|
||||
|
||||
try (jakarta.ws.rs.core.Response response = clientResource.authorization().resources().create(representation)) {
|
||||
|
||||
representation.setId(response.readEntity(ResourceRepresentation.class).getId());
|
||||
|
||||
return representation;
|
||||
}
|
||||
}
|
||||
|
||||
private ClientResource getClientResource(String name) {
|
||||
ClientsResource clients = realmsResouce().realm(REALM_NAME).clients();
|
||||
ClientRepresentation representation = clients.findByClientId(name).get(0);
|
||||
return clients.get(representation.getId());
|
||||
}
|
||||
}
|
|
@ -1,706 +0,0 @@
|
|||
/*
|
||||
* 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.authz.admin;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.PermissionsResource;
|
||||
import org.keycloak.admin.client.resource.ResourcesResource;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.util.AuthzTestUtils;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RoleBuilder;
|
||||
import org.keycloak.testsuite.util.RolesBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
||||
|
||||
private static final String RESOURCE_SERVER_CLIENT_ID = "resource-server-test";
|
||||
private static final String REALM_NAME = "authz-test";
|
||||
|
||||
@BeforeClass
|
||||
public static void enabled() {
|
||||
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
testRealms.add(RealmBuilder.create().name(REALM_NAME)
|
||||
.roles(RolesBuilder.create()
|
||||
.realmRole(RoleBuilder.create().name("uma_authorization").build())
|
||||
.realmRole(RoleBuilder.create().name("uma_protection").build())
|
||||
.realmRole(RoleBuilder.create().name("user").build())
|
||||
)
|
||||
.user(UserBuilder.create().username("marta").password("password")
|
||||
.addRoles("uma_authorization", "uma_protection", "user")
|
||||
.role("resource-server-test", "uma_protection"))
|
||||
.user(UserBuilder.create().username("kolo").password("password"))
|
||||
.client(ClientBuilder.create().clientId("resource-server-uma-test")
|
||||
.secret("secret")
|
||||
.authorizationServicesEnabled(true)
|
||||
.redirectUris("http://localhost/resource-server-uma-test")
|
||||
.defaultRoles("uma_protection")
|
||||
.directAccessGrants())
|
||||
.client(ClientBuilder.create().clientId("resource-server-test")
|
||||
.secret("secret")
|
||||
.authorizationServicesEnabled(true)
|
||||
.redirectUris("http://localhost/resource-server-test")
|
||||
.defaultRoles("uma_protection")
|
||||
.directAccessGrants())
|
||||
.client(ClientBuilder.create().clientId("public-client-test")
|
||||
.publicClient()
|
||||
.redirectUris("http://localhost:8180/auth/realms/master/app/auth/*", "https://localhost:8543/auth/realms/master/app/auth/*")
|
||||
.directAccessGrants())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
initAuthorizationSettings(getClientResource(RESOURCE_SERVER_CLIENT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBearerOnlyClientResponse() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only.json", true);
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea"), testResponse);
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, testResponse.getStatus());
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
testResponse = new AuthzTestUtils.TestResponse();
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourceb"), testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, testResponse.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathConfigurationPrecendenceWhenLazyLoadingPaths() throws IOException {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-paths.json", false);
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea"), testResponse);
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, testResponse.getStatus());
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/"), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolvingClaimsOnce() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only-with-cip.json", true);
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(
|
||||
AuthzTestUtils.createHttpRequest("/api/resourcea", token, Collections.singletonMap("claim-a", Collections.singletonList("value-claim-a"))),
|
||||
new AuthzTestUtils.TestResponse());
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
Map<String, Set<String>> claims = permission.getClaims();
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
assertEquals("value-claim-a", claims.get("claim-a").iterator().next());
|
||||
assertEquals("claim-b", claims.get("claim-b").iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomClaimProvider() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only-with-cip.json", true);
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea", token), new AuthzTestUtils.TestResponse());
|
||||
Permission permission = context.getPermissions().get(0);
|
||||
Map<String, Set<String>> claims = permission.getClaims();
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
assertEquals("test", claims.get("resolved-claim").iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDenyRedirectTo() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-on-deny-redirect.json", false);
|
||||
|
||||
AuthzTestUtils.TestResponse response = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea"), response);
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(302, response.getStatus());
|
||||
List<String> location = response.getHeaders().getOrDefault("Location", Collections.emptyList());
|
||||
assertFalse(location.isEmpty());
|
||||
assertEquals("/accessDenied", location.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotAuthenticatedDenyUnmapedPath() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only.json", true);
|
||||
|
||||
AuthzTestUtils.TestResponse response = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/unmmaped"), response);
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMappedPathEnforcementModeDisabled() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-disabled-enforce-mode-path.json", true);
|
||||
|
||||
AuthzTestUtils.TestResponse response = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/public"), response);
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourceb"), response.clear());
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, response.getStatus());
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), null).getAccessToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourcea", token), response.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resourceb", token), response.clear());
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, response.getStatus());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/public", token), response.clear());
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabledPathNoCache() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-disabled-path-nocache.json", true);
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/public"), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
ResourceRepresentation resource = clientResource.authorization().resources()
|
||||
.findByName("Root").get(0);
|
||||
|
||||
clientResource.authorization().resources().resource(resource.getId()).remove();
|
||||
|
||||
// first request caches the path and the entry is invalidated due to the lifespan
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/all-public"), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
WaitUtils.pause(1000);
|
||||
|
||||
// second request can not fail because entry should not be invalidated
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/all-public"), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyLoadedPathIsCached() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
createResource(clientResource, "Static Test Resource", "/api/any-resource/*");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName("Any Resource Permission");
|
||||
permission.addResource("Static Test Resource");
|
||||
permission.addPolicy("Always Grant Policy");
|
||||
|
||||
clientResource.authorization().permissions().resource().create(permission);
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-disabled-path-nocache.json", true);
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/any-resource/test", token), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/any-resource/test", token), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
ResourceRepresentation resource = clientResource.authorization().resources()
|
||||
.findByName("Static Test Resource").get(0);
|
||||
|
||||
clientResource.authorization().resources().resource(resource.getId()).remove();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/any-resource/test", token), new AuthzTestUtils.TestResponse());
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforcementModeDisabled() {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-disabled-enforce-mode.json", true);
|
||||
|
||||
AuthzTestUtils.TestResponse response = new AuthzTestUtils.TestResponse();
|
||||
policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource/public"), response);
|
||||
assertEquals(401, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchHttpVerbsToScopes() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
ResourceRepresentation resource = createResource(clientResource, "Resource With HTTP Scopes", "/api/resource-with-scope");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName(resource.getName() + " Permission");
|
||||
permission.addResource(resource.getName());
|
||||
permission.addPolicy("Always Grant Policy");
|
||||
|
||||
PermissionsResource permissions = clientResource.authorization().permissions();
|
||||
permissions.resource().create(permission).close();
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-match-http-verbs-scopes.json", true);
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse);
|
||||
|
||||
assertFalse("Should fail because resource does not have any scope named GET", context.isGranted());
|
||||
assertEquals(403, testResponse.getStatus());
|
||||
|
||||
resource.addScope("GET", "POST");
|
||||
|
||||
clientResource.authorization().resources().resource(resource.getId()).update(resource);
|
||||
|
||||
policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-match-http-verbs-scopes.json", true);
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "POST"), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
// create a PATCH scope without associated it with the resource so that a PATCH request is denied accordingly even though
|
||||
// the scope exists on the server
|
||||
clientResource.authorization().scopes().create(new ScopeRepresentation("PATCH"));
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "PATCH"), testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
ScopePermissionRepresentation postPermission = new ScopePermissionRepresentation();
|
||||
|
||||
postPermission.setName("GET permission");
|
||||
postPermission.addScope("GET");
|
||||
postPermission.addPolicy("Always Deny Policy");
|
||||
|
||||
permissions.scope().create(postPermission).close();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
|
||||
postPermission.addScope("GET");
|
||||
postPermission.addPolicy("Always Grant Policy");
|
||||
|
||||
permissions.scope().findById(postPermission.getId()).update(postPermission);
|
||||
|
||||
AuthzClient authzClient = policyEnforcer.getAuthzClient();
|
||||
AuthorizationResponse authorize = authzClient.authorization(token).authorize();
|
||||
token = authorize.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "POST"), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
postPermission.addScope("GET");
|
||||
postPermission.addPolicy("Always Deny Policy");
|
||||
permissions.scope().findById(postPermission.getId()).update(postPermission);
|
||||
authorize = authzClient.authorization(token).authorize();
|
||||
token = authorize.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "POST"), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
postPermission.addScope("GET");
|
||||
postPermission.addPolicy("Always Grant Policy");
|
||||
permissions.scope().findById(postPermission.getId()).update(postPermission);
|
||||
authorize = authzClient.authorization(token).authorize();
|
||||
token = authorize.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "POST"), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
postPermission = permissions.scope().findByName(postPermission.getName());
|
||||
postPermission.addScope("POST");
|
||||
postPermission.addPolicy("Always Deny Policy");
|
||||
permissions.scope().findById(postPermission.getId()).update(postPermission);
|
||||
AuthorizationRequest request = new AuthorizationRequest();
|
||||
|
||||
request.addPermission(null, "GET");
|
||||
|
||||
authorize = authzClient.authorization(token).authorize(request);
|
||||
token = authorize.getToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/resource-with-scope", token, "POST"), testResponse.clear());
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsingSubjectToken() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
ResourceRepresentation resource = createResource(clientResource, "Resource Subject Token", "/api/check-subject-token");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName(resource.getName() + " Permission");
|
||||
permission.addResource(resource.getName());
|
||||
permission.addPolicy("Only User Policy");
|
||||
|
||||
PermissionsResource permissions = clientResource.authorization().permissions();
|
||||
permissions.resource().create(permission).close();
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only.json", true);
|
||||
AuthzTestUtils.TestResponse testResponse = new AuthzTestUtils.TestResponse();
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/check-subject-token"), testResponse);
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
assertEquals(403, testResponse.getStatus());
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/check-subject-token", token), testResponse.clear());
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsingInvalidToken() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
ResourceRepresentation resource = createResource(clientResource, "Resource Subject Invalid Token", "/api/check-subject-token");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName(resource.getName() + " Permission");
|
||||
permission.addResource(resource.getName());
|
||||
permission.addPolicy("Only User Policy");
|
||||
|
||||
PermissionsResource permissions = clientResource.authorization().permissions();
|
||||
permissions.resource().create(permission).close();
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-bearer-only.json", true);
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/check-subject-token", token), new AuthzTestUtils.TestResponse());
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
oauth.doLogout(response.getRefreshToken(), null);
|
||||
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/check-subject-token", token), new AuthzTestUtils.TestResponse());
|
||||
assertFalse(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyLoadPaths() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
|
||||
for (int i = 0; i < 200; i++) {
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
representation.setType("test");
|
||||
representation.setName("Resource " + i);
|
||||
representation.setUri("/api/" + i);
|
||||
|
||||
jakarta.ws.rs.core.Response response = clientResource.authorization().resources().create(representation);
|
||||
|
||||
representation.setId(response.readEntity(ResourceRepresentation.class).getId());
|
||||
|
||||
response.close();
|
||||
}
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName("Test Permission");
|
||||
permission.setResourceType("test");
|
||||
permission.addPolicy("Only User Policy");
|
||||
|
||||
PermissionsResource permissions = clientResource.authorization().permissions();
|
||||
permissions.resource().create(permission).close();
|
||||
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-no-lazyload.json", true);
|
||||
|
||||
assertEquals(205, policyEnforcer.getPaths().size());
|
||||
|
||||
policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-lazyload.json", true);
|
||||
assertEquals(0, policyEnforcer.getPathMatcher().getPathCache().size());
|
||||
assertEquals(0, policyEnforcer.getPaths().size());
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
String token = response.getAccessToken();
|
||||
|
||||
for (int i = 0; i < 101; i++) {
|
||||
policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/" + i, token), new AuthzTestUtils.TestResponse());
|
||||
}
|
||||
|
||||
assertEquals(101, policyEnforcer.getPathMatcher().getPathCache().size());
|
||||
|
||||
for (int i = 101; i < 200; i++) {
|
||||
policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/" + i, token), new AuthzTestUtils.TestResponse());
|
||||
}
|
||||
|
||||
assertEquals(200, policyEnforcer.getPathMatcher().getPathCache().size());
|
||||
assertEquals(0, policyEnforcer.getPaths().size());
|
||||
|
||||
ResourceRepresentation resource = clientResource.authorization().resources()
|
||||
.findByName("Root").get(0);
|
||||
|
||||
clientResource.authorization().resources().resource(resource.getId()).remove();
|
||||
|
||||
policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-lazyload-with-paths.json", true);
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api/0", token), new AuthzTestUtils.TestResponse());
|
||||
|
||||
assertTrue(context.isGranted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetMethodConfigs() {
|
||||
ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
representation.setName(KeycloakModelUtils.generateId());
|
||||
representation.setUris(Collections.singleton("/api-method/*"));
|
||||
|
||||
ResourcesResource resources = clientResource.authorization().resources();
|
||||
jakarta.ws.rs.core.Response response = resources.create(representation);
|
||||
|
||||
representation.setId(response.readEntity(ResourceRepresentation.class).getId());
|
||||
|
||||
response.close();
|
||||
|
||||
try {
|
||||
PolicyEnforcer policyEnforcer = AuthzTestUtils.createPolicyEnforcer("enforcer-paths-use-method-config.json", true);
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId("public-client-test");
|
||||
oauth.doLogin("marta", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokeResponse = oauth.doAccessTokenRequest(code, null);
|
||||
String token = tokeResponse.getAccessToken();
|
||||
|
||||
AuthorizationContext context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api-method/foo", token), new AuthzTestUtils.TestResponse());
|
||||
|
||||
// GET is disabled in the config
|
||||
assertTrue(context.isGranted());
|
||||
|
||||
PolicyEnforcerConfig.PathConfig pathConfig = policyEnforcer.getPaths().get("/api-method/*");
|
||||
|
||||
assertNotNull(pathConfig);
|
||||
List<PolicyEnforcerConfig.MethodConfig> methods = pathConfig.getMethods();
|
||||
assertEquals(1, methods.size());
|
||||
assertTrue(PolicyEnforcerConfig.ScopeEnforcementMode.DISABLED.equals(methods.get(0).getScopesEnforcementMode()));
|
||||
|
||||
// other verbs should be protected
|
||||
context = policyEnforcer.enforce(AuthzTestUtils.createHttpRequest("/api-method/foo", token, "POST"), new AuthzTestUtils.TestResponse());
|
||||
|
||||
assertFalse(context.isGranted());
|
||||
} finally {
|
||||
resources.resource(representation.getId()).remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void initAuthorizationSettings(ClientResource clientResource) {
|
||||
if (clientResource.authorization().resources().findByName("Resource A").isEmpty()) {
|
||||
JSPolicyRepresentation jsPolicy = new JSPolicyRepresentation();
|
||||
|
||||
jsPolicy.setName("Always Grant Policy");
|
||||
jsPolicy.setType("script-scripts/default-policy.js");
|
||||
|
||||
clientResource.authorization().policies().js().create(jsPolicy).close();
|
||||
|
||||
RolePolicyRepresentation rolePolicy = new RolePolicyRepresentation();
|
||||
|
||||
rolePolicy.setName("Only User Policy");
|
||||
rolePolicy.addRole("user");
|
||||
|
||||
clientResource.authorization().policies().role().create(rolePolicy).close();
|
||||
|
||||
createResource(clientResource, "Resource A", "/api/resourcea");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName("Resource A Permission");
|
||||
permission.addResource("Resource A");
|
||||
permission.addPolicy(jsPolicy.getName());
|
||||
|
||||
clientResource.authorization().permissions().resource().create(permission).close();
|
||||
}
|
||||
|
||||
if (clientResource.authorization().resources().findByName("Resource B").isEmpty()) {
|
||||
JSPolicyRepresentation policy = new JSPolicyRepresentation();
|
||||
|
||||
policy.setName("Always Deny Policy");
|
||||
policy.setType("script-scripts/always-deny-policy.js");
|
||||
|
||||
clientResource.authorization().policies().js().create(policy).close();
|
||||
|
||||
createResource(clientResource, "Resource B", "/api/resourceb");
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName("Resource B Permission");
|
||||
permission.addResource("Resource B");
|
||||
permission.addPolicy(policy.getName());
|
||||
|
||||
clientResource.authorization().permissions().resource().create(permission).close();
|
||||
}
|
||||
|
||||
if (clientResource.authorization().resources().findByName("Root").isEmpty()) {
|
||||
createResource(clientResource, "Root", "/*");
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceRepresentation createResource(ClientResource clientResource, String name, String uri, String... scopes) {
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
representation.setName(name);
|
||||
representation.setUri(uri);
|
||||
representation.setScopes(Arrays.asList(scopes).stream().map(ScopeRepresentation::new).collect(Collectors.toSet()));
|
||||
|
||||
jakarta.ws.rs.core.Response response = clientResource.authorization().resources().create(representation);
|
||||
|
||||
representation.setId(response.readEntity(ResourceRepresentation.class).getId());
|
||||
|
||||
response.close();
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
||||
private ClientResource getClientResource(String name) {
|
||||
ClientsResource clients = realmsResouce().realm(REALM_NAME).clients();
|
||||
ClientRepresentation representation = clients.findByClientId(name).get(0);
|
||||
return clients.get(representation.getId());
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#
|
||||
# * 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.
|
||||
#
|
||||
|
||||
org.keycloak.testsuite.authz.admin.MyCustomCIPFactory
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"claim-b": "claim-b"
|
||||
}
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/api/resourcea",
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"claim-a": "{request.parameter['claim-a']}"
|
||||
},
|
||||
"my-custom-cip": {
|
||||
"claim-value": "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
{
|
||||
"realm": "test-realm-authz",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "test-app-authz",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/claims-provider",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"create"
|
||||
]
|
||||
}
|
||||
],
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"claim-from-request-parameter": "{request.parameter['a']}",
|
||||
"claim-from-header": "{request.header['b']}",
|
||||
"claim-from-cookie": "{request.cookie['c']}",
|
||||
"claim-from-remoteAddr": "{request.remoteAddr}",
|
||||
"claim-from-method": "{request.method}",
|
||||
"claim-from-uri": "{request.uri}",
|
||||
"claim-from-relativePath": "{request.relativePath}",
|
||||
"claim-from-secure": "{request.secure}",
|
||||
"claim-from-json-body-object": "{request.body['/a/b/c']}",
|
||||
"claim-from-json-body-array": "{request.body['/d/1']}",
|
||||
"claim-from-json-body-number": "{request.body['/e/number']}",
|
||||
"claim-from-body": "{request.body}",
|
||||
"claim-from-static-value": "static value",
|
||||
"claim-from-multiple-static-value": ["static", "value"],
|
||||
"param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']} "
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/claims-from-body-json-object",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"create"
|
||||
]
|
||||
}
|
||||
],
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"individualRoles": "{request.body['/Individual/individualRoles']}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/http-post-claim-provider",
|
||||
"claim-information-point": {
|
||||
"http": {
|
||||
"claims": {
|
||||
"claim-a": "/a",
|
||||
"claim-d": "/d",
|
||||
"claim-d0": "/d/0",
|
||||
"claim-d-all": ["/d/0", "/d/1"]
|
||||
},
|
||||
"url": "http://localhost:8989/post-claim-information-provider",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"header-b": ["header-b-value1", "header-b-value2"],
|
||||
"Authorization": "Bearer {keycloak.access_token}"
|
||||
},
|
||||
"parameters": {
|
||||
"param-a": ["param-a-value1", "param-a-value2"],
|
||||
"param-subject": "{keycloak.access_token['/sub']}",
|
||||
"param-user-name": "{keycloak.access_token['/preferred_username']}",
|
||||
"param-other-claims": "{keycloak.access_token['/custom_claim']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/http-get-claim-provider",
|
||||
"claim-information-point": {
|
||||
"http": {
|
||||
"url": "http://localhost:8989/get-claim-information-provider",
|
||||
"method": "get",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"header-b": ["header-b-value1", "header-b-value2"],
|
||||
"Authorization": "Bearer {keycloak.access_token}"
|
||||
},
|
||||
"parameters": {
|
||||
"param-a": ["param-a-value1", "param-a-value2"],
|
||||
"param-subject": "{keycloak.access_token['/sub']}",
|
||||
"param-user-name": "{keycloak.access_token['/preferred_username']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"realm": "test-realm-authz",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "test-app-authz",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/v1/product/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"create"
|
||||
]
|
||||
}
|
||||
],
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"claim-a": "{request.parameter['a']}",
|
||||
"claim-b": "{request.header['b']}",
|
||||
"claim-c": "{request.cookie['c']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"realm": "test-realm-authz",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "test-app-authz",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/v1/product/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"create"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/v1/product/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes": [
|
||||
"view"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/v1/product/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "PUT",
|
||||
"scopes": [
|
||||
"update"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/v1/product/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "DELETE",
|
||||
"scopes": [
|
||||
"delete"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"name": "Resource B",
|
||||
"path": "/api/resource/public",
|
||||
"enforcement-mode": "DISABLED"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"enforcement-mode": "DISABLED",
|
||||
"paths": [
|
||||
{
|
||||
"name": "Resource B",
|
||||
"path": "/api/resource/public"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"path-cache": {
|
||||
"lifespan": 1
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"name": "Resource B",
|
||||
"path": "/api/resource/public",
|
||||
"enforcement-mode": "DISABLED"
|
||||
},
|
||||
{
|
||||
"name": "Nonexistent",
|
||||
"path": "/api/resource/all-public/*",
|
||||
"enforcement-mode": "DISABLED"
|
||||
},
|
||||
{
|
||||
"name": "Static Test Resource",
|
||||
"path": "/api/any-resource/test"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/api/bank/account/{id}/withdrawal",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"withdrawal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"withdrawal.amount": "{request.parameter['withdrawal.amount']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"lazy-load-paths": true,
|
||||
"paths": [
|
||||
{
|
||||
"path": "/disabled",
|
||||
"enforcement-mode": "DISABLED"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"lazy-load-paths": true
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"http-method-as-scope": true
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"on-deny-redirect-to": "/accessDenied"
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"lazy-load-paths": true,
|
||||
"paths": [
|
||||
{
|
||||
"path": "/api-method/*",
|
||||
"methods": [
|
||||
{
|
||||
"method": "GET",
|
||||
"scopes": [
|
||||
"withdrawal"
|
||||
],
|
||||
"scopes-enforcement-mode": "DISABLED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"resource": "resource-server-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"lazy-load-paths": true,
|
||||
"paths": [
|
||||
{
|
||||
"name": "Root",
|
||||
"path": "/*",
|
||||
"enforcement-mode": "DISABLED"
|
||||
},
|
||||
{
|
||||
"name": "Resource A",
|
||||
"path": "/api/*"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"realm": "authz-test",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"resource": "resource-server-uma-test",
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"user-managed-access": {},
|
||||
"paths": [
|
||||
{
|
||||
"path": "/api/bank/account/{id}/withdrawal",
|
||||
"methods": [
|
||||
{
|
||||
"method": "POST",
|
||||
"scopes": [
|
||||
"withdrawal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"claim-information-point": {
|
||||
"claims": {
|
||||
"withdrawal.amount": "{request.parameter['withdrawal.amount']}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
{
|
||||
"id": "test-realm-authz",
|
||||
"realm": "test-realm-authz",
|
||||
"enabled": true,
|
||||
"sslRequired": "external",
|
||||
"requiredCredentials": [ "password" ],
|
||||
"users": [
|
||||
{
|
||||
"username": "service-account-test-app-authz",
|
||||
"enabled": true,
|
||||
"serviceAccountClientId": "test-app-authz",
|
||||
"clientRoles": {
|
||||
"test-app-authz" : ["uma_protection"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "test-app-authz",
|
||||
"enabled": true,
|
||||
"baseUrl": "/test-app-authz",
|
||||
"adminUrl": "/test-app-authz",
|
||||
"bearerOnly": false,
|
||||
"authorizationSettings": {
|
||||
"allowRemoteResourceManagement": true,
|
||||
"policyEnforcementMode": "ENFORCING",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Product Resource",
|
||||
"uri": "/v1/product/*",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "view",
|
||||
"name": "create",
|
||||
"name": "delete",
|
||||
"name": "update"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"redirectUris": [
|
||||
"/test-app-authz/*"
|
||||
],
|
||||
"secret": "secret"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -95,9 +95,5 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-policy-enforcer-tests</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
Loading…
Reference in a new issue