[KEYCLOAK-7849] - Improvements to RPT upgrade

This commit is contained in:
Pedro Igor 2018-07-13 14:57:27 -03:00
parent 34407957b9
commit 8b6979ac18
11 changed files with 420 additions and 135 deletions

View file

@ -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();

View file

@ -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));

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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) {

View file

@ -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 = new AuthorizationResponse(createRequestingPartyToken(identity, permissions, request, targetClient), request.getRpt() != null);
AuthorizationResponse response = createAuthorizationResponse(identity, permissions, request, targetClient);
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,23 +426,10 @@ public class AuthorizationTokenService {
}
}
String rpt = request.getRpt();
AccessToken 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 requestingPartyToken;
try {
requestingPartyToken = new JWSInput(rpt).readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
throw new CorsErrorResponseException(cors, "invalid_rpt", "Invalid RPT", Status.FORBIDDEN);
}
if (requestingPartyToken.isActive()) {
AccessToken.Authorization authorizationData = requestingPartyToken.getAuthorization();
if (rpt != null && rpt.isActive()) {
AccessToken.Authorization authorizationData = rpt.getAuthorization();
if (authorizationData != null) {
List<Permission> permissions = authorizationData.getPermissions();
@ -430,14 +440,14 @@ public class AuthorizationTokenService {
break;
}
Resource resourcePermission = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
Resource resource = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
if (resourcePermission != null) {
ResourcePermission permission = permissionsToEvaluate.get(resourcePermission.getId());
if (resource != null) {
ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
if (permission == null) {
permission = new ResourcePermission(resourcePermission, new ArrayList<>(), resourceServer, grantedPermission.getClaims());
permissionsToEvaluate.put(resourcePermission.getId(), permission);
permission = new ResourcePermission(resource, new ArrayList<>(), resourceServer, grantedPermission.getClaims());
permissionsToEvaluate.put(resource.getId(), permission);
if (limit != null) {
limit--;
}
@ -467,7 +477,6 @@ public class AuthorizationTokenService {
}
}
}
}
return new ArrayList<>(permissionsToEvaluate.values());
}
@ -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();
}

View file

@ -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());

View file

@ -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);

View file

@ -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())
));
}
}

View file

@ -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();

View file

@ -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
@ -117,6 +127,158 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
assertTrue(permissions.isEmpty());
}
@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();