groups initial

This commit is contained in:
Bill Burke 2015-10-29 16:33:02 -04:00
parent 0d05d38eb6
commit d896800ec6
52 changed files with 2406 additions and 2 deletions

View file

@ -0,0 +1,81 @@
package org.keycloak.representations.idm;
import org.codehaus.jackson.annotate.JsonIgnore;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class GroupRepresentation {
private String id;
private String name;
protected Map<String, Object> attributes;
protected List<String> realmRoles;
protected Map<String, List<String>> clientRoles;
protected List<GroupRepresentation> subGroups;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getRealmRoles() {
return realmRoles;
}
public void setRealmRoles(List<String> realmRoles) {
this.realmRoles = realmRoles;
}
public Map<String, List<String>> getClientRoles() {
return clientRoles;
}
public void setClientRoles(Map<String, List<String>> clientRoles) {
this.clientRoles = clientRoles;
}
public Map<String, Object> getAttributes() {
return attributes;
}
// This method can be removed once we can remove backwards compatibility with Keycloak 1.3 (then getAttributes() can be changed to return Map<String, List<String>> )
@JsonIgnore
public Map<String, List<String>> getAttributesAsListValues() {
return (Map) attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
public GroupRepresentation singleAttribute(String name, String value) {
if (this.attributes == null) attributes = new HashMap<>();
attributes.put(name, Arrays.asList(value));
return this;
}
public List<GroupRepresentation> getSubGroups() {
return subGroups;
}
public void setSubGroups(List<GroupRepresentation> subGroups) {
this.subGroups = subGroups;
}
}

View file

@ -47,6 +47,7 @@ public class RealmRepresentation {
protected String certificate;
protected String codeSecret;
protected RolesRepresentation roles;
protected List<GroupRepresentation> groups;
protected List<String> defaultRoles;
@Deprecated
protected Set<String> requiredCredentials;
@ -775,4 +776,12 @@ public class RealmRepresentation {
public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
this.clientAuthenticationFlow = clientAuthenticationFlow;
}
public List<GroupRepresentation> getGroups() {
return groups;
}
public void setGroups(List<GroupRepresentation> groups) {
this.groups = groups;
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.examples.federation.properties;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -106,6 +107,12 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
// complete we dont'care if a role is removed
}
/**
* See if the user is still in the properties file
*

View file

@ -63,4 +63,6 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
throw new IllegalStateException("Remove not supported");
}
}

View file

@ -12,6 +12,7 @@ import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -105,6 +106,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
}
@Override
public boolean isValid(RealmModel realm, UserModel local) {
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now

View file

@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
@ -319,6 +320,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
// TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper?
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
}
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)

View file

@ -57,7 +57,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
}
public boolean isActive() {
return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
}
public KeycloakDeployment getDeployment() {
@ -111,6 +111,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");
return false;
}
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(response.getNotBeforePolicy());

View file

@ -0,0 +1,74 @@
package org.keycloak.models;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface GroupModel {
String getId();
String getName();
void setName(String name);
/**
* Set single value of specified attribute. Remove all other existing values
*
* @param name
* @param value
*/
void setSingleAttribute(String name, String value);
void setAttribute(String name, List<String> values);
void removeAttribute(String name);
/**
* @param name
* @return null if there is not any value of specified attribute or first value otherwise. Don't throw exception if there are more values of the attribute
*/
String getFirstAttribute(String name);
/**
* @param name
* @return list of all attribute values or empty list if there are not any values. Never return null
*/
List<String> getAttribute(String name);
Map<String, List<String>> getAttributes();
Set<RoleModel> getRealmRoleMappings();
Set<RoleModel> getClientRoleMappings(ClientModel app);
boolean hasRole(RoleModel role);
void grantRole(RoleModel role);
Set<RoleModel> getRoleMappings();
void deleteRoleMapping(RoleModel role);
GroupModel getParent();
Set<GroupModel> getSubGroups();
/**
* You must also call joinGroup on the parent group.
*
* @param group
*/
void setParent(GroupModel group);
/**
* Automatically calls setParent() on the subGroup
*
* @param subGroup
*/
void addChild(GroupModel subGroup);
/**
* Automatically calls setParent() on the subGroup
*
* @param subGroup
*/
void removeChild(GroupModel subGroup);
}

View file

@ -328,4 +328,12 @@ public interface RealmModel extends RoleContainerModel {
void setSupportedLocales(Set<String> locales);
String getDefaultLocale();
void setDefaultLocale(String locale);
GroupModel getGroupById(String id);
List<GroupModel> getGroups();
List<GroupModel> getTopLevelGroups();
boolean removeGroup(GroupModel group);
}

View file

@ -20,8 +20,11 @@ public interface RealmProvider extends Provider {
RoleModel getRoleById(String id, RealmModel realm);
ClientModel getClientById(String id, RealmModel realm);
GroupModel getGroupById(String id, RealmModel realm);
List<RealmModel> getRealms();
boolean removeRealm(String id);
void close();
}

View file

@ -165,6 +165,16 @@ public class UserFederationManager implements UserProvider {
return user;
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
return session.userStorage().getGroupMembers(realm, group, firstResult, maxResults);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return getGroupMembers(realm, group, -1, -1);
}
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
UserModel user = session.userStorage().getUserByUsername(username.toLowerCase(), realm);
@ -347,6 +357,16 @@ public class UserFederationManager implements UserProvider {
session.userStorage().preRemove(realm, model);
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
UserFederationProvider fed = getFederationProvider(federation);
fed.preRemove(realm, group);
}
session.userStorage().preRemove(realm, group);
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {

View file

@ -119,6 +119,14 @@ public interface UserFederationProvider extends Provider {
*/
void preRemove(RealmModel realm, RoleModel role);
/**
* called before a role is removed.
*
* @param realm
* @param group
*/
void preRemove(RealmModel realm, GroupModel group);
/**
* Is the Keycloak UserModel still valid and/or existing in federated storage? Keycloak may call this method
* in various user operations. The local storage may be deleted if this method returns false.

View file

@ -101,6 +101,11 @@ public interface UserModel {
Set<RoleModel> getRoleMappings();
void deleteRoleMapping(RoleModel role);
Set<GroupModel> getGroups();
void joinGroup(GroupModel group);
void leaveGroup(GroupModel group);
boolean isMemberOf(GroupModel group);
String getFederationLink();
void setFederationLink(String link);

View file

@ -25,12 +25,16 @@ public interface UserProvider extends Provider {
UserModel getUserById(String id, RealmModel realm);
UserModel getUserByUsername(String username, RealmModel realm);
UserModel getUserByEmail(String email, RealmModel realm);
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
UserModel getUserByServiceAccountClient(ClientModel client);
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
// Service account is included for counts
int getUsersCount(RealmModel realm);
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
List<UserModel> searchForUser(String search, RealmModel realm);
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
@ -48,6 +52,7 @@ public interface UserProvider extends Provider {
void preRemove(RealmModel realm, UserFederationProviderModel link);
void preRemove(RealmModel realm, RoleModel role);
void preRemove(RealmModel realm, GroupModel group);
void preRemove(RealmModel realm, ClientModel client);
void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper);

View file

@ -0,0 +1,60 @@
package org.keycloak.models.entities;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bburke@redhat.com">Bill Burke/a>
*/
public class GroupEntity extends AbstractIdentifiableEntity {
private String name;
private String realmId;
private List<String> roleIds;
private String parentId;
private Map<String, List<String>> attributes;
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public List<String> getRoleIds() {
return roleIds;
}
public void setRoleIds(List<String> roleIds) {
this.roleIds = roleIds;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.attributes = attributes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
}

View file

@ -21,6 +21,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
private String realmId;
private List<String> roleIds;
private List<String> groupIds;
private Map<String, List<String>> attributes;
private List<String> requiredActions;
@ -157,5 +158,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
public void setServiceAccountClientLink(String serviceAccountClientLink) {
this.serviceAccountClientLink = serviceAccountClientLink;
}
public List<String> getGroupIds() {
return groupIds;
}
public void setGroupIds(List<String> groupIds) {
this.groupIds = groupIds;
}
}

View file

@ -3,6 +3,7 @@ package org.keycloak.models.utils;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
@ -279,6 +280,24 @@ public final class KeycloakModelUtils {
return false;
}
/**
*
* @param groups
* @param targetGroup
* @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
*/
public static boolean isMember(Set<GroupModel> groups, GroupModel targetGroup) {
if (groups.contains(targetGroup)) return true;
for (GroupModel mapping : groups) {
GroupModel child = mapping;
while(child.getParent() != null) {
if (child.getParent().equals(targetGroup)) return true;
child = child.getParent();
}
}
return false;
}
// USER FEDERATION RELATED STUFF
/**

View file

@ -1,6 +1,7 @@
package org.keycloak.models.utils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -255,4 +256,26 @@ public class UserModelDelegate implements UserModel {
public void setCreatedTimestamp(Long timestamp){
delegate.setCreatedTimestamp(timestamp);
}
@Override
public Set<GroupModel> getGroups() {
return delegate.getGroups();
}
@Override
public void joinGroup(GroupModel group) {
delegate.joinGroup(group);
}
@Override
public void leaveGroup(GroupModel group) {
delegate.leaveGroup(group);
}
@Override
public boolean isMemberOf(GroupModel group) {
return delegate.isMemberOf(group);
}
}

View file

@ -20,6 +20,7 @@ import org.keycloak.connections.file.FileConnectionProvider;
import org.keycloak.connections.file.InMemoryModel;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
@ -78,6 +79,11 @@ public class FileRealmProvider implements RealmProvider {
return realm;
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
return null;
}
@Override
public RealmModel getRealm(String id) {
RealmModel model = inMemoryModel.getRealm(id);

View file

@ -21,6 +21,7 @@ import org.keycloak.connections.file.InMemoryModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
@ -80,6 +81,21 @@ public class FileUserProvider implements UserProvider {
return inMemoryModel.getUser(realm.getId(), userId);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
return null;
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return null;
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
}
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
for (UserModel user : inMemoryModel.getUsers(realm.getId())) {

View file

@ -0,0 +1,208 @@
package org.keycloak.models.file.adapter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.entities.GroupEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GroupAdapter implements GroupModel {
private final GroupEntity group;
private RealmModel realm;
private KeycloakSession session;
public GroupAdapter(KeycloakSession session, RealmModel realm, GroupEntity group) {
this.group = group;
this.realm = realm;
this.session = session;
}
@Override
public String getId() {
return group.getId();
}
@Override
public String getName() {
return group.getName();
}
@Override
public void setName(String name) {
group.setName(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof GroupModel)) return false;
GroupModel that = (GroupModel) o;
return that.getId().equals(getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
@Override
public void setSingleAttribute(String name, String value) {
if (group.getAttributes() == null) {
group.setAttributes(new HashMap<String, List<String>>());
}
List<String> attrValues = new ArrayList<>();
attrValues.add(value);
group.getAttributes().put(name, attrValues);
}
@Override
public void setAttribute(String name, List<String> values) {
if (group.getAttributes() == null) {
group.setAttributes(new HashMap<String, List<String>>());
}
group.getAttributes().put(name, values);
}
@Override
public void removeAttribute(String name) {
if (group.getAttributes() == null) return;
group.getAttributes().remove(name);
}
@Override
public String getFirstAttribute(String name) {
if (group.getAttributes()==null) return null;
List<String> attrValues = group.getAttributes().get(name);
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
}
@Override
public List<String> getAttribute(String name) {
if (group.getAttributes()==null) return Collections.<String>emptyList();
List<String> attrValues = group.getAttributes().get(name);
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
}
@Override
public Map<String, List<String>> getAttributes() {
return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
public void grantRole(RoleModel role) {
if (group.getRoleIds() == null) {
group.setRoleIds(new LinkedList<String>());
}
if (group.getRoleIds().contains(role.getId())) {
return;
}
group.getRoleIds().add(role.getId());
}
@Override
public Set<RoleModel> getRoleMappings() {
if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
Set<RoleModel> roles = new HashSet<>();
for (String id : group.getRoleIds()) {
roles.add(realm.getRoleById(id));
}
return roles;
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> allRoles = getRoleMappings();
// Filter to retrieve just realm roles
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : allRoles) {
if (role.getContainer() instanceof RealmModel) {
realmRoles.add(role);
}
}
return realmRoles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
if (group == null || role == null) return;
if (group.getRoleIds() == null) return;
group.getRoleIds().remove(role.getId());
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<RoleModel>();
Set<RoleModel> roles = getRoleMappings();
for (RoleModel role : roles) {
if (app.equals(role.getContainer())) {
result.add(role);
}
}
return result;
}
@Override
public GroupModel getParent() {
if (group.getParentId() == null) return null;
return realm.getGroupById(group.getParentId());
}
@Override
public Set<GroupModel> getSubGroups() {
Set<GroupModel> subGroups = new HashSet<>();
for (GroupModel groupModel : realm.getGroups()) {
if (groupModel.getParent().equals(this)) {
subGroups.add(groupModel);
}
}
return subGroups;
}
@Override
public void setParent(GroupModel group) {
this.group.setParentId(group.getId());
}
@Override
public void addChild(GroupModel subGroup) {
subGroup.setParent(this);
}
@Override
public void removeChild(GroupModel subGroup) {
subGroup.setParent(null);
}
}

View file

@ -22,6 +22,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -88,6 +89,7 @@ public class RealmAdapter implements RealmModel {
private final Map<String, ClientModel> allApps = new HashMap<String, ClientModel>();
private ClientModel masterAdminApp = null;
private final Map<String, RoleAdapter> allRoles = new HashMap<String, RoleAdapter>();
private final Map<String, GroupAdapter> allGroups = new HashMap<String, GroupAdapter>();
private final Map<String, IdentityProviderModel> allIdProviders = new HashMap<String, IdentityProviderModel>();
public RealmAdapter(KeycloakSession session, RealmEntity realm, InMemoryModel inMemoryModel) {
@ -601,6 +603,36 @@ public class RealmAdapter implements RealmModel {
return null;
}
@Override
public GroupModel getGroupById(String id) {
GroupModel found = allGroups.get(id);
if (found != null) return found;
return null;
}
@Override
public List<GroupModel> getGroups() {
List<GroupModel> list = new LinkedList<>();
for (GroupAdapter group : allGroups.values()) {
list.add(group);
}
return list;
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> list = new LinkedList<>();
for (GroupAdapter group : allGroups.values()) {
if (group.getParent() == null) list.add(group);
}
return list;
}
@Override
public boolean removeGroup(GroupModel group) {
return allGroups.remove(group.getId()) != null;
}
@Override
public List<String> getDefaultRoles() {
return realm.getDefaultRoles();

View file

@ -21,6 +21,7 @@ import org.keycloak.models.ClientModel;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.GroupModel;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.UserConsentModel;
@ -59,6 +60,7 @@ public class UserAdapter implements UserModel, Comparable {
private final RealmModel realm;
private final Set<RoleModel> allRoles = new HashSet<RoleModel>();
private final Set<GroupModel> allGroups = new HashSet<GroupModel>();
public UserAdapter(RealmModel realm, UserEntity userEntity, InMemoryModel inMemoryModel) {
this.user = userEntity;
@ -467,6 +469,29 @@ public class UserAdapter implements UserModel, Comparable {
credentialEntity.setPeriod(credModel.getPeriod());
}
@Override
public Set<GroupModel> getGroups() {
return Collections.unmodifiableSet(allGroups);
}
@Override
public void joinGroup(GroupModel group) {
allGroups.add(group);
}
@Override
public void leaveGroup(GroupModel group) {
if (user == null || group == null) return;
allGroups.remove(group);
}
@Override
public boolean isMemberOf(GroupModel group) {
return KeycloakModelUtils.isMember(getGroups(), group);
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();

View file

@ -21,9 +21,11 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
protected Set<String> realmInvalidations = new HashSet<String>();
protected Set<String> appInvalidations = new HashSet<String>();
protected Set<String> roleInvalidations = new HashSet<String>();
protected Set<String> groupInvalidations = new HashSet<String>();
protected Map<String, RealmModel> managedRealms = new HashMap<String, RealmModel>();
protected Map<String, ClientModel> managedApplications = new HashMap<String, ClientModel>();
protected Map<String, RoleModel> managedRoles = new HashMap<String, RoleModel>();
protected Map<String, GroupModel> managedGroups = new HashMap<String, GroupModel>();
protected boolean clearAll;
@ -73,6 +75,12 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
roleInvalidations.add(id);
}
@Override
public void registerGroupInvalidation(String id) {
groupInvalidations.add(id);
}
protected void runInvalidations() {
for (String id : realmInvalidations) {
cache.invalidateCachedRealmById(id);
@ -80,6 +88,9 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
}
for (String id : groupInvalidations) {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
cache.invalidateCachedApplicationById(id);
}
@ -254,6 +265,31 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
return adapter;
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getGroupById(id, realm);
CachedGroup cached = cache.getGroup(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new CachedGroup(realm, model);
cache.addCachedGroup(cached);
} else if (groupInvalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
} else if (managedGroups.containsKey(id)) {
return managedGroups.get(id);
}
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
managedGroups.put(id, adapter);
return adapter;
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getClientById(id, realm);

View file

@ -197,6 +197,16 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
return getDelegate().getGroupMembers(realm, group, firstResult, maxResults);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return getDelegate().getGroupMembers(realm, group);
}
@Override
public UserModel getUserByServiceAccountClient(ClientModel client) {
return getDelegate().getUserByServiceAccountClient(client);
@ -313,6 +323,11 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
public void preRemove(RealmModel realm, RoleModel role) {
getDelegate().preRemove(realm, role);
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
getDelegate().preRemove(realm, group);
}
@Override
public void preRemove(RealmModel realm, UserFederationProviderModel link) {

View file

@ -0,0 +1,236 @@
package org.keycloak.models.cache.infinispan;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedUser;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class GroupAdapter implements GroupModel {
protected GroupModel updated;
protected CachedGroup cached;
protected CacheRealmProvider cacheSession;
protected KeycloakSession keycloakSession;
protected RealmModel realm;
public GroupAdapter(CachedGroup cached, CacheRealmProvider cacheSession, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
this.cacheSession = cacheSession;
this.keycloakSession = keycloakSession;
this.realm = realm;
}
protected void getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerGroupInvalidation(getId());
updated = cacheSession.getDelegate().getGroupById(getId(), realm);
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof GroupModel)) return false;
GroupModel that = (GroupModel) o;
if (!cached.getId().equals(that.getId())) return false;
return true;
}
@Override
public int hashCode() {
return cached.getId().hashCode();
}
@Override
public String getId() {
if (updated != null) return updated.getId();
return cached.getId();
}
@Override
public String getName() {
if (updated != null) return updated.getName();
return cached.getName();
}
@Override
public void setName(String name) {
getDelegateForUpdate();
updated.setName(name);
}
@Override
public void setSingleAttribute(String name, String value) {
getDelegateForUpdate();
updated.setSingleAttribute(name, value);
}
@Override
public void setAttribute(String name, List<String> values) {
getDelegateForUpdate();
updated.setAttribute(name, values);
}
@Override
public void removeAttribute(String name) {
getDelegateForUpdate();
updated.removeAttribute(name);
}
@Override
public String getFirstAttribute(String name) {
if (updated != null) return updated.getFirstAttribute(name);
return cached.getAttributes().getFirst(name);
}
@Override
public List<String> getAttribute(String name) {
List<String> values = cached.getAttributes().get(name);
if (values == null) return null;
return values;
}
@Override
public Map<String, List<String>> getAttributes() {
return cached.getAttributes();
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
if (updated != null) return updated.getRealmRoleMappings();
Set<RoleModel> roleMappings = getRoleMappings();
Set<RoleModel> realmMappings = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
RoleContainerModel container = role.getContainer();
if (container instanceof RealmModel) {
if (((RealmModel) container).getId().equals(realm.getId())) {
realmMappings.add(role);
}
}
}
return realmMappings;
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
if (updated != null) return updated.getClientRoleMappings(app);
Set<RoleModel> roleMappings = getRoleMappings();
Set<RoleModel> appMappings = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
RoleContainerModel container = role.getContainer();
if (container instanceof ClientModel) {
if (((ClientModel) container).getId().equals(app.getId())) {
appMappings.add(role);
}
}
}
return appMappings;
}
@Override
public boolean hasRole(RoleModel role) {
if (updated != null) return updated.hasRole(role);
if (cached.getRoleMappings().contains(role.getId())) return true;
Set<RoleModel> mappings = getRoleMappings();
for (RoleModel mapping: mappings) {
if (mapping.hasRole(role)) return true;
}
return false;
}
@Override
public void grantRole(RoleModel role) {
getDelegateForUpdate();
updated.grantRole(role);
}
@Override
public Set<RoleModel> getRoleMappings() {
if (updated != null) return updated.getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>();
for (String id : cached.getRoleMappings()) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
if (roleById == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated
getDelegateForUpdate();
return updated.getRoleMappings();
}
roles.add(roleById);
}
return roles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
getDelegateForUpdate();
updated.deleteRoleMapping(role);
}
@Override
public GroupModel getParent() {
if (updated != null) return updated.getParent();
if (cached.getParentId() == null) return null;
return keycloakSession.realms().getGroupById(cached.getParentId(), realm);
}
@Override
public Set<GroupModel> getSubGroups() {
if (updated != null) return updated.getSubGroups();
Set<GroupModel> subGroups = new HashSet<>();
for (String id : cached.getSubGroups()) {
GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
if (subGroup == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated
getDelegateForUpdate();
return updated.getSubGroups();
}
subGroups.add(subGroup);
}
return subGroups;
}
@Override
public void setParent(GroupModel group) {
getDelegateForUpdate();
updated.setParent(group);
}
@Override
public void addChild(GroupModel subGroup) {
getDelegateForUpdate();
updated.addChild(subGroup);
}
@Override
public void removeChild(GroupModel subGroup) {
getDelegateForUpdate();
updated.removeChild(subGroup);
}
}

View file

@ -4,6 +4,7 @@ import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
@ -101,12 +102,49 @@ public class InfinispanRealmCache implements RealmCache {
cache.remove(id);
}
@Override
public CachedGroup getGroup(String id) {
if (!enabled) return null;
return get(id, CachedGroup.class);
}
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
cache.remove(role.getId());
}
@Override
public void addCachedGroup(CachedGroup role) {
if (!enabled) return;
logger.tracev("Adding group {0}", role.getId());
cache.put(role.getId(), role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
cache.remove(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
cache.remove(id);
}
@Override
public CachedRole getRole(String id) {
if (!enabled) return null;
return get(id, CachedRole.class);
}
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());

View file

@ -1262,4 +1262,42 @@ public class RealmAdapter implements RealmModel {
if (updated != null) return updated.getRequiredActionProviderByAlias(alias);
return cached.getRequiredActionProvidersByAlias().get(alias);
}
@Override
public GroupModel getGroupById(String id) {
if (updated != null) return updated.getGroupById(id);
return cacheSession.getGroupById(id, this);
}
@Override
public List<GroupModel> getGroups() {
if (updated != null) return updated.getGroups();
if (cached.getGroups().isEmpty()) return null;
List<GroupModel> list = new LinkedList<>();
for (String id : cached.getGroups()) {
GroupModel group = cacheSession.getGroupById(id, this);
if (group == null) continue;
list.add(group);
}
return list;
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> all = getGroups();
Iterator<GroupModel> it = all.iterator();
while (it.hasNext()) {
GroupModel group = it.next();
if (group.getParent() != null) {
it.remove();
}
}
return all;
}
@Override
public boolean removeGroup(GroupModel group) {
getDelegateForUpdate();
return updated.removeGroup(group);
}
}

View file

@ -317,6 +317,44 @@ public class UserAdapter implements UserModel {
updated.deleteRoleMapping(role);
}
@Override
public Set<GroupModel> getGroups() {
if (updated != null) return updated.getGroups();
Set<GroupModel> groups = new HashSet<GroupModel>();
for (String id : cached.getRoleMappings()) {
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
if (groupModel == null) {
// chance that role was removed, so just delete to persistence and get user invalidated
getDelegateForUpdate();
return updated.getGroups();
}
groups.add(groupModel);
}
return groups;
}
@Override
public void joinGroup(GroupModel group) {
getDelegateForUpdate();
updated.joinGroup(group);
}
@Override
public void leaveGroup(GroupModel group) {
getDelegateForUpdate();
updated.leaveGroup(group);
}
@Override
public boolean isMemberOf(GroupModel group) {
if (updated != null) return updated.isMemberOf(group);
if (cached.getGroups().contains(group.getId())) return true;
Set<GroupModel> roles = getGroups();
return KeycloakModelUtils.isMember(roles, group);
}
@Override
public void addConsent(UserConsentModel consent) {
getDelegateForUpdate();
@ -348,4 +386,5 @@ public class UserAdapter implements UserModel {
getDelegateForUpdate();
return updated.revokeConsentForClient(clientId);
}
}

View file

@ -17,4 +17,6 @@ public interface CacheRealmProvider extends RealmProvider {
void registerApplicationInvalidation(String id);
void registerRoleInvalidation(String id);
void registerGroupInvalidation(String id);
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.cache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
@ -39,6 +40,16 @@ public interface RealmCache {
void invalidateRoleById(String id);
CachedGroup getGroup(String id);
void invalidateGroup(CachedGroup role);
void addCachedGroup(CachedGroup role);
void invalidateCachedGroupById(String id);
void invalidateGroupById(String id);
boolean isEnabled();
void setEnabled(boolean enabled);

View file

@ -0,0 +1,74 @@
package org.keycloak.models.cache.entities;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedGroup implements Serializable {
private String id;
private String realm;
private String name;
private String parentId;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
private Set<String> roleMappings = new HashSet<>();
private Set<String> subGroups = new HashSet<>();
public CachedGroup(RealmModel realm, GroupModel group) {
this.id = group.getId();
this.realm = realm.getId();
this.name = group.getName();
if (group.getParent() != null) this.parentId = group.getParent().getId();
this.attributes.putAll(group.getAttributes());
for (RoleModel role : group.getRoleMappings()) {
roleMappings.add(role.getId());
}
Set<GroupModel> subGroups1 = group.getSubGroups();
if (subGroups1 != null) {
for (GroupModel subGroup : subGroups1) {
subGroups.add(subGroup.getId());
}
}
}
public String getId() {
return id;
}
public String getRealm() {
return realm;
}
public MultivaluedHashMap<String, String> getAttributes() {
return attributes;
}
public Set<String> getRoleMappings() {
return roleMappings;
}
public String getName() {
return name;
}
public String getParentId() {
return parentId;
}
public Set<String> getSubGroups() {
return subGroups;
}
}

View file

@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OTPPolicy;
@ -106,6 +107,7 @@ public class CachedRealm implements Serializable {
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
protected boolean adminEventsDetailsEnabled;
private List<String> defaultRoles = new LinkedList<String>();
private Set<String> groups = new HashSet<String>();
private Map<String, String> realmRoles = new HashMap<String, String>();
private Map<String, String> clients = new HashMap<String, String>();
private boolean internationalizationEnabled;
@ -216,6 +218,9 @@ public class CachedRealm implements Serializable {
executionsById.put(execution.getId(), execution);
}
}
for (GroupModel group : model.getGroups()) {
groups.add(group.getId());
}
for (AuthenticatorConfigModel authenticator : model.getAuthenticatorConfigs()) {
authenticatorConfigs.put(authenticator.getId(), authenticator);
}
@ -507,4 +512,8 @@ public class CachedRealm implements Serializable {
public AuthenticationFlowModel getClientAuthenticationFlow() {
return clientAuthenticationFlow;
}
public Set<String> getGroups() {
return groups;
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.models.cache.entities;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel;
@ -33,6 +34,7 @@ public class CachedUser implements Serializable {
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<>();
private Set<String> groups = new HashSet<>();
public CachedUser(RealmModel realm, UserModel user) {
this.id = user.getId();
@ -53,6 +55,12 @@ public class CachedUser implements Serializable {
for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId());
}
Set<GroupModel> groupMappings = user.getGroups();
if (groupMappings != null) {
for (GroupModel group : groupMappings) {
groups.add(group.getId());
}
}
}
public String getId() {
@ -118,4 +126,8 @@ public class CachedUser implements Serializable {
public String getServiceAccountClientLink() {
return serviceAccountClientLink;
}
public Set<String> getGroups() {
return groups;
}
}

View file

@ -0,0 +1,320 @@
package org.keycloak.models.jpa;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.GroupAttributeEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.GroupRoleMappingEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class GroupAdapter implements GroupModel {
protected GroupEntity group;
protected EntityManager em;
protected RealmModel realm;
public GroupAdapter(RealmModel realm, EntityManager em, GroupEntity group) {
this.em = em;
this.group = group;
this.realm = realm;
}
public GroupEntity getGroup() {
return group;
}
@Override
public String getId() {
return group.getId();
}
@Override
public String getName() {
return group.getName();
}
@Override
public void setName(String name) {
group.setName(name);
}
@Override
public GroupModel getParent() {
GroupEntity parent = group.getParent();
if (parent == null) return null;
return realm.getGroupById(parent.getId());
}
public static GroupEntity toEntity(GroupModel model, EntityManager em) {
if (model instanceof GroupAdapter) {
return ((GroupAdapter)model).getGroup();
}
return em.getReference(GroupEntity.class, model.getId());
}
@Override
public void setParent(GroupModel group) {
GroupEntity parent = toEntity(group, em);
group.setParent(group);
}
@Override
public void addChild(GroupModel subGroup) {
subGroup.setParent(this);
}
@Override
public void removeChild(GroupModel subGroup) {
subGroup.setParent(null);
}
@Override
public Set<GroupModel> getSubGroups() {
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByParent", String.class);
query.setParameter("parent", group);
List<String> ids = query.getResultList();
Set<GroupModel> set = new HashSet<>();
for (String id : ids) {
GroupModel subGroup = realm.getGroupById(id);
if (subGroup == null) continue;
set.add(subGroup);
}
return set;
}
@Override
public void setSingleAttribute(String name, String value) {
boolean found = false;
List<GroupAttributeEntity> toRemove = new ArrayList<>();
for (GroupAttributeEntity attr : group.getAttributes()) {
if (attr.getName().equals(name)) {
if (!found) {
attr.setValue(value);
found = true;
} else {
toRemove.add(attr);
}
}
}
for (GroupAttributeEntity attr : toRemove) {
em.remove(attr);
group.getAttributes().remove(attr);
}
if (found) {
return;
}
persistAttributeValue(name, value);
}
@Override
public void setAttribute(String name, List<String> values) {
// Remove all existing
removeAttribute(name);
// Put all new
for (String value : values) {
persistAttributeValue(name, value);
}
}
private void persistAttributeValue(String name, String value) {
GroupAttributeEntity attr = new GroupAttributeEntity();
attr.setId(KeycloakModelUtils.generateId());
attr.setName(name);
attr.setValue(value);
attr.setGroup(group);
em.persist(attr);
group.getAttributes().add(attr);
}
@Override
public void removeAttribute(String name) {
Iterator<GroupAttributeEntity> it = group.getAttributes().iterator();
while (it.hasNext()) {
GroupAttributeEntity attr = it.next();
if (attr.getName().equals(name)) {
it.remove();
em.remove(attr);
}
}
}
@Override
public String getFirstAttribute(String name) {
for (GroupAttributeEntity attr : group.getAttributes()) {
if (attr.getName().equals(name)) {
return attr.getValue();
}
}
return null;
}
@Override
public List<String> getAttribute(String name) {
List<String> result = new ArrayList<>();
for (GroupAttributeEntity attr : group.getAttributes()) {
if (attr.getName().equals(name)) {
result.add(attr.getValue());
}
}
return result;
}
@Override
public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
for (GroupAttributeEntity attr : group.getAttributes()) {
result.add(attr.getName(), attr.getValue());
}
return result;
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
TypedQuery<GroupRoleMappingEntity> query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
query.setParameter("group", getGroup());
query.setParameter("roleId", role.getId());
return query;
}
@Override
public void grantRole(RoleModel role) {
if (hasRole(role)) return;
GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
entity.setGroup(getGroup());
entity.setRoleId(role.getId());
em.persist(entity);
em.flush();
em.detach(entity);
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> roleMappings = getRoleMappings();
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
RoleContainerModel container = role.getContainer();
if (container instanceof RealmModel) {
realmRoles.add(role);
}
}
return realmRoles;
}
@Override
public Set<RoleModel> getRoleMappings() {
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery<String> query = em.createNamedQuery("groupRoleMappingIds", String.class);
query.setParameter("group", getGroup());
List<String> ids = query.getResultList();
Set<RoleModel> roles = new HashSet<RoleModel>();
for (String roleId : ids) {
RoleModel roleById = realm.getRoleById(roleId);
if (roleById == null) continue;
roles.add(roleById);
}
return roles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
if (group == null || role == null) return;
TypedQuery<GroupRoleMappingEntity> query = getGroupRoleMappingEntityTypedQuery(role);
List<GroupRoleMappingEntity> results = query.getResultList();
if (results.size() == 0) return;
for (GroupRoleMappingEntity entity : results) {
em.remove(entity);
}
em.flush();
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> roleMappings = getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
RoleContainerModel container = role.getContainer();
if (container instanceof ClientModel) {
ClientModel appModel = (ClientModel)container;
if (appModel.getId().equals(app.getId())) {
roles.add(role);
}
}
}
return roles;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
UserModel that = (UserModel) o;
return that.getId().equals(getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -2,11 +2,13 @@ package org.keycloak.models.jpa;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -117,6 +119,14 @@ public class JpaRealmProvider implements RealmProvider {
return new RoleAdapter(realm, em, entity);
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
GroupEntity groupEntity = em.find(GroupEntity.class, id);
if (groupEntity == null) return null;
if (groupEntity.getRealm().getId().equals(realm.getId())) return null;
return new GroupAdapter(realm, em, groupEntity);
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
ClientEntity app = em.find(ClientEntity.class, id);

View file

@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@ -169,6 +170,8 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUsersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserGroupMembershipByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
}
@Override
@ -219,6 +222,25 @@ public class JpaUserProvider implements UserProvider {
.executeUpdate();
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
query.setParameter("groupId", group.getId());
List<UserEntity> results = query.getResultList();
List<UserModel> users = new ArrayList<UserModel>();
for (UserEntity user : results) {
users.add(new UserAdapter(realm, em, user));
}
return users;
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
}
@Override
public UserModel getUserById(String id, RealmModel realm) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class);
@ -318,6 +340,25 @@ public class JpaUserProvider implements UserProvider {
return users;
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
query.setParameter("groupId", group.getId());
if (firstResult != -1) {
query.setFirstResult(firstResult);
}
if (maxResults != -1) {
query.setMaxResults(maxResults);
}
List<UserEntity> results = query.getResultList();
List<UserModel> users = new ArrayList<UserModel>();
for (UserEntity user : results) {
users.add(new UserAdapter(realm, em, user));
}
return users;
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUser(search, realm, -1, -1);

View file

@ -6,6 +6,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -24,6 +25,7 @@ import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
import org.keycloak.models.jpa.entities.AuthenticatorConfigEntity;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
import org.keycloak.models.jpa.entities.RealmAttributeEntity;
@ -1944,4 +1946,60 @@ public class RealmAdapter implements RealmModel {
}
return null;
}
@Override
public GroupModel getGroupById(String id) {
GroupEntity groupEntity = em.find(GroupEntity.class, id);
if (groupEntity == null) return null;
if (groupEntity.getRealm().getId().equals(getId())) return null;
return new GroupAdapter(this, em, groupEntity);
}
@Override
public List<GroupModel> getGroups() {
List<GroupModel> list = new LinkedList<>();
Collection<GroupEntity> groups = realm.getGroups();
if (groups == null) return list;
for (GroupEntity entity : groups) {
list.add(new GroupAdapter(this, em, entity));
}
return list;
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> all = getGroups();
Iterator<GroupModel> it = all.iterator();
while (it.hasNext()) {
GroupModel group = it.next();
if (group.getParent() != null) {
it.remove();
}
}
return all;
}
@Override
public boolean removeGroup(GroupModel group) {
if (group == null) {
return false;
}
GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
if (!groupEntity.getRealm().getId().equals(getId())) {
return false;
}
for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(subGroup);
}
session.users().preRemove(this, group);
realm.getGroups().remove(groupEntity);
em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", group).executeUpdate();
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", group).executeUpdate();
em.remove(groupEntity);
return true;
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.jpa;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel;
@ -19,6 +20,7 @@ import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -485,6 +487,63 @@ public class UserAdapter implements UserModel {
em.flush();
}
@Override
public Set<GroupModel> getGroups() {
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery<String> query = em.createNamedQuery("userGroupIds", String.class);
query.setParameter("user", getUser());
List<String> ids = query.getResultList();
Set<GroupModel> groups = new HashSet<>();
for (String groupId : ids) {
GroupModel group = realm.getGroupById(groupId);
if (group == null) continue;
groups.add(group);
}
return groups;
}
@Override
public void joinGroup(GroupModel group) {
if (isMemberOf(group)) return;
UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
entity.setUser(getUser());
entity.setGroupId(group.getId());
em.persist(entity);
em.flush();
em.detach(entity);
}
@Override
public void leaveGroup(GroupModel group) {
if (user == null || group == null) return;
TypedQuery<UserGroupMembershipEntity> query = getUserGroupMappingQuery(group);
List<UserGroupMembershipEntity> results = query.getResultList();
if (results.size() == 0) return;
for (UserGroupMembershipEntity entity : results) {
em.remove(entity);
}
em.flush();
}
@Override
public boolean isMemberOf(GroupModel group) {
Set<GroupModel> roles = getGroups();
return KeycloakModelUtils.isMember(roles, group);
}
protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
TypedQuery<UserGroupMembershipEntity> query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
query.setParameter("user", getUser());
query.setParameter("groupId", group.getId());
return query;
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();

View file

@ -0,0 +1,70 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="getGroupAttributesByNameAndValue", query="select attr from GroupAttributeEntity attr where attr.name = :name and attr.value = :value"),
@NamedQuery(name="deleteGroupAttributesByGroup", query="delete from GroupAttributeEntity attr where attr.group = :group"),
@NamedQuery(name="deleteGroupAttributesByRealm", query="delete from GroupAttributeEntity attr where attr.group IN (select u from GroupEntity u where u.realmId=:realmId)")
})
@Table(name="USER_ATTRIBUTE")
@Entity
public class GroupAttributeEntity {
@Id
@Column(name="ID", length = 36)
protected String id;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name = "GROUP_ID")
protected GroupEntity group;
@Column(name = "NAME")
protected String name;
@Column(name = "VALUE")
protected String value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public GroupEntity getGroup() {
return group;
}
public void setGroup(GroupEntity group) {
this.group = group;
}
}

View file

@ -0,0 +1,109 @@
package org.keycloak.models.jpa.entities;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realmId = :realmId order by u.name"),
@NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realmId = :realmId"),
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
@NamedQuery(name="getGroupByName", query="select u from GroupEntity u where u.name = :name and u.realmId = :realmId"),
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realmId = :realmId"),
@NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realmId = :realmId")
})
@Entity
@Table(name="GROUP_ENTITY")
public class GroupEntity {
@Id
@Column(name="ID", length = 36)
protected String id;
@Column(name = "NAME")
protected String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PARENT_GROUP")
private GroupEntity parent;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "REALM")
private RealmEntity realm;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="group")
protected Collection<GroupAttributeEntity> attributes = new ArrayList<GroupAttributeEntity>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Collection<GroupAttributeEntity> getAttributes() {
return attributes;
}
public void setAttributes(Collection<GroupAttributeEntity> attributes) {
this.attributes = attributes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RealmEntity getRealm() {
return realm;
}
public void setRealm(RealmEntity realm) {
this.realm = realm;
}
public GroupEntity getParent() {
return parent;
}
public void setParent(GroupEntity parent) {
this.parent = parent;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GroupEntity that = (GroupEntity) o;
if (!id.equals(that.id)) return false;
return true;
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View file

@ -0,0 +1,101 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
@NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
@NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
@NamedQuery(name="deleteGroupRoleMappingsByRealm", query="delete from GroupRoleMappingEntity mapping where mapping.group IN (select u from GroupEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteGroupRoleMappingsByRole", query="delete from GroupRoleMappingEntity m where m.roleId = :roleId"),
@NamedQuery(name="deleteGroupRoleMappingsByGroup", query="delete from GroupRoleMappingEntity m where m.group = :group")
})
@Table(name="GROUP_ROLE_MAPPING")
@Entity
@IdClass(GroupRoleMappingEntity.Key.class)
public class GroupRoleMappingEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="GROUP_ID")
protected GroupEntity group;
@Id
@Column(name = "ROLE_ID")
protected String roleId;
public GroupEntity getGroup() {
return group;
}
public void setGroup(GroupEntity group) {
this.group = group;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public static class Key implements Serializable {
protected GroupEntity group;
protected String roleId;
public Key() {
}
public Key(GroupEntity group, String roleId) {
this.group = group;
this.roleId = roleId;
}
public GroupEntity getGroup() {
return group;
}
public String getRoleId() {
return roleId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!roleId.equals(key.roleId)) return false;
if (!group.equals(key.group)) return false;
return true;
}
@Override
public int hashCode() {
int result = group.hashCode();
result = 31 * result + roleId.hashCode();
return result;
}
}
}

View file

@ -133,6 +133,9 @@ public class RealmEntity {
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<GroupEntity> groups = new ArrayList<GroupEntity>();
@ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE")
@ -718,5 +721,21 @@ public class RealmEntity {
public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
this.clientAuthenticationFlow = clientAuthenticationFlow;
}
public Collection<GroupEntity> getGroups() {
return groups;
}
public void setGroups(Collection<GroupEntity> groups) {
this.groups = groups;
}
public void addGroup(GroupEntity group) {
if (groups == null) {
groups = new ArrayList<GroupEntity>();
}
groups.add(group);
}
}

View file

@ -0,0 +1,102 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="userMemberOf", query="select m from UserGroupMembershipEntity m where m.user = :user and m.groupId = :groupId"),
@NamedQuery(name="userGroupMembership", query="select m from UserGroupMembershipEntity m where m.user = :user"),
@NamedQuery(name="groupMembership", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId"),
@NamedQuery(name="userGroupIds", query="select m.groupId from UserGroupMembershipEntity m where m.user = :user"),
@NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"),
@NamedQuery(name="deleteUserGroupMembershipsByUser", query="delete from UserGroupMembershipEntity m where m.user = :user")
})
@Table(name="USER_GROUP_MEMBERSHIP")
@Entity
@IdClass(UserGroupMembershipEntity.Key.class)
public class UserGroupMembershipEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;
@Id
@Column(name = "GROUP_ID")
protected String groupId;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public static class Key implements Serializable {
protected UserEntity user;
protected String groupId;
public Key() {
}
public Key(UserEntity user, String groupId) {
this.user = user;
this.groupId = groupId;
}
public UserEntity getUser() {
return user;
}
public String getGroupId() {
return groupId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!groupId.equals(key.groupId)) return false;
if (!user.equals(key.user)) return false;
return true;
}
@Override
public int hashCode() {
int result = user.hashCode();
result = 31 * result + groupId.hashCode();
return result;
}
}
}

View file

@ -0,0 +1,225 @@
package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GroupAdapter extends AbstractMongoAdapter<MongoGroupEntity> implements GroupModel {
private final MongoGroupEntity group;
private RealmModel realm;
private KeycloakSession session;
public GroupAdapter(KeycloakSession session, RealmModel realm, MongoGroupEntity group, MongoStoreInvocationContext invContext) {
super(invContext);
this.group = group;
this.realm = realm;
this.session = session;
}
@Override
public String getId() {
return group.getId();
}
@Override
public String getName() {
return group.getName();
}
@Override
public void setName(String name) {
group.setName(name);
updateGroup();
}
protected void updateGroup() {
super.updateMongoEntity();
}
@Override
public MongoGroupEntity getMongoEntity() {
return group;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof GroupModel)) return false;
GroupModel that = (GroupModel) o;
return that.getId().equals(getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
@Override
public void setSingleAttribute(String name, String value) {
if (group.getAttributes() == null) {
group.setAttributes(new HashMap<String, List<String>>());
}
List<String> attrValues = new ArrayList<>();
attrValues.add(value);
group.getAttributes().put(name, attrValues);
updateGroup();
}
@Override
public void setAttribute(String name, List<String> values) {
if (group.getAttributes() == null) {
group.setAttributes(new HashMap<String, List<String>>());
}
group.getAttributes().put(name, values);
updateGroup();
}
@Override
public void removeAttribute(String name) {
if (group.getAttributes() == null) return;
group.getAttributes().remove(name);
updateGroup();
}
@Override
public String getFirstAttribute(String name) {
if (group.getAttributes()==null) return null;
List<String> attrValues = group.getAttributes().get(name);
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
}
@Override
public List<String> getAttribute(String name) {
if (group.getAttributes()==null) return Collections.<String>emptyList();
List<String> attrValues = group.getAttributes().get(name);
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
}
@Override
public Map<String, List<String>> getAttributes() {
return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
public void grantRole(RoleModel role) {
getMongoStore().pushItemToList(group, "roleIds", role.getId(), true, invocationContext);
}
@Override
public Set<RoleModel> getRoleMappings() {
if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
Set<RoleModel> roles = new HashSet<>();
for (String id : group.getRoleIds()) {
roles.add(realm.getRoleById(id));
}
return roles;
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> allRoles = getRoleMappings();
// Filter to retrieve just realm roles
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : allRoles) {
if (role.getContainer() instanceof RealmModel) {
realmRoles.add(role);
}
}
return realmRoles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
if (group == null || role == null) return;
getMongoStore().pullItemFromList(group, "roleIds", role.getId(), invocationContext);
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<RoleModel>();
Set<RoleModel> roles = getRoleMappings();
for (RoleModel role : roles) {
if (app.equals(role.getContainer())) {
result.add(role);
}
}
return result;
}
@Override
public GroupModel getParent() {
if (group.getParentId() == null) return null;
return realm.getGroupById(group.getParentId());
}
@Override
public Set<GroupModel> getSubGroups() {
Set<GroupModel> subGroups = new HashSet<>();
for (GroupModel groupModel : realm.getGroups()) {
if (groupModel.getParent().equals(this)) {
subGroups.add(groupModel);
}
}
return subGroups;
}
@Override
public void setParent(GroupModel group) {
this.group.setParentId(group.getId());
updateGroup();
}
@Override
public void addChild(GroupModel subGroup) {
subGroup.setParent(this);
updateGroup();
}
@Override
public void removeChild(GroupModel subGroup) {
subGroup.setParent(null);
updateGroup();
}
}

View file

@ -7,11 +7,13 @@ import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
@ -121,6 +123,14 @@ public class MongoRealmProvider implements RealmProvider {
return new RoleAdapter(session, realm, role, null, invocationContext);
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
MongoGroupEntity group = getMongoStore().loadEntity(MongoGroupEntity.class, id, invocationContext);
if (group == null) return null;
if (group.getRealmId() != null && !group.getRealmId().equals(realm.getId())) return null;
return new GroupAdapter(session, realm, group, invocationContext);
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, id, invocationContext);

View file

@ -9,6 +9,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@ -90,10 +91,26 @@ public class MongoUserProvider implements UserProvider {
}
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
QueryBuilder queryBuilder = new QueryBuilder()
.and("realmId").is(realm.getId());
queryBuilder.and("groupIds").is(group.getId());
DBObject sort = new BasicDBObject("username", 1);
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), sort, firstResult, maxResults, invocationContext);
return convertUserEntities(realm, users);
}
protected MongoStore getMongoStore() {
return invocationContext.getMongoStore();
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return getGroupMembers(realm, group, -1, -1);
}
@Override
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
DBObject query = new QueryBuilder()
@ -411,6 +428,17 @@ public class MongoUserProvider implements UserProvider {
getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
// Remove this role from all users, which has it
DBObject query = new QueryBuilder()
.and("groupIds").is(group.getId())
.get();
DBObject pull = new BasicDBObject("$pull", query);
getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// Remove this role from all users, which has it

View file

@ -9,6 +9,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -34,6 +35,7 @@ import org.keycloak.models.entities.RequiredCredentialEntity;
import org.keycloak.models.entities.UserFederationMapperEntity;
import org.keycloak.models.entities.UserFederationProviderEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -607,6 +609,47 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return model.getRoleById(id, this);
}
@Override
public GroupModel getGroupById(String id) {
return model.getGroupById(id, this);
}
@Override
public List<GroupModel> getGroups() {
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
List<GroupModel> result = new LinkedList<>();
if (groups == null) return result;
for (MongoGroupEntity group : groups) {
result.add(new GroupAdapter(session, this, group, invocationContext));
}
return result;
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> all = getGroups();
Iterator<GroupModel> it = all.iterator();
while (it.hasNext()) {
GroupModel group = it.next();
if (group.getParent() != null) {
it.remove();
}
}
return all;
}
@Override
public boolean removeGroup(GroupModel group) {
session.users().preRemove(this, group);
return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
}
@Override
public List<String> getDefaultRoles() {
return realm.getDefaultRoles();

View file

@ -7,6 +7,7 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel;
@ -450,6 +451,37 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return user;
}
@Override
public Set<GroupModel> getGroups() {
if (user.getGroupIds() == null && user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
Set<GroupModel> groups = new HashSet<>();
for (String id : user.getGroupIds()) {
groups.add(realm.getGroupById(id));
}
return groups;
}
@Override
public void joinGroup(GroupModel group) {
getMongoStore().pushItemToList(getUser(), "groupIds", group.getId(), true, invocationContext);
}
@Override
public void leaveGroup(GroupModel group) {
if (user == null || group == null) return;
getMongoStore().pullItemFromList(getUser(), "groupIds", group.getId(), invocationContext);
}
@Override
public boolean isMemberOf(GroupModel group) {
if (user.getGroupIds().contains(group.getId())) return true;
Set<GroupModel> groups = getGroups();
return KeycloakModelUtils.isMember(groups, group);
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();

View file

@ -0,0 +1,26 @@
package org.keycloak.models.mongo.keycloak.entities;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.jboss.logging.Logger;
import org.keycloak.connections.mongo.api.MongoCollection;
import org.keycloak.connections.mongo.api.MongoField;
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.entities.GroupEntity;
import org.keycloak.models.entities.RoleEntity;
import java.util.List;
/**
*/
@MongoCollection(collectionName = "groups")
public class MongoGroupEntity extends GroupEntity implements MongoIdentifiableEntity {
private static final Logger logger = Logger.getLogger(MongoGroupEntity.class);
@Override
public void afterRemove(MongoStoreInvocationContext invContext) {
}
}

View file

@ -4,11 +4,13 @@ import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.entities.ClientEntity;
import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter;
import org.keycloak.models.mongo.keycloak.adapters.GroupAdapter;
import org.keycloak.models.mongo.keycloak.adapters.UserAdapter;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -67,6 +68,11 @@ public class DummyUserFederationProvider implements UserFederationProvider {
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
}
@Override
public boolean isValid(RealmModel realm, UserModel local) {
return false;