argos/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java

699 lines
38 KiB
Java
Raw Normal View History

2023-11-20 16:08:43 +01:00
package eu.eudat.service.user;
2023-11-20 16:09:24 +01:00
import com.fasterxml.jackson.core.JsonProcessingException;
2023-11-20 16:08:43 +01:00
import eu.eudat.authorization.AuthorizationFlags;
2024-04-16 10:02:17 +02:00
import eu.eudat.authorization.AuthorizationProperties;
2023-11-20 16:09:24 +01:00
import eu.eudat.authorization.OwnedResource;
2023-11-20 16:08:43 +01:00
import eu.eudat.authorization.Permission;
2023-11-20 16:09:24 +01:00
import eu.eudat.commons.JsonHandlingService;
import eu.eudat.commons.XmlHandlingService;
import eu.eudat.commons.enums.ActionConfirmationStatus;
import eu.eudat.commons.enums.ActionConfirmationType;
2023-11-20 16:09:24 +01:00
import eu.eudat.commons.enums.ContactInfoType;
2023-11-20 16:08:43 +01:00
import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.enums.notification.NotificationContactType;
2024-04-16 10:02:17 +02:00
import eu.eudat.commons.scope.tenant.TenantScope;
2023-11-29 16:41:15 +01:00
import eu.eudat.commons.scope.user.UserScope;
2024-01-15 12:57:33 +01:00
import eu.eudat.commons.types.actionconfirmation.MergeAccountConfirmationEntity;
2024-01-15 17:03:12 +01:00
import eu.eudat.commons.types.actionconfirmation.RemoveCredentialRequestEntity;
import eu.eudat.commons.types.notification.*;
2023-11-20 16:09:24 +01:00
import eu.eudat.commons.types.user.AdditionalInfoEntity;
2024-01-16 11:56:48 +01:00
import eu.eudat.commons.types.usercredential.UserCredentialDataEntity;
2024-04-01 10:16:19 +02:00
import eu.eudat.commons.notification.NotificationProperties;
2023-11-20 16:08:43 +01:00
import eu.eudat.convention.ConventionService;
import eu.eudat.data.*;
2023-11-20 16:08:43 +01:00
import eu.eudat.errorcode.ErrorThesaurusProperties;
2023-11-20 16:09:24 +01:00
import eu.eudat.event.UserTouchedEvent;
2023-11-20 16:08:43 +01:00
import eu.eudat.event.EventBroker;
2024-01-19 14:12:33 +01:00
import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent;
import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler;
2024-01-29 17:10:55 +01:00
import eu.eudat.integrationevent.outbox.userremoval.UserRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
2023-11-20 16:09:24 +01:00
import eu.eudat.model.User;
import eu.eudat.model.UserContactInfo;
2024-01-15 12:57:33 +01:00
import eu.eudat.model.UserCredential;
2023-11-20 16:09:24 +01:00
import eu.eudat.model.builder.UserBuilder;
2024-01-16 11:56:48 +01:00
import eu.eudat.model.deleter.*;
import eu.eudat.model.persist.*;
2024-01-15 12:57:33 +01:00
import eu.eudat.model.persist.actionconfirmation.MergeAccountConfirmationPersist;
2024-01-15 17:03:12 +01:00
import eu.eudat.model.persist.actionconfirmation.RemoveCredentialRequestPersist;
import eu.eudat.query.*;
import eu.eudat.service.actionconfirmation.ActionConfirmationService;
2024-01-15 12:57:33 +01:00
import eu.eudat.service.elastic.ElasticService;
import eu.eudat.service.keycloak.KeycloakService;
2023-11-20 16:08:43 +01:00
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.deleter.DeleterFactory;
2024-01-15 12:57:33 +01:00
import gr.cite.tools.data.query.Ordering;
2023-11-20 16:09:24 +01:00
import gr.cite.tools.data.query.QueryFactory;
2023-11-20 16:08:43 +01:00
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
2024-01-17 10:20:02 +01:00
import gr.cite.tools.validation.ValidatorFactory;
import jakarta.xml.bind.JAXBException;
2023-11-20 16:09:24 +01:00
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;
import org.jetbrains.annotations.NotNull;
2023-11-20 16:08:43 +01:00
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import javax.management.InvalidApplicationException;
2023-11-20 16:09:24 +01:00
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
2023-11-20 16:08:43 +01:00
import java.time.Instant;
2024-01-04 15:25:07 +01:00
import java.util.*;
2023-11-20 16:09:24 +01:00
import java.util.stream.Collectors;
2023-11-20 16:08:43 +01:00
@Service
2023-11-20 16:09:24 +01:00
public class UserServiceImpl implements UserService {
2023-11-20 16:08:43 +01:00
2023-11-20 16:09:24 +01:00
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserServiceImpl.class));
2023-11-20 16:08:43 +01:00
2024-04-01 17:36:03 +02:00
private final TenantEntityManager entityManager;
2023-11-20 16:08:43 +01:00
private final AuthorizationService authorizationService;
private final DeleterFactory deleterFactory;
private final BuilderFactory builderFactory;
private final ConventionService conventionService;
private final ErrorThesaurusProperties errors;
private final MessageSource messageSource;
private final EventBroker eventBroker;
2023-11-20 16:09:24 +01:00
private final JsonHandlingService jsonHandlingService;
private final XmlHandlingService xmlHandlingService;
2023-11-20 16:09:24 +01:00
private final QueryFactory queryFactory;
2023-11-29 16:41:15 +01:00
private final UserScope userScope;
private final KeycloakService keycloakService;
private final ActionConfirmationService actionConfirmationService;
private final NotificationProperties notificationProperties;
2024-01-19 14:12:33 +01:00
private final NotifyIntegrationEventHandler eventHandler;
private final ValidatorFactory validatorFactory;
2024-01-15 12:57:33 +01:00
private final ElasticService elasticService;
2024-01-29 17:10:55 +01:00
private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler;
private final UserRemovalIntegrationEventHandler userRemovalIntegrationEventHandler;
2024-04-16 10:02:17 +02:00
private final AuthorizationProperties authorizationProperties;
private final TenantScope tenantScope;
2024-01-29 17:10:55 +01:00
2023-11-20 16:08:43 +01:00
@Autowired
2023-11-20 16:09:24 +01:00
public UserServiceImpl(
2024-04-16 10:02:17 +02:00
TenantEntityManager entityManager,
2024-01-29 17:10:55 +01:00
AuthorizationService authorizationService,
DeleterFactory deleterFactory,
BuilderFactory builderFactory,
ConventionService conventionService,
ErrorThesaurusProperties errors,
MessageSource messageSource,
EventBroker eventBroker,
JsonHandlingService jsonHandlingService,
XmlHandlingService xmlHandlingService, QueryFactory queryFactory,
2024-04-16 10:02:17 +02:00
UserScope userScope, KeycloakService keycloakService, ActionConfirmationService actionConfirmationService, NotificationProperties notificationProperties, NotifyIntegrationEventHandler eventHandler, ValidatorFactory validatorFactory, ElasticService elasticService, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, UserRemovalIntegrationEventHandler userRemovalIntegrationEventHandler, AuthorizationProperties authorizationProperties, TenantScope tenantScope) {
2023-11-20 16:08:43 +01:00
this.entityManager = entityManager;
this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory;
this.builderFactory = builderFactory;
this.conventionService = conventionService;
this.errors = errors;
this.messageSource = messageSource;
this.eventBroker = eventBroker;
2023-11-20 16:09:24 +01:00
this.jsonHandlingService = jsonHandlingService;
this.xmlHandlingService = xmlHandlingService;
2023-11-20 16:09:24 +01:00
this.queryFactory = queryFactory;
2023-11-29 16:41:15 +01:00
this.userScope = userScope;
this.keycloakService = keycloakService;
this.actionConfirmationService = actionConfirmationService;
this.notificationProperties = notificationProperties;
this.eventHandler = eventHandler;
this.validatorFactory = validatorFactory;
2024-01-15 12:57:33 +01:00
this.elasticService = elasticService;
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler;
this.userRemovalIntegrationEventHandler = userRemovalIntegrationEventHandler;
2024-04-16 10:02:17 +02:00
this.authorizationProperties = authorizationProperties;
this.tenantScope = tenantScope;
2023-11-20 16:08:43 +01:00
}
2023-11-20 16:09:24 +01:00
//region persist
@Override
public User persist(UserPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
logger.debug(new MapLogEntry("persisting data User").And("model", model).And("fields", fields));
2023-11-20 16:08:43 +01:00
2023-11-20 16:09:24 +01:00
this.authorizationService.authorizeAtLeastOneForce(model.getId() != null ? List.of(new OwnedResource(model.getId())) : null, Permission.EditUser);
2023-11-20 16:08:43 +01:00
Boolean isUpdate = this.conventionService.isValidGuid(model.getId());
2023-11-20 16:09:24 +01:00
UserEntity data;
2023-11-20 16:08:43 +01:00
if (isUpdate) {
2023-11-20 16:09:24 +01:00
data = this.entityManager.find(UserEntity.class, model.getId());
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2023-11-20 16:08:43 +01:00
if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage());
} else {
2023-11-20 16:09:24 +01:00
data = new UserEntity();
2023-11-20 16:08:43 +01:00
data.setId(UUID.randomUUID());
data.setIsActive(IsActive.Active);
data.setCreatedAt(Instant.now());
}
2023-11-20 16:09:24 +01:00
data.setAdditionalInfo(this.jsonHandlingService.toJson(this.buildAdditionalInfoEntity(model.getAdditionalInfo())));
2023-11-20 16:08:43 +01:00
data.setName(model.getName());
data.setUpdatedAt(Instant.now());
2023-11-20 16:09:24 +01:00
if (isUpdate) this.entityManager.merge(data);
else this.entityManager.persist(data);
2023-11-20 16:08:43 +01:00
this.entityManager.flush();
2023-11-20 16:09:24 +01:00
this.eventBroker.emit(new UserTouchedEvent(data.getId()));
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(data.getId());
2024-03-12 17:27:16 +01:00
return this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, User._id), data);
2023-11-20 16:08:43 +01:00
}
2023-11-20 16:09:24 +01:00
private @NotNull AdditionalInfoEntity buildAdditionalInfoEntity(UserAdditionalInfoPersist persist){
AdditionalInfoEntity data = new AdditionalInfoEntity();
if (persist == null) return data;
data.setOrganizationId(persist.getOrganizationId());
data.setRoleOrganization(persist.getRoleOrganization());
data.setCulture(persist.getCulture());
data.setTimezone(persist.getTimezone());
data.setLanguage(persist.getLanguage());
data.setAvatarUrl(persist.getAvatarUrl());
return data;
}
//endregion
//region delete
@Override
2023-11-20 16:08:43 +01:00
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
2023-11-20 16:09:24 +01:00
logger.debug("deleting User: {}", id);
2023-11-20 16:08:43 +01:00
2023-11-20 16:09:24 +01:00
this.authorizationService.authorizeForce(Permission.DeleteUser);
2023-11-20 16:08:43 +01:00
2023-11-20 16:09:24 +01:00
this.deleterFactory.deleter(UserDeleter.class).deleteAndSaveByIds(List.of(id));
2024-01-29 17:10:55 +01:00
this.userRemovalIntegrationEventHandler.handle(id);
2023-11-20 16:08:43 +01:00
}
2023-11-20 16:09:24 +01:00
//endregion
//region export
@Override
public byte[] exportCsv() throws IOException {
this.authorizationService.authorizeForce(Permission.ExportUsers);
FieldSet fieldSet = new BaseFieldSet().ensure(User._id).ensure(User._name).ensure(User._contacts + "." + UserContactInfo._value).ensure(User._contacts + "." + UserContactInfo._type);
List<User> users = this.builderFactory.builder(UserBuilder.class).build(fieldSet, this.queryFactory.query(UserQuery.class).collectAs(fieldSet));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final CSVFormat format = CSVFormat.DEFAULT.withHeader("User Id", "User Name", "User Email").withQuoteMode(QuoteMode.NON_NUMERIC);
final CSVPrinter csvPrinter = new CSVPrinter(new PrintWriter(out), format);
for (User user : users) {
csvPrinter.printRecord(user.getId(), user.getName(), (user.getContacts() != null ? String.join(" ", user.getContacts().stream().filter(x-> ContactInfoType.Email.equals(x.getType())).map(UserContactInfo::getValue).toList()) : ""));
}
csvPrinter.flush();
return out.toByteArray();
}
//endregion
@Override
public User patchRoles(UserRolePatchPersist model, FieldSet fields) throws InvalidApplicationException {
logger.debug(new MapLogEntry("persisting data UserRole").And("model", model).And("fields", fields));
this.authorizationService.authorizeForce(Permission.EditUser);
UserEntity data = this.entityManager.find(UserEntity.class, model.getId());
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage());
List<UserCredentialEntity> userCredentials = this.queryFactory.query(UserCredentialQuery.class).userIds(data.getId()).collect();
if (userCredentials.isEmpty())
throw new MyApplicationException("Currently cannot update roles for this user");
if (userCredentials.getFirst().getExternalId() == null)
throw new MyApplicationException("Currently cannot update roles for this user");
2024-04-16 12:17:58 +02:00
String subjectId = userCredentials.getFirst().getExternalId();
2024-04-16 10:02:17 +02:00
this.applyGlobalRoles(data.getId(), subjectId, model);
this.applyTenantRoles(data.getId(), subjectId, model);
this.entityManager.flush();
this.eventBroker.emit(new UserTouchedEvent(data.getId()));
2024-04-16 10:02:17 +02:00
this.userTouchedIntegrationEventHandler.handle(data.getId());
return this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, User._id), data);
}
2024-04-16 12:17:58 +02:00
private void applyGlobalRoles(UUID userId, String subjectId, UserRolePatchPersist model) throws InvalidApplicationException {
try {
this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
List<UserRoleEntity> existingItems = this.queryFactory.query(UserRoleQuery.class).userIds(userId).tenantIsSet(false).roles(this.authorizationProperties.getAllowedGlobalRoles()).collect();
List<UUID> foundIds = new ArrayList<>();
for (String roleName : model.getRoles().stream().filter(x -> x != null && !x.isBlank() && this.authorizationProperties.getAllowedGlobalRoles().contains(x)).distinct().toList()) {
UserRoleEntity item = existingItems.stream().filter(x -> x.getRole().equals(roleName)).findFirst().orElse(null);
if (item == null) {
item = new UserRoleEntity();
item.setId(UUID.randomUUID());
item.setUserId(userId);
item.setRole(roleName);
item.setCreatedAt(Instant.now());
this.entityManager.persist(item);
this.keycloakService.addUserToGlobalRoleGroup(subjectId, roleName);
}
foundIds.add(item.getId());
2023-11-20 16:09:24 +01:00
}
2023-11-20 16:08:43 +01:00
2024-04-16 12:17:58 +02:00
this.entityManager.flush();
2024-04-16 10:02:17 +02:00
2024-04-16 12:17:58 +02:00
List<UserRoleEntity> toDelete = existingItems.stream().filter(x -> foundIds.stream().noneMatch(y -> y.equals(x.getId()))).collect(Collectors.toList());
toDelete.forEach(x -> this.keycloakService.removeUserGlobalRoleGroup(subjectId, x.getRole()));
this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete);
2023-11-20 16:08:43 +01:00
2024-04-16 12:17:58 +02:00
this.entityManager.flush();
} finally {
this.tenantScope.removeTempTenant(this.entityManager.getEntityManager());
}
2024-04-16 10:02:17 +02:00
}
2024-04-16 12:17:58 +02:00
private void applyTenantRoles(UUID userId, String subjectId, UserRolePatchPersist model) throws InvalidApplicationException {
2024-04-16 10:02:17 +02:00
if (!tenantScope.isSet()) throw new MyForbiddenException("tenant scope required");
UserRoleQuery userRoleQuery = this.queryFactory.query(UserRoleQuery.class).userIds(userId).roles(this.authorizationProperties.getAllowedTenantRoles());
if (tenantScope.isDefaultTenant()) userRoleQuery.tenantIsSet(false);
else userRoleQuery.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
2023-11-20 16:09:24 +01:00
2024-04-16 10:02:17 +02:00
List<UserRoleEntity> existingItems = userRoleQuery.collect();
List<UUID> foundIds = new ArrayList<>();
for (String roleName : model.getRoles().stream().filter(x-> x != null && !x.isBlank() && this.authorizationProperties.getAllowedTenantRoles().contains(x)).distinct().toList()) {
UserRoleEntity item = existingItems.stream().filter(x-> x.getRole().equals(roleName)).findFirst().orElse(null);
if (item == null) {
item = new UserRoleEntity();
item.setId(UUID.randomUUID());
item.setUserId(userId);
item.setRole(roleName);
item.setCreatedAt(Instant.now());
item.setTenantId(this.tenantScope.getTenant());
this.entityManager.persist(item);
2024-04-16 12:17:58 +02:00
this.keycloakService.addUserToTenantRoleGroup(subjectId, this.tenantScope.getTenantCode(), roleName);
2024-04-16 10:02:17 +02:00
}
foundIds.add(item.getId());
}
2024-01-29 17:10:55 +01:00
2024-04-16 10:02:17 +02:00
this.entityManager.flush();
List<UserRoleEntity> toDelete = existingItems.stream().filter(x-> foundIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList());
2024-04-16 12:17:58 +02:00
toDelete.forEach(x -> {
try {
this.keycloakService.removeUserTenantRoleGroup(subjectId, this.tenantScope.getTenantCode(), x.getRole());
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
});
2024-04-16 10:02:17 +02:00
this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete);
this.entityManager.flush();
2023-11-20 16:09:24 +01:00
}
2023-11-29 16:41:15 +01:00
//region mine
@Override
2024-04-01 17:36:03 +02:00
public void updateLanguageMine(String language) throws JsonProcessingException, InvalidApplicationException {
2023-11-29 16:41:15 +01:00
logger.debug(new MapLogEntry("persisting User language").And("language", language));
UUID userId = this.userScope.getUserIdSafe();
if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage());
UserEntity data = this.entityManager.find(UserEntity.class, userId);
2023-11-29 16:49:00 +01:00
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userId, User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2023-11-29 16:41:15 +01:00
AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo());
if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity();
additionalInfoEntity.setLanguage(language);
data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity));
data.setUpdatedAt(Instant.now());
this.entityManager.merge(data);
this.entityManager.flush();
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(data.getId());
2023-11-29 16:41:15 +01:00
this.eventBroker.emit(new UserTouchedEvent(data.getId()));
}
@Override
2024-04-01 17:36:03 +02:00
public void updateTimezoneMine(String timezone) throws JsonProcessingException, InvalidApplicationException {
2023-11-29 16:41:15 +01:00
logger.debug(new MapLogEntry("persisting User timezone").And("timezone", timezone));
UUID userId = this.userScope.getUserIdSafe();
if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage());
UserEntity data = this.entityManager.find(UserEntity.class, userId);
2023-11-29 16:49:00 +01:00
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userId, User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2023-11-29 16:41:15 +01:00
AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo());
if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity();
additionalInfoEntity.setTimezone(timezone);
data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity));
data.setUpdatedAt(Instant.now());
this.entityManager.merge(data);
this.entityManager.flush();
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(data.getId());
2023-11-29 16:41:15 +01:00
this.eventBroker.emit(new UserTouchedEvent(data.getId()));
}
@Override
2024-04-01 17:36:03 +02:00
public void updateCultureMine(String culture) throws JsonProcessingException, InvalidApplicationException {
2023-11-29 16:41:15 +01:00
logger.debug(new MapLogEntry("persisting User culture").And("culture", culture));
UUID userId = this.userScope.getUserIdSafe();
if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage());
UserEntity data = this.entityManager.find(UserEntity.class, userId);
2023-11-29 16:49:00 +01:00
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userId, User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2023-11-29 16:41:15 +01:00
AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo());
if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity();
additionalInfoEntity.setCulture(culture);
data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity));
data.setUpdatedAt(Instant.now());
this.entityManager.merge(data);
this.entityManager.flush();
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(data.getId());
2023-11-29 16:41:15 +01:00
this.eventBroker.emit(new UserTouchedEvent(data.getId()));
}
//endregion
//notifications
2024-01-15 12:57:33 +01:00
public void sendMergeAccountConfirmation(UserMergeRequestPersist model) throws InvalidApplicationException, JAXBException {
UserContactInfoEntity userContactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).values(model.getEmail()).types(ContactInfoType.Email).first();
if (userContactInfoEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getEmail(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
UserEntity user = this.queryFactory.query(UserQuery.class).ids(userContactInfoEntity.getUserId()).isActive(IsActive.Active).first();
if (user == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userContactInfoEntity.getUserId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
String token = this.createMergeAccountConfirmation(model.getEmail());
createMergeNotificationEvent(token, user, model.getEmail(), NotificationContactType.EMAIL);
}
private void createMergeNotificationEvent(String token, UserEntity user, String email, NotificationContactType type) throws InvalidApplicationException {
2024-01-19 14:12:33 +01:00
NotifyIntegrationEvent event = new NotifyIntegrationEvent();
event.setUserId(user.getId());
List<ContactPair> contactPairs = new ArrayList<>();
contactPairs.add(new ContactPair(ContactInfoType.Email, email));
NotificationContactData contactData = new NotificationContactData(contactPairs, null, null);
event.setContactHint(jsonHandlingService.toJsonSafe(contactData));
event.setContactTypeHint(type);
2024-01-04 15:25:07 +01:00
event.setNotificationType(notificationProperties.getMergeAccountConfirmationType());
NotificationFieldData data = new NotificationFieldData();
List<FieldInfo> fieldInfoList = new ArrayList<>();
fieldInfoList.add(new FieldInfo("{userName}", DataType.String, user.getName()));
fieldInfoList.add(new FieldInfo("{confirmationToken}", DataType.String, token));
2024-01-04 15:25:07 +01:00
fieldInfoList.add(new FieldInfo("{expiration_time}", DataType.String, this.secondsToTime(this.notificationProperties.getEmailExpirationTimeSeconds())));
data.setFields(fieldInfoList);
event.setData(jsonHandlingService.toJsonSafe(data));
eventHandler.handle(event);
}
2024-01-15 12:57:33 +01:00
public void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException {
UserCredentialEntity data = this.entityManager.find(UserCredentialEntity.class, model.getCredentialId());
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getCredentialId(), UserCredentialEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!data.getUserId().equals(this.userScope.getUserId())) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage());
2024-01-15 12:57:33 +01:00
String token = this.createRemoveConfirmation(data.getId());
this.createRemoveCredentialNotificationEvent(token, data.getUserId(), NotificationContactType.EMAIL);
}
2024-01-15 12:57:33 +01:00
private void createRemoveCredentialNotificationEvent(String token, UUID userId, NotificationContactType type) throws InvalidApplicationException {
2024-01-19 14:12:33 +01:00
NotifyIntegrationEvent event = new NotifyIntegrationEvent();
event.setUserId(userId);
event.setContactTypeHint(type);
2024-01-04 15:25:07 +01:00
event.setNotificationType(notificationProperties.getRemoveCredentialConfirmationType());
NotificationFieldData data = new NotificationFieldData();
List<FieldInfo> fieldInfoList = new ArrayList<>();
fieldInfoList.add(new FieldInfo("{confirmationToken}", DataType.String, token));
2024-01-04 15:25:07 +01:00
fieldInfoList.add(new FieldInfo("{expiration_time}", DataType.String, this.secondsToTime(this.notificationProperties.getEmailExpirationTimeSeconds())));
data.setFields(fieldInfoList);
event.setData(jsonHandlingService.toJsonSafe(data));
eventHandler.handle(event);
}
2024-01-15 12:57:33 +01:00
private String createMergeAccountConfirmation(String email) throws JAXBException, InvalidApplicationException {
ActionConfirmationPersist persist = new ActionConfirmationPersist();
persist.setType(ActionConfirmationType.MergeAccount);
persist.setStatus(ActionConfirmationStatus.Requested);
persist.setToken(UUID.randomUUID().toString());
persist.setMergeAccountConfirmation(new MergeAccountConfirmationPersist());
persist.getMergeAccountConfirmation().setEmail(email);
persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds()));
validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist);
this.actionConfirmationService.persist(persist, null);
return persist.getToken();
}
private String createRemoveConfirmation(UUID credentialId) throws JAXBException, InvalidApplicationException {
ActionConfirmationPersist persist = new ActionConfirmationPersist();
2024-01-15 12:57:33 +01:00
persist.setType(ActionConfirmationType.RemoveCredential);
persist.setStatus(ActionConfirmationStatus.Requested);
persist.setToken(UUID.randomUUID().toString());
2024-01-15 12:57:33 +01:00
persist.setRemoveCredentialRequest(new RemoveCredentialRequestPersist());
persist.getRemoveCredentialRequest().setCredentialId(credentialId);
2024-01-04 15:25:07 +01:00
persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds()));
validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist);
this.actionConfirmationService.persist(persist, null);
return persist.getToken();
}
private String secondsToTime(int seconds) {
int sec = seconds % 60;
int hour = seconds / 60;
int min = hour % 60;
hour = hour / 60;
return (hour + ":" + min + ":" + sec);
}
2024-01-15 12:57:33 +01:00
public void confirmMergeAccount(String token) throws IOException, InvalidApplicationException {
ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.MergeAccount).isActive(IsActive.Active).first();
2024-01-04 15:25:07 +01:00
if (action == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.checkActionState(action);
2024-01-15 12:57:33 +01:00
MergeAccountConfirmationEntity mergeAccountConfirmationEntity = this.xmlHandlingService.fromXmlSafe(MergeAccountConfirmationEntity.class, action.getData());
if (mergeAccountConfirmationEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{mergeAccountConfirmationEntity, MergeAccountConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
UserContactInfoEntity userContactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).values(mergeAccountConfirmationEntity.getEmail()).types(ContactInfoType.Email).first();
if (userContactInfoEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{mergeAccountConfirmationEntity.getEmail(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
UserEntity userToBeMerge = this.queryFactory.query(UserQuery.class).ids(userContactInfoEntity.getUserId()).isActive(IsActive.Active).first();
if (userToBeMerge == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{userContactInfoEntity.getUserId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!this.userScope.getUserIdSafe().equals(userToBeMerge.getId())) throw new MyForbiddenException("Only requested user can approve");
2024-01-15 12:57:33 +01:00
UserEntity newUser = this.queryFactory.query(UserQuery.class).ids(action.getCreatedById()).isActive(IsActive.Active).first();
if (newUser == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{action.getCreatedById(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!newUser.getId().equals(userToBeMerge.getId())){
this.mergeNewUserToOld(newUser, userToBeMerge);
}
action.setUpdatedAt(Instant.now());
action.setStatus(ActionConfirmationStatus.Accepted);
2024-01-15 12:57:33 +01:00
this.entityManager.merge(action);
this.entityManager.flush();
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(newUser.getId());
this.userRemovalIntegrationEventHandler.handle(userToBeMerge.getId());
2024-01-15 12:57:33 +01:00
}
private void mergeNewUserToOld(UserEntity newUser, UserEntity oldUser) throws IOException, InvalidApplicationException {
List<UserCredentialEntity> userCredentials = this.queryFactory.query(UserCredentialQuery.class).userIds(oldUser.getId()).collect();
for (UserCredentialEntity userCredential : userCredentials){
userCredential.setUserId(newUser.getId());
this.entityManager.merge(userCredential);
}
List<UserContactInfoEntity> userContactInfos = this.queryFactory.query(UserContactInfoQuery.class).userIds(oldUser.getId()).collect();
UserContactInfoQuery newUserContactInfoQuery = this.queryFactory.query(UserContactInfoQuery.class).userIds(newUser.getId());
newUserContactInfoQuery.setOrder(new Ordering().addDescending(UserContactInfo._ordinal));
UserContactInfoEntity newUserContactInfo = newUserContactInfoQuery.first();
int ordinal = newUserContactInfo == null ? 0 : newUserContactInfo.getOrdinal() + 1;
for (UserContactInfoEntity userContactInfo : userContactInfos){
userContactInfo.setUserId(newUser.getId());
userContactInfo.setOrdinal(ordinal);
this.entityManager.merge(userContactInfo);
ordinal++;
}
List<UserRoleEntity> userRoles = this.queryFactory.query(UserRoleQuery.class).userIds(oldUser.getId()).collect();
List<UserRoleEntity> newUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(newUser.getId()).collect();
List<UserRoleEntity> rolesToDelete = new ArrayList<>();
for (UserRoleEntity userRole : userRoles){
if (newUserRoles.stream().anyMatch(x-> x.getRole().equals(userRole.getRole()))) {
rolesToDelete.add(userRole);
} else {
userRole.setUserId(newUser.getId());
this.entityManager.merge(userRole);
}
}
this.deleterFactory.deleter(UserRoleDeleter.class).delete(rolesToDelete);
List<UserSettingsEntity> userSettings = this.queryFactory.query(UserSettingsQuery.class).entityIds(oldUser.getId()).collect();
List<UserSettingsEntity> newUserSettings = this.queryFactory.query(UserSettingsQuery.class).entityIds(newUser.getId()).collect();
List<UserSettingsEntity> userSettingsToDelete = new ArrayList<>();
for (UserSettingsEntity userSetting : userSettings){
if (newUserSettings.stream().anyMatch(x-> x.getKey().equals(userSetting.getKey()))) {
userSettingsToDelete.add(userSetting);
} else {
userSetting.setEntityId(newUser.getId());
this.entityManager.merge(userSetting);
}
}
this.deleterFactory.deleter(UserSettingsSettingsDeleter.class).delete(userSettingsToDelete);
List<TagEntity> tags = this.queryFactory.query(TagQuery.class).createdByIds(oldUser.getId()).collect();
for (TagEntity tag : tags){
tag.setCreatedById(newUser.getId());
this.entityManager.merge(tag);
}
List<StorageFileEntity> storageFiles = this.queryFactory.query(StorageFileQuery.class).ownerIds(oldUser.getId()).collect();
for (StorageFileEntity storageFile : storageFiles){
storageFile.setOwnerId(newUser.getId());
this.entityManager.merge(storageFile);
}
List<LockEntity> locks = this.queryFactory.query(LockQuery.class).lockedByIds(oldUser.getId()).collect();
for (LockEntity lock : locks){
lock.setLockedBy(newUser.getId());
this.entityManager.merge(lock);
}
List<DmpUserEntity> dmpUsers = this.queryFactory.query(DmpUserQuery.class).userIds(oldUser.getId()).collect();
for (DmpUserEntity dmpUser : dmpUsers){
dmpUser.setUserId(newUser.getId());
this.entityManager.merge(dmpUser);
}
List<UserDescriptionTemplateEntity> userDescriptionTemplates = this.queryFactory.query(UserDescriptionTemplateQuery.class).userIds(oldUser.getId()).collect();
for (UserDescriptionTemplateEntity userDescriptionTemplate : userDescriptionTemplates){
userDescriptionTemplate.setUserId(newUser.getId());
this.entityManager.merge(userDescriptionTemplate);
}
List<DmpEntity> dmps = this.queryFactory.query(DmpQuery.class).creatorIds(oldUser.getId()).collect();
for (DmpEntity dmp : dmps){
dmp.setCreatorId(newUser.getId());
this.entityManager.merge(dmp);
}
List<DescriptionEntity> descriptions = this.queryFactory.query(DescriptionQuery.class).createdByIds(oldUser.getId()).collect();
for (DescriptionEntity description : descriptions){
description.setCreatedById(newUser.getId());
this.entityManager.merge(description);
}
2024-01-04 15:25:07 +01:00
2024-01-15 12:57:33 +01:00
oldUser.setIsActive(IsActive.Inactive);
this.entityManager.merge(oldUser);
2024-01-04 15:25:07 +01:00
2024-01-15 12:57:33 +01:00
this.entityManager.flush();
for (DmpEntity dmp : dmps){
this.elasticService.persistDmp(dmp);
}
for (DescriptionEntity description : descriptions){
this.elasticService.persistDescription(description);
}
}
2024-01-04 15:25:07 +01:00
public void confirmRemoveCredential(String token) throws InvalidApplicationException {
ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.RemoveCredential).isActive(IsActive.Active).first();
2024-01-04 15:25:07 +01:00
if (action == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.checkActionState(action);
2024-01-15 17:03:12 +01:00
RemoveCredentialRequestEntity removeCredentialRequestEntity = this.xmlHandlingService.fromXmlSafe(RemoveCredentialRequestEntity.class, action.getData());
if (removeCredentialRequestEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{removeCredentialRequestEntity, RemoveCredentialRequestEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2024-01-15 12:57:33 +01:00
2024-01-15 17:03:12 +01:00
UserCredentialEntity userCredentialEntity = this.queryFactory.query(UserCredentialQuery.class).ids(removeCredentialRequestEntity.getCredentialId()).first();
if (userCredentialEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{removeCredentialRequestEntity.getCredentialId(), UserCredential.class.getSimpleName()}, LocaleContextHolder.getLocale()));
2024-01-16 11:56:48 +01:00
2024-01-15 12:57:33 +01:00
if (!this.userScope.getUserIdSafe().equals(userCredentialEntity.getId())) throw new MyForbiddenException("Only requested user can approve");
2024-01-16 11:56:48 +01:00
if (userCredentialEntity.getData() != null){
UserCredentialDataEntity userCredentialDataEntity = this.jsonHandlingService.fromJsonSafe(UserCredentialDataEntity.class, userCredentialEntity.getData());
if (userCredentialDataEntity != null && !this.conventionService.isNullOrEmpty(userCredentialDataEntity.getEmail())) {
List<UserContactInfoEntity> userContactInfos = this.queryFactory.query(UserContactInfoQuery.class).values(userCredentialDataEntity.getEmail()).userIds(userCredentialEntity.getUserId()).collect();
if (!this.conventionService.isListNullOrEmpty(userContactInfos))
this.deleterFactory.deleter(UserContactInfoDeleter.class).delete(userContactInfos);
}
}
2024-01-15 12:57:33 +01:00
this.deleterFactory.deleter(UserCredentialDeleter.class).delete(List.of(userCredentialEntity));
action.setUpdatedAt(Instant.now());
action.setStatus(ActionConfirmationStatus.Accepted);
this.entityManager.merge(action);
2024-01-15 12:57:33 +01:00
this.entityManager.flush();
2024-01-29 17:10:55 +01:00
this.userTouchedIntegrationEventHandler.handle(userCredentialEntity.getUserId());
}
private void checkActionState(ActionConfirmationEntity action) throws MyApplicationException {
if (action.getStatus().equals(ActionConfirmationStatus.Accepted)){
throw new MyApplicationException("Account is already confirmed!");
}
if (action.getExpiresAt().compareTo(Instant.now()) < 0){
throw new MyApplicationException("Token has expired!");
}
}
2023-11-20 16:09:24 +01:00
}