keycloak-memoizeit

[KEYCLOAK-7670] - PEP not returning correct status code when

6/28/2018 11:51:54 AM

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 20f6c90..bc90b65 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -67,8 +67,12 @@ public abstract class AbstractPolicyEnforcer {
         KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
 
         if (securityContext == null) {
-            if (pathConfig != null) {
-                challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
+            if (!isDefaultAccessDeniedUri(request)) {
+                if (pathConfig != null) {
+                    challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
+                } else {
+                    handleAccessDenied(httpFacade);
+                }
             }
             return createEmptyAuthorizationContext(false);
         }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index ba46088..632aa66 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -122,12 +122,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
 
     @Override
     protected void handleAccessDenied(OIDCHttpFacade facade) {
-        KeycloakSecurityContext securityContext = facade.getSecurityContext();
-
-        if (securityContext == null) {
-            return;
-        }
-
         String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
         HttpFacade.Response response = facade.getResponse();
 
@@ -211,17 +205,16 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
 
     private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
         List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
-        if (authHeaders == null || authHeaders.size() == 0) {
-            return false;
-        }
 
-        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;
+        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 false;
+        return getPolicyEnforcer().getDeployment().isBearerOnly();
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerClaimsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerClaimsTest.java
new file mode 100644
index 0000000..cff948b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerClaimsTest.java
@@ -0,0 +1,590 @@
+/*
+ * 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.admin.client.authorization;
+
+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 java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.security.cert.X509Certificate;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.AuthorizationContext;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade.Cookie;
+import org.keycloak.adapters.spi.HttpFacade.Request;
+import org.keycloak.adapters.spi.HttpFacade.Response;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.AccessToken;
+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.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.util.JsonSerialization;
+
+/**
+ * @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 onBeforeClass() {
+        ProfileAssume.assumePreview();
+    }
+
+    @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/*")
+                        .directAccessGrants())
+                .build());
+    }
+
+    @Test
+    public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
+        initAuthorizationSettings(getClientResource("resource-server-uma-test"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-uma-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        AuthzClient authzClient = getAuthzClient("enforcer-uma-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        headers.put("Authorization", Arrays.asList("Bearer " + token));
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.setTicket(extractTicket(headers));
+
+        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(request);
+        token = response.getToken();
+
+        assertNotNull(token);
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+
+        request = new AuthorizationRequest();
+
+        request.setTicket(extractTicket(headers));
+
+        response = authzClient.authorization("marta", "password").authorize(request);
+        token = response.getToken();
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        request = new AuthorizationRequest();
+
+        request.setTicket(extractTicket(headers));
+
+        response = authzClient.authorization("marta", "password").authorize(request);
+        token = response.getToken();
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "GET", token, headers, parameters));
+        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"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        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(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        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"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        headers.put("Authorization", Arrays.asList("Bearer " + token));
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        assertTrue(context.isGranted());
+    }
+
+    @Test
+    public void testEnforceEntitlementAccessWithClaimsWithBearerTokenFromPublicClient() {
+        initAuthorizationSettings(getClientResource("resource-server-test"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        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));
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        assertTrue(context.isGranted());
+    }
+
+    private String extractTicket(HashMap<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");
+
+            StringBuilder code = new StringBuilder();
+
+            code.append("var context = $evaluation.getContext();");
+            code.append("var attributes = context.getAttributes();");
+            code.append("var withdrawalAmount = attributes.getValue('withdrawal.amount');");
+            code.append("if (withdrawalAmount && withdrawalAmount.asDouble(0) <= 100) {");
+            code.append("   $evaluation.grant();");
+            code.append("}");
+
+            policy.setCode(code.toString());
+
+            clientResource.authorization().policies().js().create(policy);
+
+            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);
+        }
+    }
+
+    private InputStream getAdapterConfiguration(String fileName) {
+        return getClass().getResourceAsStream("/authorization-test/" + fileName);
+    }
+
+    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()));
+
+        javax.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());
+    }
+
+    private OIDCHttpFacade createHttpFacade(String path, String method, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+        return new OIDCHttpFacade() {
+            Request request;
+            Response response;
+
+            @Override
+            public KeycloakSecurityContext getSecurityContext() {
+                AccessToken accessToken;
+                try {
+                    accessToken = new JWSInput(token).readJsonContent(AccessToken.class);
+                } catch (JWSInputException cause) {
+                    throw new RuntimeException(cause);
+                }
+                return new KeycloakSecurityContext(token, accessToken, null, null);
+            }
+
+            @Override
+            public Request getRequest() {
+                if (request == null) {
+                    request = createHttpRequest(path, method, headers, parameters, requestBody);
+                }
+                return request;
+            }
+
+            @Override
+            public Response getResponse() {
+                if (response == null) {
+                    response = createHttpResponse(headers);
+                }
+                return response;
+            }
+
+            @Override
+            public X509Certificate[] getCertificateChain() {
+                return new X509Certificate[0];
+            }
+        };
+    }
+
+    private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
+        return createHttpFacade(path, null, token, headers, parameters, null);
+    }
+
+    private OIDCHttpFacade createHttpFacade(String path, String method, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
+        return createHttpFacade(path, method, token, headers, parameters, null);
+    }
+
+    private Response createHttpResponse(Map<String, List<String>> headers) {
+        return new Response() {
+
+            private int status;
+
+            @Override
+            public void setStatus(int status) {
+                this.status = status;
+            }
+
+            @Override
+            public void addHeader(String name, String value) {
+                setHeader(name, value);
+            }
+
+            @Override
+            public void setHeader(String name, String value) {
+                headers.put(name, Arrays.asList(value));
+            }
+
+            @Override
+            public void resetCookie(String name, String path) {
+
+            }
+
+            @Override
+            public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+
+            }
+
+            @Override
+            public OutputStream getOutputStream() {
+                return null;
+            }
+
+            @Override
+            public void sendError(int code) {
+
+            }
+
+            @Override
+            public void sendError(int code, String message) {
+
+            }
+
+            @Override
+            public void end() {
+
+            }
+        };
+    }
+
+    private Request createHttpRequest(String path, String method, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+        return new Request() {
+
+            private InputStream inputStream;
+
+            @Override
+            public String getMethod() {
+                return method == null ? "GET" : method;
+            }
+
+            @Override
+            public String getURI() {
+                return path;
+            }
+
+            @Override
+            public String getRelativePath() {
+                return path;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return true;
+            }
+
+            @Override
+            public String getFirstParam(String param) {
+                List<String> values = parameters.getOrDefault(param, Collections.emptyList());
+
+                if (!values.isEmpty()) {
+                    return values.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public String getQueryParamValue(String param) {
+                return getFirstParam(param);
+            }
+
+            @Override
+            public Cookie getCookie(String cookieName) {
+                return null;
+            }
+
+            @Override
+            public String getHeader(String name) {
+                List<String> headers = getHeaders(name);
+
+                if (!headers.isEmpty()) {
+                    return headers.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public List<String> getHeaders(String name) {
+                return headers.getOrDefault(name, Collections.emptyList());
+            }
+
+            @Override
+            public InputStream getInputStream() {
+                return getInputStream(false);
+            }
+
+            @Override
+            public InputStream getInputStream(boolean buffer) {
+                if (requestBody == null) {
+                    return new ByteArrayInputStream(new byte[] {});
+                }
+
+                if (inputStream != null) {
+                    return inputStream;
+                }
+
+                if (buffer) {
+                    return inputStream = new BufferedInputStream(requestBody);
+                }
+
+                return requestBody;
+            }
+
+            @Override
+            public String getRemoteAddr() {
+                return "user-remote-addr";
+            }
+
+            @Override
+            public void setError(AuthenticationError error) {
+
+            }
+
+            @Override
+            public void setError(LogoutError error) {
+
+            }
+        };
+    }
+
+    protected AuthzClient getAuthzClient(String fileName) {
+        try {
+            return AuthzClient.create(JsonSerialization.readValue(getAdapterConfiguration(fileName), Configuration.class));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to create authz client", cause);
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
index 4713239..2df71b6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
@@ -18,7 +18,6 @@ package org.keycloak.testsuite.admin.client.authorization;
 
 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 java.io.BufferedInputStream;
@@ -35,6 +34,7 @@ import java.util.stream.Collectors;
 
 import javax.security.cert.X509Certificate;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.keycloak.AuthorizationContext;
@@ -58,12 +58,9 @@ import org.keycloak.jose.jws.JWSInputException;
 import org.keycloak.representations.AccessToken;
 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.ScopePermissionRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.ProfileAssume;
@@ -117,242 +114,115 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
                 .build());
     }
 
-    @Test
-    public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
-        initAuthorizationSettings(getClientResource("resource-server-uma-test"));
-
-        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-uma-claims-test.json"));
-        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
-        HashMap<String, List<String>> headers = new HashMap<>();
-        HashMap<String, List<String>> parameters = new HashMap<>();
-
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        AuthzClient authzClient = getAuthzClient("enforcer-uma-claims-test.json");
-        String token = authzClient.obtainAccessToken("marta", "password").getToken();
-
-        headers.put("Authorization", Arrays.asList("Bearer " + token));
-
-        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-        assertFalse(context.isGranted());
-
-        AuthorizationRequest request = new AuthorizationRequest();
-
-        request.setTicket(extractTicket(headers));
-
-        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(request);
-        token = response.getToken();
-
-        assertNotNull(token);
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-        assertTrue(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("200"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-        assertFalse(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-        assertTrue(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("10"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-
-        request = new AuthorizationRequest();
-
-        request.setTicket(extractTicket(headers));
-
-        response = authzClient.authorization("marta", "password").authorize(request);
-        token = response.getToken();
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
-        assertTrue(context.isGranted());
-
-        request = new AuthorizationRequest();
-
-        request.setTicket(extractTicket(headers));
-
-        response = authzClient.authorization("marta", "password").authorize(request);
-        token = response.getToken();
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "GET", token, headers, parameters));
-        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());
+    @Before
+    public void onBefore() {
+        initAuthorizationSettings(getClientResource("resource-server-test"));
     }
 
     @Test
-    public void testEnforceEntitlementAccessWithClaimsWithoutBearerToken() {
-        initAuthorizationSettings(getClientResource("resource-server-test"));
-
-        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+    public void testBearerOnlyClientResponse() {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-bearer-only.json"));
         PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
-        HashMap<String, List<String>> headers = new HashMap<>();
-        HashMap<String, List<String>> parameters = new HashMap<>();
-
-        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
-        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+        OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea");
+        AuthorizationContext context = policyEnforcer.enforce(httpFacade);
 
-        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
         assertFalse(context.isGranted());
+        assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
 
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        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"));
+        oauth.realm(REALM_NAME);
+        oauth.clientId("public-client-test");
+        oauth.doLogin("marta", "password");
 
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertFalse(context.isGranted());
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
+        String token = response.getAccessToken();
 
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
+        httpFacade = createHttpFacade("/api/resourcea", token);
 
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        context = policyEnforcer.enforce(httpFacade);
         assertTrue(context.isGranted());
 
-        parameters.put("withdrawal.amount", Arrays.asList("10"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-
-        assertTrue(context.isGranted());
+        httpFacade = createHttpFacade("/api/resourceb");
 
-        assertEquals(1, context.getPermissions().size());
-        permission = context.getPermissions().get(0);
-        assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
+        context = policyEnforcer.enforce(httpFacade);
+        assertFalse(context.isGranted());
+        assertEquals(403, TestResponse.class.cast(httpFacade.getResponse()).getStatus());
     }
 
     @Test
-    public void testEnforceEntitlementAccessWithClaimsWithBearerToken() {
-        initAuthorizationSettings(getClientResource("resource-server-test"));
-
-        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+    public void testOnDenyRedirectTo() {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-on-deny-redirect.json"));
         PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
-        HashMap<String, List<String>> headers = new HashMap<>();
-        HashMap<String, List<String>> parameters = new HashMap<>();
-
-        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
-        String token = authzClient.obtainAccessToken("marta", "password").getToken();
-
-        headers.put("Authorization", Arrays.asList("Bearer " + token));
-
-        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertFalse(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertTrue(context.isGranted());
+        OIDCHttpFacade httpFacade = createHttpFacade("/api/resourcea");
+        AuthorizationContext context = policyEnforcer.enforce(httpFacade);
 
-        parameters.put("withdrawal.amount", Arrays.asList("200"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
         assertFalse(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertTrue(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("10"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-
-        assertTrue(context.isGranted());
+        TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
+        assertEquals(302, response.getStatus());
+        List<String> location = response.getHeaders().getOrDefault("Location", Collections.emptyList());
+        assertFalse(location.isEmpty());
+        assertEquals("/accessDenied", location.get(0));
     }
 
     @Test
-    public void testEnforceEntitlementAccessWithClaimsWithBearerTokenFromPublicClient() {
-        initAuthorizationSettings(getClientResource("resource-server-test"));
-
-        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+    public void testNotAuthenticatedDenyUnmapedPath() {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-bearer-only.json"));
         PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
-        HashMap<String, List<String>> headers = new HashMap<>();
-        HashMap<String, List<String>> parameters = new HashMap<>();
+        OIDCHttpFacade httpFacade = createHttpFacade("/api/unmmaped");
+        AuthorizationContext context = policyEnforcer.enforce(httpFacade);
 
-        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));
-
-        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
         assertFalse(context.isGranted());
+        TestResponse response = TestResponse.class.cast(httpFacade.getResponse());
+        assertEquals(403, response.getStatus());
+    }
 
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
-
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertTrue(context.isGranted());
-
-        parameters.put("withdrawal.amount", Arrays.asList("200"));
+    private void initAuthorizationSettings(ClientResource clientResource) {
+        if (clientResource.authorization().resources().findByName("Resource A").isEmpty()) {
+            JSPolicyRepresentation policy = new JSPolicyRepresentation();
 
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertFalse(context.isGranted());
+            policy.setName("Resource A Policy");
 
-        parameters.put("withdrawal.amount", Arrays.asList("50"));
+            StringBuilder code = new StringBuilder();
 
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
-        assertTrue(context.isGranted());
+            code.append("$evaluation.grant();");
 
-        parameters.put("withdrawal.amount", Arrays.asList("10"));
+            policy.setCode(code.toString());
 
-        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+            clientResource.authorization().policies().js().create(policy);
 
-        assertTrue(context.isGranted());
-    }
+            createResource(clientResource, "Resource A", "/api/resourcea");
 
-    private String extractTicket(HashMap<String, List<String>> headers) {
-        List<String> wwwAuthenticateHeader = headers.get("WWW-Authenticate");
+            ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
 
-        assertNotNull(wwwAuthenticateHeader);
-        assertFalse(wwwAuthenticateHeader.isEmpty());
+            permission.setName("Resource A Permission");
+            permission.addResource("Resource A");
+            permission.addPolicy(policy.getName());
 
-        String wwwAuthenticate = wwwAuthenticateHeader.get(0);
-        return wwwAuthenticate.substring(wwwAuthenticate.indexOf("ticket=") + "ticket=\"".length(), wwwAuthenticate.lastIndexOf('"'));
-    }
+            clientResource.authorization().permissions().resource().create(permission);
+        }
 
-    private void initAuthorizationSettings(ClientResource clientResource) {
-        if (clientResource.authorization().resources().findByName("Bank Account").isEmpty()) {
+        if (clientResource.authorization().resources().findByName("Resource B").isEmpty()) {
             JSPolicyRepresentation policy = new JSPolicyRepresentation();
 
-            policy.setName("Withdrawal Limit Policy");
+            policy.setName("Resource B Policy");
 
             StringBuilder code = new StringBuilder();
 
-            code.append("var context = $evaluation.getContext();");
-            code.append("var attributes = context.getAttributes();");
-            code.append("var withdrawalAmount = attributes.getValue('withdrawal.amount');");
-            code.append("if (withdrawalAmount && withdrawalAmount.asDouble(0) <= 100) {");
-            code.append("   $evaluation.grant();");
-            code.append("}");
+            code.append("$evaluation.deny();");
 
             policy.setCode(code.toString());
 
             clientResource.authorization().policies().js().create(policy);
 
-            createResource(clientResource, "Bank Account", "/api/bank/account/{id}/withdrawal", "withdrawal");
+            createResource(clientResource, "Resource B", "/api/resourceb");
 
-            ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
+            ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
 
-            permission.setName("Withdrawal Permission");
-            permission.addScope("withdrawal");
+            permission.setName("Resource B Permission");
+            permission.addResource("Resource B");
             permission.addPolicy(policy.getName());
 
-            clientResource.authorization().permissions().scope().create(permission);
+            clientResource.authorization().permissions().resource().create(permission);
         }
     }
 
@@ -387,13 +257,16 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
 
             @Override
             public KeycloakSecurityContext getSecurityContext() {
-                AccessToken accessToken;
-                try {
-                    accessToken = new JWSInput(token).readJsonContent(AccessToken.class);
-                } catch (JWSInputException cause) {
-                    throw new RuntimeException(cause);
+                if (token != null) {
+                    AccessToken accessToken;
+                    try {
+                        accessToken = new JWSInput(token).readJsonContent(AccessToken.class);
+                    } catch (JWSInputException cause) {
+                        throw new RuntimeException(cause);
+                    }
+                    return new KeycloakSecurityContext(token, accessToken, null, null);
                 }
-                return new KeycloakSecurityContext(token, accessToken, null, null);
+                return null;
             }
 
             @Override
@@ -419,64 +292,16 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
         };
     }
 
-    private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
-        return createHttpFacade(path, null, token, headers, parameters, null);
+    private OIDCHttpFacade createHttpFacade(String path, String token) {
+        return createHttpFacade(path, null, token, new HashMap<>(), new HashMap<>(), null);
     }
 
-    private OIDCHttpFacade createHttpFacade(String path, String method, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
-        return createHttpFacade(path, method, token, headers, parameters, null);
+    private OIDCHttpFacade createHttpFacade(String path) {
+        return createHttpFacade(path, null, null, new HashMap<>(), new HashMap<>(), null);
     }
 
     private Response createHttpResponse(Map<String, List<String>> headers) {
-        return new Response() {
-
-            private int status;
-
-            @Override
-            public void setStatus(int status) {
-                this.status = status;
-            }
-
-            @Override
-            public void addHeader(String name, String value) {
-                setHeader(name, value);
-            }
-
-            @Override
-            public void setHeader(String name, String value) {
-                headers.put(name, Arrays.asList(value));
-            }
-
-            @Override
-            public void resetCookie(String name, String path) {
-
-            }
-
-            @Override
-            public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
-
-            }
-
-            @Override
-            public OutputStream getOutputStream() {
-                return null;
-            }
-
-            @Override
-            public void sendError(int code) {
-
-            }
-
-            @Override
-            public void sendError(int code, String message) {
-
-            }
-
-            @Override
-            public void end() {
-
-            }
-        };
+        return new TestResponse();
     }
 
     private Request createHttpRequest(String path, String method, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
@@ -587,4 +412,63 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
             throw new RuntimeException("Failed to create authz client", cause);
         }
     }
+
+    private class TestResponse implements Response {
+
+        private final Map<String, List<String>> headers = new HashMap<>();
+        private int status;
+
+        @Override
+        public void setStatus(int status) {
+            this.status = status;
+        }
+
+        public int getStatus() {
+            return status;
+        }
+
+        @Override
+        public void addHeader(String name, String value) {
+            setHeader(name, value);
+        }
+
+        @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 resetCookie(String name, String path) {
+
+        }
+
+        @Override
+        public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return null;
+        }
+
+        @Override
+        public void sendError(int code) {
+            status = code;
+        }
+
+        @Override
+        public void sendError(int code, String message) {
+            status = code;
+        }
+
+        @Override
+        public void end() {
+
+        }
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-bearer-only.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-bearer-only.json
new file mode 100644
index 0000000..5f7f4e9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-bearer-only.json
@@ -0,0 +1,11 @@
+{
+  "realm": "authz-test",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "resource-server-test",
+  "credentials": {
+    "secret": "secret"
+  },
+  "bearer-only": true,
+  "policy-enforcer": {}
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-on-deny-redirect.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-on-deny-redirect.json
new file mode 100644
index 0000000..c254401
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-on-deny-redirect.json
@@ -0,0 +1,12 @@
+{
+  "realm": "authz-test",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "resource-server-test",
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "on-deny-redirect-to": "/accessDenied"
+  }
+}