add dmp associated user query

This commit is contained in:
Efstratios Giannopoulos 2024-01-05 11:05:40 +02:00
parent 1a567c9a81
commit c9640bcb54
8 changed files with 247 additions and 4 deletions

View File

@ -86,6 +86,7 @@ public class AuditableAction {
public static final EventId User_MergeConfirm = new EventId(11011, "User_MergeConfirm");
public static final EventId User_RemoveCredentialRequest = new EventId(11012, "User_RemoveCredentialRequest");
public static final EventId User_RemoveCredentialConfirm = new EventId(11013, "User_RemoveCredentialConfirm");
public static final EventId User_DmpAssociatedQuery = new EventId(11014, "User_DmpAssociatedQuery");
public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query");
public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup");

View File

@ -56,7 +56,7 @@ public final class Permission {
public static String EditUser = "EditUser";
public static String DeleteUser = "DeleteUser";
public static String ExportUsers = "ExportUsers";
public static String BrowseDmpAssociatedUser = "BrowseDmpAssociatedUser";
//StorageFile

View File

@ -0,0 +1,39 @@
package eu.eudat.model;
import java.util.UUID;
public class DmpAssociatedUser {
private UUID id;
public static final String _id = "id";
private String name;
public static final String _name = "name";
private String email;
public static final String _email = "email";
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@ -0,0 +1,102 @@
package eu.eudat.model.builder;
import eu.eudat.authorization.AuthorizationFlags;
import eu.eudat.commons.JsonHandlingService;
import eu.eudat.commons.enums.ContactInfoType;
import eu.eudat.commons.types.user.AdditionalInfoEntity;
import eu.eudat.convention.ConventionService;
import eu.eudat.data.UserEntity;
import eu.eudat.model.*;
import eu.eudat.model.referencetypedefinition.ReferenceTypeSourceBaseConfiguration;
import eu.eudat.query.TenantUserQuery;
import eu.eudat.query.UserContactInfoQuery;
import eu.eudat.query.UserCredentialQuery;
import eu.eudat.query.UserRoleQuery;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DmpAssociatedUserBuilder extends BaseBuilder<DmpAssociatedUser, UserEntity> {
private final QueryFactory queryFactory;
private final BuilderFactory builderFactory;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public DmpAssociatedUserBuilder(ConventionService conventionService,
QueryFactory queryFactory,
BuilderFactory builderFactory) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(DmpAssociatedUserBuilder.class)));
this.queryFactory = queryFactory;
this.builderFactory = builderFactory;
}
public DmpAssociatedUserBuilder authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
public List<DmpAssociatedUser> build(FieldSet fields, List<UserEntity> data) throws MyApplicationException {
this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0));
this.logger.trace(new DataLogEntry("requested fields", fields));
if (fields == null || data == null || fields.isEmpty())
return new ArrayList<>();
List<DmpAssociatedUser> models = new ArrayList<>();
Map<UUID, List<UserContactInfo>> contactsMap = this.collectUserContactInfos(new BaseFieldSet().ensure(UserContactInfo._value).ensure(UserContactInfo._type).ensure(UserContactInfo._ordinal), data);
for (UserEntity d : data) {
DmpAssociatedUser m = new DmpAssociatedUser();
if (fields.hasField(this.asIndexer(User._id))) m.setId(d.getId());
if (fields.hasField(this.asIndexer(User._name))) m.setName(d.getName());
if (contactsMap != null && contactsMap.containsKey(d.getId())){
List<UserContactInfo> contactInfos = contactsMap.get(d.getId());
if (contactInfos != null) {
contactInfos.sort(Comparator.comparing(UserContactInfo::getOrdinal));
m.setEmail(contactInfos.stream().filter(x -> ContactInfoType.Email.equals(x.getType())).map(UserContactInfo::getValue).findFirst().orElse(null));
}
}
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
return models;
}
private Map<UUID, List<UserContactInfo>> collectUserContactInfos(FieldSet fields, List<UserEntity> data) throws MyApplicationException {
if (fields.isEmpty() || data.isEmpty()) return null;
this.logger.debug("checking related - {}", UserContactInfo.class.getSimpleName());
Map<UUID, List<UserContactInfo>> itemMap;
FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(this.asIndexer(UserContactInfo._user, User._id));
UserContactInfoQuery query = this.queryFactory.query(UserContactInfoQuery.class).authorize(this.authorize).userIds(data.stream().map(UserEntity::getId).distinct().collect(Collectors.toList()));
itemMap = this.builderFactory.builder(UserContactInfoBuilder.class).authorize(this.authorize).asMasterKey(query, clone, x -> x.getUser().getId());
if (!fields.hasField(this.asIndexer(UserContactInfo._user, User._id))) {
itemMap.values().stream().flatMap(List::stream).filter(x -> x != null && x.getUser() != null).peek(x -> {
x.getUser().setId(null);
});
}
return itemMap;
}
}

View File

@ -0,0 +1,39 @@
package eu.eudat.model.censorship;
import eu.eudat.authorization.OwnedResource;
import eu.eudat.authorization.Permission;
import eu.eudat.convention.ConventionService;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.UUID;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DmpAssociatedUserCensor extends BaseCensor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DmpAssociatedUserCensor.class));
protected final AuthorizationService authService;
public DmpAssociatedUserCensor(ConventionService conventionService, AuthorizationService authService) {
super(conventionService);
this.authService = authService;
}
public void censor(FieldSet fields, UUID userId) {
logger.debug(new DataLogEntry("censoring fields", fields));
if (fields == null || fields.isEmpty())
return;
this.authService.authorizeAtLeastOneForce(userId != null ? List.of(new OwnedResource(userId)) : null, Permission.BrowseDmpAssociatedUser);
}
}

View File

@ -5,22 +5,23 @@ import eu.eudat.authorization.Permission;
import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.scope.user.UserScope;
import eu.eudat.data.*;
import eu.eudat.model.DmpDescriptionTemplate;
import eu.eudat.model.Language;
import eu.eudat.model.User;
import eu.eudat.model.PublicUser;
import eu.eudat.model.UserRole;
import eu.eudat.query.utils.BuildSubQueryInput;
import eu.eudat.query.utils.QueryUtilsService;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.query.FieldResolver;
import gr.cite.tools.data.query.QueryBase;
import gr.cite.tools.data.query.QueryContext;
import gr.cite.tools.exception.MyNotFoundException;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Subquery;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.time.Instant;
@ -30,6 +31,7 @@ import java.util.*;
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserQuery extends QueryBase<UserEntity> {
private String like;
private Boolean dmpAssociated;
private Collection<UUID> ids;
private Collection<String> emails;
private Collection<UUID> excludedIds;
@ -117,6 +119,11 @@ public class UserQuery extends QueryBase<UserEntity> {
return this;
}
public UserQuery dmpAssociated(Boolean dmpAssociated) {
this.dmpAssociated = dmpAssociated;
return this;
}
public UserQuery authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
@ -186,7 +193,7 @@ public class UserQuery extends QueryBase<UserEntity> {
new BuildSubQueryInput.Builder<>(UserContactInfoQuery.class, UUID.class, queryContext)
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(UserContactInfoEntity._id))
.filterFunc((subQueryRoot, cb) -> {
CriteriaBuilder.In<String> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._value));
CriteriaBuilder.In<String> inClause = cb.in(subQueryRoot.get(UserContactInfoEntity._value));
for (String item : this.emails)
inClause.value(item);
return inClause;
@ -199,6 +206,30 @@ public class UserQuery extends QueryBase<UserEntity> {
QueryContext<UserRoleEntity, UUID> subQuery = this.applySubQuery(this.userRoleQuery, queryContext, UUID.class, userRoleEntityRoot -> userRoleEntityRoot.get(UserRoleEntity._userId));
predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserEntity._id)).value(subQuery.Query));
}
if (this.dmpAssociated){
UUID userId;
if (this.userScope.isSet()) userId = this.userScope.getUserIdSafe();
else throw new MyNotFoundException("Only user scoped allowed");
Subquery<UUID> dmpUserDmpQuery = queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(
new BuildSubQueryInput.Builder<>(DmpUserQuery.class, UUID.class, queryContext)
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(DmpUserEntity._dmpId))
.filterFunc((subQueryRoot, cb) -> cb.and(
cb.equal(subQueryRoot.get(DmpUserEntity._userId), userId),
cb.equal(subQueryRoot.get(DmpUserEntity._isActive), IsActive.Active)
))
));
Subquery<UUID> dmpUserUserQuery = queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(
new BuildSubQueryInput.Builder<>(DmpUserQuery.class, UUID.class, queryContext)
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(DmpUserEntity._userId))
.filterFunc((subQueryRoot, cb) -> cb.and(
cb.in(subQueryRoot.get(DmpUserEntity._dmpId)).value(dmpUserDmpQuery) ,
cb.equal(subQueryRoot.get(DmpUserEntity._isActive), IsActive.Active)
))
));
predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserEntity._id)).value(dmpUserUserQuery));
}
if (!predicates.isEmpty()) {

View File

@ -3,12 +3,16 @@ package eu.eudat.controllers.v2;
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 eu.eudat.commons.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.UserPersist;
import eu.eudat.model.persist.UserRolePatchPersist;
@ -107,6 +111,23 @@ public class UserController {
return new QueryResult<>(models, count);
}
@PostMapping("dmp-associated/query")
public QueryResult<DmpAssociatedUser> 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<UserEntity> data = query.collectAs(lookup.getProject());
List<DmpAssociatedUser> models = this.builderFactory.builder(DmpAssociatedUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).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));

View File

@ -217,6 +217,16 @@ permissions:
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
BrowseDmpAssociatedUser:
roles:
- Admin
- DescriptionTemplateEditor
- Manager
- User
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# DescriptionTemplateType
BrowseDescriptionTemplateType:
roles: