keycloak-uncached
Changes
authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java 6(+3 -3)
core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java 27(+18 -9)
core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java 39(+7 -32)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java 12(+6 -6)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 162(+91 -71)
services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java 11(+6 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionEqualsTest.java 74(+74 -0)
Details
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
index e0a8a99..64f0f6f 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java
@@ -25,8 +25,8 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
-import org.keycloak.representations.idm.authorization.PermissionTicketToken.ResourcePermission;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -80,13 +80,13 @@ public class HttpMethodAuthenticator<R> {
method.param("claim_token", request.getClaimToken());
method.param("claim_token_format", request.getClaimTokenFormat());
method.param("pct", request.getPct());
- method.param("rpt", request.getRpt());
+ method.param("rpt", request.getRptToken());
method.param("scope", request.getScope());
method.param("audience", request.getAudience());
method.param("subject_token", request.getSubjectToken());
if (permissions != null) {
- for (ResourcePermission permission : permissions.getResources()) {
+ for (Permission permission : permissions.getPermissions()) {
String resourceId = permission.getResourceId();
Set<String> scopes = permission.getScopes();
StringBuilder value = new StringBuilder();
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
index 90cba85..a50bf2d 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
@@ -23,7 +23,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import org.keycloak.representations.idm.authorization.PermissionTicketToken.ResourcePermission;
+import org.keycloak.representations.AccessToken;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -31,7 +31,6 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken.Reso
public class AuthorizationRequest {
private String ticket;
- private String rpt;
private String claimToken;
private String claimTokenFormat;
private String pct;
@@ -42,6 +41,8 @@ public class AuthorizationRequest {
private String subjectToken;
private boolean submitRequest;
private Map<String, List<String>> claims;
+ private AccessToken rpt;
+ private String rptToken;
public AuthorizationRequest(String ticket) {
this.ticket = ticket;
@@ -59,14 +60,22 @@ public class AuthorizationRequest {
this.ticket = ticket;
}
- public String getRpt() {
+ public AccessToken getRpt() {
return this.rpt;
}
- public void setRpt(String rpt) {
+ public void setRpt(AccessToken rpt) {
this.rpt = rpt;
}
+ public void setRpt(String rpt) {
+ this.rptToken = rpt;
+ }
+
+ public String getRptToken() {
+ return rptToken;
+ }
+
public void setClaimToken(String claimToken) {
this.claimToken = claimToken;
}
@@ -145,12 +154,12 @@ public class AuthorizationRequest {
public void addPermission(String resourceId, String... scopes) {
if (permissions == null) {
- permissions = new PermissionTicketToken(new ArrayList<ResourcePermission>());
+ permissions = new PermissionTicketToken(new ArrayList<Permission>());
}
- ResourcePermission permission = null;
+ Permission permission = null;
- for (ResourcePermission resourcePermission : permissions.getResources()) {
+ for (Permission resourcePermission : permissions.getPermissions()) {
if (resourcePermission.getResourceId() != null && resourcePermission.getResourceId().equals(resourceId)) {
permission = resourcePermission;
break;
@@ -158,8 +167,8 @@ public class AuthorizationRequest {
}
if (permission == null) {
- permission = new ResourcePermission(resourceId, new HashSet<String>());
- permissions.getResources().add(permission);
+ permission = new Permission(resourceId, new HashSet<String>());
+ permissions.getPermissions().add(permission);
}
permission.getScopes().addAll(Arrays.asList(scopes));
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
index 53760c3..c1afa01 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
@@ -33,7 +33,7 @@ public class Permission {
private String resourceId;
@JsonProperty("rsname")
- private final String resourceName;
+ private String resourceName;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<String> scopes;
@@ -45,6 +45,10 @@ public class Permission {
this(null, null, null, null);
}
+ public Permission(final String resourceId, final Set<String> scopes) {
+ this(resourceId, null, scopes, null);
+ }
+
public Permission(final String resourceId, String resourceName, final Set<String> scopes, Map<String, Set<String>> claims) {
this.resourceId = resourceId;
this.resourceName = resourceName;
@@ -52,10 +56,18 @@ public class Permission {
this.claims = claims;
}
+ public void setResourceId(String resourceId) {
+ this.resourceId = resourceId;
+ }
+
public String getResourceId() {
return this.resourceId;
}
+ public void setResourceName(String resourceName) {
+ this.resourceName = resourceName;
+ }
+
public String getResourceName() {
return this.resourceName;
}
@@ -75,11 +87,29 @@ public class Permission {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || !getClass().isAssignableFrom(o.getClass())) return false;
Permission that = (Permission) o;
- return getResourceId().equals(that.resourceId);
+ if (getResourceId() != null || getResourceName() != null) {
+ if (!getResourceId().equals(that.resourceId)) {
+ return false;
+ }
+
+ if (getScopes().isEmpty() && that.getScopes().isEmpty()) {
+ return true;
+ }
+ } else if (that.resourceId != null) {
+ return false;
+ }
+
+ for (String scope : that.getScopes()) {
+ if (getScopes().contains(scope)) {
+ return true;
+ }
+ }
+
+ return false;
}
@Override
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
index a9f6ba5..b20728d 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
@@ -33,16 +33,16 @@ import org.keycloak.representations.JsonWebToken;
*/
public class PermissionTicketToken extends JsonWebToken {
- private final List<ResourcePermission> resources;
+ private final List<Permission> permissions;
@JsonDeserialize(using = StringListMapDeserializer.class)
private Map<String, List<String>> claims;
public PermissionTicketToken() {
- this(new ArrayList<ResourcePermission>());
+ this(new ArrayList<Permission>());
}
- public PermissionTicketToken(List<ResourcePermission> resources, String audience, AccessToken accessToken) {
+ public PermissionTicketToken(List<Permission> permissions, String audience, AccessToken accessToken) {
if (accessToken != null) {
id(TokenIdGenerator.generateId());
subject(accessToken.getSubject());
@@ -54,15 +54,15 @@ public class PermissionTicketToken extends JsonWebToken {
if (audience != null) {
audience(audience);
}
- this.resources = resources;
+ this.permissions = permissions;
}
- public PermissionTicketToken(List<ResourcePermission> resources) {
+ public PermissionTicketToken(List<Permission> resources) {
this(resources, null, null);
}
- public List<ResourcePermission> getResources() {
- return this.resources;
+ public List<Permission> getPermissions() {
+ return this.permissions;
}
public Map<String, List<String>> getClaims() {
@@ -72,29 +72,4 @@ public class PermissionTicketToken extends JsonWebToken {
public void setClaims(Map<String, List<String>> claims) {
this.claims = claims;
}
-
- public static class ResourcePermission {
-
- @JsonProperty("id")
- private String resourceId;
-
- @JsonProperty("scopes")
- private Set<String> scopes;
-
- public ResourcePermission() {
- }
-
- public ResourcePermission(String resourceId, Set<String> scopes) {
- this.resourceId = resourceId;
- this.scopes = scopes;
- }
-
- public String getResourceId() {
- return resourceId;
- }
-
- public Set<String> getScopes() {
- return scopes;
- }
- }
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
index 6ab219b..6b671e3 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
@@ -31,11 +31,11 @@ import org.keycloak.authorization.model.Resource;
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.PolicyResult;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
/**
@@ -75,12 +75,12 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
if ("uma".equals(policy.getType())) {
ResourcePermission grantedPermission = evaluation.getPermission();
- List<PermissionTicketToken.ResourcePermission> permissions = ticket.getResources();
+ List<Permission> permissions = ticket.getPermissions();
- Iterator<PermissionTicketToken.ResourcePermission> itPermissions = permissions.iterator();
+ Iterator<Permission> itPermissions = permissions.iterator();
while (itPermissions.hasNext()) {
- PermissionTicketToken.ResourcePermission permission = itPermissions.next();
+ Permission permission = itPermissions.next();
if (permission.getResourceId().equals(grantedPermission.getResource().getId())) {
Set<String> scopes = permission.getScopes();
@@ -109,10 +109,10 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
if (request.isSubmitRequest()) {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
- List<PermissionTicketToken.ResourcePermission> permissions = ticket.getResources();
+ List<Permission> permissions = ticket.getPermissions();
if (permissions != null) {
- for (PermissionTicketToken.ResourcePermission permission : permissions) {
+ for (Permission permission : permissions) {
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
if (resource == null) {
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 97e6727..05a732f 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -28,10 +28,7 @@ import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
-import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MediaType;
@@ -70,7 +67,6 @@ import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.TokenManager.AccessTokenResponseBuilder;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessToken.Authorization;
-import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
@@ -149,7 +145,7 @@ public class AuthorizationTokenService {
}
// it is not secure to allow public clients to push arbitrary claims because message can be tampered
- if (isPublicClientRequestingEntitlemesWithClaims(request)) {
+ if (isPublicClientRequestingEntitlementWithClaims(request)) {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
}
@@ -163,9 +159,9 @@ public class AuthorizationTokenService {
KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
List<Result> evaluation;
- if (ticket.getResources().isEmpty() && request.getRpt() == null) {
+ if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
evaluation = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
- } else if(!request.getPermissions().getResources().isEmpty()) {
+ } else if(!request.getPermissions().getPermissions().isEmpty()) {
evaluation = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
} else {
evaluation = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
@@ -173,21 +169,21 @@ public class AuthorizationTokenService {
List<Permission> permissions = Permissions.permits(evaluation, request.getMetadata(), authorization, resourceServer);
- if (permissions.isEmpty()) {
- if (request.isSubmitRequest()) {
- throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
- } else {
- throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
- }
- }
+ if (isGranted(ticket, request, permissions)) {
+ ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId());
+ AuthorizationResponse response = createAuthorizationResponse(identity, permissions, request, targetClient);
- ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId());
- AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(identity, permissions, request, targetClient), request.getRpt() != null);
+ return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
+ .allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient)
+ .allowedMethods(HttpMethod.POST)
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+ }
- return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
- .allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient)
- .allowedMethods(HttpMethod.POST)
- .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+ if (request.isSubmitRequest()) {
+ throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
+ } else {
+ throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
+ }
} catch (ErrorResponseException | CorsErrorResponseException cause) {
if (logger.isDebugEnabled()) {
logger.debug("Error while evaluating permissions", cause);
@@ -199,7 +195,7 @@ public class AuthorizationTokenService {
}
}
- private boolean isPublicClientRequestingEntitlemesWithClaims(AuthorizationRequest request) {
+ private boolean isPublicClientRequestingEntitlementWithClaims(AuthorizationRequest request) {
return request.getClaimToken() != null && getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
}
@@ -221,7 +217,7 @@ public class AuthorizationTokenService {
.evaluate();
}
- private AccessTokenResponse createRequestingPartyToken(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) {
+ private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) {
KeycloakSession keycloakSession = getKeycloakSession();
AccessToken accessToken = identity.getAccessToken();
UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(getRealm(), accessToken.getSessionState());
@@ -253,7 +249,31 @@ public class AuthorizationTokenService {
rpt.audience(targetClient.getClientId());
}
- return responseBuilder.build();
+ return new AuthorizationResponse(responseBuilder.build(), isUpgraded(request, authorization));
+ }
+
+ private boolean isUpgraded(AuthorizationRequest request, Authorization authorization) {
+ AccessToken previousRpt = request.getRpt();
+
+ if (previousRpt == null) {
+ return false;
+ }
+
+ Authorization previousAuthorization = previousRpt.getAuthorization();
+
+ if (previousAuthorization != null) {
+ List<Permission> previousPermissions = previousAuthorization.getPermissions();
+
+ if (previousPermissions != null) {
+ for (Permission previousPermission : previousPermissions) {
+ if (!authorization.getPermissions().contains(previousPermission)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
}
private PermissionTicketToken getPermissionTicket(AuthorizationRequest request) {
@@ -320,7 +340,7 @@ public class AuthorizationTokenService {
Metadata metadata = request.getMetadata();
Integer limit = metadata != null ? metadata.getLimit() : null;
- for (PermissionTicketToken.ResourcePermission requestedResource : ticket.getResources()) {
+ for (Permission requestedResource : ticket.getPermissions()) {
if (limit != null && limit <= 0) {
break;
}
@@ -339,16 +359,19 @@ public class AuthorizationTokenService {
if (resource != null) {
existingResources.add(resource);
} else {
- Resource ownerResource = resourceStore.findByName(requestedResource.getResourceId(), identity.getId(), resourceServer.getId());
+ String resourceName = requestedResource.getResourceId();
+ Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId());
if (ownerResource != null) {
+ requestedResource.setResourceId(ownerResource.getId());
existingResources.add(ownerResource);
}
if (!identity.isResourceServer()) {
- Resource serverResource = resourceStore.findByName(requestedResource.getResourceId(), resourceServer.getId());
+ Resource serverResource = resourceStore.findByName(resourceName, resourceServer.getId());
if (serverResource != null) {
+ requestedResource.setResourceId(serverResource.getId());
existingResources.add(serverResource);
}
}
@@ -403,63 +426,49 @@ public class AuthorizationTokenService {
}
}
- String rpt = request.getRpt();
-
- if (rpt != null) {
- if (!Tokens.verifySignature(getKeycloakSession(), getRealm(), rpt)) {
- throw new CorsErrorResponseException(cors, "invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
- }
+ AccessToken rpt = request.getRpt();
- AccessToken requestingPartyToken;
+ if (rpt != null && rpt.isActive()) {
+ AccessToken.Authorization authorizationData = rpt.getAuthorization();
- try {
- requestingPartyToken = new JWSInput(rpt).readJsonContent(AccessToken.class);
- } catch (JWSInputException e) {
- throw new CorsErrorResponseException(cors, "invalid_rpt", "Invalid RPT", Status.FORBIDDEN);
- }
+ if (authorizationData != null) {
+ List<Permission> permissions = authorizationData.getPermissions();
- if (requestingPartyToken.isActive()) {
- AccessToken.Authorization authorizationData = requestingPartyToken.getAuthorization();
-
- if (authorizationData != null) {
- List<Permission> permissions = authorizationData.getPermissions();
+ if (permissions != null) {
+ for (Permission grantedPermission : permissions) {
+ if (limit != null && limit <= 0) {
+ break;
+ }
- if (permissions != null) {
- for (Permission grantedPermission : permissions) {
- if (limit != null && limit <= 0) {
- break;
- }
+ Resource resource = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
- Resource resourcePermission = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
+ if (resource != null) {
+ ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
- if (resourcePermission != null) {
- ResourcePermission permission = permissionsToEvaluate.get(resourcePermission.getId());
+ if (permission == null) {
+ permission = new ResourcePermission(resource, new ArrayList<>(), resourceServer, grantedPermission.getClaims());
+ permissionsToEvaluate.put(resource.getId(), permission);
+ if (limit != null) {
+ limit--;
+ }
+ } else {
+ if (grantedPermission.getClaims() != null) {
+ for (Entry<String, Set<String>> entry : grantedPermission.getClaims().entrySet()) {
+ Set<String> claims = permission.getClaims().get(entry.getKey());
- if (permission == null) {
- permission = new ResourcePermission(resourcePermission, new ArrayList<>(), resourceServer, grantedPermission.getClaims());
- permissionsToEvaluate.put(resourcePermission.getId(), permission);
- if (limit != null) {
- limit--;
- }
- } else {
- if (grantedPermission.getClaims() != null) {
- for (Entry<String, Set<String>> entry : grantedPermission.getClaims().entrySet()) {
- Set<String> claims = permission.getClaims().get(entry.getKey());
-
- if (claims != null) {
- claims.addAll(entry.getValue());
- }
+ if (claims != null) {
+ claims.addAll(entry.getValue());
}
}
}
+ }
- for (String scopeName : grantedPermission.getScopes()) {
- Scope scope = scopeStore.findByName(scopeName, resourceServer.getId());
+ for (String scopeName : grantedPermission.getScopes()) {
+ Scope scope = scopeStore.findByName(scopeName, resourceServer.getId());
- if (scope != null) {
- if (!permission.getScopes().contains(scope)) {
- permission.getScopes().add(scope);
- }
+ if (scope != null) {
+ if (!permission.getScopes().contains(scope)) {
+ permission.getScopes().add(scope);
}
}
}
@@ -492,6 +501,17 @@ public class AuthorizationTokenService {
}
}
+ private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, List<Permission> permissions) {
+ List<Permission> requestedPermissions = ticket.getPermissions();
+
+ // denies in case a rpt was provided along with the authorization request but any requested permission was not granted
+ if (request.getRpt() != null && !requestedPermissions.isEmpty() && requestedPermissions.stream().anyMatch(permission -> !permissions.contains(permission))) {
+ return false;
+ }
+
+ return !permissions.isEmpty();
+ }
+
private KeycloakSession getKeycloakSession() {
return this.authorization.getKeycloakSession();
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
index e045d31..2386f12 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
@@ -35,6 +35,7 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
import org.keycloak.representations.idm.authorization.PermissionTicketToken;
@@ -63,9 +64,9 @@ public class AbstractPermissionService {
return Response.status(Response.Status.CREATED).entity(new PermissionResponse(createPermissionTicket(request))).build();
}
- private List<PermissionTicketToken.ResourcePermission> verifyRequestedResource(List<PermissionRequest> request) {
+ private List<Permission> verifyRequestedResource(List<PermissionRequest> request) {
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
- List<PermissionTicketToken.ResourcePermission> requestedResources = new ArrayList<>();
+ List<Permission> requestedResources = new ArrayList<>();
for (PermissionRequest permissionRequest : request) {
String resourceSetId = permissionRequest.getResourceId();
@@ -102,10 +103,10 @@ public class AbstractPermissionService {
}
if (resources.isEmpty()) {
- requestedResources.add(new PermissionTicketToken.ResourcePermission(null, verifyRequestedScopes(permissionRequest, null)));
+ requestedResources.add(new Permission(null, verifyRequestedScopes(permissionRequest, null)));
} else {
for (Resource resource : resources) {
- requestedResources.add(new PermissionTicketToken.ResourcePermission(resource.getId(), verifyRequestedScopes(permissionRequest, resource)));
+ requestedResources.add(new Permission(resource.getId(), verifyRequestedScopes(permissionRequest, resource)));
}
}
}
@@ -147,7 +148,7 @@ public class AbstractPermissionService {
}
private String createPermissionTicket(List<PermissionRequest> request) {
- List<PermissionTicketToken.ResourcePermission> permissions = verifyRequestedResource(request);
+ List<Permission> permissions = verifyRequestedResource(request);
KeyManager.ActiveRsaKey keys = this.authorization.getKeycloakSession().keys().getActiveRsaKey(this.authorization.getRealm());
ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index ad92195..67cb0dc 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -1079,7 +1079,20 @@ public class TokenEndpoint {
authorizationRequest.setClaimToken(claimToken);
authorizationRequest.setClaimTokenFormat(claimTokenFormat);
authorizationRequest.setPct(formParams.getFirst("pct"));
- authorizationRequest.setRpt(formParams.getFirst("rpt"));
+ String rpt = formParams.getFirst("rpt");
+
+ if (rpt != null) {
+ if (!Tokens.verifySignature(session, realm, rpt)) {
+ throw new CorsErrorResponseException(cors, "invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
+ }
+
+ try {
+ authorizationRequest.setRpt(new JWSInput(rpt).readJsonContent(AccessToken.class));
+ } catch (JWSInputException e) {
+ throw new CorsErrorResponseException(cors, "invalid_rpt", "Invalid RPT", Status.FORBIDDEN);
+ }
+ }
+
authorizationRequest.setScope(formParams.getFirst("scope"));
authorizationRequest.setAudience(formParams.getFirst("audience"));
authorizationRequest.setSubjectToken(formParams.getFirst("subject_token") != null ? formParams.getFirst("subject_token") : accessTokenString);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionEqualsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionEqualsTest.java
new file mode 100644
index 0000000..e96fda3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionEqualsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authz;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.junit.Test;
+import org.keycloak.representations.idm.authorization.Permission;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PermissionEqualsTest {
+
+ @Test
+ public void testEquals() {
+ assertTrue(new Permission("1", null, Collections.emptySet(), Collections.emptyMap()).equals(
+ new Permission("1", null, Collections.emptySet(), Collections.emptyMap())
+ ));
+ assertFalse(new Permission("1", null, Collections.emptySet(), Collections.emptyMap()).equals(
+ new Permission("2", null, Collections.emptySet(), Collections.emptyMap())
+ ));
+ assertFalse(new Permission("1", null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap()).equals(
+ new Permission("1", null, Collections.emptySet(), Collections.emptyMap())
+ ));
+ assertTrue(new Permission("1", null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap()).equals(
+ new Permission("1", null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap())
+ ));
+ assertTrue(new Permission("1", null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap()).equals(
+ new Permission("1", null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap())
+ ));
+ assertFalse(new Permission("1", null, new HashSet<>(Arrays.asList("read")), Collections.emptyMap()).equals(
+ new Permission("1", null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap())
+ ));
+ assertFalse(new Permission(null, null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap()).equals(
+ new Permission("1", null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap())
+ ));
+ assertFalse(new Permission("1", null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap()).equals(
+ new Permission(null, null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap())
+ ));
+ assertTrue(new Permission(null, null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap()).equals(
+ new Permission(null, null, new HashSet<>(Arrays.asList("write")), Collections.emptyMap())
+ ));
+ assertTrue(new Permission(null, null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap()).equals(
+ new Permission(null, null, new HashSet<>(Arrays.asList("read")), Collections.emptyMap())
+ ));
+ assertFalse(new Permission(null, null, new HashSet<>(Arrays.asList("read", "write")), Collections.emptyMap()).equals(
+ new Permission(null, null, new HashSet<>(Arrays.asList("update")), Collections.emptyMap())
+ ));
+ assertFalse(new Permission(null, null, Collections.emptySet(), Collections.emptyMap()).equals(
+ new Permission(null, null, new HashSet<>(Arrays.asList("read")), Collections.emptyMap())
+ ));
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
index aa9ce50..87172f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
@@ -36,6 +36,7 @@ import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
@@ -303,14 +304,14 @@ public class PermissionManagementTest extends AbstractResourceServerTest {
PermissionTicketToken token = new JWSInput(ticket).readJsonContent(PermissionTicketToken.class);
- List<PermissionTicketToken.ResourcePermission> tokenPermissions = token.getResources();
+ List<Permission> tokenPermissions = token.getPermissions();
assertNotNull(tokenPermissions);
assertEquals(expectedPermissions, scopeNames.length > 0 ? scopeNames.length : tokenPermissions.size());
- Iterator<PermissionTicketToken.ResourcePermission> permissionIterator = tokenPermissions.iterator();
+ Iterator<Permission> permissionIterator = tokenPermissions.iterator();
while (permissionIterator.hasNext()) {
- PermissionTicketToken.ResourcePermission resourcePermission = permissionIterator.next();
+ Permission resourcePermission = permissionIterator.next();
long count = tickets.stream().filter(representation -> representation.getResource().equals(resourcePermission.getResourceId())).count();
if (count == (scopeNames.length > 0 ? scopeNames.length : 1)) {
permissionIterator.remove();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
index 5d4c1a1..a6be35e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.authz;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
import java.net.URI;
@@ -38,17 +39,18 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.BasicAuthHelper;
@@ -81,6 +83,14 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
response = authorization.permissions().resource().create(permission);
response.close();
+
+ policy = new JSPolicyRepresentation();
+
+ policy.setName("Deny Policy");
+ policy.setCode("$evaluation.deny();");
+
+ response = authorization.policies().js().create(policy);
+ response.close();
}
@Test
@@ -118,6 +128,158 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
}
@Test
+ public void testObtainRptWithUpgradeOnlyScopes() throws Exception {
+ AuthorizationResponse response = authorize("marta", "password", null, new String[] {"ScopeA", "ScopeB"});
+ String rpt = response.getToken();
+ AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
+ assertTrue(permissions.isEmpty());
+
+ response = authorize("marta", "password", "Resource A", new String[] {"ScopeC"}, rpt);
+
+ authorization = toAccessToken(response.getToken()).getAuthorization();
+ permissions = authorization.getPermissions();
+
+ assertTrue(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB", "ScopeC");
+ assertTrue(permissions.isEmpty());
+ }
+
+ @Test
+ public void testObtainRptWithUpgradeWithUnauthorizedResource() throws Exception {
+ AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
+ String rpt = response.getToken();
+ AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
+ assertTrue(permissions.isEmpty());
+
+ ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+ ResourceRepresentation resourceB = addResource("Resource B", "ScopeA", "ScopeB", "ScopeC");
+
+ permission.setName(resourceB.getName() + " Permission");
+ permission.addResource(resourceB.getName());
+ permission.addPolicy("Deny Policy");
+
+ getClient(getRealm()).authorization().permissions().resource().create(permission).close();
+
+ try {
+ authorize("marta", "password", "Resource B", new String[]{"ScopeC"}, rpt);
+ fail("Should be denied, resource b not granted");
+ } catch (AuthorizationDeniedException ignore) {
+
+ }
+ }
+
+ @Test
+ public void testObtainRptWithUpgradeWithUnauthorizedResourceFromRpt() throws Exception {
+ ResourcePermissionRepresentation permissionA = new ResourcePermissionRepresentation();
+ ResourceRepresentation resourceA = addResource(KeycloakModelUtils.generateId(), "ScopeA", "ScopeB", "ScopeC");
+
+ permissionA.setName(resourceA.getName() + " Permission");
+ permissionA.addResource(resourceA.getName());
+ permissionA.addPolicy("Default Policy");
+
+ AuthorizationResource authzResource = getClient(getRealm()).authorization();
+
+ authzResource.permissions().resource().create(permissionA).close();
+ AuthorizationResponse response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeA", "ScopeB"});
+ String rpt = response.getToken();
+ AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB");
+ assertTrue(permissions.isEmpty());
+
+ ResourceRepresentation resourceB = addResource(KeycloakModelUtils.generateId(), "ScopeA", "ScopeB", "ScopeC");
+ ResourcePermissionRepresentation permissionB = new ResourcePermissionRepresentation();
+
+ permissionB.setName(resourceB.getName() + " Permission");
+ permissionB.addResource(resourceB.getName());
+ permissionB.addPolicy("Default Policy");
+
+ authzResource.permissions().resource().create(permissionB).close();
+ response = authorize("marta", "password", resourceB.getId(), new String[] {"ScopeC"}, rpt);
+ rpt = response.getToken();
+ authorization = toAccessToken(rpt).getAuthorization();
+ permissions = authorization.getPermissions();
+
+ assertTrue(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB");
+ assertPermissions(permissions, resourceB.getName(), "ScopeC");
+ assertTrue(permissions.isEmpty());
+
+ permissionB = authzResource.permissions().resource().findByName(permissionB.getName());
+ permissionB.removePolicy("Default Policy");
+ permissionB.addPolicy("Deny Policy");
+
+ authzResource.permissions().resource().findById(permissionB.getId()).update(permissionB);
+
+ response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeC"}, rpt);
+ rpt = response.getToken();
+ authorization = toAccessToken(rpt).getAuthorization();
+ permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB", "ScopeC");
+ assertTrue(permissions.isEmpty());
+ }
+
+ @Test
+ public void testObtainRptOnlyAuthorizedScopes() throws Exception {
+ ResourceRepresentation resourceA = addResource(KeycloakModelUtils.generateId(), "READ", "WRITE");
+ ScopePermissionRepresentation permissionA = new ScopePermissionRepresentation();
+
+ permissionA.setName(KeycloakModelUtils.generateId());
+ permissionA.addScope("READ");
+ permissionA.addPolicy("Default Policy");
+
+ AuthorizationResource authzResource = getClient(getRealm()).authorization();
+
+ authzResource.permissions().scope().create(permissionA).close();
+
+ ScopePermissionRepresentation permissionB = new ScopePermissionRepresentation();
+
+ permissionB.setName(KeycloakModelUtils.generateId());
+ permissionB.addScope("WRITE");
+ permissionB.addPolicy("Deny Policy");
+
+ authzResource.permissions().scope().create(permissionB).close();
+
+ AuthorizationResponse response = authorize("marta", "password", resourceA.getName(), new String[] {"READ"});
+ String rpt = response.getToken();
+ AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, resourceA.getName(), "READ");
+ assertTrue(permissions.isEmpty());
+
+ response = authorize("marta", "password", resourceA.getName(), new String[] {"READ", "WRITE"});
+ rpt = response.getToken();
+ authorization = toAccessToken(rpt).getAuthorization();
+ permissions = authorization.getPermissions();
+
+ assertFalse(response.isUpgraded());
+ assertNotNull(permissions);
+ assertPermissions(permissions, resourceA.getName(), "READ");
+ assertTrue(permissions.isEmpty());
+ }
+
+ @Test
public void testObtainRptWithOwnerManagedResource() throws Exception {
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
ResourceRepresentation resourceA = addResource("Resource Marta", "marta", true, "ScopeA", "ScopeB", "ScopeC");