Merge pull request #5211 from pedroigor/KEYCLOAK-7367

[KEYCLOAK-7367] - User-Managed Policy Provider
This commit is contained in:
Pedro Igor 2018-06-04 09:35:13 -03:00 committed by GitHub
commit f8919f8baa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1125 additions and 176 deletions

View file

@ -18,9 +18,12 @@ 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 org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.attribute.Attributes.Entry;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
@ -42,11 +45,13 @@ public class GroupPolicyProvider implements PolicyProvider {
@Override
public void evaluate(Evaluation evaluation) {
GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
RealmModel realm = evaluation.getAuthorizationProvider().getRealm();
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getRealm();
Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
if (groupsClaim == null || groupsClaim.isEmpty()) {
return;
List<String> userGroups = evaluation.getRealm().getUserGroups(evaluation.getContext().getIdentity().getId());
groupsClaim = new Entry(policy.getGroupsClaim(), userGroups);
}
for (GroupPolicyRepresentation.GroupDefinition definition : policy.getGroups()) {

View file

@ -0,0 +1,55 @@
/*
* 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.policy.provider.permission;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class AbstractPermissionProvider implements PolicyProvider {
public AbstractPermissionProvider() {
}
@Override
public void evaluate(Evaluation evaluation) {
if (!(evaluation instanceof DefaultEvaluation)) {
throw new IllegalArgumentException("Unexpected evaluation instance type [" + evaluation.getClass() + "]");
}
Policy policy = evaluation.getPolicy();
AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
policy.getAssociatedPolicies().forEach(associatedPolicy -> {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
DefaultEvaluation.class.cast(evaluation).setPolicy(associatedPolicy);
policyProvider.evaluate(evaluation);
evaluation.denyIfNoEffect();
});
}
@Override
public void close() {
}
}

View file

@ -1,13 +1,12 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
* 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
* 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,
@ -15,26 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.policy.provider.resource;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
package org.keycloak.authorization.policy.provider.permission;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ResourcePolicyProvider implements PolicyProvider {
public class ResourcePolicyProvider extends AbstractPermissionProvider {
public ResourcePolicyProvider() {
}
@Override
public void evaluate(Evaluation evaluation) {
}
@Override
public void close() {
}
}

View file

@ -1,4 +1,20 @@
package org.keycloak.authorization.policy.provider.resource;
/*
* 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.policy.provider.permission;
import java.util.HashMap;
import java.util.Map;

View file

@ -1,13 +1,12 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
* 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
* 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,
@ -15,22 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.policy.provider.scope;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
package org.keycloak.authorization.policy.provider.permission;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ScopePolicyProvider implements PolicyProvider {
public class ScopePolicyProvider extends AbstractPermissionProvider {
@Override
public void evaluate(Evaluation evaluation) {
}
@Override
public void close() {
}
}

View file

@ -1,4 +1,20 @@
package org.keycloak.authorization.policy.provider.scope;
/*
* 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.policy.provider.permission;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;

View file

@ -0,0 +1,24 @@
/*
* 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.policy.provider.permission;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UMAPolicyProvider extends AbstractPermissionProvider {
}

View file

@ -0,0 +1,128 @@
/*
* 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.policy.provider.permission;
import java.util.ArrayList;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UMAPolicyProviderFactory implements PolicyProviderFactory<PolicyRepresentation> {
private UMAPolicyProvider provider = new UMAPolicyProvider();
@Override
public String getName() {
return "UMA";
}
@Override
public String getGroup() {
return "Others";
}
@Override
public boolean isInternal() {
return true;
}
@Override
public PolicyProvider create(AuthorizationProvider authorization) {
return provider;
}
@Override
public PolicyProvider create(KeycloakSession session) {
return null;
}
@Override
public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public PolicyRepresentation toRepresentation(Policy policy) {
return new PolicyRepresentation();
}
@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);
}
}
@Override
public void onRemove(Policy policy, AuthorizationProvider authorization) {
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "uma";
}
}

View file

@ -36,10 +36,11 @@
org.keycloak.authorization.policy.provider.aggregated.AggregatePolicyProviderFactory
org.keycloak.authorization.policy.provider.js.JSPolicyProviderFactory
org.keycloak.authorization.policy.provider.resource.ResourcePolicyProviderFactory
org.keycloak.authorization.policy.provider.permission.ResourcePolicyProviderFactory
org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
org.keycloak.authorization.policy.provider.scope.ScopePolicyProviderFactory
org.keycloak.authorization.policy.provider.permission.ScopePolicyProviderFactory
org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
org.keycloak.authorization.policy.provider.permission.UMAPolicyProviderFactory

View file

@ -18,6 +18,7 @@ package org.keycloak.models.cache.infinispan.authorization;
import org.keycloak.authorization.model.CachedModel;
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;
@ -41,7 +42,7 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
@Override
public PermissionTicket getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
updated = cacheSession.getPermissionTicketStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@ -113,7 +114,7 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
@Override
public void setGrantedTimestamp(Long millis) {
getDelegateForUpdate();
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
updated.setGrantedTimestamp(millis);
}
@ -122,6 +123,19 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
return cacheSession.getResourceServerStore().findById(cached.getResourceServerId());
}
@Override
public Policy getPolicy() {
if (isUpdated()) return updated.getPolicy();
return cacheSession.getPolicyStore().findById(cached.getPolicy(), cached.getResourceServerId());
}
@Override
public void setPolicy(Policy policy) {
getDelegateForUpdate();
cacheSession.registerPermissionTicketInvalidation(cached.getId(), cached.getOwner(), cached.getRequester(), cached.getResourceId(), cached.getScopeId(), cached.getResourceServerId());
updated.setPolicy(policy);
}
@Override
public Resource getResource() {
return cacheSession.getResourceStore().findById(cached.getResourceId(), getResourceServer().getId());

View file

@ -100,7 +100,6 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId());
updated.setName(name);
}
@Override
@ -278,6 +277,19 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
return scopes;
}
@Override
public String getOwner() {
if (isUpdated()) return updated.getOwner();
return cached.getOwner();
}
@Override
public void setOwner(String owner) {
getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId());
updated.setOwner(owner);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -199,7 +199,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
for (Scope scope : updated.getScopes()) {
if (!scopes.contains(scope)) {
PermissionTicketStore permissionStore = cacheSession.getPermissionTicketStoreDelegate();
PermissionTicketStore permissionStore = cacheSession.getPermissionTicketStore();
List<PermissionTicket> permissions = permissionStore.findByScope(scope.getId(), getResourceServer().getId());
for (PermissionTicket permission : permissions) {

View file

@ -135,10 +135,11 @@ public class StoreFactoryCacheManager extends CacheManager {
}
}
public void permissionTicketUpdated(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
public void permissionTicketUpdated(String id, String owner, String requester, String resource, String scope, String serverId, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByOwner(owner, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(resource, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, serverId));
if (scope != null) {
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));
}
@ -148,8 +149,8 @@ public class StoreFactoryCacheManager extends CacheManager {
policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
}
public void permissionTicketRemoval(String id, String owner, String resource, String scope, String serverId, Set<String> invalidations) {
permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
public void permissionTicketRemoval(String id, String owner, String requester, String resource, String scope, String serverId, Set<String> invalidations) {
permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
}
}

View file

@ -30,6 +30,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.authorization.UserManagedPermissionUtil;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@ -283,12 +284,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
}
public void registerPermissionTicketInvalidation(String id, String owner, String resource, String scope, String serverId) {
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
public void registerPermissionTicketInvalidation(String id, String owner, String requester, String resource, String scope, String serverId) {
cache.permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
PermissionTicketAdapter adapter = managedPermissionTickets.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(PermissionTicketUpdatedEvent.create(id, owner, resource, scope, serverId));
invalidationEvents.add(PermissionTicketUpdatedEvent.create(id, owner, requester, resource, scope, serverId));
}
private Set<String> getResourceTypes(Set<String> resources, String serverId) {
@ -384,6 +385,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return "permission.ticket.scope." + scopeId + "." + serverId;
}
public static String getPermissionTicketByGranted(String userId, String serverId) {
return "permission.ticket.granted." + userId + "." + serverId;
}
public static String getPermissionTicketByOwner(String owner, String serverId) {
return "permission.ticket.owner." + owner + "." + serverId;
}
@ -836,7 +841,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public PermissionTicket create(String resourceId, String scopeId, String requester, ResourceServer resourceServer) {
PermissionTicket created = getPermissionTicketStoreDelegate().create(resourceId, scopeId, requester, resourceServer);
registerPermissionTicketInvalidation(created.getId(), created.getOwner(), created.getResource().getId(), scopeId, created.getResourceServer().getId());
registerPermissionTicketInvalidation(created.getId(), created.getOwner(), created.getRequester(), created.getResource().getId(), scopeId, created.getResourceServer().getId());
return created;
}
@ -851,9 +856,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (permission.getScope() != null) {
scopeId = permission.getScope().getId();
}
invalidationEvents.add(PermissionTicketRemovedEvent.create(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId()));
cache.permissionTicketRemoval(id, permission.getOwner(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId(), invalidations);
invalidationEvents.add(PermissionTicketRemovedEvent.create(id, permission.getOwner(), permission.getRequester(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId()));
cache.permissionTicketRemoval(id, permission.getOwner(), permission.getRequester(), permission.getResource().getId(), scopeId, permission.getResourceServer().getId(), invalidations);
getPermissionTicketStoreDelegate().delete(id);
UserManagedPermissionUtil.removePolicy(permission, StoreFactoryCacheSession.this);
}
@ -908,6 +914,13 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return getPermissionTicketStoreDelegate().find(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<PermissionTicket> findGranted(String userId, String resourceServerId) {
String cacheKey = getPermissionTicketByGranted(userId, resourceServerId);
return cacheQuery(cacheKey, PermissionTicketListQuery.class, () -> getPermissionTicketStoreDelegate().findGranted(userId, resourceServerId),
(revision, permissions) -> new PermissionTicketListQuery(revision, cacheKey, permissions.stream().map(permission -> permission.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
String cacheKey = getPermissionTicketByOwner(owner, resourceServerId);

View file

@ -18,6 +18,7 @@
package org.keycloak.models.cache.infinispan.authorization.entities;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
/**
@ -33,6 +34,7 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
private boolean granted;
private Long createdTimestamp;
private Long grantedTimestamp;
private String policy;
public CachedPermissionTicket(Long revision, PermissionTicket permissionTicket) {
super(revision, permissionTicket.getId());
@ -46,6 +48,10 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
this.granted = permissionTicket.isGranted();
createdTimestamp = permissionTicket.getCreatedTimestamp();
grantedTimestamp = permissionTicket.getGrantedTimestamp();
Policy policy = permissionTicket.getPolicy();
if (policy != null) {
this.policy = policy.getId();
}
}
public String getOwner() {
@ -80,4 +86,7 @@ public class CachedPermissionTicket extends AbstractRevisioned implements InReso
return this.resourceServerId;
}
public String getPolicy() {
return policy;
}
}

View file

@ -45,6 +45,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
private Set<String> associatedPoliciesIds;
private Set<String> resourcesIds;
private Set<String> scopesIds;
private final String owner;
public CachedPolicy(Long revision, Policy policy) {
super(revision, policy.getId());
@ -58,6 +59,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
this.associatedPoliciesIds = policy.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet());
this.resourcesIds = policy.getResources().stream().map(Resource::getId).collect(Collectors.toSet());
this.scopesIds = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
this.owner = policy.getOwner();
}
public String getType() {
@ -100,4 +102,7 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
return this.resourceServerId;
}
public String getOwner() {
return owner;
}
}

View file

@ -32,11 +32,13 @@ public class PermissionTicketRemovedEvent extends InvalidationEvent implements A
private String resource;
private String scope;
private String serverId;
private String requester;
public static PermissionTicketRemovedEvent create(String id, String owner, String resource, String scope, String serverId) {
public static PermissionTicketRemovedEvent create(String id, String owner, String requester, String resource, String scope, String serverId) {
PermissionTicketRemovedEvent event = new PermissionTicketRemovedEvent();
event.id = id;
event.owner = owner;
event.requester = requester;
event.resource = resource;
event.scope = scope;
event.serverId = serverId;
@ -55,6 +57,6 @@ public class PermissionTicketRemovedEvent extends InvalidationEvent implements A
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.permissionTicketRemoval(id, owner, resource, scope, serverId, invalidations);
cache.permissionTicketRemoval(id, owner, requester, resource, scope, serverId, invalidations);
}
}

View file

@ -32,11 +32,13 @@ public class PermissionTicketUpdatedEvent extends InvalidationEvent implements A
private String resource;
private String scope;
private String serverId;
private String requester;
public static PermissionTicketUpdatedEvent create(String id, String owner, String resource, String scope, String serverId) {
public static PermissionTicketUpdatedEvent create(String id, String owner, String requester, String resource, String scope, String serverId) {
PermissionTicketUpdatedEvent event = new PermissionTicketUpdatedEvent();
event.id = id;
event.owner = owner;
event.requester = requester;
event.resource = resource;
event.scope = scope;
event.serverId = serverId;
@ -55,6 +57,6 @@ public class PermissionTicketUpdatedEvent extends InvalidationEvent implements A
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
cache.permissionTicketUpdated(id, owner, resource, scope, serverId, invalidations);
cache.permissionTicketUpdated(id, owner, requester, resource, scope, serverId, invalidations);
}
}

View file

@ -76,6 +76,10 @@ public class PermissionTicketEntity {
@JoinColumn(name = "RESOURCE_SERVER_ID")
private ResourceServerEntity resourceServer;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "POLICY_ID")
private PolicyEntity policy;
public String getId() {
return id;
}
@ -144,6 +148,14 @@ public class PermissionTicketEntity {
return grantedTimestamp != null;
}
public PolicyEntity getPolicy() {
return policy;
}
public void setPolicy(PolicyEntity policy) {
this.policy = policy;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -113,6 +113,9 @@ public class PolicyEntity {
@JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
private Set<ScopeEntity> scopes = new HashSet<>();
@Column(name = "OWNER")
private String owner;
public String getId() {
return this.id;
}
@ -201,6 +204,14 @@ public class PolicyEntity {
this.associatedPolicies = associatedPolicies;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -56,7 +56,7 @@ import org.hibernate.annotations.FetchMode;
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"),
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.uri = :uri"),
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.type = :type"),
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"),
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
@NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId")

View file

@ -18,9 +18,11 @@ package org.keycloak.authorization.jpa.store;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
@ -102,9 +104,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
List<String> result = query.getResultList();
List<PermissionTicket> list = new LinkedList<>();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (String id : result) {
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
if (Objects.nonNull(ticket)) {
list.add(ticket);
}
}
return list;
}
@ -118,9 +126,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
List<String> result = query.getResultList();
List<PermissionTicket> list = new LinkedList<>();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (String id : result) {
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
if (Objects.nonNull(ticket)) {
list.add(ticket);
}
}
return list;
}
@ -139,9 +153,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
List<String> result = query.getResultList();
List<PermissionTicket> list = new LinkedList<>();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (String id : result) {
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
if (Objects.nonNull(ticket)) {
list.add(ticket);
}
}
return list;
}
@ -184,6 +204,8 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
}
} else if (PermissionTicket.REQUESTER_IS_NULL.equals(name)) {
predicates.add(builder.isNull(root.get("requester")));
} else if (PermissionTicket.POLICY_IS_NOT_NULL.equals(name)) {
predicates.add(builder.isNotNull(root.get("policy")));
} else {
throw new RuntimeException("Unsupported filter [" + name + "]");
}
@ -196,21 +218,35 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
if (firstResult != -1) {
query.setFirstResult(firstResult);
}
if (maxResult != -1) {
query.setMaxResults(maxResult);
}
List<String> result = query.getResultList();
List<PermissionTicket> list = new LinkedList<>();
PermissionTicketStore ticket = provider.getStoreFactory().getPermissionTicketStore();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (String id : result) {
list.add(ticket.findById(id, resourceServerId));
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
if (Objects.nonNull(ticket)) {
list.add(ticket);
}
}
return list;
}
@Override
public List<PermissionTicket> findGranted(String userId, String resourceServerId) {
HashMap<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
filters.put(PermissionTicket.REQUESTER, userId);
return find(filters, resourceServerId, -1, -1);
}
@Override
public List<PermissionTicket> findByOwner(String owner, String resourceServerId) {
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByType", String.class);
@ -221,9 +257,15 @@ public class JPAPermissionTicketStore implements PermissionTicketStore {
List<String> result = query.getResultList();
List<PermissionTicket> list = new LinkedList<>();
PermissionTicketStore ticketStore = provider.getStoreFactory().getPermissionTicketStore();
for (String id : result) {
list.add(provider.getStoreFactory().getPermissionTicketStore().findById(id, resourceServerId));
PermissionTicket ticket = ticketStore.findById(id, resourceServerId);
if (Objects.nonNull(ticket)) {
list.add(ticket);
}
}
return list;
}
}

View file

@ -142,11 +142,21 @@ public class JPAPolicyStore implements PolicyStore {
}
} else if ("id".equals(name)) {
predicates.add(root.get(name).in(value));
} else if ("owner".equals(name)) {
predicates.add(root.get(name).in(value));
} else if ("owner_is_not_null".equals(name)) {
predicates.add(builder.isNotNull(root.get("owner")));
} else if ("resource".equals(name)) {
predicates.add(root.join("resources").get("id").in(value));
} else {
predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%"));
}
});
if (!attributes.containsKey("owner") && !attributes.containsKey("owner_is_not_null")) {
predicates.add(builder.isNull(root.get("owner")));
}
querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name")));
Query query = entityManager.createQuery(querybuilder);

View file

@ -267,6 +267,7 @@ public class JPAResourceStore implements ResourceStore {
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", type);
query.setParameter("ownerId", resourceServerId);
query.setParameter("serverId", resourceServerId);
List<String> result = query.getResultList();

View file

@ -16,9 +16,12 @@
*/
package org.keycloak.authorization.jpa.store;
import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy;
import javax.persistence.EntityManager;
import org.keycloak.authorization.jpa.entities.PermissionTicketEntity;
import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
@ -34,13 +37,13 @@ import org.keycloak.models.jpa.JpaModel;
*/
public class PermissionTicketAdapter implements PermissionTicket, JpaModel<PermissionTicketEntity> {
private PermissionTicketEntity entity;
private EntityManager em;
private StoreFactory storeFactory;
private final EntityManager entityManager;
private final PermissionTicketEntity entity;
private final StoreFactory storeFactory;
public PermissionTicketAdapter(PermissionTicketEntity entity, EntityManager em, StoreFactory storeFactory) {
public PermissionTicketAdapter(PermissionTicketEntity entity, EntityManager entityManager, StoreFactory storeFactory) {
this.entity = entity;
this.em = em;
this.entityManager = entityManager;
this.storeFactory = storeFactory;
}
@ -82,6 +85,7 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel<Permi
@Override
public void setGrantedTimestamp(Long millis) {
entity.setGrantedTimestamp(millis);
updatePolicy(this, storeFactory);
}
@Override
@ -89,6 +93,24 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel<Permi
return storeFactory.getResourceServerStore().findById(entity.getResourceServer().getId());
}
@Override
public Policy getPolicy() {
PolicyEntity policy = entity.getPolicy();
if (policy == null) {
return null;
}
return storeFactory.getPolicyStore().findById(policy.getId(), entity.getResourceServer().getId());
}
@Override
public void setPolicy(Policy policy) {
if (policy != null) {
entity.setPolicy(entityManager.getReference(PolicyEntity.class, policy.getId()));
}
}
@Override
public Resource getResource() {
return storeFactory.getResourceStore().findById(entity.getResource().getId(), getResourceServer().getId());

View file

@ -207,6 +207,16 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
entity.getResources().remove(ResourceAdapter.toEntity(em, resource));
}
@Override
public void setOwner(String owner) {
entity.setOwner(owner);
}
@Override
public String getOwner() {
return entity.getOwner();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
<changeSet author="psilva@redhat.com" id="authz-4.0.0.Beta3">
<addColumn tableName="RESOURCE_SERVER_POLICY">
<column name="OWNER" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="RESOURCE_SERVER_PERM_TICKET">
<column name="POLICY_ID" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<addForeignKeyConstraint baseColumnNames="POLICY_ID" baseTableName="RESOURCE_SERVER_PERM_TICKET" constraintName="FK_FRSRPO2128CX4WNKOG82SSRFY" referencedColumnNames="ID" referencedTableName="RESOURCE_SERVER_POLICY"/>
</changeSet>
</databaseChangeLog>

View file

@ -55,4 +55,5 @@
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
<include file="META-INF/jpa-changelog-authz-4.0.0.CR1.xml"/>
<include file="META-INF/jpa-changelog-authz-4.0.0.Beta3.xml"/>
</databaseChangeLog>

View file

@ -235,7 +235,7 @@ public final class AuthorizationProvider implements Provider {
@Override
public void delete(String id) {
Scope scope = findById(id, null);
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
PermissionTicketStore ticketStore = AuthorizationProvider.this.getStoreFactory().getPermissionTicketStore();
List<PermissionTicket> permissions = ticketStore.findByScope(id, scope.getResourceServer().getId());
for (PermissionTicket permission : permissions) {
@ -414,6 +414,7 @@ public final class AuthorizationProvider implements Provider {
@Override
public void delete(String id) {
Resource resource = findById(id, null);
StoreFactory storeFactory = AuthorizationProvider.this.getStoreFactory();
PermissionTicketStore ticketStore = storeFactory.getPermissionTicketStore();
List<PermissionTicket> permissions = ticketStore.findByResource(id, resource.getResourceServer().getId());
@ -421,6 +422,17 @@ public final class AuthorizationProvider implements Provider {
ticketStore.delete(permission.getId());
}
PolicyStore policyStore = storeFactory.getPolicyStore();
List<Policy> policies = policyStore.findByResource(id, resource.getResourceServer().getId());
for (Policy policyModel : policies) {
if (policyModel.getResources().size() == 1) {
policyStore.delete(policyModel.getId());
} else {
policyModel.removeResource(resource);
}
}
delegate.delete(id);
}

View file

@ -0,0 +1,129 @@
/*
* 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;
import java.util.HashMap;
import java.util.List;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UserManagedPermissionUtil {
public static void updatePolicy(PermissionTicket ticket, StoreFactory storeFactory) {
Scope scope = ticket.getScope();
Policy policy = ticket.getPolicy();
if (policy == null) {
HashMap<String, String> filter = new HashMap<>();
filter.put(PermissionTicket.OWNER, ticket.getOwner());
filter.put(PermissionTicket.REQUESTER, ticket.getRequester());
filter.put(PermissionTicket.RESOURCE, ticket.getResource().getId());
filter.put(PermissionTicket.POLICY_IS_NOT_NULL, Boolean.TRUE.toString());
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().find(filter, ticket.getResourceServer().getId(), -1, 1);
if (!tickets.isEmpty()) {
policy = tickets.iterator().next().getPolicy();
}
}
if (ticket.isGranted()) {
if (policy == null) {
policy = createUserManagedPermission(ticket, storeFactory);
}
if (scope != null && !policy.getScopes().contains(scope)) {
policy.addScope(scope);
}
ticket.setPolicy(policy);
} else if (scope != null) {
policy.removeScope(scope);
ticket.setPolicy(null);
}
}
public static void removePolicy(PermissionTicket ticket, StoreFactory storeFactory) {
Policy policy = ticket.getPolicy();
if (policy != null) {
HashMap<String, String> filter = new HashMap<>();
filter.put(PermissionTicket.OWNER, ticket.getOwner());
filter.put(PermissionTicket.REQUESTER, ticket.getRequester());
filter.put(PermissionTicket.RESOURCE, ticket.getResource().getId());
filter.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().find(filter, ticket.getResourceServer().getId(), -1, -1);
if (tickets.isEmpty()) {
PolicyStore policyStore = storeFactory.getPolicyStore();
for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
policyStore.delete(associatedPolicy.getId());
}
policyStore.delete(policy.getId());
} else if (ticket.getScope() != null) {
policy.removeScope(ticket.getScope());
}
}
}
private static Policy createUserManagedPermission(PermissionTicket ticket, StoreFactory storeFactory) {
PolicyStore policyStore = storeFactory.getPolicyStore();
UserPolicyRepresentation userPolicyRep = new UserPolicyRepresentation();
userPolicyRep.setName(KeycloakModelUtils.generateId());
userPolicyRep.addUser(ticket.getRequester());
Policy userPolicy = policyStore.create(userPolicyRep, ticket.getResourceServer());
userPolicy.setOwner(ticket.getOwner());
PolicyRepresentation policyRep = new PolicyRepresentation();
policyRep.setName(KeycloakModelUtils.generateId());
policyRep.setType("uma");
policyRep.addPolicy(userPolicy.getId());
Policy policy = policyStore.create(policyRep, ticket.getResourceServer());
policy.setOwner(ticket.getOwner());
policy.addResource(ticket.getResource());
Scope scope = ticket.getScope();
if (scope != null) {
policy.addScope(scope);
}
return policy;
}
}

View file

@ -29,6 +29,7 @@ public interface PermissionTicket {
String GRANTED = "granted";
String REQUESTER = "requester";
String REQUESTER_IS_NULL = "requester_is_null";
String POLICY_IS_NOT_NULL = "policy_is_not_null";
/**
* Returns the unique identifier for this instance.
@ -73,4 +74,8 @@ public interface PermissionTicket {
* @return a resource server
*/
ResourceServer getResourceServer();
Policy getPolicy();
void setPolicy(Policy policy);
}

View file

@ -148,6 +148,10 @@ public interface Policy {
*/
Set<Scope> getScopes();
String getOwner();
void setOwner(String owner);
void addScope(Scope scope);
void removeScope(Scope scope);

View file

@ -48,12 +48,21 @@ public class DefaultEvaluation implements Evaluation {
private final ResourcePermission permission;
private final EvaluationContext executionContext;
private final Decision decision;
private final Policy policy;
private Policy policy;
private final Policy parentPolicy;
private final AuthorizationProvider authorizationProvider;
private final Realm realm;
private Effect effect;
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider) {
this.permission = permission;
this.executionContext = executionContext;
this.parentPolicy = parentPolicy;
this.decision = decision;
this.authorizationProvider = authorizationProvider;
this.realm = createRealm();
}
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
this.permission = permission;
this.executionContext = executionContext;
@ -98,6 +107,9 @@ public class DefaultEvaluation implements Evaluation {
@Override
public Policy getPolicy() {
if (policy == null) {
return parentPolicy;
}
return this.policy;
}
@ -119,7 +131,8 @@ public class DefaultEvaluation implements Evaluation {
return effect;
}
void denyIfNoEffect() {
@Override
public void denyIfNoEffect() {
if (this.effect == null) {
deny();
}
@ -248,4 +261,8 @@ public class DefaultEvaluation implements Evaluation {
}
};
}
public void setPolicy(Policy policy) {
this.policy = policy;
}
}

View file

@ -63,7 +63,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) {
createEvaluation(permission, executionContext, decision, null, null).grant();
createEvaluation(permission, executionContext, decision, null).grant();
return;
}
@ -95,7 +95,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
}
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
createEvaluation(permission, executionContext, decision, null, null).grant();
createEvaluation(permission, executionContext, decision, null).grant();
}
}
@ -113,25 +113,22 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
return;
}
for (Policy associatedPolicy : parentPolicy.getAssociatedPolicies()) {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
PolicyProvider policyProvider = authorization.getProvider(parentPolicy.getType());
if (policyProvider == null) {
throw new RuntimeException("Unknown parentPolicy provider for type [" + associatedPolicy.getType() + "].");
}
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy);
policyProvider.evaluate(evaluation);
evaluation.denyIfNoEffect();
if (policyProvider == null) {
throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
}
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy);
policyProvider.evaluate(evaluation);
verified.compareAndSet(false, true);
};
}
private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy, Policy associatedPolicy) {
return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision, authorization);
private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy) {
return new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorization);
}
private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {

View file

@ -69,4 +69,9 @@ public interface Evaluation {
* Denies the requested permission.
*/
void deny();
/**
* Denies the requested permission if a decision was not made yet.
*/
void denyIfNoEffect();
}

View file

@ -57,53 +57,6 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
this.authorization = authorization;
}
@Override
protected void onDeny(Result result) {
ResourcePermission permission = result.getPermission();
Resource resource = permission.getResource();
if (resource != null && resource.isOwnerManagedAccess()) {
if (!resource.getOwner().equals(identity.getId())) {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.RESOURCE, resource.getId());
filters.put(PermissionTicket.REQUESTER, identity.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
List<PermissionTicket> permissions = authorization.getStoreFactory().getPermissionTicketStore().find(filters, resource.getResourceServer().getId(), -1, -1);
if (!permissions.isEmpty()) {
List<Scope> grantedScopes = new ArrayList<>();
for (PolicyResult policyResult : result.getResults()) {
for (PermissionTicket ticket : permissions) {
Scope grantedScope = ticket.getScope();
if ("resource".equals(policyResult.getPolicy().getType())) {
policyResult.setStatus(Effect.PERMIT);
}
if (grantedScope != null) {
grantedScopes.add(grantedScope);
for (Scope policyScope : policyResult.getPolicy().getScopes()) {
if (policyScope.equals(grantedScope)) {
policyResult.setStatus(Effect.PERMIT);
}
}
}
}
}
permission.getScopes().clear();
permission.getScopes().addAll(grantedScopes);
}
}
}
super.onDeny(result);
}
@Override
public void onComplete() {
super.onComplete();
@ -111,9 +64,10 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
if (request.isSubmitRequest()) {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore();
List<PermissionTicketToken.ResourcePermission> resources = ticket.getResources();
if (ticket.getResources() != null) {
for (PermissionTicketToken.ResourcePermission permission : ticket.getResources()) {
if (resources != null) {
for (PermissionTicketToken.ResourcePermission permission : resources) {
Resource resource = resourceStore.findById(permission.getResourceId(), resourceServer.getId());
if (resource == null) {

View file

@ -34,6 +34,10 @@ public interface PolicyProviderFactory<R extends AbstractPolicyRepresentation> e
String getGroup();
default boolean isInternal() {
return false;
}
PolicyProvider create(AuthorizationProvider authorization);
R toRepresentation(Policy policy);

View file

@ -90,4 +90,13 @@ public interface PermissionTicketStore {
List<PermissionTicket> findByScope(String scopeId, String resourceServerId);
List<PermissionTicket> find(Map<String, String> attributes, String resourceServerId, int firstResult, int maxResult);
/**
* Returns a list of {@link PermissionTicket} granted to the given {@code userId}.
*
* @param userId the user id
* @param resourceServerId the resource server id
* @return a list of permissions granted for a particular user
*/
List<PermissionTicket> findGranted(String userId, String resourceServerId);
}

View file

@ -2444,7 +2444,7 @@ public class RepresentationToModel {
if (granted && !ticket.isGranted()) {
ticket.setGrantedTimestamp(System.currentTimeMillis());
} else if (!granted) {
ticket.setGrantedTimestamp(null);
ticketStore.delete(ticket.getId());
}
return ticket;

View file

@ -55,7 +55,6 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.AdminEventBuilder;
@ -253,12 +252,13 @@ public class PolicyService {
this.auth.realm().requireViewAuthorization();
return Response.ok(
authorization.getProviderFactories().stream()
.map(provider -> {
.filter(factory -> !factory.isInternal())
.map(factory -> {
PolicyProviderRepresentation representation = new PolicyProviderRepresentation();
representation.setName(provider.getName());
representation.setGroup(provider.getGroup());
representation.setType(provider.getId());
representation.setName(factory.getName());
representation.setGroup(factory.getGroup());
representation.setType(factory.getId());
return representation;
})

View file

@ -164,17 +164,6 @@ public class ResourceSetService {
return Response.status(Status.NOT_FOUND).build();
}
PolicyStore policyStore = storeFactory.getPolicyStore();
List<Policy> policies = policyStore.findByResource(id, resourceServer.getId());
for (Policy policyModel : policies) {
if (policyModel.getResources().size() == 1) {
policyStore.delete(policyModel.getId());
} else {
policyModel.removeResource(resource);
}
}
storeFactory.getResourceStore().delete(id);
if (authorization.getRealm().isAdminEventsEnabled()) {
@ -254,7 +243,8 @@ public class ResourceSetService {
public Response getPermissions(@PathParam("id") String id) {
requireView();
StoreFactory storeFactory = authorization.getStoreFactory();
Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
ResourceStore resourceStore = storeFactory.getResourceStore();
Resource model = resourceStore.findById(id, resourceServer.getId());
if (model == null) {
return Response.status(Status.NOT_FOUND).build();
@ -264,21 +254,36 @@ public class ResourceSetService {
Set<Policy> policies = new HashSet<>();
policies.addAll(policyStore.findByResource(model.getId(), resourceServer.getId()));
policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
if (model.getType() != null) {
policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
HashMap<String, String[]> resourceFilter = new HashMap<>();
resourceFilter.put("owner", new String[]{resourceServer.getId()});
resourceFilter.put("type", new String[]{model.getType()});
for (Resource resourceType : resourceStore.findByResourceServer(resourceFilter, resourceServer.getId(), -1, -1)) {
policies.addAll(policyStore.findByResource(resourceType.getId(), resourceServer.getId()));
}
}
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), id, resourceServer.getId()));
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), null, resourceServer.getId()));
List<PolicyRepresentation> representation = new ArrayList<>();
for (Policy policyModel : policies) {
PolicyRepresentation policy = new PolicyRepresentation();
if (!"uma".equalsIgnoreCase(policyModel.getType())) {
PolicyRepresentation policy = new PolicyRepresentation();
policy.setId(policyModel.getId());
policy.setName(policyModel.getName());
policy.setType(policyModel.getType());
policy.setId(policyModel.getId());
policy.setName(policyModel.getName());
policy.setType(policyModel.getType());
if (!representation.contains(policy)) {
representation.add(policy);
if (!representation.contains(policy)) {
representation.add(policy);
}
}
}

View file

@ -23,7 +23,10 @@ import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
@ -182,7 +185,8 @@ public class PermissionTicketService {
@QueryParam("returnNames") Boolean returnNames,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
PermissionTicketStore permissionTicketStore = authorization.getStoreFactory().getPermissionTicketStore();
StoreFactory storeFactory = authorization.getStoreFactory();
PermissionTicketStore permissionTicketStore = storeFactory.getPermissionTicketStore();
Map<String, String> filters = new HashMap<>();
@ -191,15 +195,22 @@ public class PermissionTicketService {
}
if (scopeId != null) {
filters.put(PermissionTicket.SCOPE, scopeId);
ScopeStore scopeStore = storeFactory.getScopeStore();
Scope scope = scopeStore.findById(scopeId, resourceServer.getId());
if (scope == null) {
scope = scopeStore.findByName(scopeId, resourceServer.getId());
}
filters.put(PermissionTicket.SCOPE, scope != null ? scope.getId() : scopeId);
}
if (owner != null) {
filters.put(PermissionTicket.OWNER, owner);
filters.put(PermissionTicket.OWNER, getUserId(owner));
}
if (requester != null) {
filters.put(PermissionTicket.REQUESTER, requester);
filters.put(PermissionTicket.REQUESTER, getUserId(requester));
}
if (granted != null) {
@ -212,4 +223,22 @@ public class PermissionTicketService {
.collect(Collectors.toList()))
.build();
}
private String getUserId(String userIdOrName) {
UserProvider userProvider = authorization.getKeycloakSession().users();
RealmModel realm = authorization.getRealm();
UserModel userModel = userProvider.getUserById(userIdOrName, realm);
if (userModel != null) {
return userModel.getId();
}
userModel = userProvider.getUserByUsername(userIdOrName, realm);
if (userModel != null) {
return userModel.getId();
}
return userIdOrName;
}
}

View file

@ -20,6 +20,7 @@ package org.keycloak.authorization.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@ -33,6 +34,7 @@ import javax.ws.rs.core.Response.Status;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect;
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;
@ -71,9 +73,22 @@ public final class Permissions {
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)));
// 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)));
// 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));
}
permissions.addAll(userManagedPermissions.values());
return permissions;
}
@ -156,7 +171,9 @@ public final class Permissions {
boolean resourceDenied = false;
ResourcePermission permission = result.getPermission();
List<Result.PolicyResult> results = result.getResults();
List<Result.PolicyResult> userManagedPermissions = new ArrayList<>();
int deniedCount = results.size();
Resource resource = permission.getResource();
for (Result.PolicyResult policyResult : results) {
Policy policy = policyResult.getPolicy();
@ -175,6 +192,8 @@ public final class Permissions {
// Later they will be filtered based on any denied scope, if any.
// TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
grantedScopes.addAll(permission.getScopes());
} if (resource.isOwnerManagedAccess() && "uma".equals(policy.getType())) {
userManagedPermissions.add(policyResult);
}
deniedCount--;
} else {
@ -183,7 +202,7 @@ public final class Permissions {
deniedScopes.addAll(policyScopes);
} else if (isResourcePermission(policy)) {
resourceDenied = true;
deniedScopes.addAll(permission.getResource().getScopes());
deniedScopes.addAll(resource.getScopes());
}
}
}
@ -193,6 +212,14 @@ public final class Permissions {
grantedScopes.removeAll(deniedScopes);
}
for (Result.PolicyResult policyResult : userManagedPermissions) {
Policy policy = policyResult.getPolicy();
grantedScopes.addAll(policy.getScopes());
resourceDenied = false;
}
// if there are no policy results is because the permission didn't match any policy.
// In this case, if results is empty is because we are in permissive mode.
if (!results.isEmpty()) {

View file

@ -23,7 +23,9 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.function.Supplier;
@ -40,12 +42,16 @@ import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.common.util.Base64Url;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessToken.Authorization;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.PermissionResponse;
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.util.ClientBuilder;
@ -390,6 +396,108 @@ public class EntitlementAPITest extends AbstractAuthzTest {
PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
@Test
public void testObtainAllEntitlements() throws Exception {
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
AuthorizationResource authorization = client.authorization();
JSPolicyRepresentation policy = new JSPolicyRepresentation();
policy.setName("Only Owner Policy");
policy.setCode("if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
authorization.policies().js().create(policy).close();
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Marta Resource");
resource.setOwner("marta");
resource.setOwnerManagedAccess(true);
resource = authorization.resources().create(resource).readEntity(ResourceRepresentation.class);
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName("Marta Resource Permission");
permission.addResource(resource.getId());
permission.addPolicy(policy.getName());
authorization.permissions().resource().create(permission);
assertTrue(hasPermission("marta", "password", resource.getId()));
assertFalse(hasPermission("kolo", "password", resource.getId()));
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG);
PermissionResponse permissionResponse = authzClient.protection().permission().create(new PermissionRequest(resource.getId()));
AuthorizationRequest request = new AuthorizationRequest();
request.setTicket(permissionResponse.getTicket());
try {
authzClient.authorization(accessToken).authorize(request);
} catch (Exception ignore) {
}
List<PermissionTicketRepresentation> tickets = authzClient.protection().permission().findByResource(resource.getId());
assertEquals(1, tickets.size());
PermissionTicketRepresentation ticket = tickets.get(0);
ticket.setGranted(true);
authzClient.protection().permission().update(ticket);
assertTrue(hasPermission("kolo", "password", resource.getId()));
resource.addScope("Scope A");
authorization.resources().resource(resource.getId()).update(resource);
// the addition of a new scope invalidates the permission previously grante to the resource
assertFalse(hasPermission("kolo", "password", resource.getId()));
accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
permissionResponse = authzClient.protection().permission().create(new PermissionRequest(resource.getId(), "Scope A"));
request = new AuthorizationRequest();
request.setTicket(permissionResponse.getTicket());
try {
authzClient.authorization(accessToken).authorize(request);
} catch (Exception ignore) {
}
tickets = authzClient.protection().permission().find(resource.getId(), "Scope A", null, null, false, false, null, null);
assertEquals(1, tickets.size());
ticket = tickets.get(0);
ticket.setGranted(true);
authzClient.protection().permission().update(ticket);
assertTrue(hasPermission("kolo", "password", resource.getId(), "Scope A"));
resource.addScope("Scope B");
authorization.resources().resource(resource.getId()).update(resource);
assertTrue(hasPermission("kolo", "password", resource.getId()));
assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B"));
resource.setScopes(new HashSet<>());
authorization.resources().resource(resource.getId()).update(resource);
assertTrue(hasPermission("kolo", "password", resource.getId()));
assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope A"));
}
public void testResourceServerAsAudience(String testClientId, String resourceServerClientId, String configFile) throws Exception {
AuthorizationRequest request = new AuthorizationRequest();
@ -402,6 +510,29 @@ public class EntitlementAPITest extends AbstractAuthzTest {
assertEquals(resourceServerClientId, rpt.getAudience()[0]);
}
private boolean hasPermission(String userName, String password, String resourceId, String... scopeIds) throws Exception {
String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", userName, password).getAccessToken();
AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest());
AccessToken rpt = toAccessToken(response.getToken());
Authorization authz = rpt.getAuthorization();
List<Permission> permissions = authz.getPermissions();
assertNotNull(permissions);
assertFalse(permissions.isEmpty());
for (Permission grantedPermission : permissions) {
if (grantedPermission.getResourceId().equals(resourceId)) {
return scopeIds == null || scopeIds.length == 0 || grantedPermission.getScopes().containsAll(Arrays.asList(scopeIds));
}
}
return false;
}
private boolean hasPermission(String userName, String password, String resourceId) throws Exception {
return hasPermission(userName, password, resourceId, null);
}
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();

View file

@ -0,0 +1,109 @@
/*
* 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.assertNotNull;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.util.AdminClientUtil;
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;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class GroupPathWithoutGroupClaimPolicyTest extends GroupPathPolicyTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
ProtocolMapperRepresentation groupProtocolMapper = new ProtocolMapperRepresentation();
groupProtocolMapper.setName("groups");
groupProtocolMapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
groupProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
groupProtocolMapper.setConsentRequired(false);
Map<String, String> config = new HashMap<>();
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
groupProtocolMapper.setConfig(config);
testRealms.add(RealmBuilder.create().name("authz-test")
.roles(RolesBuilder.create()
.realmRole(RoleBuilder.create().name("uma_authorization").build())
)
.group(GroupBuilder.create().name("Group A")
.subGroups(Arrays.asList("Group B", "Group D").stream().map(name -> {
if ("Group B".equals(name)) {
return GroupBuilder.create().name(name).subGroups(Arrays.asList("Group C", "Group E").stream().map(new Function<String, GroupRepresentation>() {
@Override
public GroupRepresentation apply(String name) {
return GroupBuilder.create().name(name).build();
}
}).collect(Collectors.toList())).build();
}
return GroupBuilder.create().name(name).build();
}).collect(Collectors.toList())).build())
.group(GroupBuilder.create().name("Group E").build())
.user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization").addGroups("Group A"))
.user(UserBuilder.create().username("alice").password("password").addRoles("uma_authorization"))
.user(UserBuilder.create().username("kolo").password("password").addRoles("uma_authorization"))
.client(ClientBuilder.create().clientId("resource-server-test")
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
.build());
}
}

View file

@ -101,9 +101,20 @@ public class PermissionManagementTest extends AbstractResourceServerTest {
@Test
public void testDeleteResourceAndPermissionTicket() throws Exception {
ResourceRepresentation resource = addResource("Resource A", true);
PermissionResponse response = getAuthzClient().protection().permission().create(new PermissionRequest(resource.getName()));
assertNotNull(response.getTicket());
ResourceRepresentation resource = addResource("Resource A", "kolo", true, "ScopeA", "ScopeB", "ScopeC");
AuthzClient authzClient = getAuthzClient();
PermissionResponse response = authzClient.protection("marta", "password").permission().create(new PermissionRequest(resource.getId(), "ScopeA", "ScopeB", "ScopeC"));
AuthorizationRequest request = new AuthorizationRequest();
request.setTicket(response.getTicket());
request.setClaimToken(authzClient.obtainAccessToken("marta", "password").getToken());
try {
authzClient.authorization().authorize(request);
} catch (Exception e) {
}
assertPersistence(response, resource, "ScopeA", "ScopeB", "ScopeC");
getAuthzClient().protection().resource().delete(resource.getId());
assertTrue(getAuthzClient().protection().permission().findByResource(resource.getId()).isEmpty());

View file

@ -665,6 +665,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
}
return baseAttributes;
}
}, policy, policy, evaluation -> {}, authorization);
}, policy, evaluation -> {}, authorization);
}
}

View file

@ -56,7 +56,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
JSPolicyRepresentation policy = new JSPolicyRepresentation();
policy.setName("Only Owner Policy");
policy.setCode("print($evaluation.getPermission().getResource().getOwner());print($evaluation.getContext().getIdentity().getId());if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
policy.setCode("if ($evaluation.getContext().getIdentity().getId() == $evaluation.getPermission().getResource().getOwner()) {$evaluation.grant();}");
Response response = authorization.policies().js().create(policy);
response.close();
@ -98,6 +98,90 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
}
}
/**
* Makes sure permissions granted to a typed resource instance does not grant access to resource instances with the same type.
*
* @throws Exception
*/
@Test
public void testOnlyOwnerCanAccessResourceWithType() throws Exception {
ResourceRepresentation typedResource = addResource("Typed Resource", getClient(getRealm()).toRepresentation().getId(), false, "ScopeA", "ScopeB");
typedResource.setType("my:resource");
getClient(getRealm()).authorization().resources().resource(typedResource.getId()).update(typedResource);
resource = addResource("Resource A", "marta", true, "ScopeA", "ScopeB");
resource.setType(typedResource.getType());
getClient(getRealm()).authorization().resources().resource(resource.getId()).update(resource);
ResourceRepresentation resourceB = addResource("Resource B", "marta", true, "ScopeA", "ScopeB");
resourceB.setType(typedResource.getType());
getClient(getRealm()).authorization().resources().resource(resourceB.getId()).update(resourceB);
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName(resource.getType() + " Permission");
permission.setResourceType(resource.getType());
permission.addPolicy("Only Owner Policy");
getClient(getRealm()).authorization().permissions().resource().create(permission).close();
AuthorizationResponse response = authorize("marta", "password", resource.getName(), new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken();
assertNotNull(rpt);
assertFalse(response.isUpgraded());
AccessToken accessToken = toAccessToken(rpt);
AccessToken.Authorization authorization = accessToken.getAuthorization();
assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions);
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
assertTrue(permissions.isEmpty());
try {
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"});
fail("User should not have access to resource from another user");
} catch (AuthorizationDeniedException ade) {
}
List<PermissionTicketRepresentation> tickets = getAuthzClient().protection().permission().find(resource.getId(), null, null, null, null, null, null, null);
for (PermissionTicketRepresentation ticket : tickets) {
ticket.setGranted(true);
getAuthzClient().protection().permission().update(ticket);
}
try {
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA", "ScopeB"});
} catch (AuthorizationDeniedException ade) {
fail("User should have access to resource from another user");
}
permissions = authorization.getPermissions();
assertNotNull(permissions);
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
assertTrue(permissions.isEmpty());
try {
response = authorize("kolo", "password", resourceB.getId(), new String[] {"ScopeA", "ScopeB"});
fail("User should not have access to resource from another user");
} catch (AuthorizationDeniedException ade) {
}
}
@Test
public void testUserGrantsAccessToResource() throws Exception {
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
@ -306,7 +390,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
try {
response = authorize("kolo", "password", resource.getId(), new String[] {"ScopeA"});
fail("User should have access to resource from another user");
fail("User should not have access to resource from another user");
} catch (AuthorizationDeniedException ade) {
}