Merge pull request #5211 from pedroigor/KEYCLOAK-7367
[KEYCLOAK-7367] - User-Managed Policy Provider
This commit is contained in:
commit
f8919f8baa
48 changed files with 1125 additions and 176 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
33
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
Executable file
33
model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
Executable 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>
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -148,6 +148,10 @@ public interface Policy {
|
|||
*/
|
||||
Set<Scope> getScopes();
|
||||
|
||||
String getOwner();
|
||||
|
||||
void setOwner(String owner);
|
||||
|
||||
void addScope(Scope scope);
|
||||
|
||||
void removeScope(Scope scope);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() + "].");
|
||||
throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
|
||||
}
|
||||
|
||||
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy);
|
||||
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy);
|
||||
|
||||
policyProvider.evaluate(evaluation);
|
||||
evaluation.denyIfNoEffect();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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,13 +254,27 @@ public class ResourceSetService {
|
|||
Set<Policy> policies = new HashSet<>();
|
||||
|
||||
policies.addAll(policyStore.findByResource(model.getId(), 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) {
|
||||
if (!"uma".equalsIgnoreCase(policyModel.getType())) {
|
||||
PolicyRepresentation policy = new PolicyRepresentation();
|
||||
|
||||
policy.setId(policyModel.getId());
|
||||
|
@ -281,6 +285,7 @@ public class ResourceSetService {
|
|||
representation.add(policy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(representation).build();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -665,6 +665,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
|
|||
}
|
||||
return baseAttributes;
|
||||
}
|
||||
}, policy, policy, evaluation -> {}, authorization);
|
||||
}, policy, evaluation -> {}, authorization);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue