Merge pull request #4198 from pedroigor/KEYCLOAK-4992
RFEs based on Openshift.io authz requirements
This commit is contained in:
commit
40c4140021
13 changed files with 585 additions and 42 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,31 +4,81 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
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>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public class EntitlementRequest {
|
public class EntitlementRequest {
|
||||||
|
|
||||||
private String rpt;
|
private String rpt;
|
||||||
|
private AuthorizationRequestMetadata metadata;
|
||||||
|
|
||||||
private List<PermissionRequest> permissions = new ArrayList<>();
|
private List<PermissionRequest> permissions = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the permissions being requested.
|
||||||
|
*
|
||||||
|
* @return the permissions being requested (not {@code null})
|
||||||
|
*/
|
||||||
public List<PermissionRequest> getPermissions() {
|
public List<PermissionRequest> getPermissions() {
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRpt() {
|
/**
|
||||||
return rpt;
|
* Set the permissions being requested
|
||||||
}
|
*
|
||||||
|
* @param permissions the permissions being requests (not {@code null})
|
||||||
public void setRpt(String rpt) {
|
*/
|
||||||
this.rpt = rpt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPermissions(List<PermissionRequest> permissions) {
|
public void setPermissions(List<PermissionRequest> permissions) {
|
||||||
this.permissions = 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) {
|
public void addPermission(PermissionRequest request) {
|
||||||
getPermissions().add(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,25 @@ public class PermissionRequest {
|
||||||
|
|
||||||
private Set<String> scopes;
|
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() {
|
public String getResourceSetId() {
|
||||||
return this.resourceSetId;
|
return this.resourceSetId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package org.keycloak.authorization.client.resource;
|
package org.keycloak.authorization.client.resource;
|
||||||
|
|
||||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
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.EntitlementRequest;
|
||||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
|
import org.keycloak.authorization.client.util.HttpMethod;
|
||||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
@ -23,9 +25,8 @@ public class EntitlementResource {
|
||||||
public EntitlementResponse getAll(String resourceServerId) {
|
public EntitlementResponse getAll(String resourceServerId) {
|
||||||
try {
|
try {
|
||||||
return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
|
return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
|
||||||
.authorizationBearer(this.eat)
|
.authorizationBearer(eat)
|
||||||
.response()
|
.response().json(EntitlementResponse.class).execute();
|
||||||
.json(EntitlementResponse.class).execute();
|
|
||||||
} catch (HttpResponseException e) {
|
} catch (HttpResponseException e) {
|
||||||
if (403 == e.getStatusCode()) {
|
if (403 == e.getStatusCode()) {
|
||||||
throw new AuthorizationDeniedException(e);
|
throw new AuthorizationDeniedException(e);
|
||||||
|
@ -39,7 +40,7 @@ public class EntitlementResource {
|
||||||
public EntitlementResponse get(String resourceServerId, EntitlementRequest request) {
|
public EntitlementResponse get(String resourceServerId, EntitlementRequest request) {
|
||||||
try {
|
try {
|
||||||
return this.http.<EntitlementResponse>post("/authz/entitlement/" + resourceServerId)
|
return this.http.<EntitlementResponse>post("/authz/entitlement/" + resourceServerId)
|
||||||
.authorizationBearer(this.eat)
|
.authorizationBearer(eat)
|
||||||
.json(JsonSerialization.writeValueAsBytes(request))
|
.json(JsonSerialization.writeValueAsBytes(request))
|
||||||
.response().json(EntitlementResponse.class).execute();
|
.response().json(EntitlementResponse.class).execute();
|
||||||
} catch (HttpResponseException e) {
|
} catch (HttpResponseException e) {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.keycloak.authorization.policy.evaluation;
|
package org.keycloak.authorization.policy.evaluation;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -33,7 +34,7 @@ import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||||||
*/
|
*/
|
||||||
public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
|
public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
|
||||||
|
|
||||||
private Map<ResourcePermission, Result> results = new HashMap();
|
private Map<ResourcePermission, Result> results = new LinkedHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDecision(DefaultEvaluation evaluation) {
|
public void onDecision(DefaultEvaluation evaluation) {
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class PolicyEvaluationResponseBuilder {
|
||||||
AccessToken accessToken = identity.getAccessToken();
|
AccessToken accessToken = identity.getAccessToken();
|
||||||
AccessToken.Authorization authorizationData = new AccessToken.Authorization();
|
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);
|
accessToken.setAuthorization(authorizationData);
|
||||||
|
|
||||||
response.setRpt(accessToken);
|
response.setRpt(accessToken);
|
||||||
|
|
|
@ -108,8 +108,15 @@ public class AuthorizationTokenService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PermissionTicket ticket = verifyPermissionTicket(authorizationRequest);
|
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<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()) {
|
if (!entitlements.isEmpty()) {
|
||||||
AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
|
AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
|
||||||
|
|
|
@ -23,6 +23,7 @@ package org.keycloak.authorization.authorization.representation;
|
||||||
*/
|
*/
|
||||||
public class AuthorizationRequest {
|
public class AuthorizationRequest {
|
||||||
|
|
||||||
|
private AuthorizationRequestMetadata metadata;
|
||||||
private String ticket;
|
private String ticket;
|
||||||
private String rpt;
|
private String rpt;
|
||||||
|
|
||||||
|
@ -31,10 +32,6 @@ public class AuthorizationRequest {
|
||||||
this.rpt = rpt;
|
this.rpt = rpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthorizationRequest(String ticket) {
|
|
||||||
this(ticket, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorizationRequest() {
|
public AuthorizationRequest() {
|
||||||
this(null, null);
|
this(null, null);
|
||||||
}
|
}
|
||||||
|
@ -46,4 +43,8 @@ public class AuthorizationRequest {
|
||||||
public String getRpt() {
|
public String getRpt() {
|
||||||
return this.rpt;
|
return this.rpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthorizationRequestMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -36,6 +37,7 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
@ -45,6 +47,7 @@ import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
|
import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
|
||||||
import org.keycloak.authorization.common.KeycloakEvaluationContext;
|
import org.keycloak.authorization.common.KeycloakEvaluationContext;
|
||||||
import org.keycloak.authorization.common.KeycloakIdentity;
|
import org.keycloak.authorization.common.KeycloakIdentity;
|
||||||
import org.keycloak.authorization.entitlement.representation.EntitlementRequest;
|
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.model.Scope;
|
||||||
import org.keycloak.authorization.permission.ResourcePermission;
|
import org.keycloak.authorization.permission.ResourcePermission;
|
||||||
import org.keycloak.authorization.policy.evaluation.Result;
|
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.ResourceStore;
|
||||||
import org.keycloak.authorization.store.ScopeStore;
|
import org.keycloak.authorization.store.ScopeStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
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);
|
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}")
|
@Path("{resource_server_id}")
|
||||||
|
@ -154,13 +158,13 @@ public class EntitlementService {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
|
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 {
|
try {
|
||||||
List<Result> result = authorization.evaluators().from(permissions, new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate();
|
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()) {
|
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();
|
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) {
|
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
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;
|
Resource resource;
|
||||||
|
|
||||||
if (requestedResource.getResourceSetId() != null) {
|
if (requestedResource.getResourceSetId() != null) {
|
||||||
|
@ -208,14 +218,17 @@ public class EntitlementService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<ScopeRepresentation> requestedScopes = requestedResource.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet());
|
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) {
|
if (resource != null) {
|
||||||
permissionsToEvaluate.put(resource.getId(), collect);
|
permissionsToEvaluate.put(resource.getId(), scopeNames);
|
||||||
|
if (limit != null) {
|
||||||
|
limit--;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
|
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
|
||||||
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
|
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
|
||||||
List<Resource> resources = new ArrayList<Resource>();
|
List<Resource> resources = new ArrayList<>();
|
||||||
|
|
||||||
resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> {
|
resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> {
|
||||||
Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId());
|
Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId());
|
||||||
|
@ -228,17 +241,21 @@ public class EntitlementService {
|
||||||
}).filter(s -> s != null).collect(Collectors.toList()), resourceServer.getId()));
|
}).filter(s -> s != null).collect(Collectors.toList()), resourceServer.getId()));
|
||||||
|
|
||||||
for (Resource resource1 : resources) {
|
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();
|
String rpt = entitlementRequest.getRpt();
|
||||||
|
|
||||||
if (rpt != null && !"".equals(rpt)) {
|
if (rpt != null && !"".equals(rpt)) {
|
||||||
KeycloakContext context = authorization.getKeycloakSession().getContext();
|
KeycloakContext context = authorization.getKeycloakSession().getContext();
|
||||||
|
|
||||||
if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
|
if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
|
||||||
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
|
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
@ -258,7 +275,11 @@ public class EntitlementService {
|
||||||
List<Permission> permissions = authorizationData.getPermissions();
|
List<Permission> permissions = authorizationData.getPermissions();
|
||||||
|
|
||||||
if (permissions != null) {
|
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());
|
Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId(), resourceServer.getId());
|
||||||
|
|
||||||
if (resourcePermission != null) {
|
if (resourcePermission != null) {
|
||||||
|
@ -267,6 +288,9 @@ public class EntitlementService {
|
||||||
if (scopes == null) {
|
if (scopes == null) {
|
||||||
scopes = new HashSet<>();
|
scopes = new HashSet<>();
|
||||||
permissionsToEvaluate.put(resourcePermission.getId(), scopes);
|
permissionsToEvaluate.put(resourcePermission.getId(), scopes);
|
||||||
|
if (limit != null) {
|
||||||
|
limit--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> scopePermission = permission.getScopes();
|
Set<String> scopePermission = permission.getScopes();
|
||||||
|
@ -275,7 +299,7 @@ public class EntitlementService {
|
||||||
scopes.addAll(scopePermission);
|
scopes.addAll(scopePermission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,78 @@
|
||||||
package org.keycloak.authorization.entitlement.representation;
|
package org.keycloak.authorization.entitlement.representation;
|
||||||
|
|
||||||
import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public class EntitlementRequest {
|
public class EntitlementRequest {
|
||||||
|
|
||||||
private String rpt;
|
private String rpt;
|
||||||
|
private AuthorizationRequestMetadata metadata;
|
||||||
|
|
||||||
private List<PermissionRequest> permissions = new ArrayList<>();
|
private List<PermissionRequest> permissions = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the permissions being requested.
|
||||||
|
*
|
||||||
|
* @return the permissions being requested (not {@code null})
|
||||||
|
*/
|
||||||
public List<PermissionRequest> getPermissions() {
|
public List<PermissionRequest> getPermissions() {
|
||||||
return permissions;
|
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() {
|
public String getRpt() {
|
||||||
return rpt;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.keycloak.authorization.util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -29,6 +30,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.Decision.Effect;
|
import org.keycloak.authorization.Decision.Effect;
|
||||||
|
import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
|
||||||
import org.keycloak.authorization.identity.Identity;
|
import org.keycloak.authorization.identity.Identity;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
|
@ -134,8 +136,8 @@ public final class Permissions {
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServerId) {
|
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) {
|
for (Result result : evaluation) {
|
||||||
Set<Scope> deniedScopes = new HashSet<>();
|
Set<Scope> deniedScopes = new HashSet<>();
|
||||||
|
@ -188,14 +190,14 @@ public final class Permissions {
|
||||||
|
|
||||||
if (deniedCount == 0) {
|
if (deniedCount == 0) {
|
||||||
result.setStatus(Effect.PERMIT);
|
result.setStatus(Effect.PERMIT);
|
||||||
grantPermission(authorizationProvider, permissions, permission, resourceServerId);
|
grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
|
||||||
} else {
|
} else {
|
||||||
// if a full deny or resource denied or the requested scopes were denied
|
// if a full deny or resource denied or the requested scopes were denied
|
||||||
if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
|
if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
|
||||||
result.setStatus(Effect.DENY);
|
result.setStatus(Effect.DENY);
|
||||||
} else {
|
} else {
|
||||||
result.setStatus(Effect.PERMIT);
|
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());
|
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<>();
|
List<Resource> resources = new ArrayList<>();
|
||||||
Resource resource = permission.getResource();
|
Resource resource = permission.getResource();
|
||||||
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
|
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
|
||||||
|
@ -224,14 +226,14 @@ public final class Permissions {
|
||||||
|
|
||||||
if (!permissionScopes.isEmpty()) {
|
if (!permissionScopes.isEmpty()) {
|
||||||
ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
|
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()) {
|
if (!resources.isEmpty()) {
|
||||||
for (Resource allowedResource : resources) {
|
for (Resource allowedResource : resources) {
|
||||||
String resourceId = allowedResource.getId();
|
String resourceId = allowedResource.getId();
|
||||||
String resourceName = allowedResource.getName();
|
String resourceName = metadata == null || metadata.isIncludeResourceName() ? allowedResource.getName() : null;
|
||||||
Permission evalPermission = permissions.get(allowedResource.getId());
|
Permission evalPermission = permissions.get(allowedResource.getId());
|
||||||
|
|
||||||
if (evalPermission == null) {
|
if (evalPermission == null) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue