diff --git a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
index 95365ea195..364d8877ab 100644
--- a/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
+++ b/examples/authz/servlet-authz/src/main/webapp/logout-include.jsp
@@ -7,5 +7,5 @@
String contextPath = request.getContextPath();
String redirectUri = scheme + "://" + host + ":" + port + contextPath;
%>
-
Click ">here to logout.
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 391e0d8206..fa4b0c19ab 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -20,5 +20,6 @@
js-database
photoz
hello-world-authz-service
+ servlet-authz
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/README.md b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
new file mode 100644
index 0000000000..f93acb52ca
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/README.md
@@ -0,0 +1,54 @@
+# About the Example Application
+
+This is a simple Servlet-based application that will introduce you to some of the main concepts around Keycloak Authorization Services.
+
+For this application, users can be regular users, premium users or administrators, where:
+
+* Regular users have very limited access.
+* Premium users have access to the *premium area*
+* Administrators have access to the *administration area*
+
+In Keycloak, all the paths being protected are resources on the server.
+
+This application will also show you how to create a dynamic menu with the permissions granted to an user.
+
+## Create the Example Realm and a Resource Server
+
+Considering that your Keycloak Server is up and running, log in to the Keycloak Administration Console.
+
+Now, create a new realm based on the following configuration file:
+
+ examples/authz/servlet-authz/servlet-authz-realm.json
+
+That will import a pre-configured realm with everything you need to run this example. For more details about how to import a realm
+into Keycloak, check the Keycloak's reference documentation.
+
+After importing that file, you'll have a new realm called ``servlet-authz``.
+
+Now, let's import another configuration using the Administration Console in order to configure the client application ``servlet-authz-app`` as a resource server with all resources, scopes, permissions and policies.
+
+Click on ``Clients`` on the left side menu. Click on the ``servlet-authz-app`` on the client listing page. This will
+open the ``Client Details`` page. Once there, click on the `Authorization` tab.
+
+Click on the ``Select file`` button, which means you want to import a resource server configuration. Now select the file that is located at:
+
+ examples/authz/servlet-authz/servlet-authz-app-config.json
+
+Now click ``Upload`` and the resource server will be updated accordingly.
+
+## Deploy and Run the Example Applications
+
+To deploy the example application, follow these steps:
+
+ cd examples/authz/servlet-authz
+ mvn clean package wildfly:deploy
+
+Now, try to access the client application using the following URL:
+
+ http://localhost:8080/servlet-authz-app
+
+If everything is correct, you will be redirect to Keycloak login page. You can login to the application with the following credentials:
+
+* username: jdoe / password: jdoe
+* username: alice / password: alice
+* username: admin / password: admin
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
new file mode 100755
index 0000000000..fc9f6b0641
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+
+
+ org.keycloak.testsuite
+ integration-arquillian-test-apps
+ 2.1.0-SNAPSHOT
+
+
+ servlet-authz-app
+ war
+
+ Keycloak Authz: Servlet Authorization Test
+ Servlet Authorization Test
+
+
+
+
+ org.keycloak
+ keycloak-authz-client
+ ${project.version}
+ provided
+
+
+ org.keycloak
+ keycloak-core
+ ${project.version}
+ provided
+
+
+
+
+ ${project.artifactId}
+
+
+ org.jboss.as.plugins
+ jboss-as-maven-plugin
+
+ false
+
+
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+
+ false
+
+
+
+
+
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
new file mode 100644
index 0000000000..43ebde42b8
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -0,0 +1,147 @@
+{
+ "allowRemoteResourceManagement": true,
+ "policyEnforcementMode": "ENFORCING",
+ "resources": [
+ {
+ "name": "Admin Resource",
+ "uri": "/protected/admin/*",
+ "type": "http://servlet-authz/protected/admin",
+ "scopes": [
+ {
+ "name": "urn:servlet-authz:protected:admin:access"
+ }
+ ]
+ },
+ {
+ "name": "Protected Resource",
+ "uri": "/*",
+ "type": "http://servlet-authz/protected/resource",
+ "scopes": [
+ {
+ "name": "urn:servlet-authz:protected:resource:access"
+ }
+ ]
+ },
+ {
+ "name": "Premium Resource",
+ "uri": "/protected/premium/*",
+ "type": "urn:servlet-authz:protected:resource",
+ "scopes": [
+ {
+ "name": "urn:servlet-authz:protected:premium:access"
+ }
+ ]
+ },
+ {
+ "name": "Main Page",
+ "type": "urn:servlet-authz:protected:resource",
+ "scopes": [
+ {
+ "name": "urn:servlet-authz:page:main:actionForAdmin"
+ },
+ {
+ "name": "urn:servlet-authz:page:main:actionForUser"
+ },
+ {
+ "name": "urn:servlet-authz:page:main:actionForPremiumUser"
+ }
+ ]
+ }
+ ],
+ "policies": [
+ {
+ "name": "Any Admin Policy",
+ "description": "Defines that adminsitrators can do something",
+ "type": "role",
+ "config": {
+ "roles": "[{\"id\":\"admin\"}]"
+ }
+ },
+ {
+ "name": "Any User Policy",
+ "description": "Defines that any user can do something",
+ "type": "role",
+ "config": {
+ "roles": "[{\"id\":\"user\"}]"
+ }
+ },
+ {
+ "name": "Only Premium User Policy",
+ "description": "Defines that only premium users can do something",
+ "type": "role",
+ "logic": "POSITIVE",
+ "config": {
+ "roles": "[{\"id\":\"user_premium\"}]"
+ }
+ },
+ {
+ "name": "All Users Policy",
+ "description": "Defines that all users can do something",
+ "type": "aggregate",
+ "decisionStrategy": "AFFIRMATIVE",
+ "config": {
+ "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
+ }
+ },
+ {
+ "name": "Premium Resource Permission",
+ "description": "A policy that defines access to premium resources",
+ "type": "resource",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"Premium Resource\"]",
+ "applyPolicies": "[\"Only Premium User Policy\"]"
+ }
+ },
+ {
+ "name": "Administrative Resource Permission",
+ "description": "A policy that defines access to administrative resources",
+ "type": "resource",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"Admin Resource\"]",
+ "applyPolicies": "[\"Any Admin Policy\"]"
+ }
+ },
+ {
+ "name": "Protected Resource Permission",
+ "description": "A policy that defines access to any protected resource",
+ "type": "resource",
+ "decisionStrategy": "AFFIRMATIVE",
+ "config": {
+ "resources": "[\"Protected Resource\"]",
+ "applyPolicies": "[\"All Users Policy\"]"
+ }
+ },
+ {
+ "name": "Action 1 on Main Page Resource Permission",
+ "description": "A policy that defines access to action 1 on the main page",
+ "type": "scope",
+ "decisionStrategy": "AFFIRMATIVE",
+ "config": {
+ "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]",
+ "applyPolicies": "[\"Any Admin Policy\"]"
+ }
+ },
+ {
+ "name": "Action 2 on Main Page Resource Permission",
+ "description": "A policy that defines access to action 2 on the main page",
+ "type": "scope",
+ "decisionStrategy": "AFFIRMATIVE",
+ "config": {
+ "scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]",
+ "applyPolicies": "[\"Any User Policy\"]"
+ }
+ },
+ {
+ "name": "Action 3 on Main Page Resource Permission",
+ "description": "A policy that defines access to action 3 on the main page",
+ "type": "scope",
+ "decisionStrategy": "AFFIRMATIVE",
+ "config": {
+ "scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]",
+ "applyPolicies": "[\"Only Premium User Policy\"]"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
new file mode 100644
index 0000000000..371e4510f5
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-realm.json
@@ -0,0 +1,95 @@
+{
+ "realm": "servlet-authz",
+ "enabled": true,
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": [
+ "password"
+ ],
+ "users": [
+ {
+ "username": "alice",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "alice"
+ }
+ ],
+ "realmRoles": [
+ "user"
+ ]
+ },
+ {
+ "username": "jdoe",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "jdoe"
+ }
+ ],
+ "realmRoles": [
+ "user",
+ "user_premium"
+ ]
+ },
+ {
+ "username": "admin",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "admin"
+ }
+ ],
+ "realmRoles": [
+ "user",
+ "admin"
+ ],
+ "clientRoles": {
+ "realm-management": [
+ "realm-admin"
+ ]
+ }
+ },
+ {
+ "username": "service-account-servlet-authz-app",
+ "enabled": true,
+ "serviceAccountClientId": "servlet-authz-app",
+ "clientRoles": {
+ "servlet-authz-app" : ["uma_protection"]
+ }
+ }
+ ],
+ "roles": {
+ "realm": [
+ {
+ "name": "user",
+ "description": "User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Administrator privileges"
+ },
+ {
+ "name": "user_premium",
+ "description": "User Premium privileges"
+ }
+ ]
+ },
+ "clients": [
+ {
+ "clientId": "servlet-authz-app",
+ "enabled": true,
+ "baseUrl": "/servlet-authz-app",
+ "adminUrl": "/servlet-authz-app",
+ "bearerOnly": false,
+ "authorizationServicesEnabled": true,
+ "redirectUris": [
+ "/servlet-authz-app/*"
+ ],
+ "secret": "secret"
+ }
+ ]
+}
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
new file mode 100644
index 0000000000..515ffa5c73
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/META-INF/jboss-deployment-structure.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000000..7b362a782f
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,14 @@
+{
+ "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"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..14d0615978
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,47 @@
+
+
+
+ servlet-authz-app
+
+
+
+ All Resources
+ /*
+
+
+ user
+
+
+
+
+
+ All Resources
+ /*
+
+
+ admin
+
+
+
+
+ KEYCLOAK
+ servlet-authz
+
+
+
+ admin
+
+
+
+ user
+
+
+
+ 403
+ /accessDenied.jsp
+
+
+
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
new file mode 100644
index 0000000000..6f25023af0
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/accessDenied.jsp
@@ -0,0 +1,6 @@
+
+
+ You can not access this resource.
+ <%@include file="logout-include.jsp"%>
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
new file mode 100755
index 0000000000..3fbfca269c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/index.jsp
@@ -0,0 +1,35 @@
+<%@page import="org.keycloak.AuthorizationContext" %>
+<%@ page import="org.keycloak.KeycloakSecurityContext" %>
+<%@ page import="org.keycloak.representations.idm.authorization.Permission" %>
+
+<%
+ KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
+ AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
+%>
+
+
+
+ <%@include file="logout-include.jsp"%>
+ This is a public resource. Try to access one of these protected resources:
+
+ Dynamic Menu
+ User Premium
+ Administration
+
+ Your permissions are:
+
+
+ <%
+ for (Permission permission : authzContext.getPermissions()) {
+ %>
+ -
+
Resource: <%= permission.getResourceSetName() %>
+ ID: <%= permission.getResourceSetId() %>
+ Scopes: <%= permission.getScopes() %>
+
+ <%
+ }
+ %>
+
+
+
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
new file mode 100644
index 0000000000..21ef2edebf
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/logout-include.jsp
@@ -0,0 +1,11 @@
+<%@ page import="org.keycloak.common.util.KeycloakUriBuilder" %>
+<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
+<%
+ String scheme = request.getScheme();
+ String host = request.getServerName();
+ int port = request.getServerPort();
+ String contextPath = request.getContextPath();
+ String redirectUri = scheme + "://" + host + ":" + port + contextPath;
+%>
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
new file mode 100644
index 0000000000..5946cd660c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/admin/onlyAdmin.jsp
@@ -0,0 +1,6 @@
+
+
+ Only Administrators can access this page.
+ <%@include file="../../logout-include.jsp"%>
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
new file mode 100644
index 0000000000..1473d223f3
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/dynamicMenu.jsp
@@ -0,0 +1,48 @@
+<%@page import="org.keycloak.AuthorizationContext" %>
+<%@ page import="org.keycloak.KeycloakSecurityContext" %>
+
+<%
+ KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
+ AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
+%>
+
+
+
+Any authenticated user can access this page.
+<%@include file="../logout-include.jsp"%>
+
+Here is a dynamic menu built from the permissions returned by the server:
+
+
+ <%
+ if (authzContext.hasResourcePermission("Protected Resource")) {
+ %>
+ -
+ Do user thing
+
+ <%
+ }
+ %>
+
+ <%
+ if (authzContext.hasResourcePermission("Premium Resource")) {
+ %>
+ -
+ Do user premium thing
+
+ <%
+ }
+ %>
+
+ <%
+ if (authzContext.hasPermission("Admin Resource", "urn:servlet-authz:protected:admin:access")) {
+ %>
+ -
+ Do administration thing
+
+ <%
+ }
+ %>
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
new file mode 100644
index 0000000000..9244f9ca5e
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/premium/onlyPremium.jsp
@@ -0,0 +1,6 @@
+
+
+Only for premium users.
+<%@include file="../../logout-include.jsp"%>
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
index 9621ae53ef..3e905447b0 100755
--- a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
+++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
@@ -43,5 +43,13 @@
+
+
+
+
+
+
+
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
new file mode 100644
index 0000000000..2753c5e5bd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
@@ -0,0 +1,352 @@
+/*
+ * 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 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.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.RealmResource;
+import org.keycloak.admin.client.resource.RoleResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
+import org.keycloak.testsuite.util.WaitUtils;
+import org.keycloak.util.JsonSerialization;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.util.IOUtil.loadJson;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+
+/**
+ * @author Pedro Igor
+ */
+public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAdapterTest {
+
+ private static final String REALM_NAME = "servlet-authz";
+ private static final String RESOURCE_SERVER_ID = "servlet-authz-app";
+
+ @ArquillianResource
+ private Deployer deployer;
+
+ @Override
+ public void addAdapterTestRealms(List testRealms) {
+ testRealms.add(
+ loadRealm(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-realm.json")));
+ }
+
+ @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+ public static WebArchive deployment() throws IOException {
+ return exampleDeployment(RESOURCE_SERVER_ID);
+ }
+
+ @Override
+ public void beforeAbstractKeycloakTest() throws Exception {
+ super.beforeAbstractKeycloakTest();
+ importResourceServerSettings();
+ }
+
+ @Test
+ public void testUserPermissions() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+
+ login("alice", "alice");
+
+ assertFalse(wasDenied());
+
+ assertTrue(hasLink("User Premium"));
+ assertTrue(hasLink("Administration"));
+ assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+ assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+ assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+
+ navigateToDynamicMenuPage();
+
+ assertTrue(hasText("Do user thing"));
+ assertFalse(hasText("Do user premium thing"));
+ assertFalse(hasText("Do administration thing"));
+
+
+ navigateToUserPremiumPage();
+
+ assertTrue(wasDenied());
+
+ navigateToAdminPage();
+
+ assertTrue(wasDenied());
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ @Test
+ public void testUserPremiumPermissions() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+
+ login("jdoe", "jdoe");
+
+ assertFalse(wasDenied());
+ assertTrue(hasLink("User Premium"));
+ assertTrue(hasLink("Administration"));
+ assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+ assertTrue(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+ assertFalse(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+
+ navigateToDynamicMenuPage();
+
+ assertTrue(hasText("Do user thing"));
+ assertTrue(hasText("Do user premium thing"));
+ assertFalse(hasText("Do administration thing"));
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ @Test
+ public void testAdminPermissions() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+
+ login("admin", "admin");
+
+ assertFalse(wasDenied());
+
+ assertTrue(hasLink("User Premium"));
+ assertTrue(hasLink("Administration"));
+ assertTrue(hasText("urn:servlet-authz:page:main:actionForUser"));
+ assertTrue(hasText("urn:servlet-authz:page:main:actionForAdmin"));
+ assertFalse(hasText("urn:servlet-authz:page:main:actionForPremiumUser"));
+
+ navigateToDynamicMenuPage();
+
+ assertTrue(hasText("Do user thing"));
+ assertTrue(hasText("Do administration thing"));
+ assertFalse(hasText("Do user premium thing"));
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ @Test
+ public void testGrantPremiumAccess() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+
+ login("alice", "alice");
+
+ assertFalse(wasDenied());
+
+ navigateToUserPremiumPage();
+
+ assertTrue(wasDenied());
+
+ for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+ if ("Premium Resource Permission".equals(policy.getName())) {
+ policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
+ getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+ }
+ }
+
+ login("alice", "alice");
+
+ navigateToUserPremiumPage();
+
+ assertFalse(wasDenied());
+
+ for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+ if ("Premium Resource Permission".equals(policy.getName())) {
+ policy.getConfig().put("applyPolicies", "[\"Only Premium User Policy\"]");
+ getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+ }
+ }
+
+ login("alice", "alice");
+ navigateToUserPremiumPage();
+
+ assertTrue(wasDenied());
+
+ PolicyRepresentation onlyAlicePolicy = new PolicyRepresentation();
+
+ onlyAlicePolicy.setName("Temporary Premium Access Policy");
+ onlyAlicePolicy.setType("user");
+ HashMap config = new HashMap<>();
+ UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
+ List users = usersResource.search("alice", null, null, null, null, null);
+
+ assertFalse(users.isEmpty());
+
+ config.put("users", JsonSerialization.writeValueAsString(Arrays.asList(users.get(0).getId())));
+
+ onlyAlicePolicy.setConfig(config);
+ getAuthorizationResource().policies().create(onlyAlicePolicy);
+
+ for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+ if ("Premium Resource Permission".equals(policy.getName())) {
+ policy.getConfig().put("applyPolicies", "[\"Temporary Premium Access Policy\"]");
+ getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+ }
+ }
+
+ logOut();
+ login("alice", "alice");
+ navigateToUserPremiumPage();
+
+ assertFalse(wasDenied());
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ @Test
+ public void testGrantAdministrativePermissions() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+
+ login("jdoe", "jdoe");
+
+ navigateToAdminPage();
+ assertTrue(wasDenied());
+
+ RealmResource realmResource = realmsResouce().realm(REALM_NAME);
+ UsersResource usersResource = realmResource.users();
+ List users = usersResource.search("jdoe", null, null, null, null, null);
+
+ assertFalse(users.isEmpty());
+
+ UserResource userResource = usersResource.get(users.get(0).getId());
+
+ RoleRepresentation adminRole = realmResource.roles().get("admin").toRepresentation();
+ userResource.roles().realmLevel().add(Arrays.asList(adminRole));
+
+ login("jdoe", "jdoe");
+
+ navigateToAdminPage();
+ assertFalse(wasDenied());
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
+ private boolean hasLink(String text) {
+ return getLink(text) != null;
+ }
+
+ private boolean hasText(String text) {
+ return this.driver.getPageSource().contains(text);
+ }
+
+ private WebElement getLink(String text) {
+ return this.driver.findElement(By.xpath("//a[text() = '" + text + "']"));
+ }
+
+ private void importResourceServerSettings() throws FileNotFoundException {
+ getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class));
+ }
+
+ private AuthorizationResource getAuthorizationResource() throws FileNotFoundException {
+ 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();
+ By by = By.xpath("//a[text() = 'Sign Out']");
+ WaitUtils.waitUntilElement(by);
+ this.driver.findElement(by).click();
+ pause(500);
+ }
+
+ private void login(String username, String password) throws InterruptedException {
+ navigateTo();
+ Thread.sleep(2000);
+ if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) {
+ Thread.sleep(2000);
+ logOut();
+ navigateTo();
+ }
+
+ Thread.sleep(2000);
+
+ this.loginPage.form().login(username, password);
+ }
+
+ private void navigateTo() {
+ this.driver.navigate().to(getResourceServerUrl());
+ WaitUtils.waitUntilElement(By.xpath("//a[text() = 'Dynamic Menu']"));
+ }
+
+ private boolean wasDenied() {
+ return this.driver.getPageSource().contains("You can not access this resource.");
+ }
+
+ private URL getResourceServerUrl() {
+ try {
+ return new URL(this.appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Could not obtain resource server url.", e);
+ }
+ }
+
+ private void navigateToDynamicMenuPage() {
+ navigateTo();
+ getLink("Dynamic Menu").click();
+ }
+
+ private void navigateToUserPremiumPage() {
+ navigateTo();
+ getLink("User Premium").click();
+ }
+
+ private void navigateToAdminPage() {
+ navigateTo();
+ getLink("Administration").click();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyServletAuthzAdapterTest.java
new file mode 100644
index 0000000000..9a9a49b8de
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyServletAuthzAdapterTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index b8c715f229..7ae36b2c53 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -287,6 +287,12 @@
${project.version}
war
+
+ org.keycloak.testsuite
+ servlet-authz-app
+ ${project.version}
+ war
+
${examples.home}
true