Merge pull request #5240 from pedroigor/KEYCLOAK-7353

[KEYCLOAK-7353] Support Policy Management in Protection API
This commit is contained in:
Pedro Igor 2018-06-07 11:05:49 -03:00 committed by GitHub
commit aa128d6c07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 2245 additions and 375 deletions

View file

@ -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<>();
if (grantedClaims != null) {
Map<String, List<String>> claims = resolveClaims(actualPathConfig, httpFacade);
for (Entry<String, Map<String, Object>> entry : claimInformationPointConfig.entrySet()) {
ClaimInformationPointProviderFactory factory = policyEnforcer.getClaimInformationPointProviderFactories().get(entry.getKey());
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;
}
}

View file

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

View file

@ -319,6 +319,7 @@ public class PolicyEnforcer {
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
return config;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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();
representation.setScopes(policy.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()));
representation.setOwner(policy.getOwner());
for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
AbstractPolicyRepresentation associatedRep = ModelToRepresentation.toRepresentation(associatedPolicy, authorization, false, false);
RealmModel realm = authorization.getRealm();
if ("role".equals(associatedRep.getType())) {
RolePolicyRepresentation rep = RolePolicyRepresentation.class.cast(associatedRep);
for (RoleDefinition definition : rep.getRoles()) {
RoleModel role = realm.getRoleById(definition.getId());
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<PolicyRepresentation> getRepresentationType() {
return PolicyRepresentation.class;
}
private void verifyCircularReference(Policy policy, List<String> ids) {
if (!policy.getType().equals("uma")) {
return;
}
if (ids.contains(policy.getId())) {
throw new RuntimeException("Circular reference found [" + policy.getName() + "].");
}
ids.add(policy.getId());
for (Policy associated : policy.getAssociatedPolicies()) {
verifyCircularReference(associated, ids);
}
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);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 + "]");
}

View file

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

View file

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

View file

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

View file

@ -232,6 +232,11 @@ public final class AuthorizationProvider implements Provider {
return delegate.create(name, resourceServer);
}
@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);
@ -411,6 +416,11 @@ public final class AuthorizationProvider implements Provider {
return delegate.create(name, resourceServer, owner);
}
@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);

View file

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

View file

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

View file

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

View file

@ -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;
@ -57,6 +58,50 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
this.authorization = authorization;
}
@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();
@ -64,17 +109,17 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
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());
}
}

View file

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

View file

@ -40,6 +40,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.
*

View file

@ -42,6 +42,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.
*

View file

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

View file

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

View file

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

View file

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

View file

@ -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});
}
Set<String> resources = resourceStore.findByResourceServer(resourceFilters, resourceServer.getId(), -1, 1).stream().map(Resource::getId).collect(Collectors.toSet());
if (resources.isEmpty()) {
return Response.ok().build();
}
search.put("resource", resources.toArray(new String[resources.size()]));
} else {
search.put("resource", new String[] {resourceModel.getId()});
}
}
if (scope != null && !"".equals(scope.trim())) {
HashMap<String, String[]> scopeSearch = new HashMap<>();
if (scope != null && !"".equals(scope.trim())) {
ScopeStore scopeStore = storeFactory.getScopeStore();
Scope scopeModel = scopeStore.findById(scope, resourceServer.getId());
scopeSearch.put("name", new String[]{scope});
if (scopeModel == null) {
Map<String, String[]> scopeFilters = new HashMap<>();
storeFactory.getScopeStore().findByResourceServer(scopeSearch, resourceServer.getId(), -1, 1).forEach(scope1 -> {
policies.addAll(policyStore.findByScopeIds(Arrays.asList(scope1.getId()), resourceServer.getId()));
});
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 (policies.isEmpty()) {
return Response.ok(Collections.emptyList()).build();
}
new ArrayList<>(policies).forEach(policy -> findAssociatedPolicies(policy, policies));
search.put("id", policies.stream().map(Policy::getId).toArray(String[]::new));
}
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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1069,7 +1069,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");

View file

@ -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());
}
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())) {
if (!id.contains(":")) {
policy = policyStore.findById(id, client.getId());
iterator.remove();
break;
}
}
Set<Scope> scopesToKeep = new HashSet<>();
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()));
}
for (Scope scope : policy.getScopes()) {
if (!scopesToKeep.contains(scope)) {
policy.removeScope(scope);
}
}
}
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);
}

View file

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

View file

@ -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
@ -182,6 +205,52 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
parameters.put("withdrawal.amount", Arrays.asList("50"));
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"));
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());
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
public void testEnforceEntitlementAccessWithClaimsWithBearerToken() {
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<>();
AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
String token = authzClient.obtainAccessToken("marta", "password").getToken();
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());
@ -203,7 +272,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
}
@Test
public void testEnforceEntitlementAccessWithClaimsWithBearerToken() {
public void testEnforceEntitlementAccessWithClaimsWithBearerTokenFromPublicClient() {
initAuthorizationSettings(getClientResource("resource-server-test"));
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
@ -211,8 +280,13 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
HashMap<String, List<String>> headers = new HashMap<>();
HashMap<String, List<String>> parameters = new HashMap<>();
AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
String token = authzClient.obtainAccessToken("marta", "password").getToken();
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));
@ -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

View file

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

View file

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

View file

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

View file

@ -180,6 +180,70 @@
</form>
</div>
</div>
<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>

View file

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