From 8e877a7f6ce8f8fc3ef1d3820101a5d3671b35bf Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 12 Apr 2017 14:34:27 -0300 Subject: [PATCH] [KEYCLOAK-3135] - More tests --- .../user/UserPolicyProviderFactory.java | 37 ++-- .../RolePolicyRepresentation.java | 12 ++ .../infinispan/CachedResourceStore.java | 16 +- .../InfinispanStoreFactoryProvider.java | 20 +- .../InfinispanStoreProviderFactory.java | 7 +- .../store/JPAAuthorizationStoreFactory.java | 7 +- .../jpa/store/JPAPolicyStore.java | 67 +++---- .../jpa/store/JPAStoreFactory.java | 27 +-- .../authorization/AuthorizationProvider.java | 102 ++++++++++- .../evaluation/DefaultPolicyEvaluator.java | 5 +- .../store/AuthorizationStoreFactory.java | 9 +- .../CachedStoreProviderFactory.java | 3 - .../models/utils/RepresentationToModel.java | 104 ++--------- .../DefaultAuthorizationProviderFactory.java | 19 +- .../admin/PolicyResourceService.java | 11 +- .../authorization/admin/PolicyService.java | 11 +- .../integration-arquillian/tests/base/pom.xml | 1 + .../AbstractServletAuthzAdapterTest.java | 127 ++++++------- .../testsuite/admin/AuthzCleanupTest.java | 46 +++-- .../AbstractPermissionManagementTest.java | 2 +- .../RolePolicyManagementTest.java | 32 +++- .../UserPolicyManagementTest.java | 172 ++++++++++++++++++ 22 files changed, 498 insertions(+), 339 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java index 715c9e879b..2d3c219111 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java @@ -39,6 +39,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.UserRemovedEvent; +import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.util.JsonSerialization; @@ -112,30 +113,36 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory updatedRoles = new HashSet<>(); + UserProvider userProvider = session.users(); + Set updatedUsers = new HashSet<>(); if (users != null) { - for (String userId : users) { - UserModel user = session.users().getUserByUsername(userId, realm); - - if (user == null) { - user = session.users().getUserById(userId, realm); - } - - if (user == null) { - throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found."); - } - - updatedRoles.add(user.getId()); - } try { + for (String userId : users) { + UserModel user = null; + + try { + user = userProvider.getUserByUsername(userId, realm); + } catch (Exception ignore) { + } + + if (user == null) { + user = userProvider.getUserById(userId, realm); + } + + if (user == null) { + throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found."); + } + + updatedUsers.add(user.getId()); + } } catch (Exception e) { throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e); } } Map config = policy.getConfig(); - config.put("users", JsonSerialization.writeValueAsString(updatedRoles)); + config.put("users", JsonSerialization.writeValueAsString(updatedUsers)); policy.setConfig(config); } catch (IOException cause) { throw new RuntimeException("Failed to deserialize roles", cause); diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java index 7dd7c57663..2a89710623 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/RolePolicyRepresentation.java @@ -41,6 +41,18 @@ public class RolePolicyRepresentation extends AbstractPolicyRepresentation { roles.add(new RoleDefinition(name, required)); } + public void addRole(String name) { + addRole(name, false); + } + + public void addClientRole(String clientId, String name) { + addRole(clientId + "/" +name, false); + } + + public void addClientRole(String clientId, String name, boolean required) { + addRole(clientId + "/" + name, required); + } + public static class RoleDefinition { private String id; diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index 6c72f25cc5..e820ab0ffa 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -49,16 +49,14 @@ public class CachedResourceStore implements ResourceStore { private static final String RESOURCE_ID_CACHE_PREFIX = "rsc-id-"; private static final String RESOURCE_NAME_CACHE_PREFIX = "rsc-name-"; - private final KeycloakSession session; private final CachedStoreFactoryProvider cacheStoreFactory; private final CacheTransaction transaction; private final List cacheKeys; - private StoreFactory storeFactory; + private StoreFactory delegateStoreFactory; private ResourceStore delegate; private final Cache>> cache; public CachedResourceStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { - this.session = session; this.cacheStoreFactory = cacheStoreFactory; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); @@ -67,12 +65,12 @@ public class CachedResourceStore implements ResourceStore { cacheKeys.add("findByOwner"); cacheKeys.add("findByUri"); cacheKeys.add("findByName"); - this.storeFactory = delegate; + this.delegateStoreFactory = delegate; } @Override public Resource create(String name, ResourceServer resourceServer, String owner) { - Resource resource = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); + Resource resource = getDelegate().create(name, getDelegateStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); this.transaction.whenRollback(() -> { resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResource(resource.getId())); @@ -178,14 +176,14 @@ public class CachedResourceStore implements ResourceStore { private ResourceStore getDelegate() { if (this.delegate == null) { - this.delegate = getStoreFactory().getResourceStore(); + this.delegate = getDelegateStoreFactory().getResourceStore(); } return this.delegate; } - private StoreFactory getStoreFactory() { - return this.storeFactory; + private StoreFactory getDelegateStoreFactory() { + return this.delegateStoreFactory; } private Resource createAdapter(CachedResource cached) { @@ -272,7 +270,7 @@ public class CachedResourceStore implements ResourceStore { @Override public void updateScopes(Set scopes) { - getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); + getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getDelegateStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); cached.updateScopes(scopes); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index 25507b0094..bbc38482b1 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -21,8 +21,6 @@ package org.keycloak.models.authorization.infinispan; import java.util.ArrayList; import java.util.List; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.store.AuthorizationStoreFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; @@ -37,24 +35,20 @@ import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; */ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvider { - private final KeycloakSession session; private final CacheTransaction transaction; - private final StoreFactory delegate; private final CachedResourceStore resourceStore; private final CachedScopeStore scopeStore; private final CachedPolicyStore policyStore; private ResourceServerStore resourceServerStore; - public InfinispanStoreFactoryProvider(AuthorizationProvider authorizationProvider) { - this.session = authorizationProvider.getKeycloakSession(); + public InfinispanStoreFactoryProvider(KeycloakSession session) { this.transaction = new CacheTransaction(); - this.session.getTransactionManager().enlistAfterCompletion(transaction); - AuthorizationStoreFactory providerFactory = (AuthorizationStoreFactory) this.session.getKeycloakSessionFactory().getProviderFactory(StoreFactory.class); - delegate = providerFactory.create(authorizationProvider); - resourceStore = new CachedResourceStore(this.session, this, this.transaction, delegate); - resourceServerStore = new CachedResourceServerStore(this.session, this.transaction, delegate); - scopeStore = new CachedScopeStore(this.session, this, this.transaction, delegate); - policyStore = new CachedPolicyStore(this.session, this, this.transaction, delegate); + session.getTransactionManager().enlistAfterCompletion(transaction); + StoreFactory delegate = session.getProvider(StoreFactory.class); + resourceStore = new CachedResourceStore(session, this, this.transaction, delegate); + resourceServerStore = new CachedResourceServerStore(session, this.transaction, delegate); + scopeStore = new CachedScopeStore(session, this, this.transaction, delegate); + policyStore = new CachedPolicyStore(session, this, this.transaction, delegate); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java index d3df3db334..ad58890f3a 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java @@ -33,7 +33,7 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory; public class InfinispanStoreProviderFactory implements CachedStoreProviderFactory, EnvironmentDependentProviderFactory { @Override public CachedStoreFactoryProvider create(KeycloakSession session) { - return null; + return new InfinispanStoreFactoryProvider(session); } @Override @@ -60,9 +60,4 @@ public class InfinispanStoreProviderFactory implements CachedStoreProviderFactor public boolean isSupported() { return true; } - - @Override - public StoreFactory create(AuthorizationProvider authorizationProvider) { - return new InfinispanStoreFactoryProvider(authorizationProvider); - } } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java index 9885c243a3..87508071cc 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAAuthorizationStoreFactory.java @@ -33,7 +33,7 @@ import org.keycloak.models.KeycloakSession; public class JPAAuthorizationStoreFactory implements AuthorizationStoreFactory { @Override public StoreFactory create(KeycloakSession session) { - return null; + return new JPAStoreFactory(getEntityManager(session)); } @Override @@ -54,9 +54,4 @@ public class JPAAuthorizationStoreFactory implements AuthorizationStoreFactory { private EntityManager getEntityManager(KeycloakSession session) { return session.getProvider(JpaConnectionProvider.class).getEntityManager(); } - - @Override - public StoreFactory create(AuthorizationProvider authorizationProvider) { - return new JPAStoreFactory(getEntityManager(authorizationProvider.getKeycloakSession()), authorizationProvider); - } } 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 6b30961564..c6671de0e5 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 @@ -17,18 +17,10 @@ */ package org.keycloak.authorization.jpa.store; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.jpa.entities.PolicyEntity; -import org.keycloak.authorization.jpa.entities.ResourceServerEntity; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.policy.provider.PolicyProviderFactory; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.RepresentationToModel; -import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; -import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.NoResultException; @@ -37,10 +29,14 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; + +import org.keycloak.authorization.jpa.entities.PolicyEntity; +import org.keycloak.authorization.jpa.entities.ResourceServerEntity; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; /** * @author Pedro Igor @@ -48,13 +44,9 @@ import java.util.Map; public class JPAPolicyStore implements PolicyStore { private final EntityManager entityManager; - private final StoreFactory storeFactory; - private final AuthorizationProvider authorization; - public JPAPolicyStore(EntityManager entityManager, JPAStoreFactory jpaStoreFactory, AuthorizationProvider authorization) { + public JPAPolicyStore(EntityManager entityManager) { this.entityManager = entityManager; - this.storeFactory = jpaStoreFactory; - this.authorization = authorization; } @Override @@ -62,30 +54,15 @@ public class JPAPolicyStore implements PolicyStore { PolicyEntity entity = new PolicyEntity(); entity.setId(KeycloakModelUtils.generateId()); - entity.setResourceServer((ResourceServerEntity) resourceServer); entity.setType(representation.getType()); entity.setName(representation.getName()); + entity.setResourceServer((ResourceServerEntity) resourceServer); this.entityManager.persist(entity); - - entity = (PolicyEntity) RepresentationToModel.toModel(representation, storeFactory, entity); - - PolicyProviderFactory provider = authorization.getProviderFactory(entity.getType()); - - if (representation instanceof PolicyRepresentation) { - provider.onImport(entity, PolicyRepresentation.class.cast(representation), authorization); - } else { - provider.onCreate(entity, representation, authorization); - } - - + this.entityManager.flush(); return entity; } - public EntityManager getEntityManager() { - return this.entityManager; - } - @Override public void delete(String id) { Policy policy = entityManager.find(PolicyEntity.class, id); @@ -117,7 +94,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public Policy findByName(String name, String resourceServerId) { try { - Query query = getEntityManager().createQuery("from PolicyEntity where name = :name and resourceServer.id = :serverId"); + Query query = entityManager.createQuery("from PolicyEntity where name = :name and resourceServer.id = :serverId"); query.setParameter("name", name); query.setParameter("serverId", resourceServerId); @@ -130,7 +107,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResourceServer(final String resourceServerId) { - Query query = getEntityManager().createQuery("from PolicyEntity where resourceServer.id = :serverId"); + Query query = entityManager.createQuery("from PolicyEntity where resourceServer.id = :serverId"); query.setParameter("serverId", resourceServerId); @@ -176,7 +153,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResource(final String resourceId, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"); query.setParameter("resourceId", resourceId); query.setParameter("serverId", resourceServerId); @@ -186,7 +163,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByResourceType(final String resourceType, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"); query.setParameter("serverId", resourceServerId); query.setParameter("type", resourceType); @@ -201,7 +178,7 @@ public class JPAPolicyStore implements PolicyStore { } // Use separate subquery to handle DB2 and MSSSQL - Query query = getEntityManager().createQuery("select pe from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"); + Query query = entityManager.createQuery("select pe from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"); query.setParameter("serverId", resourceServerId); query.setParameter("scopeIds", scopeIds); @@ -211,7 +188,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findByType(String type, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"); + Query query = entityManager.createQuery("select p from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"); query.setParameter("serverId", resourceServerId); query.setParameter("type", type); @@ -221,7 +198,7 @@ public class JPAPolicyStore implements PolicyStore { @Override public List findDependentPolicies(String policyId, String resourceServerId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"); + Query query = entityManager.createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"); query.setParameter("serverId", resourceServerId); query.setParameter("policyId", policyId); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java index 8bf2e378e6..e45d343af2 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAStoreFactory.java @@ -18,46 +18,49 @@ package org.keycloak.authorization.jpa.store; -import org.keycloak.authorization.AuthorizationProvider; +import javax.persistence.EntityManager; + import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; -import javax.persistence.EntityManager; - /** * @author Pedro Igor */ public class JPAStoreFactory implements StoreFactory { - private final EntityManager entityManager; - private final AuthorizationProvider authorization; + private final PolicyStore policyStore; + private final ResourceServerStore resourceServerStore; + private final ResourceStore resourceStore; + private final ScopeStore scopeStore; - public JPAStoreFactory(EntityManager entityManager, AuthorizationProvider authorizationProvider) { - this.entityManager = entityManager; - this.authorization = authorizationProvider; + public JPAStoreFactory(EntityManager entityManager) { + policyStore = new JPAPolicyStore(entityManager); + resourceServerStore = new JPAResourceServerStore(entityManager); + resourceStore = new JPAResourceStore(entityManager); + scopeStore = new JPAScopeStore(entityManager); } @Override public PolicyStore getPolicyStore() { - return new JPAPolicyStore(entityManager, this, authorization); + return policyStore; } @Override public ResourceServerStore getResourceServerStore() { - return new JPAResourceServerStore(this.entityManager); + return resourceServerStore; } @Override public ResourceStore getResourceStore() { - return new JPAResourceStore(this.entityManager); + return resourceStore; } @Override public ScopeStore getScopeStore() { - return new JPAScopeStore(this.entityManager); + return scopeStore; } @Override 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 ff2019a923..76e6894943 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 @@ -22,16 +22,22 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.permission.evaluator.Evaluators; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.authorization.store.ResourceStore; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; -import org.keycloak.models.cache.authorization.CachedStoreProviderFactory; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.provider.Provider; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; /** *

The main contract here is the creation of {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances. Usually @@ -67,11 +73,10 @@ public final class AuthorizationProvider implements Provider { private final KeycloakSession keycloakSession; private final RealmModel realm; - public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map policyProviderFactories) { + public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory, Map policyProviderFactories) { this.keycloakSession = session; this.realm = realm; - CachedStoreProviderFactory providerFactory = (CachedStoreProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CachedStoreFactoryProvider.class); - storeFactory = providerFactory.create(this); + this.storeFactory = storeFactory; this.policyProviderFactories = policyProviderFactories; this.policyEvaluator = new DefaultPolicyEvaluator(this); } @@ -92,7 +97,92 @@ public final class AuthorizationProvider implements Provider { * @return the {@link StoreFactory} */ public StoreFactory getStoreFactory() { - return this.storeFactory; + return createStoreFactory(); + } + + private StoreFactory createStoreFactory() { + return new StoreFactory() { + @Override + public ResourceStore getResourceStore() { + return storeFactory.getResourceStore(); + } + + @Override + public ResourceServerStore getResourceServerStore() { + return storeFactory.getResourceServerStore(); + } + + @Override + public ScopeStore getScopeStore() { + return storeFactory.getScopeStore(); + } + + @Override + public PolicyStore getPolicyStore() { + PolicyStore policyStore = storeFactory.getPolicyStore(); + return new PolicyStore() { + @Override + public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) { + return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer)); + } + + @Override + public void delete(String id) { + policyStore.delete(id); + } + + @Override + public Policy findById(String id, String resourceServerId) { + return policyStore.findById(id, resourceServerId); + } + + @Override + public Policy findByName(String name, String resourceServerId) { + return policyStore.findByName(name, resourceServerId); + } + + @Override + public List findByResourceServer(String resourceServerId) { + return policyStore.findByResourceServer(resourceServerId); + } + + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return policyStore.findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + + @Override + public List findByResource(String resourceId, String resourceServerId) { + return policyStore.findByResource(resourceId, resourceServerId); + } + + @Override + public List findByResourceType(String resourceType, String resourceServerId) { + return policyStore.findByResourceType(resourceType, resourceServerId); + } + + @Override + public List findByScopeIds(List scopeIds, String resourceServerId) { + return policyStore.findByScopeIds(scopeIds, resourceServerId); + } + + @Override + public List findByType(String type, String resourceServerId) { + return policyStore.findByType(type, resourceServerId); + } + + @Override + public List findDependentPolicies(String id, String resourceServerId) { + return policyStore.findDependentPolicies(id, resourceServerId); + } + }; + } + + @Override + public void close() { + storeFactory.close(); + } + }; } /** 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 094607a6c1..1ec8887d5d 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 @@ -35,6 +35,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; @@ -46,11 +47,13 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { private final AuthorizationProvider authorization; private final StoreFactory storeFactory; private final PolicyStore policyStore; + private final ResourceStore resourceStore; public DefaultPolicyEvaluator(AuthorizationProvider authorization) { this.authorization = authorization; storeFactory = this.authorization.getStoreFactory(); policyStore = storeFactory.getPolicyStore(); + resourceStore = storeFactory.getResourceStore(); } @Override @@ -159,7 +162,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { String type = resource.getType(); if (type != null) { - List resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type, resource.getResourceServer().getId()); + List resourcesByType = resourceStore.findByType(type, resource.getResourceServer().getId()); for (Resource resourceType : resourcesByType) { if (resourceType.getOwner().equals(resource.getResourceServer().getClientId())) { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java index 7f008ec56c..76a3fab2fe 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/AuthorizationStoreFactory.java @@ -18,7 +18,9 @@ package org.keycloak.authorization.store; -import org.keycloak.authorization.AuthorizationProvider; +import java.util.HashMap; +import java.util.Map; + import org.keycloak.authorization.store.syncronization.ClientApplicationSynchronizer; import org.keycloak.authorization.store.syncronization.RealmSynchronizer; import org.keycloak.authorization.store.syncronization.Synchronizer; @@ -30,9 +32,6 @@ import org.keycloak.models.UserModel.UserRemovedEvent; import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderFactory; -import java.util.HashMap; -import java.util.Map; - /** * @author Pedro Igor */ @@ -62,6 +61,4 @@ public interface AuthorizationStoreFactory extends ProviderFactory } }); } - - StoreFactory create(AuthorizationProvider authorizationProvider); } diff --git a/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java index eb18dbdb44..b8563cb74c 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java @@ -18,13 +18,10 @@ package org.keycloak.models.cache.authorization; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.store.StoreFactory; import org.keycloak.provider.ProviderFactory; /** * @author Pedro Igor */ public interface CachedStoreProviderFactory extends ProviderFactory { - StoreFactory create(AuthorizationProvider authorizationProvider); } 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 7dd47bd62f..82058d4819 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 @@ -1933,90 +1933,11 @@ public class RepresentationToModel { private static Policy importPolicies(AuthorizationProvider authorization, ResourceServer resourceServer, List policiesToImport, String parentPolicyName) { StoreFactory storeFactory = authorization.getStoreFactory(); - KeycloakSession session = authorization.getKeycloakSession(); - RealmModel realm = authorization.getRealm(); for (PolicyRepresentation policyRepresentation : policiesToImport) { if (parentPolicyName != null && !parentPolicyName.equals(policyRepresentation.getName())) { continue; } - Map config = policyRepresentation.getConfig(); - - String scopes = config.get("scopes"); - - if (scopes != null && !scopes.isEmpty()) { - try { - ScopeStore scopeStore = storeFactory.getScopeStore(); - List scopesMap = (List) JsonSerialization.readValue(scopes, List.class); - config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> { - Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId()); - - if (newScope == null) { - newScope = scopeStore.findById(scopeName, resourceServer.getId()); - } - - if (newScope == null) { - throw new RuntimeException("Scope with name [" + scopeName + "] not defined."); - } - - return newScope.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while importing policy [" + policyRepresentation.getName() + "].", e); - } - } - - String policyResources = config.get("resources"); - - if (policyResources != null && !policyResources.isEmpty()) { - ResourceStore resourceStore = storeFactory.getResourceStore(); - try { - List resources = JsonSerialization.readValue(policyResources, List.class); - config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> { - Resource resource = resourceStore.findByName(resourceName, resourceServer.getId()); - - if (resource == null) { - resource = resourceStore.findById(resourceName, resourceServer.getId()); - } - - if (resource == null) { - throw new RuntimeException("Resource with name [" + resourceName + "] not defined."); - } - - return resource.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while importing policy [" + policyRepresentation.getName() + "].", e); - } - } - - String applyPolicies = config.get("applyPolicies"); - - if (applyPolicies != null && !applyPolicies.isEmpty()) { - PolicyStore policyStore = storeFactory.getPolicyStore(); - try { - List policies = (List) JsonSerialization.readValue(applyPolicies, List.class); - config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> { - Policy policy = policyStore.findByName(policyName, resourceServer.getId()); - - if (policy == null) { - policy = policyStore.findById(policyName, resourceServer.getId()); - } - - if (policy == null) { - policy = importPolicies(authorization, resourceServer, policiesToImport, policyName); - if (policy == null) { - throw new RuntimeException("Policy with name [" + policyName + "] not defined."); - } - } - - return policy.getId(); - }).collect(Collectors.toList()))); - } catch (Exception e) { - throw new RuntimeException("Error while importing policy [" + policyRepresentation.getName() + "].", e); - } - } - PolicyStore policyStore = storeFactory.getPolicyStore(); Policy policy = policyStore.findById(policyRepresentation.getId(), resourceServer.getId()); @@ -2024,16 +1945,12 @@ public class RepresentationToModel { policy = policyStore.findByName(policyRepresentation.getName(), resourceServer.getId()); } - PolicyProviderFactory providerFactory = authorization.getProviderFactory(policyRepresentation.getType()); - if (policy == null) { policy = policyStore.create(policyRepresentation, resourceServer); } else { - toModel(policyRepresentation, storeFactory, policy); + policy = toModel(policyRepresentation, authorization, policy); } - providerFactory.onImport(policy, policyRepresentation, authorization); - if (parentPolicyName != null && parentPolicyName.equals(policyRepresentation.getName())) { return policy; } @@ -2042,7 +1959,7 @@ public class RepresentationToModel { return null; } - public static Policy toModel(AbstractPolicyRepresentation representation, StoreFactory storeFactory, Policy model) { + public static Policy toModel(AbstractPolicyRepresentation representation, AuthorizationProvider authorization, Policy model) { model.setName(representation.getName()); model.setDescription(representation.getDescription()); model.setDecisionStrategy(representation.getDecisionStrategy()); @@ -2087,10 +2004,23 @@ public class RepresentationToModel { model.setConfig(policy.getConfig()); } + StoreFactory storeFactory = authorization.getStoreFactory(); + updateResources(resources, model, storeFactory); updateScopes(scopes, model, storeFactory); updateAssociatedPolicies(policies, model, storeFactory); + PolicyProviderFactory provider = authorization.getProviderFactory(model.getType()); + + if (representation instanceof PolicyRepresentation) { + provider.onImport(model, PolicyRepresentation.class.cast(representation), authorization); + } else if (representation.getId() == null) { + provider.onCreate(model, representation, authorization); + } else { + provider.onUpdate(model, representation, authorization); + } + + representation.setId(model.getId()); return model; @@ -2238,10 +2168,6 @@ public class RepresentationToModel { policy.removeResource(resourceModel); } } - } else { - for (Resource resourceModel : new HashSet(policy.getResources())) { - policy.removeResource(resourceModel); - } } policy.getConfig().remove("resources"); diff --git a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java index ca59f42e12..cc06284c6c 100644 --- a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java +++ b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java @@ -21,14 +21,15 @@ package org.keycloak.authorization; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; import org.keycloak.Config; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; +import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; import org.keycloak.provider.ProviderFactory; /** @@ -36,7 +37,6 @@ import org.keycloak.provider.ProviderFactory; */ public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory { - private Executor scheduler; private Map policyProviderFactories; @Override @@ -46,15 +46,6 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide @Override public void init(Config.Scope config) { - //TODO: user-defined configuration -// Executor executor = Executors.newWorkStealingPool(); -// this.scheduler = command -> { -// Map, Object> contextDataMap = ResteasyProviderFactory.getContextDataMap(); -// executor.execute(() -> { -// ResteasyProviderFactory.pushContextDataMap(contextDataMap); -// command.run(); -// }); -// }; } @Override @@ -74,7 +65,11 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide @Override public AuthorizationProvider create(KeycloakSession session, RealmModel realm) { - return new AuthorizationProvider(session, realm, policyProviderFactories); + StoreFactory storeFactory = session.getProvider(CachedStoreFactoryProvider.class); + if (storeFactory == null) { + storeFactory = session.getProvider(StoreFactory.class); + } + return new AuthorizationProvider(session, realm, storeFactory, policyProviderFactories); } private Map configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) { diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java index 07e4ef9bee..e2db57eda8 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java @@ -33,7 +33,6 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; @@ -78,15 +77,7 @@ public class PolicyResourceService { representation.setId(policy.getId()); - Policy updated = RepresentationToModel.toModel(representation, authorization.getStoreFactory(), policy); - - PolicyProviderFactory resource = getProviderFactory(updated.getType()); - - if (representation instanceof PolicyRepresentation) { - resource.onImport(updated, PolicyRepresentation.class.cast(representation), authorization); - } else { - resource.onUpdate(updated, representation, authorization); - } + RepresentationToModel.toModel(representation, authorization, policy); return Response.status(Status.CREATED).build(); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index d57fec894d..011aa2d3a1 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -122,16 +122,7 @@ public class PolicyService { throw new ErrorResponseException("Policy with name [" + representation.getName() + "] already exists", "Conflicting policy", Status.CONFLICT); } - Policy policy = policyStore.create(representation, resourceServer); - PolicyProviderFactory provider = getPolicyProviderFactory(policy.getType()); - - if (representation instanceof PolicyRepresentation) { - provider.onImport(policy, PolicyRepresentation.class.cast(representation), authorization); - } else { - provider.onCreate(policy, representation, authorization); - } - - return policy; + return policyStore.create(representation, resourceServer); } @Path("/search") diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 02cd87898a..65081de8d2 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -96,6 +96,7 @@ true migration-test/* + authorization-test/* diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java index fd2785222b..de1a2de858 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java @@ -16,6 +16,22 @@ */ package org.keycloak.testsuite.adapter.example.authorization; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.util.IOUtil.loadJson; +import static org.keycloak.testsuite.util.IOUtil.loadRealm; +import static org.keycloak.testsuite.util.WaitUtils.pause; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.test.api.ArquillianResource; @@ -41,22 +57,6 @@ import org.keycloak.util.JsonSerialization; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.keycloak.testsuite.util.IOUtil.loadJson; -import static org.keycloak.testsuite.util.IOUtil.loadRealm; -import static org.keycloak.testsuite.util.WaitUtils.pause; - /** * @author Pedro Igor */ @@ -82,17 +82,9 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda return exampleDeployment(RESOURCE_SERVER_ID); } - @Override - public void beforeAbstractKeycloakTest() throws Exception { - super.beforeAbstractKeycloakTest(); - importResourceServerSettings(); - } - @Test public void testRegularUserPermissions() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { login("alice", "alice"); assertFalse(wasDenied()); assertTrue(hasLink("User Premium")); @@ -111,16 +103,12 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateToAdminPage(); assertTrue(wasDenied()); - } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); - } + }); } @Test public void testUserPremiumPermissions() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { login("jdoe", "jdoe"); assertFalse(wasDenied()); assertTrue(hasLink("User Premium")); @@ -139,16 +127,12 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateToAdminPage(); assertTrue(wasDenied()); - } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); - } + }); } @Test public void testAdminPermissions() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { login("admin", "admin"); assertFalse(wasDenied()); assertTrue(hasLink("User Premium")); @@ -167,16 +151,12 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateToAdminPage(); assertFalse(wasDenied()); - } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); - } + }); } @Test public void testGrantPremiumAccessToUser() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { login("alice", "alice"); assertFalse(wasDenied()); @@ -233,16 +213,12 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateToUserPremiumPage(); assertFalse(wasDenied()); - } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); - } + }); } @Test public void testGrantAdministrativePermissions() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { login("jdoe", "jdoe"); navigateToAdminPage(); @@ -263,23 +239,30 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateToAdminPage(); assertFalse(wasDenied()); - } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); - } + }); } //KEYCLOAK-3830 @Test public void testAccessPublicResource() throws Exception { - try { - this.deployer.deploy(RESOURCE_SERVER_ID); - + performTests(() -> { driver.navigate().to(getResourceServerUrl() + "/public-html.html"); WaitUtils.waitForPageToLoad(driver); assertTrue(hasText("This is public resource that should be accessible without login.")); - + }); + } + + private void performTests(TestRunnable assertion) { + try { + importResourceServerSettings(); + deployer.deploy(RESOURCE_SERVER_ID); + assertion.run(); + } catch (FileNotFoundException cause) { + throw new RuntimeException("Failed to import authorization settings", cause); + } catch (Exception cause) { + throw new RuntimeException("Error while executing tests", cause); } finally { - this.deployer.undeploy(RESOURCE_SERVER_ID); + deployer.undeploy(RESOURCE_SERVER_ID); } } @@ -299,7 +282,7 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/servlet-authz-app-authz-service.json")), ResourceServerRepresentation.class)); } - private AuthorizationResource getAuthorizationResource() throws FileNotFoundException { + private AuthorizationResource getAuthorizationResource() { return getClientResource(RESOURCE_SERVER_ID).authorization(); } @@ -317,18 +300,22 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda pause(500); } - private void login(String username, String password) throws InterruptedException { - navigateTo(); - Thread.sleep(2000); - if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) { - Thread.sleep(2000); - logOut(); + private void login(String username, String password) { + try { navigateTo(); + Thread.sleep(2000); + if (this.driver.getCurrentUrl().startsWith(getResourceServerUrl().toString())) { + Thread.sleep(2000); + logOut(); + navigateTo(); + } + + Thread.sleep(2000); + + this.loginPage.form().login(username, password); + } catch (Exception cause) { + throw new RuntimeException("Login failed", cause); } - - Thread.sleep(2000); - - this.loginPage.form().login(username, password); } private void navigateTo() { @@ -362,4 +349,8 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda navigateTo(); getLink("Administration").click(); } + + private interface TestRunnable { + void run() throws Exception; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AuthzCleanupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AuthzCleanupTest.java index 814fb5147a..69d8d6b075 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AuthzCleanupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AuthzCleanupTest.java @@ -16,9 +16,12 @@ */ package org.keycloak.testsuite.admin; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +import java.util.List; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Ignore; import org.junit.Test; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -26,23 +29,19 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.runonserver.RunOnServerDeployment; - -import java.util.List; - -import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.RealmBuilder; /** * @author Bill Burke * @version $Revision: 1 $ */ -@Ignore public class AuthzCleanupTest extends AbstractKeycloakTest { @Deployment @@ -52,35 +51,32 @@ public class AuthzCleanupTest extends AbstractKeycloakTest { @Override public void addTestRealms(List testRealms) { - RealmRepresentation testRealmRep = new RealmRepresentation(); - testRealmRep.setId(TEST); - testRealmRep.setRealm(TEST); - testRealmRep.setEnabled(true); - testRealms.add(testRealmRep); + testRealms.add(RealmBuilder.create().name(TEST) + .client(ClientBuilder.create().clientId("myclient") + .secret("secret") + .authorizationServicesEnabled(true) + .redirectUris("http://localhost/myclient") + .defaultRoles("client-role-1", "client-role-2").build()).build()); } public static void setup(KeycloakSession session) { - AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); RealmModel realm = session.realms().getRealmByName(TEST); - ClientModel client = session.realms().addClient(realm, "myclient"); - RoleModel role1 = client.addRole("client-role1"); - RoleModel role2 = client.addRole("client-role2"); - - ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId()); - createRolePolicy(authz, resourceServer, role1); - createRolePolicy(authz, resourceServer, role2); - - + session.getContext().setRealm(realm); + AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + ClientModel myclient = realm.getClientByClientId("myclient"); + ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(myclient.getId()); + createRolePolicy(authz, resourceServer, "client-role-1"); + createRolePolicy(authz, resourceServer, "client-role-2"); } - private static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { + private static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, String roleName) { RolePolicyRepresentation representation = new RolePolicyRepresentation(); - representation.setName(role.getName()); + representation.setName(roleName); representation.setType("role"); representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); representation.setLogic(Logic.POSITIVE); - representation.addRole(role.getName(), true); + representation.addRole(roleName, true); return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPermissionManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPermissionManagementTest.java index 2a534240f2..264a888bee 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPermissionManagementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPermissionManagementTest.java @@ -110,7 +110,7 @@ public abstract class AbstractPermissionManagementTest extends AbstractKeycloakT if (expected.getScopes() != null) { assertEquals(expected.getScopes().size(), associatedScopes.size()); - assertEquals(expected.getScopes().size(), associatedScopes.stream().map(representation1 -> representation1.getName()).filter(scopeName -> !expected.getScopes().contains(scopeName)).count()); + assertEquals(0, associatedScopes.stream().map(representation1 -> representation1.getName()).filter(scopeName -> !expected.getScopes().contains(scopeName)).count()); } else { assertTrue(associatedScopes.isEmpty()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java index b4960aa83f..1b4a701088 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java @@ -17,17 +17,21 @@ package org.keycloak.testsuite.admin.client.authorization; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Collections; import java.util.stream.Collectors; +import javax.management.relation.Role; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import org.junit.Test; import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.PolicyResource; import org.keycloak.admin.client.resource.RolePoliciesResource; import org.keycloak.admin.client.resource.RolePolicyResource; import org.keycloak.admin.client.resource.RolesResource; @@ -35,6 +39,7 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RolesBuilder; @@ -88,8 +93,8 @@ public class RolePolicyManagementTest extends AbstractPermissionManagementTest { roles.create(new RoleRepresentation("Client Role B", "desc", false)); - representation.addRole("Client Role A", true); - representation.addRole(clientRep.getClientId() + "/Client Role B", true); + representation.addRole("Client Role A"); + representation.addClientRole(clientRep.getClientId(), "Client Role B", true); assertCreated(authorization, representation); } @@ -158,6 +163,29 @@ public class RolePolicyManagementTest extends AbstractPermissionManagementTest { } } + @Test + public void testGenericConfig() { + AuthorizationResource authorization = getClient().authorization(); + RolePolicyRepresentation representation = new RolePolicyRepresentation(); + + representation.setName("Test Generic Config Permission"); + representation.addRole("Role A", false); + + RolePoliciesResource policies = authorization.policies().roles(); + Response response = policies.create(representation); + RolePolicyRepresentation created = response.readEntity(RolePolicyRepresentation.class); + + PolicyResource policy = authorization.policies().policy(created.getId()); + PolicyRepresentation genericConfig = policy.toRepresentation(); + + assertNotNull(genericConfig.getConfig()); + assertNotNull(genericConfig.getConfig().get("roles")); + + RoleRepresentation role = getRealm().roles().get("Role A").toRepresentation(); + + assertTrue(genericConfig.getConfig().get("roles").contains(role.getId())); + } + private void assertCreated(AuthorizationResource authorization, RolePolicyRepresentation representation) { RolePoliciesResource permissions = authorization.policies().roles(); Response response = permissions.create(representation); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java new file mode 100644 index 0000000000..9a80d68a1e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2016 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.admin.client.authorization; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collections; +import java.util.stream.Collectors; + +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; + +import org.junit.Test; +import org.keycloak.admin.client.resource.AuthorizationResource; +import org.keycloak.admin.client.resource.PolicyResource; +import org.keycloak.admin.client.resource.UserPoliciesResource; +import org.keycloak.admin.client.resource.UserPolicyResource; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +/** + * @author Pedro Igor + */ +public class UserPolicyManagementTest extends AbstractPermissionManagementTest { + + @Override + protected RealmBuilder createTestRealm() { + return super.createTestRealm() + .user(UserBuilder.create().username("User A")) + .user(UserBuilder.create().username("User B")) + .user(UserBuilder.create().username("User C")); + } + + @Test + public void testCreateUserPolicy() { + AuthorizationResource authorization = getClient().authorization(); + UserPolicyRepresentation representation = new UserPolicyRepresentation(); + + representation.setName("Realm User Policy"); + representation.setDescription("description"); + representation.setDecisionStrategy(DecisionStrategy.CONSENSUS); + representation.setLogic(Logic.NEGATIVE); + representation.addUser("User A"); + representation.addUser("User B"); + + assertCreated(authorization, representation); + } + + @Test + public void testUpdate() { + AuthorizationResource authorization = getClient().authorization(); + UserPolicyRepresentation representation = new UserPolicyRepresentation(); + + representation.setName("Update Test User Policy"); + representation.setDescription("description"); + representation.setDecisionStrategy(DecisionStrategy.CONSENSUS); + representation.setLogic(Logic.NEGATIVE); + representation.addUser("User A"); + representation.addUser("User B"); + representation.addUser("User C"); + + assertCreated(authorization, representation); + + representation.setName("changed"); + representation.setDescription("changed"); + representation.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); + representation.setLogic(Logic.POSITIVE); + representation.setUsers(representation.getUsers().stream().filter(userName -> !userName.equals("User A")).collect(Collectors.toSet())); + + UserPoliciesResource policies = authorization.policies().users(); + UserPolicyResource permission = policies.findById(representation.getId()); + + permission.update(representation); + assertRepresentation(representation, permission); + + representation.setUsers(representation.getUsers().stream().filter(userName -> !userName.equals("User C")).collect(Collectors.toSet())); + + permission.update(representation); + assertRepresentation(representation, permission); + } + + @Test + public void testDelete() { + AuthorizationResource authorization = getClient().authorization(); + UserPolicyRepresentation representation = new UserPolicyRepresentation(); + + representation.setName("Test Delete Permission"); + representation.addUser("User A"); + + UserPoliciesResource policies = authorization.policies().users(); + Response response = policies.create(representation); + UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class); + + policies.findById(created.getId()).remove(); + + UserPolicyResource removed = policies.findById(created.getId()); + + try { + removed.toRepresentation(); + fail("Permission not removed"); + } catch (NotFoundException ignore) { + + } + } + + @Test + public void testGenericConfig() { + AuthorizationResource authorization = getClient().authorization(); + UserPolicyRepresentation representation = new UserPolicyRepresentation(); + + representation.setName("Test Generic Config Permission"); + representation.addUser("User A"); + + UserPoliciesResource policies = authorization.policies().users(); + Response response = policies.create(representation); + UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class); + + PolicyResource policy = authorization.policies().policy(created.getId()); + PolicyRepresentation genericConfig = policy.toRepresentation(); + + assertNotNull(genericConfig.getConfig()); + assertNotNull(genericConfig.getConfig().get("users")); + + UserRepresentation user = getRealm().users().search("User A").get(0); + + assertTrue(genericConfig.getConfig().get("users").contains(user.getId())); + } + + private void assertCreated(AuthorizationResource authorization, UserPolicyRepresentation representation) { + UserPoliciesResource permissions = authorization.policies().users(); + Response response = permissions.create(representation); + UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class); + UserPolicyResource permission = permissions.findById(created.getId()); + assertRepresentation(representation, permission); + } + + private void assertRepresentation(UserPolicyRepresentation representation, UserPolicyResource permission) { + UserPolicyRepresentation actual = permission.toRepresentation(); + assertRepresentation(representation, actual, () -> permission.resources(), () -> Collections.emptyList(), () -> permission.associatedPolicies()); + assertEquals(representation.getUsers().size(), actual.getUsers().size()); + assertEquals(0, actual.getUsers().stream().filter(userId -> !representation.getUsers().stream() + .filter(userName -> getUserName(userId).equalsIgnoreCase(userName)) + .findFirst().isPresent()) + .count()); + } + + private String getUserName(String id) { + return getRealm().users().get(id).toRepresentation().getUsername(); + } +}