[KEYCLOAK-7849] - Improvements to RPT upgrade
This commit is contained in:
parent
34407957b9
commit
8b6979ac18
11 changed files with 420 additions and 135 deletions
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
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);
|
||||
}
|
||||
if (rpt != null && rpt.isActive()) {
|
||||
AccessToken.Authorization authorizationData = rpt.getAuthorization();
|
||||
|
||||
AccessToken requestingPartyToken;
|
||||
if (authorizationData != null) {
|
||||
List<Permission> permissions = authorizationData.getPermissions();
|
||||
|
||||
try {
|
||||
requestingPartyToken = new JWSInput(rpt).readJsonContent(AccessToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_rpt", "Invalid RPT", Status.FORBIDDEN);
|
||||
}
|
||||
if (permissions != null) {
|
||||
for (Permission grantedPermission : permissions) {
|
||||
if (limit != null && limit <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (requestingPartyToken.isActive()) {
|
||||
AccessToken.Authorization authorizationData = requestingPartyToken.getAuthorization();
|
||||
Resource resource = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
|
||||
|
||||
if (authorizationData != null) {
|
||||
List<Permission> permissions = authorizationData.getPermissions();
|
||||
if (resource != null) {
|
||||
ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
|
||||
|
||||
if (permissions != null) {
|
||||
for (Permission grantedPermission : permissions) {
|
||||
if (limit != null && limit <= 0) {
|
||||
break;
|
||||
}
|
||||
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());
|
||||
|
||||
Resource resourcePermission = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
|
||||
|
||||
if (resourcePermission != null) {
|
||||
ResourcePermission permission = permissionsToEvaluate.get(resourcePermission.getId());
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue