KEYCLOAK-15847 Add MapUserProvider
This commit is contained in:
parent
3ddedc49f5
commit
8e376aef51
52 changed files with 2573 additions and 244 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -160,7 +160,7 @@ jobs:
|
|||
run: |
|
||||
declare -A PARAMS TESTGROUP
|
||||
PARAMS["quarkus"]="-Pauth-server-quarkus"
|
||||
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map"
|
||||
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map -Dkeycloak.user.provider=map"
|
||||
PARAMS["wildfly"]="-Pauth-server-wildfly"
|
||||
TESTGROUP["group1"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(a[abc]|ad[a-l]|[^a-q]).*]" # Tests alphabetically before admin tests and those after "r"
|
||||
TESTGROUP["group2"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(ad[^a-l]|a[^a-d]|b).*]" # Admin tests and those starting with "b"
|
||||
|
|
|
@ -353,7 +353,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
Map<String, String> attributes = new HashMap<String, String>();
|
||||
attributes.put(UserModel.SEARCH,search);
|
||||
return searchForUserStream(attributes, realm, firstResult, maxResults);
|
||||
|
@ -365,7 +365,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
String search = params.get(UserModel.SEARCH);
|
||||
if(search!=null) {
|
||||
int spaceIndex = search.lastIndexOf(' ');
|
||||
|
@ -401,7 +401,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
return realm.getComponentsStream(model.getId(), LDAPStorageMapper.class.getName())
|
||||
.sorted(ldapMappersComparator.sortAsc())
|
||||
.map(mapperModel ->
|
||||
|
@ -417,7 +417,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
return realm.getComponentsStream(model.getId(), LDAPStorageMapper.class.getName())
|
||||
.sorted(ldapMappersComparator.sortAsc())
|
||||
.map(mapperModel -> mapperManager.getMapper(mapperModel).getRoleMembers(realm, role, firstResult, maxResults))
|
||||
|
|
|
@ -454,7 +454,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
return getDelegate().getGroupMembersStream(realm, group, firstResult, maxResults);
|
||||
}
|
||||
|
||||
|
@ -464,7 +464,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
return getDelegate().getRoleMembersStream(realm, role, firstResult, maxResults);
|
||||
}
|
||||
|
||||
|
@ -472,7 +472,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role) {
|
||||
return getDelegate().getRoleMembersStream(realm, role);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UserModel getServiceAccount(ClientModel client) {
|
||||
// Just an attempt to find the user from cache by default serviceAccount username
|
||||
|
@ -576,7 +576,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, Integer firstResult, Integer maxResults, boolean includeServiceAccounts) {
|
||||
return getDelegate().getUsersStream(realm, firstResult, maxResults, includeServiceAccounts);
|
||||
}
|
||||
|
||||
|
@ -596,7 +596,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
return getDelegate().searchForUserStream(search, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
|
@ -606,7 +606,7 @@ public class UserCacheSession implements UserCache.Streams {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
return getDelegate().searchForUserStream(attributes, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,18 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
credentialStore = new JpaUserCredentialStore(session, em);
|
||||
}
|
||||
|
||||
private static <T> TypedQuery<T> paginateQuery(TypedQuery<T> query, Integer first, Integer max) {
|
||||
if (first != null && first > 0) {
|
||||
query = query.setFirstResult(first);
|
||||
}
|
||||
|
||||
if (max != null && max >= 0) {
|
||||
query = query.setMaxResults(max);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
if (id == null) {
|
||||
|
@ -121,13 +133,11 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
}
|
||||
|
||||
if (addDefaultRequiredActions) {
|
||||
Optional<String> requiredAction = realm.getRequiredActionProvidersStream()
|
||||
.filter(RequiredActionProviderModel::isEnabled)
|
||||
.filter(RequiredActionProviderModel::isDefaultAction)
|
||||
.map(RequiredActionProviderModel::getAlias)
|
||||
.findFirst();
|
||||
if (requiredAction.isPresent())
|
||||
userModel.addRequiredAction(requiredAction.get());
|
||||
realm.getRequiredActionProvidersStream()
|
||||
.filter(RequiredActionProviderModel::isEnabled)
|
||||
.filter(RequiredActionProviderModel::isDefaultAction)
|
||||
.map(RequiredActionProviderModel::getAlias)
|
||||
.forEach(userModel::addRequiredAction);
|
||||
}
|
||||
|
||||
return userModel;
|
||||
|
@ -140,7 +150,7 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
UserEntity userEntity = em.find(UserEntity.class, user.getId());
|
||||
UserEntity userEntity = em.find(UserEntity.class, user.getId(), LockModeType.PESSIMISTIC_WRITE);
|
||||
if (userEntity == null) return false;
|
||||
removeUser(userEntity);
|
||||
return true;
|
||||
|
@ -150,18 +160,10 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
String id = user.getId();
|
||||
em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
|
||||
em.createNamedQuery("deleteUserGroupMembershipsByUser").setParameter("user", user).executeUpdate();
|
||||
em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate();
|
||||
em.createNamedQuery("deleteUserConsentClientScopesByUser").setParameter("user", user).executeUpdate();
|
||||
em.createNamedQuery("deleteUserConsentsByUser").setParameter("user", user).executeUpdate();
|
||||
em.flush();
|
||||
// not sure why i have to do a clear() here. I was getting some messed up errors that Hibernate couldn't
|
||||
// un-delete the UserEntity.
|
||||
em.clear();
|
||||
user = em.find(UserEntity.class, id, LockModeType.PESSIMISTIC_WRITE);
|
||||
if (user != null) {
|
||||
em.remove(user);
|
||||
}
|
||||
|
||||
em.remove(user);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
|
@ -749,44 +751,29 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, Integer firstResult, Integer maxResults, boolean includeServiceAccounts) {
|
||||
String queryName = includeServiceAccounts ? "getAllUsersByRealm" : "getAllUsersByRealmExcludeServiceAccount" ;
|
||||
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery(queryName, UserEntity.class);
|
||||
query.setParameter("realmId", realm.getId());
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
return closing(query.getResultStream().map(entity -> new UserAdapter(session, realm, em, entity)));
|
||||
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream().map(entity -> new UserAdapter(session, realm, em, entity)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer 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);
|
||||
}
|
||||
return closing(query.getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("usersInRole", UserEntity.class);
|
||||
query.setParameter("roleId", role.getId());
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
return closing(query.getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -795,7 +782,7 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put(UserModel.SEARCH, search);
|
||||
session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, false);
|
||||
|
@ -808,7 +795,7 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<UserEntity> queryBuilder = builder.createQuery(UserEntity.class);
|
||||
Root<UserEntity> root = queryBuilder.from(UserEntity.class);
|
||||
|
@ -915,16 +902,9 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
|
|||
|
||||
TypedQuery<UserEntity> query = em.createQuery(queryBuilder);
|
||||
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
|
||||
UserProvider users = session.users();
|
||||
return closing(query.getResultStream().map(userEntity -> users.getUserById(userEntity.getId(), realm)));
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream())
|
||||
.map(userEntity -> users.getUserById(userEntity.getId(), realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -263,12 +263,6 @@ public class UserAdapter implements UserModel.Streams, JpaModel<UserEntity> {
|
|||
return user.getRequiredActions().stream().map(action -> action.getAction()).distinct();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
String actionName = action.name();
|
||||
addRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String actionName) {
|
||||
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
||||
|
@ -283,12 +277,6 @@ public class UserAdapter implements UserModel.Streams, JpaModel<UserEntity> {
|
|||
user.getRequiredActions().add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
String actionName = action.name();
|
||||
removeRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String actionName) {
|
||||
Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
|
||||
|
|
|
@ -110,7 +110,7 @@ public class UserEntity {
|
|||
@BatchSize(size = 20)
|
||||
protected Collection<CredentialEntity> credentials;
|
||||
|
||||
@OneToMany(mappedBy="user")
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
|
||||
@Fetch(FetchMode.SELECT)
|
||||
@BatchSize(size = 20)
|
||||
protected Collection<FederatedIdentityEntity> federatedIdentities;
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -51,6 +51,7 @@ public class Serialization {
|
|||
}
|
||||
try {
|
||||
// Naive solution but will do.
|
||||
@SuppressWarnings("unchecked")
|
||||
final T res = MAPPER.readValue(MAPPER.writeValueAsBytes(orig), (Class<T>) orig.getClass());
|
||||
return res;
|
||||
} catch (IOException ex) {
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mhajas
|
||||
*/
|
||||
public abstract class AbstractUserEntity<K> implements AbstractEntity<K> {
|
||||
|
||||
private final K id;
|
||||
private final String realmId;
|
||||
|
||||
private String username;
|
||||
private String firstName;
|
||||
private Long createdTimestamp;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private boolean enabled;
|
||||
private boolean emailVerified;
|
||||
// This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings
|
||||
private String emailConstraint = KeycloakModelUtils.generateId();
|
||||
private Map<String, List<String>> attributes = new HashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private final Map<String, UserCredentialEntity> credentials = new HashMap<>();
|
||||
private final List<String> credentialsOrder = new LinkedList<>();
|
||||
private final Map<String, UserFederatedIdentityEntity> federatedIdentities = new HashMap<>();
|
||||
private final Map<String, UserConsentEntity> userConsents = new HashMap<>();
|
||||
private Set<String> groupsMembership = new HashSet<>();
|
||||
private Set<String> rolesMembership = new HashSet<>();
|
||||
private String federationLink;
|
||||
private String serviceAccountClientLink;
|
||||
private int notBefore;
|
||||
|
||||
static Comparator<AbstractUserEntity<?>> COMPARE_BY_USERNAME = Comparator.comparing(AbstractUserEntity::getUsername);
|
||||
|
||||
/**
|
||||
* Flag signalizing that any of the setters has been meaningfully used.
|
||||
*/
|
||||
protected boolean updated;
|
||||
|
||||
protected AbstractUserEntity() {
|
||||
this.id = null;
|
||||
this.realmId = null;
|
||||
}
|
||||
|
||||
public AbstractUserEntity(K id, String realmId) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
Objects.requireNonNull(realmId, "realmId");
|
||||
|
||||
this.id = id;
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.updated
|
||||
|| userConsents.values().stream().anyMatch(UserConsentEntity::isUpdated)
|
||||
|| credentials.values().stream().anyMatch(UserCredentialEntity::isUpdated)
|
||||
|| federatedIdentities.values().stream().anyMatch(UserFederatedIdentityEntity::isUpdated);
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.updated |= !Objects.equals(this.username, username);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.updated |= !Objects.equals(this.firstName, firstName);
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public Long getCreatedTimestamp() {
|
||||
return createdTimestamp;
|
||||
}
|
||||
|
||||
public void setCreatedTimestamp(Long createdTimestamp) {
|
||||
this.updated |= !Objects.equals(this.createdTimestamp, createdTimestamp);
|
||||
this.createdTimestamp = createdTimestamp;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.updated |= !Objects.equals(this.lastName, lastName);
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email, boolean duplicateEmailsAllowed) {
|
||||
this.updated |= !Objects.equals(this.email, email);
|
||||
this.email = email;
|
||||
this.emailConstraint = email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.updated |= !Objects.equals(this.enabled, enabled);
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEmailVerified() {
|
||||
return emailVerified;
|
||||
}
|
||||
|
||||
public void setEmailVerified(boolean emailVerified) {
|
||||
this.updated |= !Objects.equals(this.emailVerified, emailVerified);
|
||||
this.emailVerified = emailVerified;
|
||||
}
|
||||
|
||||
public String getEmailConstraint() {
|
||||
return emailConstraint;
|
||||
}
|
||||
|
||||
public void setEmailConstraint(String emailConstraint) {
|
||||
this.updated |= !Objects.equals(this.emailConstraint, emailConstraint);
|
||||
this.emailConstraint = emailConstraint;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public List<String> getAttribute(String name) {
|
||||
return attributes.getOrDefault(name, Collections.emptyList());
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.updated |= !Objects.equals(this.attributes, attributes);
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public void setAttribute(String name, List<String> value) {
|
||||
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name) {
|
||||
this.updated |= this.attributes.remove(name) != null;
|
||||
}
|
||||
|
||||
public Set<String> getRequiredActions() {
|
||||
return requiredActions;
|
||||
}
|
||||
|
||||
public void setRequiredActions(Set<String> requiredActions) {
|
||||
this.updated |= !Objects.equals(this.requiredActions, requiredActions);
|
||||
this.requiredActions = requiredActions;
|
||||
}
|
||||
|
||||
public void addRequiredAction(String requiredAction) {
|
||||
this.updated |= this.requiredActions.add(requiredAction);
|
||||
}
|
||||
|
||||
public void removeRequiredAction(String requiredAction) {
|
||||
this.updated |= this.requiredActions.remove(requiredAction);
|
||||
}
|
||||
|
||||
public void updateCredential(UserCredentialEntity credentialEntity) {
|
||||
this.updated |= credentials.replace(credentialEntity.getId(), credentialEntity) != null;
|
||||
}
|
||||
|
||||
public void addCredential(UserCredentialEntity credentialEntity) {
|
||||
if (credentials.containsKey(credentialEntity.getId())) {
|
||||
throw new ModelDuplicateException("A CredentialModel with given id already exists");
|
||||
}
|
||||
|
||||
this.updated = true;
|
||||
credentials.put(credentialEntity.getId(), credentialEntity);
|
||||
credentialsOrder.add(credentialEntity.getId());
|
||||
}
|
||||
|
||||
public boolean removeCredential(String credentialId) {
|
||||
if (!credentials.containsKey(credentialId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.updated = true;
|
||||
this.credentials.remove(credentialId);
|
||||
this.credentialsOrder.remove(credentialId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public UserCredentialEntity getCredential(String id) {
|
||||
return credentials.get(id);
|
||||
}
|
||||
|
||||
public Stream<UserCredentialEntity> getCredentials() {
|
||||
return credentialsOrder.stream()
|
||||
.map(credentials::get);
|
||||
}
|
||||
|
||||
public int getCredentialIndex(String credentialId) {
|
||||
return credentialsOrder.indexOf(credentialId);
|
||||
}
|
||||
|
||||
public void moveCredential(int currentPosition, int newPosition) {
|
||||
this.updated |= currentPosition != newPosition;
|
||||
credentialsOrder.add(newPosition, credentialsOrder.remove(currentPosition));
|
||||
}
|
||||
|
||||
public Stream<UserFederatedIdentityEntity> getFederatedIdentities() {
|
||||
return federatedIdentities.values().stream();
|
||||
}
|
||||
|
||||
public void setFederatedIdentities(Collection<UserFederatedIdentityEntity> federatedIdentities) {
|
||||
this.updated = true;
|
||||
this.federatedIdentities.clear();
|
||||
this.federatedIdentities.putAll(federatedIdentities.stream()
|
||||
.collect(Collectors.toMap(UserFederatedIdentityEntity::getIdentityProvider, Function.identity())));
|
||||
}
|
||||
|
||||
public void addFederatedIdentity(UserFederatedIdentityEntity federatedIdentity) {
|
||||
String idpId = federatedIdentity.getIdentityProvider();
|
||||
this.updated |= !Objects.equals(this.federatedIdentities.put(idpId, federatedIdentity), federatedIdentity);
|
||||
}
|
||||
|
||||
public UserFederatedIdentityEntity getFederatedIdentity(String federatedIdentity) {
|
||||
return this.federatedIdentities.get(federatedIdentity);
|
||||
}
|
||||
|
||||
public boolean removeFederatedIdentity(String providerId) {
|
||||
boolean removed = federatedIdentities.remove(providerId) != null;
|
||||
this.updated |= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void updateFederatedIdentity(UserFederatedIdentityEntity federatedIdentityModel) {
|
||||
this.updated |= federatedIdentities.replace(federatedIdentityModel.getIdentityProvider(), federatedIdentityModel) != null;
|
||||
}
|
||||
|
||||
public Stream<UserConsentEntity> getUserConsents() {
|
||||
return userConsents.values().stream();
|
||||
}
|
||||
|
||||
public UserConsentEntity getUserConsent(String clientId) {
|
||||
return this.userConsents.get(clientId);
|
||||
}
|
||||
|
||||
|
||||
public void addUserConsent(UserConsentEntity userConsentEntity) {
|
||||
String clientId = userConsentEntity.getClientId();
|
||||
this.updated |= !Objects.equals(this.userConsents.put(clientId, userConsentEntity), userConsentEntity);
|
||||
}
|
||||
|
||||
public boolean removeUserConsent(String clientId) {
|
||||
boolean removed = userConsents.remove(clientId) != null;
|
||||
this.updated |= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
public Set<String> getGroupsMembership() {
|
||||
return groupsMembership;
|
||||
}
|
||||
|
||||
public void setGroupsMembership(Set<String> groupsMembership) {
|
||||
this.updated |= Objects.equals(groupsMembership, this.groupsMembership);
|
||||
this.groupsMembership = groupsMembership;
|
||||
}
|
||||
|
||||
public void addGroupsMembership(String groupId) {
|
||||
this.updated |= this.groupsMembership.add(groupId);
|
||||
}
|
||||
|
||||
public void removeGroupsMembership(String groupId) {
|
||||
this.updated |= this.groupsMembership.remove(groupId);
|
||||
}
|
||||
|
||||
public Set<String> getRolesMembership() {
|
||||
return rolesMembership;
|
||||
}
|
||||
|
||||
public void setRolesMembership(Set<String> rolesMembership) {
|
||||
this.updated |= Objects.equals(rolesMembership, this.rolesMembership);
|
||||
this.rolesMembership = rolesMembership;
|
||||
}
|
||||
|
||||
public void addRolesMembership(String roleId) {
|
||||
this.updated |= this.rolesMembership.add(roleId);
|
||||
}
|
||||
|
||||
public void removeRolesMembership(String roleId) {
|
||||
this.updated |= this.rolesMembership.remove(roleId);
|
||||
}
|
||||
|
||||
public String getFederationLink() {
|
||||
return federationLink;
|
||||
}
|
||||
|
||||
public void setFederationLink(String federationLink) {
|
||||
this.updated |= !Objects.equals(this.federationLink, federationLink);
|
||||
this.federationLink = federationLink;
|
||||
}
|
||||
|
||||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.updated |= !Objects.equals(this.serviceAccountClientLink, serviceAccountClientLink);
|
||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(int notBefore) {
|
||||
this.updated |= !Objects.equals(this.notBefore, notBefore);
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class AbstractUserModel<E extends AbstractEntity> implements UserModel.Streams {
|
||||
|
||||
protected final KeycloakSession session;
|
||||
protected final RealmModel realm;
|
||||
protected final E entity;
|
||||
|
||||
public AbstractUserModel(KeycloakSession session, RealmModel realm, E entity) {
|
||||
Objects.requireNonNull(entity, "entity");
|
||||
Objects.requireNonNull(realm, "realm");
|
||||
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof UserModel)) return false;
|
||||
|
||||
UserModel that = (UserModel) o;
|
||||
return Objects.equals(that.getId(), getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
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;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
|
||||
public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity entity) {
|
||||
super(session, realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return entity.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
username = KeycloakModelUtils.toLowerCaseSafe(username);
|
||||
// Do not continue if current username of entity is the requested username
|
||||
if (username != null && username.equals(entity.getUsername())) return;
|
||||
|
||||
if (checkUsernameUniqueness(realm, username)) {
|
||||
throw new ModelDuplicateException("A user with username " + username + " already exists");
|
||||
}
|
||||
|
||||
entity.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
return entity.getCreatedTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedTimestamp(Long timestamp) {
|
||||
entity.setCreatedTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return entity.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
entity.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private Optional<String> getSpecialAttributeValue(String name) {
|
||||
if (UserModel.FIRST_NAME.equals(name)) {
|
||||
return Optional.ofNullable(entity.getFirstName());
|
||||
} else if (UserModel.LAST_NAME.equals(name)) {
|
||||
return Optional.ofNullable(entity.getLastName());
|
||||
} else if (UserModel.EMAIL.equals(name)) {
|
||||
return Optional.ofNullable(entity.getEmail());
|
||||
} else if (UserModel.USERNAME.equals(name)) {
|
||||
return Optional.ofNullable(entity.getUsername());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean setSpecialAttributeValue(String name, String value) {
|
||||
if (UserModel.FIRST_NAME.equals(name)) {
|
||||
entity.setFirstName(value);
|
||||
return true;
|
||||
} else if (UserModel.LAST_NAME.equals(name)) {
|
||||
entity.setLastName(value);
|
||||
return true;
|
||||
} else if (UserModel.EMAIL.equals(name)) {
|
||||
setEmail(value);
|
||||
return true;
|
||||
} else if (UserModel.USERNAME.equals(name)) {
|
||||
setUsername(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (setSpecialAttributeValue(name, value)) return;
|
||||
if (value == null) {
|
||||
entity.removeAttribute(name);
|
||||
return;
|
||||
}
|
||||
entity.setAttribute(name, Collections.singletonList(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
String valueToSet = (values != null && values.size() > 0) ? values.get(0) : null;
|
||||
if (setSpecialAttributeValue(name, valueToSet)) return;
|
||||
|
||||
entity.removeAttribute(name);
|
||||
if (valueToSet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
entity.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
return getSpecialAttributeValue(name)
|
||||
.orElseGet(() -> entity.getAttribute(name).stream().findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
return getSpecialAttributeValue(name).map(Collections::singletonList)
|
||||
.orElseGet(() -> entity.getAttribute(name)).stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>(entity.getAttributes());
|
||||
result.add(UserModel.FIRST_NAME, entity.getFirstName());
|
||||
result.add(UserModel.LAST_NAME, entity.getLastName());
|
||||
result.add(UserModel.EMAIL, entity.getEmail());
|
||||
result.add(UserModel.USERNAME, entity.getUsername());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getRequiredActionsStream() {
|
||||
return entity.getRequiredActions().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String action) {
|
||||
entity.addRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String action) {
|
||||
entity.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return entity.getFirstName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
entity.setFirstName(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastName() {
|
||||
return entity.getLastName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
entity.setLastName(lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return entity.getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
email = KeycloakModelUtils.toLowerCaseSafe(email);
|
||||
if (email != null && email.equals(entity.getEmail())) return;
|
||||
boolean duplicatesAllowed = realm.isDuplicateEmailsAllowed();
|
||||
|
||||
if (!duplicatesAllowed && email != null && checkEmailUniqueness(realm, email)) {
|
||||
throw new ModelDuplicateException("A user with email " + email + " already exists");
|
||||
}
|
||||
|
||||
entity.setEmail(email, duplicatesAllowed);
|
||||
}
|
||||
|
||||
public abstract boolean checkEmailUniqueness(RealmModel realm, String email);
|
||||
public abstract boolean checkUsernameUniqueness(RealmModel realm, String username);
|
||||
|
||||
@Override
|
||||
public boolean isEmailVerified() {
|
||||
return entity.isEmailVerified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmailVerified(boolean verified) {
|
||||
entity.setEmailVerified(verified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream() {
|
||||
return session.groups().getGroupsStream(realm, entity.getGroupsMembership().stream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
entity.addGroupsMembership(group.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
entity.removeGroupsMembership(group.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return entity.getGroupsMembership().contains(group.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFederationLink() {
|
||||
return entity.getFederationLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFederationLink(String link) {
|
||||
entity.setFederationLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return entity.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
entity.setServiceAccountClientLink(clientInternalId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||
return getRoleMappingsStream().filter(RoleUtils::isRealmRole);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRoleMappingsStream(ClientModel app) {
|
||||
return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
return entity.getRolesMembership().contains(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
entity.addRolesMembership(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
return entity.getRolesMembership().stream().map(realm::getRoleById);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
entity.removeRolesMembership(role.getId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MapUserEntity extends AbstractUserEntity<UUID> {
|
||||
|
||||
public static final Comparator<MapUserEntity> COMPARE_BY_USERNAME = Comparator.comparing(MapUserEntity::getUsername, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
protected MapUserEntity() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MapUserEntity(UUID id, String realmId) {
|
||||
super(id, realmId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,891 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.credential.UserCredentialStore;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.map.common.Serialization;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.utils.DefaultRoles;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
import static org.keycloak.models.UserModel.EMAIL;
|
||||
import static org.keycloak.models.UserModel.EMAIL_VERIFIED;
|
||||
import static org.keycloak.models.UserModel.FIRST_NAME;
|
||||
import static org.keycloak.models.UserModel.LAST_NAME;
|
||||
import static org.keycloak.models.UserModel.USERNAME;
|
||||
|
||||
public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
|
||||
private static final Predicate<MapUserEntity> ALWAYS_FALSE = c -> { return false; };
|
||||
private final KeycloakSession session;
|
||||
final MapKeycloakTransaction<UUID, MapUserEntity> tx;
|
||||
private final MapStorage<UUID, MapUserEntity> userStore;
|
||||
|
||||
public MapUserProvider(KeycloakSession session, MapStorage<UUID, MapUserEntity> store) {
|
||||
this.session = session;
|
||||
this.userStore = store;
|
||||
this.tx = new MapKeycloakTransaction<>(userStore);
|
||||
session.getTransactionManager().enlist(tx);
|
||||
}
|
||||
|
||||
private MapUserEntity registerEntityForChanges(MapUserEntity origEntity) {
|
||||
MapUserEntity res = tx.get(origEntity.getId(), id -> Serialization.from(origEntity));
|
||||
tx.putIfChanged(origEntity.getId(), res, MapUserEntity::isUpdated);
|
||||
return res;
|
||||
}
|
||||
|
||||
private Function<MapUserEntity, UserModel> entityToAdapterFunc(RealmModel realm) {
|
||||
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||
return origEntity -> new MapUserAdapter(session, realm, registerEntityForChanges(origEntity)) {
|
||||
|
||||
@Override
|
||||
public boolean checkEmailUniqueness(RealmModel realm, String email) {
|
||||
return getUserByEmail(email, realm) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkUsernameUniqueness(RealmModel realm, String username) {
|
||||
return getUserByUsername(username, realm) != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Predicate<MapUserEntity> entityRealmFilter(RealmModel realm) {
|
||||
if (realm == null || realm.getId() == null) {
|
||||
return MapUserProvider.ALWAYS_FALSE;
|
||||
}
|
||||
String realmId = realm.getId();
|
||||
return entity -> Objects.equals(realmId, entity.getRealmId());
|
||||
}
|
||||
|
||||
private ModelException userDoesntExistException() {
|
||||
return new ModelException("Specified user doesn't exist.");
|
||||
}
|
||||
|
||||
private Optional<MapUserEntity> getEntityById(RealmModel realm, String id) {
|
||||
try {
|
||||
return getEntityById(realm, UUID.fromString(id));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private MapUserEntity getRegisteredEntityByIdOrThrow(RealmModel realm, String id) {
|
||||
return getEntityById(realm, id)
|
||||
.map(this::registerEntityForChanges)
|
||||
.orElseThrow(this::userDoesntExistException);
|
||||
}
|
||||
|
||||
private Optional<MapUserEntity> getEntityById(RealmModel realm, UUID id) {
|
||||
MapUserEntity mapUserEntity = tx.get(id, userStore::get);
|
||||
if (mapUserEntity != null && entityRealmFilter(realm).test(mapUserEntity)) {
|
||||
return Optional.of(mapUserEntity);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<MapUserEntity> getRegisteredEntityById(RealmModel realm, String id) {
|
||||
return getEntityById(realm, id).map(this::registerEntityForChanges);
|
||||
}
|
||||
|
||||
private Stream<MapUserEntity> getNotRemovedUpdatedUsersStream() {
|
||||
Stream<MapUserEntity> updatedAndNotRemovedUsersStream = userStore.entrySet().stream()
|
||||
.map(tx::getUpdated) // If the group has been removed, tx.get will return null, otherwise it will return me.getValue()
|
||||
.filter(Objects::nonNull);
|
||||
return Stream.concat(tx.createdValuesStream(), updatedAndNotRemovedUsersStream);
|
||||
}
|
||||
|
||||
private Stream<MapUserEntity> getUnsortedUserEntitiesStream(RealmModel realm) {
|
||||
return getNotRemovedUpdatedUsersStream()
|
||||
.filter(entityRealmFilter(realm));
|
||||
}
|
||||
|
||||
private <T> Stream<T> paginatedStream(Stream<T> originalStream, Integer first, Integer max) {
|
||||
if (first != null && first > 0) {
|
||||
originalStream = originalStream.skip(first);
|
||||
}
|
||||
|
||||
if (max != null && max >= 0) {
|
||||
originalStream = originalStream.limit(max);
|
||||
}
|
||||
|
||||
return originalStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
|
||||
if (user == null || user.getId() == null) {
|
||||
return;
|
||||
}
|
||||
LOG.tracef("addFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialLink.getIdentityProvider(), getShortStackTrace());
|
||||
|
||||
getRegisteredEntityById(realm, user.getId())
|
||||
.ifPresent(userEntity ->
|
||||
userEntity.addFederatedIdentity(UserFederatedIdentityEntity.fromModel(socialLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
|
||||
LOG.tracef("removeFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace());
|
||||
return getRegisteredEntityById(realm, user.getId())
|
||||
.map(entity -> entity.removeFederatedIdentity(socialProvider))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, IdentityProviderModel provider) {
|
||||
String socialProvider = provider.getAlias();
|
||||
LOG.tracef("preRemove[RealmModel realm, IdentityProviderModel provider](%s, %s)%s", realm, socialProvider, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(userEntity -> userEntity.removeFederatedIdentity(socialProvider));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||
LOG.tracef("updateFederatedIdentity(%s, %s, %s)%s", realm, federatedUser.getId(), federatedIdentityModel.getIdentityProvider(), getShortStackTrace());
|
||||
getRegisteredEntityById(realm, federatedUser.getId())
|
||||
.ifPresent(entity -> entity.updateFederatedIdentity(UserFederatedIdentityEntity.fromModel(federatedIdentityModel)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(UserModel user, RealmModel realm) {
|
||||
LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
|
||||
return getEntityById(realm, user.getId())
|
||||
.map(AbstractUserEntity::getFederatedIdentities).orElseGet(Stream::empty)
|
||||
.map(UserFederatedIdentityEntity::toModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
|
||||
LOG.tracef("getFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace());
|
||||
return getEntityById(realm, user.getId())
|
||||
.map(userEntity -> userEntity.getFederatedIdentity(socialProvider))
|
||||
.map(UserFederatedIdentityEntity::toModel)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
|
||||
LOG.tracef("getUserByFederatedIdentity(%s, %s)%s", realm, socialLink, getShortStackTrace());
|
||||
return getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.nonNull(userEntity.getFederatedIdentity(socialLink.getIdentityProvider())))
|
||||
.filter(userEntity -> Objects.equals(userEntity.getFederatedIdentity(socialLink.getIdentityProvider()).getUserId(), socialLink.getUserId()))
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
list -> {
|
||||
if (list.size() == 0) {
|
||||
return null;
|
||||
} else if (list.size() != 1) {
|
||||
throw new IllegalStateException("More results found for identityProvider=" + socialLink.getIdentityProvider() +
|
||||
", userId=" + socialLink.getUserId() + ", results=" + list);
|
||||
}
|
||||
|
||||
return entityToAdapterFunc(realm).apply(list.get(0));
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(RealmModel realm, String userId, UserConsentModel consent) {
|
||||
LOG.tracef("addConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace());
|
||||
|
||||
UserConsentEntity consentEntity = UserConsentEntity.fromModel(consent);
|
||||
getRegisteredEntityById(realm, userId).ifPresent(userEntity -> userEntity.addUserConsent(consentEntity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientInternalId) {
|
||||
LOG.tracef("getConsentByClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace());
|
||||
return getEntityById(realm, userId)
|
||||
.map(userEntity -> userEntity.getUserConsent(clientInternalId))
|
||||
.map(consent -> UserConsentEntity.toModel(realm, consent))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserConsentModel> getConsentsStream(RealmModel realm, String userId) {
|
||||
LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace());
|
||||
return getEntityById(realm, userId)
|
||||
.map(AbstractUserEntity::getUserConsents)
|
||||
.orElse(Stream.empty())
|
||||
.map(consent -> UserConsentEntity.toModel(realm, consent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
|
||||
LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace());
|
||||
|
||||
MapUserEntity user = getRegisteredEntityByIdOrThrow(realm, userId);
|
||||
UserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId());
|
||||
if (userConsentEntity == null) {
|
||||
throw new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]");
|
||||
}
|
||||
|
||||
userConsentEntity.setGrantedClientScopesIds(
|
||||
consent.getGrantedClientScopes().stream()
|
||||
.map(ClientScopeModel::getId)
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
userConsentEntity.setLastUpdatedDate(Time.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) {
|
||||
LOG.tracef("revokeConsentForClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace());
|
||||
return getRegisteredEntityById(realm, userId)
|
||||
.map(userEntity -> userEntity.removeUserConsent(clientInternalId))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||
LOG.tracef("setNotBeforeForUser(%s, %s, %d)%s", realm, user.getId(), notBefore, getShortStackTrace());
|
||||
getRegisteredEntityById(realm, user.getId()).ifPresent(userEntity -> userEntity.setNotBefore(notBefore));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||
LOG.tracef("getNotBeforeOfUser(%s, %s)%s", realm, user.getId(), getShortStackTrace());
|
||||
return getEntityById(realm, user.getId())
|
||||
.map(AbstractUserEntity::getNotBefore)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getServiceAccount(ClientModel client) {
|
||||
LOG.tracef("getServiceAccount(%s)%s", client.getId(), getShortStackTrace());
|
||||
return getUnsortedUserEntitiesStream(client.getRealm())
|
||||
.filter(userEntity -> Objects.equals(userEntity.getServiceAccountClientLink(), client.getId()))
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
list -> {
|
||||
if (list.size() == 0) {
|
||||
return null;
|
||||
} else if (list.size() != 1) {
|
||||
throw new IllegalStateException("More service account linked users found for client=" + client.getClientId() +
|
||||
", results=" + list);
|
||||
}
|
||||
|
||||
return entityToAdapterFunc(client.getRealm()).apply(list.get(0));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
LOG.tracef("addUser(%s, %s, %s, %s, %s)%s", realm, id, username, addDefaultRoles, addDefaultRequiredActions, getShortStackTrace());
|
||||
if (getUnsortedUserEntitiesStream(realm)
|
||||
.anyMatch(userEntity -> Objects.equals(userEntity.getUsername(), username))) {
|
||||
throw new ModelDuplicateException("User with username '" + username + "' in realm " + realm.getName() + " already exists" );
|
||||
}
|
||||
|
||||
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
|
||||
|
||||
if (tx.get(entityId, userStore::get) != null) {
|
||||
throw new ModelDuplicateException("User exists: " + entityId);
|
||||
}
|
||||
|
||||
MapUserEntity entity = new MapUserEntity(entityId, realm.getId());
|
||||
entity.setUsername(username.toLowerCase());
|
||||
entity.setCreatedTimestamp(Time.currentTimeMillis());
|
||||
|
||||
tx.putIfAbsent(entityId, entity);
|
||||
final UserModel userModel = entityToAdapterFunc(realm).apply(entity);
|
||||
|
||||
if (addDefaultRoles) {
|
||||
DefaultRoles.addDefaultRoles(realm, userModel);
|
||||
|
||||
// No need to check if user has group as it's new user
|
||||
realm.getDefaultGroupsStream().forEach(userModel::joinGroup);
|
||||
}
|
||||
|
||||
if (addDefaultRequiredActions){
|
||||
realm.getRequiredActionProvidersStream()
|
||||
.filter(RequiredActionProviderModel::isEnabled)
|
||||
.filter(RequiredActionProviderModel::isDefaultAction)
|
||||
.map(RequiredActionProviderModel::getAlias)
|
||||
.forEach(userModel::addRequiredAction);
|
||||
}
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm) {
|
||||
LOG.tracef("preRemove[RealmModel](%s)%s", realm, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.map(MapUserEntity::getId)
|
||||
.forEach(tx::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeImportedUsers(RealmModel realm, String storageProviderId) {
|
||||
LOG.tracef("removeImportedUsers(%s, %s)%s", realm, storageProviderId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.equals(userEntity.getFederationLink(), storageProviderId))
|
||||
.map(MapUserEntity::getId)
|
||||
.forEach(tx::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlinkUsers(RealmModel realm, String storageProviderId) {
|
||||
LOG.tracef("unlinkUsers(%s, %s)%s", realm, storageProviderId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.equals(userEntity.getFederationLink(), storageProviderId))
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(userEntity -> userEntity.setFederationLink(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
String roleId = role.getId();
|
||||
LOG.tracef("preRemove[RoleModel](%s, %s)%s", realm, roleId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> userEntity.getRolesMembership().contains(roleId))
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(userEntity -> userEntity.removeRolesMembership(roleId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
String groupId = group.getId();
|
||||
LOG.tracef("preRemove[GroupModel](%s, %s)%s", realm, groupId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> userEntity.getGroupsMembership().contains(groupId))
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(userEntity -> userEntity.removeGroupsMembership(groupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, ClientModel client) {
|
||||
String clientId = client.getId();
|
||||
LOG.tracef("preRemove[ClientModel](%s, %s)%s", realm, clientId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.nonNull(userEntity.getUserConsent(clientId)))
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(userEntity -> userEntity.removeUserConsent(clientId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(ProtocolMapperModel protocolMapper) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(ClientScopeModel clientScope) {
|
||||
String clientScopeId = clientScope.getId();
|
||||
LOG.tracef("preRemove[ClientScopeModel](%s)%s", clientScopeId, getShortStackTrace());
|
||||
|
||||
getUnsortedUserEntitiesStream(clientScope.getRealm())
|
||||
.map(this::registerEntityForChanges)
|
||||
.flatMap(AbstractUserEntity::getUserConsents)
|
||||
.forEach(consent -> consent.removeGrantedClientScopesIds(clientScopeId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, ComponentModel component) {
|
||||
String componentId = component.getId();
|
||||
LOG.tracef("preRemove[ComponentModel](%s, %s)%s", realm, componentId, getShortStackTrace());
|
||||
if (component.getProviderType().equals(UserStorageProvider.class.getName())) {
|
||||
removeImportedUsers(realm, componentId);
|
||||
}
|
||||
if (component.getProviderType().equals(ClientStorageProvider.class.getName())) {
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.forEach(removeConsentsForExternalClient(componentId));
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<MapUserEntity> removeConsentsForExternalClient(String componentId) {
|
||||
return userEntity -> {
|
||||
List<UserConsentEntity> consentModels = userEntity.getUserConsents()
|
||||
.filter(consent ->
|
||||
Objects.equals(new StorageId(consent.getClientId()).getProviderId(), componentId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (consentModels.size() > 0) {
|
||||
userEntity = registerEntityForChanges(userEntity);
|
||||
for (UserConsentEntity consentEntity : consentModels) {
|
||||
userEntity.removeUserConsent(consentEntity.getClientId());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||
String roleId = role.getId();
|
||||
LOG.tracef("grantToAllUsers(%s, %s)%s", realm, roleId, getShortStackTrace());
|
||||
getUnsortedUserEntitiesStream(realm)
|
||||
.map(this::registerEntityForChanges)
|
||||
.forEach(entity -> entity.addRolesMembership(roleId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
LOG.tracef("getUserById(%s, %s)%s", realm, id, getShortStackTrace());
|
||||
return getEntityById(realm, id).map(entityToAdapterFunc(realm)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
if (username == null) return null;
|
||||
final String usernameLowercase = username.toLowerCase();
|
||||
|
||||
LOG.tracef("getUserByUsername(%s, %s)%s", realm, username, getShortStackTrace());
|
||||
return getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.equals(userEntity.getUsername(), usernameLowercase))
|
||||
.findFirst()
|
||||
.map(entityToAdapterFunc(realm)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||
LOG.tracef("getUserByEmail(%s, %s)%s", realm, email, getShortStackTrace());
|
||||
List<MapUserEntity> usersWithEmail = getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> Objects.equals(userEntity.getEmail(), email))
|
||||
.collect(Collectors.toList());
|
||||
if (usersWithEmail.size() == 0) return null;
|
||||
|
||||
if (usersWithEmail.size() > 1) {
|
||||
// Realm settings have been changed from allowing duplicate emails to not allowing them
|
||||
// but duplicates haven't been removed.
|
||||
throw new ModelDuplicateException("Multiple users with email '" + email + "' exist in Keycloak.");
|
||||
}
|
||||
|
||||
MapUserEntity userEntity = registerEntityForChanges(usersWithEmail.get(0));
|
||||
|
||||
if (!realm.isDuplicateEmailsAllowed()) {
|
||||
if (userEntity.getEmail() != null && !userEntity.getEmail().equals(userEntity.getEmailConstraint())) {
|
||||
// Realm settings have been changed from allowing duplicate emails to not allowing them.
|
||||
// We need to update the email constraint to reflect this change in the user entities.
|
||||
userEntity.setEmailConstraint(userEntity.getEmail());
|
||||
}
|
||||
}
|
||||
|
||||
return new MapUserAdapter(session, realm, userEntity) {
|
||||
@Override
|
||||
public boolean checkEmailUniqueness(RealmModel realm, String email) {
|
||||
return getUserByEmail(email, realm) != null;
|
||||
}
|
||||
@Override
|
||||
public boolean checkUsernameUniqueness(RealmModel realm, String username) {
|
||||
return getUserByUsername(username, realm) != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
LOG.tracef("getUsersCount(%s)%s", realm, getShortStackTrace());
|
||||
return getUsersCount(realm, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm, boolean includeServiceAccount) {
|
||||
LOG.tracef("getUsersCount(%s, %s)%s", realm, includeServiceAccount, getShortStackTrace());
|
||||
Stream<MapUserEntity> unsortedUserEntitiesStream = getUnsortedUserEntitiesStream(realm);
|
||||
|
||||
if (!includeServiceAccount) {
|
||||
unsortedUserEntitiesStream = unsortedUserEntitiesStream
|
||||
.filter(userEntity -> Objects.isNull(userEntity.getServiceAccountClientLink()));
|
||||
}
|
||||
|
||||
return (int) unsortedUserEntitiesStream.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, Integer firstResult, Integer maxResults, boolean includeServiceAccounts) {
|
||||
LOG.tracef("getUsersStream(%s, %d, %d, %s)%s", realm, firstResult, maxResults, includeServiceAccounts, getShortStackTrace());
|
||||
Stream<MapUserEntity> usersStream = getUnsortedUserEntitiesStream(realm);
|
||||
if (!includeServiceAccounts) {
|
||||
usersStream = usersStream.filter(userEntity -> Objects.isNull(userEntity.getServiceAccountClientLink()));
|
||||
}
|
||||
|
||||
return paginatedStream(usersStream.sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults)
|
||||
.map(entityToAdapterFunc(realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm) {
|
||||
LOG.tracef("getUsersStream(%s)%s", realm, getShortStackTrace());
|
||||
return getUsersStream(realm, null, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, boolean includeServiceAccounts) {
|
||||
LOG.tracef("getUsersStream(%s)%s", realm, getShortStackTrace());
|
||||
return getUsersStream(realm, null, null, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults) {
|
||||
LOG.tracef("getUsersStream(%s, %d, %d)%s", realm, firstResult, maxResults, getShortStackTrace());
|
||||
return getUsersStream(realm, firstResult, maxResults, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm) {
|
||||
LOG.tracef("searchForUserStream(%s, %s)%s", realm, search, getShortStackTrace());
|
||||
return searchForUserStream(search, realm, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
LOG.tracef("searchForUserStream(%s, %s, %d, %d)%s", realm, search, firstResult, maxResults, getShortStackTrace());
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put(UserModel.SEARCH, search);
|
||||
session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, false);
|
||||
return searchForUserStream(attributes, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm) {
|
||||
LOG.tracef("searchForUserStream(%s, %s)%s", realm, params, getShortStackTrace());
|
||||
return searchForUserStream(params, realm, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
LOG.tracef("searchForUserStream(%s, %s, %d, %d)%s", realm, attributes, firstResult, maxResults, getShortStackTrace());
|
||||
/* Find all predicates based on attributes map */
|
||||
List<Predicate<MapUserEntity>> predicatesList = new ArrayList<>();
|
||||
|
||||
if (!session.getAttributeOrDefault(UserModel.INCLUDE_SERVICE_ACCOUNT, true)) {
|
||||
predicatesList.add(userEntity -> Objects.isNull(userEntity.getServiceAccountClientLink()));
|
||||
}
|
||||
|
||||
final boolean exactSearch = Boolean.parseBoolean(attributes.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString()));
|
||||
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String searchedString = value.toLowerCase();
|
||||
Function<Function<MapUserEntity, String>, Predicate<MapUserEntity>> containsOrExactPredicate =
|
||||
func -> {
|
||||
return userEntity -> testContainsOrExact(func.apply(userEntity), searchedString, exactSearch);
|
||||
};
|
||||
|
||||
switch (key) {
|
||||
case UserModel.SEARCH:
|
||||
List<Predicate<MapUserEntity>> orPredicates = new ArrayList<>();
|
||||
orPredicates.add(userEntity -> StringUtils.containsIgnoreCase(userEntity.getUsername(), searchedString));
|
||||
orPredicates.add(userEntity -> StringUtils.containsIgnoreCase(userEntity.getEmail(), searchedString));
|
||||
orPredicates.add(userEntity -> StringUtils.containsIgnoreCase(concatFirstNameLastName(userEntity), searchedString));
|
||||
|
||||
predicatesList.add(orPredicates.stream().reduce(Predicate::or).orElse(t -> false));
|
||||
break;
|
||||
|
||||
case USERNAME:
|
||||
predicatesList.add(containsOrExactPredicate.apply(MapUserEntity::getUsername));
|
||||
break;
|
||||
case FIRST_NAME:
|
||||
predicatesList.add(containsOrExactPredicate.apply(MapUserEntity::getFirstName));
|
||||
break;
|
||||
case LAST_NAME:
|
||||
predicatesList.add(containsOrExactPredicate.apply(MapUserEntity::getLastName));
|
||||
break;
|
||||
case EMAIL:
|
||||
predicatesList.add(containsOrExactPredicate.apply(MapUserEntity::getEmail));
|
||||
break;
|
||||
case EMAIL_VERIFIED: {
|
||||
boolean booleanValue = Boolean.parseBoolean(searchedString);
|
||||
predicatesList.add(userEntity -> Objects.equals(userEntity.isEmailVerified(), booleanValue));
|
||||
break;
|
||||
}
|
||||
case UserModel.ENABLED: {
|
||||
boolean booleanValue = Boolean.parseBoolean(searchedString);
|
||||
predicatesList.add(userEntity -> Objects.equals(userEntity.isEnabled(), booleanValue));
|
||||
break;
|
||||
}
|
||||
case UserModel.IDP_ALIAS: {
|
||||
predicatesList.add(mapUserEntity -> Objects.nonNull(mapUserEntity.getFederatedIdentity(value)));
|
||||
break;
|
||||
}
|
||||
case UserModel.IDP_USER_ID: {
|
||||
predicatesList.add(mapUserEntity -> mapUserEntity.getFederatedIdentities()
|
||||
.anyMatch(idp -> Objects.equals(idp.getUserId(), value)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> userGroups = (Set<String>) session.getAttribute(UserModel.GROUPS);
|
||||
if (userGroups != null && userGroups.size() > 0) {
|
||||
final ResourceStore resourceStore = session.getProvider(AuthorizationProvider.class).getStoreFactory()
|
||||
.getResourceStore();
|
||||
final Predicate<String> resourceByGroupIdExists = id -> resourceStore
|
||||
.findByResourceServer(Collections.singletonMap("name", new String[] { "group.resource." + id }),
|
||||
null, 0, 1).size() == 1;
|
||||
|
||||
predicatesList.add(userEntity -> {
|
||||
return userEntity.getGroupsMembership()
|
||||
.stream()
|
||||
.filter(userGroups::contains)
|
||||
.anyMatch(resourceByGroupIdExists);
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare resulting predicate
|
||||
Predicate<MapUserEntity> resultingPredicate = predicatesList.stream()
|
||||
.reduce(Predicate::and) // Combine all predicates with and
|
||||
.orElse(t -> true); // If there is no predicate in predicatesList, return all users
|
||||
|
||||
Stream<MapUserEntity> usersStream = getUnsortedUserEntitiesStream(realm) // Get stream of all users in the realm
|
||||
.filter(resultingPredicate) // Apply all predicates to userStream
|
||||
.sorted(AbstractUserEntity.COMPARE_BY_USERNAME); // Sort before paginating
|
||||
|
||||
return paginatedStream(usersStream, firstResult, maxResults) // paginate if necessary
|
||||
.map(entityToAdapterFunc(realm))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
private String concatFirstNameLastName(MapUserEntity entity) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (entity.getFirstName() != null) {
|
||||
stringBuilder.append(entity.getFirstName());
|
||||
}
|
||||
|
||||
stringBuilder.append(" ");
|
||||
|
||||
if (entity.getLastName() != null) {
|
||||
stringBuilder.append(entity.getLastName());
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private boolean testContainsOrExact(String testedString, String searchedString, boolean exactMatch) {
|
||||
if (exactMatch) {
|
||||
return StringUtils.equalsIgnoreCase(testedString, searchedString);
|
||||
} else {
|
||||
return StringUtils.containsIgnoreCase(testedString, searchedString);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
LOG.tracef("getGroupMembersStream(%s, %s, %d, %d)%s", realm, group.getId(), firstResult, maxResults, getShortStackTrace());
|
||||
return paginatedStream(getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> userEntity.getGroupsMembership().contains(group.getId()))
|
||||
.sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults)
|
||||
.map(entityToAdapterFunc(realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group) {
|
||||
LOG.tracef("getGroupMembersStream(%s, %s)%s", realm, group.getId(), getShortStackTrace());
|
||||
return getGroupMembersStream(realm, group, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm) {
|
||||
LOG.tracef("searchForUserByUserAttributeStream(%s, %s, %s)%s", realm, attrName, attrValue, getShortStackTrace());
|
||||
return getUnsortedUserEntitiesStream(realm)
|
||||
.filter(userEntity -> userEntity.getAttribute(attrName).contains(attrValue))
|
||||
.map(entityToAdapterFunc(realm))
|
||||
.sorted(UserModel.COMPARE_BY_USERNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
return addUser(realm, null, username.toLowerCase(), true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
String userId = user.getId();
|
||||
Optional<MapUserEntity> userById = getEntityById(realm, userId);
|
||||
if (userById.isPresent()) {
|
||||
tx.remove(UUID.fromString(userId));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
LOG.tracef("getRoleMembersStream(%s, %s, %d, %d)%s", realm, role, firstResult, maxResults, getShortStackTrace());
|
||||
return paginatedStream(getUnsortedUserEntitiesStream(realm)
|
||||
.filter(entity -> entity.getRolesMembership().contains(role.getId()))
|
||||
.sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults)
|
||||
.map(entityToAdapterFunc(realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||
getRegisteredEntityById(realm, user.getId())
|
||||
.ifPresent(updateCredential(cred));
|
||||
}
|
||||
|
||||
private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) {
|
||||
return user -> {
|
||||
UserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId());
|
||||
if (credentialEntity == null) return;
|
||||
|
||||
credentialEntity.setCreatedDate(credentialModel.getCreatedDate());
|
||||
credentialEntity.setUserLabel(credentialModel.getUserLabel());
|
||||
credentialEntity.setType(credentialModel.getType());
|
||||
credentialEntity.setSecretData(credentialModel.getSecretData());
|
||||
credentialEntity.setCredentialData(credentialModel.getCredentialData());
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||
LOG.tracef("createCredential(%s, %s, %s)%s", realm, user.getId(), cred.getId(), getShortStackTrace());
|
||||
UserCredentialEntity credentialEntity = UserCredentialEntity.fromModel(cred);
|
||||
|
||||
getRegisteredEntityByIdOrThrow(realm, user.getId())
|
||||
.addCredential(credentialEntity);
|
||||
|
||||
return UserCredentialEntity.toModel(credentialEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
|
||||
LOG.tracef("removeStoredCredential(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
|
||||
return getRegisteredEntityById(realm, user.getId())
|
||||
.map(mapUserEntity -> mapUserEntity.removeCredential(id))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
|
||||
LOG.tracef("getStoredCredentialById(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
|
||||
return getEntityById(realm, user.getId())
|
||||
.map(mapUserEntity -> mapUserEntity.getCredential(id))
|
||||
.map(UserCredentialEntity::toModel)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
|
||||
LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
|
||||
return getEntityById(realm, user.getId())
|
||||
.map(AbstractUserEntity::getCredentials)
|
||||
.orElseGet(Stream::empty)
|
||||
.map(UserCredentialEntity::toModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
|
||||
LOG.tracef("getStoredCredentialsByTypeStream(%s, %s, %s)%s", realm, user.getId(), type, getShortStackTrace());
|
||||
return getStoredCredentialsStream(realm, user)
|
||||
.filter(credential -> Objects.equals(type, credential.getType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
|
||||
LOG.tracef("getStoredCredentialByNameAndType(%s, %s, %s, %s)%s", realm, user.getId(), name, type, getShortStackTrace());
|
||||
return getStoredCredentialsByType(realm, user, type).stream()
|
||||
.filter(credential -> Objects.equals(name, credential.getUserLabel()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) {
|
||||
LOG.tracef("moveCredentialTo(%s, %s, %s, %s)%s", realm, user.getId(), id, newPreviousCredentialId, getShortStackTrace());
|
||||
String userId = user.getId();
|
||||
MapUserEntity userEntity = getRegisteredEntityById(realm, userId).orElse(null);
|
||||
if (userEntity == null) {
|
||||
LOG.warnf("User with id: [%s] not found", userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find index of credential which should be before id in the list
|
||||
int newPreviousCredentialIdIndex = -1; // If newPreviousCredentialId == null we need to put id credential to index 0
|
||||
if (newPreviousCredentialId != null) {
|
||||
newPreviousCredentialIdIndex = userEntity.getCredentialIndex(newPreviousCredentialId);
|
||||
if (newPreviousCredentialIdIndex == -1) { // If not null previous credential not found, print warning and return false
|
||||
LOG.warnf("Credential with id: [%s] for user: [%s] not found", newPreviousCredentialId, userId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find current index of credential (id) which will be moved
|
||||
int currentPositionOfId = userEntity.getCredentialIndex(id);
|
||||
if (currentPositionOfId == -1) {
|
||||
LOG.warnf("Credential with id: [%s] for user: [%s] not found", id, userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If id is before newPreviousCredentialId in priority list, it will be moved to position -1
|
||||
if (currentPositionOfId < newPreviousCredentialIdIndex) {
|
||||
newPreviousCredentialIdIndex -= 1;
|
||||
}
|
||||
|
||||
// Move credential to desired index
|
||||
userEntity.moveCredential(currentPositionOfId, newPreviousCredentialIdIndex + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserProviderFactory;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mhajas
|
||||
*/
|
||||
public class MapUserProviderFactory extends AbstractMapProviderFactory<UserProvider> implements UserProviderFactory {
|
||||
|
||||
private MapStorage<UUID, MapUserEntity> store;
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||
this.store = sp.getStorage("users", UUID.class, MapUserEntity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UserProvider create(KeycloakSession session) {
|
||||
return new MapUserProvider(session, store);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class UserConsentEntity {
|
||||
|
||||
private String clientId;
|
||||
private final Set<String> grantedClientScopesIds = new HashSet<>();
|
||||
private Long createdDate;
|
||||
private Long lastUpdatedDate;
|
||||
private boolean updated;
|
||||
|
||||
private UserConsentEntity() {}
|
||||
|
||||
public static UserConsentEntity fromModel(UserConsentModel model) {
|
||||
long currentTime = Time.currentTimeMillis();
|
||||
|
||||
UserConsentEntity consentEntity = new UserConsentEntity();
|
||||
consentEntity.setClientId(model.getClient().getId());
|
||||
consentEntity.setCreatedDate(currentTime);
|
||||
consentEntity.setLastUpdatedDate(currentTime);
|
||||
|
||||
model.getGrantedClientScopes()
|
||||
.stream()
|
||||
.map(ClientScopeModel::getId)
|
||||
.forEach(consentEntity::addGrantedClientScopeId);
|
||||
|
||||
return consentEntity;
|
||||
}
|
||||
|
||||
public static UserConsentModel toModel(RealmModel realm, UserConsentEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientModel client = realm.getClientById(entity.getClientId());
|
||||
if (client == null) {
|
||||
throw new ModelException("Client with id " + entity.getClientId() + " is not available");
|
||||
}
|
||||
UserConsentModel model = new UserConsentModel(client);
|
||||
model.setCreatedDate(entity.getCreatedDate());
|
||||
model.setLastUpdatedDate(entity.getLastUpdatedDate());
|
||||
|
||||
entity.getGrantedClientScopesIds().stream()
|
||||
.map(scopeId -> KeycloakModelUtils.findClientScopeById(realm, client, scopeId))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(model::addGrantedClientScope);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.updated = !Objects.equals(this.clientId, clientId);
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public Set<String> getGrantedClientScopesIds() {
|
||||
return grantedClientScopesIds;
|
||||
}
|
||||
|
||||
public void addGrantedClientScopeId(String scope) {
|
||||
this.updated |= grantedClientScopesIds.add(scope);
|
||||
}
|
||||
|
||||
public void setGrantedClientScopesIds(Set<String> scopesIds) {
|
||||
this.updated |= !Objects.equals(grantedClientScopesIds, scopesIds);
|
||||
this.grantedClientScopesIds.clear();
|
||||
this.grantedClientScopesIds.addAll(scopesIds);
|
||||
}
|
||||
|
||||
public void removeGrantedClientScopesIds(String scopesId) {
|
||||
this.updated |= this.grantedClientScopesIds.remove(scopesId);
|
||||
}
|
||||
|
||||
public Long getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Long createdDate) {
|
||||
this.updated |= !Objects.equals(this.createdDate, createdDate);
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public Long getLastUpdatedDate() {
|
||||
return lastUpdatedDate;
|
||||
}
|
||||
|
||||
public void setLastUpdatedDate(Long lastUpdatedDate) {
|
||||
this.updated |= !Objects.equals(this.lastUpdatedDate, lastUpdatedDate);
|
||||
this.lastUpdatedDate = lastUpdatedDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class UserCredentialEntity {
|
||||
|
||||
private String id;
|
||||
private String type;
|
||||
private String userLabel;
|
||||
private Long createdDate;
|
||||
private String secretData;
|
||||
private String credentialData;
|
||||
private boolean updated;
|
||||
|
||||
UserCredentialEntity() {}
|
||||
|
||||
public static UserCredentialEntity fromModel(CredentialModel model) {
|
||||
UserCredentialEntity credentialEntity = new UserCredentialEntity();
|
||||
String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId();
|
||||
credentialEntity.setId(id);
|
||||
credentialEntity.setCreatedDate(model.getCreatedDate());
|
||||
credentialEntity.setUserLabel(model.getUserLabel());
|
||||
credentialEntity.setType(model.getType());
|
||||
credentialEntity.setSecretData(model.getSecretData());
|
||||
credentialEntity.setCredentialData(model.getCredentialData());
|
||||
|
||||
return credentialEntity;
|
||||
}
|
||||
|
||||
public static CredentialModel toModel(UserCredentialEntity entity) {
|
||||
CredentialModel model = new CredentialModel();
|
||||
model.setId(entity.getId());
|
||||
model.setType(entity.getType());
|
||||
model.setCreatedDate(entity.getCreatedDate());
|
||||
model.setUserLabel(entity.getUserLabel());
|
||||
model.setSecretData(entity.getSecretData());
|
||||
model.setCredentialData(entity.getCredentialData());
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.updated |= !Objects.equals(this.id, id);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.updated |= !Objects.equals(this.type, type);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getUserLabel() {
|
||||
return userLabel;
|
||||
}
|
||||
|
||||
public void setUserLabel(String userLabel) {
|
||||
this.updated |= !Objects.equals(this.userLabel, userLabel);
|
||||
this.userLabel = userLabel;
|
||||
}
|
||||
|
||||
public Long getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Long createdDate) {
|
||||
this.updated |= !Objects.equals(this.createdDate, createdDate);
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getSecretData() {
|
||||
return secretData;
|
||||
}
|
||||
|
||||
public void setSecretData(String secretData) {
|
||||
this.updated |= !Objects.equals(this.secretData, secretData);
|
||||
this.secretData = secretData;
|
||||
}
|
||||
|
||||
public String getCredentialData() {
|
||||
return credentialData;
|
||||
}
|
||||
|
||||
public void setCredentialData(String credentialData) {
|
||||
this.updated |= !Objects.equals(this.credentialData, credentialData);
|
||||
this.credentialData = credentialData;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class UserFederatedIdentityEntity {
|
||||
private String token;
|
||||
private String userId;
|
||||
private String identityProvider;
|
||||
private String userName;
|
||||
private boolean updated;
|
||||
|
||||
private UserFederatedIdentityEntity() {}
|
||||
|
||||
public static UserFederatedIdentityEntity fromModel(FederatedIdentityModel model) {
|
||||
if (model == null) return null;
|
||||
UserFederatedIdentityEntity entity = new UserFederatedIdentityEntity();
|
||||
entity.setIdentityProvider(model.getIdentityProvider());
|
||||
entity.setUserId(model.getUserId());
|
||||
entity.setUserName(model.getUserName().toLowerCase());
|
||||
entity.setToken(model.getToken());
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static FederatedIdentityModel toModel(UserFederatedIdentityEntity entity) {
|
||||
if (entity == null) return null;
|
||||
return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken());
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.updated |= !Objects.equals(this.token, token);
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.updated |= !Objects.equals(this.userId, userId);
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getIdentityProvider() {
|
||||
return identityProvider;
|
||||
}
|
||||
|
||||
public void setIdentityProvider(String identityProvider) {
|
||||
this.updated |= !Objects.equals(this.identityProvider, identityProvider);
|
||||
this.identityProvider = identityProvider;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.updated |= !Objects.equals(this.userName, userName);
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
}
|
|
@ -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.map.user.MapUserProviderFactory
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.map.user;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.hamcrest.Matchers;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AbstractUserEntityCredentialsOrderTest {
|
||||
|
||||
private AbstractUserEntity<Integer> user;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
user = new AbstractUserEntity<Integer>(1, "realmId") {};
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
UserCredentialEntity credentialModel = new UserCredentialEntity();
|
||||
credentialModel.setId(Integer.toString(i));
|
||||
|
||||
user.addCredential(credentialModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertOrder(Integer... ids) {
|
||||
List<Integer> currentList = user.getCredentials().map(entity -> Integer.valueOf(entity.getId())).collect(Collectors.toList());
|
||||
assertThat(currentList, Matchers.contains(ids));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectOrder() {
|
||||
assertOrder(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveToZero() {
|
||||
user.moveCredential(2, 0);
|
||||
assertOrder(3, 1, 2, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveBack() {
|
||||
user.moveCredential(3, 1);
|
||||
assertOrder(1, 4, 2, 3, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveForward() {
|
||||
user.moveCredential(1, 3);
|
||||
assertOrder(1, 3, 4, 2, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSamePosition() {
|
||||
user.moveCredential(1, 1);
|
||||
assertOrder(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSamePositionZero() {
|
||||
user.moveCredential(0, 0);
|
||||
assertOrder(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,15 +18,54 @@
|
|||
package org.keycloak.events;
|
||||
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*
|
||||
* This interface provides a way to listen to events that happen during the keycloak run.
|
||||
* <p/>
|
||||
* There are two types of events:
|
||||
* <ul>
|
||||
* <li>Event - User events (fired when users do some action, like log in, register etc.)</li>
|
||||
* <li>Admin event - An administrator did some action like client created/updated etc.</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* Implementors can leverage the fact that the {@code onEvent} and {@code onAdminEvent} are run within a running
|
||||
* transaction. Hence, if the event processing uses JPA, it can insert event details into a table, and the whole
|
||||
* transaction including the event is either committed or rolled back. However if transaction processing is not
|
||||
* an option, e.g. in the case of log files, it is recommended to hook onto transaction after the commit is complete
|
||||
* via the {@link org.keycloak.models.KeycloakTransactionManager#enlistAfterCompletion(KeycloakTransaction)} method, so
|
||||
* that the events are stacked in memory and only written to the file after the original transaction completes
|
||||
* successfully.
|
||||
*
|
||||
*/
|
||||
public interface EventListenerProvider extends Provider {
|
||||
|
||||
/**
|
||||
*
|
||||
* Called when a user event occurs e.g. log in, register.
|
||||
* <p/>
|
||||
* Note this method should not do any action that cannot be rolled back, see {@link EventListenerProvider} javadoc
|
||||
* for more details.
|
||||
*
|
||||
* @param event to be triggered
|
||||
*/
|
||||
void onEvent(Event event);
|
||||
|
||||
/**
|
||||
*
|
||||
* Called when an admin event occurs e.g. a client was updated/deleted.
|
||||
* <p/>
|
||||
* Note this method should not do any action that cannot be rolled back, see {@link EventListenerProvider} javadoc
|
||||
* for more details.
|
||||
*
|
||||
* @param event to be triggered
|
||||
* @param includeRepresentation when false, event listener should NOT include representation field in the resulting
|
||||
* action
|
||||
*/
|
||||
void onEvent(AdminEvent event, boolean includeRepresentation);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.events;
|
||||
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class EventListenerTransaction extends AbstractKeycloakTransaction {
|
||||
|
||||
private static class AdminEventEntry {
|
||||
private final AdminEvent event;
|
||||
private final boolean includeRepresentation;
|
||||
|
||||
public AdminEventEntry(AdminEvent event, boolean includeRepresentation) {
|
||||
this.event = event;
|
||||
this.includeRepresentation = includeRepresentation;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<AdminEventEntry> adminEventsToSend = new LinkedList<>();
|
||||
private final List<Event> eventsToSend = new LinkedList<>();
|
||||
private final BiConsumer<AdminEvent, Boolean> adminEventConsumer;
|
||||
private final Consumer<Event> eventConsumer;
|
||||
|
||||
public EventListenerTransaction(BiConsumer<AdminEvent, Boolean> adminEventConsumer, Consumer<Event> eventConsumer) {
|
||||
this.adminEventConsumer = adminEventConsumer;
|
||||
this.eventConsumer = eventConsumer;
|
||||
}
|
||||
|
||||
public void addAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||
adminEventsToSend.add(new AdminEventEntry(adminEvent, includeRepresentation));
|
||||
}
|
||||
|
||||
public void addEvent(Event event) {
|
||||
eventsToSend.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void commitImpl() {
|
||||
adminEventsToSend.forEach(this::consumeAdminEventEntry);
|
||||
if (eventConsumer != null) {
|
||||
eventsToSend.forEach(eventConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
private void consumeAdminEventEntry(AdminEventEntry entry) {
|
||||
if (adminEventConsumer != null) {
|
||||
adminEventConsumer.accept(entry.event, entry.includeRepresentation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollbackImpl() {
|
||||
adminEventsToSend.clear();
|
||||
eventsToSend.clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -41,6 +41,20 @@ public class AdminEvent {
|
|||
|
||||
private String error;
|
||||
|
||||
public AdminEvent() {}
|
||||
public AdminEvent(AdminEvent toCopy) {
|
||||
this.time = toCopy.getTime();
|
||||
this.realmId = toCopy.getRealmId();
|
||||
this.authDetails = new AuthDetails(toCopy.getAuthDetails());
|
||||
this.resourceType = toCopy.getResourceTypeAsString();
|
||||
this.operationType = toCopy.getOperationType();
|
||||
this.resourcePath = toCopy.getResourcePath();
|
||||
this.representation = toCopy.getRepresentation();
|
||||
this.error = toCopy.getError();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the time of the event
|
||||
*
|
||||
|
|
|
@ -29,6 +29,14 @@ public class AuthDetails {
|
|||
private String userId;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
public AuthDetails() {}
|
||||
public AuthDetails(AuthDetails toCopy) {
|
||||
this.realmId = toCopy.getRealmId();
|
||||
this.clientId = toCopy.getClientId();
|
||||
this.userId = toCopy.getUserId();
|
||||
this.ipAddress = toCopy.getIpAddress();
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models;
|
|||
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -45,6 +46,8 @@ public interface UserModel extends RoleMapperModel {
|
|||
String SEARCH = "keycloak.session.realm.users.query.search";
|
||||
String EXACT = "keycloak.session.realm.users.query.exact";
|
||||
|
||||
Comparator<UserModel> COMPARE_BY_USERNAME = Comparator.comparing(UserModel::getUsername, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
interface UserRemovedEvent extends ProviderEvent {
|
||||
RealmModel getRealm();
|
||||
UserModel getUser();
|
||||
|
@ -56,7 +59,13 @@ public interface UserModel extends RoleMapperModel {
|
|||
// No default method here to allow Abstract subclasses where the username is provided in a different manner
|
||||
String getUsername();
|
||||
|
||||
// No default method here to allow Abstract subclasses where the username is provided in a different manner
|
||||
/**
|
||||
* Sets username for this user.
|
||||
*
|
||||
* No default method here to allow Abstract subclasses where the username is provided in a different manner
|
||||
*
|
||||
* @param username username string
|
||||
*/
|
||||
void setUsername(String username);
|
||||
|
||||
/**
|
||||
|
@ -129,9 +138,17 @@ public interface UserModel extends RoleMapperModel {
|
|||
|
||||
void removeRequiredAction(String action);
|
||||
|
||||
void addRequiredAction(RequiredAction action);
|
||||
default void addRequiredAction(RequiredAction action) {
|
||||
if (action == null) return;
|
||||
String actionName = action.name();
|
||||
addRequiredAction(actionName);
|
||||
}
|
||||
|
||||
void removeRequiredAction(RequiredAction action);
|
||||
default void removeRequiredAction(RequiredAction action) {
|
||||
if (action == null) return;
|
||||
String actionName = action.name();
|
||||
removeRequiredAction(actionName);
|
||||
}
|
||||
|
||||
String getFirstName();
|
||||
|
||||
|
@ -143,6 +160,11 @@ public interface UserModel extends RoleMapperModel {
|
|||
|
||||
String getEmail();
|
||||
|
||||
/**
|
||||
* Sets email for this user.
|
||||
*
|
||||
* @param email the email
|
||||
*/
|
||||
void setEmail(String email);
|
||||
|
||||
boolean isEmailVerified();
|
||||
|
|
|
@ -87,12 +87,25 @@ public interface UserProvider extends Provider,
|
|||
return value != null ? value.stream() : Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param realm
|
||||
* @param userId
|
||||
* @param consent
|
||||
* @throws ModelException when consent doesn't exist for the userId
|
||||
*/
|
||||
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
||||
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
||||
|
||||
void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore);
|
||||
int getNotBeforeOfUser(RealmModel realm, UserModel user);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client
|
||||
* @throws IllegalArgumentException when there are more service accounts associated with the given clientId
|
||||
* @return
|
||||
*/
|
||||
UserModel getServiceAccount(ClientModel client);
|
||||
|
||||
/**
|
||||
|
@ -114,7 +127,7 @@ public interface UserProvider extends Provider,
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getUsersStream(RealmModel, int, int, boolean) getUsersStream} instead.
|
||||
* @deprecated Use {@link #getUsersStream(RealmModel, Integer, Integer, boolean) getUsersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
|
||||
|
@ -128,8 +141,8 @@ public interface UserProvider extends Provider,
|
|||
* @param includeServiceAccounts {@code true} if service accounts should be included in the result; {@code false} otherwise.
|
||||
* @return a non-null {@link Stream} of users associated withe the realm.
|
||||
*/
|
||||
default Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
List<UserModel> value = this.getUsers(realm, firstResult, maxResults, includeServiceAccounts);
|
||||
default Stream<UserModel> getUsersStream(RealmModel realm, Integer firstResult, Integer maxResults, boolean includeServiceAccounts) {
|
||||
List<UserModel> value = this.getUsers(realm, firstResult == null ? -1 : firstResult, maxResults == null ? -1 : maxResults, includeServiceAccounts);
|
||||
return value != null ? value.stream() : Stream.empty();
|
||||
}
|
||||
|
||||
|
@ -204,6 +217,6 @@ public interface UserProvider extends Provider,
|
|||
}
|
||||
|
||||
@Override
|
||||
Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
|
||||
Stream<UserModel> getUsersStream(RealmModel realm, Integer firstResult, Integer maxResults, boolean includeServiceAccounts);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,5 +31,12 @@ public interface UserLookupProvider {
|
|||
|
||||
UserModel getUserByUsername(String username, RealmModel realm);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param email
|
||||
* @param realm
|
||||
* @throws org.keycloak.models.ModelDuplicateException when there are more users with same email
|
||||
* @return
|
||||
*/
|
||||
UserModel getUserByEmail(String email, RealmModel realm);
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ public interface UserQueryProvider {
|
|||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
* @deprecated Use {@link #searchForUserStream(String, RealmModel, int, int) searchForUserStream} instead.
|
||||
* @deprecated Use {@link #searchForUserStream(String, RealmModel, Integer, Integer) searchForUserStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
|
@ -231,8 +231,8 @@ public interface UserQueryProvider {
|
|||
* @param maxResults maximum number of results to return. Ignored if negative.
|
||||
* @return a non-null {@link Stream} of users that match the search criteria.
|
||||
*/
|
||||
default Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
List<UserModel> value = this.searchForUser(search, realm, firstResult, maxResults);
|
||||
default Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
List<UserModel> value = this.searchForUser(search, realm, firstResult == null ? -1 : firstResult, maxResults == null ? -1 : maxResults);
|
||||
return value != null ? value.stream() : Stream.empty();
|
||||
}
|
||||
|
||||
|
@ -294,7 +294,7 @@ public interface UserQueryProvider {
|
|||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
* @deprecated Use {@link #searchForUserStream(Map, RealmModel, int, int) searchForUserStream} instead.
|
||||
* @deprecated Use {@link #searchForUserStream(Map, RealmModel, Integer, Integer) searchForUserStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults);
|
||||
|
@ -317,8 +317,8 @@ public interface UserQueryProvider {
|
|||
* @param maxResults maximum number of results to return. Ignored if negative.
|
||||
* @return a non-null {@link Stream} of users that match the search criteria.
|
||||
*/
|
||||
default Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
List<UserModel> value = this.searchForUser(params, realm, firstResult, maxResults);
|
||||
default Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
List<UserModel> value = this.searchForUser(params, realm, firstResult == null ? -1 : firstResult, maxResults == null ? -1 : maxResults);
|
||||
return value != null ? value.stream() : Stream.empty();
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ public interface UserQueryProvider {
|
|||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
* @deprecated Use {@link #getGroupMembersStream(RealmModel, GroupModel, int, int) getGroupMembersStream} instead.
|
||||
* @deprecated Use {@link #getGroupMembersStream(RealmModel, GroupModel, Integer, Integer) getGroupMembersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||
|
@ -379,8 +379,8 @@ public interface UserQueryProvider {
|
|||
* @param maxResults maximum number of results to return. Ignored if negative.
|
||||
* @return a non-null {@link Stream} of users that belong to the group.
|
||||
*/
|
||||
default Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
List<UserModel> value = this.getGroupMembers(realm, group, firstResult, maxResults);
|
||||
default Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
List<UserModel> value = this.getGroupMembers(realm, group, firstResult == null ? -1 : firstResult, maxResults == null ? -1 : maxResults);
|
||||
return value != null ? value.stream() : Stream.empty();
|
||||
}
|
||||
|
||||
|
@ -405,17 +405,17 @@ public interface UserQueryProvider {
|
|||
* @return a non-null {@link Stream} of users that have the specified role.
|
||||
*/
|
||||
default Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role) {
|
||||
return Stream.empty();
|
||||
return getRoleMembersStream(realm, role, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for users that have a specific role with a specific roleId.
|
||||
*
|
||||
* @param role
|
||||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @param role
|
||||
* @return
|
||||
* @deprecated Use {@link #getRoleMembersStream(RealmModel, RoleModel, int, int) getRoleMembersStream} instead.
|
||||
* @deprecated Use {@link #getRoleMembersStream(RealmModel, RoleModel, Integer, Integer) getRoleMembersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default List<UserModel> getRoleMembers(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
|
@ -431,7 +431,7 @@ public interface UserQueryProvider {
|
|||
* @param maxResults maximum number of results to return. Ignored if negative.
|
||||
* @return a non-null {@link Stream} of users that have the specified role.
|
||||
*/
|
||||
default Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
default Stream<UserModel> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
|
@ -506,7 +506,7 @@ public interface UserQueryProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults);
|
||||
|
||||
@Override
|
||||
default List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
|
||||
|
@ -522,7 +522,7 @@ public interface UserQueryProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults);
|
||||
Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, Integer firstResult, Integer maxResults);
|
||||
|
||||
@Override
|
||||
default List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
|
@ -538,7 +538,7 @@ public interface UserQueryProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||
Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults);
|
||||
|
||||
@Override
|
||||
default List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.email.EmailException;
|
|||
import org.keycloak.email.EmailTemplateProvider;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventListenerTransaction;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -42,6 +43,7 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
|||
private RealmProvider model;
|
||||
private EmailTemplateProvider emailTemplateProvider;
|
||||
private Set<EventType> includedEvents;
|
||||
private EventListenerTransaction tx = new EventListenerTransaction(null, this::sendEmail);
|
||||
|
||||
public EmailEventListenerProvider(KeycloakSession session, EmailTemplateProvider emailTemplateProvider, Set<EventType> includedEvents) {
|
||||
this.session = session;
|
||||
|
@ -54,15 +56,19 @@ public class EmailEventListenerProvider implements EventListenerProvider {
|
|||
public void onEvent(Event event) {
|
||||
if (includedEvents.contains(event.getType())) {
|
||||
if (event.getRealmId() != null && event.getUserId() != null) {
|
||||
RealmModel realm = model.getRealm(event.getRealmId());
|
||||
UserModel user = session.users().getUserById(event.getUserId(), realm);
|
||||
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
||||
try {
|
||||
emailTemplateProvider.setRealm(realm).setUser(user).sendEvent(event);
|
||||
} catch (EmailException e) {
|
||||
log.error("Failed to send type mail", e);
|
||||
}
|
||||
}
|
||||
tx.addEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEmail(Event event) {
|
||||
RealmModel realm = model.getRealm(event.getRealmId());
|
||||
UserModel user = session.users().getUserById(event.getUserId(), realm);
|
||||
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
||||
try {
|
||||
emailTemplateProvider.setRealm(realm).setUser(user).sendEvent(event);
|
||||
} catch (EmailException e) {
|
||||
log.error("Failed to send type mail", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.common.util.StackUtil;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventListenerTransaction;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -40,16 +41,28 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
|
|||
private final Logger logger;
|
||||
private final Logger.Level successLevel;
|
||||
private final Logger.Level errorLevel;
|
||||
private final EventListenerTransaction tx = new EventListenerTransaction(this::logAdminEvent, this::logEvent);
|
||||
|
||||
public JBossLoggingEventListenerProvider(KeycloakSession session, Logger logger, Logger.Level successLevel, Logger.Level errorLevel) {
|
||||
this.session = session;
|
||||
this.logger = logger;
|
||||
this.successLevel = successLevel;
|
||||
this.errorLevel = errorLevel;
|
||||
|
||||
this.session.getTransactionManager().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
tx.addEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||
tx.addAdminEvent(adminEvent, includeRepresentation);
|
||||
}
|
||||
|
||||
private void logEvent(Event event) {
|
||||
Logger.Level level = event.getError() != null ? errorLevel : successLevel;
|
||||
|
||||
if (logger.isEnabled(level)) {
|
||||
|
@ -85,15 +98,15 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||
|
||||
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||
if(authSession!=null) {
|
||||
sb.append(", authSessionParentId=");
|
||||
sb.append(authSession.getParentSession().getId());
|
||||
sb.append(", authSessionTabId=");
|
||||
sb.append(authSession.getTabId());
|
||||
}
|
||||
|
||||
|
||||
if(logger.isTraceEnabled()) {
|
||||
setKeycloakContext(sb);
|
||||
|
||||
|
@ -106,8 +119,7 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||
private void logAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||
Logger.Level level = adminEvent.getError() != null ? errorLevel : successLevel;
|
||||
|
||||
if (logger.isEnabled(level)) {
|
||||
|
@ -132,7 +144,7 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
|
|||
sb.append(", error=");
|
||||
sb.append(adminEvent.getError());
|
||||
}
|
||||
|
||||
|
||||
if(logger.isTraceEnabled()) {
|
||||
setKeycloakContext(sb);
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ public abstract class AbstractPartialImport<T> implements PartialImport<T> {
|
|||
create(realm, session, resourceRep);
|
||||
} catch (Exception e) {
|
||||
ServicesLogger.LOGGER.overwriteError(e, getName(resourceRep));
|
||||
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||
throw e;
|
||||
}
|
||||
|
||||
String modelId = getModelId(realm, session, resourceRep);
|
||||
|
@ -122,7 +122,7 @@ public abstract class AbstractPartialImport<T> implements PartialImport<T> {
|
|||
results.addResult(added(modelId, resourceRep));
|
||||
} catch (Exception e) {
|
||||
ServicesLogger.LOGGER.creationError(e, getName(resourceRep));
|
||||
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.partialimport;
|
|||
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||
|
@ -58,44 +59,40 @@ public class PartialImportManager {
|
|||
}
|
||||
|
||||
public Response saveResources() {
|
||||
try {
|
||||
|
||||
PartialImportResults results = new PartialImportResults();
|
||||
PartialImportResults results = new PartialImportResults();
|
||||
|
||||
for (PartialImport partialImport : partialImports) {
|
||||
try {
|
||||
for (PartialImport partialImport : partialImports) {
|
||||
partialImport.prepare(rep, realm, session);
|
||||
} catch (ErrorResponseException error) {
|
||||
if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly();
|
||||
return error.getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
for (PartialImport partialImport : partialImports) {
|
||||
try {
|
||||
for (PartialImport partialImport : partialImports) {
|
||||
partialImport.removeOverwrites(realm, session);
|
||||
results.addAllResults(partialImport.doImport(rep, realm, session));
|
||||
} catch (ErrorResponseException error) {
|
||||
if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly();
|
||||
return error.getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
for (PartialImportResult result : results.getResults()) {
|
||||
switch (result.getAction()) {
|
||||
case ADDED : fireCreatedEvent(result); break;
|
||||
case OVERWRITTEN: fireUpdateEvent(result); break;
|
||||
for (PartialImportResult result : results.getResults()) {
|
||||
switch (result.getAction()) {
|
||||
case ADDED : fireCreatedEvent(result); break;
|
||||
case OVERWRITTEN: fireUpdateEvent(result); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (session.getTransactionManager().isActive()) {
|
||||
try {
|
||||
if (session.getTransactionManager().isActive()) {
|
||||
session.getTransactionManager().commit();
|
||||
} catch (ModelException e) {
|
||||
return ErrorResponse.exists(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(results).build();
|
||||
return Response.ok(results).build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists(e.getLocalizedMessage());
|
||||
} catch (ErrorResponseException error) {
|
||||
if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly();
|
||||
return error.getResponse();
|
||||
} catch (Exception e) {
|
||||
if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly();
|
||||
return ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireCreatedEvent(PartialImportResult result) {
|
||||
|
|
|
@ -72,9 +72,9 @@ public class AdminEventBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Refreshes the builder assuming that the realm event information has
|
||||
* Refreshes the builder assuming that the realm event information has
|
||||
* changed. Thought to be used when the updateRealmEventsConfig has
|
||||
* modified the events configuration. Now the store and the listeners are
|
||||
* modified the events configuration. Now the store and the listeners are
|
||||
* updated to have previous and new setup.
|
||||
* @param session The session
|
||||
* @return The same builder
|
||||
|
@ -82,7 +82,7 @@ public class AdminEventBuilder {
|
|||
public AdminEventBuilder refreshRealmEventsConfig(KeycloakSession session) {
|
||||
return this.updateStore(session).addListeners(session);
|
||||
}
|
||||
|
||||
|
||||
private AdminEventBuilder updateStore(KeycloakSession session) {
|
||||
if (realm.isAdminEventsEnabled() && store == null) {
|
||||
this.store = session.getProvider(EventStoreProvider.class);
|
||||
|
@ -92,7 +92,7 @@ public class AdminEventBuilder {
|
|||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private AdminEventBuilder addListeners(KeycloakSession session) {
|
||||
realm.getEventsListenersStream()
|
||||
.filter(((Predicate<String>) listeners::containsKey).negate())
|
||||
|
@ -233,15 +233,15 @@ public class AdminEventBuilder {
|
|||
}
|
||||
|
||||
private void send() {
|
||||
boolean includeRepresentation = false;
|
||||
if(realm.isAdminEventsDetailsEnabled()) {
|
||||
includeRepresentation = true;
|
||||
}
|
||||
adminEvent.setTime(Time.currentTimeMillis());
|
||||
boolean includeRepresentation = realm.isAdminEventsDetailsEnabled();
|
||||
|
||||
// Event needs to be copied because the same builder can be used with another event
|
||||
AdminEvent eventCopy = new AdminEvent(adminEvent);
|
||||
eventCopy.setTime(Time.currentTimeMillis());
|
||||
|
||||
if (store != null) {
|
||||
try {
|
||||
store.onEvent(adminEvent, includeRepresentation);
|
||||
store.onEvent(eventCopy, includeRepresentation);
|
||||
} catch (Throwable t) {
|
||||
ServicesLogger.LOGGER.failedToSaveEvent(t);
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ public class AdminEventBuilder {
|
|||
if (listeners != null) {
|
||||
for (EventListenerProvider l : listeners.values()) {
|
||||
try {
|
||||
l.onEvent(adminEvent, includeRepresentation);
|
||||
l.onEvent(eventCopy, includeRepresentation);
|
||||
} catch (Throwable t) {
|
||||
ServicesLogger.LOGGER.failedToSendType(t, l);
|
||||
}
|
||||
|
|
|
@ -101,10 +101,11 @@ public class ClientScopeResource {
|
|||
|
||||
try {
|
||||
RepresentationToModel.updateClientScope(rep, clientScope);
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||
|
||||
if (session.getTransactionManager().isActive()) {
|
||||
session.getTransactionManager().commit();
|
||||
}
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||
return Response.noContent().build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists("Client Scope " + rep.getName() + " already exists");
|
||||
|
|
|
@ -128,13 +128,25 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
if (userCache != null) {
|
||||
userCache.evict(realm, user);
|
||||
}
|
||||
|
||||
// This needs to be running in separate transaction because removing the user may end up with throwing
|
||||
// PessimisticLockException which also rollbacks Jpa transaction, hence when it is running in current transaction
|
||||
// it will become not usable for all consequent jpa calls. It will end up with Transaction is in rolled back
|
||||
// state error
|
||||
runJobInTransaction(session.getKeycloakSessionFactory(), session -> {
|
||||
RealmModel realmModel = session.realms().getRealm(realm.getId());
|
||||
if (realmModel == null) return;
|
||||
UserModel deletedUser = session.userLocalStorage().getUserById(userId, realmModel);
|
||||
if (deletedUser != null) {
|
||||
new UserManager(session).removeUser(realmModel, deletedUser, session.userLocalStorage());
|
||||
logger.debugf("Removed invalid user '%s'", userName);
|
||||
try {
|
||||
new UserManager(session).removeUser(realmModel, deletedUser, session.userLocalStorage());
|
||||
logger.debugf("Removed invalid user '%s'", userName);
|
||||
} catch (ModelException ex) {
|
||||
// Ignore exception, possible cause may be concurrent deleteInvalidUser calls which means
|
||||
// ModelException exception may be ignored because users will be removed with next call or is
|
||||
// already removed
|
||||
logger.debugf(ex, "ModelException thrown during deleteInvalidUser with username '%s'", userName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -268,7 +280,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(final RealmModel realm, final GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
Stream<UserModel> results = query((provider) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).getGroupMembersStream(realm, group);
|
||||
|
@ -289,7 +301,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getRoleMembersStream(final RealmModel realm, final RoleModel role, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getRoleMembersStream(final RealmModel realm, final RoleModel role, Integer firstResult, Integer maxResults) {
|
||||
Stream<UserModel> results = query((provider) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).getRoleMembersStream(realm, role);
|
||||
|
@ -316,7 +328,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getUsersStream(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
|
||||
public Stream<UserModel> getUsersStream(final RealmModel realm, Integer firstResult, Integer maxResults, final boolean includeServiceAccounts) {
|
||||
Stream<UserModel> results = query((provider) -> {
|
||||
if (provider instanceof UserProvider) { // it is local storage
|
||||
return ((UserProvider) provider).getUsersStream(realm, includeServiceAccounts);
|
||||
|
@ -375,7 +387,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
Stream<UserModel> results = query((provider) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).searchForUserStream(search, realm);
|
||||
|
@ -391,7 +403,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> attributes, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
Stream<UserModel> results = query((provider) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
if (attributes.containsKey(UserModel.SEARCH)) {
|
||||
|
|
|
@ -19,7 +19,9 @@ package org.keycloak.testsuite.events;
|
|||
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventListenerTransaction;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
@ -27,20 +29,24 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class EventsListenerProvider implements EventListenerProvider {
|
||||
public class TestEventsListenerProvider implements EventListenerProvider {
|
||||
|
||||
private static final BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
|
||||
private static final BlockingQueue<AdminEvent> adminEvents = new LinkedBlockingQueue<>();
|
||||
private final EventListenerTransaction tx = new EventListenerTransaction((event, includeRepre) -> adminEvents.add(event), events::add);
|
||||
|
||||
public TestEventsListenerProvider(KeycloakSession session) {
|
||||
session.getTransactionManager().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
events.add(event);
|
||||
tx.addEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||
// Save the copy for case when same AdminEventBuilder is used more times during same transaction to avoid overwriting previously referenced event
|
||||
adminEvents.add(copy(event));
|
||||
tx.addAdminEvent(event, includeRepresentation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,17 +69,4 @@ public class EventsListenerProvider implements EventListenerProvider {
|
|||
public static void clearAdminEvents() {
|
||||
adminEvents.clear();
|
||||
}
|
||||
|
||||
private AdminEvent copy(AdminEvent adminEvent) {
|
||||
AdminEvent newEvent = new AdminEvent();
|
||||
newEvent.setAuthDetails(adminEvent.getAuthDetails());
|
||||
newEvent.setError(adminEvent.getError());
|
||||
newEvent.setOperationType(adminEvent.getOperationType());
|
||||
newEvent.setResourceType(adminEvent.getResourceType());
|
||||
newEvent.setRealmId(adminEvent.getRealmId());
|
||||
newEvent.setRepresentation(adminEvent.getRepresentation());
|
||||
newEvent.setResourcePath(adminEvent.getResourcePath());
|
||||
newEvent.setTime(adminEvent.getTime());
|
||||
return newEvent;
|
||||
}
|
||||
}
|
|
@ -26,15 +26,13 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class EventsListenerProviderFactory implements EventListenerProviderFactory {
|
||||
public class TestEventsListenerProviderFactory implements EventListenerProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "event-queue";
|
||||
|
||||
private static final EventsListenerProvider INSTANCE = new EventsListenerProvider();
|
||||
|
||||
@Override
|
||||
public EventListenerProvider create(KeycloakSession session) {
|
||||
return INSTANCE;
|
||||
return new TestEventsListenerProvider(session);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -243,7 +243,7 @@ public class FailableHardcodedStorageProvider implements UserStorageProvider, Us
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
checkForceFail();
|
||||
if (!search.equals(username)) return Stream.empty();
|
||||
UserModel model = getUserByUsername(username, realm);
|
||||
|
@ -259,7 +259,7 @@ public class FailableHardcodedStorageProvider implements UserStorageProvider, Us
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
checkForceFail();
|
||||
if (!username.equals(params.get("username")))return Stream.empty();
|
||||
UserModel model = getUserByUsername(username, realm);
|
||||
|
@ -267,7 +267,7 @@ public class FailableHardcodedStorageProvider implements UserStorageProvider, Us
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
checkForceFail();
|
||||
return Stream.empty();
|
||||
}
|
||||
|
|
|
@ -315,14 +315,14 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
String tSearch = translateUserName(search);
|
||||
Stream<String> userStream = userPasswords.keySet().stream()
|
||||
.sorted()
|
||||
.filter(userName -> translateUserName(userName).contains(search));
|
||||
if (firstResult > 0)
|
||||
if (firstResult != null && firstResult > 0)
|
||||
userStream = userStream.skip(firstResult);
|
||||
if (maxResults >= 0)
|
||||
if (maxResults != null && maxResults >= 0)
|
||||
userStream = userStream.limit(maxResults);
|
||||
return userStream.map(userName -> createUser(realm, userName));
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, Integer firstResult, Integer maxResults) {
|
||||
Stream<String> userStream = userPasswords.keySet().stream()
|
||||
.sorted();
|
||||
|
||||
|
@ -356,15 +356,15 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
|
|||
}
|
||||
}
|
||||
|
||||
if (firstResult > 0)
|
||||
if (firstResult != null && firstResult > 0)
|
||||
userStream = userStream.skip(firstResult);
|
||||
if (maxResults >= 0)
|
||||
if (maxResults != null && maxResults >= 0)
|
||||
userStream = userStream.limit(maxResults);
|
||||
return userStream.map(userName -> createUser(realm, userName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
return getMembershipStream(realm, group, firstResult, maxResults)
|
||||
.map(userName -> createUser(realm, userName));
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ import org.keycloak.services.util.CookieHelper;
|
|||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.testsuite.components.TestProvider;
|
||||
import org.keycloak.testsuite.components.TestProviderFactory;
|
||||
import org.keycloak.testsuite.events.EventsListenerProvider;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProvider;
|
||||
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
||||
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
|
||||
import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
|
||||
|
@ -227,7 +227,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
@Path("/poll-event-queue")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public EventRepresentation getEvent() {
|
||||
Event event = EventsListenerProvider.poll();
|
||||
Event event = TestEventsListenerProvider.poll();
|
||||
if (event != null) {
|
||||
return ModelToRepresentation.toRepresentation(event);
|
||||
} else {
|
||||
|
@ -239,7 +239,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
@Path("/poll-admin-event-queue")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AdminEventRepresentation getAdminEvent() {
|
||||
AdminEvent adminEvent = EventsListenerProvider.pollAdminEvent();
|
||||
AdminEvent adminEvent = TestEventsListenerProvider.pollAdminEvent();
|
||||
if (adminEvent != null) {
|
||||
return ModelToRepresentation.toRepresentation(adminEvent);
|
||||
} else {
|
||||
|
@ -251,7 +251,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
@Path("/clear-event-queue")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response clearEventQueue() {
|
||||
EventsListenerProvider.clear();
|
||||
TestEventsListenerProvider.clear();
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
@Path("/clear-admin-event-queue")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response clearAdminEventQueue() {
|
||||
EventsListenerProvider.clearAdminEvents();
|
||||
TestEventsListenerProvider.clearAdminEvents();
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,4 +32,4 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.testsuite.events.EventsListenerProviderFactory
|
||||
org.keycloak.testsuite.events.TestEventsListenerProviderFactory
|
|
@ -23,7 +23,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.util.TestCleanup;
|
||||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -73,7 +73,7 @@ public abstract class AbstractAdminTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
List<String> eventListeners = new ArrayList<>();
|
||||
eventListeners.add(JBossLoggingEventListenerProviderFactory.ID);
|
||||
eventListeners.add(EventsListenerProviderFactory.PROVIDER_ID);
|
||||
eventListeners.add(TestEventsListenerProviderFactory.PROVIDER_ID);
|
||||
adminRealmRep.setEventsListeners(eventListeners);
|
||||
|
||||
testRealms.add(adminRealmRep);
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.keycloak.models.credential.OTPCredentialModel;
|
|||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -1847,9 +1848,7 @@ public class UserTest extends AbstractAdminTest {
|
|||
} catch (ClientErrorException e) {
|
||||
assertEquals(409, e.getResponse().getStatus());
|
||||
|
||||
// TODO adminEvents: Event queue should be empty, but it's not because of bug in UsersResource.updateUser, which sends event earlier than transaction commit.
|
||||
// assertAdminEvents.assertEmpty();
|
||||
assertAdminEvents.poll();
|
||||
assertAdminEvents.assertEmpty();
|
||||
} finally {
|
||||
enableBruteForce(false);
|
||||
switchEditUsernameAllowedOn(false);
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.testsuite.AbstractAuthTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
|
@ -56,7 +56,7 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
|
|||
@Before
|
||||
public void setupAdminEvents() {
|
||||
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||
if (realm.getEventsListeners() == null || !realm.getEventsListeners().contains(EventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
if (realm.getEventsListeners() == null || !realm.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
realm = RealmBuilder.edit(testRealmResource().toRepresentation()).testEventListener().build();
|
||||
testRealmResource().update(realm);
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.hasItem;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
@ -376,8 +378,8 @@ public class PartialImportTest extends AbstractAuthTest {
|
|||
String id = result.getId();
|
||||
UserResource userRsc = testRealmResource().users().get(id);
|
||||
UserRepresentation user = userRsc.toRepresentation();
|
||||
assertTrue(user.getUsername().startsWith(USER_PREFIX));
|
||||
Assert.assertTrue(userIds.contains(id));
|
||||
Assert.assertThat(user.getUsername(), startsWith(USER_PREFIX));
|
||||
Assert.assertThat(userIds, hasItem(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
|||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.runonserver.RunHelpers;
|
||||
import org.keycloak.testsuite.updaters.Creator;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
|
@ -358,7 +358,7 @@ public class RealmTest extends AbstractAdminTest {
|
|||
RealmEventsConfigRepresentation repOrig = copyRealmEventsConfigRepresentation(rep);
|
||||
|
||||
// the "event-queue" listener should be enabled by default
|
||||
assertTrue("event-queue should be enabled initially", rep.getEventsListeners().contains(EventsListenerProviderFactory.PROVIDER_ID));
|
||||
assertTrue("event-queue should be enabled initially", rep.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID));
|
||||
|
||||
// first modification => remove "event-queue", should be sent to the queue
|
||||
rep.setEnabledEventTypes(Arrays.asList(EventType.LOGIN.name(), EventType.LOGIN_ERROR.name()));
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.testsuite.arquillian.InfinispanStatistics;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.util.TestCleanup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -73,7 +73,7 @@ public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
|
|||
|
||||
List<String> eventListeners = new ArrayList<>();
|
||||
eventListeners.add(JBossLoggingEventListenerProviderFactory.ID);
|
||||
eventListeners.add(EventsListenerProviderFactory.PROVIDER_ID);
|
||||
eventListeners.add(TestEventsListenerProviderFactory.PROVIDER_ID);
|
||||
adminRealmRep.setEventsListeners(eventListeners);
|
||||
|
||||
testRealms.add(adminRealmRep);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -69,6 +69,7 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
|
@ -93,7 +94,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
UserRepresentation user = UserBuilder.create()
|
||||
.id("login-test")
|
||||
.id(UUID.randomUUID().toString())
|
||||
.username("login-test")
|
||||
.email("login@test.com")
|
||||
.enabled(true)
|
||||
|
@ -102,7 +103,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
userId = user.getId();
|
||||
|
||||
UserRepresentation user2 = UserBuilder.create()
|
||||
.id("login-test2")
|
||||
.id(UUID.randomUUID().toString())
|
||||
.username("login-test2")
|
||||
.email("login2@test.com")
|
||||
.enabled(true)
|
||||
|
@ -299,15 +300,15 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
.assertEvent();
|
||||
}
|
||||
|
||||
private void setUserEnabled(String userName, boolean enabled) {
|
||||
UserRepresentation rep = adminClient.realm("test").users().get(userName).toRepresentation();
|
||||
private void setUserEnabled(String id, boolean enabled) {
|
||||
UserRepresentation rep = adminClient.realm("test").users().get(id).toRepresentation();
|
||||
rep.setEnabled(enabled);
|
||||
adminClient.realm("test").users().get(userName).update(rep);
|
||||
adminClient.realm("test").users().get(id).update(rep);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginInvalidPasswordDisabledUser() {
|
||||
setUserEnabled("login-test", false);
|
||||
setUserEnabled(userId, false);
|
||||
|
||||
try {
|
||||
loginPage.open();
|
||||
|
@ -327,13 +328,13 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
} finally {
|
||||
setUserEnabled("login-test", true);
|
||||
setUserEnabled(userId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginDisabledUser() {
|
||||
setUserEnabled("login-test", false);
|
||||
setUserEnabled(userId, false);
|
||||
|
||||
try {
|
||||
loginPage.open();
|
||||
|
@ -353,7 +354,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
} finally {
|
||||
setUserEnabled("login-test", true);
|
||||
setUserEnabled(userId, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,7 +549,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
} finally {
|
||||
setPasswordPolicy(null);
|
||||
UserResource userRsc = adminClient.realm("test").users().get("login-test");
|
||||
UserResource userRsc = adminClient.realm("test").users().get(userId);
|
||||
ApiUtil.resetUserPassword(userRsc, "password", false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.keycloak.testsuite.util.UserBuilder;
|
|||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Tests for {@link org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticator}
|
||||
|
@ -64,6 +65,8 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
|
|||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
private AuthenticationFlowRepresentation flow;
|
||||
private final static String userId = UUID.randomUUID().toString();
|
||||
private final static String failId = UUID.randomUUID().toString();
|
||||
|
||||
public static final String EXECUTION_ID = "scriptAuth";
|
||||
|
||||
|
@ -71,7 +74,7 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
|
|||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
|
||||
UserRepresentation failUser = UserBuilder.create()
|
||||
.id("fail")
|
||||
.id(failId)
|
||||
.username("fail")
|
||||
.email("fail@test.com")
|
||||
.enabled(true)
|
||||
|
@ -79,7 +82,7 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
|
|||
.build();
|
||||
|
||||
UserRepresentation okayUser = UserBuilder.create()
|
||||
.id("user")
|
||||
.id(userId)
|
||||
.username("user")
|
||||
.email("user@test.com")
|
||||
.enabled(true)
|
||||
|
@ -153,7 +156,7 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
|
|||
|
||||
loginPage.login("user", "password");
|
||||
|
||||
events.expectLogin().user("user").detail(Details.USERNAME, "user").assertEvent();
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "user").assertEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,7 +187,7 @@ public class ScriptAuthenticatorTest extends AbstractFlowTest {
|
|||
|
||||
loginPage.login("user", "password");
|
||||
|
||||
events.expectLogin().user("user").detail(Details.USERNAME, "user").assertEvent();
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "user").assertEvent();
|
||||
}
|
||||
|
||||
private void addConfigFromFile(String filename) {
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RolesRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -105,16 +105,16 @@ public class RealmBuilder {
|
|||
rep.setEventsListeners(new LinkedList<String>());
|
||||
}
|
||||
|
||||
if (!rep.getEventsListeners().contains(EventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
rep.getEventsListeners().add(EventsListenerProviderFactory.PROVIDER_ID);
|
||||
if (!rep.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
rep.getEventsListeners().add(TestEventsListenerProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RealmBuilder removeTestEventListener() {
|
||||
if (rep.getEventsListeners() != null && rep.getEventsListeners().contains(EventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
rep.getEventsListeners().remove(EventsListenerProviderFactory.PROVIDER_ID);
|
||||
if (rep.getEventsListeners() != null && rep.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID)) {
|
||||
rep.getEventsListeners().remove(TestEventsListenerProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
|
@ -37,10 +37,12 @@ import java.util.stream.Collectors;
|
|||
import java.util.stream.IntStream;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
@ -97,6 +99,7 @@ public class UserModelTest extends KeycloakModelTest {
|
|||
|
||||
assertTrue(session.users().removeUser(realm, user));
|
||||
assertFalse(session.users().removeUser(realm, user));
|
||||
assertNull(session.users().getUserByUsername(user.getUsername(), realm));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -191,6 +194,7 @@ public class UserModelTest extends KeycloakModelTest {
|
|||
final RealmModel realm = session.realms().getRealm(realmId);
|
||||
final UserModel user = session.users().addUser(realm, "user-" + i);
|
||||
user.joinGroup(session.groups().getGroupById(realm, groupId));
|
||||
log.infof("Created user with id: %s", user.getId());
|
||||
userIds.add(user.getId());
|
||||
}));
|
||||
|
||||
|
@ -205,11 +209,11 @@ public class UserModelTest extends KeycloakModelTest {
|
|||
});
|
||||
});
|
||||
|
||||
inComittedTransaction(1, (session, i) -> {
|
||||
IntStream.range(0, 7).parallel().forEach(index -> inComittedTransaction(index, (session, i) -> {
|
||||
final RealmModel realm = session.realms().getRealm(realmId);
|
||||
final GroupModel group = session.groups().getGroupById(realm, groupId);
|
||||
assertThat(session.users().getGroupMembersStream(realm, group).count(), is(100L - DELETED_USER_COUNT));
|
||||
});
|
||||
}));
|
||||
|
||||
// Now delete the users, and count those that were not found to be deleted. This should be equal to the number
|
||||
// of users removed directly in the user federation.
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
},
|
||||
|
||||
"user": {
|
||||
"provider": "${keycloak.user.provider:}"
|
||||
"provider": "${keycloak.user.provider:jpa}"
|
||||
},
|
||||
|
||||
"userFederatedStorage": {
|
||||
|
|
Loading…
Reference in a new issue