keycloak-aplcache
Changes
authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java 12(+11 -1)
authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java 30(+4 -26)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java 3(+2 -1)
Details
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
index 175d081..ca45017 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
@@ -26,7 +26,9 @@ public class AuthorizationRequestMetadata {
public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
@JsonProperty(INCLUDE_RESOURCE_NAME)
- private boolean includeResourceName;
+ private boolean includeResourceName = true;
+
+ private int limit;
public boolean isIncludeResourceName() {
return includeResourceName;
@@ -35,4 +37,12 @@ public class AuthorizationRequestMetadata {
public void setIncludeResourceName(boolean includeResourceName) {
this.includeResourceName = includeResourceName;
}
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
index d0dc2cf..8c12abf 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
@@ -24,31 +24,9 @@ public class EntitlementResource {
public EntitlementResponse getAll(String resourceServerId) {
try {
- return getAll(resourceServerId, null);
- } catch (HttpResponseException e) {
- if (403 == e.getStatusCode()) {
- throw new AuthorizationDeniedException(e);
- }
- throw new RuntimeException("Failed to obtain entitlements.", e);
- } catch (Exception e) {
- throw new RuntimeException("Failed to obtain entitlements.", e);
- }
- }
-
- public EntitlementResponse getAll(String resourceServerId, AuthorizationRequestMetadata metadata) {
- try {
- HttpMethod<EntitlementResponse> method = this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
- .authorizationBearer(this.eat);
-
- if (metadata != null) {
- StringBuilder params = new StringBuilder();
-
- params.append(AuthorizationRequestMetadata.INCLUDE_RESOURCE_NAME).append("=").append(metadata.isIncludeResourceName());
-
- method.param("metadata", params.toString());
- }
-
- return method.response().json(EntitlementResponse.class).execute();
+ return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
+ .authorizationBearer(eat)
+ .response().json(EntitlementResponse.class).execute();
} catch (HttpResponseException e) {
if (403 == e.getStatusCode()) {
throw new AuthorizationDeniedException(e);
@@ -62,7 +40,7 @@ public class EntitlementResource {
public EntitlementResponse get(String resourceServerId, EntitlementRequest request) {
try {
return this.http.<EntitlementResponse>post("/authz/entitlement/" + resourceServerId)
- .authorizationBearer(this.eat)
+ .authorizationBearer(eat)
.json(JsonSerialization.writeValueAsBytes(request))
.response().json(EntitlementResponse.class).execute();
} catch (HttpResponseException e) {
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
index 02312ba..2ffc049 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
@@ -19,6 +19,7 @@
package org.keycloak.authorization.policy.evaluation;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -33,7 +34,7 @@ import org.keycloak.representations.idm.authorization.DecisionStrategy;
*/
public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
- private Map<ResourcePermission, Result> results = new HashMap();
+ private Map<ResourcePermission, Result> results = new LinkedHashMap<>();
@Override
public void onDecision(DefaultEvaluation evaluation) {
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
index 77aa1d5..faa90ce 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
@@ -29,17 +29,9 @@ public class AuthorizationRequestMetadata {
public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
@JsonProperty(INCLUDE_RESOURCE_NAME)
- private boolean includeResourceName;
+ private boolean includeResourceName = true;
- public AuthorizationRequestMetadata() {
- this(null);
- }
-
- public AuthorizationRequestMetadata(Map<String, String> claims) {
- if (claims != null) {
- includeResourceName = Boolean.valueOf(claims.getOrDefault(INCLUDE_RESOURCE_NAME, "true")).booleanValue();
- }
- }
+ private int limit;
public boolean isIncludeResourceName() {
return includeResourceName;
@@ -48,4 +40,12 @@ public class AuthorizationRequestMetadata {
public void setIncludeResourceName(boolean includeResourceName) {
this.includeResourceName = includeResourceName;
}
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index a37269d..54097bb 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -56,6 +57,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
@@ -102,7 +104,7 @@ public class EntitlementService {
@GET()
@Produces("application/json")
@Consumes("application/json")
- public Response getAll(@PathParam("resource_server_id") String resourceServerId, @QueryParam("metadata") String metadataParam) {
+ public Response getAll(@PathParam("resource_server_id") String resourceServerId) {
KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
if (resourceServerId == null) {
@@ -123,7 +125,7 @@ public class EntitlementService {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
}
- return evaluate(getMetadata(metadataParam), Permissions.all(resourceServer, identity, authorization), identity, resourceServer);
+ return evaluate(null, Permissions.all(resourceServer, identity, authorization), identity, resourceServer);
}
@Path("{resource_server_id}")
@@ -194,9 +196,15 @@ public class EntitlementService {
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
StoreFactory storeFactory = authorization.getStoreFactory();
- Map<String, Set<String>> permissionsToEvaluate = new HashMap<>();
+ Map<String, Set<String>> permissionsToEvaluate = new LinkedHashMap<>();
+ AuthorizationRequestMetadata metadata = entitlementRequest.getMetadata();
+ Integer limit = metadata != null && metadata.getLimit() > 0 ? metadata.getLimit() : null;
+
+ for (PermissionRequest requestedResource : entitlementRequest.getPermissions()) {
+ if (limit != null && limit <= 0) {
+ break;
+ }
- entitlementRequest.getPermissions().forEach(requestedResource -> {
Resource resource;
if (requestedResource.getResourceSetId() != null) {
@@ -210,14 +218,17 @@ public class EntitlementService {
}
Set<ScopeRepresentation> requestedScopes = requestedResource.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet());
- Set<String> collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet());
+ Set<String> scopeNames = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet());
if (resource != null) {
- permissionsToEvaluate.put(resource.getId(), collect);
+ permissionsToEvaluate.put(resource.getId(), scopeNames);
+ if (limit != null) {
+ limit--;
+ }
} else {
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
- List<Resource> resources = new ArrayList<Resource>();
+ List<Resource> resources = new ArrayList<>();
resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> {
Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId());
@@ -230,17 +241,21 @@ public class EntitlementService {
}).filter(s -> s != null).collect(Collectors.toList()), resourceServer.getId()));
for (Resource resource1 : resources) {
- permissionsToEvaluate.put(resource1.getId(), collect);
+ permissionsToEvaluate.put(resource1.getId(), scopeNames);
+ if (limit != null) {
+ limit--;
+ }
}
- permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect);
+ permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", scopeNames);
}
- });
+ }
String rpt = entitlementRequest.getRpt();
if (rpt != null && !"".equals(rpt)) {
KeycloakContext context = authorization.getKeycloakSession().getContext();
+
if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
}
@@ -260,7 +275,11 @@ public class EntitlementService {
List<Permission> permissions = authorizationData.getPermissions();
if (permissions != null) {
- permissions.forEach(permission -> {
+ for (Permission permission : permissions) {
+ if (limit != null && limit <= 0) {
+ break;
+ }
+
Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId(), resourceServer.getId());
if (resourcePermission != null) {
@@ -269,6 +288,9 @@ public class EntitlementService {
if (scopes == null) {
scopes = new HashSet<>();
permissionsToEvaluate.put(resourcePermission.getId(), scopes);
+ if (limit != null) {
+ limit--;
+ }
}
Set<String> scopePermission = permission.getScopes();
@@ -277,7 +299,7 @@ public class EntitlementService {
scopes.addAll(scopePermission);
}
}
- });
+ }
}
}
}
@@ -297,27 +319,4 @@ public class EntitlementService {
}
}).collect(Collectors.toList());
}
-
- private AuthorizationRequestMetadata getMetadata(@QueryParam("metadata") String metadataParam) {
- AuthorizationRequestMetadata metadata;
-
- if (metadataParam != null) {
- Map<String, String> claims = new HashMap<>();
-
- for (String claim : metadataParam.split(",")) {
- String[] values = claim.split("=");
-
- if (values.length < 2) {
- throw new ErrorResponseException("invalid_metadata", "Invalid metadata", Status.BAD_REQUEST);
- }
-
- claims.put(values[0], values[1]);
- }
-
- metadata = new AuthorizationRequestMetadata(claims);
- } else {
- metadata = null;
- }
- return metadata;
- }
}
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 2ee705e..325c056 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -21,6 +21,7 @@ package org.keycloak.authorization.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -136,7 +137,7 @@ public final class Permissions {
}
public static List<Permission> permits(List<Result> evaluation, AuthorizationRequestMetadata metadata, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
- Map<String, Permission> permissions = new HashMap<>();
+ Map<String, Permission> permissions = new LinkedHashMap<>();
for (Result result : evaluation) {
Set<Scope> deniedScopes = new HashSet<>();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
index b9f4226..0f2ac61 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.authz;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -80,27 +81,27 @@ public class EntitlementAPITest extends AbstractKeycloakTest {
public void configureAuthorization() throws Exception {
ClientResource client = getClient(getRealm());
AuthorizationResource authorization = client.authorization();
- ResourceRepresentation resource = new ResourceRepresentation("Resource A");
-
- Response response = authorization.resources().create(resource);
- response.close();
JSPolicyRepresentation policy = new JSPolicyRepresentation();
policy.setName("Default Policy");
policy.setCode("$evaluation.grant();");
- response = authorization.policies().js().create(policy);
- response.close();
+ authorization.policies().js().create(policy).close();
+
+ for (int i = 1; i <= 20; i++) {
+ ResourceRepresentation resource = new ResourceRepresentation("Resource " + i);
- ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+ authorization.resources().create(resource).close();
- permission.setName(resource.getName() + " Permission");
- permission.addResource(resource.getName());
- permission.addPolicy(policy.getName());
+ ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
- response = authorization.permissions().resource().create(permission);
- response.close();
+ permission.setName(resource.getName() + " Permission");
+ permission.addResource(resource.getName());
+ permission.addPolicy(policy.getName());
+
+ authorization.permissions().resource().create(permission).close();
+ }
}
@Test
@@ -109,12 +110,11 @@ public class EntitlementAPITest extends AbstractKeycloakTest {
metadata.setIncludeResourceName(false);
- assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).getAll("resource-server-test", metadata));
assertResponse(metadata, () -> {
EntitlementRequest request = new EntitlementRequest();
request.setMetadata(metadata);
- request.addPermission(new PermissionRequest("Resource A"));
+ request.addPermission(new PermissionRequest("Resource 1"));
return getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
});
@@ -126,13 +126,12 @@ public class EntitlementAPITest extends AbstractKeycloakTest {
metadata.setIncludeResourceName(true);
- assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).getAll("resource-server-test", metadata));
assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).getAll("resource-server-test"));
EntitlementRequest request = new EntitlementRequest();
request.setMetadata(metadata);
- request.addPermission(new PermissionRequest("Resource A"));
+ request.addPermission(new PermissionRequest("Resource 13"));
assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
@@ -141,17 +140,102 @@ public class EntitlementAPITest extends AbstractKeycloakTest {
assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
}
- private void assertResponse(AuthorizationRequestMetadata metadata, Supplier<EntitlementResponse> responseSupplier) {
- EntitlementResponse response = responseSupplier.get();
- AccessToken accessToken;
+ @Test
+ public void testPermissionLimit() {
+ EntitlementRequest request = new EntitlementRequest();
- try {
- accessToken = new JWSInput(response.getRpt()).readJsonContent(AccessToken.class);
- } catch (JWSInputException cause) {
- throw new RuntimeException("Failed to deserialize RPT", cause);
+ for (int i = 1; i <= 10; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+ metadata.setLimit(10);
+
+ request.setMetadata(metadata);
+
+ EntitlementResponse response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ AccessToken rpt = toAccessToken(response);
+
+ List<Permission> permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+
+ for (int i = 0; i < 10; i++) {
+ assertEquals("Resource " + (i + 1), permissions.get(i).getResourceSetName());
}
- AccessToken.Authorization authorization = accessToken.getAuthorization();
+ request = new EntitlementRequest();
+
+ for (int i = 11; i <= 15; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+
+ for (int i = 0; i < 10; i++) {
+ if (i < 5) {
+ assertEquals("Resource " + (i + 11), permissions.get(i).getResourceSetName());
+ } else {
+ assertEquals("Resource " + (i - 4), permissions.get(i).getResourceSetName());
+ }
+ }
+
+ request = new EntitlementRequest();
+
+ for (int i = 16; i <= 18; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+ assertEquals("Resource 16", permissions.get(0).getResourceSetName());
+ assertEquals("Resource 17", permissions.get(1).getResourceSetName());
+ assertEquals("Resource 18", permissions.get(2).getResourceSetName());
+ assertEquals("Resource 11", permissions.get(3).getResourceSetName());
+ assertEquals("Resource 12", permissions.get(4).getResourceSetName());
+ assertEquals("Resource 13", permissions.get(5).getResourceSetName());
+ assertEquals("Resource 14", permissions.get(6).getResourceSetName());
+ assertEquals("Resource 15", permissions.get(7).getResourceSetName());
+ assertEquals("Resource 1", permissions.get(8).getResourceSetName());
+ assertEquals("Resource 2", permissions.get(9).getResourceSetName());
+
+ request = new EntitlementRequest();
+
+ metadata.setLimit(5);
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(5, permissions.size());
+ assertEquals("Resource 16", permissions.get(0).getResourceSetName());
+ assertEquals("Resource 17", permissions.get(1).getResourceSetName());
+ assertEquals("Resource 18", permissions.get(2).getResourceSetName());
+ assertEquals("Resource 11", permissions.get(3).getResourceSetName());
+ assertEquals("Resource 12", permissions.get(4).getResourceSetName());
+ }
+
+ private void assertResponse(AuthorizationRequestMetadata metadata, Supplier<EntitlementResponse> responseSupplier) {
+ AccessToken.Authorization authorization = toAccessToken(responseSupplier.get()).getAuthorization();
List<Permission> permissions = authorization.getPermissions();
@@ -167,6 +251,17 @@ public class EntitlementAPITest extends AbstractKeycloakTest {
}
}
+ private AccessToken toAccessToken(EntitlementResponse response) {
+ AccessToken accessToken;
+
+ try {
+ accessToken = new JWSInput(response.getRpt()).readJsonContent(AccessToken.class);
+ } catch (JWSInputException cause) {
+ throw new RuntimeException("Failed to deserialize RPT", cause);
+ }
+ return accessToken;
+ }
+
private RealmResource getRealm() throws Exception {
return adminClient.realm("authz-test");
}