Merge branch 'dmp-refactoring' of https://code-repo.d4science.org/MaDgiK-CITE/argos into dmp-refactoring

This commit is contained in:
Sofia Papacharalampous 2024-06-21 12:40:00 +03:00
commit 6e12d13a3b
22 changed files with 897 additions and 24 deletions

View File

@ -81,11 +81,11 @@ public class AuthorizationContentResolverImpl implements AuthorizationContentRes
List<AnnotationEntity> annotationEntities = this.queryFactory.query(AnnotationQuery.class).disableTracking().ids(ids).collectAs(new BaseFieldSet().ensure(Annotation._id).ensure(Annotation._entityId).ensure(Annotation._id)); List<AnnotationEntity> annotationEntities = this.queryFactory.query(AnnotationQuery.class).disableTracking().ids(ids).collectAs(new BaseFieldSet().ensure(Annotation._id).ensure(Annotation._entityId).ensure(Annotation._id));
List<EntityUserEntity> entityUsers = this.queryFactory.query(EntityUserQuery.class).disableTracking().entityIds(annotationEntities.stream().map(AnnotationEntity::getEntityId).distinct().toList()).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId)); List<EntityUserEntity> entityUsers = this.queryFactory.query(EntityUserQuery.class).disableTracking().entityIds(annotationEntities.stream().map(AnnotationEntity::getEntityId).distinct().toList()).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId));
Map<UUID, List<EntityUserEntity>> dmpUsersMap = entityUsers.stream().collect(Collectors.groupingBy(EntityUserEntity::getEntityId)); Map<UUID, List<EntityUserEntity>> entityUsersMap = entityUsers.stream().collect(Collectors.groupingBy(EntityUserEntity::getEntityId));
for (AnnotationEntity annotation : annotationEntities){ for (AnnotationEntity annotation : annotationEntities){
List<EntityUserEntity> dmpDescriptionUsers = dmpUsersMap.getOrDefault(annotation.getEntityId(), new ArrayList<>()); List<EntityUserEntity> annotationEntityUsers = entityUsersMap.getOrDefault(annotation.getEntityId(), new ArrayList<>());
if (!dmpDescriptionUsers.isEmpty()) { if (!annotationEntityUsers.isEmpty()) {
affiliatedResources.get(annotation.getId()).setAffiliated(true); affiliatedResources.get(annotation.getId()).setAffiliated(true);
} }
} }

View File

@ -104,6 +104,8 @@ public class AuditableAction {
public static final EventId User_RemoveCredentialConfirm = new EventId(11013, "User_RemoveCredentialConfirm"); public static final EventId User_RemoveCredentialConfirm = new EventId(11013, "User_RemoveCredentialConfirm");
public static final EventId User_DmpAssociatedQuery = new EventId(11014, "User_DmpAssociatedQuery"); public static final EventId User_DmpAssociatedQuery = new EventId(11014, "User_DmpAssociatedQuery");
public static final EventId User_AllowMergeAccount = new EventId(11015, "User_AllowMergeAccount"); public static final EventId User_AllowMergeAccount = new EventId(11015, "User_AllowMergeAccount");
public static final EventId User_InviteToTenant = new EventId(11016, "User_InviteToTenant");
public static final EventId User_InviteToTenantConfirm = new EventId(11017, "User_InviteToTenantConfirm");
public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query"); public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query");
public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup"); public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup");

View File

@ -9,7 +9,8 @@ public enum ActionConfirmationType implements DatabaseEnum<Short> {
MergeAccount((short) 0), MergeAccount((short) 0),
RemoveCredential((short) 1), RemoveCredential((short) 1),
DmpInvitation((short) 2); DmpInvitation((short) 2),
UserInviteToTenant ((short) 3);
private final Short value; private final Short value;

View File

@ -21,6 +21,7 @@ public class NotificationProperties {
private UUID descriptionTemplateInvitationType; private UUID descriptionTemplateInvitationType;
private UUID contactSupportType; private UUID contactSupportType;
private UUID publicContactSupportType; private UUID publicContactSupportType;
private UUID tenantSpecificInvitationUserType;
private int emailExpirationTimeSeconds; private int emailExpirationTimeSeconds;
private String contactSupportEmail; private String contactSupportEmail;
@ -151,4 +152,12 @@ public class NotificationProperties {
public void setDescriptionAnnotationCreated(UUID descriptionAnnotationCreated) { public void setDescriptionAnnotationCreated(UUID descriptionAnnotationCreated) {
this.descriptionAnnotationCreated = descriptionAnnotationCreated; this.descriptionAnnotationCreated = descriptionAnnotationCreated;
} }
public UUID getTenantSpecificInvitationUserType() {
return tenantSpecificInvitationUserType;
}
public void setTenantSpecificInvitationUserType(UUID tenantSpecificInvitationUserType) {
this.tenantSpecificInvitationUserType = tenantSpecificInvitationUserType;
}
} }

View File

@ -0,0 +1,48 @@
package org.opencdmp.commons.types.actionconfirmation;
import jakarta.xml.bind.annotation.*;
import java.util.List;
@XmlRootElement(name = "user-invite-to-tenant-confirmation")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserInviteToTenantRequestEntity {
@XmlAttribute(name = "email")
private String email;
@XmlAttribute(name = "tenantCode")
private String tenantCode;
@XmlElementWrapper(name = "roles")
@XmlElement(name = "role")
private List<String> roles;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}

View File

@ -26,6 +26,10 @@ public class ActionConfirmation {
public static final String _removeCredentialRequest = "removeCredentialRequest"; public static final String _removeCredentialRequest = "removeCredentialRequest";
private UserInviteToTenantRequest userInviteToTenantRequest;
public static final String _userInviteToTenantRequest = "userInviteToTenantRequest";
private DmpInvitation dmpInvitation; private DmpInvitation dmpInvitation;
public static final String _dmpInvitation = "dmpInvitation"; public static final String _dmpInvitation = "dmpInvitation";
@ -154,4 +158,12 @@ public class ActionConfirmation {
public void setRemoveCredentialRequest(RemoveCredentialRequest removeCredentialRequest) { public void setRemoveCredentialRequest(RemoveCredentialRequest removeCredentialRequest) {
this.removeCredentialRequest = removeCredentialRequest; this.removeCredentialRequest = removeCredentialRequest;
} }
public UserInviteToTenantRequest getUserInviteToTenantRequest() {
return userInviteToTenantRequest;
}
public void setUserInviteToTenantRequest(UserInviteToTenantRequest userInviteToTenantRequest) {
this.userInviteToTenantRequest = userInviteToTenantRequest;
}
} }

View File

@ -0,0 +1,42 @@
package org.opencdmp.model.actionconfirmation;
import java.util.List;
public class UserInviteToTenantRequest {
private String email;
public static final String _email = "email";
private String tenantCode;
public static final String _tenantCode = "tenantCode";
private List<String> roles;
public static final String _roles = "roles";
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}

View File

@ -13,6 +13,7 @@ import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.actionconfirmation.DmpInvitationEntity; import org.opencdmp.commons.types.actionconfirmation.DmpInvitationEntity;
import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity; import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity;
import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity; import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity;
import org.opencdmp.commons.types.actionconfirmation.UserInviteToTenantRequestEntity;
import org.opencdmp.convention.ConventionService; import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.ActionConfirmationEntity; import org.opencdmp.data.ActionConfirmationEntity;
import org.opencdmp.model.actionconfirmation.ActionConfirmation; import org.opencdmp.model.actionconfirmation.ActionConfirmation;
@ -60,6 +61,7 @@ public class ActionConfirmationBuilder extends BaseBuilder<ActionConfirmation, A
FieldSet mergeAccountConfirmationFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._mergeAccountConfirmation)); FieldSet mergeAccountConfirmationFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._mergeAccountConfirmation));
FieldSet removeCredentialRequestFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._removeCredentialRequest)); FieldSet removeCredentialRequestFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._removeCredentialRequest));
FieldSet userInviteToTenantRequestFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._userInviteToTenantRequest));
FieldSet dmpInvitationFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._dmpInvitation)); FieldSet dmpInvitationFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._dmpInvitation));
FieldSet userFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._createdBy)); FieldSet userFields = fields.extractPrefixed(this.asPrefix(ActionConfirmation._createdBy));
@ -89,6 +91,10 @@ public class ActionConfirmationBuilder extends BaseBuilder<ActionConfirmation, A
RemoveCredentialRequestEntity emailConfirmation = this.xmlHandlingService.fromXmlSafe(RemoveCredentialRequestEntity.class, d.getData()); RemoveCredentialRequestEntity emailConfirmation = this.xmlHandlingService.fromXmlSafe(RemoveCredentialRequestEntity.class, d.getData());
m.setRemoveCredentialRequest(this.builderFactory.builder(RemoveCredentialRequestBuilder.class).authorize(this.authorize).build(removeCredentialRequestFields, emailConfirmation)); m.setRemoveCredentialRequest(this.builderFactory.builder(RemoveCredentialRequestBuilder.class).authorize(this.authorize).build(removeCredentialRequestFields, emailConfirmation));
} }
case UserInviteToTenant -> {
UserInviteToTenantRequestEntity emailConfirmation = this.xmlHandlingService.fromXmlSafe(UserInviteToTenantRequestEntity.class, d.getData());
m.setUserInviteToTenantRequest(this.builderFactory.builder(UserInviteToTenantRequestBuilder.class).authorize(this.authorize).build(userInviteToTenantRequestFields, emailConfirmation));
}
default -> throw new InternalError("unknown type: " + d.getType()); default -> throw new InternalError("unknown type: " + d.getType());
} }

View File

@ -0,0 +1,57 @@
package org.opencdmp.model.builder.actionconfirmation;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.types.actionconfirmation.UserInviteToTenantRequestEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.model.actionconfirmation.UserInviteToTenantRequest;
import org.opencdmp.model.builder.BaseBuilder;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserInviteToTenantRequestBuilder extends BaseBuilder<UserInviteToTenantRequest, UserInviteToTenantRequestEntity> {
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public UserInviteToTenantRequestBuilder(
ConventionService conventionService) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(UserInviteToTenantRequestBuilder.class)));
}
public UserInviteToTenantRequestBuilder authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
public List<UserInviteToTenantRequest> build(FieldSet fields, List<UserInviteToTenantRequestEntity> data) throws MyApplicationException {
this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0));
this.logger.trace(new DataLogEntry("requested fields", fields));
if (fields == null || data == null || fields.isEmpty())
return new ArrayList<>();
List<UserInviteToTenantRequest> models = new ArrayList<>();
for (UserInviteToTenantRequestEntity d : data) {
UserInviteToTenantRequest m = new UserInviteToTenantRequest();
if (fields.hasField(this.asIndexer(UserInviteToTenantRequest._email))) m.setEmail(d.getEmail());
if (fields.hasField(this.asIndexer(UserInviteToTenantRequest._tenantCode))) m.setTenantCode(d.getTenantCode());
if (fields.hasField(this.asIndexer(UserInviteToTenantRequest._roles))) m.setRoles(d.getRoles());
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
return models;
}
}

View File

@ -49,6 +49,10 @@ public class ActionConfirmationPersist {
private static final String _removeCredentialRequest = "removeCredentialRequest"; private static final String _removeCredentialRequest = "removeCredentialRequest";
private UserInviteToTenantRequestPersist userInviteToTenantRequestPersist;
private static final String _userInviteToTenantRequest = "userInviteToTenantRequest";
private Instant expiresAt; private Instant expiresAt;
private static final String _expiresAt = "expiresAt"; private static final String _expiresAt = "expiresAt";
@ -105,6 +109,14 @@ public class ActionConfirmationPersist {
this.removeCredentialRequest = removeCredentialRequest; this.removeCredentialRequest = removeCredentialRequest;
} }
public UserInviteToTenantRequestPersist getUserInviteToTenantRequest() {
return userInviteToTenantRequestPersist;
}
public void setUserInviteToTenantRequest(UserInviteToTenantRequestPersist userInviteToTenantRequestPersist) {
this.userInviteToTenantRequestPersist = userInviteToTenantRequestPersist;
}
public String getToken() { public String getToken() {
return token; return token;
} }
@ -182,12 +194,14 @@ public class ActionConfirmationPersist {
.iff(() -> ActionConfirmationType.DmpInvitation.equals(item.getType())) .iff(() -> ActionConfirmationType.DmpInvitation.equals(item.getType()))
.must(() -> !this.isNull(item.getDmpInvitation())) .must(() -> !this.isNull(item.getDmpInvitation()))
.failOn(ActionConfirmationPersist._dmpInvitation).failWith(messageSource.getMessage("Validation_Required", new Object[]{ActionConfirmationPersist._dmpInvitation}, LocaleContextHolder.getLocale())), .failOn(ActionConfirmationPersist._dmpInvitation).failWith(messageSource.getMessage("Validation_Required", new Object[]{ActionConfirmationPersist._dmpInvitation}, LocaleContextHolder.getLocale())),
this.spec() this.spec()
.iff(() -> ActionConfirmationType.RemoveCredential.equals(item.getType())) .iff(() -> ActionConfirmationType.RemoveCredential.equals(item.getType()))
.must(() -> !this.isNull(item.getRemoveCredentialRequest())) .must(() -> !this.isNull(item.getRemoveCredentialRequest()))
.failOn(ActionConfirmationPersist._removeCredentialRequest).failWith(messageSource.getMessage("Validation_Required", new Object[]{ActionConfirmationPersist._removeCredentialRequest}, LocaleContextHolder.getLocale())), .failOn(ActionConfirmationPersist._removeCredentialRequest).failWith(messageSource.getMessage("Validation_Required", new Object[]{ActionConfirmationPersist._removeCredentialRequest}, LocaleContextHolder.getLocale())),
this.spec()
.iff(() -> ActionConfirmationType.UserInviteToTenant.equals(item.getType()))
.must(() -> !this.isNull(item.getUserInviteToTenantRequest()))
.failOn(ActionConfirmationPersist._userInviteToTenantRequest).failWith(messageSource.getMessage("Validation_Required", new Object[]{ActionConfirmationPersist._userInviteToTenantRequest}, LocaleContextHolder.getLocale())),
this.refSpec() this.refSpec()
.iff(() -> !this.isNull(item.getDmpInvitation())) .iff(() -> !this.isNull(item.getDmpInvitation()))
.on(ActionConfirmationPersist._dmpInvitation) .on(ActionConfirmationPersist._dmpInvitation)
@ -198,12 +212,16 @@ public class ActionConfirmationPersist {
.on(ActionConfirmationPersist._mergeAccountConfirmation) .on(ActionConfirmationPersist._mergeAccountConfirmation)
.over(item.getMergeAccountConfirmation()) .over(item.getMergeAccountConfirmation())
.using(() -> this.validatorFactory.validator(MergeAccountConfirmationPersist.MergeAccountConfirmationPersistValidator.class)), .using(() -> this.validatorFactory.validator(MergeAccountConfirmationPersist.MergeAccountConfirmationPersistValidator.class)),
this.refSpec() this.refSpec()
.iff(() -> !this.isNull(item.getRemoveCredentialRequest())) .iff(() -> !this.isNull(item.getRemoveCredentialRequest()))
.on(ActionConfirmationPersist._removeCredentialRequest) .on(ActionConfirmationPersist._removeCredentialRequest)
.over(item.getRemoveCredentialRequest()) .over(item.getRemoveCredentialRequest())
.using(() -> this.validatorFactory.validator(RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.class)) .using(() -> this.validatorFactory.validator(RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.class)),
this.refSpec()
.iff(() -> !this.isNull(item.getUserInviteToTenantRequest()))
.on(ActionConfirmationPersist._userInviteToTenantRequest)
.over(item.getUserInviteToTenantRequest())
.using(() -> this.validatorFactory.validator(UserInviteToTenantRequestPersist.UserInviteToTenantRequestPersistValidator.class))
); );
} }

View File

@ -0,0 +1,93 @@
package org.opencdmp.model.persist;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.validation.BaseValidator;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
public class UserInviteToTenantRequestPersist {
private String email;
public static final String _email = "email";
private String tenantCode;
public static final String _tenantCode = "tenantCode";
private List<String> roles;
public static final String _roles = "roles";
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
@Component(UserInviteToTenantRequestPersistValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserInviteToTenantRequestPersistValidator extends BaseValidator<UserInviteToTenantRequestPersist> {
public static final String ValidatorName = "UserInviteToTenantRequestPersistValidator";
private final MessageSource messageSource;
protected UserInviteToTenantRequestPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<UserInviteToTenantRequestPersist> modelClass() {
return UserInviteToTenantRequestPersist.class;
}
@Override
protected List<Specification> specifications(UserInviteToTenantRequestPersist item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isEmpty(item.getEmail()))
.failOn(UserInviteToTenantRequestPersist._email).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserInviteToTenantRequestPersist._email}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isEmpty(item.getTenantCode()))
.failOn(UserInviteToTenantRequestPersist._tenantCode).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserInviteToTenantRequestPersist._tenantCode}, LocaleContextHolder.getLocale())),
this.spec()
.iff(() -> !this.isEmpty(item.getEmail()))
.must(() -> this.isValidEmail(item.getEmail()))
.failOn(UserInviteToTenantRequestPersist._email).failWith(messageSource.getMessage("Validation_UnexpectedValue", new Object[]{UserInviteToTenantRequestPersist._email}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isListNullOrEmpty(item.getRoles()))
.failOn(UserInviteToTenantRequestPersist._roles).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserInviteToTenantRequestPersist._roles}, LocaleContextHolder.getLocale()))
);
}
}
}

View File

@ -0,0 +1,67 @@
package org.opencdmp.model.persist;
import gr.cite.tools.validation.ValidatorFactory;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.validation.BaseValidator;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
public class UserTenantUsersInviteRequest {
private List<UserInviteToTenantRequestPersist> users;
public static final String _users = "users";
public List<UserInviteToTenantRequestPersist> getUsers() {
return users;
}
public void setUsers(List<UserInviteToTenantRequestPersist> users) {
this.users = users;
}
@Component(UserTenantUsersInviteRequestValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserTenantUsersInviteRequestValidator extends BaseValidator<UserTenantUsersInviteRequest> {
public static final String ValidatorName = "UserTenantUsersInviteRequestValidator";
private final ValidatorFactory validatorFactory;
private final MessageSource messageSource;
protected UserTenantUsersInviteRequestValidator(ConventionService conventionService, ErrorThesaurusProperties errors, ValidatorFactory validatorFactory, MessageSource messageSource) {
super(conventionService, errors);
this.validatorFactory = validatorFactory;
this.messageSource = messageSource;
}
@Override
protected Class<UserTenantUsersInviteRequest> modelClass() {
return UserTenantUsersInviteRequest.class;
}
@Override
protected List<Specification> specifications(UserTenantUsersInviteRequest item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isListNullOrEmpty(item.getUsers()))
.failOn(UserTenantUsersInviteRequest._users).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTenantUsersInviteRequest._users}, LocaleContextHolder.getLocale())),
this.navSpec()
.iff(() -> !this.isListNullOrEmpty(item.getUsers()))
.on(UserTenantUsersInviteRequest._users)
.over(item.getUsers())
.using((itm) -> this.validatorFactory.validator(UserInviteToTenantRequestPersist.UserInviteToTenantRequestPersistValidator.class))
);
}
}
}

View File

@ -21,6 +21,7 @@ import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.commons.types.actionconfirmation.DmpInvitationEntity; import org.opencdmp.commons.types.actionconfirmation.DmpInvitationEntity;
import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity; import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity;
import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity; import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity;
import org.opencdmp.commons.types.actionconfirmation.UserInviteToTenantRequestEntity;
import org.opencdmp.convention.ConventionService; import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.ActionConfirmationEntity; import org.opencdmp.data.ActionConfirmationEntity;
import org.opencdmp.data.TenantEntityManager; import org.opencdmp.data.TenantEntityManager;
@ -29,6 +30,7 @@ import org.opencdmp.model.actionconfirmation.ActionConfirmation;
import org.opencdmp.model.builder.actionconfirmation.ActionConfirmationBuilder; import org.opencdmp.model.builder.actionconfirmation.ActionConfirmationBuilder;
import org.opencdmp.model.deleter.ActionConfirmationDeleter; import org.opencdmp.model.deleter.ActionConfirmationDeleter;
import org.opencdmp.model.persist.ActionConfirmationPersist; import org.opencdmp.model.persist.ActionConfirmationPersist;
import org.opencdmp.model.persist.UserInviteToTenantRequestPersist;
import org.opencdmp.model.persist.actionconfirmation.DmpInvitationPersist; import org.opencdmp.model.persist.actionconfirmation.DmpInvitationPersist;
import org.opencdmp.model.persist.actionconfirmation.MergeAccountConfirmationPersist; import org.opencdmp.model.persist.actionconfirmation.MergeAccountConfirmationPersist;
import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist; import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist;
@ -104,6 +106,7 @@ public class ActionConfirmationServiceImpl implements ActionConfirmationService
case MergeAccount -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildMergeAccountConfirmationEntity(model.getMergeAccountConfirmation()))); case MergeAccount -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildMergeAccountConfirmationEntity(model.getMergeAccountConfirmation())));
case DmpInvitation -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildDmpInvitationEntity(model.getDmpInvitation()))); case DmpInvitation -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildDmpInvitationEntity(model.getDmpInvitation())));
case RemoveCredential -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildMergeAccountConfirmationEntity(model.getRemoveCredentialRequest()))); case RemoveCredential -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildMergeAccountConfirmationEntity(model.getRemoveCredentialRequest())));
case UserInviteToTenant -> data.setData(this.xmlHandlingService.toXmlSafe(this.buildUserInviteToTenantRequestEntity(model.getUserInviteToTenantRequest())));
default -> throw new InternalError("unknown type: " + model.getType()); default -> throw new InternalError("unknown type: " + model.getType());
} }
data.setUpdatedAt(Instant.now()); data.setUpdatedAt(Instant.now());
@ -145,6 +148,17 @@ public class ActionConfirmationServiceImpl implements ActionConfirmationService
return data; return data;
} }
private @NotNull UserInviteToTenantRequestEntity buildUserInviteToTenantRequestEntity(UserInviteToTenantRequestPersist persist){
UserInviteToTenantRequestEntity data = new UserInviteToTenantRequestEntity();
if (persist == null) return data;
data.setEmail(persist.getEmail());
data.setTenantCode(persist.getTenantCode());
data.setRoles(persist.getRoles());
return data;
}
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug("deleting : {}", id); logger.debug("deleting : {}", id);

View File

@ -7,9 +7,7 @@ import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.fieldset.FieldSet;
import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.JAXBException;
import org.opencdmp.model.persist.UserMergeRequestPersist; import org.opencdmp.model.persist.*;
import org.opencdmp.model.persist.UserPersist;
import org.opencdmp.model.persist.UserRolePatchPersist;
import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist; import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist;
import org.opencdmp.model.user.User; import org.opencdmp.model.user.User;
@ -37,9 +35,13 @@ public interface UserService {
void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException; void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException;
void sendUserToTenantInvitation(UserTenantUsersInviteRequest users) throws InvalidApplicationException, JAXBException;
boolean doesTokenBelongToLoggedInUser(String token) throws InvalidApplicationException, IOException; boolean doesTokenBelongToLoggedInUser(String token) throws InvalidApplicationException, IOException;
void confirmMergeAccount(String token) throws InvalidApplicationException, IOException; void confirmMergeAccount(String token) throws InvalidApplicationException, IOException;
void confirmRemoveCredential(String token) throws InvalidApplicationException; void confirmRemoveCredential(String token) throws InvalidApplicationException;
void confirmUserInviteToTenant(String token) throws InvalidApplicationException;
} }

View File

@ -33,9 +33,8 @@ import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.scope.user.UserScope; import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity; import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity;
import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity; import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity;
import org.opencdmp.commons.types.notification.DataType; import org.opencdmp.commons.types.actionconfirmation.UserInviteToTenantRequestEntity;
import org.opencdmp.commons.types.notification.FieldInfo; import org.opencdmp.commons.types.notification.*;
import org.opencdmp.commons.types.notification.NotificationFieldData;
import org.opencdmp.commons.types.reference.DefinitionEntity; import org.opencdmp.commons.types.reference.DefinitionEntity;
import org.opencdmp.commons.types.user.AdditionalInfoEntity; import org.opencdmp.commons.types.user.AdditionalInfoEntity;
import org.opencdmp.commons.types.usercredential.UserCredentialDataEntity; import org.opencdmp.commons.types.usercredential.UserCredentialDataEntity;
@ -912,4 +911,145 @@ public class UserServiceImpl implements UserService {
throw new MyApplicationException("Token has expired!"); throw new MyApplicationException("Token has expired!");
} }
} }
public void sendUserToTenantInvitation(UserTenantUsersInviteRequest users) throws InvalidApplicationException, JAXBException {
String tenantName = null;
String tenantCode = null;
if (this.tenantScope.getTenantCode() != null && !this.tenantScope.getTenantCode().equals(this.tenantScope.getDefaultTenantCode())) {
TenantEntity tenantEntity = this.queryFactory.query(TenantQuery.class).disableTracking().authorize(AuthorizationFlags.AllExceptPublic).codes(this.tenantScope.getTenantCode()).isActive(IsActive.Active).first();
if (tenantEntity == null) throw new MyApplicationException("Tenant not found");
tenantName = tenantEntity.getName();
tenantCode = tenantEntity.getCode();
} else {
tenantName = "OpenCDMP";
tenantCode = this.tenantScope.getDefaultTenantCode();
}
for (UserInviteToTenantRequestPersist user: users.getUsers()) {
String token = this.createUserInviteToTenantConfirmation(user, tenantCode);
this.createTenantSpecificInvitationUserNotificationEvent(token, user.getEmail(), tenantName);
}
}
private String createUserInviteToTenantConfirmation(UserInviteToTenantRequestPersist model, String tenantCode) throws JAXBException, InvalidApplicationException {
ActionConfirmationPersist persist = new ActionConfirmationPersist();
persist.setType(ActionConfirmationType.UserInviteToTenant);
persist.setStatus(ActionConfirmationStatus.Requested);
persist.setToken(UUID.randomUUID().toString());
persist.setUserInviteToTenantRequest(new UserInviteToTenantRequestPersist());
persist.getUserInviteToTenantRequest().setEmail(model.getEmail());
persist.getUserInviteToTenantRequest().setRoles(model.getRoles());
persist.getUserInviteToTenantRequest().setTenantCode(tenantCode);
persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds()));
this.validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist);
this.actionConfirmationService.persist(persist, null);
try {
this.entityManager.disableTenantFilters();
} finally {
this.entityManager.reloadTenantFilters();
}
return persist.getToken();
}
private void createTenantSpecificInvitationUserNotificationEvent(String token, String email, String tenantName) throws InvalidApplicationException {
UserEntity currentUser = this.entityManager.find(UserEntity.class, this.userScope.getUserIdSafe());
if (currentUser == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{ this.userScope.getUserIdSafe(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
NotifyIntegrationEvent event = new NotifyIntegrationEvent();
List<ContactPair> contactPairs = new ArrayList<>();
contactPairs.add(new ContactPair(ContactInfoType.Email, email));
NotificationContactData contactData = new NotificationContactData(contactPairs, null, null);
event.setContactHint(this.jsonHandlingService.toJsonSafe(contactData));
event.setContactTypeHint(NotificationContactType.EMAIL);
event.setNotificationType(this.notificationProperties.getTenantSpecificInvitationUserType());
NotificationFieldData data = new NotificationFieldData();
List<FieldInfo> fieldInfoList = new ArrayList<>();
fieldInfoList.add(new FieldInfo("{userName}", DataType.String, currentUser.getName()));
fieldInfoList.add(new FieldInfo("{confirmationToken}", DataType.String, token));
fieldInfoList.add(new FieldInfo("{expiration_time}", DataType.String, this.secondsToTime(this.notificationProperties.getEmailExpirationTimeSeconds())));
fieldInfoList.add(new FieldInfo("{tenantName}", DataType.String, tenantName));
data.setFields(fieldInfoList);
event.setData(this.jsonHandlingService.toJsonSafe(data));
this.eventHandler.handle(event);
}
public void confirmUserInviteToTenant(String token) throws InvalidApplicationException {
ActionConfirmationEntity action;
try {
this.entityManager.disableTenantFilters();
action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.UserInviteToTenant).isActive(IsActive.Active).first();
} finally {
this.entityManager.reloadTenantFilters();
}
if (action == null)
throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.checkActionState(action);
UserInviteToTenantRequestEntity userInviteToTenantRequest = this.xmlHandlingService.fromXmlSafe(UserInviteToTenantRequestEntity.class, action.getData());
if (userInviteToTenantRequest == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{action.getId(), UserInviteToTenantRequestEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
TenantEntity tenantEntity = this.queryFactory.query(TenantQuery.class).disableTracking().authorize(AuthorizationFlags.AllExceptPublic).codes(userInviteToTenantRequest.getTenantCode()).isActive(IsActive.Active).first();
if (tenantEntity == null) throw new MyApplicationException("Tenant not found");
this.addUserToTenant(tenantEntity, userInviteToTenantRequest);
}
private void addUserToTenant(TenantEntity tenant, UserInviteToTenantRequestEntity userInviteToTenantRequest) throws InvalidApplicationException {
UUID userId = null;
try {
this.entityManager.disableTenantFilters();
UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).disableTracking().values(userInviteToTenantRequest.getEmail()).types(ContactInfoType.Email).first();
if (contactInfoEntity != null){
userId = contactInfoEntity.getUserId();
}
if (userId != null) {
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(userId).first();
if (userCredential == null) throw new MyApplicationException();
TenantUserEntity tenantUserEntity = new TenantUserEntity();
tenantUserEntity.setId(UUID.randomUUID());
tenantUserEntity.setUserId(userId);
tenantUserEntity.setIsActive(IsActive.Active);
tenantUserEntity.setTenantId(tenant.getId());
tenantUserEntity.setCreatedAt(Instant.now());
tenantUserEntity.setUpdatedAt(Instant.now());
this.entityManager.persist(tenantUserEntity);
this.eventBroker.emit(new UserAddedToTenantEvent(tenantUserEntity.getUserId(), tenantUserEntity.getTenantId()));
for (String role: userInviteToTenantRequest.getRoles()) {
UserRoleEntity item = new UserRoleEntity();
item.setId(UUID.randomUUID());
item.setUserId(userId);
item.setTenantId(tenant.getId());
item.setRole(role);
item.setCreatedAt(Instant.now());
this.entityManager.persist(item);
}
this.eventBroker.emit(new UserCredentialTouchedEvent(userCredential.getId(), userCredential.getExternalId()));
this.entityManager.flush();
this.userTouchedIntegrationEventHandler.handle(userId);
this.eventBroker.emit(new UserTouchedEvent(userId));
this.entityManager.flush();
for (String role: userInviteToTenantRequest.getRoles()) {
this.keycloakService.addUserToTenantRoleGroup(userCredential.getExternalId(), tenant.getCode(), role);
}
}
} finally {
this.entityManager.reloadTenantFilters();
}
}
} }

View File

@ -24,9 +24,7 @@ import org.opencdmp.model.builder.DmpAssociatedUserBuilder;
import org.opencdmp.model.builder.UserBuilder; import org.opencdmp.model.builder.UserBuilder;
import org.opencdmp.model.censorship.DmpAssociatedUserCensor; import org.opencdmp.model.censorship.DmpAssociatedUserCensor;
import org.opencdmp.model.censorship.UserCensor; import org.opencdmp.model.censorship.UserCensor;
import org.opencdmp.model.persist.UserMergeRequestPersist; import org.opencdmp.model.persist.*;
import org.opencdmp.model.persist.UserPersist;
import org.opencdmp.model.persist.UserRolePatchPersist;
import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist; import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist;
import org.opencdmp.model.result.QueryResult; import org.opencdmp.model.result.QueryResult;
import org.opencdmp.model.user.User; import org.opencdmp.model.user.User;
@ -334,4 +332,33 @@ public class UserController {
return true; return true;
} }
@PostMapping("invite-users-to-tenant")
@Transactional
@ValidationFilterAnnotation(validator = UserTenantUsersInviteRequest.UserTenantUsersInviteRequestValidator.ValidatorName, argumentName = "model")
public Boolean inviteUsersToTenant(@RequestBody UserTenantUsersInviteRequest users) throws InvalidApplicationException, JAXBException {
logger.debug(new MapLogEntry("send tenant invitation to users").And("users", users));
this.userTypeService.sendUserToTenantInvitation(users);
this.auditService.track(AuditableAction.User_InviteToTenant, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("users", users)
));
return true;
}
@GetMapping("confirm-invite-user-to-tenant/token/{token}")
@Transactional
public Boolean confirmInviteUserToTenant(@PathVariable("token") String token) throws InvalidApplicationException {
logger.debug(new MapLogEntry("confirm merge account to user").And("token", token));
this.userTypeService.confirmUserInviteToTenant(token);
this.auditService.track(AuditableAction.User_InviteToTenantConfirm, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("token", token)
));
return true;
}
} }

View File

@ -13,4 +13,5 @@ notification:
descriptionTemplateInvitationType: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B descriptionTemplateInvitationType: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B
contactSupportType: 5B1D6C52-88F9-418B-9B8A-6F1F963D9EAD contactSupportType: 5B1D6C52-88F9-418B-9B8A-6F1F963D9EAD
publicContactSupportType: B542B606-ACC6-4629-ADEF-4D8EE2F01222 publicContactSupportType: B542B606-ACC6-4629-ADEF-4D8EE2F01222
tenantSpecificInvitationUserType: 497dada5-eccc-4bc0-9e0b-63e22b4eb0be
contactSupportEmail: support@dmp.com contactSupportEmail: support@dmp.com

View File

@ -57,8 +57,8 @@
<button [disabled]="saving" mat-menu-item (click)="formSubmit()" type="button">{{ 'DMP-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button> <button [disabled]="saving" mat-menu-item (click)="formSubmit()" type="button">{{ 'DMP-EDITOR.ACTIONS.SAVE-AND-CONTINUE' | translate }}</button>
</mat-menu> </mat-menu>
</div> </div>
<div class="col-auto d-flex align-items-center" [matTooltipDisabled]="canFinalize && formGroup.pristine" matTooltip="{{'DMP-EDITOR.ACTIONS.FINALIZE.CAN-NOT-FINALIZE' | translate}}"> <div class="col-auto d-flex align-items-center" *ngIf="canFinalize" [matTooltipDisabled]="canFinalize && formGroup.pristine" matTooltip="{{'DMP-EDITOR.ACTIONS.FINALIZE.CAN-NOT-FINALIZE' | translate}}">
<button [disabled]="!canFinalize || !formGroup.pristine" mat-raised-button class="save-btn mr-2" type="button" (click)="finalize()">{{ 'DMP-EDITOR.ACTIONS.FINALIZE.FINALIZE' | translate }}</button> <button [disabled]="!formGroup.pristine" mat-raised-button class="save-btn mr-2" type="button" (click)="finalize()">{{ 'DMP-EDITOR.ACTIONS.FINALIZE.FINALIZE' | translate }}</button>
</div> </div>
<div *ngIf="formGroup.pristine" class="col-auto d-flex align-items-center"> <div *ngIf="formGroup.pristine" class="col-auto d-flex align-items-center">
<button [disabled]="saving" *ngIf="isLocked" mat-raised-button class="save-btn mr-2" type="button">{{ 'DMP-EDITOR.ACTIONS.LOCKED' | translate}}</button> <button [disabled]="saving" *ngIf="isLocked" mat-raised-button class="save-btn mr-2" type="button">{{ 'DMP-EDITOR.ACTIONS.LOCKED' | translate}}</button>

View File

@ -660,15 +660,18 @@ public class DescriptionTemplateXmlMigrationService {
ExternalFetcherApiSourceConfigurationEntity apiEntity = new ExternalFetcherApiSourceConfigurationEntity(); ExternalFetcherApiSourceConfigurationEntity apiEntity = new ExternalFetcherApiSourceConfigurationEntity();
String source = persist.getAutoCompleteOptions() != null ? persist.getAutoCompleteOptions().getSource() : null;
if ( source == null || source.isEmpty()) {
URI uri; URI uri;
if (persist.getUrl().contains("?")) { if (persist.getUrl().contains("?")) {
uri = new URI(persist.getUrl().substring(0, persist.getUrl().trim().lastIndexOf("?"))); uri = new URI(persist.getUrl().substring(0, persist.getUrl().trim().lastIndexOf("?")));
} else { } else {
uri = new URI(persist.getUrl().trim()); uri = new URI(persist.getUrl().trim());
} }
String source = persist.getAutoCompleteOptions().getSource(); source = uri.getHost();
source = source != null && !source.isEmpty() ? source : uri.getHost(); }
String parsedUrl = persist.getUrl().trim(); String parsedUrl = persist.getUrl().trim();
parsedUrl = parsedUrl.replace("%20", " "); parsedUrl = parsedUrl.replace("%20", " ");
parsedUrl = parsedUrl.replace("%22", "\""); parsedUrl = parsedUrl.replace("%22", "\"");
@ -705,14 +708,14 @@ public class DescriptionTemplateXmlMigrationService {
data.setResultsArrayPath(persist.getOptionsRoot()); data.setResultsArrayPath(persist.getOptionsRoot());
if (persist.getAutoCompleteOptions() == null && this.conventionService.isNullOrEmpty(persist.getAutoCompleteOptions().getLabel())) { if (persist.getAutoCompleteOptions() != null && !this.conventionService.isNullOrEmpty(persist.getAutoCompleteOptions().getLabel())) {
data.setFieldsMapping(new ArrayList<>()); data.setFieldsMapping(new ArrayList<>());
ResultFieldsMappingConfigurationEntity labelField = new ResultFieldsMappingConfigurationEntity(); ResultFieldsMappingConfigurationEntity labelField = new ResultFieldsMappingConfigurationEntity();
labelField.setCode(ReferenceEntity.KnownFields.Label); labelField.setCode(ReferenceEntity.KnownFields.Label);
labelField.setResponsePath(persist.getAutoCompleteOptions().getLabel()); labelField.setResponsePath(persist.getAutoCompleteOptions().getLabel());
data.getFieldsMapping().add(labelField); data.getFieldsMapping().add(labelField);
} }
if (persist.getAutoCompleteOptions() == null && this.conventionService.isNullOrEmpty(persist.getAutoCompleteOptions().getValue())) { if (persist.getAutoCompleteOptions() != null && !this.conventionService.isNullOrEmpty(persist.getAutoCompleteOptions().getValue())) {
ResultFieldsMappingConfigurationEntity idField = new ResultFieldsMappingConfigurationEntity(); ResultFieldsMappingConfigurationEntity idField = new ResultFieldsMappingConfigurationEntity();
idField.setCode(ReferenceEntity.KnownFields.ReferenceId); idField.setCode(ReferenceEntity.KnownFields.ReferenceId);
idField.setResponsePath(persist.getAutoCompleteOptions().getValue()); idField.setResponsePath(persist.getAutoCompleteOptions().getValue());

View File

@ -48,6 +48,9 @@ notification:
- #publicContactSupportType - #publicContactSupportType
type: B542B606-ACC6-4629-ADEF-4D8EE2F01222 type: B542B606-ACC6-4629-ADEF-4D8EE2F01222
contacts: [ email ] contacts: [ email ]
- #tenantSpecificInvitationUserType
type: 497dada5-eccc-4bc0-9e0b-63e22b4eb0be
contacts: [ email ]
message: message:
email: email:
flows: flows:
@ -386,6 +389,29 @@ notification:
bcc-mode: 0 bcc-mode: 0
allow-attachments: false allow-attachments: false
cipher-fields: [ ] cipher-fields: [ ]
- #tenantSpecificInvitationUserType
key: 497dada5-eccc-4bc0-9e0b-63e22b4eb0be
subject-path: classpath:notification_templates/tenantspecificinvitationuser/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/tenantspecificinvitationuser/email/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}", "{tenantName}" ]
optional:
- key: "{expiration_time}"
value: ---
formatting:
'[{userName}]': null
'[{tenantName}]': null
'[{installation-url}]': null
'[{expiration_time}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
template-cache: template-cache:
prefix: ${CACHE_DISAMBIGUATION:} prefix: ${CACHE_DISAMBIGUATION:}
key-pattern: "{prefix}:Notification_Message_Email_Template:{key}:v0" key-pattern: "{prefix}:Notification_Message_Email_Template:{key}:v0"