argos/dmp-backend/core/src/main/java/eu/eudat/service/tenant/TenantServiceImpl.java

294 lines
15 KiB
Java

package eu.eudat.service.tenant;
import eu.eudat.authorization.AuthorizationFlags;
import eu.eudat.authorization.AuthorizationProperties;
import eu.eudat.authorization.Permission;
import eu.eudat.commons.XmlHandlingService;
import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.commons.types.tenant.*;
import eu.eudat.convention.ConventionService;
import eu.eudat.data.TenantEntity;
import eu.eudat.data.TenantEntityManager;
import eu.eudat.data.UserCredentialEntity;
import eu.eudat.data.UserRoleEntity;
import eu.eudat.errorcode.ErrorThesaurusProperties;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler;
import eu.eudat.model.Tenant;
import eu.eudat.model.UserCredential;
import eu.eudat.model.builder.TenantBuilder;
import eu.eudat.model.deleter.TenantDeleter;
import eu.eudat.model.deleter.UserRoleDeleter;
import eu.eudat.model.persist.TenantPersist;
import eu.eudat.model.persist.tenantconfig.*;
import eu.eudat.model.tenantconfig.TenantSource;
import eu.eudat.query.UserCredentialQuery;
import eu.eudat.query.UserRoleQuery;
import eu.eudat.service.encryption.EncryptionService;
import eu.eudat.service.keycloak.KeycloakService;
import eu.eudat.service.responseutils.ResponseUtilsService;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.QueryFactory;
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;
import org.jetbrains.annotations.NotNull;
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.crypto.*;
import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class TenantServiceImpl implements TenantService {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantServiceImpl.class));
private final TenantEntityManager entityManager;
private final AuthorizationService authorizationService;
private final DeleterFactory deleterFactory;
private final BuilderFactory builderFactory;
private final ConventionService conventionService;
private final MessageSource messageSource;
private final XmlHandlingService xmlHandlingService;
private final ErrorThesaurusProperties errors;
private final EncryptionService encryptionService;
private final TenantProperties properties;
private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler;
private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler;
private final KeycloakService keycloakService;
private final AuthorizationProperties authorizationProperties;
private final TenantScope tenantScope;
private final QueryFactory queryFactory;
@Autowired
public TenantServiceImpl(
TenantEntityManager entityManager,
AuthorizationService authorizationService,
DeleterFactory deleterFactory,
BuilderFactory builderFactory,
ConventionService conventionService,
MessageSource messageSource,
XmlHandlingService xmlHandlingService,
ErrorThesaurusProperties errors,
EncryptionService encryptionService, TenantProperties properties, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler, KeycloakService keycloakService, AuthorizationProperties authorizationProperties, TenantScope tenantScope, QueryFactory queryFactory) {
this.entityManager = entityManager;
this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory;
this.builderFactory = builderFactory;
this.conventionService = conventionService;
this.messageSource = messageSource;
this.xmlHandlingService = xmlHandlingService;
this.errors = errors;
this.encryptionService = encryptionService;
this.properties = properties;
this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler;
this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler;
this.keycloakService = keycloakService;
this.authorizationProperties = authorizationProperties;
this.tenantScope = tenantScope;
this.queryFactory = queryFactory;
}
@Override
public Tenant persist(TenantPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("persisting data").And("model", model).And("fields", fields));
this.authorizationService.authorizeForce(Permission.EditTenant);
Boolean isUpdate = this.conventionService.isValidGuid(model.getId());
TenantEntity data;
if (isUpdate) {
data = this.entityManager.find(TenantEntity.class, model.getId());
if (data == null)
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Tenant.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash()))
throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage());
} else {
data = new TenantEntity();
data.setId(UUID.randomUUID());
data.setIsActive(IsActive.Active);
data.setCreatedAt(Instant.now());
}
data.setCode(model.getCode());
data.setName(model.getName());
data.setDescription(model.getDescription());
data.setUpdatedAt(Instant.now());
data.setConfig(this.xmlHandlingService.toXmlSafe(this.buildConfigEntity(model.getConfig())));
if (isUpdate) this.entityManager.merge(data);
else this.entityManager.persist(data);
this.entityManager.flush();
if (!isUpdate) {
this.keycloakService.createTenantGroups(data.getCode());
this.autoAssignGlobalAdminsToNewTenant(data);
}
TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent();
tenantTouchedIntegrationEvent.setId(data.getId());
tenantTouchedIntegrationEvent.setCode(data.getCode());
this.tenantTouchedIntegrationEventHandler.handle(tenantTouchedIntegrationEvent);
return this.builderFactory.builder(TenantBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Tenant._id), data);
}
private void autoAssignGlobalAdminsToNewTenant(TenantEntity tenant){
if (!this.authorizationProperties.getAutoAssignGlobalAdminToNewTenants()) return;
List<UserRoleEntity> existingItems;
List<UserCredentialEntity> userCredentialEntities;
try {
this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
existingItems = this.queryFactory.query(UserRoleQuery.class).tenantIsSet(false).roles(this.authorizationProperties.getGlobalAdminRole()).collect();
userCredentialEntities = this.queryFactory.query(UserCredentialQuery.class).userIds(existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()).collect();
} finally {
this.tenantScope.removeTempTenant(this.entityManager.getEntityManager());
}
try {
this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), tenant.getId(), tenant.getCode());
for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) {
UserCredentialEntity userCredential = userCredentialEntities.stream().filter(x-> !this.conventionService.isNullOrEmpty(x.getExternalId()) && x.getUserId().equals(userId)).findFirst().orElse(null);
if (userCredential == null) continue;
UserRoleEntity item = new UserRoleEntity();
item.setId(UUID.randomUUID());
item.setUserId(userId);
item.setRole(this.authorizationProperties.getGlobalAdminRole());
item.setCreatedAt(Instant.now());
this.entityManager.persist(item);
this.keycloakService.addUserToGlobalRoleGroup(userCredential.getExternalId(), this.authorizationProperties.getGlobalAdminRole());
}
} finally {
this.tenantScope.removeTempTenant(this.entityManager.getEntityManager());
}
this.entityManager.flush();
}
private @NotNull TenantConfigEntity buildConfigEntity(TenantConfigPersist persist) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
TenantConfigEntity data = new TenantConfigEntity();
if (persist == null) return data;
if (persist.getDeposit() != null) {
data.setDeposit(this.buildDepositConfigEntity(persist.getDeposit()));
}
if (persist.getFileTransformers() != null) {
data.setFileTransformers(this.buildFileConfigEntity(persist.getFileTransformers()));
}
return data;
}
private @NotNull TenantDepositConfigEntity buildDepositConfigEntity(TenantDepositConfigPersist persist) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
TenantDepositConfigEntity data = new TenantDepositConfigEntity();
if (persist == null) return data;
if (!this.conventionService.isListNullOrEmpty(persist.getSources())) {
data.setSources(new ArrayList<>());
for (TenantSourcePersist sourcePersist : persist.getSources()) {
data.getSources().add(this.buildSourceEntity(sourcePersist));
}
}
return data;
}
private @NotNull TenantFileTransformersConfigEntity buildFileConfigEntity(TenantFileTransformersConfigPersist persist) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
TenantFileTransformersConfigEntity data = new TenantFileTransformersConfigEntity();
if (persist == null) return data;
if (!this.conventionService.isListNullOrEmpty(persist.getSources())) {
data.setSources(new ArrayList<>());
for (TenantSourcePersist sourcePersist : persist.getSources()) {
data.getSources().add(this.buildSourceEntity(sourcePersist));
}
}
return data;
}
private @NotNull TenantSourceEntity buildSourceEntity(TenantSourcePersist persist) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
TenantSourceEntity data = new TenantSourceEntity();
if (persist == null) return data;
data.setUrl(persist.getUrl());
data.setCodes(persist.getCodes());
data.setIssuerUrl(persist.getIssuerUrl());
data.setClientId(persist.getClientId());
data.setClientSecret(this.encryptionService.encryptAES(persist.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv()));
//data.setClientSecret(persist.getClientSecret());
data.setScope(persist.getScope());
return data;
}
@Override
public Tenant decryptTenant(Tenant model) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
if (model.getConfig() != null && model.getConfig().getDeposit() != null && model.getConfig().getDeposit().getSources() != null) {
for (TenantSource source : model.getConfig().getDeposit().getSources().stream().toList()) {
source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv()));
}
}
if (model.getConfig() != null && model.getConfig().getFileTransformers() != null && model.getConfig().getFileTransformers().getSources() != null) {
for (TenantSource source : model.getConfig().getFileTransformers().getSources().stream().toList()) {
source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv()));
}
}
TenantEntity data = this.entityManager.find(TenantEntity.class, model.getId());
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Tenant.class.getSimpleName()}, LocaleContextHolder.getLocale()));
TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent();
tenantTouchedIntegrationEvent.setId(data.getId());
tenantTouchedIntegrationEvent.setCode(data.getCode());
this.tenantTouchedIntegrationEventHandler.handle(tenantTouchedIntegrationEvent);
return model;
}
@Override
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug("deleting : {}", id);
this.authorizationService.authorizeForce(Permission.DeleteTenant);
this.deleterFactory.deleter(TenantDeleter.class).deleteAndSaveByIds(List.of(id));
TenantRemovalIntegrationEvent tenantRemovalIntegrationEvent = new TenantRemovalIntegrationEvent();
tenantRemovalIntegrationEvent.setId(id);
this.tenantRemovalIntegrationEventHandler.handle(tenantRemovalIntegrationEvent);
}
}