keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 58(+35 -23)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java 47(+12 -35)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java 1(+1 -0)
authz/client/src/main/java/org/keycloak/authorization/client/representation/ServerConfiguration.java 7(+7 -0)
authz/client/src/main/java/org/keycloak/authorization/client/resource/PolicyResource.java 187(+187 -0)
authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java 4(+4 -0)
authz/client/src/main/java/org/keycloak/authorization/client/util/HttpMethodAuthenticator.java 1(+1 -0)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java 2(+1 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java 8(+4 -4)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java 10(+5 -5)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java 8(+4 -4)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java 25(+14 -11)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java 2(+1 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java 2(+1 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java 2(+1 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java 318(+292 -26)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java 8(+4 -4)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java 6(+3 -3)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java 2(+1 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java 8(+5 -3)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java 7(+3 -4)
authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java 2(+1 -1)
core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java 12(+12 -0)
core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java 15(+15 -0)
core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java 10(+5 -5)
core/src/main/java/org/keycloak/representations/idm/authorization/UmaPermissionRepresentation.java 134(+134 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 14(+12 -2)
server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java 13(+13 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java 10(+10 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java 63(+54 -9)
server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java 2(+1 -1)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java 54(+44 -10)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 84(+46 -38)
services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java 2(+2 -0)
services/src/main/java/org/keycloak/authorization/protection/policy/UserManagedPermissionService.java 183(+183 -0)
services/src/main/java/org/keycloak/forms/account/freemarker/model/AuthorizationBean.java 67(+66 -1)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java 100(+89 -11)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java 8(+6 -2)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index c54191f..20f6c90 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -28,6 +28,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
@@ -165,7 +166,7 @@ public abstract class AbstractPolicyEnforcer {
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
}
- return hasValidClaims(actualPathConfig, httpFacade, authorization);
+ return hasValidClaims(actualPathConfig, permission, httpFacade, authorization);
}
}
} else {
@@ -187,35 +188,22 @@ public abstract class AbstractPolicyEnforcer {
return false;
}
- private boolean hasValidClaims(PathConfig actualPathConfig, OIDCHttpFacade httpFacade, Authorization authorization) {
- Map<String, Map<String, Object>> claimInformationPointConfig = actualPathConfig.getClaimInformationPointConfig();
+ private boolean hasValidClaims(PathConfig actualPathConfig, Permission permission, OIDCHttpFacade httpFacade, Authorization authorization) {
+ Map<String, Set<String>> grantedClaims = permission.getClaims();
- if (claimInformationPointConfig != null) {
- Map<String, List<String>> claims = new HashMap<>();
-
- for (Entry<String, Map<String, Object>> entry : claimInformationPointConfig.entrySet()) {
- ClaimInformationPointProviderFactory factory = policyEnforcer.getClaimInformationPointProviderFactories().get(entry.getKey());
+ if (grantedClaims != null) {
+ Map<String, List<String>> claims = resolveClaims(actualPathConfig, httpFacade);
- if (factory == null) {
- throw new RuntimeException("Could not find claim information provider with name [" + entry.getKey() + "]");
- }
-
- claims.putAll(factory.create(entry.getValue()).resolve(httpFacade));
+ if (claims.isEmpty()) {
+ return false;
}
- Map<String, List<String>> grantedClaims = authorization.getClaims();
+ for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
+ List<String> requestClaims = claims.get(entry.getKey());
- if (grantedClaims != null) {
- if (claims.isEmpty()) {
+ if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
return false;
}
- for (Entry<String, List<String>> entry : grantedClaims.entrySet()) {
- List<String> requestClaims = claims.get(entry.getKey());
-
- if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
- return false;
- }
- }
}
}
@@ -342,4 +330,28 @@ public abstract class AbstractPolicyEnforcer {
private PathConfig getPathConfig(Request request) {
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
}
+
+ protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, OIDCHttpFacade httpFacade) {
+ Map<String, List<String>> claims = getClaims(getEnforcerConfig().getClaimInformationPointConfig(), httpFacade);
+
+ claims.putAll(getClaims(pathConfig.getClaimInformationPointConfig(), httpFacade));
+
+ return claims;
+ }
+
+ private Map<String, List<String>> getClaims(Map<String, Map<String, Object>>claimInformationPointConfig, HttpFacade httpFacade) {
+ Map<String, List<String>> claims = new HashMap<>();
+
+ if (claimInformationPointConfig != null) {
+ for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
+ ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
+
+ if (factory != null) {
+ claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
+ }
+ }
+ }
+
+ return claims;
+ }
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index 4e87c90..67149bd 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -22,7 +22,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
@@ -87,19 +86,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
grantedPermissions.add(newPermission);
}
}
-
- Map<String, List<String>> newClaims = newAuthorization.getClaims();
-
- if (newClaims != null) {
- Map<String, List<String>> claims = authorization.getClaims();
-
- if (claims == null) {
- claims = new HashMap<>();
- authorization.setClaims(claims);
- }
-
- claims.putAll(newClaims);
- }
}
original.setAuthorization(authorization);
@@ -169,11 +155,11 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
String ticket = getPermissionTicket(pathConfig, methodConfig, getAuthzClient(), httpFacade);
authzRequest.setTicket(ticket);
} else {
- if (accessToken.getAuthorization() != null) {
+ if (isBearerAuthorization(httpFacade) || accessToken.getAuthorization() != null) {
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
}
- Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
+ Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
if (!claims.isEmpty()) {
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
@@ -186,7 +172,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
}
LOGGER.debug("Obtaining authorization for authenticated user.");
- AuthorizationResponse authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
+ AuthorizationResponse authzResponse;
+
+ if (isBearerAuthorization(httpFacade)) {
+ authzRequest.setSubjectToken(accessTokenString);
+ authzResponse = getAuthzClient().authorization().authorize(authzRequest);
+ } else {
+ authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
+ }
if (authzResponse != null) {
return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
@@ -200,7 +193,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
return null;
}
- private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, HttpFacade httpFacade) {
+ private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, OIDCHttpFacade httpFacade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
@@ -209,7 +202,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setResourceId(pathConfig.getId());
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
- Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
+ Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
if (!claims.isEmpty()) {
permissionRequest.setClaims(claims);
@@ -221,22 +214,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
return null;
}
- private Map<String, List<String>> getClaims(PathConfig pathConfig, HttpFacade httpFacade) {
- Map<String, List<String>> claims = new HashMap<>();
- Map<String, Map<String, Object>> claimInformationPointConfig = pathConfig.getClaimInformationPointConfig();
-
- if (claimInformationPointConfig != null) {
- for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
- ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
-
- if (factory != null) {
- claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
- }
- }
- }
- return claims;
- }
-
private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
index d23de4f..3bd4070 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
@@ -319,6 +319,7 @@ public class PolicyEnforcer {
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
+ config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
return config;
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/ServerConfiguration.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/ServerConfiguration.java
index f708e52..eabf085 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/ServerConfiguration.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/ServerConfiguration.java
@@ -102,6 +102,9 @@ public class ServerConfiguration {
@JsonProperty("permission_endpoint")
private String permissionEndpoint;
+
+ @JsonProperty("policy_endpoint")
+ private String policyEndpoint;
public String getIssuer() {
return issuer;
@@ -206,4 +209,8 @@ public class ServerConfiguration {
public String getPermissionEndpoint() {
return permissionEndpoint;
}
+
+ public String getPolicyEndpoint() {
+ return policyEndpoint;
+ }
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/PolicyResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/PolicyResource.java
new file mode 100644
index 0000000..46207eb
--- /dev/null
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/PolicyResource.java
@@ -0,0 +1,187 @@
+/*
+ * 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.authorization.client.resource;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.keycloak.authorization.client.representation.ServerConfiguration;
+import org.keycloak.authorization.client.util.Http;
+import org.keycloak.authorization.client.util.Throwables;
+import org.keycloak.authorization.client.util.TokenCallable;
+import org.keycloak.representations.idm.authorization.UmaPermissionRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * An entry point for managing user-managed permissions for a particular resource
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PolicyResource {
+
+ private String resourceId;
+ private final Http http;
+ private final ServerConfiguration serverConfiguration;
+ private final TokenCallable pat;
+
+ public PolicyResource(String resourceId, Http http, ServerConfiguration serverConfiguration, TokenCallable pat) {
+ this.resourceId = resourceId;
+ this.http = http;
+ this.serverConfiguration = serverConfiguration;
+ this.pat = pat;
+ }
+
+ /**
+ * Creates a new user-managed permission as represented by the given {@code permission}.
+ *
+ * @param permission the permission to create
+ * @return if successful, the permission created
+ */
+ public UmaPermissionRepresentation create(final UmaPermissionRepresentation permission) {
+ if (permission == null) {
+ throw new IllegalArgumentException("Permission must not be null");
+ }
+
+ Callable<UmaPermissionRepresentation> callable = new Callable<UmaPermissionRepresentation>() {
+ @Override
+ public UmaPermissionRepresentation call() throws Exception {
+ return http.<UmaPermissionRepresentation>post(serverConfiguration.getPolicyEndpoint() + "/" + resourceId)
+ .authorizationBearer(pat.call())
+ .json(JsonSerialization.writeValueAsBytes(permission))
+ .response().json(UmaPermissionRepresentation.class).execute();
+ }
+ };
+ try {
+ return callable.call();
+ } catch (Exception cause) {
+ return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error creating policy for resurce [" + resourceId + "]", cause);
+ }
+ }
+
+ /**
+ * Updates an existing user-managed permission
+ *
+ * @param permission the permission to update
+ */
+ public void update(final UmaPermissionRepresentation permission) {
+ if (permission == null) {
+ throw new IllegalArgumentException("Permission must not be null");
+ }
+
+ if (permission.getId() == null) {
+ throw new IllegalArgumentException("Permission id must not be null");
+ }
+
+ Callable<Void> callable = new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ http.<Void>put(serverConfiguration.getPolicyEndpoint() + "/"+ permission.getId())
+ .authorizationBearer(pat.call())
+ .json(JsonSerialization.writeValueAsBytes(permission)).execute();
+ return null;
+ }
+ };
+ try {
+ callable.call();
+ } catch (Exception cause) {
+ Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error updating policy for resurce [" + resourceId + "]", cause);
+ }
+ }
+
+ /**
+ * Deletes an existing user-managed permission
+ *
+ * @param id the permission id
+ */
+ public void delete(final String id) {
+ Callable<Void> callable = new Callable<Void>() {
+ @Override
+ public Void call() {
+ http.<UmaPermissionRepresentation>delete(serverConfiguration.getPolicyEndpoint() + "/" + id)
+ .authorizationBearer(pat.call())
+ .response().execute();
+ return null;
+ }
+ };
+ try {
+ callable.call();
+ } catch (Exception cause) {
+ Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error updating policy for resurce [" + resourceId + "]", cause);
+ }
+ }
+
+ /**
+ * Queries the server for permission matching the given parameters.
+ *
+ * @param id the permission id
+ * @param name the name of the permission
+ * @param scope the scope associated with the permission
+ * @param firstResult the position of the first resource to retrieve
+ * @param maxResult the maximum number of resources to retrieve
+ * @return the permissions matching the given parameters
+ */
+ public List<UmaPermissionRepresentation> find(final String name,
+ final String scope,
+ final Integer firstResult,
+ final Integer maxResult) {
+ Callable<List<UmaPermissionRepresentation>> callable = new Callable<List<UmaPermissionRepresentation>>() {
+ @Override
+ public List<UmaPermissionRepresentation> call() {
+ return http.<List<UmaPermissionRepresentation>>get(serverConfiguration.getPolicyEndpoint())
+ .authorizationBearer(pat.call())
+ .param("name", name)
+ .param("resource", resourceId)
+ .param("scope", scope)
+ .param("first", firstResult == null ? null : firstResult.toString())
+ .param("max", maxResult == null ? null : maxResult.toString())
+ .response().json(new TypeReference<List<UmaPermissionRepresentation>>(){}).execute();
+ }
+ };
+ try {
+ return callable.call();
+ } catch (Exception cause) {
+ return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error querying policies for resource [" + resourceId + "]", cause);
+ }
+ }
+
+ /**
+ * Queries the server for a permission with the given {@code id}.
+ *
+ * @param id the permission id
+ * @return the permission with the given id
+ */
+ public UmaPermissionRepresentation findById(final String id) {
+ if (id == null) {
+ throw new IllegalArgumentException("Permission id must not be null");
+ }
+
+ Callable<UmaPermissionRepresentation> callable = new Callable<UmaPermissionRepresentation>() {
+ @Override
+ public UmaPermissionRepresentation call() {
+ return http.<UmaPermissionRepresentation>get(serverConfiguration.getPolicyEndpoint() + "/" + id)
+ .authorizationBearer(pat.call())
+ .response().json(UmaPermissionRepresentation.class).execute();
+ }
+ };
+ try {
+ return callable.call();
+ } catch (Exception cause) {
+ return Throwables.retryAndWrapExceptionIfNecessary(callable, pat, "Error creating policy for resurce [" + resourceId + "]", cause);
+ }
+ }
+}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
index 03fa945..08030e1 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java
@@ -64,6 +64,10 @@ public class ProtectionResource {
return new PermissionResource(http, serverConfiguration, pat);
}
+ public PolicyResource policy(String resourceId) {
+ return new PolicyResource(resourceId, http, serverConfiguration, pat);
+ }
+
/**
* Introspects the given <code>rpt</code> using the token introspection endpoint.
*
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 33674fb..e0a8a99 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
@@ -83,6 +83,7 @@ public class HttpMethodAuthenticator<R> {
method.param("rpt", request.getRpt());
method.param("scope", request.getScope());
method.param("audience", request.getAudience());
+ method.param("subject_token", request.getSubjectToken());
if (permissions != null) {
for (ResourcePermission permission : permissions.getResources()) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java
index d6c4f07..476d182 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java
@@ -73,7 +73,7 @@ public class AggregatePolicyProviderFactory implements PolicyProviderFactory<Agg
}
@Override
- public AggregatePolicyRepresentation toRepresentation(Policy policy) {
+ public AggregatePolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
return new AggregatePolicyRepresentation();
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
index ffd92bf..54feda6 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
@@ -1,6 +1,6 @@
package org.keycloak.authorization.policy.provider.client;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
@@ -13,15 +13,15 @@ import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation
public class ClientPolicyProvider implements PolicyProvider {
- private final Function<Policy, ClientPolicyRepresentation> representationFunction;
+ private final BiFunction<Policy, AuthorizationProvider, ClientPolicyRepresentation> representationFunction;
- public ClientPolicyProvider(Function<Policy, ClientPolicyRepresentation> representationFunction) {
+ public ClientPolicyProvider(BiFunction<Policy, AuthorizationProvider, ClientPolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
- ClientPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
+ ClientPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy(), evaluation.getAuthorizationProvider());
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
EvaluationContext context = evaluation.getContext();
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
index c118c26..c48dacd 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
@@ -30,7 +30,7 @@ import org.keycloak.util.JsonSerialization;
public class ClientPolicyProviderFactory implements PolicyProviderFactory<ClientPolicyRepresentation> {
- private ClientPolicyProvider provider = new ClientPolicyProvider(policy -> toRepresentation(policy));
+ private ClientPolicyProvider provider = new ClientPolicyProvider(this::toRepresentation);
@Override
public String getName() {
@@ -48,7 +48,7 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory<Client
}
@Override
- public ClientPolicyRepresentation toRepresentation(Policy policy) {
+ public ClientPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
representation.setClients(new HashSet<>(Arrays.asList(getClients(policy))));
return representation;
@@ -75,12 +75,12 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory<Client
}
@Override
- public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
- ClientPolicyRepresentation userRep = toRepresentation(policy);
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
+ ClientPolicyRepresentation userRep = toRepresentation(policy, authorization);
Map<String, String> config = new HashMap<>();
try {
- RealmModel realm = authorizationProvider.getRealm();
+ RealmModel realm = authorization.getRealm();
config.put("clients", JsonSerialization.writeValueAsString(userRep.getClients().stream().map(id -> realm.getClientById(id).getClientId()).collect(Collectors.toList())));
} catch (IOException cause) {
throw new RuntimeException("Failed to export user policy [" + policy.getName() + "]", cause);
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
index 5f4fcd8..fa76a6d 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
@@ -19,7 +19,7 @@ package org.keycloak.authorization.policy.provider.group;
import static org.keycloak.models.utils.ModelToRepresentation.buildGroupPath;
import java.util.List;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.attribute.Attributes;
@@ -36,16 +36,16 @@ import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
*/
public class GroupPolicyProvider implements PolicyProvider {
- private final Function<Policy, GroupPolicyRepresentation> representationFunction;
+ private final BiFunction<Policy, AuthorizationProvider, GroupPolicyRepresentation> representationFunction;
- public GroupPolicyProvider(Function<Policy, GroupPolicyRepresentation> representationFunction) {
+ public GroupPolicyProvider(BiFunction<Policy, AuthorizationProvider, GroupPolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
- GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
+ GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy(), authorizationProvider);
RealmModel realm = authorizationProvider.getRealm();
Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
index f18e20d..6f45011 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
@@ -43,7 +43,7 @@ import org.keycloak.util.JsonSerialization;
*/
public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPolicyRepresentation> {
- private GroupPolicyProvider provider = new GroupPolicyProvider(policy -> toRepresentation(policy));
+ private GroupPolicyProvider provider = new GroupPolicyProvider(this::toRepresentation);
@Override
public String getId() {
@@ -71,7 +71,7 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPo
}
@Override
- public GroupPolicyRepresentation toRepresentation(Policy policy) {
+ public GroupPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
representation.setGroupsClaim(policy.getConfig().get("groupsClaim"));
@@ -109,19 +109,24 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPo
}
@Override
- public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
Map<String, String> config = new HashMap<>();
- GroupPolicyRepresentation groupPolicy = toRepresentation(policy);
+ GroupPolicyRepresentation groupPolicy = toRepresentation(policy, authorization);
Set<GroupPolicyRepresentation.GroupDefinition> groups = groupPolicy.getGroups();
for (GroupPolicyRepresentation.GroupDefinition definition: groups) {
- GroupModel group = authorizationProvider.getRealm().getGroupById(definition.getId());
+ GroupModel group = authorization.getRealm().getGroupById(definition.getId());
definition.setId(null);
definition.setPath(ModelToRepresentation.buildGroupPath(group));
}
try {
- config.put("groupsClaim", groupPolicy.getGroupsClaim());
+ String groupsClaim = groupPolicy.getGroupsClaim();
+
+ if (groupsClaim != null) {
+ config.put("groupsClaim", groupsClaim);
+ }
+
config.put("groups", JsonSerialization.writeValueAsString(groups));
} catch (IOException cause) {
throw new RuntimeException("Failed to export group policy [" + policy.getName() + "]", cause);
@@ -147,17 +152,15 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPo
}
private void updatePolicy(Policy policy, String groupsClaim, Set<GroupPolicyRepresentation.GroupDefinition> groups, AuthorizationProvider authorization) {
- if (groupsClaim == null) {
- throw new RuntimeException("Group claims property not provided");
- }
-
if (groups == null || groups.isEmpty()) {
throw new RuntimeException("You must provide at least one group");
}
Map<String, String> config = new HashMap<>(policy.getConfig());
- config.put("groupsClaim", groupsClaim);
+ if (groupsClaim != null) {
+ config.put("groupsClaim", groupsClaim);
+ }
List<GroupModel> topLevelGroups = authorization.getRealm().getTopLevelGroups();
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
index 1b2aa16..5de5db5 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
@@ -43,7 +43,7 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRe
}
@Override
- public JSPolicyRepresentation toRepresentation(Policy policy) {
+ public JSPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
JSPolicyRepresentation representation = new JSPolicyRepresentation();
representation.setCode(policy.getConfig().get("code"));
return representation;
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java
index 726f584..b8d982d 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java
@@ -56,7 +56,7 @@ public class ResourcePolicyProviderFactory implements PolicyProviderFactory<Reso
}
@Override
- public ResourcePermissionRepresentation toRepresentation(Policy policy) {
+ public ResourcePermissionRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
representation.setResourceType(policy.getConfig().get("defaultResourceType"));
return representation;
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java
index 7ac63da..52af952 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java
@@ -58,7 +58,7 @@ public class ScopePolicyProviderFactory implements PolicyProviderFactory<ScopePe
}
@Override
- public ScopePermissionRepresentation toRepresentation(Policy policy) {
+ public ScopePermissionRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
return new ScopePermissionRepresentation();
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java
index 3d11753..9b0ddec 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java
@@ -16,22 +16,40 @@
*/
package org.keycloak.authorization.policy.provider.permission;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation.GroupDefinition;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation.RoleDefinition;
+import org.keycloak.representations.idm.authorization.UmaPermissionRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public class UMAPolicyProviderFactory implements PolicyProviderFactory<PolicyRepresentation> {
+public class UMAPolicyProviderFactory implements PolicyProviderFactory<UmaPermissionRepresentation> {
private UMAPolicyProvider provider = new UMAPolicyProvider();
@@ -57,53 +75,249 @@ public class UMAPolicyProviderFactory implements PolicyProviderFactory<PolicyRep
@Override
public PolicyProvider create(KeycloakSession session) {
- return null;
+ return provider;
}
@Override
- public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
- verifyCircularReference(policy, new ArrayList<>());
+ public void onCreate(Policy policy, UmaPermissionRepresentation representation, AuthorizationProvider authorization) {
+ policy.setOwner(representation.getOwner());
+ PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
+ Set<String> roles = representation.getRoles();
+
+ if (roles != null) {
+ for (String role : roles) {
+ createRolePolicy(policy, policyStore, role, representation.getOwner());
+ }
+ }
+
+ Set<String> groups = representation.getGroups();
+
+ if (groups != null) {
+ for (String group : groups) {
+ createGroupPolicy(policy, policyStore, group, representation.getOwner());
+ }
+ }
+
+ Set<String> clients = representation.getClients();
+
+ if (clients != null) {
+ for (String client : clients) {
+ createClientPolicy(policy, policyStore, client, representation.getOwner());
+ }
+ }
+
+ String condition = representation.getCondition();
+
+ if (condition != null) {
+ createJSPolicy(policy, policyStore, condition, representation.getOwner());
+ }
}
@Override
- public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
- verifyCircularReference(policy, new ArrayList<>());
+ public void onUpdate(Policy policy, UmaPermissionRepresentation representation, AuthorizationProvider authorization) {
+ PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
+ Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+
+ for (Policy associatedPolicy : associatedPolicies) {
+ AbstractPolicyRepresentation associatedRep = ModelToRepresentation.toRepresentation(associatedPolicy, authorization, false, false);
+
+ if ("role".equals(associatedRep.getType())) {
+ RolePolicyRepresentation rep = RolePolicyRepresentation.class.cast(associatedRep);
+
+ rep.setRoles(new HashSet<>());
+
+ Set<String> updatedRoles = representation.getRoles();
+
+ if (updatedRoles != null) {
+ for (String role : updatedRoles) {
+ rep.addRole(role);
+ }
+ }
+
+ if (rep.getRoles().isEmpty()) {
+ policyStore.delete(associatedPolicy.getId());
+ } else {
+ RepresentationToModel.toModel(rep, authorization, associatedPolicy);
+ }
+ } else if ("js".equals(associatedRep.getType())) {
+ JSPolicyRepresentation rep = JSPolicyRepresentation.class.cast(associatedRep);
+
+ if (representation.getCondition() != null) {
+ rep.setCode(representation.getCondition());
+ RepresentationToModel.toModel(rep, authorization, associatedPolicy);
+ } else {
+ policyStore.delete(associatedPolicy.getId());
+ }
+ } else if ("group".equals(associatedRep.getType())) {
+ GroupPolicyRepresentation rep = GroupPolicyRepresentation.class.cast(associatedRep);
+
+ rep.setGroups(new HashSet<>());
+
+ Set<String> updatedGroups = representation.getGroups();
+
+ if (updatedGroups != null) {
+ for (String group : updatedGroups) {
+ rep.addGroupPath(group);
+ }
+ }
+
+ if (rep.getGroups().isEmpty()) {
+ policyStore.delete(associatedPolicy.getId());
+ } else {
+ RepresentationToModel.toModel(rep, authorization, associatedPolicy);
+ }
+ } else if ("client".equals(associatedRep.getType())) {
+ ClientPolicyRepresentation rep = ClientPolicyRepresentation.class.cast(associatedRep);
+
+ rep.setClients(new HashSet<>());
+
+ Set<String> updatedClients = representation.getClients();
+
+ if (updatedClients != null) {
+ for (String client : updatedClients) {
+ rep.addClient(client);
+ }
+ }
+
+ if (rep.getClients().isEmpty()) {
+ policyStore.delete(associatedPolicy.getId());
+ } else {
+ RepresentationToModel.toModel(rep, authorization, associatedPolicy);
+ }
+ }
+ }
+
+ Set<String> updatedRoles = representation.getRoles();
+
+ if (updatedRoles != null) {
+ boolean createPolicy = true;
+
+ for (Policy associatedPolicy : associatedPolicies) {
+ if ("role".equals(associatedPolicy.getType())) {
+ createPolicy = false;
+ }
+ }
+
+ if (createPolicy) {
+ for (String role : updatedRoles) {
+ createRolePolicy(policy, policyStore, role, policy.getOwner());
+ }
+ }
+ }
+
+ Set<String> updatedGroups = representation.getGroups();
+
+ if (updatedGroups != null) {
+ boolean createPolicy = true;
+
+ for (Policy associatedPolicy : associatedPolicies) {
+ if ("group".equals(associatedPolicy.getType())) {
+ createPolicy = false;
+ }
+ }
+
+ if (createPolicy) {
+ for (String group : updatedGroups) {
+ createGroupPolicy(policy, policyStore, group, policy.getOwner());
+ }
+ }
+ }
+
+ Set<String> updatedClients = representation.getClients();
+
+ if (updatedClients != null) {
+ boolean createPolicy = true;
+
+ for (Policy associatedPolicy : associatedPolicies) {
+ if ("client".equals(associatedPolicy.getType())) {
+ createPolicy = false;
+ }
+ }
+
+ if (createPolicy) {
+ for (String client : updatedClients) {
+ createClientPolicy(policy, policyStore, client, policy.getOwner());
+ }
+ }
+ }
+
+ String condition = representation.getCondition();
+
+ if (condition != null) {
+ boolean createPolicy = true;
+
+ for (Policy associatedPolicy : associatedPolicies) {
+ if ("js".equals(associatedPolicy.getType())) {
+ createPolicy = false;
+ }
+ }
+
+ if (createPolicy) {
+ createJSPolicy(policy, policyStore, condition, policy.getOwner());
+ }
+ }
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
- verifyCircularReference(policy, new ArrayList<>());
}
@Override
- public PolicyRepresentation toRepresentation(Policy policy) {
- return new PolicyRepresentation();
- }
+ public UmaPermissionRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
+ UmaPermissionRepresentation representation = new UmaPermissionRepresentation();
- @Override
- public Class<PolicyRepresentation> getRepresentationType() {
- return PolicyRepresentation.class;
- }
+ representation.setScopes(policy.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()));
+ representation.setOwner(policy.getOwner());
- private void verifyCircularReference(Policy policy, List<String> ids) {
- if (!policy.getType().equals("uma")) {
- return;
- }
+ for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
+ AbstractPolicyRepresentation associatedRep = ModelToRepresentation.toRepresentation(associatedPolicy, authorization, false, false);
+ RealmModel realm = authorization.getRealm();
- if (ids.contains(policy.getId())) {
- throw new RuntimeException("Circular reference found [" + policy.getName() + "].");
- }
+ if ("role".equals(associatedRep.getType())) {
+ RolePolicyRepresentation rep = RolePolicyRepresentation.class.cast(associatedRep);
- ids.add(policy.getId());
+ for (RoleDefinition definition : rep.getRoles()) {
+ RoleModel role = realm.getRoleById(definition.getId());
- for (Policy associated : policy.getAssociatedPolicies()) {
- verifyCircularReference(associated, ids);
+ if (role.isClientRole()) {
+ representation.addClientRole(ClientModel.class.cast(role.getContainer()).getClientId(),role.getName());
+ } else {
+ representation.addRole(role.getName());
+ }
+ }
+ } else if ("js".equals(associatedRep.getType())) {
+ JSPolicyRepresentation rep = JSPolicyRepresentation.class.cast(associatedRep);
+ representation.setCondition(rep.getCode());
+ } else if ("group".equals(associatedRep.getType())) {
+ GroupPolicyRepresentation rep = GroupPolicyRepresentation.class.cast(associatedRep);
+
+ for (GroupDefinition definition : rep.getGroups()) {
+ representation.addGroup(ModelToRepresentation.buildGroupPath(realm.getGroupById(definition.getId())));
+ }
+ } else if ("client".equals(associatedRep.getType())) {
+ ClientPolicyRepresentation rep = ClientPolicyRepresentation.class.cast(associatedRep);
+
+ for (String client : rep.getClients()) {
+ representation.addClient(realm.getClientById(client).getClientId());
+ }
+ }
}
+
+ return representation;
+ }
+
+ @Override
+ public Class<UmaPermissionRepresentation> getRepresentationType() {
+ return UmaPermissionRepresentation.class;
}
@Override
public void onRemove(Policy policy, AuthorizationProvider authorization) {
+ PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
+ for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
+ policyStore.delete(associatedPolicy.getId());
+ }
}
@Override
@@ -125,4 +339,56 @@ public class UMAPolicyProviderFactory implements PolicyProviderFactory<PolicyRep
public String getId() {
return "uma";
}
+
+ private void createJSPolicy(Policy policy, PolicyStore policyStore, String condition, String owner) {
+ JSPolicyRepresentation rep = new JSPolicyRepresentation();
+
+ rep.setName(KeycloakModelUtils.generateId());
+ rep.setCode(condition);
+
+ Policy associatedPolicy = policyStore.create(rep, policy.getResourceServer());
+
+ associatedPolicy.setOwner(owner);
+
+ policy.addAssociatedPolicy(associatedPolicy);
+ }
+
+ private void createClientPolicy(Policy policy, PolicyStore policyStore, String client, String owner) {
+ ClientPolicyRepresentation rep = new ClientPolicyRepresentation();
+
+ rep.setName(KeycloakModelUtils.generateId());
+ rep.addClient(client);
+
+ Policy associatedPolicy = policyStore.create(rep, policy.getResourceServer());
+
+ associatedPolicy.setOwner(owner);
+
+ policy.addAssociatedPolicy(associatedPolicy);
+ }
+
+ private void createGroupPolicy(Policy policy, PolicyStore policyStore, String group, String owner) {
+ GroupPolicyRepresentation rep = new GroupPolicyRepresentation();
+
+ rep.setName(KeycloakModelUtils.generateId());
+ rep.addGroupPath(group);
+
+ Policy associatedPolicy = policyStore.create(rep, policy.getResourceServer());
+
+ associatedPolicy.setOwner(owner);
+
+ policy.addAssociatedPolicy(associatedPolicy);
+ }
+
+ private void createRolePolicy(Policy policy, PolicyStore policyStore, String role, String owner) {
+ RolePolicyRepresentation rep = new RolePolicyRepresentation();
+
+ rep.setName(KeycloakModelUtils.generateId());
+ rep.addRole(role, false);
+
+ Policy associatedPolicy = policyStore.create(rep, policy.getResourceServer());
+
+ associatedPolicy.setOwner(owner);
+
+ policy.addAssociatedPolicy(associatedPolicy);
+ }
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
index 4a5718a..2435ba8 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
@@ -18,7 +18,7 @@
package org.keycloak.authorization.policy.provider.role;
import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.identity.Identity;
@@ -35,16 +35,16 @@ import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
*/
public class RolePolicyProvider implements PolicyProvider {
- private final Function<Policy, RolePolicyRepresentation> representationFunction;
+ private final BiFunction<Policy, AuthorizationProvider, RolePolicyRepresentation> representationFunction;
- public RolePolicyProvider(Function<Policy, RolePolicyRepresentation> representationFunction) {
+ public RolePolicyProvider(BiFunction<Policy, AuthorizationProvider, RolePolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
- Set<RolePolicyRepresentation.RoleDefinition> roleIds = representationFunction.apply(policy).getRoles();
+ Set<RolePolicyRepresentation.RoleDefinition> roleIds = representationFunction.apply(policy, evaluation.getAuthorizationProvider()).getRoles();
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
Identity identity = evaluation.getContext().getIdentity();
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
index bfd3e96..7565e24 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
@@ -52,7 +52,7 @@ import java.util.Set;
*/
public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePolicyRepresentation> {
- private RolePolicyProvider provider = new RolePolicyProvider(policy -> toRepresentation(policy));
+ private RolePolicyProvider provider = new RolePolicyProvider(this::toRepresentation);
@Override
public String getName() {
@@ -75,7 +75,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
}
@Override
- public RolePolicyRepresentation toRepresentation(Policy policy) {
+ public RolePolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
RolePolicyRepresentation representation = new RolePolicyRepresentation();
try {
@@ -114,7 +114,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
@Override
public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
Map<String, String> config = new HashMap<>();
- Set<RolePolicyRepresentation.RoleDefinition> roles = toRepresentation(policy).getRoles();
+ Set<RolePolicyRepresentation.RoleDefinition> roles = toRepresentation(policy, authorizationProvider).getRoles();
for (RolePolicyRepresentation.RoleDefinition roleDefinition : roles) {
RoleModel role = authorizationProvider.getRealm().getRoleById(roleDefinition.getId());
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java
index ffaf6ce..c1e3f41 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java
@@ -66,7 +66,7 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory<TimePoli
}
@Override
- public TimePolicyRepresentation toRepresentation(Policy policy) {
+ public TimePolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
TimePolicyRepresentation representation = new TimePolicyRepresentation();
Map<String, String> config = policy.getConfig();
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
index f891257..3e1f9b7 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
@@ -17,8 +17,10 @@
*/
package org.keycloak.authorization.policy.provider.user;
+import java.util.function.BiFunction;
import java.util.function.Function;
+import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
@@ -30,16 +32,16 @@ import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
*/
public class UserPolicyProvider implements PolicyProvider {
- private final Function<Policy, UserPolicyRepresentation> representationFunction;
+ private final BiFunction<Policy, AuthorizationProvider, UserPolicyRepresentation> representationFunction;
- public UserPolicyProvider(Function<Policy, UserPolicyRepresentation> representationFunction) {
+ public UserPolicyProvider(BiFunction<Policy, AuthorizationProvider, UserPolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
EvaluationContext context = evaluation.getContext();
- UserPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
+ UserPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy(), evaluation.getAuthorizationProvider());
for (String userId : representation.getUsers()) {
if (context.getIdentity().getId().equals(userId)) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
index 9ae349d..6f4c0bf 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
@@ -25,7 +25,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.stream.Collectors;
import org.keycloak.Config;
@@ -52,7 +51,7 @@ import org.keycloak.util.JsonSerialization;
*/
public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPolicyRepresentation> {
- private UserPolicyProvider provider = new UserPolicyProvider((Function<Policy, UserPolicyRepresentation>) policy -> toRepresentation(policy));
+ private UserPolicyProvider provider = new UserPolicyProvider(this::toRepresentation);
@Override
public String getName() {
@@ -75,7 +74,7 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPoli
}
@Override
- public UserPolicyRepresentation toRepresentation(Policy policy) {
+ public UserPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
UserPolicyRepresentation representation = new UserPolicyRepresentation();
try {
@@ -113,7 +112,7 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPoli
@Override
public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
- UserPolicyRepresentation userRep = toRepresentation(policy);
+ UserPolicyRepresentation userRep = toRepresentation(policy, authorizationProvider);
Map<String, String> config = new HashMap<>();
try {
diff --git a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java
index a879aad..800259a 100644
--- a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java
+++ b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java
@@ -51,7 +51,7 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory<RulePo
}
@Override
- public RulePolicyRepresentation toRepresentation(Policy policy) {
+ public RulePolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
RulePolicyRepresentation representation = new RulePolicyRepresentation();
representation.setArtifactGroupId(policy.getConfig().get("mavenArtifactGroupId"));
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 542712d..36778e1 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -88,9 +88,6 @@ public class AccessToken extends IDToken {
@JsonProperty("permissions")
private List<Permission> permissions;
- @JsonProperty("claims")
- private Map<String, List<String>> claims;
-
public List<Permission> getPermissions() {
return permissions;
}
@@ -98,14 +95,6 @@ public class AccessToken extends IDToken {
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
-
- public void setClaims(Map<String, List<String>> claims) {
- this.claims = claims;
- }
-
- public Map<String, List<String>> getClaims() {
- return claims;
- }
}
@JsonProperty("trusted-certs")
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index 81dc506..ae448d8 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -54,6 +54,10 @@ public class PolicyEnforcerConfig {
@JsonInclude(JsonInclude.Include.NON_NULL)
private UserManagedAccessConfig userManagedAccess;
+ @JsonProperty("claim-information-point")
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private Map<String, Map<String, Object>> claimInformationPointConfig;
+
public List<PathConfig> getPaths() {
return this.paths;
}
@@ -102,6 +106,14 @@ public class PolicyEnforcerConfig {
this.onDenyRedirectTo = onDenyRedirectTo;
}
+ public Map<String, Map<String, Object>> getClaimInformationPointConfig() {
+ return claimInformationPointConfig;
+ }
+
+ public void setClaimInformationPointConfig(Map<String, Map<String, Object>> config) {
+ this.claimInformationPointConfig = config;
+ }
+
public static class PathConfig {
public static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
index ada763c..8d7bbeb 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AbstractPolicyRepresentation.java
@@ -35,6 +35,7 @@ public class AbstractPolicyRepresentation {
private Set<String> scopes;
private Logic logic = Logic.POSITIVE;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
+ private String owner;
public String getId() {
return this.id;
@@ -135,6 +136,20 @@ public class AbstractPolicyRepresentation {
this.scopes.addAll(Arrays.asList(id));
}
+ public void removeScope(String scope) {
+ if (scopes != null) {
+ scopes.remove(scope);
+ }
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) return true;
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 14f1f3d..6682075 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
@@ -39,7 +39,7 @@ public class AuthorizationRequest {
private PermissionTicketToken permissions = new PermissionTicketToken();
private Metadata metadata;
private String audience;
- private String accessToken;
+ private String subjectToken;
private boolean submitRequest;
private Map<String, List<String>> claims;
@@ -123,12 +123,12 @@ public class AuthorizationRequest {
return audience;
}
- public void setAccessToken(String accessToken) {
- this.accessToken = accessToken;
+ public void setSubjectToken(String subjectToken) {
+ this.subjectToken = subjectToken;
}
- public String getAccessToken() {
- return accessToken;
+ public String getSubjectToken() {
+ return subjectToken;
}
public Map<String, List<String>> getClaims() {
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/UmaPermissionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/UmaPermissionRepresentation.java
new file mode 100644
index 0000000..a7bccea
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/UmaPermissionRepresentation.java
@@ -0,0 +1,134 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual 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.representations.idm.authorization;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:federico@martel-innovate.com">Federico M. Facca</a>
+ */
+public class UmaPermissionRepresentation extends AbstractPolicyRepresentation {
+
+ private String id;
+ private String description;
+ private Set<String> roles;
+ private Set<String> groups;
+ private Set<String> clients;
+ private String condition;
+
+ @Override
+ public String getType() {
+ return "uma";
+ }
+
+ public void setId(String id){
+ this.id = id;
+ }
+
+ public String getId(){
+ return id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setRoles(Set<String> roles) {
+ this.roles = roles;
+ }
+
+ public void addRole(String... role) {
+ if (roles == null) {
+ roles = new HashSet<>();
+ }
+
+ roles.addAll(Arrays.asList(role));
+ }
+
+ public void addClientRole(String clientId, String roleName) {
+ addRole(clientId + "/" + roleName);
+ }
+
+ public void removeRole(String role) {
+ if (roles != null) {
+ roles.remove(role);
+ }
+ }
+
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ public void setGroups(Set<String> groups) {
+ this.groups = groups;
+ }
+
+ public void addGroup(String... group) {
+ if (groups == null) {
+ groups = new HashSet<>();
+ }
+
+ groups.addAll(Arrays.asList(group));
+ }
+
+ public void removeGroup(String group) {
+ if (groups != null) {
+ groups.remove(group);
+ }
+ }
+
+ public Set<String> getGroups() {
+ return groups;
+ }
+
+ public void setClients(Set<String> clients) {
+ this.clients = clients;
+ }
+
+ public void addClient(String... client) {
+ if (clients == null) {
+ clients = new HashSet<>();
+ }
+
+ clients.addAll(Arrays.asList(client));
+ }
+
+ public void removeClient(String client) {
+ if (clients != null) {
+ clients.remove(client);
+ }
+ }
+
+ public Set<String> getClients() {
+ return clients;
+ }
+
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
index b08629a..06e9f25 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
@@ -454,7 +454,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
protected class ScopeCache implements ScopeStore {
@Override
public Scope create(String name, ResourceServer resourceServer) {
- Scope scope = getScopeStoreDelegate().create(name, resourceServer);
+ return create(null, name, resourceServer);
+ }
+
+ @Override
+ public Scope create(String id, String name, ResourceServer resourceServer) {
+ Scope scope = getScopeStoreDelegate().create(id, name, resourceServer);
registerScopeInvalidation(scope.getId(), scope.getName(), resourceServer.getId());
return scope;
}
@@ -538,7 +543,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
protected class ResourceCache implements ResourceStore {
@Override
public Resource create(String name, ResourceServer resourceServer, String owner) {
- Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
+ return create(null, name, resourceServer, owner);
+ }
+
+ @Override
+ public Resource create(String id, String name, ResourceServer resourceServer, String owner) {
+ Resource resource = getResourceStoreDelegate().create(id, name, resourceServer, owner);
Resource cached = findById(resource.getId(), resourceServer.getId());
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId(), resource.getOwner());
return cached;
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
index 3ab4da9..1e7f825 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
@@ -206,6 +206,8 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
predicates.add(builder.isNull(root.get("requester")));
} else if (PermissionTicket.POLICY_IS_NOT_NULL.equals(name)) {
predicates.add(builder.isNotNull(root.get("policy")));
+ } else if (PermissionTicket.POLICY.equals(name)) {
+ predicates.add(root.join("policy").get("id").in(value));
} else {
throw new RuntimeException("Unsupported filter [" + name + "]");
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
index 021f451..66a31d0 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
@@ -58,7 +58,12 @@ public class JPAPolicyStore implements PolicyStore {
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
PolicyEntity entity = new PolicyEntity();
- entity.setId(KeycloakModelUtils.generateId());
+ if (representation.getId() == null) {
+ entity.setId(KeycloakModelUtils.generateId());
+ } else {
+ entity.setId(representation.getId());
+ }
+
entity.setType(representation.getType());
entity.setName(representation.getName());
entity.setResourceServer(ResourceServerAdapter.toEntity(entityManager, resourceServer));
@@ -136,9 +141,9 @@ public class JPAPolicyStore implements PolicyStore {
attributes.forEach((name, value) -> {
if ("permission".equals(name)) {
if (Boolean.valueOf(value[0])) {
- predicates.add(root.get("type").in("resource", "scope"));
+ predicates.add(root.get("type").in("resource", "scope", "uma"));
} else {
- predicates.add(builder.not(root.get("type").in("resource", "scope")));
+ predicates.add(builder.not(root.get("type").in("resource", "scope", "uma")));
}
} else if ("id".equals(name)) {
predicates.add(root.get(name).in(value));
@@ -148,6 +153,8 @@ public class JPAPolicyStore implements PolicyStore {
predicates.add(builder.isNotNull(root.get("owner")));
} else if ("resource".equals(name)) {
predicates.add(root.join("resources").get("id").in(value));
+ } else if ("scope".equals(name)) {
+ predicates.add(root.join("scopes").get("id").in(value));
} else {
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index 7f6338d..38ddb80 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -53,9 +53,19 @@ public class JPAResourceStore implements ResourceStore {
@Override
public Resource create(String name, ResourceServer resourceServer, String owner) {
+ return create(null, name, resourceServer, owner);
+ }
+
+ @Override
+ public Resource create(String id, String name, ResourceServer resourceServer, String owner) {
ResourceEntity entity = new ResourceEntity();
- entity.setId(KeycloakModelUtils.generateId());
+ if (id == null) {
+ entity.setId(KeycloakModelUtils.generateId());
+ } else {
+ entity.setId(id);
+ }
+
entity.setName(name);
entity.setResourceServer(ResourceServerAdapter.toEntity(entityManager, resourceServer));
entity.setOwner(owner);
@@ -185,6 +195,8 @@ public class JPAResourceStore implements ResourceStore {
predicates.add(builder.equal(builder.lower(root.get(name)), value[0].toLowerCase()));
} else if ("uri_not_null".equals(name)) {
predicates.add(builder.isNotNull(root.get("uri")));
+ } else if ("owner".equals(name)) {
+ predicates.add(root.get(name).in(value));
} else {
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java
index befde65..c7debc3 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java
@@ -54,9 +54,19 @@ public class JPAScopeStore implements ScopeStore {
@Override
public Scope create(final String name, final ResourceServer resourceServer) {
+ return create(null, name, resourceServer);
+ }
+
+ @Override
+ public Scope create(String id, final String name, final ResourceServer resourceServer) {
ScopeEntity entity = new ScopeEntity();
- entity.setId(KeycloakModelUtils.generateId());
+ if (id == null) {
+ entity.setId(KeycloakModelUtils.generateId());
+ } else {
+ entity.setId(id);
+ }
+
entity.setName(name);
entity.setResourceServer(ResourceServerAdapter.toEntity(entityManager, resourceServer));
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
index 57f4c93..25888b8 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -233,6 +233,11 @@ public final class AuthorizationProvider implements Provider {
}
@Override
+ public Scope create(String id, String name, ResourceServer resourceServer) {
+ return delegate.create(id, name, resourceServer);
+ }
+
+ @Override
public void delete(String id) {
Scope scope = findById(id, null);
PermissionTicketStore ticketStore = AuthorizationProvider.this.getStoreFactory().getPermissionTicketStore();
@@ -412,6 +417,11 @@ public final class AuthorizationProvider implements Provider {
}
@Override
+ public Resource create(String id, String name, ResourceServer resourceServer, String owner) {
+ return delegate.create(id, name, resourceServer, owner);
+ }
+
+ @Override
public void delete(String id) {
Resource resource = findById(id, null);
StoreFactory storeFactory = AuthorizationProvider.this.getStoreFactory();
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
index 493bfc2..c011a11 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
@@ -30,6 +30,7 @@ public interface PermissionTicket {
String REQUESTER = "requester";
String REQUESTER_IS_NULL = "requester_is_null";
String POLICY_IS_NOT_NULL = "policy_is_not_null";
+ String POLICY = "policy";
/**
* Returns the unique identifier for this instance.
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
index efd3c27..3789281 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
@@ -22,11 +22,14 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
/**
@@ -42,9 +45,19 @@ public class ResourcePermission {
private Map<String, Set<String>> claims;
public ResourcePermission(Resource resource, List<Scope> scopes, ResourceServer resourceServer) {
+ this(resource, scopes, resourceServer, null);
+ }
+
+ public ResourcePermission(Resource resource, List<Scope> scopes, ResourceServer resourceServer, Map<String, ? extends Collection<String>> claims) {
this.resource = resource;
this.scopes = scopes;
this.resourceServer = resourceServer;
+ if (claims != null) {
+ this.claims = new HashMap<>();
+ for (Entry<String, ? extends Collection<String>> entry : claims.entrySet()) {
+ this.claims.computeIfAbsent(entry.getKey(), key -> new LinkedHashSet<>()).addAll(entry.getValue());
+ }
+ }
}
/**
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index 24127d6..dd1a81a 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -192,6 +192,16 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
}
}
+ if (policyResources.isEmpty() && scopes.isEmpty()) {
+ String defaultResourceType = policy.getConfig().get("defaultResourceType");
+
+ if (defaultResourceType == null) {
+ return false;
+ }
+
+ return defaultResourceType.equals(permission.getResource().getType());
+ }
+
return false;
}
}
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 d86888d..6ab219b 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
@@ -16,8 +16,8 @@
*/
package org.keycloak.authorization.policy.evaluation;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.PermissionTicket;
+import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
@@ -58,23 +59,67 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
}
@Override
+ public void onDecision(DefaultEvaluation evaluation) {
+ super.onDecision(evaluation);
+ removePermissionsIfGranted(evaluation);
+ }
+
+ /**
+ * Removes permissions (represented by {@code ticket}) granted by any user-managed policy so we don't create unnecessary permission tickets.
+ *
+ * @param evaluation the evaluation
+ */
+ private void removePermissionsIfGranted(DefaultEvaluation evaluation) {
+ if (Effect.PERMIT.equals(evaluation.getEffect())) {
+ Policy policy = evaluation.getParentPolicy();
+
+ if ("uma".equals(policy.getType())) {
+ ResourcePermission grantedPermission = evaluation.getPermission();
+ List<PermissionTicketToken.ResourcePermission> permissions = ticket.getResources();
+
+ Iterator<PermissionTicketToken.ResourcePermission> itPermissions = permissions.iterator();
+
+ while (itPermissions.hasNext()) {
+ PermissionTicketToken.ResourcePermission permission = itPermissions.next();
+
+ if (permission.getResourceId().equals(grantedPermission.getResource().getId())) {
+ Set<String> scopes = permission.getScopes();
+ Iterator<String> itScopes = scopes.iterator();
+
+ while (itScopes.hasNext()) {
+ Scope scope = authorization.getStoreFactory().getScopeStore().findByName(itScopes.next(), resourceServer.getId());
+ if (policy.getScopes().contains(scope)) {
+ itScopes.remove();
+ }
+ }
+
+ if (scopes.isEmpty()) {
+ itPermissions.remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
public void onComplete() {
super.onComplete();
if (request.isSubmitRequest()) {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
- List<PermissionTicketToken.ResourcePermission> resources = ticket.getResources();
+ List<PermissionTicketToken.ResourcePermission> permissions = ticket.getResources();
- if (resources != null) {
- for (PermissionTicketToken.ResourcePermission permission : resources) {
+ if (permissions != null) {
+ for (PermissionTicketToken.ResourcePermission permission : permissions) {
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
if (resource == null) {
resource = resourceStore.findByName(permission.getResourceId(), identity.getId(), resourceServer.getId());
}
- if (!resource.isOwnerManagedAccess() || resource.getOwner().equals(identity.getId()) || resource.getOwner().equals(resourceServer.getId())) {
+ if (resource == null || !resource.isOwnerManagedAccess() || resource.getOwner().equals(identity.getId()) || resource.getOwner().equals(resourceServer.getId())) {
continue;
}
@@ -91,9 +136,9 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
filters.put(PermissionTicket.REQUESTER, identity.getId());
filters.put(PermissionTicket.SCOPE_IS_NULL, Boolean.TRUE.toString());
- List<PermissionTicket> permissions = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
+ List<PermissionTicket> tickets = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
- if (permissions.isEmpty()) {
+ if (tickets.isEmpty()) {
authorization.getStoreFactory().getPermissionTicketStore().create(resource.getId(), null, identity.getId(), resource.getResourceServer());
}
} else {
@@ -112,9 +157,9 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
filters.put(PermissionTicket.REQUESTER, identity.getId());
filters.put(PermissionTicket.SCOPE, scope.getId());
- List<PermissionTicket> permissions = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
+ List<PermissionTicket> tickets = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
- if (permissions.isEmpty()) {
+ if (tickets.isEmpty()) {
authorization.getStoreFactory().getPermissionTicketStore().create(resource.getId(), scope.getId(), identity.getId(), resource.getResourceServer());
}
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
index 1d353fd..2d3ab76 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
@@ -40,7 +40,7 @@ public interface PolicyProviderFactory<R extends AbstractPolicyRepresentation> e
PolicyProvider create(AuthorizationProvider authorization);
- R toRepresentation(Policy policy);
+ R toRepresentation(Policy policy, AuthorizationProvider authorization);
Class<R> getRepresentationType();
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java
index fd6f85c..78d55db 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java
@@ -41,6 +41,17 @@ public interface ResourceStore {
Resource create(String name, ResourceServer resourceServer, String owner);
/**
+ * <p>Creates a {@link Resource} instance backed by this persistent storage implementation.
+ *
+ * @param id the id of this resource. It must be unique.
+ * @param name the name of this resource. It must be unique.
+ * @param resourceServer the resource server to where the given resource belongs to
+ * @param owner the owner of this resource or null if the resource server is the owner
+ * @return an instance backed by the underlying storage implementation
+ */
+ Resource create(String id, String name, ResourceServer resourceServer, String owner);
+
+ /**
* Removes a {@link Resource} instance, with the given {@code id} from the persistent storage.
*
* @param id the identifier of an existing resource instance
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java
index fa9e70d..011b6ab 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java
@@ -43,6 +43,18 @@ public interface ScopeStore {
Scope create(String name, ResourceServer resourceServer);
/**
+ * Creates a new {@link Scope} instance. The new instance is not necessarily persisted though, which may require
+ * a call to the {#save} method to actually make it persistent.
+ *
+ * @param id the id of the scope
+ * @param name the name of the scope
+ * @param resourceServer the resource server to which this scope belongs
+ *
+ * @return a new instance of {@link Scope}
+ */
+ Scope create(String id, String name, ResourceServer resourceServer);
+
+ /**
* Deletes a scope from the underlying persistence mechanism.
*
* @param id the id of the scope to delete
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index d35eb8d..9bd2f8e 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -17,6 +17,8 @@
package org.keycloak.models.utils;
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.IOException;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
@@ -24,7 +26,6 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
-import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
@@ -37,9 +38,11 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.*;
import org.keycloak.representations.idm.authorization.*;
import org.keycloak.storage.StorageId;
+import org.keycloak.util.JsonSerialization;
import java.util.*;
import java.util.stream.Collectors;
+import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -777,7 +780,7 @@ public class ModelToRepresentation {
}
} else {
try {
- representation = (R) providerFactory.toRepresentation(policy);
+ representation = (R) providerFactory.toRepresentation(policy, authorization);
} catch (Exception cause) {
throw new RuntimeException("Could not create policy [" + policy.getType() + "] representation", cause);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index fe449cd..99d3405 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -2381,7 +2381,7 @@ public class RepresentationToModel {
return existing;
}
- Resource model = resourceStore.create(resource.getName(), resourceServer, ownerId);
+ Resource model = resourceStore.create(resource.getId(), resource.getName(), resourceServer, ownerId);
model.setDisplayName(resource.getDisplayName());
model.setType(resource.getType());
@@ -2426,7 +2426,7 @@ public class RepresentationToModel {
return existing;
}
- Scope model = scopeStore.create(scope.getName(), resourceServer);
+ Scope model = scopeStore.create(scope.getId(), scope.getName(), resourceServer);
model.setDisplayName(scope.getDisplayName());
model.setIconUri(scope.getIconUri());
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 3ba29dd..3e3fa27 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -19,11 +19,11 @@ package org.keycloak.authorization.admin;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -34,11 +34,9 @@ import java.util.stream.Stream;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
-import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder;
@@ -49,7 +47,6 @@ 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.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.store.ScopeStore;
@@ -62,10 +59,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@@ -90,36 +87,34 @@ public class PolicyEvaluationService {
this.auth = auth;
}
- static class Decision extends DecisionResultCollector {
- Throwable error;
- List<Result> results;
-
- @Override
- protected void onComplete(List<Result> results) {
- this.results = results;
- }
-
- @Override
- public void onError(Throwable cause) {
- this.error = cause;
-
- }
- }
-
- public static <T> List<T> asList(T... a) {
- List<T> list = new LinkedList<T>();
- for (T t : a) list.add(t);
- return list;
- }
-
@POST
@Consumes("application/json")
@Produces("application/json")
- public Response evaluate(PolicyEvaluationRequest evaluationRequest) throws Throwable {
+ public Response evaluate(PolicyEvaluationRequest evaluationRequest) {
this.auth.realm().requireViewAuthorization();
CloseableKeycloakIdentity identity = createIdentity(evaluationRequest);
try {
- return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity)), resourceServer, authorization, identity)).build();
+ AuthorizationRequest request = new AuthorizationRequest();
+ Map<String, List<String>> claims = new HashMap<>();
+ Map<String, String> givenAttributes = evaluationRequest.getContext().get("attributes");
+
+ if (givenAttributes != null) {
+ givenAttributes.forEach((key, entryValue) -> {
+ if (entryValue != null) {
+ List<String> values = new ArrayList();
+
+ for (String value : entryValue.split(",")) {
+ values.add(value);
+ }
+
+ claims.put(key, values);
+ }
+ });
+ }
+
+ request.setClaims(claims);
+
+ return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity), request), resourceServer, authorization, identity)).build();
} catch (Exception e) {
throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
} finally {
@@ -127,8 +122,8 @@ public class PolicyEvaluationService {
}
}
- private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext) {
- return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate();
+ private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
+ return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate();
}
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
@@ -157,7 +152,7 @@ public class PolicyEvaluationService {
};
}
- private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
+ private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization, AuthorizationRequest request) {
List<ResourceRepresentation> resources = representation.getResources();
return resources.stream().flatMap((Function<ResourceRepresentation, Stream<ResourcePermission>>) resource -> {
StoreFactory storeFactory = authorization.getStoreFactory();
@@ -175,18 +170,18 @@ public class PolicyEvaluationService {
if (resource.getId() != null) {
Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId(), resourceServer.getId());
- return Permissions.createResourcePermissions(resourceModel, scopeNames, authorization).stream();
+ return Arrays.asList(Permissions.createResourcePermissions(resourceModel, scopeNames, authorization, request)).stream();
} else if (resource.getType() != null) {
- return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().flatMap(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization).stream());
+ return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().map(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization, request));
} else {
ScopeStore scopeStore = storeFactory.getScopeStore();
List<Scope> scopes = scopeNames.stream().map(scopeName -> scopeStore.findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList());
- List<ResourcePermission> collect = new ArrayList<ResourcePermission>();
+ List<ResourcePermission> collect = new ArrayList<>();
if (!scopes.isEmpty()) {
- collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList()));
+ collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, Arrays.asList(scope), resourceServer)).collect(Collectors.toList()));
} else {
- collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization));
+ collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization, request));
}
return collect.stream();
@@ -266,13 +261,6 @@ public class PolicyEvaluationService {
}
AccessToken.Access realmAccess = accessToken.getRealmAccess();
- Map<String, Object> claims = accessToken.getOtherClaims();
- Map<String, String> givenAttributes = representation.getContext().get("attributes");
-
- if (givenAttributes != null) {
- givenAttributes.forEach((key, value) -> claims.put(key, asList(value)));
- }
-
if (representation.getRoleIds() != null) {
representation.getRoleIds().forEach(roleName -> realmAccess.addRole(roleName));
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
index f4df4c4..c89c340 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
@@ -73,8 +73,10 @@ public class PolicyResourceService {
@Consumes("application/json")
@Produces("application/json")
@NoCache
- public Response update(@Context UriInfo uriInfo,String payload) {
- this.auth.realm().requireManageAuthorization();
+ public Response update(@Context UriInfo uriInfo, String payload) {
+ if (auth != null) {
+ this.auth.realm().requireManageAuthorization();
+ }
AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
@@ -94,7 +96,9 @@ public class PolicyResourceService {
@DELETE
public Response delete(@Context UriInfo uriInfo) {
- this.auth.realm().requireManageAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireManageAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -119,7 +123,9 @@ public class PolicyResourceService {
@Produces("application/json")
@NoCache
public Response findById() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -137,7 +143,9 @@ public class PolicyResourceService {
@Produces("application/json")
@NoCache
public Response getDependentPolicies() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -161,7 +169,9 @@ public class PolicyResourceService {
@Produces("application/json")
@NoCache
public Response getScopes() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -182,7 +192,9 @@ public class PolicyResourceService {
@Produces("application/json")
@NoCache
public Response getResources() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -203,7 +215,9 @@ public class PolicyResourceService {
@Produces("application/json")
@NoCache
public Response getAssociatedPolicies() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
if (policy == null) {
return Response.status(Status.NOT_FOUND).build();
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index cb87631..20a3e69 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -24,6 +24,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
@@ -43,10 +45,14 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@@ -103,7 +109,9 @@ public class PolicyService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public Response create(@Context UriInfo uriInfo, String payload) {
- this.auth.realm().requireManageAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireManageAuthorization();
+ }
AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
Policy policy = create(representation);
@@ -143,7 +151,10 @@ public class PolicyService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public Response findByName(@QueryParam("name") String name) {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
+
StoreFactory storeFactory = authorization.getStoreFactory();
if (name == null) {
@@ -168,9 +179,12 @@ public class PolicyService {
@QueryParam("resource") String resource,
@QueryParam("scope") String scope,
@QueryParam("permission") Boolean permission,
+ @QueryParam("owner") String owner,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
Map<String, String[]> search = new HashMap<>();
@@ -186,42 +200,56 @@ public class PolicyService {
search.put("type", new String[] {type});
}
+ if (owner != null && !"".equals(owner.trim())) {
+ search.put("owner", new String[] {owner});
+ }
+
StoreFactory storeFactory = authorization.getStoreFactory();
- PolicyStore policyStore = storeFactory.getPolicyStore();
- if (resource != null || scope != null) {
- List<Policy> policies = new ArrayList<>();
+ if (resource != null && !"".equals(resource.trim())) {
+ ResourceStore resourceStore = storeFactory.getResourceStore();
+ Resource resourceModel = resourceStore.findById(resource, resourceServer.getId());
- if (resource != null && !"".equals(resource.trim())) {
- HashMap<String, String[]> resourceSearch = new HashMap<>();
+ if (resourceModel == null) {
+ Map<String, String[]> resourceFilters = new HashMap<>();
- resourceSearch.put("name", new String[]{resource});
+ resourceFilters.put("name", new String[]{resource});
- storeFactory.getResourceStore().findByResourceServer(resourceSearch, resourceServer.getId(), -1, 1).forEach(resource1 -> {
- policies.addAll(policyStore.findByResource(resource1.getId(), resourceServer.getId()));
- if (resource1.getType() != null) {
- policies.addAll(policyStore.findByResourceType(resource1.getType(), resourceServer.getId()));
- }
- });
- }
+ if (owner != null) {
+ resourceFilters.put("owner", new String[]{owner});
+ }
- if (scope != null && !"".equals(scope.trim())) {
- HashMap<String, String[]> scopeSearch = new HashMap<>();
+ Set<String> resources = resourceStore.findByResourceServer(resourceFilters, resourceServer.getId(), -1, 1).stream().map(Resource::getId).collect(Collectors.toSet());
- scopeSearch.put("name", new String[]{scope});
+ if (resources.isEmpty()) {
+ return Response.ok().build();
+ }
- storeFactory.getScopeStore().findByResourceServer(scopeSearch, resourceServer.getId(), -1, 1).forEach(scope1 -> {
- policies.addAll(policyStore.findByScopeIds(Arrays.asList(scope1.getId()), resourceServer.getId()));
- });
+ search.put("resource", resources.toArray(new String[resources.size()]));
+ } else {
+ search.put("resource", new String[] {resourceModel.getId()});
}
+ }
- if (policies.isEmpty()) {
- return Response.ok(Collections.emptyList()).build();
- }
+ if (scope != null && !"".equals(scope.trim())) {
+ ScopeStore scopeStore = storeFactory.getScopeStore();
+ Scope scopeModel = scopeStore.findById(scope, resourceServer.getId());
- new ArrayList<>(policies).forEach(policy -> findAssociatedPolicies(policy, policies));
+ if (scopeModel == null) {
+ Map<String, String[]> scopeFilters = new HashMap<>();
- search.put("id", policies.stream().map(Policy::getId).toArray(String[]::new));
+ scopeFilters.put("name", new String[]{scope});
+
+ Set<String> scopes = scopeStore.findByResourceServer(scopeFilters, resourceServer.getId(), -1, 1).stream().map(Scope::getId).collect(Collectors.toSet());
+
+ if (scopes.isEmpty()) {
+ return Response.ok().build();
+ }
+
+ search.put("scope", scopes.toArray(new String[scopes.size()]));
+ } else {
+ search.put("scope", new String[] {scopeModel.getId()});
+ }
}
if (permission != null) {
@@ -249,7 +277,10 @@ public class PolicyService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public Response findPolicyProviders() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
+
return Response.ok(
authorization.getProviderFactories().stream()
.filter(factory -> !factory.isInternal())
@@ -268,7 +299,10 @@ public class PolicyService {
@Path("evaluate")
public PolicyEvaluationService getPolicyEvaluateResource() {
- this.auth.realm().requireViewAuthorization();
+ if (auth != null) {
+ this.auth.realm().requireViewAuthorization();
+ }
+
PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization, this.auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
index 2911877..6acc572 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
@@ -41,7 +41,7 @@ public class PolicyTypeService extends PolicyService {
private final String type;
- PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ public PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
super(resourceServer, authorization, auth, adminEvent);
this.type = type;
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
index b5023b5..3842a94 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
@@ -19,12 +19,15 @@ package org.keycloak.authorization.admin.representation;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.common.KeycloakIdentity;
+import org.keycloak.authorization.model.PermissionTicket;
+import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.DecisionEffect;
import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse;
@@ -169,33 +172,64 @@ public class PolicyEvaluationResponseBuilder {
return response;
}
- private static PolicyEvaluationResponse.PolicyResultRepresentation toRepresentation(Result.PolicyResult policy, AuthorizationProvider authorization) {
+ private static PolicyEvaluationResponse.PolicyResultRepresentation toRepresentation(Result.PolicyResult result, AuthorizationProvider authorization) {
PolicyEvaluationResponse.PolicyResultRepresentation policyResultRep = new PolicyEvaluationResponse.PolicyResultRepresentation();
PolicyRepresentation representation = new PolicyRepresentation();
+ Policy policy = result.getPolicy();
- representation.setId(policy.getPolicy().getId());
- representation.setName(policy.getPolicy().getName());
- representation.setType(policy.getPolicy().getType());
- representation.setDecisionStrategy(policy.getPolicy().getDecisionStrategy());
+ representation.setId(policy.getId());
+ representation.setName(policy.getName());
+ representation.setType(policy.getType());
+ representation.setDecisionStrategy(policy.getDecisionStrategy());
+ representation.setDescription(policy.getDescription());
- representation.setResources(policy.getPolicy().getResources().stream().map(resource -> resource.getName()).collect(Collectors.toSet()));
+ if ("uma".equals(representation.getType())) {
+ Map<String, String> filters = new HashMap<>();
- Set<String> scopeNames = policy.getPolicy().getScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet());
+ filters.put(PermissionTicket.POLICY, policy.getId());
+
+ List<PermissionTicket> tickets = authorization.getStoreFactory().getPermissionTicketStore().find(filters, policy.getResourceServer().getId(), -1, 1);
+
+ if (!tickets.isEmpty()) {
+ KeycloakSession keycloakSession = authorization.getKeycloakSession();
+ PermissionTicket ticket = tickets.get(0);
+ UserModel owner = keycloakSession.users().getUserById(ticket.getOwner(), authorization.getRealm());
+ UserModel requester = keycloakSession.users().getUserById(ticket.getRequester(), authorization.getRealm());
+
+ representation.setDescription("Resource owner (" + getUserEmailOrUserName(owner) + ") grants access to " + getUserEmailOrUserName(requester));
+ } else {
+ String description = representation.getDescription();
+
+ if (description != null) {
+ representation.setDescription(description + " (User-Managed Policy)");
+ } else {
+ representation.setDescription("User-Managed Policy");
+ }
+ }
+ }
+
+ representation.setResources(policy.getResources().stream().map(resource -> resource.getName()).collect(Collectors.toSet()));
+
+ Set<String> scopeNames = policy.getScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet());
representation.setScopes(scopeNames);
policyResultRep.setPolicy(representation);
- if (policy.getStatus() == Decision.Effect.DENY) {
+ if (result.getStatus() == Decision.Effect.DENY) {
policyResultRep.setStatus(DecisionEffect.DENY);
policyResultRep.setScopes(representation.getScopes());
} else {
policyResultRep.setStatus(DecisionEffect.PERMIT);
}
- policyResultRep.setAssociatedPolicies(policy.getAssociatedPolicies().stream().map(result -> toRepresentation(result, authorization)).collect(Collectors.toList()));
+ policyResultRep.setAssociatedPolicies(result.getAssociatedPolicies().stream().map(policy1 -> toRepresentation(policy1, authorization)).collect(Collectors.toList()));
return policyResultRep;
}
+
+ private static String getUserEmailOrUserName(UserModel user) {
+ return (user.getEmail() != null ? user.getEmail() : user.getUsername());
+ }
}
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 b791f7f..a0351e1 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -100,7 +100,7 @@ public class AuthorizationTokenService {
try {
Map claims = JsonSerialization.readValue(Base64Url.decode(authorizationRequest.getClaimToken()), Map.class);
authorizationRequest.setClaims(claims);
- return new KeycloakEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getAccessToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession());
+ return new KeycloakEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getSubjectToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession());
} catch (IOException cause) {
throw new RuntimeException("Failed to map claims from claim token [" + claimToken + "]", cause);
}
@@ -112,7 +112,7 @@ public class AuthorizationTokenService {
try {
KeycloakSession keycloakSession = authorization.getKeycloakSession();
RealmModel realm = authorization.getRealm();
- String accessToken = authorizationRequest.getAccessToken();
+ String accessToken = authorizationRequest.getSubjectToken();
if (accessToken == null) {
throw new RuntimeException("Claim token can not be null and must be a valid IDToken");
@@ -161,7 +161,7 @@ public class AuthorizationTokenService {
List<Result> evaluation;
if (ticket.getResources().isEmpty() && request.getRpt() == null) {
- evaluation = evaluateAllPermissions(resourceServer, evaluationContext, identity);
+ evaluation = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
} else if(!request.getPermissions().getResources().isEmpty()) {
evaluation = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
} else {
@@ -212,9 +212,9 @@ public class AuthorizationTokenService {
.evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results();
}
- private List<Result> evaluateAllPermissions(ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+ private List<Result> evaluateAllPermissions(AuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
return authorization.evaluators()
- .from(Permissions.all(resourceServer, identity, authorization), evaluationContext)
+ .from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext)
.evaluate();
}
@@ -235,7 +235,6 @@ public class AuthorizationTokenService {
Authorization authorization = new Authorization();
authorization.setPermissions(entitlements);
- authorization.setClaims(request.getClaims());
rpt.setAuthorization(authorization);
@@ -309,8 +308,9 @@ public class AuthorizationTokenService {
private List<ResourcePermission> createPermissions(PermissionTicketToken ticket, AuthorizationRequest request, ResourceServer resourceServer, KeycloakIdentity identity, AuthorizationProvider authorization) {
StoreFactory storeFactory = authorization.getStoreFactory();
- Map<String, Set<String>> permissionsToEvaluate = new LinkedHashMap<>();
+ Map<String, ResourcePermission> permissionsToEvaluate = new LinkedHashMap<>();
ResourceStore resourceStore = storeFactory.getResourceStore();
+ ScopeStore scopeStore = storeFactory.getScopeStore();
Metadata metadata = request.getMetadata();
Integer limit = metadata != null ? metadata.getLimit() : null;
@@ -359,31 +359,37 @@ public class AuthorizationTokenService {
requestedScopes.addAll(Arrays.asList(clientAdditionalScopes.split(" ")));
}
+ List<Scope> requestedScopesModel = requestedScopes.stream().map(s -> scopeStore.findByName(s, resourceServer.getId())).collect(Collectors.toList());
+
if (!existingResources.isEmpty()) {
for (Resource resource : existingResources) {
- Set<String> scopes = permissionsToEvaluate.get(resource.getId());
+ ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
- if (scopes == null) {
- scopes = new HashSet<>();
- permissionsToEvaluate.put(resource.getId(), scopes);
+ if (permission == null) {
+ permission = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
+ permissionsToEvaluate.put(resource.getId(), permission);
if (limit != null) {
limit--;
}
+ } else {
+ for (Scope scope : requestedScopesModel) {
+ if (!permission.getScopes().contains(scope)) {
+ permission.getScopes().add(scope);
+ }
+ }
}
-
- scopes.addAll(requestedScopes);
}
} else {
- List<Resource> resources = resourceStore.findByScope(new ArrayList<>(requestedScopes), ticket.getAudience()[0]);
+ List<Resource> resources = resourceStore.findByScope(new ArrayList<>(requestedScopes), resourceServer.getId());
for (Resource resource : resources) {
- permissionsToEvaluate.put(resource.getId(), requestedScopes);
+ permissionsToEvaluate.put(resource.getId(), Permissions.createResourcePermissions(resource, requestedScopes, authorization, request));
if (limit != null) {
limit--;
}
}
- permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", requestedScopes);
+ permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
}
}
@@ -409,28 +415,42 @@ public class AuthorizationTokenService {
List<Permission> permissions = authorizationData.getPermissions();
if (permissions != null) {
- for (Permission permission : permissions) {
+ for (Permission grantedPermission : permissions) {
if (limit != null && limit <= 0) {
break;
}
- Resource resourcePermission = resourceStore.findById(permission.getResourceId(), ticket.getAudience()[0]);
+ Resource resourcePermission = resourceStore.findById(grantedPermission.getResourceId(), ticket.getAudience()[0]);
if (resourcePermission != null) {
- Set<String> scopes = permissionsToEvaluate.get(resourcePermission.getId());
+ ResourcePermission permission = permissionsToEvaluate.get(resourcePermission.getId());
- if (scopes == null) {
- scopes = new HashSet<>();
- permissionsToEvaluate.put(resourcePermission.getId(), scopes);
+ 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());
+ }
+ }
+ }
}
- Set<String> scopePermission = permission.getScopes();
+ for (String scopeName : grantedPermission.getScopes()) {
+ Scope scope = scopeStore.findByName(scopeName, resourceServer.getId());
- if (scopePermission != null) {
- scopes.addAll(scopePermission);
+ if (scope != null) {
+ if (!permission.getScopes().contains(scope)) {
+ permission.getScopes().add(scope);
+ }
+ }
}
}
}
@@ -439,19 +459,7 @@ public class AuthorizationTokenService {
}
}
- ScopeStore scopeStore = storeFactory.getScopeStore();
-
- return permissionsToEvaluate.entrySet().stream()
- .flatMap((Function<Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
- String key = entry.getKey();
- if ("$KC_SCOPE_PERMISSION".equals(key)) {
- List<Scope> scopes = entry.getValue().stream().map(scopeName -> scopeStore.findByName(scopeName, resourceServer.getId())).filter(scope -> Objects.nonNull(scope)).collect(Collectors.toList());
- return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream();
- } else {
- Resource entryResource = resourceStore.findById(key, resourceServer.getId());
- return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream();
- }
- }).collect(Collectors.toList());
+ return new ArrayList<>(permissionsToEvaluate.values());
}
private PermissionTicketToken verifyPermissionTicket(AuthorizationRequest request) {
diff --git a/services/src/main/java/org/keycloak/authorization/config/UmaConfiguration.java b/services/src/main/java/org/keycloak/authorization/config/UmaConfiguration.java
index 67fb296..f622dc4 100644
--- a/services/src/main/java/org/keycloak/authorization/config/UmaConfiguration.java
+++ b/services/src/main/java/org/keycloak/authorization/config/UmaConfiguration.java
@@ -61,6 +61,7 @@ public class UmaConfiguration extends OIDCConfigurationRepresentation {
configuration.setPermissionEndpoint(uriBuilder.clone().path(RealmsResource.class).path(RealmsResource.class, "getAuthorizationService").path(AuthorizationService.class, "getProtectionService").path(ProtectionService.class, "permission").build(realm.getName()).toString());
configuration.setResourceRegistrationEndpoint(uriBuilder.clone().path(RealmsResource.class).path(RealmsResource.class, "getAuthorizationService").path(AuthorizationService.class, "getProtectionService").path(ProtectionService.class, "resource").build(realm.getName()).toString());
+ configuration.setPolicyEndpoint(uriBuilder.clone().path(RealmsResource.class).path(RealmsResource.class, "getAuthorizationService").path(AuthorizationService.class, "getProtectionService").path(ProtectionService.class, "policy").build(realm.getName()).toString());
return configuration;
}
@@ -70,6 +71,9 @@ public class UmaConfiguration extends OIDCConfigurationRepresentation {
@JsonProperty("permission_endpoint")
private String permissionEndpoint;
+
+ @JsonProperty("policy_endpoint")
+ private String policyEndpoint;
public String getResourceRegistrationEndpoint() {
return this.resourceRegistrationEndpoint;
@@ -86,4 +90,12 @@ public class UmaConfiguration extends OIDCConfigurationRepresentation {
void setPermissionEndpoint(String permissionEndpoint) {
this.permissionEndpoint = permissionEndpoint;
}
+
+ public String getPolicyEndpoint() {
+ return this.policyEndpoint;
+ }
+
+ void setPolicyEndpoint(String policyEndpoint) {
+ this.policyEndpoint = policyEndpoint;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
index 59ff1fd..fafbd71 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
@@ -125,6 +125,8 @@ public class PermissionTicketService {
throw new ErrorResponseException("invalid_permission", "Permission already exists", Response.Status.BAD_REQUEST);
PermissionTicket ticket = ticketStore.create(resource.getId(), scope.getId(), user.getId(), resourceServer);
+ if(representation.isGranted())
+ ticket.setGrantedTimestamp(java.lang.System.currentTimeMillis());
representation = ModelToRepresentation.toRepresentation(ticket, authorization);
return Response.ok(representation).build();
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/policy/UserManagedPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/policy/UserManagedPermissionService.java
new file mode 100644
index 0000000..d9663a4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/protection/policy/UserManagedPermissionService.java
@@ -0,0 +1,183 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.authorization.protection.policy;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.admin.PermissionService;
+import org.keycloak.authorization.admin.PolicyTypeResourceService;
+import org.keycloak.authorization.common.KeycloakIdentity;
+import org.keycloak.authorization.identity.Identity;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.representations.idm.authorization.UmaPermissionRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:federico@martel-innovate.com">Federico M. Facca</a>
+ */
+public class UserManagedPermissionService {
+
+ private final ResourceServer resourceServer;
+ private final Identity identity;
+ private final AuthorizationProvider authorization;
+ private final PermissionService delegate;
+
+ public UserManagedPermissionService(KeycloakIdentity identity, ResourceServer resourceServer, AuthorizationProvider authorization, AdminEventBuilder eventBuilder) {
+ this.identity = identity;
+ this.resourceServer = resourceServer;
+ this.authorization = authorization;
+ delegate = new PermissionService(resourceServer, authorization, null, eventBuilder);
+ ResteasyProviderFactory.getInstance().injectProperties(delegate);
+ }
+
+ @POST
+ @Path("{resourceId}")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response create(@Context UriInfo uriInfo, @PathParam("resourceId") String resourceId, UmaPermissionRepresentation representation) {
+ if (representation.getId() != null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Newly created uma policies should not have an id", Response.Status.BAD_REQUEST);
+ }
+
+ checkRequest(resourceId, representation);
+
+ representation.addResource(resourceId);
+ representation.setOwner(identity.getId());
+
+ return findById(delegate.create(representation).getId());
+ }
+
+ @Path("{policyId}")
+ @PUT
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response update(@Context UriInfo uriInfo, @PathParam("policyId") String policyId, String payload) {
+ UmaPermissionRepresentation representation;
+
+ try {
+ representation = JsonSerialization.readValue(payload, UmaPermissionRepresentation.class);
+ } catch (IOException e) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Failed to parse representation", Status.BAD_REQUEST);
+ }
+
+ checkRequest(getAssociatedResourceId(policyId), representation);
+
+ return PolicyTypeResourceService.class.cast(delegate.getResource(policyId)).update(uriInfo, payload);
+ }
+
+ @Path("{policyId}")
+ @DELETE
+ public Response delete(@Context UriInfo uriInfo, @PathParam("policyId") String policyId) {
+ checkRequest(getAssociatedResourceId(policyId), null);
+ PolicyTypeResourceService.class.cast(delegate.getResource(policyId)).delete(uriInfo);
+ return Response.noContent().build();
+ }
+
+ @Path("{policyId}")
+ @GET
+ @Produces("application/json")
+ public Response findById(@PathParam("policyId") String policyId) {
+ checkRequest(getAssociatedResourceId(policyId), null);
+ return PolicyTypeResourceService.class.cast(delegate.getResource(policyId)).findById();
+ }
+
+ @GET
+ @NoCache
+ @Produces("application/json")
+ public Response find(@QueryParam("name") String name,
+ @QueryParam("resource") String resource,
+ @QueryParam("scope") String scope,
+ @QueryParam("first") Integer firstResult,
+ @QueryParam("max") Integer maxResult) {
+ return delegate.findAll(null, name, "uma", resource, scope, true, identity.getId(), firstResult, maxResult);
+ }
+
+ private Policy getPolicy(@PathParam("policyId") String policyId) {
+ Policy existing = authorization.getStoreFactory().getPolicyStore().findById(policyId, resourceServer.getId());
+
+ if (existing == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Policy with [" + policyId + "] does not exist", Status.NOT_FOUND);
+ }
+
+ return existing;
+ }
+
+ private void checkRequest(String resourceId, UmaPermissionRepresentation representation) {
+ ResourceStore resourceStore = this.authorization.getStoreFactory().getResourceStore();
+ Resource resource = resourceStore.findById(resourceId, resourceServer.getId());
+
+ if (resource == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Resource [" + resourceId + "] cannot be found", Response.Status.BAD_REQUEST);
+ }
+
+ if (!resource.getOwner().equals(identity.getId())) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Only resource onwer can access policies for resource [" + resourceId + "]", Status.BAD_REQUEST);
+ }
+
+ if (!resource.isOwnerManagedAccess()) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Only resources with owner managed accessed can have policies", Status.BAD_REQUEST);
+ }
+
+ if (!resourceServer.isAllowRemoteResourceManagement()) {
+ throw new ErrorResponseException(OAuthErrorException.REQUEST_NOT_SUPPORTED, "Remote Resource Management not enabled on resource server [" + resourceServer.getId() + "]", Status.FORBIDDEN);
+ }
+
+ if (representation != null) {
+ Set<String> resourceScopes = resource.getScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet());
+ Set<String> scopes = representation.getScopes();
+
+ if (scopes == null || scopes.isEmpty()) {
+ scopes = resourceScopes;
+ representation.setScopes(scopes);
+ }
+
+ if (!resourceScopes.containsAll(scopes)) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Some of the scopes [" + scopes + "] are not valid for resource [" + resourceId + "]", Response.Status.BAD_REQUEST);
+ }
+ }
+ }
+
+ private String getAssociatedResourceId(String policyId) {
+ return getPolicy(policyId).getResources().iterator().next().getId();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index ce4dff6..f4bbba6 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -38,6 +38,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response.Status;
import org.keycloak.authorization.protection.permission.PermissionTicketService;
+import org.keycloak.authorization.protection.policy.UserManagedPermissionService;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -57,12 +58,7 @@ public class ProtectionService {
public Object resource() {
KeycloakIdentity identity = createIdentity(true);
ResourceServer resourceServer = getResourceServer(identity);
- RealmModel realm = authorization.getRealm();
- ClientModel client = realm.getClientById(resourceServer.getId());
- KeycloakSession keycloakSession = authorization.getKeycloakSession();
- UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
- AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection);
- ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null, adminEvent.realm(realm).authClient(client).authUser(serviceAccount));
+ ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null, createAdminEventBuilder(identity, resourceServer));
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
@@ -73,6 +69,15 @@ public class ProtectionService {
return resource;
}
+ private AdminEventBuilder createAdminEventBuilder(KeycloakIdentity identity, ResourceServer resourceServer) {
+ RealmModel realm = authorization.getRealm();
+ ClientModel client = realm.getClientById(resourceServer.getId());
+ KeycloakSession keycloakSession = authorization.getKeycloakSession();
+ UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
+ AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection);
+ return adminEvent.realm(realm).authClient(client).authUser(serviceAccount);
+ }
+
@Path("/permission")
public Object permission() {
KeycloakIdentity identity = createIdentity(false);
@@ -94,6 +99,17 @@ public class ProtectionService {
return resource;
}
+
+ @Path("/uma-policy")
+ public Object policy() {
+ KeycloakIdentity identity = createIdentity(false);
+
+ UserManagedPermissionService resource = new UserManagedPermissionService(identity, getResourceServer(identity), this.authorization, createAdminEventBuilder(identity, getResourceServer(identity)));
+
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+
+ return resource;
+ }
private KeycloakIdentity createIdentity(boolean checkProtectionScope) {
KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 180ec33..32219dd 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -44,6 +44,7 @@ import org.keycloak.authorization.policy.evaluation.Result;
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.AuthorizationRequest.Metadata;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ErrorResponseException;
@@ -68,23 +69,23 @@ public final class Permissions {
* @param authorization
* @return
*/
- public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) {
+ public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization, AuthorizationRequest request) {
List<ResourcePermission> permissions = new ArrayList<>();
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
// obtain all resources where owner is the resource server
- resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
+ resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
// obtain all resources where owner is the current user
- resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization)));
+ resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
// obtain all resources granted to the user via permission tickets (uma)
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId());
Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
for (PermissionTicket ticket : tickets) {
- userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer));
+ userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer, request.getClaims()));
}
permissions.addAll(userManagedPermissions.values());
@@ -92,8 +93,7 @@ public final class Permissions {
return permissions;
}
- public static List<ResourcePermission> createResourcePermissions(Resource resource, Set<String> requestedScopes, AuthorizationProvider authorization) {
- List<ResourcePermission> permissions = new ArrayList<>();
+ public static ResourcePermission createResourcePermissions(Resource resource, Set<String> requestedScopes, AuthorizationProvider authorization, AuthorizationRequest request) {
String type = resource.getType();
ResourceServer resourceServer = resource.getResourceServer();
List<Scope> scopes;
@@ -127,12 +127,11 @@ public final class Permissions {
return byName;
}).collect(Collectors.toList());
}
- permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer()));
- return permissions;
+ return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
}
- public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization) {
+ public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
List<ResourcePermission> permissions = new ArrayList<>();
String type = resource.getType();
ResourceServer resourceServer = resource.getResourceServer();
@@ -153,7 +152,7 @@ public final class Permissions {
});
}
- permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer()));
+ permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims()));
return permissions;
}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index a84765b..169b5bf 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -320,7 +320,6 @@ public class ExportUtils {
} else {
rep.getOwner().setId(null);
}
- rep.setId(null);
rep.getScopes().forEach(scopeRepresentation -> {
scopeRepresentation.setId(null);
scopeRepresentation.setIconUri(null);
@@ -335,10 +334,10 @@ public class ExportUtils {
PolicyStore policyStore = storeFactory.getPolicyStore();
policies.addAll(policyStore.findByResourceServer(settingsModel.getId())
- .stream().filter(policy -> !policy.getType().equals("resource") && !policy.getType().equals("scope"))
+ .stream().filter(policy -> !policy.getType().equals("resource") && !policy.getType().equals("scope") && policy.getOwner() == null)
.map(policy -> createPolicyRepresentation(authorization, policy)).collect(Collectors.toList()));
policies.addAll(policyStore.findByResourceServer(settingsModel.getId())
- .stream().filter(policy -> policy.getType().equals("resource") || policy.getType().equals("scope"))
+ .stream().filter(policy -> (policy.getType().equals("resource") || policy.getType().equals("scope") && policy.getOwner() == null))
.map(policy -> createPolicyRepresentation(authorization, policy)).collect(Collectors.toList()));
representation.setPolicies(policies);
@@ -346,7 +345,6 @@ public class ExportUtils {
List<ScopeRepresentation> scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel.getId()).stream().map(scope -> {
ScopeRepresentation rep = toRepresentation(scope);
- rep.setId(null);
rep.setPolicies(null);
rep.setResources(null);
@@ -362,8 +360,6 @@ public class ExportUtils {
try {
PolicyRepresentation rep = toRepresentation(policy, authorizationProvider, true, true);
- rep.setId(null);
-
Map<String, String> config = new HashMap<>(rep.getConfig());
rep.setConfig(config);
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AuthorizationBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AuthorizationBean.java
index 837a843..4ffe503 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AuthorizationBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AuthorizationBean.java
@@ -19,6 +19,7 @@ package org.keycloak.forms.account.freemarker.model;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -30,6 +31,7 @@ import javax.ws.rs.core.UriInfo;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
+import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PermissionTicketStore;
@@ -260,7 +262,7 @@ public class AuthorizationBean {
if (shares == null) {
Map<String, String> filters = new HashMap<>();
- filters.put(PermissionTicket.RESOURCE, resource.getId());
+ filters.put(PermissionTicket.RESOURCE, this.resource.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
shares = toPermissionRepresentation(findPermissions(filters));
@@ -269,6 +271,31 @@ public class AuthorizationBean {
return shares;
}
+ public Collection<ManagedPermissionBean> getPolicies() {
+ Map<String, String[]> filters = new HashMap<>();
+
+ filters.put("type", new String[] {"uma"});
+ filters.put("resource", new String[] {this.resource.getId()});
+ filters.put("owner", new String[] {getOwner().getId()});
+
+ List<Policy> policies = authorization.getStoreFactory().getPolicyStore().findByResourceServer(filters, getResourceServer().getId(), -1, -1);
+
+ if (policies.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return policies.stream()
+ .filter(policy -> {
+ Map<String, String> filters1 = new HashMap<>();
+
+ filters1.put(PermissionTicket.POLICY, policy.getId());
+
+ return authorization.getStoreFactory().getPermissionTicketStore().find(filters1, resourceServer.getId(), -1, 1)
+ .isEmpty();
+ })
+ .map(ManagedPermissionBean::new).collect(Collectors.toList());
+ }
+
public ResourceServerBean getResourceServer() {
return resourceServer;
}
@@ -326,6 +353,10 @@ public class AuthorizationBean {
this.clientModel = clientModel;
}
+ public String getId() {
+ return clientModel.getId();
+ }
+
public String getName() {
String name = clientModel.getName();
@@ -336,6 +367,10 @@ public class AuthorizationBean {
return clientModel.getClientId();
}
+ public String getClientId() {
+ return clientModel.getClientId();
+ }
+
public String getRedirectUri() {
Set<String> redirectUris = clientModel.getRedirectUris();
@@ -346,4 +381,34 @@ public class AuthorizationBean {
return redirectUris.iterator().next();
}
}
+
+ public class ManagedPermissionBean {
+
+ private final Policy policy;
+ private List<ManagedPermissionBean> policies;
+
+ public ManagedPermissionBean(Policy policy) {
+ this.policy = policy;
+ }
+
+ public String getId() {
+ return policy.getId();
+ }
+
+ public Collection<ScopeRepresentation> getScopes() {
+ return policy.getScopes().stream().map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
+ }
+
+ public String getDescription() {
+ return this.policy.getDescription();
+ }
+
+ public Collection<ManagedPermissionBean> getPolicies() {
+ if (this.policies == null) {
+ this.policies = policy.getAssociatedPolicies().stream().map(ManagedPermissionBean::new).collect(Collectors.toList());
+ }
+
+ return this.policies;
+ }
+ }
}
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 a7ba496..19fb4a2 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
@@ -1048,7 +1048,7 @@ public class TokenEndpoint {
authorizationRequest.setRpt(formParams.getFirst("rpt"));
authorizationRequest.setScope(formParams.getFirst("scope"));
authorizationRequest.setAudience(formParams.getFirst("audience"));
- authorizationRequest.setAccessToken(accessTokenString);
+ authorizationRequest.setSubjectToken(formParams.getFirst("subject_token") != null ? formParams.getFirst("subject_token") : accessTokenString);
String submitRequest = formParams.getFirst("submit_request");
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
index d316e34..1355614 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
@@ -20,9 +20,11 @@ import org.jboss.logging.Logger;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
+import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PermissionTicketStore;
+import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.Base64Url;
@@ -726,45 +728,90 @@ public class AccountFormService extends AbstractSecuredLocalService {
boolean isGrant = "grant".equals(action);
boolean isDeny = "deny".equals(action);
boolean isRevoke = "revoke".equals(action);
+ boolean isRevokePolicy = "revokePolicy".equals(action);
+ boolean isRevokePolicyAll = "revokePolicyAll".equals(action);
- Map<String, String> filters = new HashMap<>();
+ if (isRevokePolicy || isRevokePolicyAll) {
+ List<String> ids = new ArrayList(Arrays.asList(permissionId));
+ Iterator<String> iterator = ids.iterator();
+ PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore();
+ Policy policy = null;
- filters.put(PermissionTicket.RESOURCE, resource.getId());
- filters.put(PermissionTicket.REQUESTER, session.users().getUserByUsername(requester, realm).getId());
+ while (iterator.hasNext()) {
+ String id = iterator.next();
- if (isRevoke) {
- filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
- } else {
- filters.put(PermissionTicket.GRANTED, Boolean.FALSE.toString());
- }
+ if (!id.contains(":")) {
+ policy = policyStore.findById(id, client.getId());
+ iterator.remove();
+ break;
+ }
+ }
- List<PermissionTicket> tickets = ticketStore.find(filters, resource.getResourceServer().getId(), -1, -1);
- Iterator<PermissionTicket> iterator = tickets.iterator();
+ Set<Scope> scopesToKeep = new HashSet<>();
- while (iterator.hasNext()) {
- PermissionTicket ticket = iterator.next();
+ if (isRevokePolicyAll) {
+ for (Scope scope : policy.getScopes()) {
+ policy.removeScope(scope);
+ }
+ } else {
+ for (String id : ids) {
+ scopesToKeep.add(authorization.getStoreFactory().getScopeStore().findById(id.split(":")[1], client.getId()));
+ }
- if (isGrant) {
- if (permissionId != null && permissionId.length > 0 && !Arrays.asList(permissionId).contains(ticket.getId())) {
- continue;
+ for (Scope scope : policy.getScopes()) {
+ if (!scopesToKeep.contains(scope)) {
+ policy.removeScope(scope);
+ }
}
}
- if (isGrant && !ticket.isGranted()) {
- ticket.setGrantedTimestamp(System.currentTimeMillis());
- iterator.remove();
- } else if (isDeny || isRevoke) {
- if (permissionId != null && permissionId.length > 0 && Arrays.asList(permissionId).contains(ticket.getId())) {
+ if (policy.getScopes().isEmpty()) {
+ for (Policy associated : policy.getAssociatedPolicies()) {
+ policyStore.delete(associated.getId());
+ }
+
+ policyStore.delete(policy.getId());
+ }
+ } else {
+ Map<String, String> filters = new HashMap<>();
+
+ filters.put(PermissionTicket.RESOURCE, resource.getId());
+ filters.put(PermissionTicket.REQUESTER, session.users().getUserByUsername(requester, realm).getId());
+
+ if (isRevoke) {
+ filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
+ } else {
+ filters.put(PermissionTicket.GRANTED, Boolean.FALSE.toString());
+ }
+
+ List<PermissionTicket> tickets = ticketStore.find(filters, resource.getResourceServer().getId(), -1, -1);
+ Iterator<PermissionTicket> iterator = tickets.iterator();
+
+ while (iterator.hasNext()) {
+ PermissionTicket ticket = iterator.next();
+
+ if (isGrant) {
+ if (permissionId != null && permissionId.length > 0 && !Arrays.asList(permissionId).contains(ticket.getId())) {
+ continue;
+ }
+ }
+
+ if (isGrant && !ticket.isGranted()) {
+ ticket.setGrantedTimestamp(System.currentTimeMillis());
iterator.remove();
+ } else if (isDeny || isRevoke) {
+ if (permissionId != null && permissionId.length > 0 && Arrays.asList(permissionId).contains(ticket.getId())) {
+ iterator.remove();
+ }
}
}
- }
- for (PermissionTicket ticket : tickets) {
- ticketStore.delete(ticket.getId());
+ for (PermissionTicket ticket : tickets) {
+ ticketStore.delete(ticket.getId());
+ }
}
- if (isRevoke) {
+ if (isRevoke || isRevokePolicy || isRevokePolicyAll) {
return forwardToPage("resource-detail", AccountPages.RESOURCE_DETAIL);
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java
index 4da1695..b450b76 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java
@@ -50,7 +50,7 @@ public class TestPolicyProviderFactory implements PolicyProviderFactory {
}
@Override
- public AbstractPolicyRepresentation toRepresentation(Policy policy) {
+ public AbstractPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) {
return new PolicyRepresentation();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
index 81f2ffd..4713239 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.admin.client.authorization;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -38,6 +39,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OIDCHttpFacade;
@@ -59,12 +61,14 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
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.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.RolesBuilder;
@@ -106,6 +110,10 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
+ .client(ClientBuilder.create().clientId("public-client-test")
+ .publicClient()
+ .redirectUris("http://localhost:8180/auth/realms/master/app/auth/*")
+ .directAccessGrants())
.build());
}
@@ -125,7 +133,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
headers.put("Authorization", Arrays.asList("Bearer " + token));
- AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
assertFalse(context.isGranted());
AuthorizationRequest request = new AuthorizationRequest();
@@ -137,22 +145,22 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
assertNotNull(token);
- context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
assertTrue(context.isGranted());
parameters.put("withdrawal.amount", Arrays.asList("200"));
- context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
assertFalse(context.isGranted());
parameters.put("withdrawal.amount", Arrays.asList("50"));
- context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
assertTrue(context.isGranted());
parameters.put("withdrawal.amount", Arrays.asList("10"));
- context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
request = new AuthorizationRequest();
@@ -161,8 +169,23 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
response = authzClient.authorization("marta", "password").authorize(request);
token = response.getToken();
- context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "POST", token, headers, parameters));
assertTrue(context.isGranted());
+
+ request = new AuthorizationRequest();
+
+ request.setTicket(extractTicket(headers));
+
+ response = authzClient.authorization("marta", "password").authorize(request);
+ token = response.getToken();
+
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", "GET", token, headers, parameters));
+ assertTrue(context.isGranted());
+
+ assertEquals(1, context.getPermissions().size());
+ Permission permission = context.getPermissions().get(0);
+
+ assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
}
@Test
@@ -184,6 +207,9 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
assertTrue(context.isGranted());
+ assertEquals(1, context.getPermissions().size());
+ Permission permission = context.getPermissions().get(0);
+ assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
parameters.put("withdrawal.amount", Arrays.asList("200"));
@@ -200,6 +226,10 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
assertTrue(context.isGranted());
+
+ assertEquals(1, context.getPermissions().size());
+ permission = context.getPermissions().get(0);
+ assertEquals(parameters.get("withdrawal.amount").get(0), permission.getClaims().get("withdrawal.amount").iterator().next());
}
@Test
@@ -241,6 +271,50 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
assertTrue(context.isGranted());
}
+ @Test
+ public void testEnforceEntitlementAccessWithClaimsWithBearerTokenFromPublicClient() {
+ initAuthorizationSettings(getClientResource("resource-server-test"));
+
+ KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+ PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+ HashMap<String, List<String>> headers = new HashMap<>();
+ HashMap<String, List<String>> parameters = new HashMap<>();
+
+ oauth.realm(REALM_NAME);
+ oauth.clientId("public-client-test");
+ oauth.doLogin("marta", "password");
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
+ String token = response.getAccessToken();
+
+ headers.put("Authorization", Arrays.asList("Bearer " + token));
+
+ AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ assertFalse(context.isGranted());
+
+ parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ assertTrue(context.isGranted());
+
+ parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ assertFalse(context.isGranted());
+
+ parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+ assertTrue(context.isGranted());
+
+ parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+ context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+ assertTrue(context.isGranted());
+ }
+
private String extractTicket(HashMap<String, List<String>> headers) {
List<String> wwwAuthenticateHeader = headers.get("WWW-Authenticate");
@@ -306,7 +380,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
return clients.get(representation.getId());
}
- private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+ private OIDCHttpFacade createHttpFacade(String path, String method, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
return new OIDCHttpFacade() {
Request request;
Response response;
@@ -325,7 +399,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
@Override
public Request getRequest() {
if (request == null) {
- request = createHttpRequest(path, headers, parameters, requestBody);
+ request = createHttpRequest(path, method, headers, parameters, requestBody);
}
return request;
}
@@ -346,7 +420,11 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
}
private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
- return createHttpFacade(path, token, headers, parameters, null);
+ return createHttpFacade(path, null, token, headers, parameters, null);
+ }
+
+ private OIDCHttpFacade createHttpFacade(String path, String method, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
+ return createHttpFacade(path, method, token, headers, parameters, null);
}
private Response createHttpResponse(Map<String, List<String>> headers) {
@@ -401,14 +479,14 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
};
}
- private Request createHttpRequest(String path, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+ private Request createHttpRequest(String path, String method, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
return new Request() {
private InputStream inputStream;
@Override
public String getMethod() {
- return "GET";
+ return method == null ? "GET" : method;
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
index 237d5e3..c5edbcc 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
@@ -67,13 +67,17 @@ public abstract class AbstractResourceServerTest extends AbstractKeycloakTest {
.user(UserBuilder.create().username("marta").password("password")
.addRoles("uma_authorization", "uma_protection")
.role("resource-server-test", "uma_protection"))
+ .user(UserBuilder.create().username("alice").password("password")
+ .addRoles("uma_authorization", "uma_protection")
+ .role("resource-server-test", "uma_protection"))
.user(UserBuilder.create().username("kolo").password("password"))
.client(ClientBuilder.create().clientId("resource-server-test")
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
- .directAccessGrants())
+ .directAccessGrants()
+ .serviceAccountsEnabled(true))
.client(ClientBuilder.create().clientId("test-app")
.redirectUris("http://localhost:8180/auth/realms/master/app/auth")
.publicClient())
@@ -155,7 +159,7 @@ public abstract class AbstractResourceServerTest extends AbstractKeycloakTest {
return authorization.authorize(authorizationRequest);
}
- protected RealmResource getRealm() throws Exception {
+ protected RealmResource getRealm() {
return adminClient.realm("authz-test");
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedPermissionServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedPermissionServiceTest.java
new file mode 100644
index 0000000..4abe759
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedPermissionServiceTest.java
@@ -0,0 +1,561 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.NotFoundException;
+
+import org.junit.Test;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.resource.AuthorizationResource;
+import org.keycloak.authorization.client.resource.PolicyResource;
+import org.keycloak.authorization.client.resource.ProtectionResource;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.AuthorizationResponse;
+import org.keycloak.representations.idm.authorization.PermissionRequest;
+import org.keycloak.representations.idm.authorization.PermissionResponse;
+import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.UmaPermissionRepresentation;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.GroupBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class UserManagedPermissionServiceTest extends AbstractResourceServerTest {
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ testRealms.add(RealmBuilder.create().name(REALM_NAME)
+ .roles(RolesBuilder.create()
+ .realmRole(RoleBuilder.create().name("uma_authorization").build())
+ .realmRole(RoleBuilder.create().name("uma_protection").build())
+ .realmRole(RoleBuilder.create().name("role_a").build())
+ .realmRole(RoleBuilder.create().name("role_b").build())
+ .realmRole(RoleBuilder.create().name("role_c").build())
+ .realmRole(RoleBuilder.create().name("role_d").build())
+ )
+ .group(GroupBuilder.create().name("group_a")
+ .subGroups(Arrays.asList(GroupBuilder.create().name("group_b").build()))
+ .build())
+ .group(GroupBuilder.create().name("group_c").build())
+ .user(UserBuilder.create().username("marta").password("password")
+ .addRoles("uma_authorization", "uma_protection")
+ .role("resource-server-test", "uma_protection"))
+ .user(UserBuilder.create().username("alice").password("password")
+ .addRoles("uma_authorization", "uma_protection")
+ .role("resource-server-test", "uma_protection"))
+ .user(UserBuilder.create().username("kolo").password("password")
+ .addRoles("role_a")
+ .addGroups("group_a"))
+ .client(ClientBuilder.create().clientId("resource-server-test")
+ .secret("secret")
+ .authorizationServicesEnabled(true)
+ .redirectUris("http://localhost/resource-server-test")
+ .defaultRoles("uma_protection")
+ .directAccessGrants()
+ .serviceAccountsEnabled(true))
+ .client(ClientBuilder.create().clientId("client-a")
+ .redirectUris("http://localhost/resource-server-test")
+ .publicClient())
+ .build());
+ }
+
+ @Test
+ public void testCreate() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName("Resource A");
+ resource.setOwnerManagedAccess(true);
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ resource = getAuthzClient().protection().resource().create(resource);
+
+ UmaPermissionRepresentation newPermission = new UmaPermissionRepresentation();
+
+ newPermission.setName("Custom User-Managed Permission");
+ newPermission.setDescription("Users from specific roles are allowed to access");
+ newPermission.addScope("Scope A", "Scope B", "Scope C");
+ newPermission.addRole("role_a", "role_b", "role_c", "role_d");
+ newPermission.addGroup("/group_a", "/group_a/group_b", "/group_c");
+ newPermission.addClient("client-a", "resource-server-test");
+ newPermission.setCondition("$evaluation.grant()");
+
+ ProtectionResource protection = getAuthzClient().protection("marta", "password");
+
+ UmaPermissionRepresentation permission = protection.policy(resource.getId()).create(newPermission);
+
+ assertEquals(newPermission.getName(), permission.getName());
+ assertEquals(newPermission.getDescription(), permission.getDescription());
+ assertTrue(permission.getScopes().containsAll(newPermission.getScopes()));
+ assertTrue(permission.getRoles().containsAll(newPermission.getRoles()));
+ assertTrue(permission.getGroups().containsAll(newPermission.getGroups()));
+ assertTrue(permission.getClients().containsAll(newPermission.getClients()));
+ assertEquals(newPermission.getCondition(), permission.getCondition());
+ }
+
+ @Test
+ public void testUpdate() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName("Resource A");
+ resource.setOwnerManagedAccess(true);
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ resource = getAuthzClient().protection().resource().create(resource);
+
+ UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
+
+ permission.setName("Custom User-Managed Permission");
+ permission.setDescription("Users from specific roles are allowed to access");
+ permission.addScope("Scope A");
+ permission.addRole("role_a");
+
+ ProtectionResource protection = getAuthzClient().protection("marta", "password");
+
+ permission = protection.policy(resource.getId()).create(permission);
+
+ assertEquals(1, getAssociatedPolicies(permission).size());
+
+ permission.setName("Changed");
+ permission.setDescription("Changed");
+
+ protection.policy(resource.getId()).update(permission);
+
+ UmaPermissionRepresentation updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertEquals(permission.getName(), updated.getName());
+ assertEquals(permission.getDescription(), updated.getDescription());
+
+ permission.removeRole("role_a");
+ permission.addRole("role_b", "role_c");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(1, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getRoles().containsAll(updated.getRoles()));
+
+ permission.addRole("role_d");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(1, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getRoles().containsAll(updated.getRoles()));
+
+ permission.addGroup("/group_a/group_b");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(2, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getGroups().containsAll(updated.getGroups()));
+
+ permission.addGroup("/group_a");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(2, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getGroups().containsAll(updated.getGroups()));
+
+ permission.removeGroup("/group_a/group_b");
+ permission.addGroup("/group_c");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(2, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getGroups().containsAll(updated.getGroups()));
+
+ permission.addClient("client-a");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(3, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getClients().containsAll(updated.getClients()));
+
+ permission.addClient("resource-server-test");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(3, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getClients().containsAll(updated.getClients()));
+
+ permission.removeClient("client-a");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(3, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertTrue(permission.getClients().containsAll(updated.getClients()));
+
+ permission.setCondition("$evaluation.grant()");
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(4, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertEquals(permission.getCondition(), updated.getCondition());
+
+ permission.setCondition(null);
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(3, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertEquals(permission.getCondition(), updated.getCondition());
+
+ permission.setRoles(null);
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(2, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertEquals(permission.getRoles(), updated.getRoles());
+
+ permission.setClients(null);
+
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(1, getAssociatedPolicies(permission).size());
+ updated = protection.policy(resource.getId()).findById(permission.getId());
+
+ assertEquals(permission.getClients(), updated.getClients());
+
+ permission.setGroups(null);
+
+ try {
+ protection.policy(resource.getId()).update(permission);
+ assertEquals(1, getAssociatedPolicies(permission).size());
+ fail("Permission must be removed because the last associated policy was removed");
+ } catch (NotFoundException ignore) {
+
+ } catch (Exception e) {
+ fail("Expected not found");
+ }
+ }
+
+ @Test
+ public void testUserManagedPermission() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName("Resource A");
+ resource.setOwnerManagedAccess(true);
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ resource = getAuthzClient().protection().resource().create(resource);
+
+ UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
+
+ permission.setName("Custom User-Managed Permission");
+ permission.setDescription("Users from specific roles are allowed to access");
+ permission.addScope("Scope A");
+ permission.addRole("role_a");
+
+ ProtectionResource protection = getAuthzClient().protection("marta", "password");
+
+ permission = protection.policy(resource.getId()).create(permission);
+
+ AuthorizationResource authorization = getAuthzClient().authorization("kolo", "password");
+
+ AuthorizationRequest request = new AuthorizationRequest();
+
+ request.addPermission(resource.getId(), "Scope A");
+
+ AuthorizationResponse authzResponse = authorization.authorize(request);
+
+ assertNotNull(authzResponse);
+
+ permission.removeRole("role_a");
+ permission.addRole("role_b");
+
+ protection.policy(resource.getId()).update(permission);
+
+ try {
+ authzResponse = authorization.authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+
+ try {
+ authzResponse = getAuthzClient().authorization("alice", "password").authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+
+ permission.addRole("role_a");
+
+ protection.policy(resource.getId()).update(permission);
+
+ authzResponse = authorization.authorize(request);
+
+ assertNotNull(authzResponse);
+
+ protection.policy(resource.getId()).delete(permission.getId());
+
+ try {
+ authzResponse = authorization.authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+
+ try {
+ getAuthzClient().protection("marta", "password").policy(resource.getId()).findById(permission.getId());
+ fail("Permission must not exist");
+ } catch (Exception e) {
+ assertEquals(404, HttpResponseException.class.cast(e.getCause()).getStatusCode());
+ }
+ }
+
+ @Test
+ public void testPermissionInAdditionToUserGrantedPermission() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName("Resource A");
+ resource.setOwnerManagedAccess(true);
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ resource = getAuthzClient().protection().resource().create(resource);
+
+ PermissionResponse ticketResponse = getAuthzClient().protection().permission().create(new PermissionRequest(resource.getId(), "Scope A"));
+
+ AuthorizationRequest request = new AuthorizationRequest();
+
+ request.setTicket(ticketResponse.getTicket());
+
+ try {
+ getAuthzClient().authorization("kolo", "password").authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ assertTrue(e.getMessage().contains("request_submitted"));
+ }
+
+ List<PermissionTicketRepresentation> tickets = getAuthzClient().protection().permission().findByResource(resource.getId());
+
+ assertEquals(1, tickets.size());
+
+ PermissionTicketRepresentation ticket = tickets.get(0);
+
+ ticket.setGranted(true);
+
+ getAuthzClient().protection().permission().update(ticket);
+
+ AuthorizationResponse authzResponse = getAuthzClient().authorization("kolo", "password").authorize(request);
+
+ assertNotNull(authzResponse);
+
+ UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
+
+ permission.setName("Custom User-Managed Permission");
+ permission.addScope("Scope A");
+ permission.addRole("role_a");
+
+ ProtectionResource protection = getAuthzClient().protection("marta", "password");
+
+ permission = protection.policy(resource.getId()).create(permission);
+
+ authzResponse = getAuthzClient().authorization("kolo", "password").authorize(request);
+
+ ticket.setGranted(false);
+
+ getAuthzClient().protection().permission().update(ticket);
+
+ authzResponse = getAuthzClient().authorization("kolo", "password").authorize(request);
+
+ permission = getAuthzClient().protection("marta", "password").policy(resource.getId()).findById(permission.getId());
+
+ assertNotNull(permission);
+
+ permission.removeRole("role_a");
+ permission.addRole("role_b");
+
+ getAuthzClient().protection("marta", "password").policy(resource.getId()).update(permission);
+
+ try {
+ getAuthzClient().authorization("kolo", "password").authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+
+ request = new AuthorizationRequest();
+
+ request.addPermission(resource.getId());
+
+ try {
+ getAuthzClient().authorization("kolo", "password").authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+
+ getAuthzClient().protection("marta", "password").policy(resource.getId()).delete(permission.getId());
+
+ try {
+ getAuthzClient().authorization("kolo", "password").authorize(request);
+ fail("User should not have permission");
+ } catch (Exception e) {
+ assertTrue(AuthorizationDeniedException.class.isInstance(e));
+ }
+ }
+
+ @Test
+ public void testPermissionWithoutScopes() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName(UUID.randomUUID().toString());
+ resource.setOwner("marta");
+ resource.setOwnerManagedAccess(true);
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ ProtectionResource protection = getAuthzClient().protection();
+
+ resource = protection.resource().create(resource);
+
+ UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
+
+ permission.setName("Custom User-Managed Policy");
+ permission.addRole("role_a");
+
+ PolicyResource policy = getAuthzClient().protection("marta", "password").policy(resource.getId());
+
+ permission = policy.create(permission);
+
+ assertEquals(3, permission.getScopes().size());
+ assertTrue(Arrays.asList("Scope A", "Scope B", "Scope C").containsAll(permission.getScopes()));
+
+ permission = policy.findById(permission.getId());
+
+ assertTrue(Arrays.asList("Scope A", "Scope B", "Scope C").containsAll(permission.getScopes()));
+ assertEquals(3, permission.getScopes().size());
+
+ permission.removeScope("Scope B");
+
+ policy.update(permission);
+ permission = policy.findById(permission.getId());
+
+ assertEquals(2, permission.getScopes().size());
+ assertTrue(Arrays.asList("Scope A", "Scope C").containsAll(permission.getScopes()));
+ }
+
+ @Test
+ public void testOnlyResourceOwnerCanManagePolicies() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName(UUID.randomUUID().toString());
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ ProtectionResource protection = getAuthzClient().protection();
+
+ resource = protection.resource().create(resource);
+
+ try {
+ getAuthzClient().protection("alice", "password").policy(resource.getId()).create(new UmaPermissionRepresentation());
+ fail("Error expected");
+ } catch (Exception e) {
+ assertTrue(HttpResponseException.class.cast(e.getCause()).toString().contains("Only resource onwer can access policies for resource"));
+ }
+ }
+
+ @Test
+ public void testOnlyResourcesWithOwnerManagedAccess() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName(UUID.randomUUID().toString());
+ resource.setOwner("marta");
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ ProtectionResource protection = getAuthzClient().protection();
+
+ resource = protection.resource().create(resource);
+
+ try {
+ getAuthzClient().protection("marta", "password").policy(resource.getId()).create(new UmaPermissionRepresentation());
+ fail("Error expected");
+ } catch (Exception e) {
+ assertTrue(HttpResponseException.class.cast(e.getCause()).toString().contains("Only resources with owner managed accessed can have policies"));
+ }
+ }
+
+ @Test
+ public void testFindPermission() {
+ ResourceRepresentation resource = new ResourceRepresentation();
+
+ resource.setName(UUID.randomUUID().toString());
+ resource.setOwner("marta");
+ resource.setOwnerManagedAccess(true);
+ resource.addScope("Scope A", "Scope B", "Scope C");
+
+ ProtectionResource protection = getAuthzClient().protection();
+
+ resource = protection.resource().create(resource);
+
+ PolicyResource policy = getAuthzClient().protection("marta", "password").policy(resource.getId());
+
+ for (int i = 0; i < 10; i++) {
+ UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
+
+ permission.setName("Custom User-Managed Policy " + i);
+ permission.addRole("role_a");
+
+ policy.create(permission);
+ }
+
+ assertEquals(10, policy.find(null, null, null, null).size());
+
+ List<UmaPermissionRepresentation> byId = policy.find("Custom User-Managed Policy 8", null, null, null);
+
+ assertEquals(1, byId.size());
+ assertEquals(byId.get(0).getId(), policy.findById(byId.get(0).getId()).getId());
+ assertEquals(10, policy.find(null, "Scope A", null, null).size());
+ assertEquals(5, policy.find(null, null, -1, 5).size());
+ assertEquals(2, policy.find(null, null, -1, 2).size());
+ }
+
+ private List<PolicyRepresentation> getAssociatedPolicies(UmaPermissionRepresentation permission) {
+ return getClient(getRealm()).authorization().policies().policy(permission.getId()).associatedPolicies();
+ }
+
+}
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 1398730..06f6094 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -200,6 +200,10 @@ doApprove=Approve
doRemoveSharing=Remove Sharing
doRemoveRequest=Remove Request
peopleAccessResource=People with access to this resource
+resourceManagedPolicies=Permissions granting access to this resource
+resourceNoPermissionsGrantingAccess=No permissions granting access to this resource
+anyAction=Any action
+description=Description
name=Name
scopes=Scopes
resource=Resource
diff --git a/themes/src/main/resources/theme/base/account/resource-detail.ftl b/themes/src/main/resources/theme/base/account/resource-detail.ftl
index fd4e5ec..e30d947 100755
--- a/themes/src/main/resources/theme/base/account/resource-detail.ftl
+++ b/themes/src/main/resources/theme/base/account/resource-detail.ftl
@@ -183,6 +183,70 @@
<div class="row">
<div class="col-md-10">
<h3>
+ ${msg("resourceManagedPolicies")}
+ </h3>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th>${msg("description")}</th>
+ <th>${msg("permission")}</th>
+ <th>${msg("action")}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <#if authorization.resource.policies?size != 0>
+ <#list authorization.resource.policies as permission>
+ <form action="${url.getResourceGrant(authorization.resource.id)}" name="revokePolicyForm-${authorization.resource.id}-${permission.id}" method="post">
+ <input type="hidden" name="action" value="revokePolicy">
+ <input type="hidden" name="permission_id" value="${permission.id}"/>
+ <tr>
+ <td>
+ <#if permission.description??>
+ ${permission.description}
+ </#if>
+ </td>
+ <td>
+ <#if permission.scopes?size != 0>
+ <#list permission.scopes as scope>
+ <div class="search-box">
+ <#if scope.displayName??>
+ ${scope.displayName}
+ <#else>
+ ${scope.name}
+ </#if>
+ <button class="close-icon" type="button" name="removePolicyScope-${authorization.resource.id}-${permission.id}-${scope.id}" onclick="removeScopeElm(this.parentNode);document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}'].submit();"><i class="fa fa-times" aria-hidden="true"></i></button>
+ <input type="hidden" name="permission_id" value="${permission.id}:${scope.id}"/>
+ </div>
+ </#list>
+ <#else>
+ ${msg("anyAction")}
+ </#if>
+ </td>
+ <td width="20%" align="middle" style="vertical-align: middle">
+ <a href="#" id="revokePolicy-${authorization.resource.name}-${permission.id}" onclick="document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}']['action'].value = 'revokePolicyAll';document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}'].submit();" type="submit" class="btn btn-primary">${msg("doRevoke")}</a>
+ </td>
+ </tr>
+ </form>
+ </#list>
+ <#else>
+ <tr>
+ <td colspan="3">
+ ${msg("resourceNoPermissionsGrantingAccess")}
+ </td>
+ </tr>
+ </#if>
+ </tbody>
+ </table>
+ </form>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-10">
+ <h3>
${msg("shareWithOthers")}
</h3>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
index cd8007f..19ff720 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
@@ -41,12 +41,18 @@
<div>
<div>
<li data-ng-repeat="policyResult in result.policies">
- <strong><a
- href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
+ <strong>
+ <a data-ng-show="policyResult.policy.type != 'uma'"
+ href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a>
+ <a data-ng-show="policyResult.policy.type == 'uma'"
+ href="">
+ {{policyResult.policy.description}}
+ </a>
+ </strong>
decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
<span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision. {{policyResult.policy.scopes.length > 0 ? (policyResult.status == 'DENY' ? 'Denied Scopes:' : 'Granted Scopes:') : ''}} <span data-ng-repeat="scope in policyResult.policy.scopes"><strong style="color: {{(policyResult.status == 'DENY' ? 'red' : 'green')}}">{{scope}}{{$last ? '' : ', '}}</strong></span>{{policyResult.policy.scopes.length > 0 ? '.' : ''}}
- <ul>
+ <ul data-ng-show="policyResult.policy.type != 'uma'">
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a
href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>