KEYCLOAK-14972 Add independent GroupProvider interface

This commit is contained in:
mhajas 2020-07-29 10:43:53 +02:00 committed by Hynek Mlnařík
parent fdcfa6e13e
commit ae39760a62
22 changed files with 545 additions and 96 deletions

View file

@ -123,7 +123,7 @@ public class SSSDFederationProvider implements UserStorageProvider,
for (String s : sssd.getGroups()) {
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "/" + s);
if (group == null) {
group = session.realms().createGroup(realm, s);
group = session.groups().createGroup(realm, s);
}
user.joinGroup(group);
}

View file

@ -68,7 +68,7 @@ public class GroupAdapter implements GroupModel {
protected boolean isUpdated() {
if (updated != null) return true;
if (!invalidated) return false;
updated = cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm);
updated = cacheSession.getGroupDelegate().getGroupById(realm, cached.getId());
if (updated == null) throw new IllegalStateException("Not found in database");
return true;
}
@ -224,7 +224,7 @@ public class GroupAdapter implements GroupModel {
public GroupModel getParent() {
if (isUpdated()) return updated.getParent();
if (cached.getParentId() == null) return null;
return keycloakSession.realms().getGroupById(cached.getParentId(), realm);
return keycloakSession.groups().getGroupById(realm, cached.getParentId());
}
@Override
@ -238,7 +238,7 @@ public class GroupAdapter implements GroupModel {
if (isUpdated()) return updated.getSubGroups();
Set<GroupModel> subGroups = new HashSet<>();
for (String id : cached.getSubGroups(modelSupplier)) {
GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
GroupModel subGroup = keycloakSession.groups().getGroupById(realm, id);
if (subGroup == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated
getDelegateForUpdate();
@ -273,6 +273,6 @@ public class GroupAdapter implements GroupModel {
}
private GroupModel getGroupModel() {
return cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm);
return cacheSession.getGroupDelegate().getGroupById(realm, cached.getId());
}
}

View file

@ -710,7 +710,7 @@ public class RealmAdapter implements CachedRealmModel {
List<GroupModel> defaultGroups = new LinkedList<>();
for (String id : cached.getDefaultGroups()) {
defaultGroups.add(cacheSession.getGroupById(id, this));
defaultGroups.add(cacheSession.getGroupById(this, id));
}
return Collections.unmodifiableList(defaultGroups);
@ -1416,7 +1416,7 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public GroupModel getGroupById(String id) {
return cacheSession.getGroupById(id, this);
return cacheSession.getGroupById(this, id);
}
@Override

View file

@ -101,6 +101,7 @@ public class RealmCacheSession implements CacheRealmProvider {
protected KeycloakSession session;
protected RealmProvider realmDelegate;
protected ClientProvider clientDelegate;
protected GroupProvider groupDelegate;
protected RoleProvider roleDelegate;
protected boolean transactionActive;
protected boolean setRollbackOnly;
@ -163,8 +164,12 @@ public class RealmCacheSession implements CacheRealmProvider {
roleDelegate = session.roleStorageManager();
return roleDelegate;
}
public GroupProvider getGroupDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (groupDelegate != null) return groupDelegate;
groupDelegate = session.groupLocalStorage();
return groupDelegate;
}
@Override
@ -826,7 +831,7 @@ public class RealmCacheSession implements CacheRealmProvider {
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
public GroupModel getGroupById(RealmModel realm, String id) {
CachedGroup cached = cache.get(id, CachedGroup.class);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
@ -834,14 +839,14 @@ public class RealmCacheSession implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
GroupModel model = getRealmDelegate().getGroupById(id, realm);
GroupModel model = getGroupDelegate().getGroupById(realm, id);
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedGroup(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getRealmDelegate().getGroupById(id, realm);
return getGroupDelegate().getGroupById(realm, id);
} else if (managedGroups.containsKey(id)) {
return managedGroups.get(id);
}
@ -857,7 +862,7 @@ public class RealmCacheSession implements CacheRealmProvider {
listInvalidations.add(realm.getId());
invalidationEvents.add(GroupMovedEvent.create(group, toParent, realm.getId()));
getRealmDelegate().moveGroup(realm, group, toParent);
getGroupDelegate().moveGroup(realm, group, toParent);
}
@Override
@ -865,7 +870,7 @@ public class RealmCacheSession implements CacheRealmProvider {
String cacheKey = getGroupsQueryCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
if (queryDB) {
return getRealmDelegate().getGroups(realm);
return getGroupDelegate().getGroups(realm);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
@ -875,7 +880,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getRealmDelegate().getGroups(realm);
List<GroupModel> model = getGroupDelegate().getGroups(realm);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
@ -886,10 +891,10 @@ public class RealmCacheSession implements CacheRealmProvider {
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
GroupModel group = session.groups().getGroupById(realm, id);
if (group == null) {
invalidations.add(cacheKey);
return getRealmDelegate().getGroups(realm);
return getGroupDelegate().getGroups(realm);
}
list.add(group);
}
@ -901,7 +906,7 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) {
return getRealmDelegate().getGroupsCount(realm, onlyTopGroups);
return getGroupDelegate().getGroupsCount(realm, onlyTopGroups);
}
@Override
@ -911,12 +916,12 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public Long getGroupsCountByNameContaining(RealmModel realm, String search) {
return getRealmDelegate().getGroupsCountByNameContaining(realm, search);
return getGroupDelegate().getGroupsCountByNameContaining(realm, search);
}
@Override
public List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
return getRealmDelegate().getGroupsByRole(realm, role, firstResult, maxResults);
return getGroupDelegate().getGroupsByRole(realm, role, firstResult, maxResults);
}
@Override
@ -924,7 +929,7 @@ public class RealmCacheSession implements CacheRealmProvider {
String cacheKey = getTopGroupsQueryCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
if (queryDB) {
return getRealmDelegate().getTopLevelGroups(realm);
return getGroupDelegate().getTopLevelGroups(realm);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
@ -934,7 +939,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getRealmDelegate().getTopLevelGroups(realm);
List<GroupModel> model = getGroupDelegate().getTopLevelGroups(realm);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
@ -945,10 +950,10 @@ public class RealmCacheSession implements CacheRealmProvider {
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
GroupModel group = session.groups().getGroupById(realm, id);
if (group == null) {
invalidations.add(cacheKey);
return getRealmDelegate().getTopLevelGroups(realm);
return getGroupDelegate().getTopLevelGroups(realm);
}
list.add(group);
}
@ -963,7 +968,7 @@ public class RealmCacheSession implements CacheRealmProvider {
String cacheKey = getTopGroupsQueryCacheKey(realm.getId() + first + max);
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId() + first + max);
if (queryDB) {
return getRealmDelegate().getTopLevelGroups(realm, first, max);
return getGroupDelegate().getTopLevelGroups(realm, first, max);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
@ -973,7 +978,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (Objects.isNull(query)) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getRealmDelegate().getTopLevelGroups(realm, first, max);
List<GroupModel> model = getGroupDelegate().getTopLevelGroups(realm, first, max);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
@ -984,10 +989,10 @@ public class RealmCacheSession implements CacheRealmProvider {
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
GroupModel group = session.groups().getGroupById(realm, id);
if (Objects.isNull(group)) {
invalidations.add(cacheKey);
return getRealmDelegate().getTopLevelGroups(realm);
return getGroupDelegate().getTopLevelGroups(realm);
}
list.add(group);
}
@ -999,7 +1004,7 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) {
return getRealmDelegate().searchForGroupByName(realm, search, first, max);
return getGroupDelegate().searchForGroupByName(realm, search, first, max);
}
@Override
@ -1013,7 +1018,7 @@ public class RealmCacheSession implements CacheRealmProvider {
invalidationEvents.add(GroupRemovedEvent.create(group, realm.getId()));
return getRealmDelegate().removeGroup(realm, group);
return getGroupDelegate().removeGroup(realm, group);
}
private GroupModel groupAdded(RealmModel realm, GroupModel group, GroupModel toParent) {
@ -1027,7 +1032,7 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
GroupModel group = getRealmDelegate().createGroup(realm, id, name, toParent);
GroupModel group = getGroupDelegate().createGroup(realm, id, name, toParent);
return groupAdded(realm, group, toParent);
}
@ -1040,7 +1045,7 @@ public class RealmCacheSession implements CacheRealmProvider {
addGroupEventIfAbsent(GroupMovedEvent.create(subGroup, null, realm.getId()));
getRealmDelegate().addTopLevelGroup(realm, subGroup);
getGroupDelegate().addTopLevelGroup(realm, subGroup);
}

View file

@ -352,7 +352,7 @@ public class UserAdapter implements CachedUserModel {
if (updated != null) return updated.getGroups();
Set<GroupModel> groups = new LinkedHashSet<>();
for (String id : cached.getGroups(modelSupplier)) {
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
GroupModel groupModel = keycloakSession.groups().getGroupById(realm, id);
if (groupModel == null) {
// chance that role was removed, so just delete to persistence and get user invalidated
getDelegateForUpdate();

View file

@ -0,0 +1,55 @@
/*
* 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.GroupProvider;
import org.keycloak.models.GroupProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.persistence.EntityManager;
public class JpaGroupProviderFactory implements GroupProviderFactory {
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public String getId() {
return "jpa";
}
@Override
public GroupProvider create(KeycloakSession session) {
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
return new JpaRealmProvider(session, em);
}
@Override
public void close() {
}
}

View file

@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
@ -57,7 +58,7 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProvider {
public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupProvider, RoleProvider {
protected static final Logger logger = Logger.getLogger(JpaRealmProvider.class);
private final KeycloakSession session;
protected EntityManager em;
@ -169,7 +170,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
removeRoles(adapter);
for (GroupModel group : adapter.getGroups()) {
session.realms().removeGroup(adapter, group);
session.groups().removeGroup(adapter, group);
}
num = em.createNamedQuery("removeClientInitialAccessByRealm")
@ -395,7 +396,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
public GroupModel getGroupById(RealmModel realm, String id) {
GroupEntity groupEntity = em.find(GroupEntity.class, id);
if (groupEntity == null) return null;
if (!groupEntity.getRealm().equals(realm.getId())) return null;
@ -413,7 +414,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else session.realms().addTopLevelGroup(realm, group);
else session.groups().addTopLevelGroup(realm, group);
}
@Override
@ -421,7 +422,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
return ref.getGroups().stream()
.map(g -> session.realms().getGroupById(g.getId(), realm))
.map(g -> session.groups().getGroupById(realm, g.getId()))
.sorted(Comparator.comparing(GroupModel::getName))
.collect(Collectors.collectingAndThen(
Collectors.toList(), Collections::unmodifiableList));
@ -479,7 +480,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
return ref.getGroups().stream()
.filter(g -> GroupEntity.TOP_PARENT_ID.equals(g.getParentId()))
.map(g -> session.realms().getGroupById(g.getId(), realm))
.map(g -> session.groups().getGroupById(realm, g.getId()))
.sorted(Comparator.comparing(GroupModel::getName))
.collect(Collectors.collectingAndThen(
Collectors.toList(), Collections::unmodifiableList));
@ -496,7 +497,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
List<GroupModel> list = new ArrayList<>();
if(Objects.nonNull(groupIds) && !groupIds.isEmpty()) {
for (String id : groupIds) {
GroupModel group = getGroupById(id, realm);
GroupModel group = getGroupById(realm, id);
list.add(group);
}
}
@ -532,7 +533,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
realm.removeDefaultGroup(group);
for (GroupModel subGroup : group.getSubGroups()) {
session.realms().removeGroup(realm, subGroup);
session.groups().removeGroup(realm, subGroup);
}
GroupEntity groupEntity = em.find(GroupEntity.class, group.getId(), LockModeType.PESSIMISTIC_WRITE);
if ((groupEntity == null) || (!groupEntity.getRealm().equals(realm.getId()))) {
@ -753,9 +754,9 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, RoleProv
if (Objects.isNull(groups)) return Collections.EMPTY_LIST;
List<GroupModel> list = new ArrayList<>();
for (String id : groups) {
GroupModel groupById = session.realms().getGroupById(id, realm);
GroupModel groupById = session.groups().getGroupById(realm, id);
while(Objects.nonNull(groupById.getParentId())) {
groupById = session.realms().getGroupById(groupById.getParentId(), realm);
groupById = session.groups().getGroupById(realm, groupById.getParentId());
}
if(!list.contains(groupById)) {
list.add(groupById);

View file

@ -797,7 +797,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST;
List<GroupModel> defaultGroups = new LinkedList<>();
for (GroupEntity entity : entities) {
defaultGroups.add(session.realms().getGroupById(entity.getId(), this));
defaultGroups.add(session.groups().getGroupById(this, entity.getId()));
}
return Collections.unmodifiableList(defaultGroups);
}
@ -2022,52 +2022,52 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public GroupModel createGroup(String id, String name, GroupModel toParent) {
return session.realms().createGroup(this, id, name, toParent);
return session.groups().createGroup(this, id, name, toParent);
}
@Override
public void moveGroup(GroupModel group, GroupModel toParent) {
session.realms().moveGroup(this, group, toParent);
session.groups().moveGroup(this, group, toParent);
}
@Override
public GroupModel getGroupById(String id) {
return session.realms().getGroupById(id, this);
return session.groups().getGroupById(this, id);
}
@Override
public List<GroupModel> getGroups() {
return session.realms().getGroups(this);
return session.groups().getGroups(this);
}
@Override
public Long getGroupsCount(Boolean onlyTopGroups) {
return session.realms().getGroupsCount(this, onlyTopGroups);
return session.groups().getGroupsCount(this, onlyTopGroups);
}
@Override
public Long getGroupsCountByNameContaining(String search) {
return session.realms().getGroupsCountByNameContaining(this, search);
return session.groups().getGroupsCountByNameContaining(this, search);
}
@Override
public List<GroupModel> getTopLevelGroups() {
return session.realms().getTopLevelGroups(this);
return session.groups().getTopLevelGroups(this);
}
@Override
public List<GroupModel> getTopLevelGroups(Integer first, Integer max) {
return session.realms().getTopLevelGroups(this, first, max);
return session.groups().getTopLevelGroups(this, first, max);
}
@Override
public List<GroupModel> searchForGroupByName(String search, Integer first, Integer max) {
return session.realms().searchForGroupByName(this, search, first, max);
return session.groups().searchForGroupByName(this, search, first, max);
}
@Override
public boolean removeGroup(GroupModel group) {
return session.realms().removeGroup(this, group);
return session.groups().removeGroup(this, group);
}
@Override

View file

@ -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.JpaGroupProviderFactory

View file

@ -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 GroupProviderFactory extends ProviderFactory<GroupProvider> {
}

View file

@ -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 GroupSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "group";
}
@Override
public Class<? extends Provider> getProviderClass() {
return GroupProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return GroupProviderFactory.class;
}
}

View file

@ -18,6 +18,7 @@
package org.keycloak.models.cache;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleProvider;
@ -25,7 +26,7 @@ import org.keycloak.models.RoleProvider;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface CacheRealmProvider extends RealmProvider, ClientProvider, RoleProvider {
public interface CacheRealmProvider extends RealmProvider, ClientProvider, GroupProvider, RoleProvider {
void clear();
RealmProvider getRealmDelegate();

View file

@ -19,6 +19,7 @@ org.keycloak.provider.ExceptionConverterSpi
org.keycloak.storage.UserStorageProviderSpi
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
org.keycloak.models.ClientSpi
org.keycloak.models.GroupSpi
org.keycloak.models.RealmSpi
org.keycloak.models.RoleSpi
org.keycloak.models.ActionTokenStoreSpi

View file

@ -0,0 +1,198 @@
/*
* 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 java.util.List;
/**
*
* Provider of group records
* @author mhajas
*
*/
public interface GroupProvider extends Provider {
/**
* Returns a group from the given realm with the corresponding id
*
* @param id Id.
* @param realm Realm.
* @return GroupModel with the corresponding id.
* @deprecated Use method {@code getGroupById(realm, id)}
*/
default GroupModel getGroupById(String id, RealmModel realm) {
return getGroupById(realm, id);
}
/**
* Returns a group from the given realm with the corresponding id
*
* @param realm Realm.
* @param id Id.
* @return GroupModel with the corresponding id.
*/
GroupModel getGroupById(RealmModel realm, String id);
/**
* Returns groups for the given realm.
*
* @param realm Realm.
* @return List of groups in the Realm.
*/
List<GroupModel> getGroups(RealmModel realm);
/**
* Returns a number of groups/top level groups (i.e. groups without parent group) for the given realm.
*
* @param realm Realm.
* @param onlyTopGroups When true the function returns a count of top level groups only.
* @return Number of groups/top level groups.
*/
Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups);
/**
* Returns number of groups with the given string in name for the given realm.
*
* @param realm Realm.
* @param search Searched string.
* @return Number of groups with the given string in its name.
*/
Long getGroupsCountByNameContaining(RealmModel realm, String search);
/**
* Returns groups with the given role in the given realm.
*
* @param realm Realm.
* @param role Role.
* @param firstResult First result to return. Ignored if negative.
* @param maxResults Maximum number of results to return. Ignored if negative.
* @return List of groups with the given role.
*/
List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults);
/**
* Returns all top level groups (i.e. groups without parent group) for the given realm.
*
* @param realm Realm.
* @return List of all top level groups in the realm.
*/
List<GroupModel> getTopLevelGroups(RealmModel realm);
/**
* Returns top level groups (i.e. groups without parent group) for the given realm.
*
* @param realm Realm.
* @param firstResult First result to return.
* @param maxResults Maximum number of results to return.
* @return List of top level groups in the realm.
*/
List<GroupModel> getTopLevelGroups(RealmModel realm, Integer firstResult, Integer maxResults);
/**
* Returns groups with the given string in name for the given realm.
*
* @param realm Realm.
* @param search Searched string.
* @param firstResult First result to return. Ignored if {@code null}.
* @param maxResults Maximum number of results to return. Ignored if {@code null}.
* @return List of groups with the given string in name.
*/
List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer firstResult, Integer maxResults);
/**
* Creates a new group with the given name in the given realm.
* Effectively the same as {@code createGroup(realm, null, name, null)}.
*
* @param realm Realm.
* @param name Name.
* @return Model of the created group.
*/
default GroupModel createGroup(RealmModel realm, String name) {
return createGroup(realm, null, name, null);
}
/**
* Creates a new group with the given id and name in the given realm.
* Effectively the same as {@code createGroup(realm, id, name, null)}
*
* @param realm Realm.
* @param id Id.
* @param name Name.
* @return Model of the created group
*/
default GroupModel createGroup(RealmModel realm, String id, String name) {
return createGroup(realm, id, name, null);
}
/**
* Creates a new group with the given name and parent to the given realm.
* Effectively the same as {@code createGroup(realm, null, name, toParent)}.
*
* @param realm Realm.
* @param name Name.
* @param toParent Parent group.
* @return Model of the created group.
*/
default GroupModel createGroup(RealmModel realm, String name, GroupModel toParent) {
return createGroup(realm, null, name, toParent);
}
/**
* Creates a new group with the given name, id, name and parent to the given realm.
*
* @param realm Realm.
* @param id Id, will be generated if {@code null}.
* @param name Name.
* @param toParent Parent group, or {@code null} if the group is top level group
* @return Model of the created group
*/
GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent);
/**
* Removes the given group for the given realm.
*
* @param realm Realm.
* @param group Group.
* @return true if the group was removed, false if group doesn't exist or doesn't belong to the given realm
*/
boolean removeGroup(RealmModel realm, GroupModel group);
/**
* This method is used for moving groups in group structure, for example:
* <ul>
* <li>making an existing child group child group of some other group,</li>
* <li>setting a top level group (i.e. group without parent group) child of some group,</li>
* <li>making a child group top level group (i.e. removing its parent group).</li>
* <ul/>
*
* @param realm Realm owning this group.
* @param group Group to update.
* @param toParent New parent group, or {@code null} if we are moving the group to top level group.
*/
void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent);
/**
* Removes parent group for the given group in the given realm.
*
* @param realm Realm.
* @param subGroup Group.
*/
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
}

View file

@ -115,6 +115,14 @@ public interface KeycloakSession {
*/
ClientProvider clients();
/**
* Returns a managed group provider instance.
*
* @return Currently used GroupProvider instance.
* @throws IllegalStateException if transaction is not active
*/
GroupProvider groups();
/**
* Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession
* transaction.
@ -192,6 +200,13 @@ public interface KeycloakSession {
*/
ClientProvider clientLocalStorage();
/**
* Keycloak specific local storage for groups. No cache in front, this api talks directly to storage configured for Keycloak
*
* @return
*/
GroupProvider groupLocalStorage();
/**
* Keycloak specific local storage for roles. No cache in front, this api talks directly to storage configured for Keycloak
*

View file

@ -28,7 +28,7 @@ import java.util.stream.Collectors;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider, RoleProvider /* up to here */ {
public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider, GroupProvider, 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();
@ -37,42 +37,7 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
RealmModel getRealm(String id);
RealmModel getRealmByName(String name);
void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent);
List<GroupModel> getGroups(RealmModel realm);
Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups);
Long getGroupsCountByNameContaining(RealmModel realm, String search);
List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults);
List<GroupModel> getTopLevelGroups(RealmModel realm);
List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max);
List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max);
boolean removeGroup(RealmModel realm, GroupModel group);
default GroupModel createGroup(RealmModel realm, String name) {
return createGroup(realm, null, name, null);
}
default GroupModel createGroup(RealmModel realm, String id, String name) {
return createGroup(realm, id, name, null);
}
default GroupModel createGroup(RealmModel realm, String name, GroupModel toParent) {
return createGroup(realm, null, name, toParent);
}
GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent);
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
ClientScopeModel getClientScopeById(String id, RealmModel realm);
GroupModel getGroupById(String id, RealmModel realm);
List<RealmModel> getRealms();
List<RealmModel> getRealmsWithProviderType(Class<?> type);
@ -232,4 +197,93 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
return searchForClientRolesStream(client, search, first, max).collect(Collectors.toSet());
}
/* GROUP PROVIDER METHODS */
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
GroupModel getGroupById(RealmModel realm, String id);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
default GroupModel getGroupById(String id, RealmModel realm) {
return getGroupById(realm, id);
}
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
List<GroupModel> getGroups(RealmModel realm);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
Long getGroupsCountByNameContaining(RealmModel realm, String search);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
List<GroupModel> getTopLevelGroups(RealmModel realm);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
boolean removeGroup(RealmModel realm, GroupModel group);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
default GroupModel createGroup(RealmModel realm, String name) {
return createGroup(realm, null, name, null);
}
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
default GroupModel createGroup(RealmModel realm, String id, String name) {
return createGroup(realm, id, name, null);
}
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
default GroupModel createGroup(RealmModel realm, String name, GroupModel toParent) {
return createGroup(realm, null, name, toParent);
}
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent);
/**
* @deprecated Use the corresponding method from {@link GroupProvider}. */
@Override
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
}

View file

@ -22,6 +22,7 @@ import org.keycloak.credential.UserCredentialStoreManager;
import org.keycloak.jose.jws.DefaultTokenManager;
import org.keycloak.keys.DefaultKeyManager;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.TokenManager;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
@ -69,6 +70,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private final Map<String, Object> attributes = new HashMap<>();
private RealmProvider model;
private ClientProvider clientProvider;
private GroupProvider groupProvider;
private RoleProvider roleProvider;
private UserStorageManager userStorageManager;
private ClientStorageManager clientStorageManager;
@ -114,6 +116,16 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
}
private GroupProvider getGroupProvider() {
// TODO: Extract GroupProvider from CacheRealmProvider and use that instead
GroupProvider cache = getProvider(CacheRealmProvider.class);
if (cache != null) {
return cache;
} else {
return groupLocalStorage();
}
}
private RoleProvider getRoleProvider() {
// TODO: Extract RoleProvider from CacheRealmProvider and use that instead
RoleProvider cache = getProvider(CacheRealmProvider.class);
@ -190,6 +202,11 @@ public class DefaultKeycloakSession implements KeycloakSession {
return getProvider(ClientProvider.class);
}
@Override
public GroupProvider groupLocalStorage() {
return getProvider(GroupProvider.class);
}
@Override
public ClientProvider clientStorageManager() {
if (clientStorageManager == null) {
@ -323,6 +340,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return clientProvider;
}
@Override
public GroupProvider groups() {
if (groupProvider == null) {
groupProvider = getGroupProvider();
}
return groupProvider;
}
@Override
public RoleProvider roles() {
if (roleProvider == null) {

View file

@ -455,7 +455,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role");
}
List<GroupModel> groupsModel = session.realms().getGroupsByRole(realm, role, firstResult, maxResults);
List<GroupModel> groupsModel = session.groups().getGroupsByRole(realm, role, firstResult, maxResults);
return groupsModel.stream()
.map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation))

View file

@ -79,7 +79,6 @@ import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@ -911,7 +910,7 @@ public class UserResource {
public void removeMembership(@PathParam("groupId") String groupId) {
auth.users().requireManageGroupMembership(user);
GroupModel group = session.realms().getGroupById(groupId, realm);
GroupModel group = session.groups().getGroupById(realm, groupId);
if (group == null) {
throw new NotFoundException("Group not found");
}
@ -934,7 +933,7 @@ public class UserResource {
@NoCache
public void joinGroup(@PathParam("groupId") String groupId) {
auth.users().requireManageGroupMembership(user);
GroupModel group = session.realms().getGroupById(groupId, realm);
GroupModel group = session.groups().getGroupById(realm, groupId);
if (group == null) {
throw new NotFoundException("Group not found");
}

View file

@ -894,7 +894,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
session.getContext().setRealm(realm);
GroupModel customerAGroup = session.realms().createGroup(realm, "Customer A");
GroupModel customerAGroup = session.groups().createGroup(realm, "Customer A");
UserModel customerAManager = session.users().addUser(realm, "customer-a-manager");
session.userCredentialManager().updateCredential(realm, customerAManager, UserCredentialModel.password("password"));
ClientModel realmAdminClient = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);

View file

@ -48,6 +48,10 @@
"provider": "${keycloak.client.provider:jpa}"
},
"group": {
"provider": "${keycloak.group.provider:jpa}"
},
"role": {
"provider": "${keycloak.role.provider:jpa}"
},

View file

@ -22,6 +22,10 @@
"provider": "${keycloak.client.provider:jpa}"
},
"group": {
"provider": "${keycloak.group.provider:jpa}"
},
"role": {
"provider": "${keycloak.role.provider:jpa}"
},