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

This commit is contained in:
Sofia Papacharalampous 2024-04-04 13:21:38 +03:00
commit 8cec0c3552
40 changed files with 335 additions and 276 deletions

View File

@ -1,9 +1,9 @@
package gr.cite.annotation.web; package gr.cite.annotation.web;
import gr.cite.annotation.web.interceptors.UserInterceptor;
import gr.cite.annotation.web.scope.tenant.TenantInterceptor; import gr.cite.annotation.web.scope.tenant.TenantInterceptor;
import gr.cite.annotation.web.scope.tenant.TenantScopeClaimInterceptor; import gr.cite.annotation.web.scope.tenant.TenantScopeClaimInterceptor;
import gr.cite.annotation.web.scope.tenant.TenantScopeHeaderInterceptor; import gr.cite.annotation.web.scope.tenant.TenantScopeHeaderInterceptor;
import gr.cite.annotation.web.scope.user.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

View File

@ -29,6 +29,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -104,7 +105,7 @@ public class SecurityConfiguration {
//In the example below, the default client handler will be ignored by the resolver //In the example below, the default client handler will be ignored by the resolver
@Override @Override
public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() { public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() {
return List.of(PermissionClientAuthorizationHandler.class); return new ArrayList<>();
} }
}; };
} }

View File

@ -1,17 +0,0 @@
//package gr.cite.intelcomp.stiviewer.web.controllers.error;
//
//import gr.cite.tools.exception.MyValidationException;
//import org.springframework.http.HttpStatus;
//import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.ControllerAdvice;
//import org.springframework.web.bind.annotation.ExceptionHandler;
//import org.springframework.web.context.request.WebRequest;
//
//@ControllerAdvice
//public class GenericErrorHandler {
//
// @ExceptionHandler(MyValidationException.class)
// public ResponseEntity<MyValidationException> handleValidationException(MyValidationException e, WebRequest webRequest) {
// return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(e);
// }
//}

View File

@ -1,92 +0,0 @@
package gr.cite.annotation.web.interceptors;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.lock.LockByKeyManager;
import gr.cite.annotation.common.scope.user.UserScope;
import gr.cite.annotation.data.UserCredentialEntity;
import gr.cite.annotation.model.UserCredential;
import gr.cite.annotation.query.UserCredentialQuery;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import java.util.UUID;
@Component
public class UserInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class));
private final UserScope userScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final UserInterceptorCacheService userInterceptorCacheService;
private final QueryFactory queryFactory;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public UserInterceptor(
UserScope userScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
UserInterceptorCacheService userInterceptorCacheService,
QueryFactory queryFactory) {
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.userInterceptorCacheService = userInterceptorCacheService;
this.queryFactory = queryFactory;
}
@Override
public void preHandle(WebRequest request) {
UUID userId = null;
if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
if (cacheValue != null) {
userId = cacheValue.getUserId();
} else {
userId = this.findExistingUserFromDbForce(subjectId);
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
this.userInterceptorCacheService.put(cacheValue);
}
}
this.userScope.setUserId(userId);
}
private UUID findExistingUserFromDbForce(String subjectId){
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
if (userCredential != null) {
return userCredential.getUserId();
} else {
throw new MyForbiddenException("User not created try again.");
}
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.userScope.setUserId(null);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -1,10 +0,0 @@
package gr.cite.annotation.web.interceptors;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.user-by-subject-id")
public class UserInterceptorCacheOptions extends CacheOptions {
}

View File

@ -1,66 +0,0 @@
package gr.cite.annotation.web.interceptors;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.UUID;
@Service
public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
public static class UserInterceptorCacheValue {
public UserInterceptorCacheValue() {
}
public UserInterceptorCacheValue(String subjectId, UUID userId) {
this.subjectId = subjectId;
this.userId = userId;
}
private String subjectId;
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@Autowired
public UserInterceptorCacheService(UserInterceptorCacheOptions options) {
super(options);
}
@Override
protected Class<UserInterceptorCacheValue> valueClass() {
return UserInterceptorCacheValue.class;
}
@Override
public String keyOf(UserInterceptorCacheValue value) {
return this.buildKey(value.getSubjectId());
}
public String buildKey(String subject) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$subject$", subject);
return this.generateKey(keyParts);
}
}

View File

@ -20,11 +20,25 @@ idpclient:
Roles: Roles:
- type: resource_access - type: resource_access
path: dmp_web.roles path: dmp_web.roles
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
GlobalRoles:
- type: resource_access
path: dmp_web.roles
TenantRoles:
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
Scope: Scope:
- type: scope - type: scope
AccessToken: AccessToken:
- type: x-access-token - type: x-access-token
visibility: SENSITIVE visibility: SENSITIVE
Tenant:
- type: x-tenant
IssuedAt: IssuedAt:
- type: iat - type: iat
Issuer: Issuer:
@ -37,5 +51,8 @@ idpclient:
- type: azp - type: azp
Authorities: Authorities:
- type: authorities - type: authorities
ExternalProviderName: TenantCodes:
- type: identity_provider - type: tenant_roles
filterBy: "(.*):(.*)"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g2]]"

View File

@ -2,10 +2,10 @@ permissions:
policies: policies:
DeferredAffiliation: DeferredAffiliation:
roles: roles:
- Admin - TenantAdmin
- User - TenantUser
- Manager - TenantManager
- DescriptionTemplateEditor - TenantDescriptionTemplateEditor
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
@ -19,14 +19,14 @@ permissions:
EditTenant: EditTenant:
roles: roles:
- Admin - Admin
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteTenant: DeleteTenant:
roles: roles:
- Admin - Admin
claims: [ ] claims: [ ]
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
AllowNoTenant: AllowNoTenant:
@ -39,47 +39,47 @@ permissions:
# Users # Users
BrowseUser: BrowseUser:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditUser: EditUser:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteUser: DeleteUser:
roles: roles:
- Admin - TenantAdmin
claims: [ ] claims: [ ]
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
#Annotation #Annotation
BrowseAnnotation: BrowseAnnotation:
roles: roles:
- Admin - TenantAdmin
entityAffiliated: true entityAffiliated: true
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
NewAnnotation: NewAnnotation:
roles: roles:
- Admin - TenantAdmin
entityAffiliated: true entityAffiliated: true
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditAnnotation: EditAnnotation:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
DeleteAnnotation: DeleteAnnotation:
roles: roles:
- Admin - TenantAdmin
entityAffiliated: false entityAffiliated: false
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
@ -87,13 +87,13 @@ permissions:
#Tenant Configuration #Tenant Configuration
BrowseTenantConfiguration: BrowseTenantConfiguration:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
EditTenantConfiguration: EditTenantConfiguration:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false

View File

@ -82,6 +82,7 @@ public class TenantEntityManager {
} }
public void enableTenantFilters() throws InvalidApplicationException { public void enableTenantFilters() throws InvalidApplicationException {
if (!tenantScope.isSet()) return;
if(!tenantScope.isDefaultTenant()) { if(!tenantScope.isDefaultTenant()) {
this.entityManager this.entityManager
.unwrap(Session.class) .unwrap(Session.class)

View File

@ -1,5 +1,6 @@
package gr.cite.annotation.integrationevent; package gr.cite.annotation.integrationevent;
import gr.cite.annotation.data.QueueOutboxEntity;
import gr.cite.annotation.integrationevent.outbox.OutboxProperties; import gr.cite.annotation.integrationevent.outbox.OutboxProperties;
import gr.cite.annotation.integrationevent.outbox.OutboxRepositoryImpl; import gr.cite.annotation.integrationevent.outbox.OutboxRepositoryImpl;
import gr.cite.queueoutbox.IntegrationEventContextCreator; import gr.cite.queueoutbox.IntegrationEventContextCreator;
@ -56,7 +57,11 @@ public class OutboxIntegrationEventConfigurer extends OutboxConfigurer {
@Bean @Bean
public IntegrationEventContextCreator integrationEventContextCreator() { public IntegrationEventContextCreator integrationEventContextCreator() {
return (message) -> new IntegrationEventContextImpl(); return (message) -> {
IntegrationEventContextImpl integrationEventContext = new IntegrationEventContextImpl();
if (message instanceof QueueOutboxEntity) integrationEventContext.setTenant(((QueueOutboxEntity)message).getTenantId());
return integrationEventContext;
};
} }
@Bean @Bean

View File

@ -1,11 +1,13 @@
package gr.cite.annotation.integrationevent.inbox; package gr.cite.annotation.integrationevent.inbox;
import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimNames;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -22,12 +24,15 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
this.isAuthenticated = isAuthenticated; this.isAuthenticated = isAuthenticated;
} }
public static InboxPrincipal build(IntegrationEventProperties properties) { public static InboxPrincipal build(IntegrationEventProperties properties, ClaimExtractorProperties claimExtractorProperties) {
InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId"); InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId");
inboxPrincipal.put("client_id", properties.getAppId()); List<ClaimExtractorProperties.KeyPath> clientKey = claimExtractorProperties.getMapping().getOrDefault("Client", null);
inboxPrincipal.put(clientKey != null && clientKey.getFirst() != null ? clientKey.getFirst().getType() : "client_id", properties.getAppId());
inboxPrincipal.put("active", "true"); inboxPrincipal.put("active", "true");
inboxPrincipal.put("nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString()); List<ClaimExtractorProperties.KeyPath> notBeforeKey = claimExtractorProperties.getMapping().getOrDefault("NotBefore", null);
inboxPrincipal.put("exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString()); inboxPrincipal.put(notBeforeKey != null && notBeforeKey.getFirst() != null ? notBeforeKey.getFirst().getType() :"nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString());
List<ClaimExtractorProperties.KeyPath> expiresAt = claimExtractorProperties.getMapping().getOrDefault("ExpiresAt", null);
inboxPrincipal.put(expiresAt != null && expiresAt.getFirst() != null ? expiresAt.getFirst().getType() :"exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString());
return inboxPrincipal; return inboxPrincipal;
} }
@ -45,7 +50,10 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
public List<String> getClaimAsStringList(String claim) { public List<String> getClaimAsStringList(String claim) {
if (claims == null) if (claims == null)
return null; return null;
return this.getClaimAsStringList(claim); if (this.claims.containsKey(claim)){
return List.of(this.claims.get(claim).toString());
}
return null;
} }
@Override @Override

View File

@ -245,6 +245,12 @@ public class InboxRepositoryImpl implements InboxRepository {
queueMessage.setId(UUID.randomUUID()); queueMessage.setId(UUID.randomUUID());
Object tenantId = inboxCreatorParams.getHeaders() != null ? inboxCreatorParams.getHeaders().getOrDefault(IntegrationEventMessageConstants.TENANT, null) : null; Object tenantId = inboxCreatorParams.getHeaders() != null ? inboxCreatorParams.getHeaders().getOrDefault(IntegrationEventMessageConstants.TENANT, null) : null;
if (tenantId instanceof UUID) queueMessage.setTenantId((UUID) tenantId); if (tenantId instanceof UUID) queueMessage.setTenantId((UUID) tenantId);
else if (tenantId instanceof String) {
try {
queueMessage.setTenantId(UUID.fromString((String) tenantId));
} catch (Exception e) {
}
}
queueMessage.setExchange(this.inboxProperties.getExchange()); queueMessage.setExchange(this.inboxProperties.getExchange());
queueMessage.setRoute(inboxCreatorParams.getRoutingKey()); queueMessage.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName()); queueMessage.setQueue(inboxCreatorParams.getQueueName());

View File

@ -15,6 +15,7 @@ import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.EntityUserQuery; import gr.cite.annotation.query.EntityUserQuery;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
@ -83,7 +84,9 @@ public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements Ann
} }
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();

View File

@ -15,6 +15,7 @@ import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.EntityUserQuery; import gr.cite.annotation.query.EntityUserQuery;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
@ -85,7 +86,9 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
} }
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();
@ -114,6 +117,7 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
data.setId(UUID.randomUUID()); data.setId(UUID.randomUUID());
data.setEntityId(entityEvent.getEntityId()); data.setEntityId(entityEvent.getEntityId());
data.setUserId(user); data.setUserId(user);
data.setTenantId(properties.getTenantId());
data.setCreatedAt(Instant.now()); data.setCreatedAt(Instant.now());
data.setUpdatedAt(Instant.now()); data.setUpdatedAt(Instant.now());
data.setIsActive(IsActive.Active); data.setIsActive(IsActive.Active);

View File

@ -10,6 +10,7 @@ import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
import gr.cite.annotation.integrationevent.inbox.InboxPrincipal; import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.service.tenant.TenantService; import gr.cite.annotation.service.tenant.TenantService;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
@ -53,7 +54,9 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
try (FakeRequestScope ignored = new FakeRequestScope()) { try (FakeRequestScope ignored = new FakeRequestScope()) {
try { try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class); TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class);
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId()))))

View File

@ -10,6 +10,8 @@ import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.persist.TenantTouchedIntegrationEventPersist; import gr.cite.annotation.model.persist.TenantTouchedIntegrationEventPersist;
import gr.cite.annotation.service.tenant.TenantService; import gr.cite.annotation.service.tenant.TenantService;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.ValidatorFactory;
@ -59,7 +61,8 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
try (FakeRequestScope ignored = new FakeRequestScope()) { try (FakeRequestScope ignored = new FakeRequestScope()) {
try { try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties)); ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();

View File

@ -14,6 +14,7 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.Tenant; import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.annotation.service.user.UserService; import gr.cite.annotation.service.user.UserService;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.exception.MyValidationException;
@ -89,7 +90,9 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
} }
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
UserRemovalConsistencyHandler userRemovalConsistencyHandler = this.applicationContext.getBean(UserRemovalConsistencyHandler.class); UserRemovalConsistencyHandler userRemovalConsistencyHandler = this.applicationContext.getBean(UserRemovalConsistencyHandler.class);
if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId())))) if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId()))))

View File

@ -13,6 +13,7 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.Tenant; import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.annotation.service.user.UserService; import gr.cite.annotation.service.user.UserService;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.BaseFieldSet;
@ -82,7 +83,9 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
} }
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();

View File

@ -75,6 +75,7 @@ public class TenantServiceImpl implements TenantService {
data.setCode(model.getCode()); data.setCode(model.getCode());
data.setIsActive(IsActive.Active); data.setIsActive(IsActive.Active);
data.setCreatedAt(Instant.now()); data.setCreatedAt(Instant.now());
data.setUpdatedAt(Instant.now());
this.entityManager.persist(data); this.entityManager.persist(data);
} else { } else {

View File

@ -5,15 +5,19 @@ import gr.cite.annotation.authorization.AuthorizationFlags;
import gr.cite.annotation.authorization.Permission; import gr.cite.annotation.authorization.Permission;
import gr.cite.annotation.common.JsonHandlingService; import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.enums.IsActive;
import gr.cite.annotation.common.scope.tenant.TenantScope;
import gr.cite.annotation.convention.ConventionService; import gr.cite.annotation.convention.ConventionService;
import gr.cite.annotation.data.*; import gr.cite.annotation.data.*;
import gr.cite.annotation.integrationevent.inbox.usertouch.UserTouchedIntegrationEvent; import gr.cite.annotation.integrationevent.inbox.usertouch.UserTouchedIntegrationEvent;
import gr.cite.annotation.model.Annotation;
import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.model.User; import gr.cite.annotation.model.User;
import gr.cite.annotation.model.builder.UserBuilder; import gr.cite.annotation.model.builder.UserBuilder;
import gr.cite.annotation.model.deleter.TenantUserDeleter; import gr.cite.annotation.model.deleter.TenantUserDeleter;
import gr.cite.annotation.model.deleter.UserContactInfoDeleter; import gr.cite.annotation.model.deleter.UserContactInfoDeleter;
import gr.cite.annotation.model.deleter.UserCredentialDeleter; import gr.cite.annotation.model.deleter.UserCredentialDeleter;
import gr.cite.annotation.model.deleter.UserDeleter; import gr.cite.annotation.model.deleter.UserDeleter;
import gr.cite.annotation.query.TenantQuery;
import gr.cite.annotation.query.TenantUserQuery; import gr.cite.annotation.query.TenantUserQuery;
import gr.cite.annotation.query.UserContactInfoQuery; import gr.cite.annotation.query.UserContactInfoQuery;
import gr.cite.annotation.query.UserCredentialQuery; import gr.cite.annotation.query.UserCredentialQuery;
@ -31,6 +35,8 @@ import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
@ -57,6 +63,9 @@ public class UserServiceImpl implements UserService {
private final BuilderFactory builderFactory; private final BuilderFactory builderFactory;
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
private final TenantScope tenantScope;
private final MessageSource messageSource;
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
@ -65,7 +74,7 @@ public class UserServiceImpl implements UserService {
DeleterFactory deleterFactory, DeleterFactory deleterFactory,
ConventionService conventionService, ConventionService conventionService,
TenantEntityManager entityManager, TenantEntityManager entityManager,
BuilderFactory builderFactory, QueryFactory queryFactory, BuilderFactory builderFactory, QueryFactory queryFactory, TenantScope tenantScope, MessageSource messageSource,
JsonHandlingService jsonHandlingService) { JsonHandlingService jsonHandlingService) {
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory; this.deleterFactory = deleterFactory;
@ -73,6 +82,8 @@ public class UserServiceImpl implements UserService {
this.entityManager = entityManager; this.entityManager = entityManager;
this.builderFactory = builderFactory; this.builderFactory = builderFactory;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.messageSource = messageSource;
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
} }
@ -197,19 +208,31 @@ public class UserServiceImpl implements UserService {
.userIds(userId) .userIds(userId)
.isActive(IsActive.Active) .isActive(IsActive.Active)
.collect(); .collect();
List<UUID> updatedCreatedIds = new ArrayList<>(); List<UUID> updatedCreatedIds = new ArrayList<>();
if (models != null) { if (models != null) {
List<TenantEntity> tenantEntities = this.queryFactory.query(TenantQuery.class)
.ids(models.stream().map(UserTouchedIntegrationEvent.TenantUser::getTenant).toList())
.isActive(IsActive.Active)
.collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
for (UserTouchedIntegrationEvent.TenantUser model : models) { for (UserTouchedIntegrationEvent.TenantUser model : models) {
TenantUserEntity data = items.stream().filter(x -> x.getTenantId().equals(model.getTenant())).findFirst().orElse(null); TenantUserEntity data = items.stream().filter(x -> x.getTenantId().equals(model.getTenant())).findFirst().orElse(null);
if (data == null) { if (data == null) {
data = new TenantUserEntity(); try {
data.setId(UUID.randomUUID()); TenantEntity tenant = tenantEntities.stream().filter(x -> x.getId().equals(model.getTenant())).findFirst().orElse(null);
data.setUserId(userId); if (tenant == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getTenant(), Tenant.class.getSimpleName()}, LocaleContextHolder.getLocale()));
data.setTenantId(model.getTenant()); this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), tenant.getId(), tenant.getCode());
data.setCreatedAt(Instant.now()); data = new TenantUserEntity();
data.setUpdatedAt(Instant.now()); data.setId(UUID.randomUUID());
data.setIsActive(IsActive.Active); data.setUserId(userId);
entityManager.persist(data); data.setTenantId(model.getTenant());
data.setCreatedAt(Instant.now());
data.setUpdatedAt(Instant.now());
data.setIsActive(IsActive.Active);
entityManager.persist(data);
} finally {
this.tenantScope.removeTempTenant(this.entityManager.getEntityManager());
}
} }
updatedCreatedIds.add(data.getId()); updatedCreatedIds.add(data.getId());
} }

View File

@ -145,6 +145,8 @@ public class AuditableAction {
public static final EventId Maintenance_GenerateElastic = new EventId(220000, "Maintenance_GenerateElastic"); public static final EventId Maintenance_GenerateElastic = new EventId(220000, "Maintenance_GenerateElastic");
public static final EventId Maintenance_ClearElastic = new EventId(230000, "Maintenance_ClearElastic"); public static final EventId Maintenance_ClearElastic = new EventId(230000, "Maintenance_ClearElastic");
public static final EventId Maintenance_SendUserTouchEvents = new EventId(230001, "Maintenance_SendUserTouchEvents");
public static final EventId Maintenance_SendTenantTouchEvents = new EventId(230002, "Maintenance_SendTenantTouchEvents");
public static final EventId Principal_Lookup = new EventId(240000, "Principal_Lookup"); public static final EventId Principal_Lookup = new EventId(240000, "Principal_Lookup");
public static final EventId Principal_MyTenants = new EventId(240001, "Principal_MyTenants"); public static final EventId Principal_MyTenants = new EventId(240001, "Principal_MyTenants");

View File

@ -22,6 +22,8 @@ public final class Permission {
public static String PublicBrowseReferenceType = "PublicBrowseReferenceType"; public static String PublicBrowseReferenceType = "PublicBrowseReferenceType";
//Elastic //Elastic
public static String ManageElastic = "ManageElastic"; public static String ManageElastic = "ManageElastic";
//Queue Events
public static String ManageQueueEvents = "ManageQueueEvents";
//Deposit //Deposit

View File

@ -87,6 +87,7 @@ public class TenantEntityManager {
} }
public void enableTenantFilters() throws InvalidApplicationException { public void enableTenantFilters() throws InvalidApplicationException {
if (!tenantScope.isSet()) return;
if(!tenantScope.isDefaultTenant()) { if(!tenantScope.isDefaultTenant()) {
this.entityManager this.entityManager
.unwrap(Session.class) .unwrap(Session.class)

View File

@ -1,6 +1,7 @@
package eu.eudat.integrationevent; package eu.eudat.integrationevent;
import eu.eudat.data.QueueOutboxEntity;
import eu.eudat.integrationevent.outbox.OutboxProperties; import eu.eudat.integrationevent.outbox.OutboxProperties;
import eu.eudat.integrationevent.outbox.OutboxRepositoryImpl; import eu.eudat.integrationevent.outbox.OutboxRepositoryImpl;
import gr.cite.queueoutbox.IntegrationEventContextCreator; import gr.cite.queueoutbox.IntegrationEventContextCreator;
@ -57,7 +58,11 @@ public class OutboxIntegrationEventConfigurer extends OutboxConfigurer {
@Bean @Bean
public IntegrationEventContextCreator integrationEventContextCreator() { public IntegrationEventContextCreator integrationEventContextCreator() {
return (message) -> new IntegrationEventContextImpl(); return (message) -> {
IntegrationEventContextImpl integrationEventContext = new IntegrationEventContextImpl();
if (message instanceof QueueOutboxEntity) integrationEventContext.setTenant(((QueueOutboxEntity)message).getTenantId());
return integrationEventContext;
};
} }
@Bean @Bean

View File

@ -1,6 +1,7 @@
package eu.eudat.integrationevent.inbox; package eu.eudat.integrationevent.inbox;
import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimNames;
@ -11,8 +12,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class InboxPrincipal implements MyPrincipal, ClaimAccessor { public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
private Map<String, Object> claims; private final Map<String, Object> claims;
private boolean isAuthenticated;
private final boolean isAuthenticated;
public InboxPrincipal(Boolean isAuthenticated, String name) { public InboxPrincipal(Boolean isAuthenticated, String name) {
this.claims = new HashMap<>(); this.claims = new HashMap<>();
@ -20,6 +22,18 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
this.isAuthenticated = isAuthenticated; this.isAuthenticated = isAuthenticated;
} }
public static InboxPrincipal build(IntegrationEventProperties properties, ClaimExtractorProperties claimExtractorProperties) {
InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId");
List<ClaimExtractorProperties.KeyPath> clientKey = claimExtractorProperties.getMapping().getOrDefault("Client", null);
inboxPrincipal.put(clientKey != null && clientKey.getFirst() != null ? clientKey.getFirst().getType() : "client_id", properties.getAppId());
inboxPrincipal.put("active", "true");
List<ClaimExtractorProperties.KeyPath> notBeforeKey = claimExtractorProperties.getMapping().getOrDefault("NotBefore", null);
inboxPrincipal.put(notBeforeKey != null && notBeforeKey.getFirst() != null ? notBeforeKey.getFirst().getType() :"nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString());
List<ClaimExtractorProperties.KeyPath> expiresAt = claimExtractorProperties.getMapping().getOrDefault("ExpiresAt", null);
inboxPrincipal.put(expiresAt != null && expiresAt.getFirst() != null ? expiresAt.getFirst().getType() :"exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString());
return inboxPrincipal;
}
@Override @Override
public Boolean isAuthenticated() { public Boolean isAuthenticated() {
return this.isAuthenticated; return this.isAuthenticated;
@ -32,8 +46,12 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
@Override @Override
public List<String> getClaimAsStringList(String claim) { public List<String> getClaimAsStringList(String claim) {
if (claims == null) return null; if (claims == null)
return this.getClaimAsStringList(claim); return null;
if (this.claims.containsKey(claim)){
return List.of(this.claims.get(claim).toString());
}
return null;
} }
@Override @Override
@ -44,13 +62,4 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
public void put(String key, Object value) { public void put(String key, Object value) {
this.claims.put(key, value); this.claims.put(key, value);
} }
public static InboxPrincipal build(IntegrationEventProperties properties) {
InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId");
inboxPrincipal.put("client_id", properties.getAppId());
inboxPrincipal.put("active", "true");
inboxPrincipal.put("nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString());
inboxPrincipal.put("exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString());
return inboxPrincipal;
}
} }

View File

@ -1,7 +1,5 @@
package eu.eudat.integrationevent.outbox.tenanttouched; package eu.eudat.integrationevent.outbox.tenanttouched;
import javax.management.InvalidApplicationException;
public interface TenantTouchedIntegrationEventHandler { public interface TenantTouchedIntegrationEventHandler {
void handle(TenantTouchedIntegrationEvent event); void handle(TenantTouchedIntegrationEvent event);

View File

@ -1,18 +1,24 @@
package eu.eudat.integrationevent.outbox.tenanttouched; package eu.eudat.integrationevent.outbox.tenanttouched;
import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.integrationevent.outbox.OutboxIntegrationEvent; import eu.eudat.integrationevent.outbox.OutboxIntegrationEvent;
import eu.eudat.integrationevent.outbox.OutboxService; import eu.eudat.integrationevent.outbox.OutboxService;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID; import java.util.UUID;
@Component("outboxtenanttouchedintegrationeventhandler")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIntegrationEventHandler { public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIntegrationEventHandler {
private final OutboxService outboxService; private final OutboxService outboxService;
public TenantTouchedIntegrationEventHandlerImpl(OutboxService outboxService) { public TenantTouchedIntegrationEventHandlerImpl(OutboxService outboxService) {
this.outboxService = outboxService; this.outboxService = outboxService;
} }
@Override @Override
public void handle(TenantTouchedIntegrationEvent event) { public void handle(TenantTouchedIntegrationEvent event) {
OutboxIntegrationEvent message = new OutboxIntegrationEvent(); OutboxIntegrationEvent message = new OutboxIntegrationEvent();
message.setMessageId(UUID.randomUUID()); message.setMessageId(UUID.randomUUID());

View File

@ -194,7 +194,7 @@ public class TenantUserQuery extends QueryBase<TenantUserEntity> {
@Override @Override
protected String fieldNameOf(FieldResolver item) { protected String fieldNameOf(FieldResolver item) {
if (item.match(TenantUser._id)) return TenantUserEntity._id; if (item.match(TenantUser._id)) return TenantUserEntity._id;
else if (item.match(TenantUser._tenant, Tenant._id)) return TenantUserEntity._tenantId; else if (item.match(TenantUser._tenant)) return TenantUserEntity._tenantId;
else if (item.prefix(TenantUser._tenant)) return TenantUserEntity._tenantId; else if (item.prefix(TenantUser._tenant)) return TenantUserEntity._tenantId;
else if (item.match(TenantUser._isActive)) return TenantUserEntity._isActive; else if (item.match(TenantUser._isActive)) return TenantUserEntity._isActive;
else if (item.match(TenantUser._createdAt)) return TenantUserEntity._createdAt; else if (item.match(TenantUser._createdAt)) return TenantUserEntity._createdAt;

View File

@ -25,6 +25,6 @@ public interface TenantService {
Tenant persist(TenantPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, Tenant persist(TenantPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException,
InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException; InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
Tenant decryptTenant(Tenant model) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException; Tenant decryptTenant(Tenant model) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException;
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;
} }

View File

@ -9,6 +9,10 @@ import eu.eudat.convention.ConventionService;
import eu.eudat.data.TenantEntity; import eu.eudat.data.TenantEntity;
import eu.eudat.data.TenantEntityManager; import eu.eudat.data.TenantEntityManager;
import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.errorcode.ErrorThesaurusProperties;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler;
import eu.eudat.model.Tenant; import eu.eudat.model.Tenant;
import eu.eudat.model.builder.TenantBuilder; import eu.eudat.model.builder.TenantBuilder;
import eu.eudat.model.deleter.TenantDeleter; import eu.eudat.model.deleter.TenantDeleter;
@ -67,18 +71,20 @@ public class TenantServiceImpl implements TenantService {
private final EncryptionService encryptionService; private final EncryptionService encryptionService;
private final TenantProperties properties; private final TenantProperties properties;
private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler;
private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler;
@Autowired @Autowired
public TenantServiceImpl( public TenantServiceImpl(
TenantEntityManager entityManager, TenantEntityManager entityManager,
AuthorizationService authorizationService, AuthorizationService authorizationService,
DeleterFactory deleterFactory, DeleterFactory deleterFactory,
BuilderFactory builderFactory, BuilderFactory builderFactory,
ConventionService conventionService, ConventionService conventionService,
MessageSource messageSource, MessageSource messageSource,
XmlHandlingService xmlHandlingService, XmlHandlingService xmlHandlingService,
ErrorThesaurusProperties errors, ErrorThesaurusProperties errors,
EncryptionService encryptionService, TenantProperties properties) { EncryptionService encryptionService, TenantProperties properties, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory; this.deleterFactory = deleterFactory;
@ -89,6 +95,8 @@ public class TenantServiceImpl implements TenantService {
this.errors = errors; this.errors = errors;
this.encryptionService = encryptionService; this.encryptionService = encryptionService;
this.properties = properties; this.properties = properties;
this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler;
this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler;
} }
@Override @Override
@ -124,6 +132,11 @@ public class TenantServiceImpl implements TenantService {
this.entityManager.flush(); this.entityManager.flush();
TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent();
tenantTouchedIntegrationEvent.setId(data.getId());
tenantTouchedIntegrationEvent.setCode(data.getCode());
this.tenantTouchedIntegrationEventHandler.handle(tenantTouchedIntegrationEvent);
return this.builderFactory.builder(TenantBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Tenant._id), data); return this.builderFactory.builder(TenantBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Tenant._id), data);
} }
@ -185,17 +198,25 @@ public class TenantServiceImpl implements TenantService {
} }
@Override @Override
public Tenant decryptTenant(Tenant model) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public Tenant decryptTenant(Tenant model) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
if (model.getConfig() != null && model.getConfig().getDeposit() != null && model.getConfig().getDeposit().getSources() != null) { if (model.getConfig() != null && model.getConfig().getDeposit() != null && model.getConfig().getDeposit().getSources() != null) {
for (TenantSource source : model.getConfig().getDeposit().getSources().stream().collect(Collectors.toList())) { for (TenantSource source : model.getConfig().getDeposit().getSources().stream().toList()) {
source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv())); source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv()));
} }
} }
if (model.getConfig() != null && model.getConfig().getFileTransformers() != null && model.getConfig().getFileTransformers().getSources() != null) { if (model.getConfig() != null && model.getConfig().getFileTransformers() != null && model.getConfig().getFileTransformers().getSources() != null) {
for (TenantSource source : model.getConfig().getFileTransformers().getSources().stream().collect(Collectors.toList())) { for (TenantSource source : model.getConfig().getFileTransformers().getSources().stream().toList()) {
source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv())); source.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), properties.getConfigEncryptionAesKey(), properties.getConfigEncryptionAesIv()));
} }
} }
TenantEntity data = this.entityManager.find(TenantEntity.class, model.getId());
if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Tenant.class.getSimpleName()}, LocaleContextHolder.getLocale()));
TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent();
tenantTouchedIntegrationEvent.setId(data.getId());
tenantTouchedIntegrationEvent.setCode(data.getCode());
this.tenantTouchedIntegrationEventHandler.handle(tenantTouchedIntegrationEvent);
return model; return model;
} }
@ -206,6 +227,10 @@ public class TenantServiceImpl implements TenantService {
this.authorizationService.authorizeForce(Permission.DeleteTenant); this.authorizationService.authorizeForce(Permission.DeleteTenant);
this.deleterFactory.deleter(TenantDeleter.class).deleteAndSaveByIds(List.of(id)); this.deleterFactory.deleter(TenantDeleter.class).deleteAndSaveByIds(List.of(id));
TenantRemovalIntegrationEvent tenantRemovalIntegrationEvent = new TenantRemovalIntegrationEvent();
tenantRemovalIntegrationEvent.setId(id);
this.tenantRemovalIntegrationEventHandler.handle(tenantRemovalIntegrationEvent);
} }
} }

View File

@ -26,6 +26,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -101,7 +102,7 @@ public class SecurityConfiguration {
//In the example below, the default client handler will be ignored by the resolver //In the example below, the default client handler will be ignored by the resolver
@Override @Override
public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() { public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() {
return List.of(PermissionClientAuthorizationHandler.class); return new ArrayList<>();
} }
}; };
} }

View File

@ -2,32 +2,67 @@ package eu.eudat.controllers;
import eu.eudat.audit.AuditableAction; import eu.eudat.audit.AuditableAction;
import eu.eudat.authorization.Permission; import eu.eudat.authorization.Permission;
import eu.eudat.commons.enums.IsActive;
import eu.eudat.data.TenantEntity;
import eu.eudat.data.TenantEntityManager;
import eu.eudat.data.UserEntity;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
import eu.eudat.query.TenantQuery;
import eu.eudat.query.UserQuery;
import eu.eudat.service.elastic.ElasticService; import eu.eudat.service.elastic.ElasticService;
import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.management.InvalidApplicationException;
import java.util.List;
@RestController @RestController
@RequestMapping(path = "api/maintenance") @RequestMapping(path = "api/maintenance")
public class MaintenanceController { public class MaintenanceController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(MaintenanceController.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(MaintenanceController.class));
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
private final ElasticService elasticService; private final ElasticService elasticService;
private final AuditService auditService; private final AuditService auditService;
@Autowired private final QueryFactory queryFactory;
public MaintenanceController(AuthorizationService authorizationService, ElasticService elasticService, AuditService auditService) {
this.authorizationService = authorizationService;
this.elasticService = elasticService;
this.auditService = auditService;
}
private final TenantEntityManager entityManager;
private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler;
private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler;
@Autowired
public MaintenanceController(
AuthorizationService authorizationService,
ElasticService elasticService,
AuditService auditService,
QueryFactory queryFactory,
TenantEntityManager entityManager,
UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler,
TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler) {
this.authorizationService = authorizationService;
this.elasticService = elasticService;
this.auditService = auditService;
this.queryFactory = queryFactory;
this.entityManager = entityManager;
this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler;
this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler;
}
@RequestMapping(method = RequestMethod.POST, value = {"/index/elastic"}) @RequestMapping(method = RequestMethod.POST, value = {"/index/elastic"})
public void generateIndex() throws Exception { public void generateIndex() throws Exception {
@ -36,6 +71,7 @@ public class MaintenanceController {
elasticService.resetDmpIndex(); elasticService.resetDmpIndex();
elasticService.resetDescriptionIndex(); elasticService.resetDescriptionIndex();
this.auditService.track(AuditableAction.Maintenance_GenerateElastic); this.auditService.track(AuditableAction.Maintenance_GenerateElastic);
} }
@ -43,9 +79,46 @@ public class MaintenanceController {
public void clearIndex() throws Exception { public void clearIndex() throws Exception {
logger.debug("clear elastic"); logger.debug("clear elastic");
this.authorizationService.authorizeForce(Permission.ManageElastic); this.authorizationService.authorizeForce(Permission.ManageElastic);
elasticService.deleteDescriptionIndex(); elasticService.deleteDescriptionIndex();
elasticService.deleteDmpIndex(); elasticService.deleteDmpIndex();
this.auditService.track(AuditableAction.Maintenance_ClearElastic); this.auditService.track(AuditableAction.Maintenance_ClearElastic);
} }
@RequestMapping(method = RequestMethod.DELETE, value = {"/events/users/touch"})
public void sendUserTouchEvents() throws InvalidApplicationException {
logger.debug("send user touch queue events");
this.authorizationService.authorizeForce(Permission.ManageQueueEvents);
this.entityManager.disableTenantFilters();
UserQuery userQuery = queryFactory.query(UserQuery.class).isActive(IsActive.Active);
List<UserEntity> users = userQuery.collect();
this.entityManager.enableTenantFilters();
for(UserEntity user : users)
this.userTouchedIntegrationEventHandler.handle(user.getId());
this.auditService.track(AuditableAction.Maintenance_SendUserTouchEvents);
}
@RequestMapping(method = RequestMethod.DELETE, value = {"/events/tenants/touch"})
public void sendTenantTouchEvents() throws InvalidApplicationException {
logger.debug("send tenant touch queue events");
this.authorizationService.authorizeForce(Permission.ManageQueueEvents);
this.entityManager.disableTenantFilters();
TenantQuery tenantQuery = queryFactory.query(TenantQuery.class).isActive(IsActive.Active);
List<TenantEntity> tenants = tenantQuery.collect();
this.entityManager.enableTenantFilters();
for (TenantEntity tenant : tenants) {
TenantTouchedIntegrationEvent event = new TenantTouchedIntegrationEvent();
event.setId(tenant.getId());
event.setCode(tenant.getCode());
this.tenantTouchedIntegrationEventHandler.handle(event);
}
this.auditService.track(AuditableAction.Maintenance_SendTenantTouchEvents);
}
} }

View File

@ -78,7 +78,7 @@ public class TenantController {
} }
@PostMapping("query") @PostMapping("query")
public QueryResult<Tenant> query(@RequestBody TenantLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public QueryResult<Tenant> query(@RequestBody TenantLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
logger.debug("querying {}", Tenant.class.getSimpleName()); logger.debug("querying {}", Tenant.class.getSimpleName());
this.censorFactory.censor(TenantCensor.class).censor(lookup.getProject(), null); this.censorFactory.censor(TenantCensor.class).censor(lookup.getProject(), null);
@ -97,7 +97,7 @@ public class TenantController {
} }
@GetMapping("{id}") @GetMapping("{id}")
public Tenant get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { public Tenant get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + Tenant.class.getSimpleName()).And("id", id).And("fields", fieldSet)); logger.debug(new MapLogEntry("retrieving" + Tenant.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(TenantCensor.class).censor(fieldSet, null); this.censorFactory.censor(TenantCensor.class).censor(fieldSet, null);

View File

@ -88,6 +88,14 @@ permissions:
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
# Queue Events
ManageQueueEvents:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Deposit # Deposit
BrowseDeposit: BrowseDeposit:

View File

@ -11,10 +11,15 @@ CREATE TABLE IF NOT EXISTS public."ant_Annotation"
"entity_type" character varying(512) COLLATE pg_catalog."default" NOT NULL, "entity_type" character varying(512) COLLATE pg_catalog."default" NOT NULL,
"anchor" character varying(512) COLLATE pg_catalog."default", "anchor" character varying(512) COLLATE pg_catalog."default",
"payload" text COLLATE pg_catalog."default", "payload" text COLLATE pg_catalog."default",
"tenant" uuid NOT NULL,
"created_at" timestamp without time zone NOT NULL, "created_at" timestamp without time zone NOT NULL,
"updated_at" timestamp without time zone NOT NULL, "updated_at" timestamp without time zone NOT NULL,
"is_active" smallint NOT NULL, "is_active" smallint NOT NULL,
CONSTRAINT "ant_Annotation_pkey" PRIMARY KEY (id) CONSTRAINT "ant_Annotation_pkey" PRIMARY KEY (id),
CONSTRAINT "ant_Annotation_tenant_fkey" FOREIGN KEY ("tenant")
REFERENCES public."ant_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
); );
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.055', '2024-02-20 12:00:00.000000+02', now(), 'Add table ant_Annotation.'); INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.055', '2024-02-20 12:00:00.000000+02', now(), 'Add table ant_Annotation.');

View File

@ -9,10 +9,15 @@ CREATE TABLE IF NOT EXISTS public."ant_EntityUser"
"id" uuid NOT NULL, "id" uuid NOT NULL,
"entity_id" uuid NOT NULL, "entity_id" uuid NOT NULL,
"user_id" uuid NOT NULL, "user_id" uuid NOT NULL,
"tenant" uuid NOT NULL,
"created_at" timestamp without time zone NOT NULL, "created_at" timestamp without time zone NOT NULL,
"updated_at" timestamp without time zone NOT NULL, "updated_at" timestamp without time zone NOT NULL,
"is_active" smallint NOT NULL, "is_active" smallint NOT NULL,
CONSTRAINT "ant_EntityUser_pkey" PRIMARY KEY (id), CONSTRAINT "ant_EntityUser_pkey" PRIMARY KEY (id),
CONSTRAINT "ant_EntityUser_tenant_fkey" FOREIGN KEY ("tenant")
REFERENCES public."ant_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT "ant_EntityUser_user_fkey" FOREIGN KEY ("user_id") CONSTRAINT "ant_EntityUser_user_fkey" FOREIGN KEY ("user_id")
REFERENCES public."ant_User" (id) MATCH SIMPLE REFERENCES public."ant_User" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION

View File

@ -0,0 +1,17 @@
export enum ResponseErrorCode {
HashConflict = 100,
Forbidden = 101,
SystemError =102,
MissingTenant = 103,
TenantCodeRequired = 108,
TenantNameRequired = 109,
TenantNotAllowed = 113,
DescriptionTemplateNewVersionConflict = 114,
DmpNewVersionConflict = 115,
DmpIsFinalized = 116,
DmpBlueprintHasNoDescriptionTemplates = 120,
DmpBlueprintNewVersionConflict = 121,
DmpDescriptionTemplateCanNotRemove = 122,
TenantTampering = 123
}

View File

@ -188,6 +188,7 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
const action = this.route.snapshot.data['action']; const action = this.route.snapshot.data['action'];
if (action && action == 'new-version') { if (action && action == 'new-version') {
this.formGroup.enable(); this.formGroup.enable();
this.formGroup.get('status').setValue(DescriptionTemplateStatus.Draft);
} }
} }
@ -283,7 +284,6 @@ export class DescriptionTemplateEditorComponent extends BaseEditor<DescriptionTe
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) { if (result) {
this.formGroup.get('status').setValue(DescriptionTemplateStatus.Finalized); this.formGroup.get('status').setValue(DescriptionTemplateStatus.Finalized);
if(this.isNewVersion) this.isNewVersion = false;
this.persistEntity(); this.persistEntity();
}}); }});
} }

View File

@ -24,6 +24,7 @@ import { isNullOrUndefined } from '@swimlane/ngx-datatable';
import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component'; import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component';
import { AuthService } from '@app/core/services/auth/auth.service'; import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code';
@Component({ @Component({
selector: 'app-base-editor-component', selector: 'app-base-editor-component',
@ -120,9 +121,13 @@ export abstract class BaseEditor<EditorModelType extends BaseEditorModel, Entity
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse); const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
if (error.statusCode === 400) { if (error.statusCode === 400) {
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error); this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
if(errorResponse.error.code === 120){ if(errorResponse.error.code === ResponseErrorCode.DmpBlueprintHasNoDescriptionTemplates){
this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error); this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error);
} }
if(errorResponse.error.code === ResponseErrorCode.DmpDescriptionTemplateCanNotRemove){
this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error);
this.refreshOnNavigateToData(null);
}
this.formService.validateAllFormFields(this.formGroup); this.formService.validateAllFormFields(this.formGroup);
} else { } else {
this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning); this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning);

View File

@ -7,6 +7,7 @@ export interface BaseEntity {
createdAt?: Date; createdAt?: Date;
updatedAt?: Date; updatedAt?: Date;
hash?: string; hash?: string;
belongsToCurrentTenant?: boolean;
} }
export interface BaseEntityPersist { export interface BaseEntityPersist {