keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 59(+41 -18)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java 15(+8 -7)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java 15(+8 -7)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java 12(+7 -5)
core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java 16(+16 -0)
examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js 12(+11 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java 2(+1 -1)
testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json 22(+22 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json 43(+43 -0)
testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/scopes.jsp 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java 12(+12 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java 14(+14 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index f3127be..15aa1e1 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -33,6 +33,7 @@ import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
@@ -96,9 +97,9 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true);
}
- Set<String> requiredScopes = getRequiredScopes(pathConfig, request);
+ MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
- if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
+ if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
@@ -108,7 +109,7 @@ public abstract class AbstractPolicyEnforcer {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
- if (!challenge(pathConfig, requiredScopes, httpFacade)) {
+ if (!challenge(pathConfig, methodConfig, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
}
@@ -118,9 +119,9 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(false);
}
- protected abstract boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade);
+ protected abstract boolean challenge(PathConfig pathConfig, MethodConfig methodConfig, OIDCHttpFacade facade);
- protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
+ protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest();
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
@@ -146,7 +147,7 @@ public abstract class AbstractPolicyEnforcer {
continue;
}
- if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
+ if (hasResourceScopePermission(methodConfig, permission)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
this.paths.remove(actualPathConfig);
@@ -155,7 +156,7 @@ public abstract class AbstractPolicyEnforcer {
}
}
} else {
- if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
+ if (hasResourceScopePermission(methodConfig, permission)) {
hasPermission = true;
return true;
}
@@ -166,7 +167,7 @@ public abstract class AbstractPolicyEnforcer {
return true;
}
- LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
+ LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, permissions);
return false;
}
@@ -186,9 +187,29 @@ public abstract class AbstractPolicyEnforcer {
return false;
}
- private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
+ private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
+ List<String> requiredScopes = methodConfig.getScopes();
Set<String> allowedScopes = permission.getScopes();
- return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
+
+ if (allowedScopes.isEmpty()) {
+ return true;
+ }
+
+ PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
+
+ if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
+ return allowedScopes.containsAll(requiredScopes);
+ }
+
+ if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
+ for (String requiredScope : requiredScopes) {
+ if (allowedScopes.contains(requiredScope)) {
+ return true;
+ }
+ }
+ }
+
+ return requiredScopes.isEmpty();
}
protected AuthzClient getAuthzClient() {
@@ -236,20 +257,22 @@ public abstract class AbstractPolicyEnforcer {
return request.getRelativePath();
}
- private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
- Set<String> requiredScopes = new HashSet<>();
-
- requiredScopes.addAll(pathConfig.getScopes());
-
+ private MethodConfig getRequiredScopes(PathConfig pathConfig, Request request) {
String method = request.getMethod();
- for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
+ for (MethodConfig methodConfig : pathConfig.getMethods()) {
if (methodConfig.getMethod().equals(method)) {
- requiredScopes.addAll(methodConfig.getScopes());
+ return methodConfig;
}
}
- return requiredScopes;
+ MethodConfig methodConfig = new MethodConfig();
+
+ methodConfig.setMethod(request.getMethod());
+ methodConfig.setScopes(pathConfig.getScopes());
+ methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
+
+ return methodConfig;
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java
index f2555d4..172c745 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java
@@ -17,7 +17,7 @@
*/
package org.keycloak.adapters.authorization;
-import java.util.Set;
+import java.util.HashSet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade;
@@ -26,6 +26,7 @@ import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.representation.PermissionRequest;
import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
@@ -40,9 +41,9 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
}
@Override
- protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
+ protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
- challengeUmaAuthentication(pathConfig, requiredScopes, facade);
+ challengeUmaAuthentication(pathConfig, methodConfig, facade);
} else {
challengeEntitlementAuthentication(facade);
}
@@ -61,10 +62,10 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
}
}
- private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
+ private void challengeUmaAuthentication(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
- String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
+ String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient);
String clientId = authzClient.getConfiguration().getResource();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401);
@@ -74,12 +75,12 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
}
}
- private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
+ private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
- permissionRequest.setScopes(requiredScopes);
+ permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
return permission.forResource(permissionRequest).getTicket();
}
}
\ No newline at end of file
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index 0dbddd4..65fdc1e 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -35,6 +35,7 @@ import org.keycloak.authorization.client.representation.EntitlementResponse;
import org.keycloak.authorization.client.representation.PermissionRequest;
import org.keycloak.authorization.client.representation.PermissionResponse;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
@@ -50,14 +51,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
}
@Override
- protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
+ protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
AccessToken original = accessToken;
- if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
+ if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
return true;
}
- accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
+ accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade);
if (accessToken == null) {
return false;
@@ -78,11 +79,11 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
original.setAuthorization(authorization);
- return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
+ return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade);
}
@Override
- protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
+ protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
handleAccessDenied(facade);
return true;
}
@@ -100,7 +101,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
}
}
- private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
+ private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
try {
String accessToken = httpFacade.getSecurityContext().getTokenString();
AuthzClient authzClient = getAuthzClient();
@@ -111,7 +112,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
- permissionRequest.setScopes(requiredScopes);
+ permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
index 496c311..390ea15 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
@@ -276,11 +276,13 @@ public final class KeycloakAdapterConfigService {
}
private void setJSONValues(ModelNode json, ModelNode values) {
- for (Property prop : new ArrayList<>(values.asPropertyList())) {
- String name = prop.getName();
- ModelNode value = prop.getValue();
- if (value.isDefined()) {
- json.get(name).set(value);
+ synchronized (values) {
+ for (Property prop : new ArrayList<>(values.asPropertyList())) {
+ String name = prop.getName();
+ ModelNode value = prop.getValue();
+ if (value.isDefined()) {
+ json.get(name).set(value);
+ }
}
}
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index a495cad..67c3c59 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -220,6 +220,9 @@ public class PolicyEnforcerConfig {
private String method;
private List<String> scopes = Collections.emptyList();
+ @JsonProperty("scopes-enforcement-mode")
+ private ScopeEnforcementMode scopesEnforcementMode = ScopeEnforcementMode.ALL;
+
public String getMethod() {
return method;
}
@@ -235,6 +238,14 @@ public class PolicyEnforcerConfig {
public void setScopes(List<String> scopes) {
this.scopes = scopes;
}
+
+ public void setScopesEnforcementMode(ScopeEnforcementMode scopesEnforcementMode) {
+ this.scopesEnforcementMode = scopesEnforcementMode;
+ }
+
+ public ScopeEnforcementMode getScopesEnforcementMode() {
+ return scopesEnforcementMode;
+ }
}
public enum EnforcementMode {
@@ -243,6 +254,11 @@ public class PolicyEnforcerConfig {
DISABLED
}
+ public enum ScopeEnforcementMode {
+ ALL,
+ ANY
+ }
+
public static class UmaProtocolConfig {
}
diff --git a/examples/authz/photoz/photoz-realm.json b/examples/authz/photoz/photoz-realm.json
index b0aeb5d..118b982 100644
--- a/examples/authz/photoz/photoz-realm.json
+++ b/examples/authz/photoz/photoz-realm.json
@@ -108,7 +108,7 @@
"redirectUris": [
"/photoz-html5-client/*"
],
- "webOrigins": ["*"]
+ "webOrigins": ["http://localhost:8080"]
},
{
"clientId": "photoz-restful-api",
@@ -119,7 +119,7 @@
"redirectUris": [
"/photoz-restful-api/*"
],
- "webOrigins" : ["*"]
+ "webOrigins" : ["http://localhost:8080"]
}
]
}
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 1fe6675..e84e24d 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -90,7 +90,7 @@ public class AlbumService {
@Path("{id}")
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
- List result = this.entityManager.createQuery("from Album where id = :id").setParameter("id", id).getResultList();
+ List result = this.entityManager.createQuery("from Album where id = :id").setParameter("id", Long.valueOf(id)).getResultList();
if (result.isEmpty()) {
return Response.status(Status.NOT_FOUND).build();
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 691e01a..ecdbf0b 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -34,7 +34,7 @@ module.config(function ($httpProvider, $routeProvider) {
controller: 'AdminAlbumCtrl',
}).when('/profile', {
templateUrl: 'partials/profile.html',
- controller: 'ProfileCtrl',
+ controller: 'ProfileCtrl'
});
});
@@ -50,6 +50,16 @@ module.controller('GlobalCtrl', function ($scope, $http, $route, $location, Albu
$route.reload();
});
}
+
+ $scope.requestPathWithAnyProtectedScope = function() {
+ $http.get(apiUrl + '/scope-any').success(function (data) {
+ });
+ }
+
+ $scope.requestPathWithAllProtectedScope = function() {
+ $http.get(apiUrl + '/scope-all').success(function (data) {
+ });
+ }
});
module.controller('TokenCtrl', function ($scope, Identity) {
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index e144d1b..788763b 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -3,7 +3,7 @@
<hr/>
<br/>
<div data-ng-show="!Identity.isAdmin()">
-<a href="#/album/create" id="create-album">Create Album</a> | <a href="#/profile">My Profile</a>
+<a href="#/album/create" id="create-album">Create Album</a> | <a href="#/profile">My Profile</a> | <a href="#" id="requestPathWithAnyProtectedScope" ng-click="requestPathWithAnyProtectedScope()">Any Scope Access</a> | <a href="#" id="requestPathWithAllProtectedScope" ng-click="requestPathWithAllProtectedScope()">All Scope Access</a>
<br/>
<br/>
<span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index bb21e50..7ec7e02 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
@@ -108,7 +108,7 @@
"redirectUris": [
"/photoz-html5-client/*"
],
- "webOrigins": ["*"]
+ "webOrigins": ["http://localhost:8280"]
},
{
"clientId": "photoz-restful-api",
@@ -118,7 +118,7 @@
"redirectUris": [
"/photoz-restful-api/*"
],
- "webOrigins" : ["*"],
+ "webOrigins" : ["http://localhost:8280"],
"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=="
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index a1230d8..7969492 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -99,7 +99,7 @@ public class AlbumService {
@Path("{id}")
@Produces("application/json")
public Response findById(@PathParam("id") String id) {
- List result = this.entityManager.createQuery("from Album where id = " + id).getResultList();
+ List result = this.entityManager.createQuery("from Album where id = " + Long.valueOf(id)).getResultList();
if (result.isEmpty()) {
return Response.status(Status.NOT_FOUND).build();
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 5b41d26..f3db78d 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -51,6 +51,28 @@
{
"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"
+ }
+ ]
}
]
}
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index ba44208..0b621f5 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
@@ -37,6 +37,18 @@
"name": "urn:photoz.com:scopes:album:admin:manage"
}
]
+ },
+ {
+ "name": "Scope Protected Resource",
+ "uri": "/scope-any",
+ "scopes": [
+ {
+ "name": "scope-a"
+ },
+ {
+ "name": "scope-b"
+ }
+ ]
}
],
"policies": [
@@ -166,6 +178,37 @@
"applyPolicies": "[\"Only Owner and Administrators Policy\"]",
"scopes": "[\"urn:photoz.com:scopes:album:delete\"]"
}
+ },
+ {
+ "name": "Deny Policy",
+ "type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "code": "// by default, grants any permission associated with this policy\n$evaluation.deny();"
+ }
+ },
+ {
+ "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": [
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
index 43ebde4..0a31003 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -46,6 +46,18 @@
"name": "urn:servlet-authz:page:main:actionForPremiumUser"
}
]
+ },
+ {
+ "name": "Resource A",
+ "uri": "/protected/scopes.jsp",
+ "scopes": [
+ {
+ "name": "read"
+ },
+ {
+ "name": "write"
+ }
+ ]
}
],
"policies": [
@@ -142,6 +154,37 @@
"scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]",
"applyPolicies": "[\"Only Premium User Policy\"]"
}
+ },
+ {
+ "name": "Deny Policy",
+ "type": "js",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "code": "// by default, grants any permission associated with this policy\n$evaluation.deny();"
+ }
+ },
+ {
+ "name": "Resource A Read Permission",
+ "type": "scope",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"Resource A\"]",
+ "scopes": "[\"read\"]",
+ "applyPolicies": "[\"Any User Policy\"]"
+ }
+ },
+ {
+ "name": "Resource A Write Permission",
+ "type": "scope",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"Resource A\"]",
+ "scopes": "[\"write\"]",
+ "applyPolicies": "[\"Deny Policy\"]"
+ }
}
]
}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/scopes.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/scopes.jsp
new file mode 100644
index 0000000..405921b
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/scopes.jsp
@@ -0,0 +1 @@
+Granted
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index 32cc58f..27df535 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -160,6 +160,18 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
pause(WAIT_AFTER_OPERATION);
}
+ public void requestResourceProtectedAnyScope() throws InterruptedException {
+ navigateTo();
+ this.driver.findElement(By.id("requestPathWithAnyProtectedScope")).click();
+ pause(WAIT_AFTER_OPERATION);
+ }
+
+ public void requestResourceProtectedAllScope() throws InterruptedException {
+ navigateTo();
+ this.driver.findElement(By.id("requestPathWithAllProtectedScope")).click();
+ pause(WAIT_AFTER_OPERATION);
+ }
+
public WebElement getOutput() {
return output;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 7223b2a..edd264c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -642,6 +642,20 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
}
+ @Test
+ public void testResourceProtectedWithAnyScope() throws Exception {
+ try {
+ this.deployer.deploy(RESOURCE_SERVER_ID);
+ loginToClientPage("alice", "alice");
+ this.clientPage.requestResourceProtectedAllScope();
+ assertTrue(this.clientPage.wasDenied());
+ this.clientPage.requestResourceProtectedAnyScope();
+ assertFalse(this.clientPage.wasDenied());
+ } finally {
+ this.deployer.undeploy(RESOURCE_SERVER_ID);
+ }
+ }
+
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);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
index a46f8f1..63852d0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
@@ -307,4 +307,14 @@ public abstract class AbstractServletAuthzFunctionalAdapterTest extends Abstract
assertFalse(wasDenied());
});
}
+
+ @Test
+ public void testAccessResourceWithAnyScope() throws Exception {
+ performTests(() -> {
+ login("jdoe", "jdoe");
+ driver.navigate().to(getResourceServerUrl() + "/protected/scopes.jsp");
+ WaitUtils.waitForPageToLoad();
+ assertTrue(hasText("Granted"));
+ });
+ }
}
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index dbe05cd..991dedc 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -316,7 +316,7 @@
<keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc>
<!-- used by PasswordPolicyTest.testBlacklistPasswordPolicyWithTestBlacklist, see KEYCLOAK-5244 -->
- <keycloak.password.blacklists.path>${project.build.directory}/test-classes/password-blacklists</keycloak.password.blacklists.path>
+ <keycloak.password.blacklists.path>${project.build.directory}/test-classes</keycloak.password.blacklists.path>
</systemPropertyVariables>
<properties>
<property>