[KEYCLOAK-7029] - Configuration of cache policies for cached resources/path
This commit is contained in:
parent
28d0ab9da8
commit
a939c45d58
13 changed files with 402 additions and 56 deletions
|
@ -159,7 +159,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
policyEnforcer.getPaths().remove(actualPathConfig);
|
||||
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, policyEnforcer.getPaths(), getAuthzClient());
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
|
|
|
@ -40,6 +40,7 @@ public class PathCache {
|
|||
private final AtomicBoolean writing = new AtomicBoolean(false);
|
||||
|
||||
private final long maxAge;
|
||||
private final boolean enabled;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
|
@ -55,9 +56,14 @@ public class PathCache {
|
|||
}
|
||||
};
|
||||
this.maxAge = maxAge;
|
||||
this.enabled = maxAge > 0;
|
||||
}
|
||||
|
||||
public void put(String uri, PathConfig newValue) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.authorization.client.resource.ProtectedResource;
|
|||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
@ -52,8 +53,8 @@ public class PolicyEnforcer {
|
|||
private final KeycloakDeployment deployment;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final PathConfigMatcher pathMatcher;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
|
@ -70,8 +71,8 @@ public class PolicyEnforcer {
|
|||
}
|
||||
});
|
||||
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
this.pathMatcher = createPathMatcher(authzClient);
|
||||
paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configurations:");
|
||||
|
@ -117,7 +118,7 @@ public class PolicyEnforcer {
|
|||
return paths;
|
||||
}
|
||||
|
||||
public PathMatcher<PathConfig> getPathMatcher() {
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
|
@ -216,10 +217,26 @@ public class PolicyEnforcer {
|
|||
return paths;
|
||||
}
|
||||
|
||||
private PathMatcher<PathConfig> createPathMatcher(final AuthzClient authzClient) {
|
||||
final PathCache pathCache = new PathCache(100, 30000);
|
||||
public class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.paths = paths;
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
return new PathMatcher<PathConfig>() {
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
@ -230,19 +247,20 @@ public class PolicyEnforcer {
|
|||
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() && (pathConfig == null || pathConfig.getPath().contains("*"))) {
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (!matchingResources.isEmpty()) {
|
||||
pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load paths from server");
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
|
@ -266,7 +284,6 @@ public class PolicyEnforcer {
|
|||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfig(targetResource);
|
||||
|
||||
|
@ -281,6 +298,9 @@ public class PolicyEnforcer {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ public class ClientAuthorizationContext extends AuthorizationContext {
|
|||
|
||||
private final AuthzClient client;
|
||||
|
||||
public ClientAuthorizationContext(AccessToken authzToken, PolicyEnforcerConfig.PathConfig current, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
|
||||
super(authzToken, current, paths);
|
||||
public ClientAuthorizationContext(AccessToken authzToken, PolicyEnforcerConfig.PathConfig current, AuthzClient client) {
|
||||
super(authzToken, current);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,18 +33,16 @@ public class AuthorizationContext {
|
|||
|
||||
private final AccessToken authzToken;
|
||||
private final PathConfig current;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private boolean granted;
|
||||
|
||||
public AuthorizationContext(AccessToken authzToken, PathConfig current, Map<String, PathConfig> paths) {
|
||||
public AuthorizationContext(AccessToken authzToken, PathConfig current) {
|
||||
this.authzToken = authzToken;
|
||||
this.current = current;
|
||||
this.paths = paths;
|
||||
this.granted = true;
|
||||
}
|
||||
|
||||
public AuthorizationContext() {
|
||||
this(null, null, null);
|
||||
this(null, null);
|
||||
this.granted = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,10 @@ public class PolicyEnforcerConfig {
|
|||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private List<PathConfig> paths = new ArrayList<>();
|
||||
|
||||
@JsonProperty("path-cache")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private PathCacheConfig pathCacheConfig;
|
||||
|
||||
@JsonProperty("lazy-load-paths")
|
||||
private Boolean lazyLoadPaths = Boolean.FALSE;
|
||||
|
||||
|
@ -53,6 +57,10 @@ public class PolicyEnforcerConfig {
|
|||
return this.paths;
|
||||
}
|
||||
|
||||
public PathCacheConfig getPathCacheConfig() {
|
||||
return pathCacheConfig;
|
||||
}
|
||||
|
||||
public Boolean getLazyLoadPaths() {
|
||||
return lazyLoadPaths;
|
||||
}
|
||||
|
@ -77,6 +85,10 @@ public class PolicyEnforcerConfig {
|
|||
this.paths = paths;
|
||||
}
|
||||
|
||||
public void setPathCacheConfig(PathCacheConfig pathCacheConfig) {
|
||||
this.pathCacheConfig = pathCacheConfig;
|
||||
}
|
||||
|
||||
public String getOnDenyRedirectTo() {
|
||||
return onDenyRedirectTo;
|
||||
}
|
||||
|
@ -250,6 +262,29 @@ public class PolicyEnforcerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
public static class PathCacheConfig {
|
||||
|
||||
@JsonProperty("max-entries")
|
||||
int maxEntries = 1000;
|
||||
long lifespan = 30000;
|
||||
|
||||
public int getMaxEntries() {
|
||||
return maxEntries;
|
||||
}
|
||||
|
||||
public void setMaxEntries(int maxEntries) {
|
||||
this.maxEntries = maxEntries;
|
||||
}
|
||||
|
||||
public long getLifespan() {
|
||||
return lifespan;
|
||||
}
|
||||
|
||||
public void setLifespan(long lifespan) {
|
||||
this.lifespan = lifespan;
|
||||
}
|
||||
}
|
||||
|
||||
public enum EnforcementMode {
|
||||
PERMISSIVE,
|
||||
ENFORCING,
|
||||
|
|
|
@ -99,6 +99,12 @@ public class AbstractPolicyRepresentation {
|
|||
this.policies.addAll(Arrays.asList(id));
|
||||
}
|
||||
|
||||
public void removePolicy(String policy) {
|
||||
if (policies != null) {
|
||||
policies.remove(policy);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"realm": "servlet-authz",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"resource" : "servlet-authz-app",
|
||||
"public-client" : false,
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
|
||||
"path-cache": {
|
||||
"lifespan": 0,
|
||||
"max-entries": 1000
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"name": "Premium Resource",
|
||||
"path": "/protected/premium/pep-disabled.jsp",
|
||||
"enforcement-mode": "DISABLED"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"realm": "servlet-authz",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url" : "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"resource" : "servlet-authz-app",
|
||||
"public-client" : false,
|
||||
"credentials": {
|
||||
"secret": "secret"
|
||||
},
|
||||
"policy-enforcer": {
|
||||
"on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
|
||||
"path-cache": {
|
||||
"lifespan": 5000,
|
||||
"max-entries": 1000
|
||||
},
|
||||
"paths": [
|
||||
{
|
||||
"name": "Premium Resource",
|
||||
"path": "/protected/premium/pep-disabled.jsp",
|
||||
"enforcement-mode": "DISABLED"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.example.authorization;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractServletCacheDisabledAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
|
||||
|
||||
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
||||
public static WebArchive deployment() throws IOException {
|
||||
return exampleDeployment(RESOURCE_SERVER_ID)
|
||||
.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-cache-disabled-authz-service.json"), "keycloak.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNewResource() {
|
||||
performTests(() -> {
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
|
||||
resource.setName("New Resource");
|
||||
resource.setUri("/new-resource");
|
||||
|
||||
getAuthorizationResource().resources().create(resource);
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName(resource.getName() + " Permission");
|
||||
permission.addResource(resource.getName());
|
||||
permission.addPolicy("Deny Policy");
|
||||
|
||||
permission = getAuthorizationResource().permissions().resource().create(permission).readEntity(ResourcePermissionRepresentation.class);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertTrue(wasDenied());
|
||||
|
||||
permission = getAuthorizationResource().permissions().resource().findById(permission.getId()).toRepresentation();
|
||||
|
||||
permission.removePolicy("Deny Policy");
|
||||
permission.addPolicy("Any User Policy");
|
||||
|
||||
getAuthorizationResource().permissions().resource().findById(permission.getId()).update(permission);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertFalse(wasDenied());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.example.authorization;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractServletCacheLifespanAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
|
||||
|
||||
@Deployment(name = RESOURCE_SERVER_ID, managed = false)
|
||||
public static WebArchive deployment() throws IOException {
|
||||
return exampleDeployment(RESOURCE_SERVER_ID)
|
||||
.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-cache-lifespan-authz-service.json"), "keycloak.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNewResourceWaitExpiration() {
|
||||
performTests(() -> {
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
|
||||
resource.setName("New Resource");
|
||||
resource.setUri("/new-resource");
|
||||
|
||||
getAuthorizationResource().resources().create(resource);
|
||||
|
||||
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
|
||||
|
||||
permission.setName(resource.getName() + " Permission");
|
||||
permission.addResource(resource.getName());
|
||||
permission.addPolicy("Deny Policy");
|
||||
|
||||
permission = getAuthorizationResource().permissions().resource().create(permission).readEntity(ResourcePermissionRepresentation.class);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
Thread.sleep(5000);
|
||||
|
||||
login("alice", "alice");
|
||||
assertFalse(wasDenied());
|
||||
|
||||
this.driver.navigate().to(getResourceServerUrl() + "/new-resource");
|
||||
assertTrue(wasDenied());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.example.authorization;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.RunAsClient;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@RunAsClient
|
||||
@AppServerContainer("app-server-wildfly")
|
||||
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
||||
public class WildflyServletCacheDisabledAdapterTest extends AbstractServletCacheDisabledAdapterTest {
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.adapter.example.authorization;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.RunAsClient;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@RunAsClient
|
||||
@AppServerContainer("app-server-wildfly")
|
||||
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
|
||||
public class WildflyServletCacheLifespanAdapterTest extends AbstractServletCacheLifespanAdapterTest {
|
||||
|
||||
}
|
Loading…
Reference in a new issue