status authz changes

This commit is contained in:
CITE\amentis 2024-10-01 13:13:29 +03:00
parent fcacf93024
commit 6ebe3228ae
33 changed files with 483 additions and 190 deletions

View File

@ -7,12 +7,16 @@ public class DescriptionStatusTouchedEvent {
public DescriptionStatusTouchedEvent() {
}
public DescriptionStatusTouchedEvent(UUID id) {
public DescriptionStatusTouchedEvent(UUID id, String tenantCode) {
this.id = id;
this.tenantCode = tenantCode;
}
private UUID id;
private String tenantCode;
public UUID getId() {
return id;
}
@ -21,4 +25,11 @@ public class DescriptionStatusTouchedEvent {
this.id = id;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
}

View File

@ -7,12 +7,15 @@ public class PlanStatusTouchedEvent {
public PlanStatusTouchedEvent() {
}
public PlanStatusTouchedEvent(UUID id) {
public PlanStatusTouchedEvent(UUID id, String tenantCode) {
this.id = id;
this.tenantCode = tenantCode;
}
private UUID id;
private String tenantCode;
public UUID getId() {
return id;
}
@ -21,4 +24,11 @@ public class PlanStatusTouchedEvent {
this.id = id;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
}

View File

@ -13,11 +13,13 @@ import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver;
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.description.PropertyDefinitionEntity;
import org.opencdmp.commons.types.descriptiontemplate.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.DescriptionEntity;
import org.opencdmp.data.DescriptionStatusEntity;
import org.opencdmp.data.DescriptionTemplateEntity;
import org.opencdmp.model.DescriptionTag;
import org.opencdmp.model.PlanDescriptionTemplate;
@ -34,8 +36,10 @@ import org.opencdmp.model.descriptionreference.DescriptionReference;
import org.opencdmp.model.descriptionstatus.DescriptionStatus;
import org.opencdmp.model.descriptiontemplate.DescriptionTemplate;
import org.opencdmp.model.plan.Plan;
import org.opencdmp.model.planstatus.PlanStatusDefinitionAuthorization;
import org.opencdmp.model.user.User;
import org.opencdmp.query.*;
import org.opencdmp.service.custompolicy.CustomPolicyService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -57,14 +61,15 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
private final AuthorizationService authorizationService;
private final AuthorizationContentResolver authorizationContentResolver;
private final TenantScope tenantScope;
private final CustomPolicyService customPolicyService;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public DescriptionBuilder(
ConventionService conventionService,
QueryFactory queryFactory,
BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope) {
ConventionService conventionService,
QueryFactory queryFactory,
BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, CustomPolicyService customPolicyService) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(DescriptionBuilder.class)));
this.queryFactory = queryFactory;
this.builderFactory = builderFactory;
@ -73,6 +78,7 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
this.authorizationService = authorizationService;
this.authorizationContentResolver = authorizationContentResolver;
this.tenantScope = tenantScope;
this.customPolicyService = customPolicyService;
}
public DescriptionBuilder authorize(EnumSet<AuthorizationFlags> values) {
@ -90,6 +96,9 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
FieldSet statusFields = fields.extractPrefixed(this.asPrefix(Description._status));
Map<UUID, DescriptionStatus> statusItemsMap = this.collectDescriptionStatuses(statusFields, data);
FieldSet availableStatusesFields = fields.extractPrefixed(this.asPrefix(Description._availableStatuses));
Map<UUID, List<DescriptionStatus>> avaialbleStatusesItemsMap = this.collectAvailableDescriptionStatuses(availableStatusesFields, data);
FieldSet planDescriptionTemplateFields = fields.extractPrefixed(this.asPrefix(Description._planDescriptionTemplate));
Map<UUID, PlanDescriptionTemplate> planDescriptionTemplateItemsMap = this.collectPlanDescriptionTemplates(planDescriptionTemplateFields, data);
@ -115,6 +124,7 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
Set<String> authorizationFlags = this.extractAuthorizationFlags(fields, Description._authorizationFlags, this.authorizationContentResolver.getPermissionNames());
Map<UUID, AffiliatedResource> affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.descriptionsAffiliation(data.stream().map(DescriptionEntity::getId).collect(Collectors.toList()));
FieldSet statusAuthorizationFlags = fields.extractPrefixed(this.asPrefix(Description._statusAuthorizationFlags));
List<Description> models = new ArrayList<>();
for (DescriptionEntity d : data) {
Description m = new Description();
@ -122,6 +132,7 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
if (fields.hasField(this.asIndexer(Description._tenantId))) m.setTenantId(d.getTenantId());
if (fields.hasField(this.asIndexer(Description._label))) m.setLabel(d.getLabel());
if (!statusFields.isEmpty() && statusItemsMap != null && statusItemsMap.containsKey(d.getStatusId())) m.setStatus(statusItemsMap.get(d.getStatusId()));
if (avaialbleStatusesItemsMap != null && !avaialbleStatusesItemsMap.isEmpty() && avaialbleStatusesItemsMap.containsKey(d.getId())) m.setAvailableStatuses(avaialbleStatusesItemsMap.get(d.getId()));
if (fields.hasField(this.asIndexer(Description._description))) m.setDescription(d.getDescription());
if (fields.hasField(this.asIndexer(Description._createdAt))) m.setCreatedAt(d.getCreatedAt());
if (fields.hasField(this.asIndexer(Description._updatedAt))) m.setUpdatedAt(d.getUpdatedAt());
@ -140,6 +151,9 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
m.setProperties(this.builderFactory.builder(PropertyDefinitionBuilder.class).withDefinition(definitionEntityMap != null ? definitionEntityMap.getOrDefault(d.getDescriptionTemplateId(), null) : null).authorize(this.authorize).build(definitionPropertiesFields, propertyDefinition));
}
if (affiliatedResourceMap != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null)));
if (!statusAuthorizationFlags.isEmpty() && !this.conventionService.isListNullOrEmpty(m.getAvailableStatuses())) {
m.setStatusAuthorizationFlags(this.evaluateStatusAuthorizationFlags(this.authorizationService, statusAuthorizationFlags, d));
}
models.add(m);
}
@ -178,6 +192,20 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
return itemMap;
}
private Map<UUID, List<DescriptionStatus>> collectAvailableDescriptionStatuses(FieldSet fields, List<DescriptionEntity> data) throws MyApplicationException {
if (fields.isEmpty() || data.isEmpty()) return null;
this.logger.debug("checking related - {}", DescriptionStatus.class.getSimpleName());
Map<UUID, List<DescriptionStatus>> itemMap = new HashMap<>();
FieldSet fieldSet = new BaseFieldSet(fields.getFields()).ensure(DescriptionStatus._id);
for (DescriptionEntity entity: data) {
List<DescriptionStatusEntity> statusEntities = this.queryFactory.query(DescriptionStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActive(IsActive.Active).authorizedStatus(true, entity.getId()).collectAs(fieldSet);
itemMap.put(entity.getId(), this.builderFactory.builder(DescriptionStatusBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, statusEntities));
}
return itemMap;
}
private Map<UUID, User> collectUsers(FieldSet fields, List<DescriptionEntity> data) throws MyApplicationException {
if (fields.isEmpty() || data.isEmpty())
return null;
@ -350,4 +378,24 @@ public class DescriptionBuilder extends BaseBuilder<Description, DescriptionEnti
return itemMap;
}
private List<String> evaluateStatusAuthorizationFlags(AuthorizationService authorizationService, FieldSet statusAuthorizationFlags, DescriptionEntity description) {
List<String> allowed = new ArrayList<>();
if (statusAuthorizationFlags == null) return allowed;
if (authorizationService == null) return allowed;
if (description == null) return allowed;
String editPermission = this.customPolicyService.getDescriptionStatusCanEditStatusPermission(description.getStatusId());
for (String permission : statusAuthorizationFlags.getFields()) {
if (statusAuthorizationFlags.hasField(this.asIndexer(PlanStatusDefinitionAuthorization._edit))) {
Boolean isAllowed = authorizationService.authorize(editPermission);
if (!isAllowed) {
isAllowed = this.authorizationService.authorizeAtLeastOne(List.of(this.authorizationContentResolver.planAffiliation(description.getPlanId())), editPermission);
}
if (isAllowed) allowed.add(permission);
}
}
return allowed;
}
}

View File

@ -14,12 +14,14 @@ import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationCont
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.EntityType;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.plan.PlanPropertiesEntity;
import org.opencdmp.commons.types.planblueprint.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.PlanBlueprintEntity;
import org.opencdmp.data.PlanEntity;
import org.opencdmp.data.PlanStatusEntity;
import org.opencdmp.model.PlanDescriptionTemplate;
import org.opencdmp.model.PlanUser;
import org.opencdmp.model.EntityDoi;
@ -33,8 +35,10 @@ import org.opencdmp.model.plan.Plan;
import org.opencdmp.model.planblueprint.PlanBlueprint;
import org.opencdmp.model.planreference.PlanReference;
import org.opencdmp.model.planstatus.PlanStatus;
import org.opencdmp.model.planstatus.PlanStatusDefinitionAuthorization;
import org.opencdmp.model.user.User;
import org.opencdmp.query.*;
import org.opencdmp.service.custompolicy.CustomPolicyService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -56,13 +60,14 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
private final AuthorizationService authorizationService;
private final AuthorizationContentResolver authorizationContentResolver;
private final TenantScope tenantScope;
private final CustomPolicyService customPolicyService;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public PlanBuilder(ConventionService conventionService,
QueryFactory queryFactory,
BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope) {
BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, CustomPolicyService customPolicyService) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(PlanBuilder.class)));
this.queryFactory = queryFactory;
this.builderFactory = builderFactory;
@ -71,6 +76,7 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
this.authorizationService = authorizationService;
this.authorizationContentResolver = authorizationContentResolver;
this.tenantScope = tenantScope;
this.customPolicyService = customPolicyService;
}
public PlanBuilder authorize(EnumSet<AuthorizationFlags> values) {
@ -90,6 +96,9 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
FieldSet statusFields = fields.extractPrefixed(this.asPrefix(Plan._status));
Map<UUID, PlanStatus> statusItemsMap = this.collectPlanStatuses(statusFields, data);
FieldSet availableStatusesFields = fields.extractPrefixed(this.asPrefix(Plan._availableStatuses));
Map<UUID, List<PlanStatus>> avaialbleStatusesItemsMap = this.collectAvailablePlanStatuses(availableStatusesFields, data);
FieldSet entityDoisFields = fields.extractPrefixed(this.asPrefix(Plan._entityDois));
Map<UUID, List<EntityDoi>> entityDoisMap = this.collectEntityDois(entityDoisFields, data);
@ -119,7 +128,8 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
Set<String> authorizationFlags = this.extractAuthorizationFlags(fields, Plan._authorizationFlags, this.authorizationContentResolver.getPermissionNames());
Map<UUID, AffiliatedResource> affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.plansAffiliation(data.stream().map(PlanEntity::getId).collect(Collectors.toList()));
FieldSet statusAuthorizationFlags = fields.extractPrefixed(this.asPrefix(Plan._statusAuthorizationFlags));
for (PlanEntity d : data) {
Plan m = new Plan();
if (fields.hasField(this.asIndexer(Plan._id))) m.setId(d.getId());
@ -127,6 +137,7 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
if (fields.hasField(this.asIndexer(Plan._label))) m.setLabel(d.getLabel());
if (fields.hasField(this.asIndexer(Plan._version))) m.setVersion(d.getVersion());
if (!statusFields.isEmpty() && statusItemsMap != null && statusItemsMap.containsKey(d.getStatusId())) m.setStatus(statusItemsMap.get(d.getStatusId()));
if (avaialbleStatusesItemsMap != null && !avaialbleStatusesItemsMap.isEmpty() && avaialbleStatusesItemsMap.containsKey(d.getId())) m.setAvailableStatuses(avaialbleStatusesItemsMap.get(d.getId()));
if (fields.hasField(this.asIndexer(Plan._groupId))) m.setGroupId(d.getGroupId());
if (fields.hasField(this.asIndexer(Plan._description))) m.setDescription(d.getDescription());
if (fields.hasField(this.asIndexer(Plan._createdAt))) m.setCreatedAt(d.getCreatedAt());
@ -155,6 +166,9 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
m.setProperties(this.builderFactory.builder(PlanPropertiesBuilder.class).withDefinition(definitionEntityMap != null ? definitionEntityMap.getOrDefault(d.getBlueprintId(), null) : null).authorize(this.authorize).build(planPropertiesFields, propertyDefinition));
}
if (affiliatedResourceMap != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null)));
if (!statusAuthorizationFlags.isEmpty() && !this.conventionService.isListNullOrEmpty(m.getAvailableStatuses())) {
m.setStatusAuthorizationFlags(this.evaluateStatusAuthorizationFlags(this.authorizationService, statusAuthorizationFlags, d));
}
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
@ -192,6 +206,20 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
return itemMap;
}
private Map<UUID, List<PlanStatus>> collectAvailablePlanStatuses(FieldSet fields, List<PlanEntity> data) throws MyApplicationException {
if (fields.isEmpty() || data.isEmpty()) return null;
this.logger.debug("checking related - {}", PlanStatus.class.getSimpleName());
Map<UUID, List<PlanStatus>> itemMap = new HashMap<>();
FieldSet fieldSet = new BaseFieldSet(fields.getFields()).ensure(PlanStatus._id);
for (PlanEntity entity: data) {
List<PlanStatusEntity> statusEntities = this.queryFactory.query(PlanStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActives(IsActive.Active).authorizedStatus(true, entity.getId()).collectAs(fieldSet);
itemMap.put(entity.getId(), this.builderFactory.builder(PlanStatusBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, statusEntities));
}
return itemMap;
}
private Map<UUID, List<PlanReference>> collectPlanReferences(FieldSet fields, List<PlanEntity> data) throws MyApplicationException {
if (fields.isEmpty() || data.isEmpty()) return null;
this.logger.debug("checking related - {}", PlanReference.class.getSimpleName());
@ -377,4 +405,24 @@ public class PlanBuilder extends BaseBuilder<Plan, PlanEntity> {
return itemMap;
}
private List<String> evaluateStatusAuthorizationFlags(AuthorizationService authorizationService, FieldSet statusAuthorizationFlags, PlanEntity plan) {
List<String> allowed = new ArrayList<>();
if (statusAuthorizationFlags == null) return allowed;
if (authorizationService == null) return allowed;
if (plan == null) return allowed;
String editPermission = this.customPolicyService.getPlanStatusCanEditStatusPermission(plan.getStatusId());
for (String permission : statusAuthorizationFlags.getFields()) {
if (statusAuthorizationFlags.hasField(this.asIndexer(PlanStatusDefinitionAuthorization._edit))) {
Boolean isAllowed = authorizationService.authorize(editPermission);
if (!isAllowed) {
isAllowed = this.authorizationService.authorizeAtLeastOne(List.of(this.authorizationContentResolver.planAffiliation(plan.getId())), editPermission);
}
if (isAllowed) allowed.add(permission);
}
}
return allowed;
}
}

View File

@ -86,6 +86,12 @@ public class Description {
public static final String _plan = "plan";
private List<DescriptionStatus> availableStatuses;
public static final String _availableStatuses = "availableStatuses";
private List<String> statusAuthorizationFlags;
public static final String _statusAuthorizationFlags = "statusAuthorizationFlags";
private Boolean belongsToCurrentTenant;
public static final String _belongsToCurrentTenant = "belongsToCurrentTenant";
@ -231,6 +237,22 @@ public class Description {
this.authorizationFlags = authorizationFlags;
}
public List<DescriptionStatus> getAvailableStatuses() {
return availableStatuses;
}
public void setAvailableStatuses(List<DescriptionStatus> availableStatuses) {
this.availableStatuses = availableStatuses;
}
public List<String> getStatusAuthorizationFlags() {
return statusAuthorizationFlags;
}
public void setStatusAuthorizationFlags(List<String> statusAuthorizationFlags) {
this.statusAuthorizationFlags = statusAuthorizationFlags;
}
public Boolean getBelongsToCurrentTenant() {
return belongsToCurrentTenant;
}

View File

@ -97,8 +97,11 @@ public class Plan {
private List<Plan> otherPlanVersions;
public static final String _otherPlanVersions = "otherPlanVersions";
private List<PlanStatus> availableTransitions;
public static final String _availableTransitions = "availableTransitions";
private List<PlanStatus> availableStatuses;
public static final String _availableStatuses = "availableStatuses";
private List<String> statusAuthorizationFlags;
public static final String _statusAuthorizationFlags = "statusAuthorizationFlags";
private Boolean belongsToCurrentTenant;
public static final String _belongsToCurrentTenant = "belongsToCurrentTenant";
@ -321,11 +324,19 @@ public class Plan {
this.otherPlanVersions = otherPlanVersions;
}
public List<PlanStatus> getAvailableTransitions() {
return availableTransitions;
public List<PlanStatus> getAvailableStatuses() {
return availableStatuses;
}
public void setAvailableTransitions(List<PlanStatus> availableTransitions) {
this.availableTransitions = availableTransitions;
public void setAvailableStatuses(List<PlanStatus> availableStatuses) {
this.availableStatuses = availableStatuses;
}
public List<String> getStatusAuthorizationFlags() {
return statusAuthorizationFlags;
}
public void setStatusAuthorizationFlags(List<String> statusAuthorizationFlags) {
this.statusAuthorizationFlags = statusAuthorizationFlags;
}
}

View File

@ -13,6 +13,7 @@ import org.opencdmp.data.DescriptionStatusEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.model.descriptionstatus.DescriptionStatus;
import org.opencdmp.query.utils.QueryUtilsService;
import org.opencdmp.service.descriptionstatus.DescriptionStatusService;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@ -34,8 +35,14 @@ public class DescriptionStatusQuery extends QueryBase<DescriptionStatusEntity> {
private Collection<UUID> excludeIds;
private Boolean authorizedStatus;
private UUID descriptionId;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
private final DescriptionStatusService descriptionStatusService;
public DescriptionStatusQuery like(String value) {
this.like = value;
return this;
@ -111,6 +118,12 @@ public class DescriptionStatusQuery extends QueryBase<DescriptionStatusEntity> {
return this;
}
public DescriptionStatusQuery authorizedStatus(Boolean authorizedStatus, UUID descriptionId) {
this.authorizedStatus = authorizedStatus;
this.descriptionId = descriptionId;
return this;
}
public DescriptionStatusQuery authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
@ -120,7 +133,8 @@ public class DescriptionStatusQuery extends QueryBase<DescriptionStatusEntity> {
private final TenantEntityManager entityManager;
public DescriptionStatusQuery(
QueryUtilsService queryUtilsService, TenantEntityManager entityManager) {
DescriptionStatusService descriptionStatusService, QueryUtilsService queryUtilsService, TenantEntityManager entityManager) {
this.descriptionStatusService = descriptionStatusService;
this.queryUtilsService = queryUtilsService;
this.entityManager = entityManager;
}
@ -172,6 +186,15 @@ public class DescriptionStatusQuery extends QueryBase<DescriptionStatusEntity> {
}
predicates.add(notInClause.not());
}
if (this.authorizedStatus != null && this.authorizedStatus && this.descriptionId != null) {
List<UUID> notAvailableStatusIds = this.descriptionStatusService.getAuthorizedNotAvailableStatusIds(this.descriptionId);
if (!notAvailableStatusIds.isEmpty()) {
CriteriaBuilder.In<UUID> notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(DescriptionStatusEntity._id));
for (UUID id : notAvailableStatusIds)
notInClause.value(id);
predicates.add(notInClause.not());
}
}
if (!predicates.isEmpty()) {
Predicate[] predicatesArray = predicates.toArray(new Predicate[0]);
return queryContext.CriteriaBuilder.and(predicatesArray);

View File

@ -13,6 +13,7 @@ import org.opencdmp.data.PlanStatusEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.model.planstatus.PlanStatus;
import org.opencdmp.query.utils.QueryUtilsService;
import org.opencdmp.service.planstatus.PlanStatusService;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@ -34,8 +35,14 @@ public class PlanStatusQuery extends QueryBase<PlanStatusEntity> {
private Collection<UUID> excludedIds;
private Boolean authorizedStatus;
private UUID planId;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
private final PlanStatusService planStatusService;
public PlanStatusQuery like(String value) {
this.like = value;
return this;
@ -116,11 +123,18 @@ public class PlanStatusQuery extends QueryBase<PlanStatusEntity> {
return this;
}
public PlanStatusQuery authorizedStatus(Boolean authorizedStatus, UUID planId) {
this.authorizedStatus = authorizedStatus;
this.planId = planId;
return this;
}
private final QueryUtilsService queryUtilsService;
private final TenantEntityManager entityManager;
public PlanStatusQuery(
QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) {
PlanStatusService planStatusService, QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) {
this.planStatusService = planStatusService;
this.queryUtilsService = queryUtilsService;
this.entityManager = tenantEntityManager;
}
@ -171,6 +185,15 @@ public class PlanStatusQuery extends QueryBase<PlanStatusEntity> {
notInClause.value(item);
predicates.add(notInClause.not());
}
if (this.authorizedStatus != null && this.authorizedStatus && this.planId != null) {
List<UUID> notAvailableStatusIds = this.planStatusService.getAuthorizedNotAvailableStatusIds(this.planId);
if (!notAvailableStatusIds.isEmpty()) {
CriteriaBuilder.In<UUID> notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(PlanStatusEntity._id));
for (UUID id : notAvailableStatusIds)
notInClause.value(id);
predicates.add(notInClause.not());
}
}
if (!predicates.isEmpty()) {
Predicate[] predicatesArray = predicates.toArray(new Predicate[0]);
return queryContext.CriteriaBuilder.and(predicatesArray);

View File

@ -4,7 +4,10 @@ package org.opencdmp.service.custompolicy;
import gr.cite.tools.cache.CacheService;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity;
import org.opencdmp.event.DescriptionStatusTouchedEvent;
import org.opencdmp.event.PlanStatusTouchedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.HashMap;
@ -54,6 +57,16 @@ public class CustomPolicyCacheService extends CacheService<CustomPolicyCacheServ
}
}
@EventListener
public void handlePlanTouchedEvent(PlanStatusTouchedEvent event) {
this.evict(this.buildKey(event.getTenantCode()));
}
@EventListener
public void handleDescriptionStatusTouchedEvent(DescriptionStatusTouchedEvent event) {
this.evict(this.buildKey(event.getTenantCode()));
}
@Autowired
public CustomPolicyCacheService(CustomPolicyCacheOptions options) {
super(options);

View File

@ -2,7 +2,6 @@ package org.opencdmp.service.custompolicy;
import gr.cite.commons.web.authz.configuration.Permission;
import org.opencdmp.commons.enums.PlanUserRole;
import java.util.HashMap;
import java.util.UUID;
@ -17,7 +16,4 @@ public interface CustomPolicyService {
String getDescriptionStatusCanEditStatusPermission(UUID id);
String getPlanStatusCanEditStatusAffiliatedPermission(UUID id, PlanUserRole planUserRole);
String getDescriptionStatusCanEditStatusAffiliatedPermission(UUID id, PlanUserRole planUserRole);
}

View File

@ -6,7 +6,6 @@ import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.PlanUserRole;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity;
@ -44,7 +43,7 @@ public class CustomPolicyServiceImpl implements CustomPolicyService{
HashMap<String, Permission> policies = new HashMap<>();
String tenantCode = null;
try {
tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode();
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
@ -84,7 +83,7 @@ public class CustomPolicyServiceImpl implements CustomPolicyService{
HashMap<String, Permission> policies = new HashMap<>();
String tenantCode = null;
try {
tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode();
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
@ -121,21 +120,11 @@ public class CustomPolicyServiceImpl implements CustomPolicyService{
@Override
public String getPlanStatusCanEditStatusPermission(UUID id){
return "PlanStatus" + "_" + id + "_" + PlanStatusDefinitionAuthorization._edit;
return ("PlanStatus" + "_" + id + "_" + PlanStatusDefinitionAuthorization._edit).toLowerCase();
}
@Override
public String getDescriptionStatusCanEditStatusPermission(UUID id){
return "DescriptionStatus" + "_" + id + "_" + DescriptionStatusDefinitionAuthorization._edit;
}
@Override
public String getPlanStatusCanEditStatusAffiliatedPermission(UUID id, PlanUserRole planUserRole){
return "PlanStatus" + "_" + id + "_" + planUserRole.name() + "_" + PlanStatusDefinitionAuthorization._edit;
}
@Override
public String getDescriptionStatusCanEditStatusAffiliatedPermission(UUID id, PlanUserRole planUserRole){
return "DescriptionStatus" + "_" + id + "_" + planUserRole.name() + "_" + DescriptionStatusDefinitionAuthorization._edit;
return ("DescriptionStatus" + "_" + id + "_" + DescriptionStatusDefinitionAuthorization._edit).toLowerCase();
}
}

View File

@ -508,8 +508,7 @@ public class DescriptionServiceImpl implements DescriptionService {
try {
this.authorizationService.authorizeForce(this.customPolicyService.getDescriptionStatusCanEditStatusPermission(model.getStatusId()));
} catch (Exception e) {
PlanUserEntity planUserEntity = this.queryFactory.query(PlanUserQuery.class).planIds(data.getPlanId()).userIds(this.userScope.getUserId()).isActives(IsActive.Active).firstAs(new BaseFieldSet().ensure(PlanUser._role));
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(data.getPlanId())), this.customPolicyService.getDescriptionStatusCanEditStatusAffiliatedPermission(model.getStatusId(), planUserEntity.getRole()));
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(data.getPlanId())), this.customPolicyService.getDescriptionStatusCanEditStatusPermission(model.getStatusId()));
}
if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage());

View File

@ -19,5 +19,5 @@ public interface DescriptionStatusService {
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;
List<DescriptionStatus> getAvailableTransitionStatuses(UUID descriptionId) throws InvalidApplicationException;
List<UUID> getAuthorizedNotAvailableStatusIds(UUID descriptionId);
}

View File

@ -15,8 +15,10 @@ import gr.cite.tools.logging.MapLogEntry;
import jakarta.xml.bind.JAXBException;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionAuthorizationEntity;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionAuthorizationItemEntity;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity;
@ -25,19 +27,20 @@ import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinit
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.DescriptionEntity;
import org.opencdmp.data.DescriptionStatusEntity;
import org.opencdmp.data.PlanStatusEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.event.DescriptionStatusTouchedEvent;
import org.opencdmp.event.EventBroker;
import org.opencdmp.event.PlanStatusTouchedEvent;
import org.opencdmp.model.builder.descriptionstatus.DescriptionStatusBuilder;
import org.opencdmp.model.deleter.DescriptionStatusDeleter;
import org.opencdmp.model.description.Description;
import org.opencdmp.model.descriptionstatus.DescriptionStatus;
import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionAuthorizationItemPersist;
import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionAuthorizationPersist;
import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionPersist;
import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusPersist;
import org.opencdmp.query.DescriptionStatusQuery;
import org.opencdmp.query.PlanStatusQuery;
import org.opencdmp.service.custompolicy.CustomPolicyService;
import org.opencdmp.service.descriptionworkflow.DescriptionWorkflowService;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
@ -64,11 +67,15 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService {
private final ConventionService conventionService;
private final MessageSource messageSource;
private final XmlHandlingService xmlHandlingService;
private final QueryFactory queryFactory;
private final DescriptionWorkflowService descriptionWorkflowService;
private final EventBroker eventBroker;
private final TenantScope tenantScope;
private final DescriptionWorkflowService descriptionWorkflowService;
private final CustomPolicyService customPolicyService;
private final AuthorizationService authorizationService;
private final AuthorizationContentResolver authorizationContentResolver;
private final QueryFactory queryFactory;
public DescriptionStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authService, TenantEntityManager entityManager, ConventionService conventionService, MessageSource messageSource, XmlHandlingService xmlHandlingService, QueryFactory queryFactory, DescriptionWorkflowService descriptionWorkflowService, EventBroker eventBroker) {
public DescriptionStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authService, TenantEntityManager entityManager, ConventionService conventionService, MessageSource messageSource, XmlHandlingService xmlHandlingService, EventBroker eventBroker, TenantScope tenantScope, DescriptionWorkflowService descriptionWorkflowService, CustomPolicyService customPolicyService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, QueryFactory queryFactory) {
this.builderFactory = builderFactory;
this.deleterFactory = deleterFactory;
@ -77,9 +84,13 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService {
this.conventionService = conventionService;
this.messageSource = messageSource;
this.xmlHandlingService = xmlHandlingService;
this.queryFactory = queryFactory;
this.descriptionWorkflowService = descriptionWorkflowService;
this.eventBroker = eventBroker;
this.tenantScope = tenantScope;
this.descriptionWorkflowService = descriptionWorkflowService;
this.customPolicyService = customPolicyService;
this.authorizationService = authorizationService;
this.authorizationContentResolver = authorizationContentResolver;
this.queryFactory = queryFactory;
}
@ -117,7 +128,7 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService {
this.entityManager.flush();
this.eventBroker.emit(new DescriptionStatusTouchedEvent(data.getId()));
this.eventBroker.emit(new DescriptionStatusTouchedEvent(data.getId(), this.tenantScope.getTenantCode()));
return this.builderFactory.builder(DescriptionStatusBuilder.class).build(BaseFieldSet.build(fields, DescriptionStatus._id), data);
}
@ -164,19 +175,44 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService {
return data;
}
public List<DescriptionStatus> getAvailableTransitionStatuses(UUID descriptionId) throws InvalidApplicationException {
DescriptionWorkflowDefinitionEntity definition = this.descriptionWorkflowService.getWorkFlowDefinition();
@Override
public List<UUID> getAuthorizedNotAvailableStatusIds(UUID descriptionId) {
DescriptionEntity description = this.entityManager.find(DescriptionEntity.class, descriptionId);
if (description == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale()));
List<DescriptionWorkflowDefinitionTransitionEntity> availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(description.getStatusId())).collect(Collectors.toList());
if (!this.conventionService.isListNullOrEmpty(availableTransitions)){
DescriptionStatusQuery query = this.queryFactory.query(DescriptionStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActive(IsActive.Active).ids(availableTransitions.stream().map(DescriptionWorkflowDefinitionTransitionEntity::getToStatusId).distinct().toList());
FieldSet fieldSet = new BaseFieldSet().ensure(DescriptionStatus._id).ensure(DescriptionStatus._name).ensure(DescriptionStatus._action).ensure(DescriptionStatus._internalStatus);
return this.builderFactory.builder(DescriptionStatusBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, query.collectAs(fieldSet));
List<UUID> notAuthorizedStatusIds = new ArrayList<>();
DescriptionWorkflowDefinitionEntity definition;
DescriptionEntity description;
try {
definition = this.descriptionWorkflowService.getWorkFlowDefinition();
description = this.entityManager.find(DescriptionEntity.class, descriptionId, true);
if (description == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, DescriptionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
notAuthorizedStatusIds.add(description.getStatusId());
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
return new ArrayList<>();
List<DescriptionWorkflowDefinitionTransitionEntity> availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(description.getStatusId())).collect(Collectors.toList());
if (!this.conventionService.isListNullOrEmpty(availableTransitions)) {
List<UUID> availableStatusIds = availableTransitions.stream().map(DescriptionWorkflowDefinitionTransitionEntity::getToStatusId).collect(Collectors.toList());
for (UUID statusId: availableStatusIds) {
// add status id with no permission
String editPermission = this.customPolicyService.getDescriptionStatusCanEditStatusPermission(statusId);
Boolean isAllowed = this.authorizationService.authorize(editPermission);
if (!isAllowed) {
isAllowed = this.authorizationService.authorizeAtLeastOne(List.of(this.authorizationContentResolver.planAffiliation(description.getPlanId())), editPermission);
}
if (!isAllowed) notAuthorizedStatusIds.add(statusId);
}
// add status ids that not included in workflow
List<DescriptionStatusEntity> statusEntities = this.queryFactory.query(DescriptionStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActive(IsActive.Active).excludeIds(notAuthorizedStatusIds).collectAs(new BaseFieldSet().ensure(DescriptionStatus._id));
if (!this.conventionService.isListNullOrEmpty(statusEntities)) {
for (DescriptionStatusEntity status: statusEntities) {
if (!availableStatusIds.contains(status.getId())) notAuthorizedStatusIds.add(status.getId());
}
}
}
return notAuthorizedStatusIds;
}
}

View File

@ -1617,8 +1617,7 @@ public class PlanServiceImpl implements PlanService {
try {
this.authorizationService.authorizeForce(this.customPolicyService.getPlanStatusCanEditStatusPermission(newStatusId));
} catch (Exception e) {
PlanUserEntity planUserEntity = this.queryFactory.query(PlanUserQuery.class).planIds(id).userIds(this.userScope.getUserId()).isActives(IsActive.Active).firstAs(new BaseFieldSet().ensure(PlanUser._role));
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(id)), this.customPolicyService.getPlanStatusCanEditStatusAffiliatedPermission(newStatusId, planUserEntity.getRole()));
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(id)), this.customPolicyService.getPlanStatusCanEditStatusPermission(newStatusId));
}
if (plan.getStatusId().equals(newStatusId)) throw new MyApplicationException("Old status equals with new");

View File

@ -1,6 +1,5 @@
package org.opencdmp.service.planstatus;
import gr.cite.commons.web.authz.configuration.Permission;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
@ -11,7 +10,6 @@ import org.opencdmp.model.persist.planstatus.PlanStatusPersist;
import org.opencdmp.model.planstatus.PlanStatus;
import javax.management.InvalidApplicationException;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@ -20,5 +18,5 @@ public interface PlanStatusService {
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;
List<PlanStatus> getAvailableTransitionStatuses(UUID planId) throws InvalidApplicationException;
List<UUID> getAuthorizedNotAvailableStatusIds(UUID planId);
}

View File

@ -1,6 +1,5 @@
package org.opencdmp.service.planstatus;
import gr.cite.commons.web.authz.configuration.PermissionPolicyContext;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -17,8 +16,10 @@ import jakarta.xml.bind.JAXBException;
import org.jetbrains.annotations.NotNull;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionAuthorizationEntity;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionAuthorizationItemEntity;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity;
@ -31,7 +32,6 @@ import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.opencdmp.event.EventBroker;
import org.opencdmp.event.PlanStatusTouchedEvent;
import org.opencdmp.event.PlanTouchedEvent;
import org.opencdmp.model.builder.planstatus.PlanStatusBuilder;
import org.opencdmp.model.deleter.PlanStatusDeleter;
import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionAuthorizationItemPersist;
@ -39,8 +39,8 @@ import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionAuthorizationPe
import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionPersist;
import org.opencdmp.model.persist.planstatus.PlanStatusPersist;
import org.opencdmp.model.planstatus.PlanStatus;
import org.opencdmp.model.planstatus.PlanStatusDefinitionAuthorization;
import org.opencdmp.query.PlanStatusQuery;
import org.opencdmp.service.custompolicy.CustomPolicyService;
import org.opencdmp.service.planworkflow.PlanWorkflowService;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
@ -66,10 +66,14 @@ public class PlanStatusServiceImpl implements PlanStatusService {
private final TenantEntityManager entityManager;
private final MessageSource messageSource;
private final ErrorThesaurusProperties errors;
private final QueryFactory queryFactory;
private final PlanWorkflowService planWorkflowService;
private final EventBroker eventBroker;
public PlanStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors, EventBroker eventBroker, QueryFactory queryFactory, PlanWorkflowService planWorkflowService) {
private final TenantScope tenantScope;
private final PlanWorkflowService planWorkflowService;
private final CustomPolicyService customPolicyService;
private final AuthorizationContentResolver authorizationContentResolver;
private final QueryFactory queryFactory;
public PlanStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors, EventBroker eventBroker, TenantScope tenantScope, PlanWorkflowService planWorkflowService, CustomPolicyService customPolicyService, AuthorizationContentResolver authorizationContentResolver, QueryFactory queryFactory) {
this.builderFactory = builderFactory;
this.deleterFactory = deleterFactory;
@ -79,9 +83,12 @@ public class PlanStatusServiceImpl implements PlanStatusService {
this.entityManager = entityManager;
this.messageSource = messageSource;
this.errors = errors;
this.queryFactory = queryFactory;
this.planWorkflowService = planWorkflowService;
this.eventBroker = eventBroker;
this.tenantScope = tenantScope;
this.planWorkflowService = planWorkflowService;
this.customPolicyService = customPolicyService;
this.authorizationContentResolver = authorizationContentResolver;
this.queryFactory = queryFactory;
}
@Override
@ -118,7 +125,7 @@ public class PlanStatusServiceImpl implements PlanStatusService {
this.entityManager.flush();
this.eventBroker.emit(new PlanStatusTouchedEvent(data.getId()));
this.eventBroker.emit(new PlanStatusTouchedEvent(data.getId(), this.tenantScope.getTenantCode()));
return this.builderFactory.builder(PlanStatusBuilder.class).build(BaseFieldSet.build(fields, PlanStatus._id), data);
}
@ -171,19 +178,44 @@ public class PlanStatusServiceImpl implements PlanStatusService {
return data;
}
public List<PlanStatus> getAvailableTransitionStatuses(UUID planId) throws InvalidApplicationException {
PlanWorkflowDefinitionEntity definition = this.planWorkflowService.getWorkFlowDefinition();
@Override
public List<UUID> getAuthorizedNotAvailableStatusIds(UUID planId) {
PlanEntity plan = this.entityManager.find(PlanEntity.class, planId);
if (plan == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{planId, PlanEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
List<PlanWorkflowDefinitionTransitionEntity> availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(plan.getStatusId())).collect(Collectors.toList());
if (!this.conventionService.isListNullOrEmpty(availableTransitions)){
PlanStatusQuery query = this.queryFactory.query(PlanStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActives(IsActive.Active).ids(availableTransitions.stream().map(PlanWorkflowDefinitionTransitionEntity::getToStatusId).distinct().toList());
FieldSet fieldSet = new BaseFieldSet().ensure(PlanStatus._id).ensure(PlanStatus._name).ensure(PlanStatus._action).ensure(PlanStatus._internalStatus);
return this.builderFactory.builder(PlanStatusBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, query.collectAs(fieldSet));
List<UUID> notAuthorizedStatusIds = new ArrayList<>();
PlanWorkflowDefinitionEntity definition;
PlanEntity plan;
try {
definition = this.planWorkflowService.getWorkFlowDefinition();
plan = this.entityManager.find(PlanEntity.class, planId, true);
if (plan == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{planId, PlanEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
notAuthorizedStatusIds.add(plan.getStatusId());
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
return new ArrayList<>();
List<PlanWorkflowDefinitionTransitionEntity> availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(plan.getStatusId())).collect(Collectors.toList());
if (!this.conventionService.isListNullOrEmpty(availableTransitions)) {
List<UUID> availableStatusIds = availableTransitions.stream().map(PlanWorkflowDefinitionTransitionEntity::getToStatusId).collect(Collectors.toList());
for (UUID statusId: availableStatusIds) {
// add status id with no permission
String editPermission = this.customPolicyService.getPlanStatusCanEditStatusPermission(statusId);
Boolean isAllowed = this.authorizationService.authorize(editPermission);
if (!isAllowed) {
isAllowed = this.authorizationService.authorizeAtLeastOne(List.of(this.authorizationContentResolver.planAffiliation(plan.getId())), editPermission);
}
if (!isAllowed) notAuthorizedStatusIds.add(statusId);
}
// add status ids that not included in workflow
List<PlanStatusEntity> statusEntities = this.queryFactory.query(PlanStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActives(IsActive.Active).excludedIds(notAuthorizedStatusIds).collectAs(new BaseFieldSet().ensure(PlanStatus._id));
if (!this.conventionService.isListNullOrEmpty(statusEntities)) {
for (PlanStatusEntity status: statusEntities) {
if (!availableStatusIds.contains(status.getId())) notAuthorizedStatusIds.add(status.getId());
}
}
}
return notAuthorizedStatusIds;
}
}

View File

@ -50,7 +50,7 @@ public class AffiliatedAuthorizationHandler extends AuthorizationHandler<Affilia
boolean hasDescriptionTemplatePermission = policy != null && this.hasPermission(policy.getDescriptionTemplate(), userDescriptionTemplateRoles);
boolean hasPlanCustomPermission = false;
if (permission.startsWith("PlanStatus_") || permission.startsWith("DescriptionStatus_")) {
if (permission.startsWith(("PlanStatus_").toLowerCase()) || permission.startsWith(("DescriptionStatus_").toLowerCase())) {
HashMap<String, CustomPermissionAttributesProperties. MyPermission> customPolicies = this.opencdmpPermissionPolicyContext.buildAffiliatedCustomPermissions();
if (customPolicies == null || customPolicies.isEmpty()) return ACCESS_DENIED;
CustomPermissionAttributesProperties.MyPermission customPolicy = customPolicies.get(permission);

View File

@ -9,7 +9,7 @@ import org.opencdmp.authorization.CustomPermissionAttributesProperties;
import org.opencdmp.authorization.PlanRole;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.PlanUserRole;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity;
import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity;
import org.opencdmp.data.DescriptionStatusEntity;
@ -20,11 +20,13 @@ import org.opencdmp.model.descriptionstatus.DescriptionStatus;
import org.opencdmp.model.planstatus.PlanStatus;
import org.opencdmp.query.DescriptionStatusQuery;
import org.opencdmp.query.PlanStatusQuery;
import org.opencdmp.service.custompolicy.CustomPolicyCacheService;
import org.opencdmp.service.custompolicy.CustomPolicyService;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import javax.management.InvalidApplicationException;
import java.util.*;
@ -33,13 +35,17 @@ public class OpencdmpPermissionPolicyContextImpl extends PermissionPolicyContext
private final CustomPolicyService customPolicyService;
private final QueryFactory queryFactory;
private final XmlHandlingService xmlHandlingService;
private final CustomPolicyCacheService customPolicyCacheService;
private final TenantScope tenantScope;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OpencdmpPermissionPolicyContextImpl.class));
public OpencdmpPermissionPolicyContextImpl(AuthorizationConfiguration authorizationConfiguration, CustomPolicyService customPolicyService, QueryFactory queryFactory, XmlHandlingService xmlHandlingService) {
public OpencdmpPermissionPolicyContextImpl(AuthorizationConfiguration authorizationConfiguration, CustomPolicyService customPolicyService, QueryFactory queryFactory, XmlHandlingService xmlHandlingService, CustomPolicyCacheService customPolicyCacheService, TenantScope tenantScope) {
super(authorizationConfiguration);
this.customPolicyService = customPolicyService;
this.queryFactory = queryFactory;
this.xmlHandlingService = xmlHandlingService;
this.customPolicyCacheService = customPolicyCacheService;
this.tenantScope = tenantScope;
}
@EventListener
@ -65,34 +71,77 @@ public class OpencdmpPermissionPolicyContextImpl extends PermissionPolicyContext
public HashMap<String, CustomPermissionAttributesProperties.MyPermission> buildAffiliatedCustomPermissions() {
HashMap<String, CustomPermissionAttributesProperties.MyPermission> affiliatedCustomPermissions = new HashMap<>();
List<PlanStatusEntity> planStatusEntities = this.queryFactory.query(PlanStatusQuery.class).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanStatus._id).ensure(PlanStatus._definition));
if (planStatusEntities != null) {
for (PlanStatusEntity entity : planStatusEntities) {
PlanStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(PlanStatusDefinitionEntity.class, entity.getDefinition());
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null && definition.getAuthorization().getEdit().getPlanRoles() != null){
for (PlanUserRole planUserRole: definition.getAuthorization().getEdit().getPlanRoles()) {
PlanRole planRole = new PlanRole(new HashSet<>(List.of(planUserRole)));
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(planRole, null);
affiliatedCustomPermissions.put(this.customPolicyService.getPlanStatusCanEditStatusAffiliatedPermission(entity.getId(), planUserRole), myPermission);
}
}
}
}
List<DescriptionStatusEntity> descriptionStatusEntities = this.queryFactory.query(DescriptionStatusQuery.class).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(DescriptionStatus._id).ensure(DescriptionStatus._definition));
if (descriptionStatusEntities != null) {
for (DescriptionStatusEntity entity : descriptionStatusEntities) {
DescriptionStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DescriptionStatusDefinitionEntity.class, entity.getDefinition());
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null && definition.getAuthorization().getEdit().getPlanRoles() != null){
for (PlanUserRole planUserRole: definition.getAuthorization().getEdit().getPlanRoles()) {
PlanRole planRole = new PlanRole(new HashSet<>(List.of(planUserRole)));
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(planRole, null);
affiliatedCustomPermissions.put(this.customPolicyService.getDescriptionStatusCanEditStatusAffiliatedPermission(entity.getId(), planUserRole), myPermission);
}
}
}
String tenantCode = null;
try {
tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode();
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
CustomPolicyCacheService.CustomPolicyCacheValue cacheValue = this.customPolicyCacheService.lookup(this.customPolicyCacheService.buildKey(tenantCode));
this.buildAffiliatedPlanCustomPermissions(tenantCode, cacheValue, affiliatedCustomPermissions);
this.buildAffiliatedDescriptionCustomPermissions(tenantCode, cacheValue, affiliatedCustomPermissions);
return affiliatedCustomPermissions;
}
private void buildAffiliatedPlanCustomPermissions(String tenantCode, CustomPolicyCacheService.CustomPolicyCacheValue cacheValue, HashMap<String, CustomPermissionAttributesProperties.MyPermission> affiliatedCustomPermissions) {
if (cacheValue == null || cacheValue.getPlanStatusDefinitionMap() == null) {
Map<UUID, PlanStatusDefinitionEntity> definitionStatusMap = new HashMap<>();
List<PlanStatusEntity> planStatusEntities = this.queryFactory.query(PlanStatusQuery.class).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanStatus._id).ensure(PlanStatus._definition));
if (planStatusEntities != null) {
for (PlanStatusEntity entity : planStatusEntities) {
PlanStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(PlanStatusDefinitionEntity.class, entity.getDefinition());
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null && definition.getAuthorization().getEdit().getPlanRoles() != null){
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null);
affiliatedCustomPermissions.put(this.customPolicyService.getPlanStatusCanEditStatusPermission(entity.getId()), myPermission);
}
}
}
if (cacheValue != null && cacheValue.getDescriptionStatusDefinitionMap() != null) {
cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, definitionStatusMap, cacheValue.getDescriptionStatusDefinitionMap());
} else {
cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, definitionStatusMap, null);
}
this.customPolicyCacheService.put(cacheValue);
} else {
for (UUID statusId: cacheValue.getPlanStatusDefinitionMap().keySet()) {
PlanStatusDefinitionEntity definition = cacheValue.getPlanStatusDefinitionMap().get(statusId);
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) {
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null);
affiliatedCustomPermissions.put(this.customPolicyService.getPlanStatusCanEditStatusPermission(statusId), myPermission);
}
}
}
}
private void buildAffiliatedDescriptionCustomPermissions(String tenantCode, CustomPolicyCacheService.CustomPolicyCacheValue cacheValue, HashMap<String, CustomPermissionAttributesProperties.MyPermission> affiliatedCustomPermissions) {
if (cacheValue == null || cacheValue.getDescriptionStatusDefinitionMap() == null) {
Map<UUID, DescriptionStatusDefinitionEntity> definitionStatusMap = new HashMap<>();
List<DescriptionStatusEntity> descriptionStatusEntities = this.queryFactory.query(DescriptionStatusQuery.class).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(DescriptionStatus._id).ensure(DescriptionStatus._definition));
if (descriptionStatusEntities != null) {
for (DescriptionStatusEntity entity : descriptionStatusEntities) {
DescriptionStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DescriptionStatusDefinitionEntity.class, entity.getDefinition());
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null && definition.getAuthorization().getEdit().getPlanRoles() != null){
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null);
affiliatedCustomPermissions.put(this.customPolicyService.getDescriptionStatusCanEditStatusPermission(entity.getId()), myPermission);
}
}
}
if (cacheValue != null && cacheValue.getPlanStatusDefinitionMap() != null) {
cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, cacheValue.getPlanStatusDefinitionMap(), definitionStatusMap);
} else {
cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, null, definitionStatusMap);
}
this.customPolicyCacheService.put(cacheValue);
} else {
for (UUID statusId: cacheValue.getDescriptionStatusDefinitionMap().keySet()) {
DescriptionStatusDefinitionEntity definition = cacheValue.getDescriptionStatusDefinitionMap().get(statusId);
if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) {
CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null);
affiliatedCustomPermissions.put(this.customPolicyService.getDescriptionStatusCanEditStatusPermission(statusId), myPermission);
}
}
}
}
}

View File

@ -8,6 +8,7 @@ import { Tag, TagPersist } from "../tag/tag";
import { User } from "../user/user";
import { AppPermission } from "@app/core/common/enum/permission.enum";
import { DescriptionStatus, DescriptionStatusDefinition } from "../description-status/description-status";
import { DescriptionStatusPermission } from "@app/core/common/enum/description-status-permission.enum";
export interface Description extends BaseDescription {
label?: string;
@ -21,7 +22,9 @@ export interface Description extends BaseDescription {
descriptionTemplate?: DescriptionTemplate;
planDescriptionTemplate?: PlanDescriptionTemplate;
plan?: Plan;
availableStatuses?: DescriptionStatus[];
authorizationFlags?: AppPermission[];
statusAuthorizationFlags?: DescriptionStatusPermission[];
}

View File

@ -15,6 +15,7 @@ import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { EntityType } from '@app/core/common/enum/entity-type';
import { PlanStatus } from '../plan-status/plan-status';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
export interface BasePlan extends BaseEntity {
label?: string;
@ -41,7 +42,9 @@ export interface Plan extends BasePlan {
descriptions?: Description[];
planDescriptionTemplates?: PlanDescriptionTemplate[];
otherPlanVersions?: Plan[];
availableStatuses?: PlanStatus[];
authorizationFlags?: AppPermission[];
statusAuthorizationFlags?: PlanStatusPermission[];
}
export interface PublicPlan extends BasePlan {

View File

@ -58,15 +58,6 @@ export class DescriptionStatusService {
catchError((error: any) => throwError(() => error)));
}
getAvailableTransitions(descriptionId: Guid, reqFields: string[] = []): Observable<Array<DescriptionStatus>> {
const url = `${this.apiBase}/available-transitions/${descriptionId}`;
const options = { params: { f: reqFields } };
return this.http
.get<Array<DescriptionStatus>>(url, options).pipe(
catchError((error: any) => throwError(() => error)));
}
// tslint:disable-next-line: member-ordering
singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active])).pipe(map(x => x.items)),

View File

@ -58,15 +58,6 @@ export class PlanStatusService {
catchError((error: any) => throwError(() => error)));
}
getAvailableTransitions(planId: Guid, reqFields: string[] = []): Observable<Array<PlanStatus>> {
const url = `${this.apiBase}/available-transitions/${planId}`;
const options = { params: { f: reqFields } };
return this.http
.get<Array<PlanStatus>>(url, options).pipe(
catchError((error: any) => throwError(() => error)));
}
// tslint:disable-next-line: member-ordering
singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active])).pipe(map(x => x.items)),

View File

@ -67,8 +67,8 @@
<button [disabled]="saving" mat-menu-item (click)="saveAndClose()" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-CLOSE' | translate }}</button>
<button [disabled]="saving" mat-menu-item (click)="saveAndContinue()" type="button">{{ 'DESCRIPTION-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu>
<ng-container *ngIf="availableStatusesTransitions && availableStatusesTransitions.length > 0 && !isLocked && item.id && isNotFinalizedPlan()">
<button *ngFor='let status of availableStatusesTransitions' [disabled]="saving" mat-button class="rounded-btn neutral mr-2" type="button" (click)="persistStatus(status)">{{ status.action?.length > 0 ? status.action : status.name }}</button>
<ng-container *ngIf="canEditStatus && !isNew && item.availableStatuses && item.availableStatuses.length > 0 && !isLocked && item.id && isNotFinalizedPlan()">
<button *ngFor='let status of item.availableStatuses' [disabled]="saving" mat-button class="rounded-btn neutral mr-2" type="button" (click)="persistStatus(status)">{{ status.action?.length > 0 ? status.action : status.name }}</button>
</ng-container>
<button [disabled]="saving" *ngIf="isLocked" mat-button disabled class="rounded-btn neutral cursor-default" type="button">{{ 'PLAN-OVERVIEW.LOCKED' | translate}}</button>
</div>

View File

@ -45,9 +45,9 @@ import { DescriptionEditorEntityResolver } from './resolvers/description-editor-
import { ToCEntry } from './table-of-contents/models/toc-entry';
import { TableOfContentsService } from './table-of-contents/services/table-of-contents-service';
import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component';
import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type';
import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum';
@Component({
selector: 'app-description-editor-component',
@ -86,7 +86,6 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
private initialTemplateId: string = Guid.EMPTY;
private permissionPerSection: Map<Guid, string[]>;
availableStatusesTransitions: DescriptionStatus[];
oldStatusId: Guid;
constructor(
@ -118,7 +117,6 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
private tableOfContentsService: TableOfContentsService,
private descriptionFormService: DescriptionFormService,
private formAnnotationService: FormAnnotationService,
private descriptionStatusService: DescriptionStatusService
) {
const descriptionLabel: string = route.snapshot.data['entity']?.label;
if (descriptionLabel) {
@ -198,7 +196,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
}
if (this.route.snapshot.url[1] && this.route.snapshot.url[1].path == 'finalize' && !this.lockStatus && !this.viewOnly) {
setTimeout(() => {
const finalizedStatus = this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null;
const finalizedStatus = this.item.availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null;
if (finalizedStatus) this.finalize(finalizedStatus.id);
}, 0);
}
@ -225,7 +223,6 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
try {
this.editorModel = data ? new DescriptionEditorModel().fromModel(data, data.descriptionTemplate) : new DescriptionEditorModel();
if (data) {
if (data.id) this.getAvailableStatuses(data.id);
if (data.status?.id) this.oldStatusId = data.status.id
if (data.status?.definition?.availableActions?.filter(x => x === DescriptionStatusAvailableActionType.Export).length > 0) this.canExport = true;
}
@ -270,15 +267,6 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
this.registerFormListeners();
}
getAvailableStatuses(id: Guid){
this.descriptionStatusService.getAvailableTransitions(id).pipe(takeUntil(this._destroyed))
.subscribe(
(statuses) => {
this.availableStatusesTransitions = statuses;
},
(error) => this.httpErrorHandlingService.handleBackedRequestError(error)
); }
calculateMultiplicityRejectedPlanDescriptionTemplates(section: PlanBlueprintDefinitionSection, descriptions: Description[]): PlanDescriptionTemplate[] {
if (section.descriptionTemplates?.length > 0) {
descriptions = descriptions?.filter(x => x?.planDescriptionTemplate?.sectionId === section.id) || [];
@ -325,7 +313,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
persistEntity(onSuccess?: (response) => void): void {
const formData = this.formService.getValue(this.formGroup.value) as DescriptionPersist;
const finalizedStatus = this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null;
const finalizedStatus = this.item.availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null;
this.descriptionService.persist(formData)
.pipe(takeUntil(this._destroyed)).subscribe(
@ -649,6 +637,10 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
return errorsCount;
}
get canEditStatus(): boolean{
return this.item.statusAuthorizationFlags?.some(x => x.toLowerCase() === DescriptionStatusPermission.Edit.toLowerCase())
}
registerFormListeners() {
this.formGroup.get('descriptionTemplateId').valueChanges

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionStatus, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status';
@ -64,6 +65,8 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver {
[nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'),
[nameof<Description>(x => x.statusAuthorizationFlags), DescriptionStatusPermission.Edit].join('.'),
[nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.sectionId)].join('.'),
[nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.isActive)].join('.'),
@ -103,7 +106,12 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver {
nameof<Description>(x => x.createdAt),
nameof<Description>(x => x.hash),
nameof<Description>(x => x.isActive)
nameof<Description>(x => x.isActive),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.id)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.name)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.internalStatus)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.action)].join('.'),
]
}

View File

@ -134,8 +134,8 @@
<div class="row">
<div class="col-12">
<div class="frame mb-3 pt-4 pl-4 pr-5 pb-3">
<ng-container *ngIf="availableStatusesTransitions && availableStatusesTransitions.length > 0 && !isLocked && isNotFinalizedPlan(description)">
<div *ngFor='let status of availableStatusesTransitions'>
<ng-container *ngIf="canEditStatus && description.availableStatuses && description.availableStatuses.length > 0 && !isLocked && isNotFinalizedPlan(description)">
<div *ngFor='let status of description.availableStatuses'>
<div class="row align-items-center" (click)="persistStatus(status, description)">
<div class="col-auto pr-0">
<button *ngIf="status.internalStatus === descriptionStatusEnum.Finalized && description.status?.internalStatus != descriptionStatusEnum.Finalized" mat-mini-fab class="finalize-btn">

View File

@ -45,9 +45,9 @@ import { nameof } from 'ts-simple-nameof';
import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionStatus, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status';
import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service';
import { PlanStatus } from '@app/core/model/plan-status/plan-status';
import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type';
import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum';
@Component({
@ -77,7 +77,6 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
canFinalize = false;
canAnnotate = false;
canInvitePlanUsers = false;
availableStatusesTransitions: DescriptionStatus[];
get canAssignPlanUsers(): boolean {
const authorizationFlags = !this.isPublicView ? (this.description?.plan as Plan)?.authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers) || this.authentication.hasPermission(AppPermission.InvitePlanUsers)) &&
@ -111,7 +110,6 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
private descriptionStatusService: DescriptionStatusService
) {
super();
}
@ -141,7 +139,6 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
this.breadcrumbService.addIdResolvedValue(data.id.toString(), data.label);
this.description = data;
this.getAvailableStatuses(this.description.id);
this.description.plan.planUsers = this.isActive || this.description.plan.isActive === IsActive.Active ? data.plan.planUsers.filter(x => x.isActive === IsActive.Active) : data.plan.planUsers;
this.researchers = this.referenceService.getReferencesForTypes(this.description?.plan?.planReferences, [this.referenceTypeService.getResearcherReferenceType()]);
this.checkLockStatus(this.description.id);
@ -228,17 +225,12 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
return this.description?.status?.definition?.availableActions?.filter(x => x === DescriptionStatusAvailableActionType.Export).length > 0;
}
getAvailableStatuses(id: Guid){
this.descriptionStatusService.getAvailableTransitions(id).pipe(takeUntil(this._destroyed))
.subscribe(
(statuses) => {
this.availableStatusesTransitions = statuses;
},
(error) => this.httpErrorHandlingService.handleBackedRequestError(error)
); }
get canEditStatus(): boolean{
return (this.description as Description).statusAuthorizationFlags?.some(x => x.toLowerCase() === DescriptionStatusPermission.Edit.toLowerCase())
}
hasAvailableFinalizeStatus() {
return this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) != null;
return (this.description as Description).availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) != null;
}
checkLockStatus(id: Guid) {
@ -524,7 +516,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
}
hasReversableStatus(description: Description): boolean {
return description.plan.status.internalStatus == PlanStatusEnum.Draft && description?.status?.internalStatus == DescriptionStatusEnum.Finalized && this.canFinalize && this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Draft) != null
return description.plan.status.internalStatus == PlanStatusEnum.Draft && description?.status?.internalStatus == DescriptionStatusEnum.Finalized && this.canFinalize && (this.description as Description).availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Draft) != null
}
reverseFinalization(description: Description, statusId: Guid) {
@ -578,6 +570,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
[nameof<Description>(x => x.authorizationFlags), AppPermission.InvitePlanUsers].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'),
[nameof<Description>(x => x.statusAuthorizationFlags), DescriptionStatusPermission.Edit].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.label)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.groupId)].join('.'),
@ -610,6 +604,11 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
[nameof<Description>(x => x.plan), nameof<Plan>(x => x.planReferences), nameof<PlanReference>(x => x.reference), nameof<Reference>(x => x.source)].join('.'),
[nameof<Description>(x => x.plan), nameof<Plan>(x => x.planReferences), nameof<PlanReference>(x => x.reference), nameof<Reference>(x => x.reference)].join('.'),
[nameof<Description>(x => x.plan), nameof<Plan>(x => x.planReferences), nameof<PlanReference>(x => x.isActive)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.id)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.name)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.internalStatus)].join('.'),
[nameof<Description>(x => x.availableStatuses), nameof<DescriptionStatus>(x => x.action)].join('.'),
]
}

View File

@ -181,8 +181,8 @@
<div class="row">
<div class="col-12">
<div class="frame mb-3 pt-4 pl-4 pr-5 pb-3">
<ng-container *ngIf="availableStatusesTransitions && availableStatusesTransitions.length > 0 && !isLocked && plan.versionStatus != planVersionStatusEnum.Previous && hasDoi(plan) && plan.belongsToCurrentTenant != false">
<div *ngFor='let status of availableStatusesTransitions'>
<ng-container *ngIf="canEditStatus && plan.availableStatuses && plan.availableStatuses.length > 0 && !isLocked && plan.versionStatus != planVersionStatusEnum.Previous && hasDoi(plan) && plan.belongsToCurrentTenant != false">
<div *ngFor='let status of plan.availableStatuses'>
<div class="row align-items-center" (click)="persistStatus(status)">
<div class="col-auto pr-0">
<button *ngIf="status.internalStatus === descriptionStatusEnum.Finalized && plan.status?.internalStatus != planStatusEnum.Finalized" mat-mini-fab class="finalize-btn">

View File

@ -52,8 +52,8 @@ import { NewVersionPlanDialogComponent } from '../new-version-dialog/plan-new-ve
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status';
import { PlanStatusService } from '@app/core/services/plan/plan-status.service';
import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
@Component({
selector: 'app-plan-overview',
@ -87,7 +87,6 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
authorFocus: string;
userName: string;
availableStatusesTransitions: PlanStatus[];
constructor(
public routerUtils: RouterUtilsService,
@ -113,7 +112,6 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
private pLanStatusService: PlanStatusService
) {
super();
}
@ -136,7 +134,6 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
this.breadcrumbService.addIdResolvedValue(data.id?.toString(), data.label);
this.plan = data;
this.getAvailableStatuses(this.plan.id);
this.plan.planUsers = this.isActive ? data?.planUsers?.filter((x) => x.isActive === IsActive.Active) : data?.planUsers;
this.plan.otherPlanVersions = data.otherPlanVersions?.filter(x => x.isActive === IsActive.Active) || null;
if (this.plan.descriptions && this.isActive) {
@ -237,14 +234,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
return this.language.instant('PLAN-OVERVIEW.INFOS.UNAUTHORIZED-ORCID');
}
getAvailableStatuses(id: Guid){
this.pLanStatusService.getAvailableTransitions(id).pipe(takeUntil(this._destroyed))
.subscribe(
(statuses) => {
this.availableStatusesTransitions = statuses;
},
(error) => this.httpErrorHandlingService.handleBackedRequestError(error)
); }
get canEditStatus(): boolean{
return (this.plan as Plan).statusAuthorizationFlags ?.some(x => x.toLowerCase() === PlanStatusPermission.Edit.toLowerCase())
}
onFetchingDeletedCallbackError(redirectRoot: string) {
this.router.navigate([this.routerUtils.generateUrl(redirectRoot)]);
@ -691,6 +683,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
[nameof<Plan>(x => x.authorizationFlags), AppPermission.AssignPlanUsers].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.EditPlan].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.DepositPlan].join('.'),
[nameof<Plan>(x => x.statusAuthorizationFlags), PlanStatusPermission.Edit].join('.'),
[nameof<Plan>(x => x.entityDois), nameof<EntityDoi>(x => x.id)].join('.'),
[nameof<Plan>(x => x.entityDois), nameof<EntityDoi>(x => x.repositoryId)].join('.'),
[nameof<Plan>(x => x.entityDois), nameof<EntityDoi>(x => x.doi)].join('.'),
@ -734,6 +727,11 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
[nameof<Plan>(x => x.otherPlanVersions), nameof<Plan>(x => x.version)].join('.'),
[nameof<Plan>(x => x.otherPlanVersions), nameof<Plan>(x => x.isActive)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.id)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.name)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.internalStatus)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.action)].join('.'),
nameof<Plan>(x => x.hash),
]
}

View File

@ -52,8 +52,8 @@
<button [disabled]="saving" mat-menu-item (click)="formSubmit()" type="button">{{ 'PLAN-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu>
</div>
<div *ngIf="availableStatusesTransitions && availableStatusesTransitions.length > 0 && item.versionStatus != planVersionStatusEnum.Previous &&!isLocked && !isNew && hasNotDoi()" class="col-auto d-flex align-items-center" [matTooltipDisabled]="formGroup.pristine" matTooltip="{{'PLAN-EDITOR.ACTIONS.FINALIZE.CAN-NOT-FINALIZE' | translate}}">
<button *ngFor='let status of availableStatusesTransitions' [disabled]="saving || !formGroup.pristine" mat-button class="rounded-btn primary-inverted mr-2" type="button" (click)="persistStatus(status)">{{ status.action?.length > 0 ? status.action : status.name }}</button>
<div *ngIf="canEditStatus && !isNew && item.availableStatuses && item.availableStatuses.length > 0 && item.versionStatus != planVersionStatusEnum.Previous &&!isLocked && !isNew && hasNotDoi()" class="col-auto d-flex align-items-center" [matTooltipDisabled]="formGroup.pristine" matTooltip="{{'PLAN-EDITOR.ACTIONS.FINALIZE.CAN-NOT-FINALIZE' | translate}}">
<button *ngFor='let status of item.availableStatuses' [disabled]="saving || !formGroup.pristine" mat-button class="rounded-btn primary-inverted mr-2" type="button" (click)="persistStatus(status)">{{ status.action?.length > 0 ? status.action : status.name }}</button>
</div>
<div *ngIf="isLocked" class="col-auto d-flex align-items-center">
<button class="col-auto d-flex align-items-center" [disabled]="saving" mat-button class="rounded-btn primary-inverted mr-2" type="button">{{ 'PLAN-EDITOR.ACTIONS.LOCKED' | translate}}</button>

View File

@ -64,6 +64,7 @@ import { PlanStatusService } from '@app/core/services/plan/plan-status.service';
import { PlanStatus } from '@app/core/model/plan-status/plan-status';
import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type';
import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
@Component({
selector: 'app-plan-editor',
@ -102,8 +103,6 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
hoveredContact: number = -1;
availableStatusesTransitions: PlanStatus[];
singleAutocompleteBlueprintConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.planBlueprintService.query(this.planBlueprintService.buildAutocompleteLookup(null, null, null, [PlanBlueprintStatus.Finalized])).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.planBlueprintService.query(this.planBlueprintService.buildAutocompleteLookup(searchQuery, null, null, [PlanBlueprintStatus.Finalized])).pipe(map(x => x.items)),
@ -166,6 +165,10 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
return !this.isDeleted && (this.isNew ? this.authService.hasPermission(AppPermission.NewPlan) : this.item.authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authService.hasPermission(AppPermission.EditPlan));
}
get canEditStatus(): boolean{
return this.item.statusAuthorizationFlags?.some(x => x.toLowerCase() === PlanStatusPermission.Edit.toLowerCase())
}
protected canAnnotate(id: Guid): boolean {
return !this.isDeleted && this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.AnnotatePlan);
}
@ -274,7 +277,6 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.editorModel = data ? new PlanEditorModel().fromModel(data) : new PlanEditorModel();
this.isDeleted = data ? data.isActive === IsActive.Inactive : false;
if (data) {
if (data.id) this.getAvailableStatuses(data.id);
if (data.descriptions && !this.isDeleted) {
if (data.status?.internalStatus == PlanStatusEnum.Finalized) {
data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status.internalStatus === DescriptionStatusEnum.Finalized);
@ -326,15 +328,6 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.sectionToFieldsMap = this.prepareErrorIndication();
}
getAvailableStatuses(id: Guid){
this.pLanStatusService.getAvailableTransitions(id).pipe(takeUntil(this._destroyed))
.subscribe(
(statuses) => {
this.availableStatusesTransitions = statuses;
},
(error) => this.httpErrorHandlingService.handleBackedRequestError(error)
); }
prepareErrorIndication(): Map<string, PlanFieldIndicator> {
if (this.selectedBlueprint?.definition == null) return;

View File

@ -17,6 +17,7 @@ import { nameof } from 'ts-simple-nameof';
import { EntityDoi } from '@app/core/model/entity-doi/entity-doi';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
@Injectable()
export class PlanEditorEntityResolver extends BaseEditorResolver {
@ -53,6 +54,8 @@ export class PlanEditorEntityResolver extends BaseEditorResolver {
[nameof<Plan>(x => x.authorizationFlags), AppPermission.EditDescription].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.ExportPlan].join('.'),
[nameof<Plan>(x => x.statusAuthorizationFlags), PlanStatusPermission.Edit].join('.'),
[nameof<Plan>(x => x.properties), nameof<PlanProperties>(x => x.planBlueprintValues), nameof<PlanBlueprintValue>(x => x.fieldId)].join('.'),
[nameof<Plan>(x => x.properties), nameof<PlanProperties>(x => x.planBlueprintValues), nameof<PlanBlueprintValue>(x => x.fieldValue)].join('.'),
[nameof<Plan>(x => x.properties), nameof<PlanProperties>(x => x.planBlueprintValues), nameof<PlanBlueprintValue>(x => x.dateValue)].join('.'),
@ -100,6 +103,11 @@ export class PlanEditorEntityResolver extends BaseEditorResolver {
[nameof<Plan>(x => x.entityDois), nameof<EntityDoi>(x => x.doi)].join('.'),
[nameof<Plan>(x => x.entityDois), nameof<EntityDoi>(x => x.isActive)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.id)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.name)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.internalStatus)].join('.'),
[nameof<Plan>(x => x.availableStatuses), nameof<PlanStatus>(x => x.action)].join('.'),
...PlanEditorEntityResolver.blueprintLookupFields(nameof<Plan>(x => x.blueprint)),
]