keycloak-uncached

Merge pull request #4198 from pedroigor/KEYCLOAK-4992 RFEs

6/5/2017 4:03:50 PM

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
new file mode 100644
index 0000000..ca45017
--- /dev/null
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 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.authorization.client.representation;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class AuthorizationRequestMetadata {
+
+    public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
+
+    @JsonProperty(INCLUDE_RESOURCE_NAME)
+    private boolean includeResourceName = true;
+
+    private int limit;
+
+    public boolean isIncludeResourceName() {
+        return includeResourceName;
+    }
+
+    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/representation/EntitlementRequest.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
index daec233..b3efa85 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
@@ -4,31 +4,81 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
+ * <p>An {@code {@link EntitlementRequest} represents a request sent to the server containing the permissions being requested.
+ *
+ * <p>Along with an entitlement request additional {@link AuthorizationRequestMetadata} information can be passed in order to define what clients expect from
+ * the server when evaluating the requested permissions and when returning with a response.
+ *
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class EntitlementRequest {
 
     private String rpt;
+    private AuthorizationRequestMetadata metadata;
 
     private List<PermissionRequest> permissions = new ArrayList<>();
 
+    /**
+     * Returns the permissions being requested.
+     *
+     * @return the permissions being requested (not {@code null})
+     */
     public List<PermissionRequest> getPermissions() {
         return permissions;
     }
 
+    /**
+     * Set the permissions being requested
+     *
+     * @param permissions the permissions being requests (not {@code null})
+     */
+    public void setPermissions(List<PermissionRequest> permissions) {
+        this.permissions = permissions;
+    }
+
+    /**
+     * Adds the given {@link PermissionRequest} to the list of requested permissions.
+     *
+     * @param request the permission to request (not {@code null})
+     */
+    public void addPermission(PermissionRequest request) {
+        getPermissions().add(request);
+    }
+
+    /**
+     * Returns a {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+     *
+     * @return a previously issued RPT (may be {@code null})
+     */
     public String getRpt() {
         return rpt;
     }
 
+    /**
+     * A {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+     *
+     * @param rpt a previously issued RPT. If {@code null}, only the requested permissions are evaluated
+     */
     public void setRpt(String rpt) {
         this.rpt = rpt;
     }
 
-    public void setPermissions(List<PermissionRequest> permissions) {
-        this.permissions = permissions;
+    /**
+     * Return the {@link Metadata} associated with this request.
+     *
+     * @return
+     */
+    public AuthorizationRequestMetadata getMetadata() {
+        return metadata;
     }
 
-    public void addPermission(PermissionRequest request) {
-        getPermissions().add(request);
+    /**
+     * The {@link Metadata} associated with this request. The metadata defines specific information that should be considered
+     * by the server when evaluating and returning permissions.
+     *
+     * @param metadata the {@link Metadata} associated with this request (may be {@code null})
+     */
+    public void setMetadata(AuthorizationRequestMetadata metadata) {
+        this.metadata = metadata;
     }
 }
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
index 39518fc..38d5471 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
@@ -34,6 +34,25 @@ public class PermissionRequest {
 
     private Set<String> scopes;
 
+    public PermissionRequest() {
+
+    }
+
+    public PermissionRequest(String resourceSetId, String resourceSetName, Set<String> scopes) {
+        this.resourceSetId = resourceSetId;
+        this.resourceSetName = resourceSetName;
+        this.scopes = scopes;
+    }
+
+    public PermissionRequest(String resourceSetName) {
+        this.resourceSetName = resourceSetName;
+    }
+
+    public PermissionRequest(String resourceSetName, Set<String> scopes) {
+        this.resourceSetName = resourceSetName;
+        this.scopes = scopes;
+    }
+
     public String getResourceSetId() {
         return this.resourceSetId;
     }
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 55c0abd..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
@@ -1,9 +1,11 @@
 package org.keycloak.authorization.client.resource;
 
 import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.representation.AuthorizationRequestMetadata;
 import org.keycloak.authorization.client.representation.EntitlementRequest;
 import org.keycloak.authorization.client.representation.EntitlementResponse;
 import org.keycloak.authorization.client.util.Http;
+import org.keycloak.authorization.client.util.HttpMethod;
 import org.keycloak.authorization.client.util.HttpResponseException;
 import org.keycloak.util.JsonSerialization;
 
@@ -23,9 +25,8 @@ public class EntitlementResource {
     public EntitlementResponse getAll(String resourceServerId) {
         try {
             return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
-                    .authorizationBearer(this.eat)
-                    .response()
-                        .json(EntitlementResponse.class).execute();
+                    .authorizationBearer(eat)
+                    .response().json(EntitlementResponse.class).execute();
         } catch (HttpResponseException e) {
             if (403 == e.getStatusCode()) {
                 throw new AuthorizationDeniedException(e);
@@ -39,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/admin/representation/PolicyEvaluationResponseBuilder.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
index 4b59450..81d556e 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
@@ -52,7 +52,7 @@ public class PolicyEvaluationResponseBuilder {
         AccessToken accessToken = identity.getAccessToken();
         AccessToken.Authorization authorizationData = new AccessToken.Authorization();
 
-        authorizationData.setPermissions(Permissions.permits(results, authorization, resourceServer.getId()));
+        authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer));
         accessToken.setAuthorization(authorizationData);
 
         response.setRpt(accessToken);
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index e4a2634..d407613 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -108,8 +108,15 @@ public class AuthorizationTokenService {
 
         try {
             PermissionTicket ticket = verifyPermissionTicket(authorizationRequest);
+            ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId());
+
+            if (resourceServer == null) {
+                throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
+            }
+
             List<Result> result = authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate();
-            List<Permission> entitlements = Permissions.permits(result, authorization, ticket.getResourceServerId());
+
+            List<Permission> entitlements = Permissions.permits(result, authorizationRequest.getMetadata(), authorization, resourceServer);
 
             if (!entitlements.isEmpty()) {
                 AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
index d4f0f24..2faf12f 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
@@ -23,6 +23,7 @@ package org.keycloak.authorization.authorization.representation;
  */
 public class AuthorizationRequest {
 
+    private AuthorizationRequestMetadata metadata;
     private String ticket;
     private String rpt;
 
@@ -31,10 +32,6 @@ public class AuthorizationRequest {
         this.rpt = rpt;
     }
 
-    public AuthorizationRequest(String ticket) {
-        this(ticket, null);
-    }
-
     public AuthorizationRequest() {
         this(null, null);
     }
@@ -46,4 +43,8 @@ public class AuthorizationRequest {
     public String getRpt() {
         return this.rpt;
     }
+
+    public AuthorizationRequestMetadata getMetadata() {
+        return metadata;
+    }
 }
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
new file mode 100644
index 0000000..faa90ce
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 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.authorization.authorization.representation;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.sun.org.apache.xpath.internal.operations.Bool;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class AuthorizationRequestMetadata {
+
+    public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
+
+    @JsonProperty(INCLUDE_RESOURCE_NAME)
+    private boolean includeResourceName = true;
+
+    private int limit;
+
+    public boolean isIncludeResourceName() {
+        return includeResourceName;
+    }
+
+    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 98f38af..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;
@@ -36,6 +37,7 @@ 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;
@@ -45,6 +47,7 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
 import org.keycloak.authorization.common.KeycloakEvaluationContext;
 import org.keycloak.authorization.common.KeycloakIdentity;
 import org.keycloak.authorization.entitlement.representation.EntitlementRequest;
@@ -54,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;
@@ -121,7 +125,7 @@ public class EntitlementService {
             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
         }
 
-        return evaluate(Permissions.all(resourceServer, identity, authorization), identity, resourceServer);
+        return evaluate(null, Permissions.all(resourceServer, identity, authorization), identity, resourceServer);
     }
 
     @Path("{resource_server_id}")
@@ -154,13 +158,13 @@ public class EntitlementService {
             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
         }
 
-        return evaluate(createPermissions(entitlementRequest, resourceServer, authorization), identity, resourceServer);
+        return evaluate(entitlementRequest.getMetadata(), createPermissions(entitlementRequest, resourceServer, authorization), identity, resourceServer);
     }
 
-    private Response evaluate(List<ResourcePermission> permissions, KeycloakIdentity identity, ResourceServer resourceServer) {
+    private Response evaluate(AuthorizationRequestMetadata metadata, List<ResourcePermission> permissions, KeycloakIdentity identity, ResourceServer resourceServer) {
         try {
             List<Result> result = authorization.evaluators().from(permissions, new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate();
-            List<Permission> entitlements = Permissions.permits(result, authorization, resourceServer.getId());
+            List<Permission> entitlements = Permissions.permits(result, metadata, authorization, resourceServer);
 
             if (!entitlements.isEmpty()) {
                 return Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
@@ -192,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) {
@@ -208,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());
@@ -228,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);
             }
@@ -258,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) {
@@ -267,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();
@@ -275,7 +299,7 @@ public class EntitlementService {
                                     scopes.addAll(scopePermission);
                                 }
                             }
-                        });
+                        }
                     }
                 }
             }
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java b/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
index 444645a..f5a5745 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
@@ -1,24 +1,78 @@
 package org.keycloak.authorization.entitlement.representation;
 
-import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
-
 import java.util.ArrayList;
 import java.util.List;
 
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
+import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
+
 /**
+ * <p>An {@code {@link EntitlementRequest} represents a request sent to the server containing the permissions being requested.
+ *
+ * <p>Along with an entitlement request additional {@link AuthorizationRequestMetadata} information can be passed in order to define what clients expect from
+ * the server when evaluating the requested permissions and when returning with a response.
+ *
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class EntitlementRequest {
 
     private String rpt;
+    private AuthorizationRequestMetadata metadata;
 
     private List<PermissionRequest> permissions = new ArrayList<>();
 
+    /**
+     * Returns the permissions being requested.
+     *
+     * @return the permissions being requested (not {@code null})
+     */
     public List<PermissionRequest> getPermissions() {
         return permissions;
     }
 
+    /**
+     * Set the permissions being requested
+     *
+     * @param permissions the permissions being requests (not {@code null})
+     */
+    public void setPermissions(List<PermissionRequest> permissions) {
+        this.permissions = permissions;
+    }
+
+    /**
+     * Returns a {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+     *
+     * @return a previously issued RPT (may be {@code null})
+     */
     public String getRpt() {
         return rpt;
     }
+
+    /**
+     * A {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+     *
+     * @param rpt a previously issued RPT. If {@code null}, only the requested permissions are evaluated
+     */
+    public void setRpt(String rpt) {
+        this.rpt = rpt;
+    }
+
+    /**
+     * Return the {@link Metadata} associated with this request.
+     *
+     * @return
+     */
+    public AuthorizationRequestMetadata getMetadata() {
+        return metadata;
+    }
+
+    /**
+     * The {@link Metadata} associated with this request. The metadata defines specific information that should be considered
+     * by the server when evaluating and returning permissions.
+     *
+     * @param metadata the {@link Metadata} associated with this request (may be {@code null})
+     */
+    public void setMetadata(AuthorizationRequestMetadata metadata) {
+        this.metadata = 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 a805fbc..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;
@@ -29,6 +30,7 @@ import java.util.stream.Collectors;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision.Effect;
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
 import org.keycloak.authorization.identity.Identity;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.Resource;
@@ -134,8 +136,8 @@ public final class Permissions {
         return permissions;
     }
 
-    public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServerId) {
-        Map<String, Permission> permissions = new HashMap<>();
+    public static List<Permission> permits(List<Result> evaluation, AuthorizationRequestMetadata metadata, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
+        Map<String, Permission> permissions = new LinkedHashMap<>();
 
         for (Result result : evaluation) {
             Set<Scope> deniedScopes = new HashSet<>();
@@ -188,14 +190,14 @@ public final class Permissions {
 
                 if (deniedCount == 0) {
                     result.setStatus(Effect.PERMIT);
-                    grantPermission(authorizationProvider, permissions, permission, resourceServerId);
+                    grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
                 } else {
                     // if a full deny or resource denied or the requested scopes were denied
                     if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
                         result.setStatus(Effect.DENY);
                     } else {
                         result.setStatus(Effect.PERMIT);
-                        grantPermission(authorizationProvider, permissions, permission, resourceServerId);
+                        grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
                     }
                 }
             }
@@ -212,7 +214,7 @@ public final class Permissions {
         return "scope".equals(policy.getType());
     }
 
-    private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, String resourceServer) {
+    private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, ResourceServer resourceServer, AuthorizationRequestMetadata metadata) {
         List<Resource> resources = new ArrayList<>();
         Resource resource = permission.getResource();
         Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
@@ -224,14 +226,14 @@ public final class Permissions {
 
             if (!permissionScopes.isEmpty()) {
                 ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
-                resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer));
+                resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()));
             }
         }
 
         if (!resources.isEmpty()) {
             for (Resource allowedResource : resources) {
                 String resourceId = allowedResource.getId();
-                String resourceName = allowedResource.getName();
+                String resourceName = metadata == null || metadata.isIncludeResourceName() ? allowedResource.getName() : null;
                 Permission evalPermission = permissions.get(allowedResource.getId());
 
                 if (evalPermission == null) {
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
new file mode 100644
index 0000000..0f2ac61
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2017 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.function.Supplier;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequestMetadata;
+import org.keycloak.authorization.client.representation.EntitlementRequest;
+import org.keycloak.authorization.client.representation.EntitlementResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Permission;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class EntitlementAPITest extends AbstractKeycloakTest {
+
+    private AuthzClient authzClient;
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        testRealms.add(RealmBuilder.create().name("authz-test")
+                .roles(RolesBuilder.create().realmRole(RoleBuilder.create().name("uma_authorization").build()))
+                .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
+                .user(UserBuilder.create().username("kolo").password("password"))
+                .client(ClientBuilder.create().clientId("resource-server-test")
+                    .secret("secret")
+                    .authorizationServicesEnabled(true)
+                    .redirectUris("http://localhost/resource-server-test")
+                    .defaultRoles("uma_protection")
+                    .directAccessGrants())
+                .build());
+    }
+
+    @Before
+    public void configureAuthorization() throws Exception {
+        ClientResource client = getClient(getRealm());
+        AuthorizationResource authorization = client.authorization();
+
+        JSPolicyRepresentation policy = new JSPolicyRepresentation();
+
+        policy.setName("Default Policy");
+        policy.setCode("$evaluation.grant();");
+
+        authorization.policies().js().create(policy).close();
+
+        for (int i = 1; i <= 20; i++) {
+            ResourceRepresentation resource = new ResourceRepresentation("Resource " + i);
+
+            authorization.resources().create(resource).close();
+
+            ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+            permission.setName(resource.getName() + " Permission");
+            permission.addResource(resource.getName());
+            permission.addPolicy(policy.getName());
+
+            authorization.permissions().resource().create(permission).close();
+        }
+    }
+
+    @Test
+    public void testRptRequestWithoutResourceName() {
+        AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+        metadata.setIncludeResourceName(false);
+
+        assertResponse(metadata, () -> {
+            EntitlementRequest request = new EntitlementRequest();
+
+            request.setMetadata(metadata);
+            request.addPermission(new PermissionRequest("Resource 1"));
+
+            return getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+        });
+    }
+
+    @Test
+    public void testRptRequestWithResourceName() {
+        AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+        metadata.setIncludeResourceName(true);
+
+        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 13"));
+
+        assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
+
+        request.setMetadata(null);
+
+        assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
+    }
+
+    @Test
+    public void testPermissionLimit() {
+        EntitlementRequest request = new EntitlementRequest();
+
+        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());
+        }
+
+        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();
+
+        assertNotNull(permissions);
+        assertFalse(permissions.isEmpty());
+
+        for (Permission permission : permissions) {
+            if (metadata.isIncludeResourceName()) {
+                assertNotNull(permission.getResourceSetName());
+            } else {
+                assertNull(permission.getResourceSetName());
+            }
+        }
+    }
+
+    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");
+    }
+
+    private ClientResource getClient(RealmResource realm) {
+        ClientsResource clients = realm.clients();
+        return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
+    }
+
+    private AuthzClient getAuthzClient() {
+        if (authzClient == null) {
+            try {
+                authzClient = AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
+            } catch (IOException cause) {
+                throw new RuntimeException("Failed to create authz client", cause);
+            }
+        }
+
+        return authzClient;
+    }
+}