diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java index fc8374251..82a4c44de 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java @@ -101,6 +101,7 @@ public final class Permission { //Description public static String BrowseDescription = "BrowseDescription"; public static String EditDescription = "EditDescription"; + public static String FinalizeDescription = "FinalizeDescription"; public static String DeleteDescription = "DeleteDescription"; public static String CloneDescription = "CloneDescription"; diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java index 846ed310e..345829a3f 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java @@ -12,4 +12,8 @@ public interface AuthorizationContentResolver { AffiliatedResource dmpAffiliation(UUID id); Map dmpsAffiliation(List ids); + + AffiliatedResource descriptionAffiliation(UUID id); + + Map descriptionsAffiliation(List ids); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java index 98f004fd7..2fda5280c 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java @@ -4,9 +4,12 @@ import eu.eudat.authorization.AffiliatedResource; import eu.eudat.authorization.PermissionNameProvider; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.data.DescriptionEntity; import eu.eudat.data.DmpEntity; import eu.eudat.data.DmpUserEntity; +import eu.eudat.model.Description; import eu.eudat.model.DmpUser; +import eu.eudat.query.DescriptionQuery; import eu.eudat.query.DmpUserQuery; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.fieldset.BaseFieldSet; @@ -14,6 +17,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.context.annotation.RequestScope; import java.util.*; +import java.util.stream.Collectors; @Service @RequestScope @@ -60,6 +64,37 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes return affiliatedResources; } + @Override + public AffiliatedResource descriptionAffiliation(UUID id) { + return this.descriptionsAffiliation(List.of(id)).getOrDefault(id, new AffiliatedResource()); + } + @Override + public Map descriptionsAffiliation(List ids){ + UUID userId = this.userScope.getUserIdSafe(); + Map affiliatedResources = new HashMap<>(); + for (UUID id : ids){ + affiliatedResources.put(id, new AffiliatedResource()); + } + if (userId == null || !userScope.isSet()) return affiliatedResources; + + List idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, DescriptionEntity.class.getSimpleName()); + if (idsToResolve.isEmpty()) return affiliatedResources; + + List descriptionEntities = this.queryFactory.query(DescriptionQuery.class).ids(ids).collectAs(new BaseFieldSet().ensure(Description._id).ensure(Description._dmp)); + List dmpUsers = this.queryFactory.query(DmpUserQuery.class).descriptionIds(ids).userIds(userId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(DmpUser._role).ensure(DmpUser._dmp)); + Map> dmpUsersMap = dmpUsers.stream().collect(Collectors.groupingBy(DmpUserEntity::getDmpId)); + + for (DescriptionEntity description : descriptionEntities){ + List dmpDescriptionUsers = dmpUsersMap.getOrDefault(description.getDmpId(), new ArrayList<>()); + for (DmpUserEntity dmpUser : dmpDescriptionUsers) { + affiliatedResources.get(description.getId()).getDmpUserRoles().add(dmpUser.getRole()); + } + } + + this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, DescriptionEntity.class.getSimpleName()); + return affiliatedResources; + } + private List getAffiliatedFromCache(List ids, UUID userId, Map affiliatedResources, String entityType){ List idsToResolve = new ArrayList<>(); for (UUID id : ids){ diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/Description.java b/dmp-backend/core/src/main/java/eu/eudat/model/Description.java index 40ae2681a..bbea69453 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/Description.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/Description.java @@ -71,6 +71,9 @@ public class Description { public static final String _descriptionTemplate = "descriptionTemplate"; + private List authorizationFlags; + public static final String _authorizationFlags = "authorizationFlags"; + private Dmp dmp; public static final String _dmp = "dmp"; @@ -203,4 +206,12 @@ public class Description { public void setDescriptionTemplate(DescriptionTemplate descriptionTemplate) { this.descriptionTemplate = descriptionTemplate; } + + public List getAuthorizationFlags() { + return authorizationFlags; + } + + public void setAuthorizationFlags(List authorizationFlags) { + this.authorizationFlags = authorizationFlags; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/builder/DescriptionBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/model/builder/DescriptionBuilder.java index 5d7c0a6d4..2be0069f6 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/builder/DescriptionBuilder.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/builder/DescriptionBuilder.java @@ -1,6 +1,8 @@ package eu.eudat.model.builder; +import eu.eudat.authorization.AffiliatedResource; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.authorization.authorizationcontentresolver.AuthorizationContentResolver; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.types.description.PropertyDefinitionEntity; @@ -8,10 +10,12 @@ import eu.eudat.commons.types.descriptiontemplate.DefinitionEntity; import eu.eudat.convention.ConventionService; import eu.eudat.data.DescriptionEntity; import eu.eudat.data.DescriptionTemplateEntity; +import eu.eudat.data.DmpEntity; import eu.eudat.data.UserRoleEntity; import eu.eudat.model.*; import eu.eudat.model.builder.descriptionpropertiesdefinition.PropertyDefinitionBuilder; import eu.eudat.query.*; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; @@ -37,6 +41,8 @@ public class DescriptionBuilder extends BaseBuilder authorize = EnumSet.of(AuthorizationFlags.None); @@ -44,12 +50,14 @@ public class DescriptionBuilder extends BaseBuilder values) { @@ -85,7 +93,10 @@ public class DescriptionBuilder extends BaseBuilder definitionEntityMap = !definitionPropertiesFields.isEmpty() ? this.collectDescriptionTemplateDefinitions(data) : null; - + + Set authorizationFlags = this.extractAuthorizationFlags(fields, Description._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); + Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.descriptionsAffiliation(data.stream().map(DescriptionEntity::getId).collect(Collectors.toList())); + List models = new ArrayList<>(); for (DescriptionEntity d : data) { Description m = new Description(); @@ -108,6 +119,7 @@ public class DescriptionBuilder extends BaseBuilder { Map> dmpDescriptionTemplatesMap = this.collectDmpDescriptionTemplates(dmpDescriptionTemplatesFields, data); Set authorizationFlags = this.extractAuthorizationFlags(fields, Dmp._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); - Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.dmpsAffiliation(data.stream().map(DmpEntity::getId).collect(Collectors.toList())); FieldSet propertiesFields = fields.extractPrefixed(this.asPrefix(Dmp._properties)); @@ -121,7 +120,7 @@ public class DmpBuilder extends BaseBuilder { DmpPropertiesEntity propertyDefinition = this.jsonHandlingService.fromJsonSafe(DmpPropertiesEntity.class, d.getProperties()); m.setProperties(this.builderFactory.builder(DmpPropertiesBuilder.class).authorize(this.authorize).build(propertiesFields, propertyDefinition)); } - if (authorizationFlags != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null))); + if (affiliatedResourceMap != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null))); models.add(m); } this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/TagCensor.java b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/TagCensor.java index 9448f73f6..ebf74eb37 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/TagCensor.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/TagCensor.java @@ -39,7 +39,7 @@ public class TagCensor extends BaseCensor { if (fields == null || fields.isEmpty()) return; - this.authService.authorizeForce(Permission.BrowseTag); + this.authService.authorizeForce(Permission.BrowseTag, Permission.DeferredAffiliation); FieldSet createdByFields = fields.extractPrefixed(this.asIndexerPrefix(Tag._createdBy)); this.censorFactory.censor(UserCensor.class).censor(createdByFields, userId); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/query/DescriptionQuery.java b/dmp-backend/core/src/main/java/eu/eudat/query/DescriptionQuery.java index 200c9684a..0c636e2d2 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/query/DescriptionQuery.java +++ b/dmp-backend/core/src/main/java/eu/eudat/query/DescriptionQuery.java @@ -314,6 +314,7 @@ public class DescriptionQuery extends QueryBase { else if (item.prefix(Description._descriptionTemplate) || item.prefix(PublicDescription._descriptionTemplate)) return DescriptionEntity._descriptionTemplateId; else if (item.match(Description._descriptionTemplate) || item.match(PublicDescription._descriptionTemplate)) return DescriptionEntity._descriptionTemplateId; else if (item.prefix(Description._dmp)) return DescriptionEntity._dmpId; + else if (item.match(Description._dmp)) return DescriptionEntity._dmpId; else return null; } diff --git a/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java b/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java index 777855b1f..4a76d6092 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java +++ b/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java @@ -5,9 +5,7 @@ import eu.eudat.authorization.Permission; import eu.eudat.commons.enums.DmpUserRole; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.user.UserScope; -import eu.eudat.data.DmpReferenceEntity; -import eu.eudat.data.DmpUserEntity; -import eu.eudat.data.ReferenceEntity; +import eu.eudat.data.*; import eu.eudat.model.DmpUser; import eu.eudat.model.PublicDmpUser; import eu.eudat.query.utils.BuildSubQueryInput; @@ -19,6 +17,7 @@ import gr.cite.tools.data.query.QueryContext; 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.stereotype.Component; @@ -36,6 +35,8 @@ public class DmpUserQuery extends QueryBase { private Collection dmpIds; + private Collection descriptionIds; + private Collection userIds; private Collection userRoles; @@ -89,6 +90,21 @@ public class DmpUserQuery extends QueryBase { return this; } + public DmpUserQuery descriptionIds(UUID value) { + this.descriptionIds = List.of(value); + return this; + } + + public DmpUserQuery descriptionIds(UUID... value) { + this.descriptionIds = Arrays.asList(value); + return this; + } + + public DmpUserQuery descriptionIds(Collection values) { + this.descriptionIds = values; + return this; + } + public DmpUserQuery userRoles(DmpUserRole value) { this.userRoles = List.of(value); return this; @@ -161,7 +177,7 @@ public class DmpUserQuery extends QueryBase { @Override protected Boolean isFalseQuery() { - return this.isEmpty(this.ids) || this.isEmpty(this.dmpIds) || this.isEmpty(this.userIds); + return this.isEmpty(this.ids) || this.isEmpty(this.dmpIds) || this.isEmpty(this.descriptionIds) || this.isEmpty(this.userIds); } @Override @@ -208,6 +224,19 @@ public class DmpUserQuery extends QueryBase { inClause.value(item); predicates.add(inClause); } + if (this.descriptionIds != null) { + Subquery descriptionSubquery = queryUtilsService.buildSubQuery(new BuildSubQueryInput<>( + new BuildSubQueryInput.Builder<>(DescriptionEntity.class, UUID.class, queryContext) + .keyPathFunc((subQueryRoot) -> subQueryRoot.get(DescriptionEntity._dmpId)) + .filterFunc((subQueryRoot, cb) -> { + CriteriaBuilder.In inClause = cb.in(subQueryRoot.get(DmpUserEntity._id)); + for (UUID item : this.descriptionIds) + inClause.value(item); + return inClause; + }) + )); + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(DmpUserEntity._dmpId)).value(descriptionSubquery)); + } if (this.userIds != null) { CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(DmpUserEntity._userId)); for (UUID item : this.userIds) diff --git a/dmp-backend/core/src/main/java/eu/eudat/query/TagQuery.java b/dmp-backend/core/src/main/java/eu/eudat/query/TagQuery.java index fde9b5e38..345f355a9 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/query/TagQuery.java +++ b/dmp-backend/core/src/main/java/eu/eudat/query/TagQuery.java @@ -1,9 +1,17 @@ package eu.eudat.query; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.authorization.Permission; import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.data.DescriptionTagEntity; +import eu.eudat.data.DmpReferenceEntity; +import eu.eudat.data.ReferenceEntity; import eu.eudat.data.TagEntity; import eu.eudat.model.Tag; +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; @@ -34,8 +42,15 @@ public class TagQuery extends QueryBase { private Collection createdByIds; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + private final AuthorizationService authService; + private final QueryUtilsService queryUtilsService; - public TagQuery() { + public TagQuery(UserScope userScope, AuthorizationService authService, QueryUtilsService queryUtilsService) { + this.userScope = userScope; + this.authService = authService; + this.queryUtilsService = queryUtilsService; } public TagQuery like(String value) { @@ -153,6 +168,37 @@ public class TagQuery extends QueryBase { protected Class entityClass() { return TagEntity.class; } + @Override + protected Predicate applyAuthZ(QueryContext queryContext) { + if (this.authorize.contains(AuthorizationFlags.None)) return null; + if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseTag)) return null; + UUID userId; + boolean usePublic = this.authorize.contains(AuthorizationFlags.Public); + if (this.authorize.contains(AuthorizationFlags.DmpAssociated)) userId = this.userScope.getUserIdSafe(); + else userId = null; + + List predicates = new ArrayList<>(); + if (userId != null || usePublic ) { + predicates.add(queryContext.CriteriaBuilder.or( + queryContext.CriteriaBuilder.isNull(queryContext.Root.get(TagEntity._createdById)), + userId != null ? queryContext.CriteriaBuilder.equal(queryContext.Root.get(TagEntity._createdById), userId) : queryContext.CriteriaBuilder.or(), //Creates a false query + queryContext.CriteriaBuilder.in(queryContext.Root.get(TagEntity._id)).value(queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(DescriptionTagEntity.class, UUID.class) + .query(queryContext.Query) + .criteriaBuilder(queryContext.CriteriaBuilder) + .keyPathFunc((subQueryRoot) -> subQueryRoot.get(DescriptionTagEntity._tagId)) + .filterFunc((subQueryRoot, cb) -> + cb.in(subQueryRoot.get(DescriptionTagEntity._descriptionId)).value(queryUtilsService.buildDescriptionAuthZSubQuery(queryContext.Query, queryContext.CriteriaBuilder, userId, usePublic)) + ) + ))) //Creates a false query + )); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } @Override protected Predicate applyFilters(QueryContext queryContext) { diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java index 25050e258..2ca8ce0ab 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java @@ -2,6 +2,7 @@ package eu.eudat.service.description; import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.authorization.Permission; +import eu.eudat.authorization.authorizationcontentresolver.AuthorizationContentResolver; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.enums.*; @@ -96,21 +97,22 @@ public class DescriptionServiceImpl implements DescriptionService { private final StorageFileProperties storageFileConfig; private final StorageFileService storageFileService; private final DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler; + private final AuthorizationContentResolver authorizationContentResolver; @Autowired public DescriptionServiceImpl( - EntityManager entityManager, - AuthorizationService authorizationService, - DeleterFactory deleterFactory, - BuilderFactory builderFactory, - ConventionService conventionService, - ErrorThesaurusProperties errors, - MessageSource messageSource, - EventBroker eventBroker, - QueryFactory queryFactory, - JsonHandlingService jsonHandlingService, - UserScope userScope, - XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler) { + EntityManager entityManager, + AuthorizationService authorizationService, + DeleterFactory deleterFactory, + BuilderFactory builderFactory, + ConventionService conventionService, + ErrorThesaurusProperties errors, + MessageSource messageSource, + EventBroker eventBroker, + QueryFactory queryFactory, + JsonHandlingService jsonHandlingService, + UserScope userScope, + XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, DescriptionTouchedIntegrationEventHandler descriptionTouchedIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -131,6 +133,7 @@ public class DescriptionServiceImpl implements DescriptionService { this.storageFileConfig = storageFileConfig; this.storageFileService = storageFileService; this.descriptionTouchedIntegrationEventHandler = descriptionTouchedIntegrationEventHandler; + this.authorizationContentResolver = authorizationContentResolver; } //region Persist @@ -139,9 +142,10 @@ public class DescriptionServiceImpl implements DescriptionService { public Description persist(DescriptionPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException { logger.debug(new MapLogEntry("persisting data description").And("model", model).And("fields", fields)); - this.authorizationService.authorizeForce(Permission.EditDescription); - Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); + if (isUpdate) this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(model.getId())), Permission.EditDescription); + else this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getDmpId())), Permission.EditDescription); + DescriptionEntity data; if (isUpdate) { @@ -336,13 +340,14 @@ public class DescriptionServiceImpl implements DescriptionService { public Description persistStatus(DescriptionStatusPersist model, FieldSet fields) throws IOException { logger.debug(new MapLogEntry("persisting data dmp").And("model", model).And("fields", fields)); - this.authorizationService.authorizeForce(Permission.EditDescription); + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(model.getId())), Permission.EditDescription); DescriptionEntity data = this.entityManager.find(DescriptionEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); if (!data.getStatus().equals(model.getStatus())){ if (data.getStatus().equals(DescriptionStatus.Finalized)){ + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(model.getId())), Permission.FinalizeDescription); DmpEntity dmpEntity = this.entityManager.find(DmpEntity.class, data.getDmpId()); if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{data.getDmpId(), DmpEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); if(!dmpEntity.getStatus().equals(DmpStatus.Draft)) throw new MyValidationException(this.errors.getDmpIsFinalized().getCode(), this.errors.getDmpIsFinalized().getMessage()); @@ -650,7 +655,7 @@ public class DescriptionServiceImpl implements DescriptionService { public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException, IOException { logger.debug("deleting description: {}", id); - this.authorizationService.authorizeForce(Permission.DeleteDescription); + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(id)), Permission.DeleteDescription); this.deleterFactory.deleter(DescriptionDeleter.class).deleteAndSaveByIds(List.of(id), false); } @@ -663,7 +668,7 @@ public class DescriptionServiceImpl implements DescriptionService { public void clone(UUID dmpId, UUID descriptionId) throws InvalidApplicationException, IOException { logger.debug("cloning description: {} with description: {}", descriptionId, dmpId); - this.authorizationService.authorizeForce(Permission.CloneDescription); + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(descriptionId)), Permission.CloneDescription); DescriptionEntity existing = this.queryFactory.query(DescriptionQuery.class).ids(descriptionId).isActive(IsActive.Active).first(); @@ -743,7 +748,8 @@ public class DescriptionServiceImpl implements DescriptionService { @Override public StorageFile uploadFieldFile(DescriptionFieldFilePersist model, MultipartFile file, FieldSet fields) throws IOException { - this.authorizationService.authorizeForce(Permission.EditDescription); + //this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(descriptionId)), Permission.CloneDescription); + this.authorizationService.authorizeForce(Permission.EditDescription);//TODO: Missing Description or dmp for authz DescriptionTemplateEntity descriptionTemplate = this.queryFactory.query(DescriptionTemplateQuery.class).ids(model.getDescriptionTemplateId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).first(); if (descriptionTemplate == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDescriptionTemplateId(), DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); @@ -783,7 +789,8 @@ public class DescriptionServiceImpl implements DescriptionService { @Override public StorageFileEntity getFieldFile(UUID descriptionId, UUID storageFileId) { - this.authorizationService.authorizeForce(Permission.BrowseDescription); + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(descriptionId)), Permission.BrowseDescription); + DescriptionEntity descriptionEntity = this.queryFactory.query(DescriptionQuery.class).isActive(IsActive.Active).ids(descriptionId).first(); if (descriptionEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); diff --git a/dmp-backend/web/src/main/resources/config/permissions.yml b/dmp-backend/web/src/main/resources/config/permissions.yml index d721300ba..0cbcfb49f 100644 --- a/dmp-backend/web/src/main/resources/config/permissions.yml +++ b/dmp-backend/web/src/main/resources/config/permissions.yml @@ -132,18 +132,48 @@ permissions: BrowseDescription: roles: - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer clients: [ ] allowAnonymous: false allowAuthenticated: false EditDescription: roles: - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + FinalizeDescription: + roles: + - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDescription: roles: - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer claims: [ ] clients: [ ] allowAnonymous: false @@ -151,11 +181,16 @@ permissions: CloneDescription: roles: - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false - # Tag BrowseTag: roles: diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index b537deeea..6051f9205 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -10,8 +10,10 @@ export enum AppPermission { DeleteDmpBlueprint = "DeleteDmpBlueprint", //Description + NewDescription = "NewDescription", BrowseDescription = "BrowseDescription", EditDescription = "EditDescription", + FinalizeDescription = "FinalizeDescription", DeleteDescription= "DeleteDescription", //Dmp diff --git a/dmp-frontend/src/app/core/model/description/description.ts b/dmp-frontend/src/app/core/model/description/description.ts index 91fcda4ae..2c2f2890d 100644 --- a/dmp-frontend/src/app/core/model/description/description.ts +++ b/dmp-frontend/src/app/core/model/description/description.ts @@ -6,6 +6,7 @@ import { Dmp, DmpDescriptionTemplate, PublicDmp } from "../dmp/dmp"; import { Reference, ReferencePersist } from "../reference/reference"; import { Tag } from "../tag/tag"; import { User } from "../user/user"; +import { AppPermission } from "@app/core/common/enum/permission.enum"; export interface Description extends BaseEntity { label?: string; @@ -19,6 +20,7 @@ export interface Description extends BaseEntity { descriptionTemplate?: DescriptionTemplate; dmpDescriptionTemplate?: DmpDescriptionTemplate; dmp?: Dmp; + authorizationFlags?: AppPermission[]; } @@ -64,9 +66,9 @@ export interface DescriptionTag extends BaseEntity { tag?: Tag; } -// +// // Persist -// +// export interface DescriptionPersist extends BaseEntityPersist { label: string; dmpId: Guid; @@ -141,4 +143,4 @@ export interface PublicDescriptionTemplate { id: Guid; label: string; description: string; -} \ No newline at end of file +} diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts index 64bdb821c..6b472b718 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts @@ -78,24 +78,6 @@ export class DescriptionEditorComponent extends BaseEditor x === AppPermission.EditDescription) || this.item.dmp.authorizationFlags?.some(x => x === AppPermission.EditDmp) ||this.authService.hasPermission(AppPermission.EditDescription); + this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !canedit); //this.visibilityRulesService.buildVisibilityRules(this.visibilityRulesService.getVisibilityRulesFromDescriptionTempalte(this.item.descriptionTemplate), this.formGroup); // this.selectedSystemFields = this.selectedSystemFieldDisabled(); diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.resolver.ts b/dmp-frontend/src/app/ui/description/editor/description-editor.resolver.ts index a7ea6406a..89240649a 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-editor.resolver.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-editor.resolver.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; import { DescriptionTemplate, DescriptionTemplateBaseFieldData, DescriptionTemplateDefinition, DescriptionTemplateExternalDatasetData, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateMultiplicity, DescriptionTemplatePage, DescriptionTemplateReferenceTypeData, DescriptionTemplateRule, DescriptionTemplateSection, DescriptionTemplateSelectData, DescriptionTemplateSelectOption, DescriptionTemplateUploadData, DescriptionTemplateUploadOption } from '@app/core/model/description-template/description-template'; import { Description, DescriptionExternalIdentifier, DescriptionField, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionReference, DescriptionTag } from '@app/core/model/description/description'; import { DescriptionTemplatesInSection, DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint'; @@ -50,6 +51,10 @@ export class DescriptionEditorResolver extends BaseEditorResolver { nameof(x => x.description), nameof(x => x.status), + [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'), + [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.id)].join('.'), [nameof(x => x.dmpDescriptionTemplate), nameof(x => x.sectionId)].join('.'), @@ -124,6 +129,8 @@ export class DescriptionEditorResolver extends BaseEditorResolver { (prefix ? prefix + '.' : '') + [nameof(x => x.status)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.isActive)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.authorizationFlags), AppPermission.EditDmp].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.blueprint), nameof(x => x.id)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.blueprint), nameof(x => x.isActive)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.blueprint), nameof(x => x.definition)].join('.'), diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.routing.ts b/dmp-frontend/src/app/ui/description/editor/description-editor.routing.ts index 2f952c7e2..6f08619da 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-editor.routing.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-editor.routing.ts @@ -21,10 +21,11 @@ const routes: Routes = [ data: { ...BreadcrumbService.generateRouteDataConfiguration({ title: 'BREADCRUMBS.EDIT-DESCRIPTION' - }), - authContext: { - permissions: [AppPermission.EditDescription] - } + }) + // , + // authContext: { + // permissions: [AppPermission.EditDescription] + // } } }, { @@ -55,10 +56,11 @@ const routes: Routes = [ data: { ...BreadcrumbService.generateRouteDataConfiguration({ title: 'BREADCRUMBS.EDIT-DESCRIPTION' - }), - authContext: { - permissions: [AppPermission.EditDescription] - } + }) + // , + // authContext: { + // permissions: [AppPermission.EditDescription] + // } } }, { @@ -72,10 +74,11 @@ const routes: Routes = [ data: { ...BreadcrumbService.generateRouteDataConfiguration({ title: 'BREADCRUMBS.EDIT-DESCRIPTION' - }), - authContext: { - permissions: [AppPermission.EditDescription] - } + }) + // , + // authContext: { + // permissions: [AppPermission.EditDescription] + // } } }, diff --git a/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts b/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts index d8e74ab0a..af6b5bd5c 100644 --- a/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts +++ b/dmp-frontend/src/app/ui/description/listing/description-listing.component.ts @@ -8,6 +8,7 @@ import { MatSort } from '@angular/material/sort'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DescriptionStatus } from '@app/core/common/enum/description-status'; import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; import { RecentActivityOrder } from '@app/core/common/enum/recent-activity-order'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { Description } from '@app/core/model/description/description'; @@ -196,6 +197,11 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit nameof(x => x.label), nameof(x => x.status), nameof(x => x.updatedAt), + + + [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), + [nameof(x => x.descriptionTemplate), nameof(x => x.id)].join('.'), [nameof(x => x.descriptionTemplate), nameof(x => x.label)].join('.'), [nameof(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), @@ -236,7 +242,7 @@ export class DescriptionListingComponent extends BaseComponent implements OnInit this.hasListingItems = true; }); } - + } openFiltersDialog(): void { diff --git a/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html b/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html index 1510a44a0..31239fb69 100644 --- a/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html +++ b/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html @@ -8,8 +8,8 @@
{{description.label}}
{{description.label}}
- {{ enumUtils.toDmpUserRolesString(dmpService.getCurrentUserRolesInDmp(description?.dmp?.dmpUsers)) }} - . + {{ enumUtils.toDmpUserRolesString(dmpService.getCurrentUserRolesInDmp(description?.dmp?.dmpUsers)) }} + . public{{'DESCRIPTION-LISTING.STATES.PUBLIC' | translate}} done{{ enumUtils.toDescriptionStatusString(description.status) }} create{{ enumUtils.toDescriptionStatusString(description.status) }} @@ -27,13 +27,13 @@ open_in_new{{'DESCRIPTION-LISTING.ACTIONS.EXPORT' | translate}} group_add{{'DESCRIPTION-LISTING.ACTIONS.INVITE-SHORT' | translate}} file_copy{{'DESCRIPTION-LISTING.ACTIONS.COPY-DESCRIPTION' | translate}} - delete{{ 'DESCRIPTION-LISTING.ACTIONS.DELETE' | translate }} + delete{{ 'DESCRIPTION-LISTING.ACTIONS.DELETE' | translate }}
- diff --git a/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts b/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts index b1f31a805..831b7767d 100644 --- a/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts +++ b/dmp-frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts @@ -26,6 +26,7 @@ import * as FileSaver from 'file-saver'; import { takeUntil } from 'rxjs/operators'; import { DescriptionStatus } from '../../../../core/common/enum/description-status'; import { DescriptionCopyDialogComponent } from '../../description-copy-dialog/description-copy-dialog.component'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; @Component({ selector: 'app-description-listing-item-component', @@ -44,6 +45,8 @@ export class DescriptionListingItemComponent extends BaseComponent implements On isUserOwner: boolean; descriptionStatusEnum = DescriptionStatus; dmpAccessTypeEnum = DmpAccessType; + canDelete: boolean = false; + canEdit: boolean = false; constructor( @@ -80,6 +83,13 @@ export class DescriptionListingItemComponent extends BaseComponent implements On this.isDeleted = false; this.setIsUserOwner(); } + + this.canDelete = this.authService.hasPermission(AppPermission.DeleteDescription) || + this.description.authorizationFlags?.some(x => x === AppPermission.DeleteDescription); + + this.canEdit = this.authService.hasPermission(AppPermission.EditDescription) || + this.description.authorizationFlags?.some(x => x === AppPermission.EditDescription); + } setIsUserOwner() { @@ -240,11 +250,4 @@ export class DescriptionListingItemComponent extends BaseComponent implements On onDeleteCallbackError(error) { this.uiNotificationService.snackBarNotification(error.error.message ? error.error.message : this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-DELETE'), SnackBarNotificationLevel.Error); } - - - - isUserDescriptionRelated(): boolean { - const principalId: Guid = this.authService.userId(); - return this.description.dmp.dmpUsers.some(x => (x.user.id === principalId)); - } } diff --git a/dmp-frontend/src/app/ui/description/overview/description-overview.component.html b/dmp-frontend/src/app/ui/description/overview/description-overview.component.html index cf5aa87b0..c24461d00 100644 --- a/dmp-frontend/src/app/ui/description/overview/description-overview.component.html +++ b/dmp-frontend/src/app/ui/description/overview/description-overview.component.html @@ -12,12 +12,12 @@

{{ description.label }}

-
+

{{ enumUtils.toDmpUserRolesString(dmpService.getCurrentUserRolesInDmp(description?.dmp?.dmpUsers)) }}

- . + .
public {{'DESCRIPTION-OVERVIEW.PUBLIC' | translate}} @@ -39,13 +39,13 @@
- - -
@@ -92,7 +92,7 @@
-
+
- +
-
+