Convert MapUserEntity to interface

This commit is contained in:
Michal Hajas 2021-12-15 13:35:19 +01:00 committed by Hynek Mlnařík
parent 9b18688ce2
commit 9849df3757
14 changed files with 512 additions and 811 deletions

View file

@ -44,6 +44,13 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.models.map.storage.MapStorageProvider; import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory; import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.user.MapUserConsentEntity;
import org.keycloak.models.map.user.MapUserConsentEntityImpl;
import org.keycloak.models.map.user.MapUserCredentialEntity;
import org.keycloak.models.map.user.MapUserCredentialEntityImpl;
import org.keycloak.models.map.user.MapUserEntityImpl;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntityImpl;
import org.keycloak.models.map.storage.ModelEntityUtil; import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
@ -80,10 +87,14 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
private final static DeepCloner CLONER = new DeepCloner.Builder() private final static DeepCloner CLONER = new DeepCloner.Builder()
.genericCloner(Serialization::from) .genericCloner(Serialization::from)
.constructor(MapClientEntityImpl.class, MapClientEntityImpl::new) .constructor(MapClientEntityImpl.class, MapClientEntityImpl::new)
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new) .constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
.constructor(MapGroupEntityImpl.class, MapGroupEntityImpl::new) .constructor(MapGroupEntityImpl.class, MapGroupEntityImpl::new)
.constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new) .constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new)
.constructor(MapUserEntityImpl.class, MapUserEntityImpl::new)
.constructor(MapUserCredentialEntityImpl.class, MapUserCredentialEntityImpl::new)
.constructor(MapUserFederatedIdentityEntityImpl.class, MapUserFederatedIdentityEntityImpl::new)
.constructor(MapUserConsentEntityImpl.class, MapUserConsentEntityImpl::new)
.build(); .build();
private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>(); private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>();

View file

@ -44,6 +44,7 @@ import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity; import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity; import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.storage.QueryParameters; import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.user.MapUserConsentEntity;
import org.keycloak.storage.SearchableModelField; import org.keycloak.storage.SearchableModelField;
import java.util.Comparator; import java.util.Comparator;
@ -52,7 +53,6 @@ import java.util.Map;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.user.MapUserEntity; import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.user.UserConsentEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity; import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
@ -279,7 +279,7 @@ public class MapFieldPredicates {
String providerId = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, "provider_id", op, values); String providerId = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, "provider_id", op, values);
String providerIdS = new StorageId((String) providerId, "").getId(); String providerIdS = new StorageId((String) providerId, "").getId();
Function<MapUserEntity, ?> getter; Function<MapUserEntity, ?> getter;
getter = ue -> ue.getUserConsents().map(UserConsentEntity::getClientId).anyMatch(v -> v != null && v.startsWith(providerIdS)); getter = ue -> Optional.ofNullable(ue.getUserConsents()).orElseGet(Collections::emptyMap).values().stream().map(MapUserConsentEntity::getClientId).anyMatch(v -> v != null && v.startsWith(providerIdS));
return mcb.fieldCompare(Boolean.TRUE::equals, getter); return mcb.fieldCompare(Boolean.TRUE::equals, getter);
} }
@ -330,7 +330,7 @@ public class MapFieldPredicates {
private static MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> checkGrantedUserRole(MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> mcb, Operator op, Object[] values) { private static MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> checkGrantedUserRole(MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values); String roleIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
Function<MapUserEntity, ?> getter; Function<MapUserEntity, ?> getter;
getter = ue -> ue.getRolesMembership().contains(roleIdS); getter = ue -> Optional.ofNullable(ue.getRolesMembership()).orElseGet(Collections::emptySet).contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter); return mcb.fieldCompare(Boolean.TRUE::equals, getter);
} }
@ -433,10 +433,10 @@ public class MapFieldPredicates {
Function<MapUserEntity, ?> getter; Function<MapUserEntity, ?> getter;
if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) { if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) {
Collection<?> c = (Collection<?>) values[0]; Collection<?> c = (Collection<?>) values[0];
getter = ue -> ue.getGroupsMembership().stream().anyMatch(c::contains); getter = ue -> Optional.ofNullable(ue.getGroupsMembership()).orElseGet(Collections::emptySet).stream().anyMatch(c::contains);
} else { } else {
String groupIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_GROUP, "group_id", op, values); String groupIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_GROUP, "group_id", op, values);
getter = ue -> ue.getGroupsMembership().contains(groupIdS); getter = ue -> Optional.ofNullable(ue.getGroupsMembership()).orElseGet(Collections::emptySet).contains(groupIdS);
} }
return mcb.fieldCompare(Boolean.TRUE::equals, getter); return mcb.fieldCompare(Boolean.TRUE::equals, getter);
@ -453,7 +453,7 @@ public class MapFieldPredicates {
private static MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> checkUserConsentsWithClientScope(MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> mcb, Operator op, Object[] values) { private static MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> checkUserConsentsWithClientScope(MapModelCriteriaBuilder<Object, MapUserEntity, UserModel> mcb, Operator op, Object[] values) {
String clientScopeIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_scope_id", op, values); String clientScopeIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_scope_id", op, values);
Function<MapUserEntity, ?> getter; Function<MapUserEntity, ?> getter;
getter = ue -> ue.getUserConsents().anyMatch(consent -> consent.getGrantedClientScopesIds().contains(clientScopeIdS)); getter = ue -> Optional.ofNullable(ue.getUserConsents()).orElseGet(Collections::emptyMap).values().stream().anyMatch(consent -> Optional.ofNullable(consent.getGrantedClientScopesIds()).orElseGet(Collections::emptySet).contains(clientScopeIdS));
return mcb.fieldCompare(Boolean.TRUE::equals, getter); return mcb.fieldCompare(Boolean.TRUE::equals, getter);
} }
@ -469,15 +469,15 @@ public class MapFieldPredicates {
final Object idpAlias = values[0]; final Object idpAlias = values[0];
Function<MapUserEntity, ?> getter; Function<MapUserEntity, ?> getter;
if (values.length == 1) { if (values.length == 1) {
getter = ue -> ue.getFederatedIdentities() getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptyMap).values().stream()
.anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider())); .anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider()));
} else if (idpAlias == null) { } else if (idpAlias == null) {
final Object idpUserId = values[1]; final Object idpUserId = values[1];
getter = ue -> ue.getFederatedIdentities() getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptyMap).values().stream()
.anyMatch(aue -> Objects.equals(idpUserId, aue.getUserId())); .anyMatch(aue -> Objects.equals(idpUserId, aue.getUserId()));
} else { } else {
final Object idpUserId = values[1]; final Object idpUserId = values[1];
getter = ue -> ue.getFederatedIdentities() getter = ue -> Optional.ofNullable(ue.getFederatedIdentities()).orElseGet(Collections::emptyMap).values().stream()
.anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider()) && Objects.equals(idpUserId, aue.getUserId())); .anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider()) && Objects.equals(idpUserId, aue.getUserId()));
} }

View file

@ -33,6 +33,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -76,7 +77,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return entity.isEnabled(); Boolean enabled = entity.isEnabled();
return enabled != null && enabled;
} }
@Override @Override
@ -147,19 +149,20 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public String getFirstAttribute(String name) { public String getFirstAttribute(String name) {
return getSpecialAttributeValue(name) return getSpecialAttributeValue(name)
.orElseGet(() -> entity.getAttribute(name).stream().findFirst() .orElseGet(() -> Optional.ofNullable(entity.getAttribute(name)).orElseGet(Collections::emptyList).stream().findFirst()
.orElse(null)); .orElse(null));
} }
@Override @Override
public Stream<String> getAttributeStream(String name) { public Stream<String> getAttributeStream(String name) {
return getSpecialAttributeValue(name).map(Collections::singletonList) return getSpecialAttributeValue(name).map(Collections::singletonList)
.orElseGet(() -> entity.getAttribute(name)).stream(); .orElseGet(() -> Optional.ofNullable(entity.getAttribute(name)).orElseGet(Collections::emptyList)).stream();
} }
@Override @Override
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>(entity.getAttributes()); Map<String, List<String>> attributes = entity.getAttributes();
MultivaluedHashMap<String, String> result = attributes == null ? new MultivaluedHashMap<>() : new MultivaluedHashMap<>(attributes);
result.add(UserModel.FIRST_NAME, entity.getFirstName()); result.add(UserModel.FIRST_NAME, entity.getFirstName());
result.add(UserModel.LAST_NAME, entity.getLastName()); result.add(UserModel.LAST_NAME, entity.getLastName());
result.add(UserModel.EMAIL, entity.getEmail()); result.add(UserModel.EMAIL, entity.getEmail());
@ -170,7 +173,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public Stream<String> getRequiredActionsStream() { public Stream<String> getRequiredActionsStream() {
return entity.getRequiredActions().stream(); Set<String> requiredActions = entity.getRequiredActions();
return requiredActions == null ? Stream.empty() : requiredActions.stream();
} }
@Override @Override
@ -233,7 +237,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public boolean isEmailVerified() { public boolean isEmailVerified() {
return entity.isEmailVerified(); Boolean emailVerified = entity.isEmailVerified();
return emailVerified != null && emailVerified;
} }
@Override @Override
@ -243,7 +248,9 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public Stream<GroupModel> getGroupsStream() { public Stream<GroupModel> getGroupsStream() {
return session.groups().getGroupsStream(realm, entity.getGroupsMembership().stream()); Set<String> groups = entity.getGroupsMembership();
if (groups == null || groups.isEmpty()) return Stream.empty();
return session.groups().getGroupsStream(realm, groups.stream());
} }
@Override @Override
@ -258,7 +265,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public boolean isMemberOf(GroupModel group) { public boolean isMemberOf(GroupModel group) {
return entity.getGroupsMembership().contains(group.getId()); Set<String> groups = entity.getGroupsMembership();
return groups != null && groups.contains(group.getId());
} }
@Override @Override
@ -294,7 +302,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public boolean hasDirectRole(RoleModel role) { public boolean hasDirectRole(RoleModel role) {
return entity.getRolesMembership().contains(role.getId()); Set<String> roles = entity.getRolesMembership();
return roles != null && entity.getRolesMembership().contains(role.getId());
} }
@Override @Override
@ -309,6 +318,8 @@ public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
@Override @Override
public Stream<RoleModel> getRoleMappingsStream() { public Stream<RoleModel> getRoleMappingsStream() {
Set<String> roles = entity.getRolesMembership();
if (roles == null || roles.isEmpty()) return Stream.empty();
return entity.getRolesMembership().stream().map(realm::getRoleById); return entity.getRolesMembership().stream().map(realm::getRoleById);
} }

View file

@ -0,0 +1,93 @@
/*
* Copyright 2021 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.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Objects;
import java.util.Set;
@GenerateEntityImplementations
@DeepCloner.Root
public interface MapUserConsentEntity extends UpdatableEntity {
public static MapUserConsentEntity fromModel(UserConsentModel model) {
long currentTime = Time.currentTimeMillis();
MapUserConsentEntity consentEntity = new MapUserConsentEntityImpl();
consentEntity.setClientId(model.getClient().getId());
consentEntity.setCreatedDate(currentTime);
consentEntity.setLastUpdatedDate(currentTime);
model.getGrantedClientScopes()
.stream()
.map(ClientScopeModel::getId)
.forEach(consentEntity::addGrantedClientScopesId);
return consentEntity;
}
public static UserConsentModel toModel(RealmModel realm, MapUserConsentEntity 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());
Set<String> grantedClientScopesIds = entity.getGrantedClientScopesIds();
if (grantedClientScopesIds != null && !grantedClientScopesIds.isEmpty()) {
grantedClientScopesIds.stream()
.map(scopeId -> KeycloakModelUtils.findClientScopeById(realm, client, scopeId))
.filter(Objects::nonNull)
.forEach(model::addGrantedClientScope);
}
return model;
}
String getClientId();
void setClientId(String clientId);
Set<String> getGrantedClientScopesIds();
void addGrantedClientScopesId(String scope);
void setGrantedClientScopesIds(Set<String> scopesIds);
void removeGrantedClientScopesId(String scopesId);
Long getCreatedDate();
void setCreatedDate(Long createdDate);
Long getLastUpdatedDate();
void setLastUpdatedDate(Long lastUpdatedDate);
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2021 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.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Comparator;
@GenerateEntityImplementations
@DeepCloner.Root
public interface MapUserCredentialEntity extends UpdatableEntity {
Comparator<MapUserCredentialEntity> ORDER_BY_PRIORITY = Comparator.comparing(MapUserCredentialEntity::getPriority);
public static MapUserCredentialEntity fromModel(CredentialModel model) {
MapUserCredentialEntity credentialEntity = new MapUserCredentialEntityImpl();
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(MapUserCredentialEntity 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;
}
String getId();
void setId(String id);
String getType();
void setType(String type);
String getUserLabel();
void setUserLabel(String userLabel);
Long getCreatedDate();
void setCreatedDate(Long createdDate);
String getSecretData();
void setSecretData(String secretData);
String getCredentialData();
void setCredentialData(String credentialData);
Integer getPriority();
void setPriority(Integer priority);
}

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2020 Red Hat, Inc. and/or its affiliates * Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,363 +17,140 @@
package org.keycloak.models.map.user; package org.keycloak.models.map.user;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.EntityWithAttributes;
import org.keycloak.models.map.common.UpdatableEntity; import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** @GenerateEntityImplementations(
* inherits = "org.keycloak.models.map.user.MapUserEntity.AbstractUserEntity"
* @author mhajas )
*/ @DeepCloner.Root
public class MapUserEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes { public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes {
private String id; public abstract class AbstractUserEntity extends UpdatableEntity.Impl implements MapUserEntity {
private String realmId;
private String username; private String id;
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;
/** @Override
* Flag signalizing that any of the setters has been meaningfully used. public boolean isUpdated() {
*/ return this.updated
|| Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptyMap).values().stream().anyMatch(MapUserConsentEntity::isUpdated)
public MapUserEntity() {} || Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyMap).values().stream().anyMatch(MapUserCredentialEntity::isUpdated)
|| Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptyMap).values().stream().anyMatch(MapUserFederatedIdentityEntity::isUpdated);
public MapUserEntity(String id, String realmId) {
this.id = id;
this.realmId = realmId;
}
@Override
public String getId() {
return this.id;
}
@Override
public void setId(String id) {
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
this.id = id;
this.updated |= id != null;
}
@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 void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = 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;
}
@Override
public List<String> getAttribute(String name) {
return attributes.getOrDefault(name, Collections.emptyList());
}
@Override
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= !Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
@Override
public void setAttribute(String name, List<String> value) {
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
}
@Override
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; @Override
credentials.put(credentialEntity.getId(), credentialEntity); public void clearUpdatedFlag() {
credentialsOrder.add(credentialEntity.getId()); this.updated = false;
} Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptyMap).values().forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyMap).values().forEach(UpdatableEntity::clearUpdatedFlag);
public boolean removeCredential(String credentialId) { Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptyMap).values().forEach(UpdatableEntity::clearUpdatedFlag);
if (!credentials.containsKey(credentialId)) {
return false;
} }
this.updated = true; @Override
this.credentials.remove(credentialId); public String getId() {
this.credentialsOrder.remove(credentialId); return this.id;
}
return true; @Override
} public void setId(String id) {
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
public UserCredentialEntity getCredential(String id) { this.id = id;
return credentials.get(id); this.updated |= id != null;
} }
public Stream<UserCredentialEntity> getCredentials() { @Override
return credentialsOrder.stream() public void setEmail(String email, boolean duplicateEmailsAllowed) {
.map(credentials::get); this.setEmail(email);
} this.setEmailConstraint(email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email);
}
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() { String getRealmId();
return federatedIdentities.values().stream(); void setRealmId(String realmId);
}
public void setFederatedIdentities(Collection<UserFederatedIdentityEntity> federatedIdentities) { String getUsername();
this.updated = true; void setUsername(String username);
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) { String getFirstName();
return this.federatedIdentities.get(federatedIdentity); void setFirstName(String firstName);
}
public boolean removeFederatedIdentity(String providerId) {
boolean removed = federatedIdentities.remove(providerId) != null;
this.updated |= removed;
return removed;
}
public void updateFederatedIdentity(UserFederatedIdentityEntity federatedIdentityModel) { Long getCreatedTimestamp();
this.updated |= federatedIdentities.replace(federatedIdentityModel.getIdentityProvider(), federatedIdentityModel) != null; void setCreatedTimestamp(Long createdTimestamp);
}
public Stream<UserConsentEntity> getUserConsents() { String getLastName();
return userConsents.values().stream(); void setLastName(String lastName);
}
public UserConsentEntity getUserConsent(String clientId) { String getEmail();
return this.userConsents.get(clientId); void setEmail(String email);
} @IgnoreForEntityImplementationGenerator
void setEmail(String email, boolean duplicateEmailsAllowed);
Boolean isEnabled();
public void addUserConsent(UserConsentEntity userConsentEntity) { void setEnabled(Boolean enabled);
String clientId = userConsentEntity.getClientId();
this.updated |= !Objects.equals(this.userConsents.put(clientId, userConsentEntity), userConsentEntity);
}
public boolean removeUserConsent(String clientId) { Boolean isEmailVerified();
boolean removed = userConsents.remove(clientId) != null; void setEmailVerified(Boolean emailVerified);
this.updated |= removed;
return removed;
}
public Set<String> getGroupsMembership() { String getEmailConstraint();
return groupsMembership; void setEmailConstraint(String emailConstraint);
}
public void setGroupsMembership(Set<String> groupsMembership) { Map<String, List<String>> getAttributes();
this.updated |= Objects.equals(groupsMembership, this.groupsMembership); List<String> getAttribute(String name);
this.groupsMembership = groupsMembership; void setAttributes(Map<String, List<String>> attributes);
} void setAttribute(String name, List<String> value);
void removeAttribute(String name);
public void addGroupsMembership(String groupId) {
this.updated |= this.groupsMembership.add(groupId);
}
public void removeGroupsMembership(String groupId) { Set<String> getRequiredActions();
this.updated |= this.groupsMembership.remove(groupId); void setRequiredActions(Set<String> requiredActions);
} void addRequiredAction(String requiredAction);
void removeRequiredAction(String requiredAction);
public Set<String> getRolesMembership() { Map<String, MapUserCredentialEntity> getCredentials();
return rolesMembership; void setCredential(String id, MapUserCredentialEntity credentialEntity);
} Boolean removeCredential(String credentialId);
MapUserCredentialEntity getCredential(String id);
public void setRolesMembership(Set<String> rolesMembership) { Map<String, MapUserFederatedIdentityEntity> getFederatedIdentities();
this.updated |= Objects.equals(rolesMembership, this.rolesMembership); void setFederatedIdentities(Map<String, MapUserFederatedIdentityEntity> federatedIdentities);
this.rolesMembership = rolesMembership; void setFederatedIdentity(String id, MapUserFederatedIdentityEntity federatedIdentity);
} MapUserFederatedIdentityEntity getFederatedIdentity(String federatedIdentity);
Boolean removeFederatedIdentity(String providerId);
public void addRolesMembership(String roleId) { Map<String, MapUserConsentEntity> getUserConsents();
this.updated |= this.rolesMembership.add(roleId); MapUserConsentEntity getUserConsent(String clientId);
} void setUserConsent(String id, MapUserConsentEntity userConsentEntity);
Boolean removeUserConsent(String clientId);
public void removeRolesMembership(String roleId) { Set<String> getGroupsMembership();
this.updated |= this.rolesMembership.remove(roleId); void setGroupsMembership(Set<String> groupsMembership);
} void addGroupsMembership(String groupId);
void removeGroupsMembership(String groupId);
public String getFederationLink() { Set<String> getRolesMembership();
return federationLink; void setRolesMembership(Set<String> rolesMembership);
} void addRolesMembership(String roleId);
void removeRolesMembership(String roleId);
public void setFederationLink(String federationLink) { String getFederationLink();
this.updated |= !Objects.equals(this.federationLink, federationLink); void setFederationLink(String federationLink);
this.federationLink = federationLink;
}
public String getServiceAccountClientLink() { String getServiceAccountClientLink();
return serviceAccountClientLink; void setServiceAccountClientLink(String 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;
}
Integer getNotBefore();
void setNotBefore(Integer notBefore);
} }

View file

@ -0,0 +1,56 @@
/*
* Copyright 2021 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 org.keycloak.models.map.annotations.GenerateEntityImplementations;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UpdatableEntity;
@GenerateEntityImplementations
@DeepCloner.Root
public interface MapUserFederatedIdentityEntity extends UpdatableEntity {
public static MapUserFederatedIdentityEntity fromModel(FederatedIdentityModel model) {
if (model == null) return null;
MapUserFederatedIdentityEntity entity = new MapUserFederatedIdentityEntityImpl();
entity.setIdentityProvider(model.getIdentityProvider());
entity.setUserId(model.getUserId());
entity.setUserName(model.getUserName().toLowerCase());
entity.setToken(model.getToken());
return entity;
}
public static FederatedIdentityModel toModel(MapUserFederatedIdentityEntity entity) {
if (entity == null) return null;
return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken());
}
String getToken();
void setToken(String token);
String getUserId();
void setUserId(String userId);
String getIdentityProvider();
void setIdentityProvider(String identityProvider);
String getUserName();
void setUserName(String userName);
}

View file

@ -45,13 +45,17 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorage; import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.client.ClientStorageProvider; import org.keycloak.storage.client.ClientStorageProvider;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -75,6 +79,8 @@ import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.crit
public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams { public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams {
// Typical priority difference between 2 credentials
public static final int PRIORITY_DIFFERENCE = 10;
private static final Logger LOG = Logger.getLogger(MapUserProvider.class); private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<MapUserEntity, UserModel> tx; final MapKeycloakTransaction<MapUserEntity, UserModel> tx;
@ -139,15 +145,18 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
getEntityById(realm, user.getId()) getEntityById(realm, user.getId())
.ifPresent(userEntity -> .ifPresent(userEntity ->
userEntity.addFederatedIdentity(UserFederatedIdentityEntity.fromModel(socialLink))); userEntity.setFederatedIdentity(socialLink.getIdentityProvider(), MapUserFederatedIdentityEntity.fromModel(socialLink)));
} }
@Override @Override
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
LOG.tracef("removeFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace()); LOG.tracef("removeFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace());
return getEntityById(realm, user.getId())
.map(entity -> entity.removeFederatedIdentity(socialProvider)) Optional<MapUserEntity> entityById = getEntityById(realm, user.getId());
.orElse(false); if (!entityById.isPresent()) return false;
Boolean result = entityById.get().removeFederatedIdentity(socialProvider);
return result == null ? true : result; // TODO: make removeFederatedIdentity return Boolean so the caller can correctly handle "I don't know" null answer
} }
@Override @Override
@ -166,15 +175,18 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
LOG.tracef("updateFederatedIdentity(%s, %s, %s)%s", realm, federatedUser.getId(), federatedIdentityModel.getIdentityProvider(), getShortStackTrace()); LOG.tracef("updateFederatedIdentity(%s, %s, %s)%s", realm, federatedUser.getId(), federatedIdentityModel.getIdentityProvider(), getShortStackTrace());
getEntityById(realm, federatedUser.getId()) getEntityById(realm, federatedUser.getId())
.ifPresent(entity -> entity.updateFederatedIdentity(UserFederatedIdentityEntity.fromModel(federatedIdentityModel))); .ifPresent(entity -> entity.setFederatedIdentity(federatedIdentityModel.getIdentityProvider(), MapUserFederatedIdentityEntity.fromModel(federatedIdentityModel)));
} }
@Override @Override
public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(RealmModel realm, UserModel user) { public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(RealmModel realm, UserModel user) {
LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace()); LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(MapUserEntity::getFederatedIdentities).orElseGet(Stream::empty) .map(MapUserEntity::getFederatedIdentities)
.map(UserFederatedIdentityEntity::toModel); .map(Map::values)
.map(Collection::stream)
.orElseGet(Stream::empty)
.map(MapUserFederatedIdentityEntity::toModel);
} }
@Override @Override
@ -182,7 +194,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("getFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace()); LOG.tracef("getFederatedIdentity(%s, %s, %s)%s", realm, user.getId(), socialProvider, getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(userEntity -> userEntity.getFederatedIdentity(socialProvider)) .map(userEntity -> userEntity.getFederatedIdentity(socialProvider))
.map(UserFederatedIdentityEntity::toModel) .map(MapUserFederatedIdentityEntity::toModel)
.orElse(null); .orElse(null);
} }
@ -213,7 +225,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("addConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace()); LOG.tracef("addConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace());
getEntityByIdOrThrow(realm, userId) getEntityByIdOrThrow(realm, userId)
.addUserConsent(UserConsentEntity.fromModel(consent)); .setUserConsent(consent.getClient().getId(), MapUserConsentEntity.fromModel(consent));
} }
@Override @Override
@ -221,7 +233,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("getConsentByClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace()); LOG.tracef("getConsentByClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace());
return getEntityById(realm, userId) return getEntityById(realm, userId)
.map(userEntity -> userEntity.getUserConsent(clientInternalId)) .map(userEntity -> userEntity.getUserConsent(clientInternalId))
.map(consent -> UserConsentEntity.toModel(realm, consent)) .map(consent -> MapUserConsentEntity.toModel(realm, consent))
.orElse(null); .orElse(null);
} }
@ -230,8 +242,10 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace()); LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace());
return getEntityById(realm, userId) return getEntityById(realm, userId)
.map(MapUserEntity::getUserConsents) .map(MapUserEntity::getUserConsents)
.map(Map::values)
.map(Collection::stream)
.orElse(Stream.empty()) .orElse(Stream.empty())
.map(consent -> UserConsentEntity.toModel(realm, consent)); .map(consent -> MapUserConsentEntity.toModel(realm, consent));
} }
@Override @Override
@ -239,7 +253,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace()); LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace());
MapUserEntity user = getEntityByIdOrThrow(realm, userId); MapUserEntity user = getEntityByIdOrThrow(realm, userId);
UserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId()); MapUserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId());
if (userConsentEntity == null) { if (userConsentEntity == null) {
throw new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]"); throw new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]");
} }
@ -256,9 +270,12 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override @Override
public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) { public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) {
LOG.tracef("revokeConsentForClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace()); LOG.tracef("revokeConsentForClient(%s, %s, %s)%s", realm, userId, clientInternalId, getShortStackTrace());
return getEntityById(realm, userId)
.map(userEntity -> userEntity.removeUserConsent(clientInternalId)) Optional<MapUserEntity> entityById = getEntityById(realm, userId);
.orElse(false); if (!entityById.isPresent()) return false;
Boolean result = entityById.get().removeUserConsent(clientInternalId);
return result == null ? true : result; // TODO: make revokeConsentForClient return Boolean so the caller can correctly handle "I don't know" null answer
} }
@Override @Override
@ -270,9 +287,11 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override @Override
public int getNotBeforeOfUser(RealmModel realm, UserModel user) { public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
LOG.tracef("getNotBeforeOfUser(%s, %s)%s", realm, user.getId(), getShortStackTrace()); LOG.tracef("getNotBeforeOfUser(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId()) Integer notBefore = getEntityById(realm, user.getId())
.orElseThrow(this::userDoesntExistException) .orElseThrow(this::userDoesntExistException)
.getNotBefore(); .getNotBefore();
return notBefore == null ? 0 : notBefore;
} }
@Override @Override
@ -313,7 +332,10 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
throw new ModelDuplicateException("User exists: " + id); throw new ModelDuplicateException("User exists: " + id);
} }
MapUserEntity entity = new MapUserEntity(id, realm.getId()); MapUserEntity entity = new MapUserEntityImpl();
entity.setId(id);
entity.setRealmId(realm.getId());
entity.setEmailConstraint(KeycloakModelUtils.generateId());
entity.setUsername(username.toLowerCase()); entity.setUsername(username.toLowerCase());
entity.setCreatedTimestamp(Time.currentTimeMillis()); entity.setCreatedTimestamp(Time.currentTimeMillis());
@ -423,8 +445,11 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId); .compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId);
try (Stream<MapUserEntity> s = tx.read(withCriteria(mcb))) { try (Stream<MapUserEntity> s = tx.read(withCriteria(mcb))) {
s.flatMap(MapUserEntity::getUserConsents) s.map(MapUserEntity::getUserConsents)
.forEach(consent -> consent.removeGrantedClientScopesIds(clientScopeId)); .filter(Objects::nonNull)
.map(Map::values)
.flatMap(Collection::stream)
.forEach(consent -> consent.removeGrantedClientScopesId(clientScopeId));
} }
} }
@ -449,8 +474,10 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
private Consumer<MapUserEntity> removeConsentsForExternalClient(String idPrefix) { private Consumer<MapUserEntity> removeConsentsForExternalClient(String idPrefix) {
return userEntity -> { return userEntity -> {
List<String> consentClientIds = userEntity.getUserConsents() Map<String, MapUserConsentEntity> userConsents = userEntity.getUserConsents();
.map(UserConsentEntity::getClientId) if (userConsents == null || userConsents.isEmpty()) return;
List<String> consentClientIds = userConsents.values().stream()
.map(MapUserConsentEntity::getClientId)
.filter(clientId -> clientId != null && clientId.startsWith(idPrefix)) .filter(clientId -> clientId != null && clientId.startsWith(idPrefix))
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -730,7 +757,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) { private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) {
return user -> { return user -> {
UserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId()); MapUserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId());
if (credentialEntity == null) return; if (credentialEntity == null) return;
credentialEntity.setCreatedDate(credentialModel.getCreatedDate()); credentialEntity.setCreatedDate(credentialModel.getCreatedDate());
@ -744,20 +771,35 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override @Override
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
LOG.tracef("createCredential(%s, %s, %s)%s", realm, user.getId(), cred.getId(), getShortStackTrace()); LOG.tracef("createCredential(%s, %s, %s)%s", realm, user.getId(), cred.getId(), getShortStackTrace());
UserCredentialEntity credentialEntity = UserCredentialEntity.fromModel(cred); MapUserEntity userEntity = getEntityByIdOrThrow(realm, user.getId());
MapUserCredentialEntity credentialEntity = MapUserCredentialEntity.fromModel(cred);
getEntityByIdOrThrow(realm, user.getId()) if (userEntity.getCredential(cred.getId()) != null) {
.addCredential(credentialEntity); throw new ModelDuplicateException("A CredentialModel with given id already exists");
}
return UserCredentialEntity.toModel(credentialEntity); Map<String, MapUserCredentialEntity> credentials = userEntity.getCredentials();
int priority = PRIORITY_DIFFERENCE;
if (credentials != null && !credentials.isEmpty()) {
priority = credentials.values().stream().max(MapUserCredentialEntity.ORDER_BY_PRIORITY).map(MapUserCredentialEntity::getPriority).orElse(0) + PRIORITY_DIFFERENCE;
}
credentialEntity.setPriority(priority);
userEntity.setCredential(credentialEntity.getId(), credentialEntity);
return MapUserCredentialEntity.toModel(credentialEntity);
} }
@Override @Override
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
LOG.tracef("removeStoredCredential(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace()); LOG.tracef("removeStoredCredential(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
return getEntityById(realm, user.getId())
.map(mapUserEntity -> mapUserEntity.removeCredential(id)) Optional<MapUserEntity> entityById = getEntityById(realm, user.getId());
.orElse(false); if (!entityById.isPresent()) return false;
Boolean result = entityById.get().removeCredential(id);
return result == null ? true : result; // TODO: make removeStoredCredential return Boolean so the caller can correctly handle "I don't know" null answer
} }
@Override @Override
@ -765,17 +807,21 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
LOG.tracef("getStoredCredentialById(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace()); LOG.tracef("getStoredCredentialById(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(mapUserEntity -> mapUserEntity.getCredential(id)) .map(mapUserEntity -> mapUserEntity.getCredential(id))
.map(UserCredentialEntity::toModel) .map(MapUserCredentialEntity::toModel)
.orElse(null); .orElse(null);
} }
@Override @Override
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace()); LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(MapUserEntity::getCredentials) .map(MapUserEntity::getCredentials)
.map(Map::values)
.map(Collection::stream)
.orElseGet(Stream::empty) .orElseGet(Stream::empty)
.map(UserCredentialEntity::toModel); .sorted(MapUserCredentialEntity.ORDER_BY_PRIORITY)
.map(MapUserCredentialEntity::toModel);
} }
@Override @Override
@ -795,38 +841,59 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override @Override
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) { 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 = getEntityByIdOrThrow(realm, user.getId());
MapUserEntity userEntity = getEntityById(realm, userId).orElse(null);
if (userEntity == null) { // 1 - Create new list and move everything to it.
LOG.warnf("User with id: [%s] not found", userId); Map<String, MapUserCredentialEntity> credentialEntityMap = userEntity.getCredentials();
List<MapUserCredentialEntity> newList = credentialEntityMap == null ? new LinkedList<>()
: credentialEntityMap.values().stream()
.sorted(MapUserCredentialEntity.ORDER_BY_PRIORITY)
.collect(Collectors.toList());
// 2 - Find indexes of our and newPrevious credential
int ourCredentialIndex = -1;
int newPreviousCredentialIndex = -1;
MapUserCredentialEntity ourCredential = null;
int i = 0;
for (MapUserCredentialEntity credential : newList) {
if (id.equals(credential.getId())) {
ourCredentialIndex = i;
ourCredential = credential;
} else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.getId())) {
newPreviousCredentialIndex = i;
}
i++;
}
if (ourCredentialIndex == -1) {
LOG.warnf("Not found credential with id [%s] of user [%s]", id, user.getUsername());
return false; return false;
} }
// Find index of credential which should be before id in the list if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) {
int newPreviousCredentialIdIndex = -1; // If newPreviousCredentialId == null we need to put id credential to index 0 LOG.warnf("Can't move up credential with id [%s] of user [%s]", id, user.getUsername());
if (newPreviousCredentialId != null) { return false;
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); // 3 - Compute index where we move our credential
return false; int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1;
// 4 - Insert our credential to new position, remove it from the old position
newList.add(toMoveIndex, ourCredential);
int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex;
newList.remove(indexToRemove);
// 5 - newList contains credentials in requested order now. Iterate through whole list and change priorities accordingly.
int expectedPriority = 0;
for (MapUserCredentialEntity credential : newList) {
expectedPriority += PRIORITY_DIFFERENCE;
if (credential.getPriority() != expectedPriority) {
credential.setPriority(expectedPriority);
LOG.tracef("Priority of credential [%s] of user [%s] changed to [%d]", credential.getId(), user.getUsername(), expectedPriority);
} }
} }
// 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; return true;
} }

View file

@ -1,124 +0,0 @@
/*
* 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.map.common.UpdatableEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class UserConsentEntity extends UpdatableEntity.Impl {
private String clientId;
private final Set<String> grantedClientScopesIds = new HashSet<>();
private Long createdDate;
private Long lastUpdatedDate;
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 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;
}
}

View file

@ -1,114 +0,0 @@
/*
* 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 org.keycloak.models.map.common.UpdatableEntity;
import java.util.Objects;
public class UserCredentialEntity extends UpdatableEntity.Impl {
private String id;
private String type;
private String userLabel;
private Long createdDate;
private String secretData;
private String credentialData;
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;
}
}

View file

@ -1,84 +0,0 @@
/*
* 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 org.keycloak.models.map.common.UpdatableEntity;
import java.util.Objects;
public class UserFederatedIdentityEntity extends UpdatableEntity.Impl {
private String token;
private String userId;
private String identityProvider;
private String userName;
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;
}
}

View file

@ -1,85 +0,0 @@
/*
* 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 MapUserEntity user;
@Before
public void init() {
user = new MapUserEntity("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);
}
}

View file

@ -31,6 +31,17 @@ import java.util.stream.Stream;
public interface UserCredentialStore extends Provider { public interface UserCredentialStore extends Provider {
void updateCredential(RealmModel realm, UserModel user, CredentialModel cred); void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred); CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
/**
* Removes credential with the {@code id} for the {@code user}.
*
* @param realm realm.
* @param user user
* @param id id
* @return {@code true} if the credential was removed, {@code false} otherwise
*
* TODO: Make this method return Boolean so that store can return "I don't know" answer, this can be used for example in async stores
*/
boolean removeStoredCredential(RealmModel realm, UserModel user, String id); boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id); CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);

View file

@ -202,6 +202,8 @@ public interface UserProvider extends Provider,
* @param userId id of the user * @param userId id of the user
* @param clientInternalId id of the client * @param clientInternalId id of the client
* @return {@code true} if the consent was removed, {@code false} otherwise * @return {@code true} if the consent was removed, {@code false} otherwise
*
* TODO: Make this method return Boolean so that store can return "I don't know" answer, this can be used for example in async stores
*/ */
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId); boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
@ -224,6 +226,8 @@ public interface UserProvider extends Provider,
* @param user the user model * @param user the user model
* @param socialProvider alias of the identity provider, see {@link IdentityProviderModel#getAlias()} * @param socialProvider alias of the identity provider, see {@link IdentityProviderModel#getAlias()}
* @return {@code true} if the association was removed, {@code false} otherwise * @return {@code true} if the association was removed, {@code false} otherwise
*
* TODO: Make this method return Boolean so that store can return "I don't know" answer, this can be used for example in async stores
*/ */
boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider); boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);