package eu.eudat.controllers; import com.fasterxml.jackson.core.JsonProcessingException; import eu.eudat.audit.AuditableAction; import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.user.UserScope; import gr.cite.tools.validation.ValidationFilterAnnotation; import eu.eudat.data.UserEntity; import eu.eudat.model.DmpAssociatedUser; import eu.eudat.model.User; import eu.eudat.model.UserRole; import eu.eudat.model.builder.DmpAssociatedUserBuilder; import eu.eudat.model.builder.UserBuilder; import eu.eudat.model.censorship.DmpAssociatedUserCensor; import eu.eudat.model.censorship.UserCensor; import eu.eudat.model.persist.actionconfirmation.RemoveCredentialRequestPersist; import eu.eudat.model.persist.UserMergeRequestPersist; import eu.eudat.model.persist.UserPersist; import eu.eudat.model.persist.UserRolePatchPersist; import eu.eudat.model.result.QueryResult; import eu.eudat.query.UserQuery; import eu.eudat.query.lookup.UserLookup; import eu.eudat.service.responseutils.ResponseUtilsService; import eu.eudat.service.user.UserService; import gr.cite.tools.auditing.AuditService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.censor.CensorFactory; 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.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import jakarta.xml.bind.JAXBException; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.management.InvalidApplicationException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.List; import java.util.Map; import java.util.UUID; @RestController @RequestMapping(path = "api/user") public class UserController { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserController.class)); private final BuilderFactory builderFactory; private final AuditService auditService; private final UserService userTypeService; private final CensorFactory censorFactory; private final QueryFactory queryFactory; private final UserScope userScope; private final MessageSource messageSource; private final ResponseUtilsService responseUtilsService; public UserController( BuilderFactory builderFactory, AuditService auditService, UserService userTypeService, CensorFactory censorFactory, QueryFactory queryFactory, UserScope userScope, MessageSource messageSource, ResponseUtilsService responseUtilsService) { this.builderFactory = builderFactory; this.auditService = auditService; this.userTypeService = userTypeService; this.censorFactory = censorFactory; this.queryFactory = queryFactory; this.userScope = userScope; this.messageSource = messageSource; this.responseUtilsService = responseUtilsService; } @PostMapping("query") public QueryResult query(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", User.class.getSimpleName()); this.censorFactory.censor(UserCensor.class).censor(lookup.getProject(), null); UserQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission); List data = query.collectAs(lookup.getProject()); List models = this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(lookup.getProject(), data); long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); this.auditService.track(AuditableAction.User_Query, "lookup", lookup); return new QueryResult<>(models, count); } @PostMapping("dmp-associated/query") public QueryResult queryDmpAssociated(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", User.class.getSimpleName()); this.censorFactory.censor(DmpAssociatedUserCensor.class).censor(lookup.getProject(), null); UserQuery query = lookup.enrich(this.queryFactory).dmpAssociated(true).isActive(IsActive.Active); List data = query.collectAs(lookup.getProject()); List models = this.builderFactory.builder(DmpAssociatedUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(lookup.getProject(), data); long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); this.auditService.track(AuditableAction.User_DmpAssociatedQuery, "lookup", lookup); return new QueryResult<>(models, count); } @GetMapping("{id}") public User get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("id", id).And("fields", fieldSet)); this.censorFactory.censor(UserCensor.class).censor(fieldSet, id); UserQuery query = this.queryFactory.query(UserQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(id); User model = this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(fieldSet, query.firstAs(fieldSet)); if (model == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, User.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.auditService.track(AuditableAction.User_Lookup, Map.ofEntries( new AbstractMap.SimpleEntry("id", id), new AbstractMap.SimpleEntry("fields", fieldSet) )); return model; } @GetMapping("/by-email/{email}") public User get(@PathVariable("email") String email, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("email", email).And("fields", fieldSet)); this.censorFactory.censor(UserCensor.class).censor(fieldSet, null); UserQuery query = this.queryFactory.query(UserQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).emails(email); User model = this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(fieldSet, query.firstAs(fieldSet)); if (model == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{email, User.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.auditService.track(AuditableAction.User_LookupByEmail, Map.ofEntries( new AbstractMap.SimpleEntry("email", email), new AbstractMap.SimpleEntry("fields", fieldSet) )); return model; } @GetMapping("/export/csv") public ResponseEntity exportCsv() throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException { logger.debug(new MapLogEntry("export" + User.class.getSimpleName())); // this.censorFactory.censor(UserCensor.class).censor(fieldSet, null); byte[] bytes = this.userTypeService.exportCsv(); this.auditService.track(AuditableAction.User_ExportCsv, Map.ofEntries( )); return this.responseUtilsService.buildResponseFileFromText(new String(bytes, StandardCharsets.UTF_8), "Users_dump.csv"); } @GetMapping("mine") public User getMine(FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving me" + User.class.getSimpleName()).And("fields", fieldSet)); this.censorFactory.censor(UserCensor.class).censor(fieldSet, this.userScope.getUserId()); UserQuery query = this.queryFactory.query(UserQuery.class).ids(this.userScope.getUserId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission); User model = this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(fieldSet, query.firstAs(fieldSet)); if (model == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{this.userScope.getUserId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.auditService.track(AuditableAction.User_Lookup, Map.ofEntries( new AbstractMap.SimpleEntry("fields", fieldSet) )); return model; } @GetMapping("mine/language/{language}") @Transactional public void updateLanguageMine(@PathVariable("language") String language) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("language", language)); this.userTypeService.updateLanguageMine(language); this.auditService.track(AuditableAction.User_LanguageMine, Map.ofEntries( new AbstractMap.SimpleEntry("language", language) )); } @GetMapping("mine/timezone/{timezone}") @Transactional public void updateTimezoneMine(@PathVariable("timezone") String timezone) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("timezone", timezone)); this.userTypeService.updateTimezoneMine(timezone); this.auditService.track(AuditableAction.User_TimezoneMine, Map.ofEntries( new AbstractMap.SimpleEntry("timezone", timezone) )); } @GetMapping("mine/culture/{culture}") @Transactional public void updateCultureMine(@PathVariable("culture") String culture) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("culture", culture)); this.userTypeService.updateCultureMine(culture); this.auditService.track(AuditableAction.User_CultureMine, Map.ofEntries( new AbstractMap.SimpleEntry("culture", culture) )); } @PostMapping("persist") @Transactional @ValidationFilterAnnotation(validator = UserPersist.UserPersistValidator.ValidatorName, argumentName = "model") public User persist(@RequestBody UserPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); User persisted = this.userTypeService.persist(model, fieldSet); this.auditService.track(AuditableAction.User_Persist, Map.ofEntries( new AbstractMap.SimpleEntry("model", model), new AbstractMap.SimpleEntry("fields", fieldSet) )); return persisted; } @PostMapping("persist/roles") @Transactional @ValidationFilterAnnotation(validator = UserRolePatchPersist.UserRolePatchPersistValidator.ValidatorName, argumentName = "model") public User persistRoles(@RequestBody UserRolePatchPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { logger.debug(new MapLogEntry("persisting" + UserRole.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); User persisted = this.userTypeService.patchRoles(model, fieldSet); this.auditService.track(AuditableAction.User_PersistRoles, Map.ofEntries( new AbstractMap.SimpleEntry("model", model), new AbstractMap.SimpleEntry("fields", fieldSet) )); return persisted; } @DeleteMapping("{id}") @Transactional public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("id", id)); this.userTypeService.deleteAndSave(id); this.auditService.track(AuditableAction.User_Delete, "id", id); } @PostMapping("mine/merge-account-request") @Transactional @ValidationFilterAnnotation(validator = UserMergeRequestPersist.UserMergeRequestPersistValidator.ValidatorName, argumentName = "model") public Boolean mergeAccount(@RequestBody UserMergeRequestPersist model) throws InvalidApplicationException, JAXBException { logger.debug(new MapLogEntry("merge account to user").And("email", model)); this.userTypeService.sendMergeAccountConfirmation(model); this.auditService.track(AuditableAction.User_MergeRequest, Map.ofEntries( new AbstractMap.SimpleEntry("model", model) )); return true; } @GetMapping("mine/confirm-merge-account/token/{token}") @Transactional public Boolean confirmMergeAccount(@PathVariable("token") String token) throws InvalidApplicationException, IOException { logger.debug(new MapLogEntry("confirm merge account to user").And("token", token)); this.userTypeService.confirmMergeAccount(token); this.auditService.track(AuditableAction.User_MergeConfirm, Map.ofEntries( new AbstractMap.SimpleEntry("token", token) )); return true; } @PostMapping("mine/remove-credential-request") @Transactional @ValidationFilterAnnotation(validator = RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.ValidatorName, argumentName = "model") public Boolean removeCredentialAccount(@RequestBody RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException { logger.debug(new MapLogEntry("remove credential request to user").And("model", model)); this.userTypeService.sendRemoveCredentialConfirmation(model); this.auditService.track(AuditableAction.User_RemoveCredentialRequest, Map.ofEntries( new AbstractMap.SimpleEntry("email", model) )); return true; } @GetMapping("mine/confirm-remove-credential/token/{token}") @Transactional public Boolean confirmRemoveCredentialAccount(@PathVariable("token") String token) throws InvalidApplicationException, JAXBException { logger.debug(new MapLogEntry("confirm remove credential to user").And("token", token)); this.userTypeService.confirmRemoveCredential(token); this.auditService.track(AuditableAction.User_RemoveCredentialConfirm, Map.ofEntries( new AbstractMap.SimpleEntry("model", token) )); return true; } }