[KEYCLOAK-3156] - Missing CORS when responding with denies
This commit is contained in:
parent
333bdb62dd
commit
f48288865b
5 changed files with 44 additions and 8 deletions
|
@ -129,7 +129,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
Set<String> allowedScopes = permission.getScopes();
|
Set<String> allowedScopes = permission.getScopes();
|
||||||
|
|
||||||
if (permission.getResourceSetId() != null) {
|
if (permission.getResourceSetId() != null) {
|
||||||
if (permission.getResourceSetId().equals(actualPathConfig.getId())) {
|
if (isResourcePermission(actualPathConfig, permission)) {
|
||||||
if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
||||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||||
|
@ -211,6 +211,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
config.setScopes(originalConfig.getScopes());
|
config.setScopes(originalConfig.getScopes());
|
||||||
config.setMethods(originalConfig.getMethods());
|
config.setMethods(originalConfig.getMethods());
|
||||||
config.setInstance(true);
|
config.setInstance(true);
|
||||||
|
config.setParentConfig(originalConfig);
|
||||||
|
|
||||||
this.paths.add(config);
|
this.paths.add(config);
|
||||||
|
|
||||||
|
@ -240,4 +241,16 @@ public abstract class AbstractPolicyEnforcer {
|
||||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||||
return new AuthorizationContext(accessToken, this.paths);
|
return new AuthorizationContext(accessToken, this.paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||||
|
// first we try a match using resource id
|
||||||
|
boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||||
|
|
||||||
|
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||||
|
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||||
|
resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceMatch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.representations.adapters.config;
|
package org.keycloak.representations.adapters.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -99,6 +100,9 @@ public class PolicyEnforcerConfig {
|
||||||
private String id;
|
private String id;
|
||||||
private boolean instance;
|
private boolean instance;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private PathConfig parentConfig;
|
||||||
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return this.path;
|
return this.path;
|
||||||
}
|
}
|
||||||
|
@ -169,6 +173,14 @@ public class PolicyEnforcerConfig {
|
||||||
public void setInstance(boolean instance) {
|
public void setInstance(boolean instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setParentConfig(PathConfig parentConfig) {
|
||||||
|
this.parentConfig = parentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathConfig getParentConfig() {
|
||||||
|
return parentConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MethodConfig {
|
public static class MethodConfig {
|
||||||
|
|
|
@ -106,7 +106,10 @@ public class AuthorizationTokenService {
|
||||||
List<Permission> entitlements = Permissions.allPermits(results);
|
List<Permission> entitlements = Permissions.allPermits(results);
|
||||||
|
|
||||||
if (entitlements.isEmpty()) {
|
if (entitlements.isEmpty()) {
|
||||||
asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
|
asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
|
||||||
|
.entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
|
||||||
|
.allowedOrigins(identity.getAccessToken())
|
||||||
|
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
||||||
} else {
|
} else {
|
||||||
AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
|
AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
|
||||||
asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
|
asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
|
||||||
|
@ -217,12 +220,14 @@ public class AuthorizationTokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
|
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
|
||||||
if (!Tokens.verifySignature(request.getTicket(), getRealm().getPublicKey())) {
|
String ticketString = request.getTicket();
|
||||||
|
|
||||||
|
if (ticketString == null || !Tokens.verifySignature(ticketString, getRealm().getPublicKey())) {
|
||||||
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
|
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PermissionTicket ticket = new JWSInput(request.getTicket()).readJsonContent(PermissionTicket.class);
|
PermissionTicket ticket = new JWSInput(ticketString).readJsonContent(PermissionTicket.class);
|
||||||
|
|
||||||
if (!ticket.isActive()) {
|
if (!ticket.isActive()) {
|
||||||
throw new ErrorResponseException("invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
|
throw new ErrorResponseException("invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
|
||||||
|
|
|
@ -80,8 +80,9 @@ public class EntitlementService {
|
||||||
this.authorization = authorization;
|
this.authorization = authorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("{resource_server_id}")
|
||||||
@OPTIONS
|
@OPTIONS
|
||||||
public Response authorizePreFlight() {
|
public Response authorizePreFlight(@PathParam("resource_server_id") String resourceServerId) {
|
||||||
return Cors.add(this.request, Response.ok()).auth().preflight().build();
|
return Cors.add(this.request, Response.ok()).auth().preflight().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +119,10 @@ public class EntitlementService {
|
||||||
List<Permission> entitlements = Permissions.allPermits(results);
|
List<Permission> entitlements = Permissions.allPermits(results);
|
||||||
|
|
||||||
if (entitlements.isEmpty()) {
|
if (entitlements.isEmpty()) {
|
||||||
asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
|
asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
|
||||||
|
.entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
|
||||||
|
.allowedOrigins(identity.getAccessToken())
|
||||||
|
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
||||||
} else {
|
} else {
|
||||||
asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
import org.keycloak.authorization.permission.ResourcePermission;
|
import org.keycloak.authorization.permission.ResourcePermission;
|
||||||
import org.keycloak.authorization.policy.evaluation.Result;
|
import org.keycloak.authorization.policy.evaluation.Result;
|
||||||
|
import org.keycloak.authorization.store.ResourceStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.representations.authorization.Permission;
|
import org.keycloak.representations.authorization.Permission;
|
||||||
|
|
||||||
|
@ -58,9 +59,10 @@ public final class Permissions {
|
||||||
public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) {
|
public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) {
|
||||||
List<ResourcePermission> permissions = new ArrayList<>();
|
List<ResourcePermission> permissions = new ArrayList<>();
|
||||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||||
|
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||||
|
|
||||||
storeFactory.getResourceStore().findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
|
resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
|
||||||
storeFactory.getResourceStore().findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
|
resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
|
||||||
|
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue