keycloak-uncached
Changes
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java 9(+7 -2)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java 29(+22 -7)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java 22(+5 -17)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProviderFactory.java 18(+17 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java 24(+24 -0)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProviderFactory.java 18(+17 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProvider.java 24(+24 -0)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java 128(+128 -0)
authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory 7(+4 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPermissionTicket.java 9(+9 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java 5(+5 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketRemovedEvent.java 6(+4 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketUpdatedEvent.java 6(+4 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java 18(+16 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java 14(+13 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java 7(+4 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 25(+19 -6)
model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PermissionTicketEntity.java 12(+12 -0)
model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java 54(+48 -6)
model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java 32(+27 -5)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java 21(+19 -2)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java 23(+10 -13)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java 5(+5 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java 52(+3 -49)
server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java 4(+4 -0)
server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java 9(+9 -0)
server-spi-private/src/main/java/org/keycloak/authorization/UserManagedPermissionUtil.java 129(+129 -0)
services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java 37(+33 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java 131(+131 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathWithoutGroupClaimPolicyTest.java 109(+109 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java 17(+14 -3)
Details
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
index c21b4c0..5f4fcd8 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
@@ -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()) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java
new file mode 100644
index 0000000..3f4f9d9
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java
@@ -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 ScopePolicyProvider extends AbstractPermissionProvider {
+
+}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProvider.java
new file mode 100644
index 0000000..50f6993
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProvider.java
@@ -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 {
+
+}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java
new file mode 100644
index 0000000..3d11753
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/UMAPolicyProviderFactory.java
@@ -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";
+ }
+}
diff --git a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
index e6fa1cc..2d8c4f3 100644
--- a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
+++ b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
@@ -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
\ No newline at end of file
+org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
+org.keycloak.authorization.policy.provider.permission.UMAPolicyProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPermissionTicket.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPermissionTicket.java
index a906a7d..40631b2 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPermissionTicket.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPermissionTicket.java
@@ -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;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
index 11d6e64..643c868 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
@@ -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;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketRemovedEvent.java
index bbef979..f5296e6 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketRemovedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketRemovedEvent.java
@@ -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);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketUpdatedEvent.java
index 1d830ed..3323531 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketUpdatedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PermissionTicketUpdatedEvent.java
@@ -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);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java
index d6a7e07..3ef19cf 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java
@@ -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);
}
@@ -123,6 +124,19 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel<Pe
}
@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());
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
index c75ffa4..4b34cf9 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
@@ -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
@@ -279,6 +278,19 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
}
@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;
if (o == null || !(o instanceof Policy)) return false;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
index 4f59cdb..e6ec3d8 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
@@ -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) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
index e54316d..c3a1424 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
@@ -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);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
index 5924573..b08629a 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
@@ -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);
}
@@ -909,6 +915,13 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
}
@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);
return cacheQuery(cacheKey, PermissionTicketListQuery.class, () -> getPermissionTicketStoreDelegate().findByOwner(owner, resourceServerId),
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PermissionTicketEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PermissionTicketEntity.java
index 1cd8d07..4bf334c 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PermissionTicketEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PermissionTicketEntity.java
@@ -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;
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
index 984c6ba..e235f58 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
@@ -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;
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
index 8c9960b..8b1d903 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
@@ -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")
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
index ee51d5e..3ab4da9 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java
@@ -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,22 +218,36 @@ 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;
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
index 139973c..021f451 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
@@ -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);
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index b9cecf7..7f6338d 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -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();
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java
index e1c56a2..6f66a2e 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java
@@ -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
@@ -90,6 +94,24 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel<Permi
}
@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());
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
index b789165..98651da 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
@@ -208,6 +208,16 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
}
@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;
if (o == null || !(o instanceof Policy)) return false;
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
new file mode 100755
index 0000000..e5073da
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-authz-4.0.0.Beta3.xml
@@ -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>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index c9d0e16..554df64 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -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>
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
index 88c898b..57f4c93 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -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);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
index 39366d6..493bfc2 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java
@@ -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);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
index f46f61a..0dafea9 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
@@ -148,6 +148,10 @@ public interface Policy {
*/
Set<Scope> getScopes();
+ String getOwner();
+
+ void setOwner(String owner);
+
void addScope(Scope scope);
void removeScope(Scope scope);
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
index cbf23cb..e92dbba 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
@@ -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;
+ }
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index 0cc0622..24127d6 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -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() + "].");
- }
+ if (policyProvider == null) {
+ 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();
- }
+ policyProvider.evaluate(evaluation);
verified.compareAndSet(false, true);
};
}
- private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy, Policy associatedPolicy) {
- return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision, authorization);
+ private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy) {
+ return new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorization);
}
private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
index 7fd6566..cdc0017 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java
@@ -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();
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
index df7c705..d86888d 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
@@ -58,62 +58,16 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
}
@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();
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) {
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
index d795d8a..1d353fd 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
@@ -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);
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java
index 654d68b..a561712 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java
@@ -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);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/UserManagedPermissionUtil.java b/server-spi-private/src/main/java/org/keycloak/authorization/UserManagedPermissionUtil.java
new file mode 100644
index 0000000..711d4d7
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/UserManagedPermissionUtil.java
@@ -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;
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 1deada1..fe449cd 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -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;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index e835ec6..cb87631 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -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;
})
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
index edde9cc..5e2ba95 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
@@ -164,17 +164,6 @@ public class ResourceSetService {
return Response.status(Status.NOT_FOUND).build();
}
- PolicyStore policyStore = storeFactory.getPolicyStore();
- List<Policy> policies = policyStore.findByResource(id, resourceServer.getId());
-
- for (Policy policyModel : policies) {
- if (policyModel.getResources().size() == 1) {
- policyStore.delete(policyModel.getId());
- } else {
- policyModel.removeResource(resource);
- }
- }
-
storeFactory.getResourceStore().delete(id);
if (authorization.getRealm().isAdminEventsEnabled()) {
@@ -254,7 +243,8 @@ public class ResourceSetService {
public Response getPermissions(@PathParam("id") String id) {
requireView();
StoreFactory storeFactory = authorization.getStoreFactory();
- Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId());
+ ResourceStore resourceStore = storeFactory.getResourceStore();
+ Resource model = resourceStore.findById(id, resourceServer.getId());
if (model == null) {
return Response.status(Status.NOT_FOUND).build();
@@ -264,21 +254,36 @@ public class ResourceSetService {
Set<Policy> policies = new HashSet<>();
policies.addAll(policyStore.findByResource(model.getId(), resourceServer.getId()));
- policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
+
+ if (model.getType() != null) {
+ policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId()));
+
+ HashMap<String, String[]> resourceFilter = new HashMap<>();
+
+ resourceFilter.put("owner", new String[]{resourceServer.getId()});
+ resourceFilter.put("type", new String[]{model.getType()});
+
+ for (Resource resourceType : resourceStore.findByResourceServer(resourceFilter, resourceServer.getId(), -1, -1)) {
+ policies.addAll(policyStore.findByResource(resourceType.getId(), resourceServer.getId()));
+ }
+ }
+
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), id, resourceServer.getId()));
policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), null, resourceServer.getId()));
List<PolicyRepresentation> representation = new ArrayList<>();
for (Policy policyModel : policies) {
- PolicyRepresentation policy = new PolicyRepresentation();
+ if (!"uma".equalsIgnoreCase(policyModel.getType())) {
+ PolicyRepresentation policy = new PolicyRepresentation();
- policy.setId(policyModel.getId());
- policy.setName(policyModel.getName());
- policy.setType(policyModel.getType());
+ policy.setId(policyModel.getId());
+ policy.setName(policyModel.getName());
+ policy.setType(policyModel.getType());
- if (!representation.contains(policy)) {
- representation.add(policy);
+ if (!representation.contains(policy)) {
+ representation.add(policy);
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
index 951a780..59ff1fd 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/PermissionTicketService.java
@@ -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;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 38b65b8..180ec33 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -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()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
index 167761f..60bc6f8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -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();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathWithoutGroupClaimPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathWithoutGroupClaimPolicyTest.java
new file mode 100644
index 0000000..3936308
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupPathWithoutGroupClaimPolicyTest.java
@@ -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());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
index 9d47f8d..aa9ce50 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
@@ -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());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
index bedd4a7..1e5ec8f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
@@ -665,6 +665,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
}
return baseAttributes;
}
- }, policy, policy, evaluation -> {}, authorization);
+ }, policy, evaluation -> {}, authorization);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
index 5ef774e..7274ca5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
@@ -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) {
}