From 28238c7a6d006eb324aaffb6c1d278b2fd796f17 Mon Sep 17 00:00:00 2001 From: amentis Date: Tue, 2 Jan 2024 11:06:10 +0200 Subject: [PATCH] change idpclaims.yml and add user interceptor --- .../notification/web/WebConfiguration.java | 2 +- .../NotificationTemplateController.java | 4 +- .../web/interceptors/UserInterceptor.java | 299 +++++++++++++++++ .../UserInterceptorCacheOptions.java | 10 + .../UserInterceptorCacheService.java | 86 +++++ .../web/scope/user/UserInterceptor.java | 308 +++++++++--------- .../user/UserInterceptorCacheOptions.java | 20 +- .../user/UserInterceptorCacheService.java | 154 ++++----- .../src/main/resources/config/idpclaims.yml | 13 +- .../target/classes/config/idpclaims.yml | 13 +- .../common/lock/LockByKeyManager.java | 58 ++++ .../types/user/AdditionalInfoEntity.java | 60 ++++ .../data/UserCredentialEntity.java | 64 ++++ .../notification/data/UserRoleEntity.java | 65 ++++ .../notification/model/UserCredential.java | 51 +++ .../gr/cite/notification/model/UserRole.java | 52 +++ .../query/UserContactInfoQuery.java | 47 ++- .../query/UserCredentialQuery.java | 193 +++++++++++ .../notification/query/UserRoleQuery.java | 194 +++++++++++ .../NotificationServiceTemplateImpl.java | 2 +- 20 files changed, 1425 insertions(+), 270 deletions(-) create mode 100644 dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java create mode 100644 dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java create mode 100644 dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserCredentialEntity.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserRoleEntity.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserCredential.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserRole.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserCredentialQuery.java create mode 100644 dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserRoleQuery.java diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java index 8f0f8367e..617ff3c0f 100644 --- a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/WebConfiguration.java @@ -3,7 +3,7 @@ package gr.cite.notification.web; import gr.cite.notification.web.scope.tenant.TenantInterceptor; import gr.cite.notification.web.scope.tenant.TenantScopeClaimInterceptor; import gr.cite.notification.web.scope.tenant.TenantScopeHeaderInterceptor; -import gr.cite.notification.web.scope.user.UserInterceptor; +import gr.cite.notification.web.interceptors.UserInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationTemplateController.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationTemplateController.java index aac49983e..820b7caf5 100644 --- a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationTemplateController.java +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/NotificationTemplateController.java @@ -63,7 +63,7 @@ public class NotificationTemplateController { public QueryResult query(@RequestBody NotificationTemplateLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", NotificationTemplate.class.getSimpleName()); - //this.censorFactory.censor(NotificationTemplateCensor.class).censor(lookup.getProject()); TODO + this.censorFactory.censor(NotificationTemplateCensor.class).censor(lookup.getProject()); NotificationTemplateQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.OwnerOrPermission); List data = query.collectAs(lookup.getProject()); @@ -80,7 +80,7 @@ public class NotificationTemplateController { public NotificationTemplate get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + NotificationTemplate.class.getSimpleName()).And("id", id).And("fields", fieldSet)); -// this.censorFactory.censor(NotificationTemplateCensor.class).censor(fieldSet); TODO + this.censorFactory.censor(NotificationTemplateCensor.class).censor(fieldSet); NotificationTemplateQuery query = this.queryFactory.query(NotificationTemplateQuery.class).authorize(AuthorizationFlags.OwnerOrPermission).ids(id); NotificationTemplate model = this.builderFactory.builder(NotificationTemplateBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.first()); diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java new file mode 100644 index 000000000..47208b1ca --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptor.java @@ -0,0 +1,299 @@ +package gr.cite.notification.web.interceptors; + + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +import gr.cite.notification.common.JsonHandlingService; +import gr.cite.notification.common.enums.ContactInfoType; +import gr.cite.notification.common.enums.IsActive; +import gr.cite.notification.common.lock.LockByKeyManager; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.common.types.user.AdditionalInfoEntity; +import gr.cite.notification.data.UserContactInfoEntity; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.data.UserEntity; +import gr.cite.notification.data.UserRoleEntity; +import gr.cite.notification.model.UserContactInfo; +import gr.cite.notification.model.UserCredential; +import gr.cite.notification.model.UserRole; +import gr.cite.notification.query.UserContactInfoQuery; +import gr.cite.notification.query.UserCredentialQuery; +import gr.cite.notification.query.UserRoleQuery; +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 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.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@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 PlatformTransactionManager transactionManager; + private final UserInterceptorCacheService userInterceptorCacheService; + private final JsonHandlingService jsonHandlingService; + private final QueryFactory queryFactory; + private final LockByKeyManager lockByKeyManager; + @PersistenceContext + public EntityManager entityManager; + + @Autowired + public UserInterceptor( + UserScope userScope, + ClaimExtractor claimExtractor, + CurrentPrincipalResolver currentPrincipalResolver, + PlatformTransactionManager transactionManager, + UserInterceptorCacheService userInterceptorCacheService, + JsonHandlingService jsonHandlingService, + QueryFactory queryFactory, + LockByKeyManager lockByKeyManager) { + this.userScope = userScope; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractor = claimExtractor; + this.transactionManager = transactionManager; + this.userInterceptorCacheService = userInterceptorCacheService; + this.jsonHandlingService = jsonHandlingService; + this.queryFactory = queryFactory; + this.lockByKeyManager = lockByKeyManager; + } + + @Override + public void preHandle(WebRequest request) throws InterruptedException { + 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 && emailExistsToUser(cacheValue.getEmails()) && userRolesSynced(cacheValue.getRoles())) { + userId = cacheValue.getUserId(); + } else { + boolean usedResource = false; + try { + usedResource = this.lockByKeyManager.tryLock(subjectId, 5000, TimeUnit.MILLISECONDS); + String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal()); + + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName(UUID.randomUUID().toString()); + definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + TransactionStatus status = null; + try { + status = transactionManager.getTransaction(definition); + userId = this.findExistingUserFromDb(subjectId); + boolean isNewUser = userId == null; + if (isNewUser) { + UserEntity user = this.addNewUser(subjectId, email); + userId = user.getId(); + } + + if (!isNewUser) this.syncUserWithClaims(userId); + + this.entityManager.flush(); + transactionManager.commit(status); + } catch (Exception ex) { + if (status != null) transactionManager.rollback(status); + throw ex; + } + + cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId); + cacheValue.setEmails(new ArrayList<>()); + if (email != null && !email.isBlank()) cacheValue.getEmails().add(email); + cacheValue.setRoles(claimExtractor.roles(currentPrincipalResolver.currentPrincipal())); + + this.userInterceptorCacheService.put(cacheValue); + } finally { + if (usedResource) this.lockByKeyManager.unlock(subjectId); + } + + } + } + this.userScope.setUserId(userId); + } + + private void syncUserWithClaims(UUID userId){ + List existingUserEmails = this.collectUserEmails(userId); + List existingUserRoles = this.collectUserRoles(userId); + if (!this.emailExistsToUser(existingUserEmails)){ + String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal()); + long contactUsedByOthersCount = this.queryFactory.query(UserContactInfoQuery.class).excludedUserIds(userId).type(ContactInfoType.Email).values(email).count(); + if (contactUsedByOthersCount > 0) { + logger.warn("user contact exists to other user" + email); + } else { + Long emailContactsCount = this.queryFactory.query(UserContactInfoQuery.class).userIds(userId).type(ContactInfoType.Email).count(); + UserContactInfoEntity contactInfo = this.buildEmailContact(userId, email); + contactInfo.setOrdinal(emailContactsCount.intValue()); + this.entityManager.persist(contactInfo); + } + } + + if (!this.userRolesSynced(existingUserRoles)){ + this.syncRoles(userId); + } + } + + private UUID findExistingUserFromDb(String subjectId){ + UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user)); + if (userCredential != null) { + return userCredential.getUserId(); + } else { + String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal()); + if (email != null && !email.isBlank()) { + UserContactInfoEntity userContactInfo = this.queryFactory.query(UserContactInfoQuery.class).type(ContactInfoType.Email).values(email).firstAs(new BaseFieldSet().ensure(UserContactInfo._user)); + if (userContactInfo != null) { + UserCredentialEntity credential = this.buildCredential(userContactInfo.getUserId(), subjectId); + this.entityManager.persist(credential); + + return credential.getUserId(); + } + } else { + throw new MyForbiddenException("Email is required"); + } + } + return null; + } + + private void syncRoles(UUID userId){ + List claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + if (claimsRoles == null) claimsRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().collect(Collectors.toList()); + + List existingUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collect(); + List foundRoles = new ArrayList<>(); + for (String claimRole : claimsRoles) { + UserRoleEntity roleEntity = existingUserRoles.stream().filter(x-> x.getRole().equals(claimRole)).findFirst().orElse(null); + if (roleEntity == null) { + roleEntity = this.buildRole(userId, claimRole); + this.entityManager.persist(roleEntity); + } + foundRoles.add(roleEntity.getId()); + } + for (UserRoleEntity existing: existingUserRoles) { + if (!foundRoles.contains(existing.getId())){ + this.entityManager.remove(existing); + } + } + } + + private List collectUserRoles(UUID userId){ + List items = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collectAs(new BaseFieldSet().ensure(UserRole._role)); + return items == null ? new ArrayList<>() : items.stream().map(UserRoleEntity::getRole).collect(Collectors.toList()); + } + + private List collectUserEmails(UUID userId){ + List items = this.queryFactory.query(UserContactInfoQuery.class).userIds(userId).type(ContactInfoType.Email).collectAs(new BaseFieldSet().ensure(UserContactInfo._value)); + return items == null ? new ArrayList<>() : items.stream().map(UserContactInfoEntity::getValue).collect(Collectors.toList()); + } + + private boolean emailExistsToUser(List existingUserEmails){ + String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal()); + return email == null || email.isBlank() || + (existingUserEmails != null && existingUserEmails.stream().anyMatch(email::equals)); + } + + private boolean userRolesSynced(List existingUserRoles){ + List claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + if (claimsRoles == null) claimsRoles = new ArrayList<>(); + if (existingUserRoles == null) existingUserRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().collect(Collectors.toList()); + existingUserRoles = existingUserRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().collect(Collectors.toList()); + if (claimsRoles.size() != existingUserRoles.size()) return false; + + for (String claim : claimsRoles ) { + if (existingUserRoles.stream().noneMatch(claim::equalsIgnoreCase)) return false; + } + return true; + } + + private UserCredentialEntity buildCredential(UUID userId, String subjectId){ + UserCredentialEntity data = new UserCredentialEntity(); + data.setId(UUID.randomUUID()); + data.setUserId(userId); + data.setCreatedAt(Instant.now()); + data.setExternalId(subjectId); + return data; + } + + private UserRoleEntity buildRole(UUID userId, String role){ + UserRoleEntity data = new UserRoleEntity(); + data.setId(UUID.randomUUID()); + data.setUserId(userId); + data.setRole(role); + data.setCreatedAt(Instant.now()); + return data; + } + + private UserContactInfoEntity buildEmailContact(UUID userId, String email){ + UserContactInfoEntity data = new UserContactInfoEntity(); + data.setId(UUID.randomUUID()); + data.setUserId(userId); + data.setValue(email); + data.setType(ContactInfoType.Email); + data.setOrdinal(0); + data.setCreatedAt(Instant.now()); + return data; + } + + + + private UserEntity addNewUser(String subjectId, String email){ + List roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal()); + + UserEntity user = new UserEntity(); + user.setId(UUID.randomUUID()); + user.setName(name); + user.setCreatedAt(Instant.now()); + user.setUpdatedAt(Instant.now()); + user.setIsActive(IsActive.Active); + user.setAdditionalInfo(this.jsonHandlingService.toJsonSafe(new AdditionalInfoEntity())); + this.entityManager.persist(user); + + UserCredentialEntity credential = this.buildCredential(user.getId(), subjectId); + this.entityManager.persist(credential); + + if (email != null && !email.isBlank()) { + UserContactInfoEntity contactInfo = this.buildEmailContact(user.getId(), email); + this.entityManager.persist(contactInfo); + } + if (roles != null) { + for (String role: roles) { + UserRoleEntity roleEntity = this.buildRole(user.getId(), role); + this.entityManager.persist(roleEntity); + } + } + + return user; + } + + @Override + public void postHandle(@NonNull WebRequest request, ModelMap model) { + this.userScope.setUserId(null); + } + + @Override + public void afterCompletion(@NonNull WebRequest request, Exception ex) { + } +} + diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java new file mode 100644 index 000000000..adc161299 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheOptions.java @@ -0,0 +1,10 @@ +package gr.cite.notification.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 { +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java new file mode 100644 index 000000000..b69813191 --- /dev/null +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/interceptors/UserInterceptorCacheService.java @@ -0,0 +1,86 @@ +package gr.cite.notification.web.interceptors; + +import gr.cite.notification.convention.ConventionService; +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.List; +import java.util.UUID; + +@Service +public class UserInterceptorCacheService extends CacheService { + + 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; + private List roles; + private List emails; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getEmails() { + return emails; + } + + public void setEmails(List emails) { + this.emails = emails; + } + } + + + @Autowired + public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) { + super(options); + } + + @Override + protected Class valueClass() { + return UserInterceptorCacheValue.class; + } + + @Override + public String keyOf(UserInterceptorCacheValue value) { + return this.buildKey(value.getSubjectId()); + } + + + public String buildKey(String subject) { + HashMap keyParts = new HashMap<>(); + keyParts.put("$subject$", subject); + return this.generateKey(keyParts); + } +} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java index 21f1f0927..43bc4fb9b 100644 --- a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptor.java @@ -1,154 +1,154 @@ -package gr.cite.notification.web.scope.user; - - -import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; -import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; -import gr.cite.notification.common.enums.IsActive; -import gr.cite.notification.common.scope.user.UserScope; -import gr.cite.notification.data.UserEntity; -import gr.cite.notification.locale.LocaleService; -import gr.cite.tools.logging.LoggerService; -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.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.ui.ModelMap; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.WebRequestInterceptor; - -import javax.management.InvalidApplicationException; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Tuple; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; -import java.time.Instant; -import java.util.List; -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 LocaleService localeService; - private final PlatformTransactionManager transactionManager; - private final UserInterceptorCacheService userInterceptorCacheService; - @PersistenceContext - public EntityManager entityManager; - - @Autowired - public UserInterceptor( - UserScope userScope, - LocaleService localeService, - ClaimExtractor claimExtractor, - CurrentPrincipalResolver currentPrincipalResolver, - PlatformTransactionManager transactionManager, - UserInterceptorCacheService userInterceptorCacheService - ) { - this.userScope = userScope; - this.localeService = localeService; - this.currentPrincipalResolver = currentPrincipalResolver; - this.claimExtractor = claimExtractor; - this.transactionManager = transactionManager; - this.userInterceptorCacheService = userInterceptorCacheService; - } - - @Override - public void preHandle(WebRequest request) throws InvalidApplicationException { - UUID userId = null; - if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { - String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); - - UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); - if (cacheValue != null) { - userId = cacheValue.getUserId(); - } else { - userId = this.getUserIdFromDatabase(subjectId); - if (userId == null) userId = this.createUser(subjectId); - - this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId)); - } - } - this.userScope.setUserId(userId); - } - - private UUID getUserIdFromDatabase(String subjectId) throws InvalidApplicationException { - CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); - Root root = query.from(UserEntity.class); - query.where( - criteriaBuilder.and( -// criteriaBuilder.equal(root.get(UserEntity._subjectId), subjectId), - criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active) - )); - - query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); - - List results = this.entityManager.createQuery(query).getResultList(); - if (results.size() == 1) { - Object o; - try { - o = results.get(0).get(UserEntity._id); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return UUID.class.cast(o); - } catch (ClassCastException e) { - return null; - } - } - return null; - } - - private UUID createUser(String subjectId) { - String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal()); - String familyName = this.claimExtractor.familyName(this.currentPrincipalResolver.currentPrincipal()); - if (name == null) name = subjectId; - UserEntity user = new UserEntity(); - user.setId(UUID.randomUUID()); - user.setCreatedAt(Instant.now()); - user.setUpdatedAt(Instant.now()); - user.setName(name); -// user.setLastName(familyName == null ? name : familyName); - user.setIsActive(IsActive.Active); -// user.setSubjectId(subjectId); -// user.setCulture(this.localeService.cultureName()); -// user.setTimezone(this.localeService.timezoneName()); -// user.setLanguage(this.localeService.language()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setName(UUID.randomUUID().toString()); - definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - TransactionStatus status = null; - try { - status = transactionManager.getTransaction(definition); - this.entityManager.persist(user); - - this.entityManager.flush(); - transactionManager.commit(status); - } catch (Exception ex) { - if (status != null) transactionManager.rollback(status); - throw ex; - } - return user.getId(); - } - - @Override - public void postHandle(@NonNull WebRequest request, ModelMap model) { - this.userScope.setUserId(null); - } - - @Override - public void afterCompletion(@NonNull WebRequest request, Exception ex) { - } -} +//package gr.cite.notification.web.scope.user; +// +// +//import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +//import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; +//import gr.cite.notification.common.enums.IsActive; +//import gr.cite.notification.common.scope.user.UserScope; +//import gr.cite.notification.data.UserEntity; +//import gr.cite.notification.locale.LocaleService; +//import gr.cite.tools.logging.LoggerService; +//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.transaction.TransactionDefinition; +//import org.springframework.transaction.TransactionStatus; +//import org.springframework.transaction.support.DefaultTransactionDefinition; +//import org.springframework.ui.ModelMap; +//import org.springframework.web.context.request.WebRequest; +//import org.springframework.web.context.request.WebRequestInterceptor; +// +//import javax.management.InvalidApplicationException; +//import javax.persistence.EntityManager; +//import javax.persistence.PersistenceContext; +//import javax.persistence.Tuple; +//import javax.persistence.criteria.CriteriaBuilder; +//import javax.persistence.criteria.CriteriaQuery; +//import javax.persistence.criteria.Root; +//import java.time.Instant; +//import java.util.List; +//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 LocaleService localeService; +// private final PlatformTransactionManager transactionManager; +// private final UserInterceptorCacheService userInterceptorCacheService; +// @PersistenceContext +// public EntityManager entityManager; +// +// @Autowired +// public UserInterceptor( +// UserScope userScope, +// LocaleService localeService, +// ClaimExtractor claimExtractor, +// CurrentPrincipalResolver currentPrincipalResolver, +// PlatformTransactionManager transactionManager, +// UserInterceptorCacheService userInterceptorCacheService +// ) { +// this.userScope = userScope; +// this.localeService = localeService; +// this.currentPrincipalResolver = currentPrincipalResolver; +// this.claimExtractor = claimExtractor; +// this.transactionManager = transactionManager; +// this.userInterceptorCacheService = userInterceptorCacheService; +// } +// +// @Override +// public void preHandle(WebRequest request) throws InvalidApplicationException { +// UUID userId = null; +// if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { +// String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); +// +// UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); +// if (cacheValue != null) { +// userId = cacheValue.getUserId(); +// } else { +// userId = this.getUserIdFromDatabase(subjectId); +// if (userId == null) userId = this.createUser(subjectId); +// +// this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId)); +// } +// } +// this.userScope.setUserId(userId); +// } +// +// private UUID getUserIdFromDatabase(String subjectId) throws InvalidApplicationException { +// CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); +// CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); +// Root root = query.from(UserEntity.class); +// query.where( +// criteriaBuilder.and( +//// criteriaBuilder.equal(root.get(UserEntity._subjectId), subjectId), +// criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active) +// )); +// +// query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); +// +// List results = this.entityManager.createQuery(query).getResultList(); +// if (results.size() == 1) { +// Object o; +// try { +// o = results.get(0).get(UserEntity._id); +// } catch (IllegalArgumentException e) { +// return null; +// } +// if (o == null) return null; +// try { +// return UUID.class.cast(o); +// } catch (ClassCastException e) { +// return null; +// } +// } +// return null; +// } +// +// private UUID createUser(String subjectId) { +// String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal()); +// String familyName = this.claimExtractor.familyName(this.currentPrincipalResolver.currentPrincipal()); +// if (name == null) name = subjectId; +// UserEntity user = new UserEntity(); +// user.setId(UUID.randomUUID()); +// user.setCreatedAt(Instant.now()); +// user.setUpdatedAt(Instant.now()); +// user.setName(name); +//// user.setLastName(familyName == null ? name : familyName); +// user.setIsActive(IsActive.Active); +//// user.setSubjectId(subjectId); +//// user.setCulture(this.localeService.cultureName()); +//// user.setTimezone(this.localeService.timezoneName()); +//// user.setLanguage(this.localeService.language()); +// +// DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); +// definition.setName(UUID.randomUUID().toString()); +// definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); +// definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); +// TransactionStatus status = null; +// try { +// status = transactionManager.getTransaction(definition); +// this.entityManager.persist(user); +// +// this.entityManager.flush(); +// transactionManager.commit(status); +// } catch (Exception ex) { +// if (status != null) transactionManager.rollback(status); +// throw ex; +// } +// return user.getId(); +// } +// +// @Override +// public void postHandle(@NonNull WebRequest request, ModelMap model) { +// this.userScope.setUserId(null); +// } +// +// @Override +// public void afterCompletion(@NonNull WebRequest request, Exception ex) { +// } +//} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java index 8a35750a6..ceaf7996e 100644 --- a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheOptions.java @@ -1,10 +1,10 @@ -package gr.cite.notification.web.scope.user; - -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 { -} +//package gr.cite.notification.web.scope.user; +// +//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 { +//} diff --git a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java index abfd32f98..338a69f03 100644 --- a/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java +++ b/dmp-backend/notification-service/notification-web/src/main/java/gr/cite/notification/web/scope/user/UserInterceptorCacheService.java @@ -1,77 +1,77 @@ -package gr.cite.notification.web.scope.user; - -import gr.cite.notification.convention.ConventionService; -import gr.cite.notification.event.UserTouchedEvent; -import gr.cite.tools.cache.CacheService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.UUID; - -@Service -public class UserInterceptorCacheService extends CacheService { - - 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; - } - } - - private final ConventionService conventionService; - - @Autowired - public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) { - super(options); - this.conventionService = conventionService; - } - - @EventListener - public void handleUserTouchedEvent(UserTouchedEvent event) { - if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId())); - if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId())); - } - - @Override - protected Class valueClass() { - return UserInterceptorCacheValue.class; - } - - @Override - public String keyOf(UserInterceptorCacheValue value) { - return this.buildKey(value.getSubjectId()); - } - - - public String buildKey(String subject) { - return this.generateKey(new HashMap<>() {{ - put("$subject$", subject); - }}); - } -} +//package gr.cite.notification.web.scope.user; +// +//import gr.cite.notification.convention.ConventionService; +//import gr.cite.notification.event.UserTouchedEvent; +//import gr.cite.tools.cache.CacheService; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.event.EventListener; +//import org.springframework.stereotype.Service; +// +//import java.util.HashMap; +//import java.util.UUID; +// +//@Service +//public class UserInterceptorCacheService extends CacheService { +// +// 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; +// } +// } +// +// private final ConventionService conventionService; +// +// @Autowired +// public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) { +// super(options); +// this.conventionService = conventionService; +// } +// +// @EventListener +// public void handleUserTouchedEvent(UserTouchedEvent event) { +// if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId())); +// if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId())); +// } +// +// @Override +// protected Class valueClass() { +// return UserInterceptorCacheValue.class; +// } +// +// @Override +// public String keyOf(UserInterceptorCacheValue value) { +// return this.buildKey(value.getSubjectId()); +// } +// +// +// public String buildKey(String subject) { +// return this.generateKey(new HashMap<>() {{ +// put("$subject$", subject); +// }}); +// } +//} diff --git a/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml b/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml index 9848dc492..3372e4ca7 100644 --- a/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml +++ b/dmp-backend/notification-service/notification-web/src/main/resources/config/idpclaims.yml @@ -19,18 +19,12 @@ idpclient: - type: email Roles: - type: resource_access - path: intelcomp-sti-viewer-dev.roles - - type: authorities - filterBy: "(.*):::TenantCode::" - extractByExpression: "(.*):(.*)" - extractExpressionValue: "[[g1]]" + path: dmp_web.roles Scope: - type: scope AccessToken: - type: x-access-token visibility: SENSITIVE - Tenant: - - type: x-tenant IssuedAt: - type: iat Issuer: @@ -43,8 +37,3 @@ idpclient: - type: azp Authorities: - type: authorities - TenantCodes: - - type: authorities - filterBy: "(.*):(.*)" - extractByExpression: "(.*):(.*)" - extractExpressionValue: "[[g2]]" diff --git a/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml b/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml index 9848dc492..3372e4ca7 100644 --- a/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml +++ b/dmp-backend/notification-service/notification-web/target/classes/config/idpclaims.yml @@ -19,18 +19,12 @@ idpclient: - type: email Roles: - type: resource_access - path: intelcomp-sti-viewer-dev.roles - - type: authorities - filterBy: "(.*):::TenantCode::" - extractByExpression: "(.*):(.*)" - extractExpressionValue: "[[g1]]" + path: dmp_web.roles Scope: - type: scope AccessToken: - type: x-access-token visibility: SENSITIVE - Tenant: - - type: x-tenant IssuedAt: - type: iat Issuer: @@ -43,8 +37,3 @@ idpclient: - type: azp Authorities: - type: authorities - TenantCodes: - - type: authorities - filterBy: "(.*):(.*)" - extractByExpression: "(.*):(.*)" - extractExpressionValue: "[[g2]]" diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java new file mode 100644 index 000000000..8c33cb9f7 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/lock/LockByKeyManager.java @@ -0,0 +1,58 @@ +package gr.cite.notification.common.lock; + +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +@Service +public class LockByKeyManager { + + private static class LockWrapper { + private final ReentrantLock lock = new ReentrantLock(); + private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1); + + private LockWrapper addThreadInQueue() { + numberOfThreadsInQueue.incrementAndGet(); + return this; + } + + private int removeThreadFromQueue() { + return numberOfThreadsInQueue.decrementAndGet(); + } + + } + + private static ConcurrentHashMap locks = new ConcurrentHashMap(); + + public void lock(String key) { + LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); + lockWrapper.lock.lock(); + } + + public boolean tryLock(String key, long timeout, TimeUnit unit) throws InterruptedException { + LockWrapper lockWrapper = null; + try { + lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue()); + return lockWrapper.lock.tryLock(timeout, unit); + } catch (Exception ex){ + if (lockWrapper != null && lockWrapper.removeThreadFromQueue() == 0) { + // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal + locks.remove(key, lockWrapper); + } + throw ex; + } + } + + public void unlock(String key) { + LockWrapper lockWrapper = locks.get(key); + lockWrapper.lock.unlock(); + if (lockWrapper.removeThreadFromQueue() == 0) { + // NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal + locks.remove(key, lockWrapper); + } + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java new file mode 100644 index 000000000..1c8c6f4b1 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/user/AdditionalInfoEntity.java @@ -0,0 +1,60 @@ +package gr.cite.notification.common.types.user; + +import java.util.UUID; + +public class AdditionalInfoEntity { + private String avatarUrl; + private String timezone; + private String culture; + private String language; + private String roleOrganization; + private UUID organizationId; + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public UUID getOrganizationId() { + return organizationId; + } + + public void setOrganizationId(UUID organizationId) { + this.organizationId = organizationId; + } + + public String getRoleOrganization() { + return roleOrganization; + } + + public void setRoleOrganization(String roleOrganization) { + this.roleOrganization = roleOrganization; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserCredentialEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserCredentialEntity.java new file mode 100644 index 000000000..ebd5b44f1 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserCredentialEntity.java @@ -0,0 +1,64 @@ +package gr.cite.notification.data; + + +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UserCredential\"") +public class UserCredentialEntity extends TenantScopedBaseEntity { + + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + public final static String _id = "id"; + + @Column(name = "\"user\"", columnDefinition = "uuid", nullable = false) + private UUID userId; + public final static String _userId = "userId"; + + @Column(name = "\"external_id\"", length = UserCredentialEntity._externalIdLength, nullable = false) + private String externalId; + public final static String _externalId = "externalId"; + public final static int _externalIdLength = 512; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserRoleEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserRoleEntity.java new file mode 100644 index 000000000..1a4cbc9c2 --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/data/UserRoleEntity.java @@ -0,0 +1,65 @@ +package gr.cite.notification.data; + + +import gr.cite.notification.data.tenant.TenantScopedBaseEntity; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "\"UserRole\"") +public class UserRoleEntity extends TenantScopedBaseEntity { + @Id + @Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false) + private UUID id; + public static final String _id = "id"; + + @Column(name = "role", length = UserRoleEntity._roleLength, nullable = false) + private String role; + public static final String _role = "role"; + public static final int _roleLength = 512; + + @Column(name = "\"user\"", nullable = false) + private UUID userId; + public static final String _userId = "userId"; + + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserCredential.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserCredential.java new file mode 100644 index 000000000..6aeee3d2d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserCredential.java @@ -0,0 +1,51 @@ +package gr.cite.notification.model; + +import java.time.Instant; +import java.util.UUID; + +public class UserCredential { + private UUID id; + public static final String _id = "id"; + + private String externalId; + public static final String _externalId = "externalId"; + + private User user; + public static final String _user = "user"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserRole.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserRole.java new file mode 100644 index 000000000..37c225d5d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/UserRole.java @@ -0,0 +1,52 @@ +package gr.cite.notification.model; + +import java.time.Instant; +import java.util.UUID; + +public class UserRole { + private UUID id; + public static final String _id = "id"; + + private String role; + public static final String _role = "role"; + + private User user; + public static final String _user = "user"; + + private Instant createdAt; + + public static final String _createdAt = "createdAt"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} + diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java index a8f851b58..3d4986146 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserContactInfoQuery.java @@ -30,9 +30,11 @@ public class UserContactInfoQuery extends QueryBase { private Collection ids; private Collection userIds; + private Collection excludedUserIds; private Collection tenantIds; private Collection isActives; private Collection type; + private Collection values; private UserQuery userQuery; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); @@ -78,6 +80,21 @@ public class UserContactInfoQuery extends QueryBase { return this; } + public UserContactInfoQuery excludedUserIds(Collection values) { + this.excludedUserIds = values; + return this; + } + + public UserContactInfoQuery excludedUserIds(UUID value) { + this.excludedUserIds = List.of(value); + return this; + } + + public UserContactInfoQuery excludedUserIds(UUID... value) { + this.excludedUserIds = Arrays.asList(value); + return this; + } + public UserContactInfoQuery tenantIds(UUID value) { this.tenantIds = List.of(value); return this; @@ -123,6 +140,21 @@ public class UserContactInfoQuery extends QueryBase { return this; } + public UserContactInfoQuery values(String value) { + this.values = List.of(value); + return this; + } + + public UserContactInfoQuery values(String... value) { + this.values = Arrays.asList(value); + return this; + } + + public UserContactInfoQuery values(Collection values) { + this.values = values; + return this; + } + public UserContactInfoQuery userSubQuery(UserQuery subQuery) { this.userQuery = subQuery; return this; @@ -135,7 +167,8 @@ public class UserContactInfoQuery extends QueryBase { @Override protected Boolean isFalseQuery() { - return this.isEmpty(this.userIds) || this.isEmpty(this.tenantIds) || this.isEmpty(this.isActives) || this.isEmpty(this.type) || this.isFalseQuery(this.userQuery); + return this.isEmpty(this.userIds) || this.isEmpty(this.excludedUserIds) || this.isEmpty(this.tenantIds) || this.isEmpty(this.isActives) + || this.isEmpty(this.type) || this.isEmpty(this.excludedUserIds) || this.isFalseQuery(this.userQuery); } @Override @@ -170,6 +203,12 @@ public class UserContactInfoQuery extends QueryBase { for (UUID item : this.userIds) inClause.value(item); predicates.add(inClause); } + if (this.excludedUserIds != null) { + CriteriaBuilder.In notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._userId)); + for (UUID item : this.excludedUserIds) + notInClause.value(item); + predicates.add(notInClause.not()); + } if (this.tenantIds != null) { CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._tenantId)); for (UUID item : this.tenantIds) inClause.value(item); @@ -180,6 +219,12 @@ public class UserContactInfoQuery extends QueryBase { for (ContactInfoType item : this.type) inClause.value(item); predicates.add(inClause); } + if (this.values != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserContactInfoEntity._value)); + for (String item : this.values) + inClause.value(item); + predicates.add(inClause); + } if (this.userQuery != null) { Subquery subQuery = queryContext.Query.subquery(this.userQuery.entityClass()); this.applySubQuery(this.userQuery, queryContext.CriteriaBuilder, subQuery); diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserCredentialQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserCredentialQuery.java new file mode 100644 index 000000000..e07a4cd7d --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserCredentialQuery.java @@ -0,0 +1,193 @@ +package gr.cite.notification.query; + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserCredentialEntity; +import gr.cite.notification.model.UserCredential; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +import java.time.Instant; +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserCredentialQuery extends QueryBase { + private Collection ids; + private Collection excludedIds; + private Collection userIds; + private Collection externalIds; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + private final AuthorizationService authService; + public UserCredentialQuery(UserScope userScope, AuthorizationService authService) { + this.userScope = userScope; + this.authService = authService; + } + + public UserCredentialQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UserCredentialQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UserCredentialQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UserCredentialQuery excludedIds(Collection values) { + this.excludedIds = values; + return this; + } + + public UserCredentialQuery excludedIds(UUID value) { + this.excludedIds = List.of(value); + return this; + } + + public UserCredentialQuery excludedIds(UUID... value) { + this.excludedIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery userIds(UUID value) { + this.userIds = List.of(value); + return this; + } + + public UserCredentialQuery userIds(UUID... value) { + this.userIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery userIds(Collection values) { + this.userIds = values; + return this; + } + + public UserCredentialQuery externalIds(String value) { + this.externalIds = List.of(value); + return this; + } + + public UserCredentialQuery externalIds(String... value) { + this.externalIds = Arrays.asList(value); + return this; + } + + public UserCredentialQuery externalIds(Collection values) { + this.externalIds = values; + return this; + } + + public UserCredentialQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return + this.isEmpty(this.ids) || + this.isEmpty(this.userIds) || + this.isEmpty(this.externalIds) || + this.isEmpty(this.excludedIds); + } + + @Override + protected Class entityClass() { + return UserCredentialEntity.class; + } + + @Override + protected Predicate applyAuthZ(QueryContext queryContext) { + if (this.authorize.contains(AuthorizationFlags.None)) return null; + if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseUser)) return null; + UUID userId; + if (this.authorize.contains(AuthorizationFlags.Owner)) userId = this.userScope.getUserIdSafe(); + else userId = null; + + List predicates = new ArrayList<>(); + if (userId != null) { + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._userId)).value(userId)); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause); + } + if (this.userIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._userId)); + for (UUID item : this.userIds) + inClause.value(item); + predicates.add(inClause); + } + if (this.excludedIds != null) { + CriteriaBuilder.In notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._id)); + for (UUID item : this.excludedIds) + notInClause.value(item); + predicates.add(notInClause.not()); + } + if (this.externalIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserCredentialEntity._externalId)); + for (String item : this.externalIds) + inClause.value(item); + predicates.add(inClause); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UserCredential._id)) return UserCredentialEntity._id; + else if (item.match(UserCredential._externalId)) return UserCredentialEntity._externalId; + else if (item.prefix(UserCredential._user)) return UserCredentialEntity._userId; + else if (item.match(UserCredential._user)) return UserCredentialEntity._userId; + else if (item.match(UserCredential._createdAt) ) return UserCredentialEntity._createdAt; + else return null; + } + + @Override + protected UserCredentialEntity convert(Tuple tuple, Set columns) { + UserCredentialEntity item = new UserCredentialEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._id, UUID.class)); + item.setExternalId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._externalId, String.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._userId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._createdAt, Instant.class)); + return item; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserRoleQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserRoleQuery.java new file mode 100644 index 000000000..33e882f1e --- /dev/null +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/UserRoleQuery.java @@ -0,0 +1,194 @@ +package gr.cite.notification.query; + + +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.notification.authorization.AuthorizationFlags; +import gr.cite.notification.authorization.Permission; +import gr.cite.notification.common.scope.user.UserScope; +import gr.cite.notification.data.UserRoleEntity; +import gr.cite.notification.model.UserRole; +import gr.cite.tools.data.query.FieldResolver; +import gr.cite.tools.data.query.QueryBase; +import gr.cite.tools.data.query.QueryContext; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +import java.time.Instant; +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class UserRoleQuery extends QueryBase { + private Collection ids; + private Collection excludedIds; + private Collection userIds; + private Collection roles; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + + private final UserScope userScope; + private final AuthorizationService authService; + public UserRoleQuery(UserScope userScope, AuthorizationService authService) { + this.userScope = userScope; + this.authService = authService; + } + + public UserRoleQuery ids(UUID value) { + this.ids = List.of(value); + return this; + } + + public UserRoleQuery ids(UUID... value) { + this.ids = Arrays.asList(value); + return this; + } + + public UserRoleQuery ids(Collection values) { + this.ids = values; + return this; + } + + public UserRoleQuery excludedIds(Collection values) { + this.excludedIds = values; + return this; + } + + public UserRoleQuery excludedIds(UUID value) { + this.excludedIds = List.of(value); + return this; + } + + public UserRoleQuery excludedIds(UUID... value) { + this.excludedIds = Arrays.asList(value); + return this; + } + + public UserRoleQuery userIds(UUID value) { + this.userIds = List.of(value); + return this; + } + + public UserRoleQuery userIds(UUID... value) { + this.userIds = Arrays.asList(value); + return this; + } + + public UserRoleQuery userIds(Collection values) { + this.userIds = values; + return this; + } + + public UserRoleQuery roles(String value) { + this.roles = List.of(value); + return this; + } + + public UserRoleQuery roles(String... value) { + this.roles = Arrays.asList(value); + return this; + } + + public UserRoleQuery roles(Collection values) { + this.roles = values; + return this; + } + + public UserRoleQuery authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + protected Boolean isFalseQuery() { + return + this.isEmpty(this.ids) || + this.isEmpty(this.userIds) || + this.isEmpty(this.roles) || + this.isEmpty(this.excludedIds); + } + + @Override + protected Class entityClass() { + return UserRoleEntity.class; + } + + @Override + protected Predicate applyAuthZ(QueryContext queryContext) { + if (this.authorize.contains(AuthorizationFlags.None)) return null; + if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseUser)) return null; + UUID userId; + if (this.authorize.contains(AuthorizationFlags.Owner)) userId = this.userScope.getUserIdSafe(); + else userId = null; + + List predicates = new ArrayList<>(); + if (userId != null) { + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(UserRoleEntity._userId)).value(userId)); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return queryContext.CriteriaBuilder.or(); //Creates a false query + } + } + + @Override + protected Predicate applyFilters(QueryContext queryContext) { + List predicates = new ArrayList<>(); + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserRoleEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause); + } + if (this.userIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserRoleEntity._userId)); + for (UUID item : this.userIds) + inClause.value(item); + predicates.add(inClause); + } + if (this.excludedIds != null) { + CriteriaBuilder.In notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserRoleEntity._id)); + for (UUID item : this.excludedIds) + notInClause.value(item); + predicates.add(notInClause.not()); + } + if (this.roles != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UserRoleEntity._role)); + for (String item : this.roles) + inClause.value(item); + predicates.add(inClause); + } + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } + } + + @Override + protected String fieldNameOf(FieldResolver item) { + if (item.match(UserRole._id)) return UserRoleEntity._id; + else if (item.match(UserRole._role)) return UserRoleEntity._role; + else if (item.prefix(UserRole._user)) return UserRoleEntity._userId; + else if (item.match(UserRole._user)) return UserRoleEntity._userId; + else if (item.match(UserRole._createdAt) ) return UserRoleEntity._createdAt; + else return null; + } + + @Override + protected UserRoleEntity convert(Tuple tuple, Set columns) { + UserRoleEntity item = new UserRoleEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, UserRoleEntity._id, UUID.class)); + item.setRole(QueryBase.convertSafe(tuple, columns, UserRoleEntity._role, String.class)); + item.setUserId(QueryBase.convertSafe(tuple, columns, UserRoleEntity._userId, UUID.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserRoleEntity._createdAt, Instant.class)); + return item; + } + +} diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java index 49b3dd455..0787f468b 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java @@ -83,7 +83,7 @@ public class NotificationServiceTemplateImpl implements NotificationTemplateServ public NotificationTemplate persist(NotificationTemplatePersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException { logger.debug(new MapLogEntry("persisting notification template").And("model", model).And("fields", fields)); -// this.authorizationService.authorizeForce(Permission.EditNotificationTemplate); TODO + this.authorizationService.authorizeForce(Permission.EditNotificationTemplate); Boolean isUpdate = this.conventionService.isValidGuid(model.getId());