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 +

Click here ">Sign Out

\ 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:

+ + + + 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; +%> +

Click here ">Sign Out

\ 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:

+ + + + \ 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