diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java index 21ea2628c5..dce35317ae 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java @@ -257,7 +257,7 @@ public class ClientAdapter implements ClientModel, CachedObject { public Stream getScopeMappingsStream() { if (isUpdated()) return updated.getScopeMappingsStream(); return cached.getScope().stream() - .map(id -> cacheSession.getRoleById(id, cachedRealm)); + .map(id -> cacheSession.getRoleById(cachedRealm, id)); } public void addScopeMapping(RoleModel role) { @@ -593,37 +593,37 @@ public class ClientAdapter implements ClientModel, CachedObject { @Override public RoleModel getRole(String name) { - return cacheSession.getClientRole(getRealm(), this, name); + return cacheSession.getClientRole(this, name); } @Override public RoleModel addRole(String name) { - return cacheSession.addClientRole(getRealm(), this, name); + return cacheSession.addClientRole(this, name); } @Override public RoleModel addRole(String id, String name) { - return cacheSession.addClientRole(getRealm(), this, id, name); + return cacheSession.addClientRole(this, id, name); } @Override public boolean removeRole(RoleModel role) { - return cacheSession.removeRole(cachedRealm, role); + return cacheSession.removeRole(role); } @Override public Stream getRolesStream() { - return cacheSession.getClientRolesStream(cachedRealm, this); + return cacheSession.getClientRolesStream(this); } @Override public Stream getRolesStream(Integer first, Integer max) { - return cacheSession.getClientRolesStream(cachedRealm, this, first, max); + return cacheSession.getClientRolesStream(this, first, max); } @Override public Stream searchForRolesStream(String search, Integer first, Integer max) { - return cacheSession.searchForClientRolesStream(cachedRealm, this, search, first, max); + return cacheSession.searchForClientRolesStream(this, search, first, max); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientScopeAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientScopeAdapter.java index 151b6c214a..b45e1a946b 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientScopeAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientScopeAdapter.java @@ -160,7 +160,7 @@ public class ClientScopeAdapter implements ClientScopeModel { public Stream getScopeMappingsStream() { if (isUpdated()) return updated.getScopeMappingsStream(); return cached.getScope().stream() - .map(id -> cacheSession.getRoleById(id, cachedRealm)); + .map(id -> cacheSession.getRoleById(cachedRealm, id)); } public void addScopeMapping(RoleModel role) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java index 45a9e60d33..9287e10822 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java @@ -202,7 +202,7 @@ public class GroupAdapter implements GroupModel { if (isUpdated()) return updated.getRoleMappings(); Set roles = new HashSet<>(); for (String id : cached.getRoleMappings(modelSupplier)) { - RoleModel roleById = keycloakSession.realms().getRoleById(id, realm); + RoleModel roleById = keycloakSession.roles().getRoleById(realm, id); if (roleById == null) { // chance that role was removed, so just delegate to persistence and get user invalidated getDelegateForUpdate(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 2072d96446..8a9881b6d3 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -701,7 +701,7 @@ public class RealmAdapter implements CachedRealmModel { @Override public RoleModel getRoleById(String id) { if (isUpdated()) return updated.getRoleById(id); - return cacheSession.getRoleById(id, this); + return cacheSession.getRoleById(this, id); } @Override @@ -1049,7 +1049,7 @@ public class RealmAdapter implements CachedRealmModel { @Override public boolean removeRole(RoleModel role) { - return cacheSession.removeRole(this, role); + return cacheSession.removeRole(role); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index 61790595d4..087c2b8f1d 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -101,6 +101,7 @@ public class RealmCacheSession implements CacheRealmProvider { protected KeycloakSession session; protected RealmProvider realmDelegate; protected ClientProvider clientDelegate; + protected RoleProvider roleDelegate; protected boolean transactionActive; protected boolean setRollbackOnly; @@ -156,6 +157,13 @@ public class RealmCacheSession implements CacheRealmProvider { clientDelegate = session.clientStorageManager(); return clientDelegate; } + public RoleProvider getRoleDelegate() { + if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); + if (roleDelegate != null) return roleDelegate; +// roleDelegate = session.roleStorageManager(); + roleDelegate = session.roleLocalStorage(); + return roleDelegate; + } @@ -591,6 +599,7 @@ public class RealmCacheSession implements CacheRealmProvider { public void close() { if (realmDelegate != null) realmDelegate.close(); if (clientDelegate != null) clientDelegate.close(); + if (roleDelegate != null) roleDelegate.close(); } @Override @@ -600,7 +609,7 @@ public class RealmCacheSession implements CacheRealmProvider { @Override public RoleModel addRealmRole(RealmModel realm, String id, String name) { - RoleModel role = getRealmDelegate().addRealmRole(realm, id, name); + RoleModel role = getRoleDelegate().addRealmRole(realm, id, name); addedRole(role.getId(), realm.getId()); return role; } @@ -610,7 +619,7 @@ public class RealmCacheSession implements CacheRealmProvider { String cacheKey = getRolesCacheKey(realm.getId()); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId()); if (queryDB) { - return getRealmDelegate().getRealmRolesStream(realm); + return getRoleDelegate().getRealmRolesStream(realm); } RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); @@ -620,8 +629,7 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - // intentionally using deprecated method here because role ids needs to be collected for cashing while the stream returned - Set model = getRealmDelegate().getRealmRoles(realm); + Set model = getRoleDelegate().getRealmRolesStream(realm).collect(Collectors.toSet()); if (model == null) return null; Set ids = model.stream().map(RoleModel::getId).collect(Collectors.toSet()); query = new RoleListQuery(loaded, cacheKey, realm, ids); @@ -631,10 +639,10 @@ public class RealmCacheSession implements CacheRealmProvider { } Set list = new HashSet<>(); for (String id : query.getRoles()) { - RoleModel role = session.realms().getRoleById(id, realm); + RoleModel role = session.roles().getRoleById(realm, id); if (role == null) { invalidations.add(cacheKey); - return getRealmDelegate().getRealmRolesStream(realm); + return getRoleDelegate().getRealmRolesStream(realm); } list.add(role); } @@ -642,11 +650,11 @@ public class RealmCacheSession implements CacheRealmProvider { } @Override - public Stream getClientRolesStream(RealmModel realm, ClientModel client) { + public Stream getClientRolesStream(ClientModel client) { String cacheKey = getRolesCacheKey(client.getId()); - boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(realm.getId()); + boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(client.getRealm().getId()); if (queryDB) { - return getRealmDelegate().getClientRolesStream(realm, client, null, null); + return getRoleDelegate().getClientRolesStream(client); } RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); @@ -656,21 +664,20 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - // intentionally using deprecated method here because role ids needs to be collected for cashing while the stream returned - Set model = getRealmDelegate().getClientRoles(realm, client, null, null); + Set model = getRoleDelegate().getClientRolesStream(client).collect(Collectors.toSet()); if (model == null) return null; Set ids = model.stream().map(RoleModel::getId).collect(Collectors.toSet()); - query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId()); + query = new RoleListQuery(loaded, cacheKey, client.getRealm(), ids, client.getClientId()); logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey); cache.addRevisioned(query, startupRevision); return model.stream(); } Set list = new HashSet<>(); for (String id : query.getRoles()) { - RoleModel role = session.realms().getRoleById(id, realm); + RoleModel role = session.roles().getRoleById(client.getRealm(), id); if (role == null) { invalidations.add(cacheKey); - return getRealmDelegate().getClientRolesStream(realm, client, null, null); + return getRoleDelegate().getClientRolesStream(client); } list.add(role); } @@ -679,33 +686,32 @@ public class RealmCacheSession implements CacheRealmProvider { @Override public Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max) { - return getRealmDelegate().getRealmRolesStream(realm, first, max); + return getRoleDelegate().getRealmRolesStream(realm, first, max); } @Override - public Stream getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max) { - return getRealmDelegate().getClientRolesStream(realm, client, first, max); + public Stream getClientRolesStream(ClientModel client, Integer first, Integer max) { + return getRoleDelegate().getClientRolesStream(client, first, max); } @Override - public Stream searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first, - Integer max) { - return getRealmDelegate().searchForClientRolesStream(realm, client, search, first, max); + public Stream searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) { + return getRoleDelegate().searchForClientRolesStream(client, search, first, max); } @Override public Stream searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) { - return getRealmDelegate().searchForRolesStream(realm, search, first, max); + return getRoleDelegate().searchForRolesStream(realm, search, first, max); } @Override - public RoleModel addClientRole(RealmModel realm, ClientModel client, String name) { - return addClientRole(realm, client, KeycloakModelUtils.generateId(), name); + public RoleModel addClientRole(ClientModel client, String name) { + return addClientRole(client, KeycloakModelUtils.generateId(), name); } @Override - public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) { - RoleModel role = getRealmDelegate().addClientRole(realm, client, id, name); + public RoleModel addClientRole(ClientModel client, String id, String name) { + RoleModel role = getRoleDelegate().addClientRole(client, id, name); addedRole(role.getId(), client.getId()); return role; } @@ -715,7 +721,7 @@ public class RealmCacheSession implements CacheRealmProvider { String cacheKey = getRoleByNameCacheKey(realm.getId(), name); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId()); if (queryDB) { - return getRealmDelegate().getRealmRole(realm, name); + return getRoleDelegate().getRealmRole(realm, name); } RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); @@ -725,27 +731,27 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - RoleModel model = getRealmDelegate().getRealmRole(realm, name); + RoleModel model = getRoleDelegate().getRealmRole(realm, name); if (model == null) return null; query = new RoleListQuery(loaded, cacheKey, realm, model.getId()); logger.tracev("adding realm role cache miss: client {0} key {1}", realm.getName(), cacheKey); cache.addRevisioned(query, startupRevision); return model; } - RoleModel role = getRoleById(query.getRoles().iterator().next(), realm); + RoleModel role = getRoleById(realm, query.getRoles().iterator().next()); if (role == null) { invalidations.add(cacheKey); - return getRealmDelegate().getRealmRole(realm, name); + return getRoleDelegate().getRealmRole(realm, name); } return role; } @Override - public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) { + public RoleModel getClientRole(ClientModel client, String name) { String cacheKey = getRoleByNameCacheKey(client.getId(), name); - boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(realm.getId()); + boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(client.getRealm().getId()); if (queryDB) { - return getRealmDelegate().getClientRole(realm, client, name); + return getRoleDelegate().getClientRole(client, name); } RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); @@ -755,34 +761,44 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - RoleModel model = getRealmDelegate().getClientRole(realm, client, name); + RoleModel model = getRoleDelegate().getClientRole(client, name); if (model == null) return null; - query = new RoleListQuery(loaded, cacheKey, realm, model.getId(), client.getClientId()); + query = new RoleListQuery(loaded, cacheKey, client.getRealm(), model.getId(), client.getClientId()); logger.tracev("adding client role cache miss: client {0} key {1}", client.getClientId(), cacheKey); cache.addRevisioned(query, startupRevision); return model; } - RoleModel role = getRoleById(query.getRoles().iterator().next(), realm); + RoleModel role = getRoleById(client.getRealm(), query.getRoles().iterator().next()); if (role == null) { invalidations.add(cacheKey); - return getRealmDelegate().getClientRole(realm, client, name); + return getRoleDelegate().getClientRole(client, name); } return role; } @Override - public boolean removeRole(RealmModel realm, RoleModel role) { + public boolean removeRole(RoleModel role) { listInvalidations.add(role.getContainer().getId()); invalidateRole(role.getId()); invalidationEvents.add(RoleRemovedEvent.create(role.getId(), role.getName(), role.getContainer().getId())); roleRemovalInvalidations(role.getId(), role.getName(), role.getContainer().getId()); - return getRealmDelegate().removeRole(realm, role); + return getRoleDelegate().removeRole(role); } @Override - public RoleModel getRoleById(String id, RealmModel realm) { + public void removeRoles(RealmModel realm) { + getRoleDelegate().removeRoles(realm); + } + + @Override + public void removeRoles(ClientModel client) { + getRoleDelegate().removeRoles(client); + } + + @Override + public RoleModel getRoleById(RealmModel realm, String id) { CachedRole cached = cache.get(id, CachedRole.class); if (cached != null && !cached.getRealm().equals(realm.getId())) { cached = null; @@ -790,7 +806,7 @@ public class RealmCacheSession implements CacheRealmProvider { if (cached == null) { Long loaded = cache.getCurrentRevision(id); - RoleModel model = getRealmDelegate().getRoleById(id, realm); + RoleModel model = getRoleDelegate().getRoleById(realm, id); if (model == null) return null; if (invalidations.contains(id)) return model; if (model.isClientRole()) { @@ -801,7 +817,7 @@ public class RealmCacheSession implements CacheRealmProvider { cache.addRevisioned(cached, startupRevision); } else if (invalidations.contains(id)) { - return getRealmDelegate().getRoleById(id, realm); + return getRoleDelegate().getRoleById(realm, id); } else if (managedRoles.containsKey(id)) { return managedRoles.get(id); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java index de73361266..a775f09e94 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java @@ -70,7 +70,7 @@ public class RoleAdapter implements RoleModel { protected boolean isUpdated() { if (updated != null) return true; if (!invalidated) return false; - updated = cacheSession.getRealmDelegate().getRoleById(cached.getId(), realm); + updated = cacheSession.getRoleDelegate().getRoleById(realm, cached.getId()); if (updated == null) throw new IllegalStateException("Not found in database"); return true; } @@ -223,7 +223,7 @@ public class RoleAdapter implements RoleModel { } private RoleModel getRoleModel() { - return cacheSession.getRealmDelegate().getRoleById(cached.getId(), realm); + return cacheSession.getRoleDelegate().getRoleById(realm, cached.getId()); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 74043f5829..77ce4293b7 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -329,7 +329,7 @@ public class UserAdapter implements CachedUserModel { if (updated != null) return updated.getRoleMappings(); Set roles = new HashSet<>(); for (String id : cached.getRoleMappings(modelSupplier)) { - RoleModel roleById = keycloakSession.realms().getRoleById(id, realm); + RoleModel roleById = keycloakSession.roles().getRoleById(realm, id); if (roleById == null) { // chance that role was removed, so just delete to persistence and get user invalidated getDelegateForUpdate(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index 0f965babb2..7436303613 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -652,37 +652,37 @@ public class ClientAdapter implements ClientModel, JpaModel { @Override public RoleModel getRole(String name) { - return session.realms().getClientRole(realm, this, name); + return session.roles().getClientRole(this, name); } @Override public RoleModel addRole(String name) { - return session.realms().addClientRole(realm, this, name); + return session.roles().addClientRole(this, name); } @Override public RoleModel addRole(String id, String name) { - return session.realms().addClientRole(realm, this, id, name); + return session.roles().addClientRole(this, id, name); } @Override public boolean removeRole(RoleModel roleModel) { - return session.realms().removeRole(realm, roleModel); + return session.roles().removeRole(roleModel); } @Override public Stream getRolesStream() { - return session.realms().getClientRolesStream(realm, this, null, null); + return session.roles().getClientRolesStream(this, null, null); } @Override public Stream getRolesStream(Integer first, Integer max) { - return session.realms().getClientRolesStream(realm, this, first, max); + return session.roles().getClientRolesStream(this, first, max); } @Override public Stream searchForRolesStream(String search, Integer first, Integer max) { - return session.realms().searchForClientRolesStream(realm, this, search, first, max); + return session.roles().searchForClientRolesStream(this, search, first, max); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 57af7642ca..350e301e33 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -32,6 +32,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.RoleProvider; import org.keycloak.models.jpa.entities.ClientEntity; import org.keycloak.models.jpa.entities.ClientInitialAccessEntity; import org.keycloak.models.jpa.entities.ClientScopeEntity; @@ -56,7 +57,7 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace; * @author Bill Burke * @version $Revision: 1 $ */ -public class JpaRealmProvider implements RealmProvider, ClientProvider { +public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProvider { protected static final Logger logger = Logger.getLogger(JpaRealmProvider.class); private final KeycloakSession session; protected EntityManager em; @@ -165,10 +166,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { adapter.removeClientScope(a.getId()); } - for (RoleModel role : adapter.getRoles()) { - // No need to go through cache. Roles were already invalidated - removeRole(adapter, role); - } + removeRoles(adapter); for (GroupModel group : adapter.getGroups()) { session.realms().removeGroup(adapter, group); @@ -231,16 +229,17 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { query.setParameter("realm", realm.getId()); List roles = query.getResultList(); if (roles.isEmpty()) return null; - return session.realms().getRoleById(roles.get(0), realm); + return session.roles().getRoleById(realm, roles.get(0)); } @Override - public RoleModel addClientRole(RealmModel realm, ClientModel client, String name) { - return addClientRole(realm, client, KeycloakModelUtils.generateId(), name); + public RoleModel addClientRole(ClientModel client, String name) { + return addClientRole(client, KeycloakModelUtils.generateId(), name); } + @Override - public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) { - if (getClientRole(realm, client, name) != null) { + public RoleModel addClientRole(ClientModel client, String id, String name) { + if (getClientRole(client, name) != null) { throw new ModelDuplicateException(); } RoleEntity roleEntity = new RoleEntity(); @@ -248,9 +247,9 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { roleEntity.setName(name); roleEntity.setClientId(client.getId()); roleEntity.setClientRole(true); - roleEntity.setRealmId(realm.getId()); + roleEntity.setRealmId(client.getRealm().getId()); em.persist(roleEntity); - RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity); + RoleAdapter adapter = new RoleAdapter(session, client.getRealm(), em, roleEntity); return adapter; } @@ -264,13 +263,13 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { } @Override - public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) { + public RoleModel getClientRole(ClientModel client, String name) { TypedQuery query = em.createNamedQuery("getClientRoleIdByName", String.class); query.setParameter("name", name); query.setParameter("client", client.getId()); List roles = query.getResultList(); if (roles.isEmpty()) return null; - return session.realms().getRoleById(roles.get(0), realm); + return session.roles().getRoleById(client.getRealm(), roles.get(0)); } @Override @@ -282,13 +281,13 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { } @Override - public Stream getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max) { + public Stream getClientRolesStream(ClientModel client, Integer first, Integer max) { TypedQuery query = em.createNamedQuery("getClientRoles", RoleEntity.class); query.setParameter("client", client.getId()); - - return getRolesStream(query, realm, first, max); + + return getRolesStream(query, client.getRealm(), first, max); } - + protected Stream getRolesStream(TypedQuery query, RealmModel realm, Integer first, Integer max) { if(Objects.nonNull(first) && Objects.nonNull(max) && first >= 0 && max >= 0) { @@ -301,10 +300,10 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { } @Override - public Stream searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first, Integer max) { + public Stream searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) { TypedQuery query = em.createNamedQuery("searchForClientRoles", RoleEntity.class); query.setParameter("client", client.getId()); - return searchForRoles(query, realm, search, first, max); + return searchForRoles(query, client.getRealm(), search, first, max); } @Override @@ -329,7 +328,15 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { } @Override - public boolean removeRole(RealmModel realm, RoleModel role) { + public boolean removeRole(RoleModel role) { + RealmModel realm; + if (role.getContainer() instanceof RealmModel) { + realm = (RealmModel) role.getContainer(); + } else if (role.getContainer() instanceof ClientModel) { + realm = ((ClientModel)role.getContainer()).getRealm(); + } else { + throw new IllegalStateException("RoleModel's container isn not instance of either RealmModel or ClientModel"); + } session.users().preRemove(realm, role); RoleContainerModel container = role.getContainer(); if (container.getDefaultRoles().contains(role.getName())) { @@ -367,7 +374,19 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { } @Override - public RoleModel getRoleById(String id, RealmModel realm) { + public void removeRoles(RealmModel realm) { + // No need to go through cache. Roles were already invalidated + realm.getRolesStream().forEach(this::removeRole); + } + + @Override + public void removeRoles(ClientModel client) { + // No need to go through cache. Roles were already invalidated + client.getRolesStream().forEach(this::removeRole); + } + + @Override + public RoleModel getRoleById(RealmModel realm, String id) { RoleEntity entity = em.find(RoleEntity.class, id); if (entity == null) return null; if (!realm.getId().equals(entity.getRealmId())) return null; @@ -681,10 +700,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider { session.users().preRemove(realm, client); - for (RoleModel role : client.getRoles()) { - // No need to go through cache. Roles were already invalidated - removeRole(realm, role); - } + removeRoles(client); ClientEntity clientEntity = em.find(ClientEntity.class, id, LockModeType.PESSIMISTIC_WRITE); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRoleProviderFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRoleProviderFactory.java new file mode 100644 index 0000000000..fd1059c260 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRoleProviderFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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.models.jpa; + +import org.keycloak.Config; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.models.RoleProvider; +import org.keycloak.models.RoleProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +import javax.persistence.EntityManager; + +public class JpaRoleProviderFactory implements RoleProviderFactory { + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getId() { + return "jpa"; + } + + @Override + public RoleProvider create(KeycloakSession session) { + EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager(); + return new JpaRealmProvider(session, em); + } + + @Override + public void close() { + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index bab75626fa..395eebf95a 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -50,7 +50,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public Long getClientsCount() { - return session.realms().getClientsCount(this); + return session.clients().getClientsCount(this); } private PasswordPolicy passwordPolicy; @@ -915,42 +915,42 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public RoleModel getRole(String name) { - return session.realms().getRealmRole(this, name); + return session.roles().getRealmRole(this, name); } @Override public RoleModel addRole(String name) { - return session.realms().addRealmRole(this, name); + return session.roles().addRealmRole(this, name); } @Override public RoleModel addRole(String id, String name) { - return session.realms().addRealmRole(this, id, name); + return session.roles().addRealmRole(this, id, name); } @Override public boolean removeRole(RoleModel role) { - return session.realms().removeRole(this, role); + return session.roles().removeRole(role); } @Override public Stream getRolesStream() { - return session.realms().getRealmRolesStream(this); + return session.roles().getRealmRolesStream(this); } @Override public Stream getRolesStream(Integer first, Integer max) { - return session.realms().getRealmRolesStream(this, first, max); + return session.roles().getRealmRolesStream(this, first, max); } @Override public Stream searchForRolesStream(String search, Integer first, Integer max) { - return session.realms().searchForRolesStream(this, search, first, max); + return session.roles().searchForRolesStream(this, search, first, max); } @Override public RoleModel getRoleById(String id) { - return session.realms().getRoleById(id, this); + return session.roles().getRoleById(this, id); } @Override @@ -2349,4 +2349,4 @@ public class RealmAdapter implements RealmModel, JpaModel { public String toString() { return String.format("%s@%08x", getId(), hashCode()); } -} \ No newline at end of file +} diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory new file mode 100644 index 0000000000..e8a33052fe --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.RoleProviderFactory @@ -0,0 +1,18 @@ +# +# Copyright 2020 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. +# + +org.keycloak.models.jpa.JpaRoleProviderFactory diff --git a/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java b/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java index 89255fcb86..44b451d553 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/AbstractClientModel.java @@ -63,37 +63,37 @@ public abstract class AbstractClientModel implements C @Override public RoleModel getRole(String name) { - return session.realms().getClientRole(realm, this, name); + return session.roles().getClientRole(this, name); } @Override public RoleModel addRole(String name) { - return session.realms().addClientRole(realm, this, name); + return session.roles().addClientRole(this, name); } @Override public RoleModel addRole(String id, String name) { - return session.realms().addClientRole(realm, this, id, name); + return session.roles().addClientRole(this, id, name); } @Override public boolean removeRole(RoleModel role) { - return session.realms().removeRole(realm, role); + return session.roles().removeRole(role); } @Override public Stream getRolesStream() { - return session.realms().getClientRolesStream(realm, this, null, null); + return session.roles().getClientRolesStream(this, null, null); } @Override public Stream getRolesStream(Integer firstResult, Integer maxResults) { - return session.realms().getClientRolesStream(realm, this, firstResult, maxResults); + return session.roles().getClientRolesStream(this, firstResult, maxResults); } @Override public Stream searchForRolesStream(String search, Integer first, Integer max) { - return session.realms().searchForClientRolesStream(realm, this, search, first, max); + return session.roles().searchForClientRolesStream(this, search, first, max); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java index 546ec40f4d..d1f76c47a8 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java @@ -41,6 +41,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.keycloak.models.map.storage.MapStorage; import static org.keycloak.common.util.StackUtil.getShortStackTrace; +import org.keycloak.models.RoleProvider; public class MapClientProvider implements ClientProvider { @@ -214,10 +215,7 @@ public class MapClientProvider implements ClientProvider { final ClientModel client = getClientById(realm, id); if (client == null) return false; session.users().preRemove(realm, client); - final RealmProvider realms = session.realms(); - for (RoleModel role : client.getRoles()) { - realms.removeRole(realm, role); - } + session.roles().removeRoles(client); session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() { @Override diff --git a/server-spi-private/src/main/java/org/keycloak/models/RoleProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/RoleProviderFactory.java new file mode 100644 index 0000000000..3d49f65b21 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/models/RoleProviderFactory.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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.models; + +import org.keycloak.provider.ProviderFactory; + +public interface RoleProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/RoleSpi.java b/server-spi-private/src/main/java/org/keycloak/models/RoleSpi.java new file mode 100644 index 0000000000..ce54a4ec49 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/models/RoleSpi.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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.models; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class RoleSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "role"; + } + + @Override + public Class getProviderClass() { + return RoleProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return RoleProviderFactory.class; + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java index 6ae0bca030..6c3aa08ca1 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java @@ -19,12 +19,13 @@ package org.keycloak.models.cache; import org.keycloak.models.ClientProvider; import org.keycloak.models.RealmProvider; +import org.keycloak.models.RoleProvider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface CacheRealmProvider extends RealmProvider, ClientProvider { +public interface CacheRealmProvider extends RealmProvider, ClientProvider, RoleProvider { void clear(); RealmProvider getRealmDelegate(); diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 57a32f20ce..640bd645ae 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -20,6 +20,7 @@ org.keycloak.storage.UserStorageProviderSpi org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.models.ClientSpi org.keycloak.models.RealmSpi +org.keycloak.models.RoleSpi org.keycloak.models.ActionTokenStoreSpi org.keycloak.models.CodeToTokenStoreSpi org.keycloak.models.SingleUseTokenStoreSpi @@ -83,4 +84,4 @@ org.keycloak.validation.ClientValidationSPI org.keycloak.headers.SecurityHeadersSpi org.keycloak.services.clientpolicy.ClientPolicySpi org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi -org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi \ No newline at end of file +org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 49ad3c8f37..9ede32cee6 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -115,6 +115,15 @@ public interface KeycloakSession { */ ClientProvider clients(); + /** + * Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession + * transaction. + * + * @return + * @throws IllegalStateException if transaction is not active + */ + RoleProvider roles(); + /** * Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession * transaction. @@ -148,6 +157,8 @@ public interface KeycloakSession { ClientProvider clientStorageManager(); +// RoleProvider roleStorageManager(); + /** * Un-cached view of all users in system including users loaded by UserStorageProviders * @@ -178,6 +189,13 @@ public interface KeycloakSession { */ ClientProvider clientLocalStorage(); + /** + * Keycloak specific local storage for roles. No cache in front, this api talks directly to database configured for Keycloak + * + * @return + */ + RoleProvider roleLocalStorage(); + /** * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage. * No cache in front. diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java index 54bf6623bf..d48e2c4344 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java @@ -23,13 +23,12 @@ import org.keycloak.provider.Provider; import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider /* up to here */ { +public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider, RoleProvider /* up to here */ { // Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession MigrationModel getMigrationModel(); @@ -44,11 +43,6 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups); - /** - * @deprecated Use the corresponding method from {@link ClientProvider}. */ - @Override - long getClientsCount(RealmModel realm); - Long getGroupsCountByNameContaining(RealmModel realm, String search); List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults); @@ -77,60 +71,9 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio void addTopLevelGroup(RealmModel realm, GroupModel subGroup); - RoleModel addRealmRole(RealmModel realm, String name); - - RoleModel addRealmRole(RealmModel realm, String id, String name); - - RoleModel getRealmRole(RealmModel realm, String name); - - // TODO switch all usages to the stream variant - @Deprecated - default Set getRealmRoles(RealmModel realm) { - return getRealmRolesStream(realm).collect(Collectors.toSet()); - } - - Stream getRealmRolesStream(RealmModel realm); - - @Deprecated - default Set getRealmRoles(RealmModel realm, Integer first, Integer max) { - return getRealmRolesStream(realm, first, max).collect(Collectors.toSet()); - } - - Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max); - - // TODO switch all usages to the stream variant - @Deprecated - default Set getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max) { - return getClientRolesStream(realm, client, first, max).collect(Collectors.toSet()); - } - - Stream getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max); - - @Deprecated - default Set searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first, - Integer max) { - return searchForClientRolesStream(realm, client, search, first, max).collect(Collectors.toSet()); - } - - Stream searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first, - Integer max); - - @Deprecated - default Set searchForRoles(RealmModel realm, String search, Integer first, Integer max) { - return searchForRolesStream(realm, search, first, max).collect(Collectors.toSet()); - } - - Stream searchForRolesStream(RealmModel realm, String search, Integer first, Integer max); - - boolean removeRole(RealmModel realm, RoleModel role); - - RoleModel getRoleById(String id, RealmModel realm); - ClientScopeModel getClientScopeById(String id, RealmModel realm); GroupModel getGroupById(String id, RealmModel realm); - - List getRealms(); List getRealmsWithProviderType(Class type); boolean removeRealm(String id); @@ -142,28 +85,10 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio void removeExpiredClientInitialAccess(); void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess); // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update - /** - * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ - default public Stream getClientRolesStream(RealmModel realm, ClientModel client) { - return getClientRolesStream(realm, client, null, null); - } - - /** - * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ - public RoleModel getClientRole(RealmModel realm, ClientModel client, String name); - - /** - * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ - public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name); - - /** - * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ - public RoleModel addClientRole(RealmModel realm, ClientModel client, String name); - // The methods below are going to be removed in future version of Keycloak // Sadly, we have to copy-paste the declarations from the respective interfaces // including the "default" body to be able to add a note on deprecation - + /** * @deprecated Use the corresponding method from {@link ClientProvider}. */ @Override @@ -183,11 +108,18 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio return this.getClients(realm, null, null); } + /** + * @deprecated Use the corresponding method from {@link ClientProvider}. */ + @Override + default List getClients(RealmModel realm, Integer firstResult, Integer maxResults) { + return getClientsStream(realm, firstResult, maxResults).collect(Collectors.toList()); + } + /** * @deprecated Use the corresponding method from {@link ClientProvider}. */ @Override default List searchClientsByClientId(String clientId, Integer firstResult, Integer maxResults, RealmModel realm) { - return searchClientsByClientId(clientId, firstResult, maxResults, realm); + return searchClientsByClientIdStream(realm, clientId, firstResult, maxResults).collect(Collectors.toList()); } /** @@ -203,19 +135,101 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio /** * @deprecated Use the corresponding method from {@link ClientProvider}. */ @Override - public ClientModel getClientByClientId(RealmModel realm, String clientId); - - /** - * @deprecated Use the corresponding method from {@link ClientProvider}. */ - @Override - public ClientModel getClientById(RealmModel realm, String id); - - /** - * @deprecated Use the corresponding method from {@link ClientProvider}. */ - @Override - public boolean removeClient(RealmModel realm, String id); - - /** - * @deprecated Use the corresponding method from {@link ClientProvider}. */ default boolean removeClient(String id, RealmModel realm) { return this.removeClient(realm, id); } + + /** + * @deprecated Use the corresponding method from {@link ClientProvider}. */ + @Override + default List getAlwaysDisplayInConsoleClients(RealmModel realm) { + return getAlwaysDisplayInConsoleClientsStream(realm).collect(Collectors.toList()); + } + + /** + * @deprecated Use the corresponding method from {@link ClientProvider}. */ + @Override + long getClientsCount(RealmModel realm); + + //Role-related methods + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + @Override + default RoleModel addRealmRole(RealmModel realm, String name) { return addRealmRole(realm, null, name); } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + @Override + RoleModel addRealmRole(RealmModel realm, String id, String name); + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + @Override + RoleModel getRealmRole(RealmModel realm, String name); + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default RoleModel getRoleById(String id, RealmModel realm) { + return getRoleById(realm, id); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + @Override + default Set getRealmRoles(RealmModel realm) { + return getRealmRoles(realm, null, null); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default Set getRealmRoles(RealmModel realm, Integer first, Integer max) { + return getRealmRolesStream(realm, first, max).collect(Collectors.toSet()); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default Set searchForRoles(RealmModel realm, String search, Integer first, Integer max) { + return searchForRolesStream(realm, search, first, max).collect(Collectors.toSet()); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default boolean removeRole(RealmModel realm, RoleModel role) { + return removeRole(role); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default RoleModel addClientRole(RealmModel realm, ClientModel client, String name) { + return addClientRole(client, name); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) { + return addClientRole(client, id, name); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default RoleModel getClientRole(RealmModel realm, ClientModel client, String name) { + return getClientRole(client, name); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default Set getClientRoles(RealmModel realm, ClientModel client) { + return getClientRolesStream(client).collect(Collectors.toSet()); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default Set getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max) { + return getClientRolesStream(client, first, max).collect(Collectors.toSet()); + } + + /** + * @deprecated Use the corresponding method from {@link RoleProvider}. */ + default Set searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first, Integer max) { + return searchForClientRolesStream(client, search, first, max).collect(Collectors.toSet()); + } + } diff --git a/server-spi/src/main/java/org/keycloak/models/RoleProvider.java b/server-spi/src/main/java/org/keycloak/models/RoleProvider.java new file mode 100644 index 0000000000..6ee50ef9f5 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/RoleProvider.java @@ -0,0 +1,186 @@ +/* + * Copyright 2020 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.models; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.keycloak.provider.Provider; + +/** + * Provider of the role records. + * @author vramik + */ +public interface RoleProvider extends Provider { + + /** + * Adds a realm role with given {@code name} to the given realm. + * The internal ID of the role will be created automatically. + * @param realm Realm owning this role. + * @param name String name of the role. + * @return Model of the created role. + */ + default RoleModel addRealmRole(RealmModel realm, String name) { + return addRealmRole(realm, null, name); + } + + /** + * Adds a realm role with given internal ID and {@code name} to the given realm. + * @param realm Realm owning this role. + * @param id Internal ID of the role or {@code null} if one is to be created by the underlying store + * @param name String name of the role. + * @return Model of the created client. + * @throws IllegalArgumentException If {@code id} does not conform + * the format understood by the underlying store. + */ + RoleModel addRealmRole(RealmModel realm, String id, String name); + + /** + * Returns all the realm roles of the given realm. + * Effectively the same as the call {@code getRealmRoles(realm, null, null)}. + * @param realm Realm. + * @return List of the roles. Never returns {@code null}. + * @deprecated use the stream variant instead + */ + @Deprecated + default Set getRealmRoles(RealmModel realm) { + return getRealmRolesStream(realm, null, null).collect(Collectors.toSet()); + } + + /** + * Returns all the realm roles of the given realm as a stream. + * Effectively the same as the call {@code getRealmRolesStream(realm, null, null)}. + * @param realm Realm. + * @return Stream of the roles. Never returns {@code null}. + */ + default Stream getRealmRolesStream(RealmModel realm) { + return getRealmRolesStream(realm, null, null); + } + + /** + * Returns the realm roles of the given realm as a stream. + * @param realm Realm. + * @param first First result to return. Ignored if negative or {@code null}. + * @param max Maximum number of results to return. Ignored if negative or {@code null}. + * @return Stream of the roles. Never returns {@code null}. + */ + Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max); + + /** + * Removes given realm role from the given realm. + * @param realm Realm. + * @param role Role to be removed. + * @return {@code true} if the role existed and has been removed, {@code false} otherwise. + */ + boolean removeRole(RoleModel role); + + /** + * Removes all roles from the given realm. + * @param realm Realm. + */ + void removeRoles(RealmModel realm); + + /** + * Adds a client role with given {@code name} to the given client. + * The internal ID of the role will be created automatically. + * @param client Client owning this role. + * @param name String name of the role. + * @return Model of the created role. + */ + RoleModel addClientRole(ClientModel client, String name); + + /** + * Adds a client role with given internal ID and {@code name} to the given client. + * @param client Client owning this role. + * @param id Internal ID of the client role or {@code null} if one is to be created by the underlying store. + * @param name String name of the role. + * @return Model of the created role. + */ + RoleModel addClientRole(ClientModel client, String id, String name); + + /** + * Returns all the client roles of the given client. + * Effectively the same as the call {@code getClientRoles(client, null, null)}. + * @param client Client. + * @return List of the roles. Never returns {@code null}. + */ + default Stream getClientRolesStream(ClientModel client) { + return getClientRolesStream(client, null, null); + } + + /** + * Returns the client roles of the given client. + * @param client Client. + * @param first First result to return. Ignored if negative or {@code null}. + * @param max Maximum number of results to return. Ignored if negative or {@code null}. + * @return List of the roles. Never returns {@code null}. + */ + Stream getClientRolesStream(ClientModel client, Integer first, Integer max); + + /** + * Removes all roles from the given client. + * @param client Client. + */ + void removeRoles(ClientModel client); + + //TODO RoleLookupProvider + /** + * Exact search for a role by given name. + * @param realm Realm. + * @param name String name of the role. + * @return Model of the role, or {@code null} if no role is found. + */ + RoleModel getRealmRole(RealmModel realm, String name); + + /** + * Exact search for a role by its internal ID.. + * @param realm Realm. + * @param id Internal ID of the role. + * @return Model of the role. + */ + RoleModel getRoleById(RealmModel realm, String id); + + /** + * Case-insensitive search for roles that contain the given string in their name or description. + * @param realm Realm. + * @param search Searched substring of the role's name or description. + * @param first First result to return. Ignored if negative or {@code null}. + * @param max Maximum number of results to return. Ignored if negative or {@code null}. + * @return Stream of the realm roles their name or description contains given search string. + * Never returns {@code null}. + */ + Stream searchForRolesStream(RealmModel realm, String search, Integer first, Integer max); + + /** + * Exact search for a client role by given name. + * @param client Client. + * @param name String name of the role. + * @return Model of the role, or {@code null} if no role is found. + */ + RoleModel getClientRole(ClientModel client, String name); + + /** + * Case-insensitive search for client roles that contain the given string in their name or description. + * @param client Client. + * @param search String to search by role's name or description. + * @param first First result to return. Ignored if negative or {@code null}. + * @param max Maximum number of results to return. Ignored if negative or {@code null}. + * @return Stream of the client roles their name or description contains given search string. + * Never returns {@code null}. + */ + Stream searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max); +} diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 939c47728f..9aebe2480a 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -29,6 +29,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakTransactionManager; import org.keycloak.models.KeyManager; import org.keycloak.models.RealmProvider; +import org.keycloak.models.RoleProvider; import org.keycloak.models.ThemeManager; import org.keycloak.models.UserCredentialManager; import org.keycloak.models.UserProvider; @@ -41,6 +42,7 @@ import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.services.clientpolicy.DefaultClientPolicyManager; import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.storage.ClientStorageManager; +//import org.keycloak.storage.RoleStorageManager; import org.keycloak.storage.UserStorageManager; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.vault.DefaultVaultTranscriber; @@ -67,8 +69,10 @@ public class DefaultKeycloakSession implements KeycloakSession { private final Map attributes = new HashMap<>(); private RealmProvider model; private ClientProvider clientProvider; + private RoleProvider roleProvider; private UserStorageManager userStorageManager; private ClientStorageManager clientStorageManager; +// private RoleStorageManager roleStorageManager; private UserCredentialStoreManager userCredentialStorageManager; private UserSessionProvider sessionProvider; private AuthenticationSessionProvider authenticationSessionProvider; @@ -110,6 +114,17 @@ public class DefaultKeycloakSession implements KeycloakSession { } } + private RoleProvider getRoleProvider() { + // TODO: Extract RoleProvider from CacheRealmProvider and use that instead + RoleProvider cache = getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { +// return roleStorageManager(); + return roleLocalStorage(); + } + } + @Override public UserCache userCache() { return getProvider(UserCache.class); @@ -184,6 +199,17 @@ public class DefaultKeycloakSession implements KeycloakSession { return clientStorageManager; } + @Override + public RoleProvider roleLocalStorage() { + return getProvider(RoleProvider.class); + } + +// @Override +// public RoleProvider roleStorageManager() { +// if (roleStorageManager == null) roleStorageManager = new RoleStorageManager(this); +// return roleStorageManager; +// } + @Override public UserProvider userStorageManager() { @@ -296,6 +322,14 @@ public class DefaultKeycloakSession implements KeycloakSession { return clientProvider; } + @Override + public RoleProvider roles() { + if (roleProvider == null) { + roleProvider = getRoleProvider(); + } + return roleProvider; + } + @Override public UserSessionProvider sessions() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java index 03f3e037ae..8c6462ba3a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java @@ -139,7 +139,7 @@ public class ClientModelTest extends AbstractKeycloakTest { assertThat("Realm Model 'original' is NULL !!", realm, notNullValue()); ClientModel from = realm.getClientByClientId("from"); - RoleModel role = currentSession.realms().getRoleById(roleId, realm); + RoleModel role = currentSession.roles().getRoleById(realm, roleId); from.removeRole(role); currentSession.clients().removeClient(realm, from.getId()); @@ -185,7 +185,7 @@ public class ClientModelTest extends AbstractKeycloakTest { ClientModel scoped = realm.getClientByClientId("scoped"); ClientModel from = realm.getClientByClientId("from"); - RoleModel role = currentSession.realms().getRoleById(roleId, realm); + RoleModel role = currentSession.roles().getRoleById(realm, roleId); from.removeRole(role); Set scopeMappings = scoped.getScopeMappings(); @@ -216,7 +216,7 @@ public class ClientModelTest extends AbstractKeycloakTest { KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRealmRoleRemove2) -> { currentSession = sessionRealmRoleRemove2; RealmModel realm = currentSession.realms().getRealmByName(realmName); - RoleModel role = currentSession.realms().getRoleById(roleId, realm); + RoleModel role = currentSession.roles().getRoleById(realm, roleId); realm.removeRole(role); }); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java index e89274c596..319ba5a88a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java @@ -35,6 +35,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.RoleProvider; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; @@ -387,7 +388,7 @@ public class OwnerReplacementTest extends AbstractKeycloakTest { // Get ID of some object from realm1 ((session, realm1) -> { - RoleModel role = session.getProvider(RealmProvider.class).addRealmRole(realm1, "foo"); + RoleModel role = session.getProvider(RoleProvider.class).addRealmRole(realm1, "foo"); realm1.addDefaultRole("foo"); return role.getId(); @@ -395,7 +396,7 @@ public class OwnerReplacementTest extends AbstractKeycloakTest { // Test lookup realm1 object in realm2 should not work ((session, realm2, realm1RoleId) -> { - RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm2); + RoleModel role = session.getProvider(RoleProvider.class).getRoleById(realm2, realm1RoleId); Assert.assertNull(role); }), @@ -414,16 +415,13 @@ public class OwnerReplacementTest extends AbstractKeycloakTest { // Try remove object from realm1 in the context of realm2 ((session, realm1, realm2, realm1RoleId) -> { - RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm1); - session.getProvider(RealmProvider.class).removeRole(realm2, role); + // not possible to remove object from realm1 in the context of realm2 any more }), // Test remove from above was not successful ((session, realm1, realm1RoleId) -> { - RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm1); - Assert.assertNotNull(role); - Assert.assertTrue(realm1.getDefaultRoles().contains("foo")); + // nothing to test }) ); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 8d9845a670..a7d4ce572a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -48,6 +48,10 @@ "provider": "${keycloak.client.provider:jpa}" }, + "role": { + "provider": "${keycloak.role.provider:jpa}" + }, + "mapStorage": { "provider": "${keycloak.mapStorage.provider:concurrenthashmap}", "concurrenthashmap": { diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java index 130dffca9e..5d153fed8d 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java @@ -72,9 +72,9 @@ public class TestCacheUtils { realm.getRoleById(role.getId()); roleContainer.getRole(role.getName()); if (roleContainer instanceof RealmModel) { - session.realms().getRealmRole(realm, role.getName()); + session.roles().getRealmRole(realm, role.getName()); } else { - session.realms().getClientRole(realm, (ClientModel) roleContainer, role.getName()); + session.roles().getClientRole((ClientModel) roleContainer, role.getName()); } } } diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json index d51f0a715b..3ef4c32228 100755 --- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json @@ -22,6 +22,10 @@ "provider": "${keycloak.client.provider:jpa}" }, + "role": { + "provider": "${keycloak.role.provider:jpa}" + }, + "mapStorage": { "provider": "${keycloak.mapStorage.provider:concurrenthashmap}", "concurrenthashmap": {