Removing PHOTOZ client and related tests testing UI. Closes #19668

This commit is contained in:
Lukas Hanusovsky 2023-05-15 14:13:38 +02:00 committed by Michal Hajas
parent d9b95e0240
commit eb77dcf014
42 changed files with 0 additions and 3581 deletions

View file

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

View file

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

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps-photoz-parent</artifactId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>photoz-html5-client</artifactId>
<packaging>war</packaging>
<name>Keycloak Authz Tests: Photoz HTML5 Client</name>
<description>Photoz Test HTML5 Client</description>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,25 +0,0 @@
<!--
~ Copyright 2021 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.
~
-->
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.keycloak.keycloak-common" services="import"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>photoz-html5-client</module-name>
</web-app>

View file

@ -1,67 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Photoz HTML5 Client</title>
<script src="lib/jwt-decode.min.js"></script>
<script src="http://localhost:8180/auth/js/keycloak.js"></script>
<script src="http://localhost:8180/auth/js/keycloak-authz.js"></script>
</head>
<h2>Result</h2>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="output"></pre>
<h2>Events</h2>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="events"></pre>
<script>
function showExpires() {
if (!keycloak.tokenParsed) {
output("Not authenticated");
return;
}
var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
if (keycloak.refreshTokenParsed) {
o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
}
output(o);
}
function showError() {
output("Error: " + getParameterByName("error") + "\n" + "Error description: " + getParameterByName("error_description"));
}
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function output(data) {
if (typeof data === 'object') {
data = JSON.stringify(data, null, ' ');
}
document.getElementById('output').innerHTML = data;
}
function event(event) {
var e = document.getElementById('events').innerHTML;
document.getElementById('events').innerHTML = new Date().toLocaleString() + "\t" + event + "\n" + e;
}
</script>
</body>
</html>

View file

@ -1,7 +0,0 @@
{
"realm": "photoz",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "photoz-html5-client",
"public-client" : true
}

View file

@ -1 +0,0 @@
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){function c(a){return decodeURIComponent(atob(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var d=a("Base64");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return c(b)}catch(e){return d.atob(b)}}},{Base64:4}],2:[function(a,b){"use strict";var c=a("./base64_url_decode"),d=a("./json_parse");b.exports=function(a){if(!a)throw new Error("Invalid token specified");return d(c(a.split(".")[1]))}},{"./base64_url_decode":1,"./json_parse":3}],3:[function(require,module,exports){module.exports=function(str){var parsed;return parsed="object"==typeof JSON?JSON.parse(str):eval("("+str+")")}},{}],4:[function(a,b,c){!function(){var a="undefined"!=typeof c?c:this,b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",d=function(){try{document.createElement("$")}catch(a){return a}}();a.btoa||(a.btoa=function(a){for(var c,e,f=0,g=b,h="";a.charAt(0|f)||(g="=",f%1);h+=g.charAt(63&c>>8-f%1*8)){if(e=a.charCodeAt(f+=.75),e>255)throw d;c=c<<8|e}return h}),a.atob||(a.atob=function(a){if(a=a.replace(/=+$/,""),a.length%4==1)throw d;for(var c,e,f=0,g=0,h="";e=a.charAt(g++);~e&&(c=f%4?64*c+e:e,f++%4)?h+=String.fromCharCode(255&c>>(-2*f&6)):0)e=b.indexOf(e);return h})}()},{}],5:[function(a){var b="undefined"!=typeof self?self:"undefined"!=typeof window?window:{},c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)},{"./lib/index":2}]},{},[5]);

View file

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

View file

@ -1,159 +0,0 @@
{
"realm": "photoz",
"enabled": true,
"userManagedAccessAllowed": true,
"sslRequired": "external",
"accessTokenLifespan": 100000,
"requiredCredentials": [
"password"
],
"users": [
{
"username": "alice",
"enabled": true,
"email": "alice@keycloak.org",
"firstName": "Alice",
"lastName": "In Chains",
"credentials": [
{
"type": "password",
"value": "alice"
}
],
"realmRoles": [
"user", "uma_authorization"
],
"clientRoles": {
"photoz-restful-api": [
"manage-albums"
],
"account": [
"manage-account"
]
}
},
{
"username": "jdoe",
"enabled": true,
"email": "jdoe@keycloak.org",
"firstName": "John",
"lastName": "Doe",
"credentials": [
{
"type": "password",
"value": "jdoe"
}
],
"realmRoles": [
"user", "uma_authorization"
],
"clientRoles": {
"photoz-restful-api": [
"manage-albums"
],
"account": [
"manage-account"
]
}
},
{
"username": "pedroigor",
"enabled": true,
"email": "pedroigor@keycloak.org",
"firstName": "Pedro Igor",
"credentials": [
{
"type": "password",
"value": "pedroigor"
}
],
"realmRoles": [
"user", "uma_authorization"
],
"clientRoles": {
"photoz-restful-api": [
"manage-albums"
],
"account": [
"manage-account"
]
}
},
{
"username": "admin",
"enabled": true,
"email": "admin@admin.com",
"firstName": "Admin",
"lastName": "Istrator",
"credentials": [
{
"type": "password",
"value": "admin"
}
],
"realmRoles": [
"admin", "uma_authorization"
],
"clientRoles": {
"realm-management": [
"realm-admin"
],
"photoz-restful-api": [
"manage-albums"
],
"account": [
"manage-account"
]
}
},
{
"username": "service-account-photoz-restful-api",
"enabled": true,
"serviceAccountClientId": "photoz-restful-api",
"clientRoles": {
"photoz-restful-api" : ["uma_protection"]
}
}
],
"roles": {
"realm": [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"clients": [
{
"clientId": "photoz-html5-client",
"enabled": true,
"adminUrl": "/photoz-html5-client",
"baseUrl": "/photoz-html5-client",
"publicClient": true,
"consentRequired" : true,
"fullScopeAllowed" : true,
"redirectUris": [
"*"
],
"webOrigins": ["*"]
},
{
"clientId": "photoz-restful-api",
"enabled": true,
"baseUrl": "/photoz-restful-api",
"authorizationServicesEnabled" : true,
"redirectUris": [
"*"
],
"webOrigins" : ["*"],
"clientAuthenticatorType": "client-jwt",
"attributes" : {
"jwt.credential.certificate" : "MIICqTCCAZECBgFT0Ngs/DANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1zZWN1cmUtcG9ydGFsMB4XDTE2MDQwMTA4MDA0MVoXDTI2MDQwMTA4MDIyMVowGDEWMBQGA1UEAwwNc2VjdXJlLXBvcnRhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJa4GixpmzP511AmI0eLPLORyJwXS8908MUvdG3hmh8jMOIhe28XjIFeZSY09vFxh22F2SUMjxU/B2Hw4PDJUkebuNR7rXhOIYCJAo6eEZzjSBY/wngFtfm74zJ/eLCobBtDvIld7jobdHTfE1Oz9+GzvtG0k7cm7ubrLT0J4I1UsFZj3b//3wa+O0vNaTwHC1Jz/m59VbtXqyO4xEzIdl416cnGCmEmk5qd5h1de2UoLi/CTad8HftIJhzN1qhlySzW/9Ha70aYlDH2hiibDsXDTrNaMdaaLik7I8Rv/nIbggysG863PKZo8wknDe62QctH5VYSSktiy4gjSJkGh7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZnnx+AHQ8txugGcFK8gWjildDgk+v31fBHBDvmLQaSzsUaIOJaK4wnlwUI+VfR46HmBXhjlDCobFLUptd+kz0G7xapcIn3b5jLrySUUD7L+LAp1vNOQU4mKhTGS3IEvNB73D3GH9rQ+M3KEcoN3f99fNKqKsUdxbmZqGf4VOQ57PUfLBw4PJJGlROPosBc7ivPRyeYnKekhoCTynq30BAD1FA1BA8ppcY4ZVGADPTAgMJxpglpFY9LiqCwdLAGW1ttnsyIJ7DpT+kybhhk7c+MU7gyQdv8xPnMR0bSCB9hndowgBn5oZ393aMscwMNCzwJ0aWBs1sUyn3X0RIsu9Jg=="
}
}
]
}

View file

@ -1,228 +0,0 @@
{
"allowRemoteResourceManagement": true,
"policyEnforcementMode": "ENFORCING",
"resources": [
{
"name": "User Profile Resource",
"uri": "/profile",
"type": "http://photoz.com/profile",
"scopes": [
{
"name": "profile:view"
}
]
},
{
"name": "Album Resource",
"uri": "/album/*",
"type": "http://photoz.com/album",
"scopes": [
{
"name": "album:view"
},
{
"name": "album:delete"
},
{
"name": "album:create"
}
]
},
{
"name": "Admin Resources",
"uri": "/admin/*",
"type": "http://photoz.com/admin",
"scopes": [
{
"name": "admin:manage"
}
]
},
{
"name": "Scope Protected Resource",
"uri": "/scope-any",
"scopes": [
{
"name": "scope-a"
},
{
"name": "scope-b"
}
]
}
],
"policies": [
{
"name": "Only Owner Policy",
"description": "Defines that only the resource owner is allowed to do something",
"type": "script-scripts/only-owner-policy.js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS"
},
{
"name": "Any Admin Policy",
"description": "Defines that adminsitrators can do something",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"roles": "[{\"id\":\"admin\",\"required\":true}]"
}
},
{
"name": "Any User Policy",
"description": "Defines that only users from well known clients are allowed to access",
"type": "role",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[]",
"roles": "[{\"id\":\"user\"},{\"id\":\"photoz-restful-api/manage-albums\",\"required\":true}]"
}
},
{
"name": "Only From a Specific Client Address",
"description": "Defines that only clients from a specific address can do something",
"type": "script-scripts/only-from-specific-address-policy.js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS"
},
{
"name": "Deny From a Specific Client Address",
"description": "Defines that only clients from a specific address can do something",
"type": "script-scripts/deny-from-specific-address-policy.js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS"
},
{
"name": "Administration Policy",
"description": "Defines that only administrators from a specific network address can do something.",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Only From a Specific Client Address\",\"Any Admin Policy\"]"
}
},
{
"name": "Only Owner and Administrators Policy",
"description": "Defines that only the resource owner and administrators can do something",
"type": "aggregate",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
}
},
{
"name": "Only From @keycloak.org or Admin",
"description": "Defines that only users from @keycloak.org",
"type": "script-scripts/only-from-specific-domain-or-admin-policy.js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS"
},
{
"name": "Album Resource Permission",
"description": "General policies that apply to all album resources.",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "AFFIRMATIVE",
"config": {
"defaultResourceType": "http://photoz.com/album",
"default": "true",
"applyPolicies": "[\"Any User Policy\",\"Administration Policy\"]"
}
},
{
"name": "Admin Resource Permission",
"description": "General policy for any administrative resource.",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"defaultResourceType": "http://photoz.com/admin",
"default": "true",
"applyPolicies": "[\"Administration Policy\"]"
}
},
{
"name": "View User Permission",
"description": "Defines who is allowed to view an user profile",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Only From @keycloak.org or Admin\"]",
"scopes": "[\"profile:view\"]"
}
},
{
"name": "Delete Album Permission",
"description": "A policy that only allows the owner to delete his albums.",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Only Owner and Administrators Policy\"]",
"scopes": "[\"album:delete\"]"
}
},
{
"name": "View Album Permission",
"description": "A policy that only allows the owner to view his albums.",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"applyPolicies": "[\"Only Owner and Administrators Policy\"]",
"scopes": "[\"album:view\"]"
}
},
{
"name": "Deny Policy",
"type": "script-scripts/always-deny-policy.js",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS"
},
{
"name": "Protected Scope A Permission",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Scope Protected Resource\"]",
"scopes": "[\"scope-a\"]",
"applyPolicies": "[\"Any User Policy\"]"
}
},
{
"name": "Protected Scope B Permission",
"type": "scope",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Scope Protected Resource\"]",
"scopes": "[\"scope-b\"]",
"applyPolicies": "[\"Deny Policy\"]"
}
}
],
"scopes": [
{
"name": "profile:view"
},
{
"name": "album:view"
},
{
"name": "album:create"
},
{
"name": "album:delete"
},
{
"name": "admin:manage"
}
]
}

View file

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps-photoz-parent</artifactId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>photoz-restful-api</artifactId>
<packaging>war</packaging>
<name>Keycloak Authz Test: Photoz RESTful API</name>
<description>Photoz RESTful API</description>
<properties>
<jakarta.persistence.version>${jakarta.persistence-legacy.version}</jakarta.persistence.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,88 +0,0 @@
package org.keycloak.example.photoz;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.entity.Photo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author mhajas
*/
public class CustomDatabase {
private static final CustomDatabase INSTANCE = new CustomDatabase();
private List<Album> albums;
private List<Photo> photos;
private Long lastIndex = 0L;
public static final CustomDatabase create() {
return INSTANCE;
}
private CustomDatabase() {
albums = new ArrayList<>();
}
public List<Album> getAll() {
return albums;
}
public void addAlbum(Album a) {
a.setId(lastIndex++);
albums.add(a);
}
public void remove(Album albumToRemove) {
Iterator<Album> iter = albums.iterator();
while (iter.hasNext()) {
Album a = iter.next();
if (a.getId().equals(albumToRemove.getId())) {
iter.remove();
}
}
}
public Album findById(Long id) {
for (Album a : albums) {
if(a.getId().equals(id)) {
return a;
}
}
return null;
}
public Album findByName(String name) {
for (Album a : albums) {
if(a.getName().equals(name)) {
return a;
}
}
return null;
}
public List<Album> findByUserId(String userId) {
List<Album> result = new ArrayList<>();
for (Album a : albums) {
if (a.getUserId().equals(userId)) {
result.add(a);
}
}
return result;
}
public int cleanAll() {
int result = albums.size() + photos.size();
albums.clear();
photos.clear();
return result;
}
}

View file

@ -1,32 +0,0 @@
package org.keycloak.example.photoz;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ErrorResponse extends WebApplicationException {
private final Response.Status status;
public ErrorResponse(String message) {
this(message, Response.Status.INTERNAL_SERVER_ERROR);
}
public ErrorResponse(String message, Response.Status status) {
super(message, status);
this.status = status;
}
@Override
public Response getResponse() {
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("message", getMessage());
return Response.status(status).entity(errorResponse).build();
}
}

View file

@ -1,27 +0,0 @@
package org.keycloak.example.photoz;
import org.keycloak.example.photoz.admin.AdminAlbumService;
import org.keycloak.example.photoz.album.AlbumService;
import org.keycloak.example.photoz.album.ProfileService;
import org.keycloak.example.photoz.unsecured.UnsecuredService;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Basic auth app.
*/
@ApplicationPath("/")
public class PhotozApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new LinkedHashSet<Class<?>>();
resources.add(AlbumService.class);
resources.add(AdminAlbumService.class);
resources.add(ProfileService.class);
resources.add(UnsecuredService.class);
return resources;
}
}

View file

@ -1,63 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.example.photoz.admin;
import org.keycloak.example.photoz.CustomDatabase;
import org.keycloak.example.photoz.entity.Album;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@Path("/admin/album")
public class AdminAlbumService {
public static final String SCOPE_ADMIN_ALBUM_MANAGE = "admin:manage";
private CustomDatabase entityManager = CustomDatabase.create();
@Context
private HttpHeaders headers;
@GET
@Produces("application/json")
public Response findAll() {
HashMap<String, List<Album>> albums = new HashMap<>();
List<Album> result = this.entityManager.getAll();
for (Album album : result) {
//We need to compile this under JDK7 so we can't use lambdas
//albums.computeIfAbsent(album.getUserId(), key -> new ArrayList<>()).add(album);
if (!albums.containsKey(album.getUserId())) {
albums.put(album.getUserId(), Collections.singletonList(album));
}
}
return Response.ok(albums).build();
}
}

View file

@ -1,166 +0,0 @@
package org.keycloak.example.photoz.album;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.example.photoz.CustomDatabase;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.HashSet;
import java.util.List;
import javax.ws.rs.core.HttpHeaders;
import org.jboss.logging.Logger;
@Path("/album")
public class AlbumService {
private final Logger log = Logger.getLogger(AlbumService.class);
public static final String SCOPE_ALBUM_VIEW = "album:view";
public static final String SCOPE_ALBUM_DELETE = "album:delete";
private CustomDatabase customDatabase = CustomDatabase.create();
@Context
private HttpServletRequest request;
@POST
@Consumes("application/json")
@Produces("application/json")
public Response create(Album newAlbum, @QueryParam("user") String invalidUser, @Context HttpHeaders headers) {
printAuthHeaders(headers);
String userId = request.getUserPrincipal().getName();
if (invalidUser != null) {
userId = invalidUser;
}
newAlbum.setUserId(userId);
log.debug("PERSISTING " + newAlbum);
customDatabase.addAlbum(newAlbum);
try {
createProtectedResource(newAlbum);
} catch (RuntimeException e) {
log.debug("ERROR " + e);
customDatabase.remove(newAlbum);
return Response.status(500).entity(e.getMessage()).build(); //
}
return Response.ok(newAlbum).build();
}
@Path("{name}")
@DELETE
public Response delete(@PathParam("name") String name, @Context HttpHeaders headers) {
printAuthHeaders(headers);
Album album = this.customDatabase.findByName(name);
try {
deleteProtectedResource(album);
this.customDatabase.remove(album);
} catch (Exception e) {
throw new RuntimeException("Could not delete album.", e);
}
return Response.ok().build();
}
@GET
@Produces("application/json")
public Response findAll(@QueryParam("getAll") Boolean getAll) {
if (getAll != null && getAll) {
return Response.ok(this.customDatabase.getAll()).build();
} else {
return Response.ok(this.customDatabase.findByUserId(request.getUserPrincipal().getName())).build();
}
}
@GET
@Path("{name}")
@Produces("application/json")
public Response findById(@PathParam("name") String name) {
Album result = this.customDatabase.findByName(name);
if (result == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(result).build();
}
private void createProtectedResource(Album album) {
log.debug("Creating ProtectedResource for " + album);
try {
HashSet<ScopeRepresentation> scopes = new HashSet<>();
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getName(), "http://photoz.com/album");
albumResource.setOwner(album.getUserId());
if (album.isUserManaged()) {
albumResource.setOwnerManagedAccess(true);
}
getAuthzClient().protection().resource().create(albumResource);
} catch (Exception e) {
throw new RuntimeException("Could not register protected resource.", e);
}
}
private void deleteProtectedResource(Album album) {
String uri = "/album/" + album.getName();
try {
ProtectionResource protection = getAuthzClient().protection();
List<ResourceRepresentation> search = protection.resource().findByUri(uri);
if (search.isEmpty()) {
throw new RuntimeException("Could not find protected resource with URI [" + uri + "]");
}
protection.resource().delete(search.get(0).getId());
} catch (RuntimeException e) {
throw new RuntimeException("Could not search protected resource.", e);
}
}
private AuthzClient getAuthzClient() {
return getAuthorizationContext().getClient();
}
private ClientAuthorizationContext getAuthorizationContext() {
return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
}
private KeycloakSecurityContext getKeycloakSecurityContext() {
return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
}
private void printAuthHeaders(HttpHeaders headers) {
log.debug("-----------------Authorization headers--------------------------");
for (String authHeader : headers.getRequestHeader(HttpHeaders.AUTHORIZATION)) {
log.debug(authHeader);
}
}
}

View file

@ -1,66 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.example.photoz.album;
import org.keycloak.example.photoz.CustomDatabase;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.security.Principal;
import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@Path("/profile")
public class ProfileService {
private static final String PROFILE_VIEW = "profile:view";
private CustomDatabase customDatabase = CustomDatabase.create();
@GET
@Produces("application/json")
public Response view(@Context HttpServletRequest request) {
Principal userPrincipal = request.getUserPrincipal();
List albums = this.customDatabase.findByUserId(userPrincipal.getName());
return Response.ok(new Profile(userPrincipal.getName(), albums.size())).build();
}
public static class Profile {
private String userName;
private int totalAlbums;
public Profile(String name, int totalAlbums) {
this.userName = name;
this.totalAlbums = totalAlbums;
}
public String getUserName() {
return userName;
}
public int getTotalAlbums() {
return totalAlbums;
}
}
}

View file

@ -1,102 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.example.photoz.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"name", "userId"})
})
public class Album implements Serializable {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(mappedBy = "album", fetch = FetchType.EAGER)
private List<Photo> photos = new ArrayList<>();
@Column(nullable = false)
private String userId;
@Transient
private boolean userManaged = false;
public Long getId() {
return this.id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public List<Photo> getPhotos() {
return this.photos;
}
public void setPhotos(final List<Photo> photos) {
this.photos = photos;
}
public void setUserId(final String userId) {
this.userId = userId;
}
public String getUserId() {
return this.userId;
}
public boolean isUserManaged() {
return userManaged;
}
public void setUserManaged(boolean userManaged) {
this.userManaged = userManaged;
}
@Override
public String toString() {
return "Album{" + "id=" + id + ", name=" + name + ", userId=" + userId + '}';
}
}

View file

@ -1,82 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.example.photoz.entity;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@Entity
public class Photo implements Serializable {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@ManyToOne
private Album album;
@Lob
@Column
@Basic(fetch = FetchType.LAZY)
private byte[] image;
public Long getId() {
return this.id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public Album getAlbum() {
return this.album;
}
public void setAlbum(final Album album) {
this.album = album;
}
public byte[] getImage() {
return this.image;
}
public void setImage(final byte[] image) {
this.image = image;
}
}

View file

@ -1,51 +0,0 @@
/*
* 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.example.photoz.unsecured;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import org.jboss.logging.Logger;
import org.keycloak.example.photoz.CustomDatabase;
/**
* Service used to ensure there is clean DB before test
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
@Path("/unsecured/clean")
public class UnsecuredService {
private final Logger log = Logger.getLogger(UnsecuredService.class);
private CustomDatabase customDatabase = CustomDatabase.create();
@GET
@Produces("application/json")
public Response cleanAll() {
int deleted = customDatabase.cleanAll();
if (deleted != 0) {
log.warnf("Database was not empty. Deleted Albums + Photos {0}", deleted);
} else {
log.debug("Database was clean before test");
}
return Response.ok().build();
}
}

View file

@ -1,27 +0,0 @@
<!--
~ 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.
~
-->
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.keycloak.keycloak-authz-client" services="import"/>
<module name="com.h2database.h2" services="import"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

View file

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

View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>photoz-restful-api</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>All Resources</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>All Resources</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Unsecured</web-resource-name>
<url-pattern>/unsecured/*</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>KEYCLOAK</auth-method>
<realm-name>photoz</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps</artifactId>
<version>999.0.0-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-test-apps-photoz-parent</artifactId>
<packaging>pom</packaging>
<name>Keycloak Authz: PhotoZ Test Parent</name>
<description>PhotoZ Test Application</description>
<modules>
<module>photoz-restful-api</module>
<module>photoz-html5-client</module>
</modules>
</project>

View file

@ -16,7 +16,6 @@
<modules>
<module>test-apps-dist</module>
<module>photoz</module>
<module>hello-world-authz-service</module>
<module>servlet-authz</module>
<module>servlet-policy-enforcer</module>

View file

@ -19,14 +19,6 @@
<target name="all">
<delete dir="target/test-apps"/>
<copy todir="target/test-apps/photoz" overwrite="true">
<fileset dir="../photoz">
<exclude name="**/target/**"/>
<exclude name="**/*.iml"/>
<exclude name="**/*.unconfigured"/>
<exclude name="**/subsystem-config.xml"/>
</fileset>
</copy>
<copy todir="target/test-apps/hello-world-authz-service" overwrite="true">
<fileset dir="../hello-world-authz-service">
<exclude name="**/target/**"/>

View file

@ -144,12 +144,6 @@
<artifactId>integration-arquillian-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-restful-api</artifactId>
<version>${project.version}</version>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>

View file

@ -1,318 +0,0 @@
/*
* 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.page;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.UIUtils;
import org.keycloak.testsuite.util.URLUtils;
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutorWithAuthorization;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.net.URL;
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.util.UIUtils.clickLink;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
public static final String DEPLOYMENT_NAME = "photoz-html5-client";
public static final int WAIT_AFTER_OPERATION = 1000;
@ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME)
private URL url;
@Drone
@JavascriptBrowser
protected WebDriver driver;
@Page
@JavascriptBrowser
protected OIDCLogin loginPage;
@FindBy(xpath = "//a[@ng-click = 'Identity.logout()']")
@JavascriptBrowser
private WebElement signOutButton;
@FindBy(id = "entitlement")
@JavascriptBrowser
private WebElement entitlement;
@FindBy(id = "entitlements")
@JavascriptBrowser
private WebElement entitlements;
@FindBy(id = "get-all-resources")
@JavascriptBrowser
private WebElement viewAllAlbums;
@FindBy(id = "output")
@JavascriptBrowser
private WebElement output;
private JavascriptTestExecutorWithAuthorization testExecutor;
private String apiUrl;
public void setTestExecutorPlayground(JavascriptTestExecutorWithAuthorization executor, String apiUrl) {
testExecutor = executor;
this.apiUrl = apiUrl;
}
public void createAlbum(String name) {
createAlbum(name, false);
}
public void createAlbum(String name, boolean managed) {
createAlbum(name, managed, false, null);
}
public void createAlbum(String name, boolean managed, boolean invalidUser, ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("POST")
.url(apiUrl + "/album" + (invalidUser ? "?user=invalidUser" : ""))
.content("JSON.stringify(JSON.parse('{\"name\" : \"" + name + "\", \"userManaged\": " + Boolean.toString(managed) + " }'))")
.addHeader("Content-Type", "application/json; charset=UTF-8")
, validator);
}
public void createAlbumWithInvalidUser(String name, ResponseValidator validator) {
createAlbum(name, false, true, validator);
}
@Override
public URL getInjectedUrl() {
return this.url;
}
public void deleteAlbum(String name, ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("DELETE")
.url(apiUrl + "/album/" + name + "/") // it doesn't work without ending "/"
, validator);
}
public void navigateToAdminAlbum(ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.addHeader("Accept", "application/json")
.url(apiUrl + "/admin/album")
, validator);
}
public void logOut() {
navigateTo();
waitUntilElement(signOutButton).is().clickable(); // Sometimes doesn't work in PhantomJS!
clickLink(signOutButton);
}
public void requestEntitlement(JavascriptStateValidator validator) {
testExecutor.executeAsyncScript("var callback = arguments[arguments.length - 1];" +
"window.authorization.entitlement('photoz-restful-api', {" +
" \"permissions\": [" +
" {" +
" \"id\" : \"Album Resource\"" +
" }" +
" ]" +
"}).then(function (rpt) {" +
" callback(JSON.stringify(jwt_decode(rpt), null, ' '));" +
"});", validator);
}
public void requestEntitlements(JavascriptStateValidator validator) {
testExecutor.executeAsyncScript("var callback = arguments[arguments.length - 1];" +
"window.authorization.entitlement('photoz-restful-api', {}).then(function (rpt) {" +
" callback(JSON.stringify(jwt_decode(rpt), null, ' '));" +
"});", validator);
}
private void waitForDenial() {
waitUntilElement(output).text().contains("You can not access");
}
private void waitForNotDenial() {
waitUntilElement(output).text().not().contains("You can not access");
}
public void viewAllAlbums() {
viewAllAlbums.click();
pause(WAIT_AFTER_OPERATION);
}
public void viewProfile(ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.addHeader("Accept", "application/json")
.url(apiUrl + "/profile")
, validator);
}
public void viewAlbum(String name, ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.addHeader("Accept", "application/json")
.url(apiUrl + "/album/" + name + "/")
, validator);
}
public void accountPage() {
testExecutor.openAccountPage(null);
}
public void accountMyResources() {
accountPage();
WebElement myResources = driver.findElement(By.xpath("//a[text() = 'My Resources']"));
waitUntilElement(myResources).is().clickable();
myResources.click();
}
public void accountMyResource(String name) {
accountMyResources();
WebElement myResource = driver.findElement(By.id("detail-" + name));
waitUntilElement(myResource).is().clickable();
myResource.click();
}
public void accountGrantResource(String name, String requester) {
accountMyResources();
grantResource(name, requester);
}
public void grantResource(String name, String requester) {
WebElement grantResource = driver.findElement(By.id("grant-" + name + "-" + requester));
waitUntilElement(grantResource).is().clickable();
grantResource.click();
}
public void accountGrantRemoveScope(String name, String requester, String scope) {
accountMyResources();
WebElement grantRemoveScope = driver.findElement(By.id("grant-remove-scope-" + name + "-" + requester + "-" + scope));
waitUntilElement(grantRemoveScope).is().clickable();
grantRemoveScope.click();
}
public void accountRevokeResource(String name, String requester) {
accountMyResource(name);
revokeResource(name, requester);
}
public void revokeResource(String name, String requester) {
WebElement revokeResource = driver.findElement(By.id("revoke-" + name + "-" + requester));
waitUntilElement(revokeResource).is().clickable();
revokeResource.click();
}
public void accountShareResource(String name, String user) {
accountMyResource(name);
shareResource(user);
}
public void accountShareRemoveScope(String name, String user, String scope) {
accountMyResource(name);
WebElement shareRemoveScope = driver.findElement(By.id("share-remove-scope-" + name + "-" + scope));
waitUntilElement(shareRemoveScope).is().clickable();
shareRemoveScope.click();
shareResource(user);
}
public void shareResource(String user) {
WebElement userIdInput = driver.findElement(By.id("user_id"));
UIUtils.setTextInputValue(userIdInput, user);
pause(200); // We need to wait a bit for the form to "accept" the input (otherwise it registers the input as empty)
waitUntilElement(userIdInput).attribute(UIUtils.VALUE_ATTR_NAME).contains(user);
WebElement shareButton = driver.findElement(By.id("share-button"));
waitUntilElement(shareButton).is().clickable();
shareButton.click();
}
public void assertError() {
assertEquals("We are sorry...", driver.findElement(By.id("kc-page-title")).getText());
}
public void accountDenyResource(String name) {
accountMyResource(name);
WebElement denyLink = driver.findElement(By.linkText("Deny"));
waitUntilElement(denyLink).is().clickable();
denyLink.click();
waitForPageToLoad();
}
public void requestResourceProtectedAnyScope(ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.url(apiUrl + "/scope-any")
, validator);
}
public void requestResourceProtectedAllScope(ResponseValidator validator) {
testExecutor.sendXMLHttpRequest(
XMLHttpRequest.create()
.method("GET")
.url(apiUrl + "/scope-all")
, validator);
}
public WebElement getOutput() {
return output;
}
@Override
public void navigateTo() {
driver.navigate().to(toString());
waitForPageToLoad();
}
@Override
public boolean isCurrent() {
return URLUtils.currentUrlStartsWith(toString());
}
public void executeScript(String script) {
testExecutor.executeScript(script);
}
}

View file

@ -1,167 +0,0 @@
package org.keycloak.testsuite.util.javascript;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.util.URLUtils;
import org.openqa.selenium.WebDriver;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
/**
* @author mhajas
*/
public class JavascriptTestExecutorWithAuthorization extends JavascriptTestExecutor {
public static JavascriptTestExecutorWithAuthorization create(WebDriver driver, OIDCLogin loginPage) {
return new JavascriptTestExecutorWithAuthorization(driver, loginPage);
}
private JavascriptTestExecutorWithAuthorization(WebDriver driver, OIDCLogin loginPage) {
super(driver, loginPage);
}
@Override
public JavascriptTestExecutorWithAuthorization init(JSObjectBuilder argumentsBuilder, JavascriptStateValidator validator) {
super.init(argumentsBuilder, validator);
Object output = jsExecutor.executeScript(
"var callback = arguments[arguments.length - 1];" +
"window.authorization = new KeycloakAuthorization(window.keycloak);" +
"while (typeof window.authorization === 'undefined') {}" + // Wait until authorization is initialized
"return 'Authz initialized'");
assertThat(output, is("Authz initialized"));
return this;
}
@Override
public JavascriptTestExecutorWithAuthorization login(JavascriptStateValidator validator) {
super.login(validator);
return this;
}
public JavascriptTestExecutorWithAuthorization loginFormWithScopesWithPossibleConsentPage(UserRepresentation user, JavascriptStateValidator validator, OAuthGrant oAuthGrantPage, String... scopes) {
String currentUrl = jsDriver.getCurrentUrl();
if (scopes.length > 0) {
StringBuilder scopesValue = new StringBuilder();
for (String scope : scopes) {
if (scopesValue.length() != 0) {
scopesValue.append(" ");
}
scopesValue.append(scope);
}
scopesValue.append(" openid");
StringBuilder urlWithScopeParam = new StringBuilder(currentUrl);
int scopeIndex = currentUrl.indexOf("scope");
if (scopeIndex != -1) {
// Remove scope param from url
urlWithScopeParam.delete(scopeIndex, currentUrl.indexOf('&', scopeIndex));
// Add scope param to the end of query
urlWithScopeParam.append("&").append("scope=");
}
if (!currentUrl.contains("?")) {
urlWithScopeParam.append("?scope=");
}
urlWithScopeParam.append(scopesValue);
URLUtils.navigateToUri(urlWithScopeParam.toString());
waitForPageToLoad();
}
loginFormWithPossibleConsentPage(user, oAuthGrantPage, validator);
return this;
}
public JavascriptTestExecutorWithAuthorization loginFormWithPossibleConsentPage(UserRepresentation user, OAuthGrant oAuthGrantPage, JavascriptStateValidator validator) {
super.loginForm(user);
try {
// simple check if we are at the consent page, if so just click 'Yes'
if (oAuthGrantPage.isCurrent(jsDriver)) {
oAuthGrantPage.accept();
waitForPageToLoad();
}
} catch (Exception ignore) {
// ignore errors when checking consent page, if an error tests will also fail
}
if (validator != null) {
validator.validate(jsDriver, null, events);
}
return this;
}
@Override
public JavascriptTestExecutor sendXMLHttpRequest(XMLHttpRequest request, ResponseValidator validator) {
// Intercept all requests and add rpt or token
// check if rpt is already present
Object o = jsExecutor.executeScript("if(window.authorization && window.authorization.rpt) return true; else return false;");
if (o == null || o.equals(false)) {
// RPT is not present yet, lets try to use bearer token
request.includeBearerToken();
} else {
// RPT token is present so we will use it
request.includeRpt();
}
// Try to send request
Map<String, Object> result = request.send(jsExecutor);
// If request was denied do UMA
if ((Long.valueOf(403).equals(result.get("status")) || Long.valueOf(401).equals(result.get("status")))) {
//extracting ticket from response
String headersString = (String) result.get("responseHeaders");
List<String> headersList = Arrays.asList(headersString.split("\r\n"));
String wwwAuthenticate = headersList.stream().filter(s -> s.toLowerCase().startsWith("www-authenticate:")).findFirst().get();
if (wwwAuthenticate.contains("UMA") && wwwAuthenticate.contains("ticket")) {
String ticket = Arrays.asList(wwwAuthenticate.split(",")).stream().filter(s -> s.startsWith("ticket")).findFirst().get();
ticket = ticket.substring(0, ticket.length() - 1).replaceFirst("ticket=\"", "");
// AuthorizationRequest for RPT
o = jsExecutor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"window.authorization" +
".authorize(" + JSObjectBuilder.create().add("ticket", ticket).build() + ")" +
".then(function (rpt) {callback(rpt)}, function() {callback('failed1')}, function() {callback('failed2')});");
o = jsExecutor.executeScript("if(window.authorization && window.authorization.rpt) return true; else return false;"); // return window.authorization && window.authorization.rpt doesn't work
if (o != null && o.equals(true)) {
request.includeRpt();
result = request.send(jsExecutor);
}
}
}
if (validator != null) {
validator.validate(result);
}
return this;
}
}

View file

@ -1,312 +0,0 @@
/*
* Copyright 2019 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.authz.adapter.example;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
import jakarta.ws.rs.core.Response;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientScopesResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.util.DroneUtils;
import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutorWithAuthorization;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.wildfly.extras.creaper.core.online.CliException;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractBasePhotozExampleAdapterTest extends AbstractPhotozJavascriptExecutorTest {
protected static final String RESOURCE_SERVER_ID = "photoz-restful-api";
protected static final String ALICE_ALBUM_NAME = "Alice-Family-Album";
private static final int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
@ArquillianResource
private Deployer deployer;
@Page
@JavascriptBrowser protected PhotozClientAuthzTestApp clientPage;
@Page
@JavascriptBrowser
private OAuthGrant oAuthGrantPage;
protected JavascriptTestExecutorWithAuthorization testExecutor;
@FindBy(id = "output")
@JavascriptBrowser
protected WebElement outputArea;
@FindBy(id = "events")
@JavascriptBrowser
protected WebElement eventsArea;
@BeforeClass
public static void enabled() {
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
testRealmPage.setAuthRealm(REALM_NAME);
oAuthGrantPage.setAuthRealm(REALM_NAME);
}
@Before
public void beforePhotozExampleAdapterTest() throws Exception {
DroneUtils.addWebDriver(jsDriver);
deployIgnoreIfDuplicate(RESOURCE_SERVER_ID);
clientPage.navigateTo();
// waitForPageToLoad();
assertCurrentUrlStartsWith(clientPage.toString());
testExecutor = JavascriptTestExecutorWithAuthorization.create(jsDriver, jsDriverTestRealmLoginPage);
clientPage.setTestExecutorPlayground(testExecutor, appServerContextRootPage + "/" + RESOURCE_SERVER_ID);
jsDriver.manage().deleteAllCookies();
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
HttpGet request = new HttpGet(clientPage.toString() + "/unsecured/clean");
httpClient.execute(request).close();
}
}
// workaround for KEYCLOAK-8660 from https://stackoverflow.com/questions/50917932/what-versions-of-jackson-are-allowed-in-jboss-6-4-20-patch
@Before
public void fixBrokenDeserializationOnEAP6() throws IOException, CliException, TimeoutException, InterruptedException {
if (AppServerTestEnricher.isEAP6AppServer()) {
OnlineManagementClient client = AppServerTestEnricher.getManagementClient();
Administration administration = new Administration(client);
client.execute("/system-property=jackson.deserialization.whitelist.packages:add(value=org.keycloak.example.photoz)");
administration.reloadIfRequired();
}
}
@After
public void afterPhotozExampleAdapterTest() {
this.deployer.undeploy(RESOURCE_SERVER_ID);
DroneUtils.removeWebDriver();
}
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadRealm(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-realm.json"));
realm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds
testRealms.add(realm);
}
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
importResourceServerSettings();
}
protected List<ResourceRepresentation> getResourcesOfUser(String username) throws FileNotFoundException {
return getAuthorizationResource().resources().resources().stream().filter(resource -> resource.getOwner().getName().equals(username)).collect(Collectors.toList());
}
protected void printUpdatedPolicies() throws FileNotFoundException {
log.debug("Check updated policies");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
log.debugf("Policy: %s", policy.getName());
for (String key : policy.getConfig().keySet()) {
log.debugf("-- key: %s, value: %s", key, policy.getConfig().get(key));
}
}
log.debug("------------------------------");
}
protected void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events) {
waitForPageToLoad();
assertCurrentUrlStartsWith(clientPage.toString(), jsDriver);
}
protected void assertWasDenied(Map<String, Object> response) {
assertThat(response.get("status"), is(equalTo(401L)));
}
protected void assertWasNotDenied(Map<String, Object> response) {
assertThat(response.get("status"), is(equalTo(200L)));
}
private void importResourceServerSettings() throws FileNotFoundException {
ResourceServerRepresentation authSettings = loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class);
authSettings.getPolicies().stream()
.filter(x -> "Only Owner Policy".equals(x.getName()))
.forEach(x -> x.getConfig().put("mavenArtifactVersion", System.getProperty("project.version")));
getAuthorizationResource().importSettings(authSettings);
}
protected AuthorizationResource getAuthorizationResource() throws FileNotFoundException {
return getClientResource(RESOURCE_SERVER_ID).authorization();
}
protected ClientResource getClientResource(String clientId) {
ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
return clients.get(resourceServer.getId());
}
protected void loginToClientPage(UserRepresentation user, String... scopes) throws InterruptedException {
log.debugf("--logging in as '%s' with password: '%s'; scopes: %s", user.getUsername(), user.getCredentials().get(0).getValue(), Arrays.toString(scopes));
if (testExecutor.isLoggedIn()) {
testExecutor.logout(this::assertOnTestAppUrl, logoutConfirmPage);
jsDriver.manage().deleteAllCookies();
jsDriver.navigate().to(testRealmLoginPage.toString());
jsDriver.manage().deleteAllCookies();
}
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(this::assertOnLoginPage)
.loginFormWithScopesWithPossibleConsentPage(user, this::assertOnTestAppUrl, oAuthGrantPage, scopes)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
new WebDriverWait(jsDriver, 10).until(this::isLoaded);
}
public boolean isLoaded(WebDriver w) {
JavascriptExecutor jsExecutor = (JavascriptExecutor) w;
Map<String, Object> o = (Map<String, Object>) jsExecutor.executeScript("return window.authorization.config");
return o != null && o.containsKey("token_endpoint");
}
protected void setManageAlbumScopeRequired() {
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
clientScope.setName("manage-albums");
clientScope.setProtocol("openid-connect");
ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation();
mapper.setName("manage-albums");
mapper.setProtocol("openid-connect");
mapper.setProtocolMapper(UserClientRoleMappingMapper.PROVIDER_ID);
Map<String, String> config = new HashMap<>();
config.put("access.token.claim", "true");
config.put("id.token.claim", "true");
config.put("userinfo.token.claim", "true");
config.put(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID, "photoz-restful-api");
mapper.setConfig(config);
clientScope.setProtocolMappers(Arrays.asList(mapper));
RealmResource realmResource = realmsResouce().realm(REALM_NAME);
ClientScopesResource clientScopes = realmResource.clientScopes();
Response resp = clientScopes.create(clientScope);
Assert.assertEquals(201, resp.getStatus());
resp.close();
String clientScopeId = ApiUtil.getCreatedId(resp);
ClientResource resourceServer = getClientResource(RESOURCE_SERVER_ID);
clientScopes.get(clientScopeId).getScopeMappings().clientLevel(resourceServer.toRepresentation().getId()).add(Arrays.asList(resourceServer.roles().get("manage-albums").toRepresentation()));
ClientResource html5ClientApp = getClientResource("photoz-html5-client");
html5ClientApp.addOptionalClientScope(clientScopeId);
html5ClientApp.getScopeMappings().realmLevel().add(Arrays.asList(realmResource.roles().get("user").toRepresentation(), realmResource.roles().get("admin").toRepresentation()));
ClientRepresentation clientRep = html5ClientApp.toRepresentation();
clientRep.setFullScopeAllowed(false);
html5ClientApp.update(clientRep);
}
/**
* Redeploy if duplicate resource is present.
* KEYCLOAK-18442
*
* @param name Name of the deployment
*/
protected void deployIgnoreIfDuplicate(String name) {
try {
deployer.deploy(name);
} catch (Exception e) {
//DeploymentException is thrown by an deployer event handler and cannot be explicitly caught
//noinspection ConstantConditions
if (e instanceof DeploymentException && e.getMessage().contains("Duplicate resource")) {
log.warnf("Duplicate resource '%s'. Trying to undeploy and deploy again...", name);
deployer.undeploy(name);
deployer.deploy(name);
return;
}
throw e;
}
}
}

View file

@ -1,196 +0,0 @@
/*
* Copyright 2019 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.authz.adapter.example;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public abstract class AbstractPhotozAccountResourcesAdapterTest extends AbstractBasePhotozExampleAdapterTest {
@Test
public void testOwnerSharingResource() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
clientPage.accountShareResource(ALICE_ALBUM_NAME, "jdoe");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
clientPage.accountShareRemoveScope(ALICE_ALBUM_NAME, "jdoe", "album:delete");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.accountRevokeResource(ALICE_ALBUM_NAME, "jdoe");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
}
@Test
public void testRequestResourceToOwner() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.accountGrantResource(ALICE_ALBUM_NAME, "jdoe");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.accountGrantRemoveScope(ALICE_ALBUM_NAME, "jdoe", "album:delete");
// get back to clientPage and init javascript adapter in order to navigate to accountPage again
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
clientPage.accountGrantResource(ALICE_ALBUM_NAME, "jdoe");
// get back to clientPage and init javascript adapter in order to log out correctly
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
}
@Test
public void testCsrfGrantAccess() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.accountMyResources();
clientPage.executeScript("document.forms[0].stateChecker.value = 'invalid'");
clientPage.grantResource(ALICE_ALBUM_NAME, "jdoe");
clientPage.assertError();
}
@Test
public void testCsrfRevokeResource() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.accountGrantResource(ALICE_ALBUM_NAME, "jdoe");
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.accountMyResource(ALICE_ALBUM_NAME);
clientPage.executeScript("document.forms[0].stateChecker.value = 'invalid'");
clientPage.revokeResource(ALICE_ALBUM_NAME, "jdoe");
clientPage.assertError();
}
@Test
public void testCrfCheckSharingResource() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME, true);
clientPage.accountMyResource(ALICE_ALBUM_NAME);
clientPage.executeScript("document.forms['shareForm'].stateChecker.value = 'invalid'");
clientPage.shareResource("jdoe");
clientPage.assertError();
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login()
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(aliceUser);
clientPage.accountMyResource(ALICE_ALBUM_NAME);
clientPage.shareResource("jdoe");
clientPage.navigateTo();
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
loginToClientPage(jdoeUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
}
}

View file

@ -1,477 +0,0 @@
/*
* 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.authz.adapter.example;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.PoliciesResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.ResourcesResource;
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.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractPhotozExampleAdapterTest extends AbstractBasePhotozExampleAdapterTest {
@Test
public void testUserCanCreateAndDeleteAlbum() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty())));
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
log.debug("Check if alice has resources deleted");
assertThat(getResourcesOfUser("alice"), is(empty()));
}
@Test
@UncaughtServerErrorExpected
public void createAlbumWithInvalidUser() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbumWithInvalidUser(ALICE_ALBUM_NAME, response -> {
assertThat(response.get("status"), is(equalTo(500L)));
assertThat(response.get("res"), is(equalTo("Could not register protected resource.")));
});
}
@Test
public void testPathConfigInvalidation() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty())));
log.debug("Adding applyPolicies \"Only Owner Policy\" to \"Delete Album Permission\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
log.debug("Check if alice has resources stored");
assertThat(getResourcesOfUser("alice"), is(not(empty())));
log.debug("Adding applyPolicies \"Only Owner and Administrators Policy\" to \"Delete Album Permission\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner and Administrators Policy\"]");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
log.debug("Check if alice has resources deleted");
assertThat(getResourcesOfUser("alice"), is(empty()));
}
@Test
public void testRegularUserCanNotAccessAdminResources() throws Exception {
loginToClientPage(aliceUser);
clientPage.navigateToAdminAlbum(this::assertWasDenied);
}
@Test
public void testAdminOnlyFromSpecificAddress() throws Exception {
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
log.debug("Changing codes \"127.0.0.1\" to \"127.3.3.3\" of \"Only From a Specific Client Address\" policies.");
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Administration Policy".equals(policy.getName())) {
policy.setPolicies(new HashSet<>());
policy.getPolicies().add("Any Admin Policy");
policy.getPolicies().add("Deny From a Specific Client Address");
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasDenied);
}
@Test
public void testAdminWithoutPermissionsToTypedResource() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
PoliciesResource policiesResource = getAuthorizationResource().policies();
List<PolicyRepresentation> policies = policiesResource.policies();
for (PolicyRepresentation policy : policies) {
if ("Album Resource Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
policiesResource.policy(policy.getId()).update(policy);
}
if ("Any User Policy".equals(policy.getName())) {
ClientResource resourceServerClient = getClientResource(RESOURCE_SERVER_ID);
RoleResource manageAlbumRole = resourceServerClient.roles().get("manage-albums");
RoleRepresentation roleRepresentation = manageAlbumRole.toRepresentation();
List<Map<String, Object>> roles = JsonSerialization.readValue(policy.getConfig().get("roles"), List.class);
roles = roles.stream().filter((Map map) -> !map.get("id").equals(roleRepresentation.getId())).collect(Collectors.toList());
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
policiesResource.policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(adminUser); // Clear cache
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : policies) {
if ("Album Resource Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Any User Policy\", \"Administration Policy\"]");
policiesResource.policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(adminUser); // Clear cache
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(empty()));
}
@Test
public void testAdminWithoutPermissionsToDeleteAlbum() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(empty()));
PoliciesResource policiesResource = getAuthorizationResource().policies();
List<PolicyRepresentation> policies = policiesResource.policies();
for (PolicyRepresentation policy : policies) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner Policy\"]");
policiesResource.policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
loginToClientPage(adminUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(not(empty())));
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : policies) {
if ("Delete Album Permission".equals(policy.getName())) {
policy.getConfig().put("applyPolicies", "[\"Only Owner and Administrators Policy\"]");
policiesResource.policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(adminUser); // Clear cache
clientPage.deleteAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(empty()));
}
@Test
public void testClientRoleRepresentingUserConsent() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
RealmResource realmResource = realmsResouce().realm(REALM_NAME);
UsersResource usersResource = realmResource.users();
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
assertFalse(users.isEmpty());
UserRepresentation userRepresentation = users.get(0);
UserResource userResource = usersResource.get(userRepresentation.getId());
ClientResource html5ClientApp = getClientResource("photoz-html5-client");
ClientRepresentation clientRepresentation = html5ClientApp.toRepresentation();
userResource.revokeConsent(clientRepresentation.getClientId());
setManageAlbumScopeRequired();
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
loginToClientPage(aliceUser, "manage-albums");
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
}
@Test
public void testClientRoleNotRequired() throws Exception {
loginToClientPage(aliceUser);
clientPage.createAlbum(ALICE_ALBUM_NAME);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
UsersResource usersResource = realmsResouce().realm(REALM_NAME).users();
List<UserRepresentation> users = usersResource.search("alice", null, null, null, null, null);
assertFalse(users.isEmpty());
UserRepresentation userRepresentation = users.get(0);
UserResource userResource = usersResource.get(userRepresentation.getId());
ClientResource html5ClientApp = getClientResource("photoz-html5-client");
userResource.revokeConsent(html5ClientApp.toRepresentation().getClientId());
ClientResource resourceServerClient = getClientResource(RESOURCE_SERVER_ID);
RoleResource manageAlbumRole = resourceServerClient.roles().get("manage-albums");
RoleRepresentation roleRepresentation = manageAlbumRole.toRepresentation();
setManageAlbumScopeRequired();
manageAlbumRole.update(roleRepresentation);
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasDenied);
for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
if ("Any User Policy".equals(policy.getName())) {
List<Map<String, Object>> roles = JsonSerialization.readValue(policy.getConfig().get("roles"), List.class);
roles.forEach(role -> {
String roleId = (String) role.get("id");
if (roleId.equals(manageAlbumRole.toRepresentation().getId())) {
role.put("required", false);
}
});
policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
getAuthorizationResource().policies().policy(policy.getId()).update(policy);
}
}
printUpdatedPolicies();
loginToClientPage(aliceUser);
clientPage.viewAlbum(ALICE_ALBUM_NAME, this::assertWasNotDenied);
}
@Test
public void testOverridePermissionFromResourceParent() throws Exception {
loginToClientPage(aliceUser);
String resourceName = "My-Resource-Instance";
clientPage.createAlbum(resourceName);
clientPage.viewAlbum(resourceName, this::assertWasNotDenied);
clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
clientPage.createAlbum(resourceName);
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, this::assertWasNotDenied);
clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
loginToClientPage(aliceUser);
clientPage.createAlbum(resourceName);
AuthorizationResource authorizationResource = getAuthorizationResource();
authorizationResource.resources().resources().forEach(resource -> {
if (resource.getName().equals(resourceName)) {
try {
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
resourceInstancePermission.setName(resourceName + "Permission");
resourceInstancePermission.setType("resource");
Map<String, String> config = new HashMap<>();
config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
resourceInstancePermission.setConfig(config);
authorizationResource.policies().create(resourceInstancePermission);
} catch (IOException e) {
throw new RuntimeException("Error creating policy.", e);
}
}
});
printUpdatedPolicies();
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(resourceName, this::assertWasDenied);
clientPage.deleteAlbum(resourceName, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.deleteAlbum(resourceName, this::assertWasNotDenied);
assertThat(getResourcesOfUser("alice"), is(empty()));
}
@Test
public void testInheritPermissionFromResourceParent() throws Exception {
loginToClientPage(aliceUser);
final String RESOURCE_NAME = "My-Resource-Instance";
clientPage.createAlbum(RESOURCE_NAME);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.createAlbum(RESOURCE_NAME);
loginToClientPage(adminUser);
clientPage.navigateToAdminAlbum(this::assertWasNotDenied);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
loginToClientPage(aliceUser);
clientPage.createAlbum(RESOURCE_NAME);
ResourcesResource resourcesResource = getAuthorizationResource().resources();
resourcesResource.resources().forEach(resource -> {
if (resource.getName().equals(RESOURCE_NAME)) {
try {
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
resourceInstancePermission.setName(RESOURCE_NAME + "Permission");
resourceInstancePermission.setType("resource");
Map<String, String> config = new HashMap<>();
config.put("resources", JsonSerialization.writeValueAsString(Arrays.asList(resource.getId())));
config.put("applyPolicies", JsonSerialization.writeValueAsString(Arrays.asList("Only Owner Policy")));
resourceInstancePermission.setConfig(config);
getAuthorizationResource().policies().create(resourceInstancePermission);
} catch (IOException e) {
throw new RuntimeException("Error creating policy.", e);
}
}
});
loginToClientPage(adminUser);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasDenied);
resourcesResource.resources().forEach(resource -> {
if (resource.getName().equals(RESOURCE_NAME)) {
resource.setScopes(resource.getScopes().stream().filter(scope -> !scope.getName().equals("album:view")).collect(Collectors.toSet()));
resourcesResource.resource(resource.getId()).update(resource);
}
});
loginToClientPage(adminUser);
clientPage.viewAlbum(RESOURCE_NAME, this::assertWasNotDenied);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasDenied);
loginToClientPage(aliceUser);
clientPage.deleteAlbum(RESOURCE_NAME, this::assertWasNotDenied);
List<ResourceRepresentation> resources = resourcesResource.resources();
assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
}
//KEYCLOAK-3777
@Test
public void testEntitlementRequest() throws Exception {
loginToClientPage(adminUser);
clientPage.requestEntitlements((driver1, output, events) -> assertThat((String) output, containsString("admin:manage")));
loginToClientPage(adminUser);
clientPage.requestEntitlement((driver1, output, events) -> {
assertThat((String) output, not(containsString("admin:manage")));
assertThat((String) output, containsString("album:view"));
assertThat((String) output, containsString("album:delete"));
}
);
}
@Test
public void testResourceProtectedWithAnyScope() throws Exception {
loginToClientPage(aliceUser);
clientPage.requestResourceProtectedAllScope(this::assertWasDenied);
clientPage.requestResourceProtectedAnyScope(response -> {
assertThat(response.get("status"), anyOf(is(equalTo(404L)), is(equalTo(0L)))); // PhantomJS returns 0 and chrome 404
});
}
}

View file

@ -1,120 +0,0 @@
package org.keycloak.testsuite.authz.adapter.example;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.pages.LogoutConfirmPage;
import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import static org.hamcrest.CoreMatchers.containsString;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author mhajas
*/
public abstract class AbstractPhotozJavascriptExecutorTest extends AbstractExampleAdapterTest {
@FunctionalInterface
interface QuadFunction<T, U, V, W> {
void apply(T a, U b, V c, W d);
}
protected static final String REALM_NAME = "photoz";
@Page
@JavascriptBrowser
protected OIDCLogin jsDriverTestRealmLoginPage;
@Page
@JavascriptBrowser
protected LogoutConfirmPage logoutConfirmPage;
@Page
@JavascriptBrowser
private OAuthGrant oAuthGrantPage;
@Drone
@JavascriptBrowser
protected WebDriver jsDriver;
protected UserRepresentation aliceUser = UserBuilder.create().username("alice").password("alice").build();
protected UserRepresentation adminUser = UserBuilder.create().username("admin").password("admin").build();
protected UserRepresentation jdoeUser = UserBuilder.create().username("jdoe").password("jdoe").build();
@BeforeClass
public static void checkIfTLSIsTurnedOn() {
Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED);
}
@Before
public void setDefaultValues() {
jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
}
protected <T> JavascriptStateValidator buildFunction(QuadFunction<T, WebDriver, Object, WebElement> f, T x) {
return (y,z,w) -> f.apply(x, y, z, w);
}
public void assertOutputContains(String value, WebDriver driver1, Object output, WebElement events) {
if (output instanceof WebElement) {
waitUntilElement((WebElement) output).text().contains(value);
} else {
Assert.assertThat((String) output, containsString(value));
}
}
protected JSObjectBuilder defaultArguments() {
return JSObjectBuilder.create().defaultSettings();
}
protected void assertSuccessfullyLoggedIn(WebDriver driver1, Object output, WebElement events) {
buildFunction(this::assertOutputContains, "Init Success (Authenticated)").validate(driver1, output, events);
}
protected void assertInitNotAuth(WebDriver driver1, Object output, WebElement events) {
buildFunction(this::assertOutputContains, "Init Success (Not Authenticated)").validate(driver1, output, events);
}
protected void assertOnLoginPage(WebDriver driver1, Object output, WebElement events) {
waitUntilElement(By.tagName("body")).is().present();
try {
assertCurrentUrlStartsWith(jsDriverTestRealmLoginPage, driver1);
} catch (AssertionError e) {
System.out.println("Test");
throw e;
}
}
protected JavascriptStateValidator all(JavascriptStateValidator[] toValidate) {
return ((driver1, output, events) -> {
for (JavascriptStateValidator val : toValidate) {
val.validate(driver1, output, events);
}
});
}
protected ResponseValidator all(ResponseValidator[] toValidate) {
return ((response) -> {
for (ResponseValidator val : toValidate) {
val.validate(response);
}
});
}
}

View file

@ -1,180 +0,0 @@
/*
* 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.authz.adapter.example;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
import jakarta.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.util.javascript.ResponseValidator;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class LifespanAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID,
webArchive -> webArchive.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-cache-lifespan-authz-service.json"), "keycloak.json"));
}
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadRealm(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-realm.json"));
realm.setAccessTokenLifespan(70); // must increase lifespan of access token in order to use bigger offset in test cases
testRealms.add(realm);
}
@Test
public void testPathConfigInvalidation() throws Exception {
loginToClientPage(aliceUser);
assertSuccess();
ResourceRepresentation resource = getAuthorizationResource().resources().findByName("Profile Resource").get(0);
AuthorizationResource authorizationResource = getAuthorizationResource();
authorizationResource.resources().resource(resource.getId()).remove();
assertThat(getAuthorizationResource().resources().findByName("Profile Resource").isEmpty(), Matchers.is(true));
loginToClientPage(aliceUser);
// should throw an error because the resource was removed and cache entry did not expire yet
assertFailure();
setTimeOffsetOfAdapter(40);
loginToClientPage(aliceUser);
assertSuccess();
setTimeOffsetOfAdapter(0);
try (Response response = authorizationResource.resources().create(resource)) {
resource = response.readEntity(ResourceRepresentation.class);
}
loginToClientPage(aliceUser);
assertSuccess();
RealmResource realm = this.realmsResouce().realm(REALM_NAME);
UserRepresentation userRepresentation = realm.users().search(aliceUser.getUsername()).get(0);
UserResource userResource = realm.users().get(userRepresentation.getId());
userRepresentation.setEmail("alice@anotherdomain.org");
userResource.update(userRepresentation);
loginToClientPage(aliceUser);
assertTicket();
try {
PolicyRepresentation resourceInstancePermission = new PolicyRepresentation();
resourceInstancePermission.setName("View User Permission");
resourceInstancePermission.setType("resource");
Map<String, String> config = new HashMap<>();
config.put("resources", JsonSerialization.writeValueAsString(Collections.singletonList(resource.getId())));
config.put("applyPolicies", JsonSerialization.writeValueAsString(Collections.singletonList("Only From @keycloak.org or Admin")));
resourceInstancePermission.setConfig(config);
authorizationResource.policies().create(resourceInstancePermission);
} catch (IOException e) {
throw new RuntimeException("Error creating policy.", e);
}
loginToClientPage(aliceUser);
// should throw an error because the resource was removed and cache entry did not expire yet
assertFailure();
userRepresentation.setEmail("alice@keycloak.org");
userResource.update(userRepresentation);
loginToClientPage(aliceUser);
assertSuccess();
}
private void assertSuccess() {
assertState(true);
}
private void assertFailure() {
assertState(false);
}
private void assertState(boolean state) {
clientPage.viewProfile((ResponseValidator) response -> {
Object res = response.get("res");
assertThat(res, Matchers.notNullValue());
Matcher<String> matcher = Matchers.containsString("userName");
assertThat(res.toString(), state ? matcher : Matchers.not(matcher));
});
}
private void assertTicket() {
clientPage.viewProfile((ResponseValidator) response -> {
Object headers = response.get("responseHeaders");
assertThat(headers, Matchers.notNullValue());
List<String> headersList = Arrays.asList(headers.toString().split("\r\n"));
String wwwAuthenticate = headersList.stream()
.filter(s -> s.toLowerCase().startsWith("www-authenticate:"))
.findFirst()
.orElse(null);
assertThat(wwwAuthenticate, Matchers.notNullValue());
assertThat(wwwAuthenticate, Matchers.containsString("UMA"));
});
}
public void setTimeOffsetOfAdapter(int offset) {
this.driver.navigate().to(clientPage.getInjectedUrl() + "timeOffset.jsp?offset=" + offset);
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright 2019 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.authz.adapter.example;
import java.io.File;
import java.io.IOException;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class PhotozAccountResourcesAdapterTest extends AbstractPhotozAccountResourcesAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID,
webArchive -> webArchive.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json"));
}
}

View file

@ -1,48 +0,0 @@
/*
* 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.authz.adapter.example;
import java.io.File;
import java.io.IOException;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class PhotozExampleLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID,
webArchive -> webArchive.addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/photoz/keycloak-lazy-load-path-authz-service.json"), "keycloak.json"));
}
}

View file

@ -1,47 +0,0 @@
/*
* 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.authz.adapter.example;
import java.io.IOException;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.testsuite.adapter.page.PhotozClientAuthzTestApp;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
public class PhotozExampleNoLazyLoadPathsAdapterTest extends AbstractPhotozExampleAdapterTest {
@Deployment(name = PhotozClientAuthzTestApp.DEPLOYMENT_NAME)
public static WebArchive deploymentClient() throws IOException {
return exampleDeployment(PhotozClientAuthzTestApp.DEPLOYMENT_NAME);
}
@Deployment(name = RESOURCE_SERVER_ID, managed = false, testable = false)
public static WebArchive deploymentResourceServer() throws IOException {
return exampleDeployment(RESOURCE_SERVER_ID);
}
}

View file

@ -271,18 +271,6 @@
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-html5-client</artifactId>
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-restful-api</artifactId>
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>servlet-authz-app</artifactId>

View file

@ -348,18 +348,6 @@
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-html5-client</artifactId>
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>photoz-restful-api</artifactId>
<version>${project.version}</version>
<type>war</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>servlet-authz-app</artifactId>