[KEYCLOAK-13942] - Invalidate pre-defined paths when paths are invalidated

This commit is contained in:
Pedro Igor 2020-10-28 17:42:30 -03:00 committed by Stian Thorgersen
parent 882f5ffea4
commit 40efbb0f9c
12 changed files with 1091 additions and 586 deletions

View file

@ -22,6 +22,7 @@ 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;
/**
@ -42,14 +43,16 @@ public class PathCache {
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 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
*/
public PathCache(final int maxEntries, long maxAge) {
public PathCache(final int maxEntries, long maxAge,
Map<String, PathConfig> paths) {
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
@ -58,10 +61,15 @@ public class PathCache {
};
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;
}
@ -80,6 +88,15 @@ public class PathCache {
}
}
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);
}
@ -115,12 +132,19 @@ public class PathCache {
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 cached.value();
return config;
}
private boolean parkForWriteAndCheckInterrupt() {

View file

@ -191,6 +191,10 @@ public class PolicyEnforcer {
if (resource != null) {
pathConfig.setId(resource.getId());
// if the resource is staticly bound to a resource it means the config can not be invalidated
if (resourceName != null) {
pathConfig.setStatic(true);
}
}
PathConfig existingPath = null;
@ -248,7 +252,7 @@ public class PolicyEnforcer {
cacheConfig = new PathCacheConfig();
}
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan(), paths);
this.authzClient = authzClient;
}
@ -263,20 +267,35 @@ public class PolicyEnforcer {
pathConfig = super.matches(targetUri);
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
if ((pathConfig == null || pathConfig.isInvalidated() || pathConfig.getPath().contains("*"))) {
try {
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
if (!matchingResources.isEmpty()) {
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);
if (pathConfig != null) {
cipConfig = pathConfig.getClaimInformationPointConfig();
enforcementMode = pathConfig.getEnforcementMode();
} else {
for (PathConfig existingPath : paths.values()) {
if (existingPath.getId().equals(targetResource.getId())
&& existingPath.isStatic()
&& !PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(existingPath.getEnforcementMode())) {
return null;
}
}
}
pathConfig = PathConfig.createPathConfigs(matchingResources.get(0)).iterator().next();
pathConfig = PathConfig.createPathConfigs(targetResource).iterator().next();
if (cipConfig != null) {
pathConfig.setClaimInformationPointConfig(cipConfig);

View file

@ -177,6 +177,10 @@ public class PolicyEnforcerConfig {
@JsonIgnore
private PathConfig parentConfig;
private boolean invalidated;
private boolean staticPath;
public String getPath() {
return this.path;
}
@ -270,6 +274,22 @@ public class PolicyEnforcerConfig {
public PathConfig getParentConfig() {
return parentConfig;
}
public void invalidate() {
this.invalidated = true;
}
public boolean isInvalidated() {
return invalidated;
}
public boolean isStatic() {
return staticPath;
}
public void setStatic(boolean staticPath) {
this.staticPath = staticPath;
}
}
public static class MethodConfig {

View file

@ -0,0 +1,80 @@
{
"realm": "photoz",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required": "external",
"resource": "photoz-restful-api",
"bearer-only" : true,
"credentials": {
"jwt": {
"client-key-password": "password",
"client-keystore-file": "classpath:keystore.jks",
"client-keystore-password": "password",
"client-key-alias": "secure-portal",
"token-timeout": 10,
"client-keystore-type": "jks"
}
},
"policy-enforcer": {
"enforcement-mode": "PERMISSIVE",
"user-managed-access": {},
"path-cache": {
"lifespan": 10000
},
"paths": [
{
"name" : "Album Resource",
"path" : "/album",
"methods" : [
{
"method": "GET",
"scopes-enforcement-mode" : "DISABLED"
}
]
},
{
"name" : "Album Resource",
"path" : "/album/{id}/",
"methods" : [
{
"method": "DELETE",
"scopes" : ["album:delete"]
},
{
"method": "GET",
"scopes" : ["album:view"]
}
]
},
{
"path" : "/profile"
},
{
"name" : "Admin Resources",
"path" : "/admin/*"
},
{
"name" : "Scope Protected Resource",
"path" : "/scope-any",
"methods": [
{
"method": "GET",
"scopes": ["scope-a", "scope-b"],
"scopes-enforcement-mode": "ANY"
}
]
},
{
"name" : "Scope Protected Resource",
"path" : "/scope-all",
"methods": [
{
"method": "GET",
"scopes": ["scope-a", "scope-b"],
"scopes-enforcement-mode": "ALL"
}
]
}
]
}
}

View file

@ -0,0 +1,5 @@
<%@page import="org.keycloak.common.util.Time"%>
<%
Time.setOffset(Integer.parseInt(request.getParameter("offset")));
%>

View file

@ -0,0 +1,106 @@
{
"realm": "servlet-policy-enforcer-authz",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required": "external",
"resource": "servlet-policy-enforcer",
"credentials": {
"secret": "secret"
},
"policy-enforcer": {
"on-deny-redirect-to": "/servlet-policy-enforcer/denied.jsp",
"lazy-load-paths": false,
"path-cache": {
"lifespan": 5
},
"paths": [
{
"name": "Welcome Resource",
"path": "/index.jsp"
},
{
"name": "Welcome Resource",
"path": "/"
},
{
"name": "Pattern 1",
"path": "/resource/{pattern}/{sub-resource}"
},
{
"name": "Pattern 2",
"path": "/{pattern}/resource-a"
},
{
"name": "Pattern 3",
"path": "/{pattern}/resource-b"
},
{
"name": "Pattern 4",
"path": "/resource-c"
},
{
"name": "Pattern 5",
"path": "/a/{pattern}/resource-d"
},
{
"name": "Pattern 6",
"path": "/resource/{pattern}"
},
{
"name": "Pattern 7",
"path": "/resource/{pattern}/f/{resource}"
},
{
"name": "Pattern 8",
"path": "/resource"
},
{
"name": "Pattern 9",
"path": "/file/*.suffix"
},
{
"name": "Pattern 10",
"path": "/resource/{pattern}/i/{resource}/*"
},
{
"name": "Pattern 11",
"path": "/api/{version}/{resource}"
},
{
"name": "Pattern 12",
"path": "/keycloak_json_uri"
},
{
"name": "Pattern 14",
"path": "/keycloak-6623/sub-resource/*"
},
{
"name": "Pattern 13",
"path": "/keycloak-6623/*"
},
{
"name": "Pattern 15",
"path": "/keycloak-7148/{id}/*"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource1"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource2/*"
},
{
"name": "Pattern 16",
"path": "/keycloak-7269/sub-resource1/{test-pattern}/specialSuffix"
},
{
"name": "Pattern 17",
"path": "/keycloak-8823/resource/{version}/subresource/{id}/{other}"
},
{
"name": "Pattern 17 Entities",
"path": "/keycloak-8823/resource/{version}/subresource/{id}/entities"
}
]
}
}

View file

@ -178,6 +178,15 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(WAIT_AFTER_OPERATION);
}
public void viewProfile(ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.addHeader("Accept", "application/json")
.url(apiUrl + "/profile")
, validator);
}
public void viewAlbum(String name, ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()

View file

@ -80,7 +80,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractBasePhoto
}
@Test
public void testOnlyOwnerCanDeleteAlbum() throws Exception {
public void testPathConfigInvalidation() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);

View file

@ -0,0 +1,599 @@
/*
* 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.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ResourcePermissionsResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.util.ServerURLs;
import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class AbstractServletPolicyEnforcerTest extends AbstractExampleAdapterTest {
protected static final String REALM_NAME = "servlet-policy-enforcer-authz";
protected static final String RESOURCE_SERVER_ID = "servlet-policy-enforcer";
@ArquillianResource
private Deployer deployer;
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-policy-enforcer/servlet-policy-enforcer-authz-realm.json")));
}
@Test
public void testPattern1() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 1 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 1 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/a/b");
assertFalse(wasDenied());
});
}
@Test
public void testPattern2() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/resource-a");
assertFalse(wasDenied());
navigateTo("/b/resource-a");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-a");
assertTrue(wasDenied());
navigateTo("/b/resource-a");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-a");
assertFalse(wasDenied());
});
}
@Test
public void testPattern3() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/resource-b");
assertFalse(wasDenied());
navigateTo("/b/resource-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-b");
assertTrue(wasDenied());
navigateTo("/b/resource-b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-a");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-b");
assertTrue(wasDenied());
navigateTo("/b/resource-a");
assertFalse(wasDenied());
});
}
@Test
public void testPattern4() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource-c");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 4 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource-c");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 4 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource-c");
assertFalse(wasDenied());
});
}
@Test
public void testPattern5() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/a/resource-d");
assertFalse(wasDenied());
navigateTo("/resource/b/resource-d");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/a/resource-d");
assertTrue(wasDenied());
navigateTo("/a/b/resource-d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/a/b/resource-d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern6() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a");
assertFalse(wasDenied());
navigateTo("/resource/b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 6 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a");
assertTrue(wasDenied());
navigateTo("/resource/b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 6 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/b");
assertFalse(wasDenied());
});
}
@Test
public void testPattern7() throws Exception {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/f/b");
assertFalse(wasDenied());
navigateTo("/resource/c/f/d");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 7 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/f/b");
assertTrue(wasDenied());
navigateTo("/resource/c/f/d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 7 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/c/f/d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern8() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 8 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 8 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource");
assertFalse(wasDenied());
});
}
@Test
public void testPattern9() {
performTests(() -> {
login("alice", "alice");
navigateTo("/file/*.suffix");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 9 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/file/*.suffix");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 9 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/file/*.suffix");
assertFalse(wasDenied());
});
}
@Test
public void testPattern10() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d/e");
assertFalse(wasDenied());
navigateTo("/resource/a/i/b/c/");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 10 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d/e");
assertTrue(wasDenied());
navigateTo("/resource/a/i/b/c/d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 10 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern11UsingResourceInstancePermission() {
performTests(() -> {
login("alice", "alice");
navigateTo("/api/v1/resource-a");
assertFalse(wasDenied());
navigateTo("/api/v1/resource-b");
assertFalse(wasDenied());
ResourceRepresentation resource = new ResourceRepresentation("/api/v1/resource-c");
resource.setUri(resource.getName());
getAuthorizationResource().resources().create(resource);
createResourcePermission(resource.getName() + " permission", resource.getName(), "Default Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertFalse(wasDenied());
updatePermissionPolicies(resource.getName() + " permission", "Deny Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertTrue(wasDenied());
updatePermissionPolicies(resource.getName() + " permission", "Default Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertFalse(wasDenied());
navigateTo("/api/v1");
assertTrue(wasDenied());
navigateTo("/api/v1/");
assertTrue(wasDenied());
navigateTo("/api");
assertTrue(wasDenied());
navigateTo("/api/");
assertTrue(wasDenied());
});
}
@Test
public void testPathWithPatternSlashAllAndResourceInstance() {
performTests(() -> {
ResourceRepresentation resource = new ResourceRepresentation("Pattern 15 Instance");
resource.setType("pattern-15");
resource.setUri("/keycloak-7148/1");
resource.setOwner("alice");
getAuthorizationResource().resources().create(resource).close();
login("alice", "alice");
navigateTo("/keycloak-7148/1");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2/sub-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 15 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-7148/1");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2/sub-b");
assertTrue(wasDenied());
// does not exist
navigateTo("/keycloak-7148/2");
assertTrue(wasDenied());
});
}
@Test
public void testPriorityOfURIForResource() {
performTests(() -> {
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 12 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 12 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertFalse(wasDenied());
});
}
@Test
public void testPathOrderWithAllPaths() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-6623");
assertFalse(wasDenied());
navigateTo("/keycloak-6623/sub-resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 13 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-6623");
assertTrue(wasDenied());
navigateTo("/keycloak-6623/sub-resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 14 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-6623");
assertTrue(wasDenied());
navigateTo("/keycloak-6623/sub-resource/resource");
assertTrue(wasDenied());
});
}
@Test
public void testMultipleUriForResourceJSONConfig() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Default Policy");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
});
}
@Test
public void testOverloadedTemplateUri() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertTrue(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Default Policy");
updatePermissionPolicies("Pattern 17 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Default Policy");
updatePermissionPolicies("Pattern 17 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
});
}
private void navigateTo(String path) {
this.driver.navigate().to(getResourceServerUrl() + path);
}
private void performTests(ExceptionRunnable assertion) {
performTests(() -> {}, assertion);
}
private void performTests(ExceptionRunnable beforeDeploy, ExceptionRunnable assertion) {
try {
beforeDeploy.run();
deployer.deploy(RESOURCE_SERVER_ID);
assertion.run();
} catch (FileNotFoundException cause) {
throw new RuntimeException("Failed to import authorization settings", cause);
} catch (Exception cause) {
throw new RuntimeException("Error while executing tests", cause);
} finally {
deployer.undeploy(RESOURCE_SERVER_ID);
}
}
private AuthorizationResource getAuthorizationResource() {
return getClientResource(RESOURCE_SERVER_ID).authorization();
}
private ClientResource getClientResource(String clientId) {
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
return clients.get(resourceServer.getId());
}
private void logOut() {
navigateTo();
UIUtils.clickLink(driver.findElement(By.xpath("//a[text() = 'Sign Out']")));
}
private void login(String username, String password) {
try {
navigateTo();
if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
logOut();
navigateTo();
}
this.loginPage.form().login(username, password);
navigateTo();
assertFalse(wasDenied());
} catch (Exception cause) {
throw new RuntimeException("Login failed", cause);
}
}
private void navigateTo() {
this.driver.navigate().to(getResourceServerUrl() + "/");
waitForPageToLoad();
}
private boolean wasDenied() {
return this.driver.getPageSource().contains("You can not access this resource");
}
private URL getResourceServerUrl() {
try {
return new URL(ServerURLs.getAppServerContextRoot() + "/" + RESOURCE_SERVER_ID);
} catch (MalformedURLException e) {
throw new RuntimeException("Could not obtain resource server url.", e);
}
}
private void updatePermissionPolicies(String permissionName, String... policyNames) {
ResourcePermissionsResource permissions = getAuthorizationResource().permissions().resource();
ResourcePermissionRepresentation permission = permissions.findByName(permissionName);
permission.addPolicy(policyNames);
permissions.findById(permission.getId()).update(permission);
}
private void createResourcePermission(String name, String resourceName, String... policyNames) {
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName(name);
permission.addResource(resourceName);
permission.addPolicy(policyNames);
getAuthorizationResource().permissions().resource().create(permission);
}
private interface ExceptionRunnable {
void run() throws Exception;
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.hamcrest.MatcherAssert.assertThat;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.Matchers;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class LifespanAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID,
webArchive -> webArchive.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-cache-lifespan-authz-service.json"), "keycloak.json"));
}
@Test
public void testPathConfigInvalidation() throws Exception {
loginToClientPage(aliceUser);
assertSuccess();
ResourceRepresentation resource = getAuthorizationResource().resources().findByName("Profile Resource").get(0);
AuthorizationResource authorizationResource = getAuthorizationResource();
authorizationResource.resources().resource(resource.getId()).remove();
loginToClientPage(aliceUser);
// should throw an error because the resource was removed and cache entry did not expire yet
clientPage.viewProfile(new ResponseValidator() {
@Override
public void validate(Map<String, Object> response) {
Object res = response.get("res");
assertThat(res, Matchers.notNullValue());
assertThat(res.toString(), Matchers.not(Matchers.containsString("userName")));
}
});
setTimeOffsetOfAdapter(20);
loginToClientPage(aliceUser);
assertSuccess();
setTimeOffsetOfAdapter(0);
try (Response response = authorizationResource.resources().create(resource)) {
resource = response.readEntity(ResourceRepresentation.class);
}
loginToClientPage(aliceUser);
assertSuccess();
RealmResource realm = this.realmsResouce().realm(REALM_NAME);
UserRepresentation userRepresentation = realm.users().search(aliceUser.getUsername()).get(0);
UserResource userResource = realm.users().get(userRepresentation.getId());
userRepresentation.setEmail("alice@anotherdomain.org");
userResource.update(userRepresentation);
loginToClientPage(aliceUser);
assertTicket();
try {
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
resourceInstancePermission.setName("View User Permission");
resourceInstancePermission.setType("resource");
Map<String, String> config = new HashMap<>();
config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only From @keycloak.org or Admin")));
resourceInstancePermission.setConfig(config);
authorizationResource.policies().create(resourceInstancePermission);
} catch (IOException e) {
throw new RuntimeException("Error creating policy.", e);
}
loginToClientPage(aliceUser);
// should throw an error because the resource was removed and cache entry did not expire yet
clientPage.viewProfile(new ResponseValidator() {
@Override
public void validate(Map<String, Object> response) {
Object res = response.get("res");
assertThat(res, Matchers.notNullValue());
assertThat(res.toString(), Matchers.not(Matchers.containsString("userName")));
}
});
userRepresentation.setEmail("alice@keycloak.org");
userResource.update(userRepresentation);
loginToClientPage(aliceUser);
assertSuccess();
}
private void assertSuccess() {
clientPage.viewProfile((ResponseValidator) response -> {
Object res = response.get("res");
assertThat(res, Matchers.notNullValue());
assertThat(res.toString(), Matchers.containsString("userName"));
});
}
private void assertTicket() {
clientPage.viewProfile((ResponseValidator) response -> {
Object headers = response.get("responseHeaders");
assertThat(headers, Matchers.notNullValue());
assertThat(headers.toString(), Matchers.containsString("WWW-Authenticate: UMA"));
});
}
public void setTimeOffsetOfAdapter(int offset) {
this.driver.navigate().to(clientPage.getInjectedUrl() + "/timeOffset.jsp?offset=" + String.valueOf(offset));
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.adapter.example.authorization;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import java.io.File;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
@EnableFeature(value = UPLOAD_SCRIPTS, skipRestart = true)
public class ServletPolicyEnforcerLifespanTest extends AbstractServletPolicyEnforcerTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() {
return exampleDeployment(RESOURCE_SERVER_ID,
webArchive -> webArchive.addAsWebInfResource(
new File(TEST_APPS_HOME_DIR
+ "/servlet-policy-enforcer/servlet-policy-enforcer-lifespan-authz-service.json"),
"keycloak.json"));
}
}

View file

@ -16,41 +16,16 @@
*/
package org.keycloak.testsuite.adapter.example.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.ResourcePermissionsResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.util.ServerURLs;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -65,559 +40,10 @@ import org.openqa.selenium.By;
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
@EnableFeature(value = UPLOAD_SCRIPTS, skipRestart = true)
public class ServletPolicyEnforcerTest extends AbstractExampleAdapterTest {
protected static final String REALM_NAME = "servlet-policy-enforcer-authz";
protected static final String RESOURCE_SERVER_ID = "servlet-policy-enforcer";
@ArquillianResource
private Deployer deployer;
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(
loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-policy-enforcer/servlet-policy-enforcer-authz-realm.json")));
}
public class ServletPolicyEnforcerTest extends AbstractServletPolicyEnforcerTest {
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
public static WebArchive deployment() throws IOException {
public static WebArchive deployment() {
return exampleDeployment(RESOURCE_SERVER_ID);
}
@Test
public void testPattern1() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 1 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 1 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/a/b");
assertFalse(wasDenied());
});
}
@Test
public void testPattern2() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/resource-a");
assertFalse(wasDenied());
navigateTo("/b/resource-a");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-a");
assertTrue(wasDenied());
navigateTo("/b/resource-a");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-a");
assertFalse(wasDenied());
});
}
@Test
public void testPattern3() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/resource-b");
assertFalse(wasDenied());
navigateTo("/b/resource-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-b");
assertTrue(wasDenied());
navigateTo("/b/resource-b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 2 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/b/resource-a");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 3 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/resource-b");
assertTrue(wasDenied());
navigateTo("/b/resource-a");
assertFalse(wasDenied());
});
}
@Test
public void testPattern4() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource-c");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 4 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource-c");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 4 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource-c");
assertFalse(wasDenied());
});
}
@Test
public void testPattern5() {
performTests(() -> {
login("alice", "alice");
navigateTo("/a/a/resource-d");
assertFalse(wasDenied());
navigateTo("/resource/b/resource-d");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/a/a/resource-d");
assertTrue(wasDenied());
navigateTo("/a/b/resource-d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 5 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/a/b/resource-d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern6() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a");
assertFalse(wasDenied());
navigateTo("/resource/b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 6 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a");
assertTrue(wasDenied());
navigateTo("/resource/b");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 6 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/b");
assertFalse(wasDenied());
});
}
@Test
public void testPattern7() throws Exception {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/f/b");
assertFalse(wasDenied());
navigateTo("/resource/c/f/d");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 7 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/f/b");
assertTrue(wasDenied());
navigateTo("/resource/c/f/d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 7 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/c/f/d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern8() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 8 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 8 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource");
assertFalse(wasDenied());
});
}
@Test
public void testPattern9() {
performTests(() -> {
login("alice", "alice");
navigateTo("/file/*.suffix");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 9 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/file/*.suffix");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 9 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/file/*.suffix");
assertFalse(wasDenied());
});
}
@Test
public void testPattern10() {
performTests(() -> {
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d/e");
assertFalse(wasDenied());
navigateTo("/resource/a/i/b/c/");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 10 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d/e");
assertTrue(wasDenied());
navigateTo("/resource/a/i/b/c/d");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 10 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/resource/a/i/b/c/d");
assertFalse(wasDenied());
});
}
@Test
public void testPattern11UsingResourceInstancePermission() {
performTests(() -> {
login("alice", "alice");
navigateTo("/api/v1/resource-a");
assertFalse(wasDenied());
navigateTo("/api/v1/resource-b");
assertFalse(wasDenied());
ResourceRepresentation resource = new ResourceRepresentation("/api/v1/resource-c");
resource.setUri(resource.getName());
getAuthorizationResource().resources().create(resource);
createResourcePermission(resource.getName() + " permission", resource.getName(), "Default Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertFalse(wasDenied());
updatePermissionPolicies(resource.getName() + " permission", "Deny Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertTrue(wasDenied());
updatePermissionPolicies(resource.getName() + " permission", "Default Policy");
login("alice", "alice");
navigateTo(resource.getUri());
assertFalse(wasDenied());
navigateTo("/api/v1");
assertTrue(wasDenied());
navigateTo("/api/v1/");
assertTrue(wasDenied());
navigateTo("/api");
assertTrue(wasDenied());
navigateTo("/api/");
assertTrue(wasDenied());
});
}
@Test
public void testPathWithPatternSlashAllAndResourceInstance() {
performTests(() -> {
ResourceRepresentation resource = new ResourceRepresentation("Pattern 15 Instance");
resource.setType("pattern-15");
resource.setUri("/keycloak-7148/1");
resource.setOwner("alice");
getAuthorizationResource().resources().create(resource).close();
login("alice", "alice");
navigateTo("/keycloak-7148/1");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a");
assertFalse(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2/sub-b");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 15 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-7148/1");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a");
assertTrue(wasDenied());
navigateTo("/keycloak-7148/1/sub-a/2/sub-b");
assertTrue(wasDenied());
// does not exist
navigateTo("/keycloak-7148/2");
assertTrue(wasDenied());
});
}
@Test
public void testPriorityOfURIForResource() {
performTests(() -> {
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 12 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 12 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/realm_uri");
assertTrue(wasDenied());
navigateTo("/keycloak_json_uri");
assertFalse(wasDenied());
});
}
@Test
public void testPathOrderWithAllPaths() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-6623");
assertFalse(wasDenied());
navigateTo("/keycloak-6623/sub-resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 13 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-6623");
assertTrue(wasDenied());
navigateTo("/keycloak-6623/sub-resource");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 14 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-6623");
assertTrue(wasDenied());
navigateTo("/keycloak-6623/sub-resource/resource");
assertTrue(wasDenied());
});
}
@Test
public void testMultipleUriForResourceJSONConfig() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-7269/sub-resource1");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertTrue(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 16 Permission", "Default Policy");
navigateTo("/keycloak-7269/sub-resource1");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource1/whatever/specialSuffix");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2");
assertFalse(wasDenied());
navigateTo("/keycloak-7269/sub-resource2/w/h/a/t/e/v/e/r");
assertFalse(wasDenied());
});
}
@Test
public void testOverloadedTemplateUri() {
performTests(() -> {
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertTrue(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Default Policy");
updatePermissionPolicies("Pattern 17 Permission", "Deny Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertTrue(wasDenied());
updatePermissionPolicies("Pattern 17 Entities Permission", "Default Policy");
updatePermissionPolicies("Pattern 17 Permission", "Default Policy");
login("alice", "alice");
navigateTo("/keycloak-8823/resource/v1/subresource/123/entities");
assertFalse(wasDenied());
navigateTo("/keycloak-8823/resource/v1/subresource/123/someother");
assertFalse(wasDenied());
});
}
private void navigateTo(String path) {
this.driver.navigate().to(getResourceServerUrl() + path);
}
private void performTests(ExceptionRunnable assertion) {
performTests(() -> {}, assertion);
}
private void performTests(ExceptionRunnable beforeDeploy, ExceptionRunnable assertion) {
try {
beforeDeploy.run();
deployer.deploy(RESOURCE_SERVER_ID);
assertion.run();
} catch (FileNotFoundException cause) {
throw new RuntimeException("Failed to import authorization settings", cause);
} catch (Exception cause) {
throw new RuntimeException("Error while executing tests", cause);
} finally {
deployer.undeploy(RESOURCE_SERVER_ID);
}
}
private AuthorizationResource getAuthorizationResource() {
return getClientResource(RESOURCE_SERVER_ID).authorization();
}
private ClientResource getClientResource(String clientId) {
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
return clients.get(resourceServer.getId());
}
private void logOut() {
navigateTo();
UIUtils.clickLink(driver.findElement(By.xpath("//a[text() = 'Sign Out']")));
}
private void login(String username, String password) {
try {
navigateTo();
if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
logOut();
navigateTo();
}
this.loginPage.form().login(username, password);
navigateTo();
assertFalse(wasDenied());
} catch (Exception cause) {
throw new RuntimeException("Login failed", cause);
}
}
private void navigateTo() {
this.driver.navigate().to(getResourceServerUrl() + "/");
waitForPageToLoad();
}
private boolean wasDenied() {
return this.driver.getPageSource().contains("You can not access this resource");
}
private URL getResourceServerUrl() {
try {
return new URL(ServerURLs.getAppServerContextRoot() + "/" + RESOURCE_SERVER_ID);
} catch (MalformedURLException e) {
throw new RuntimeException("Could not obtain resource server url.", e);
}
}
private void updatePermissionPolicies(String permissionName, String... policyNames) {
ResourcePermissionsResource permissions = getAuthorizationResource().permissions().resource();
ResourcePermissionRepresentation permission = permissions.findByName(permissionName);
permission.addPolicy(policyNames);
permissions.findById(permission.getId()).update(permission);
}
private void createResourcePermission(String name, String resourceName, String... policyNames) {
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName(name);
permission.addResource(resourceName);
permission.addPolicy(policyNames);
getAuthorizationResource().permissions().resource().create(permission);
}
private interface ExceptionRunnable {
void run() throws Exception;
}
}