diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java new file mode 100644 index 000000000..005bd9604 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java @@ -0,0 +1,9 @@ +package eu.eudat.authorization; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration("AppAuthorizationConfiguration") +@EnableConfigurationProperties(AuthorizationProperties.class) +public class AuthorizationConfiguration { +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java new file mode 100644 index 000000000..acb8fe32c --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java @@ -0,0 +1,31 @@ +package eu.eudat.authorization; + + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.HashSet; +import java.util.List; + +@ConfigurationProperties(prefix = "authorization") +public class AuthorizationProperties { + + private List allowedTenantRoles; + + public List getAllowedTenantRoles() { + return allowedTenantRoles; + } + + public void setAllowedTenantRoles(List allowedTenantRoles) { + this.allowedTenantRoles = allowedTenantRoles; + } + + private List allowedGlobalRoles; + + public List getAllowedGlobalRoles() { + return allowedGlobalRoles; + } + + public void setAllowedGlobalRoles(List allowedGlobalRoles) { + this.allowedGlobalRoles = allowedGlobalRoles; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java index 4e6edfdbd..e29f99c84 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java @@ -2,4 +2,8 @@ package eu.eudat.authorization; public class ClaimNames { public static final String ExternalProviderName = "ExternalProviderName"; + public static final String TenantCodesClaimName = "TenantCodes"; + public static final String TenantClaimName = "x-tenant"; + public static final String GlobalRolesClaimName = "GlobalRoles"; + public static final String TenantRolesClaimName = "TenantRoles"; } diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java index 3c88a8115..52a884d0d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java @@ -16,9 +16,6 @@ import java.util.concurrent.atomic.AtomicReference; @RequestScope public class TenantScope { public static final String TenantReplaceParameter = "::TenantCode::"; - public static final String TenantCodesClaimName = "TenantCodes"; - public static final String TenantClaimName = "x-tenant"; - private final MultitenancyProperties multitenancy; private final AtomicReference tenant = new AtomicReference<>(); private final AtomicReference tenantCode = new AtomicReference<>(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java index 2ad57ad54..ee1cce6c6 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java @@ -1,6 +1,7 @@ package eu.eudat.controllers; import eu.eudat.audit.AuditableAction; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.models.Account; import eu.eudat.models.AccountBuilder; @@ -83,7 +84,7 @@ public class PrincipalController { logger.debug("my-tenants"); MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); - List tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName); + List tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName); this.auditService.track(AuditableAction.Principal_MyTenants); //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java index 904bc2b67..62e2d0d0a 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java @@ -1,15 +1,20 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.AuthorizationProperties; +import eu.eudat.authorization.ClaimNames; import eu.eudat.authorization.Permission; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.lock.LockByKeyManager; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.convention.ConventionService; import eu.eudat.data.TenantUserEntity; import eu.eudat.data.UserEntity; +import eu.eudat.data.UserRoleEntity; import eu.eudat.data.tenant.TenantScopedBaseEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; +import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.query.utils.BuildSubQueryInput; import eu.eudat.query.utils.QueryUtilsService; import gr.cite.commons.web.authz.service.AuthorizationService; @@ -19,7 +24,6 @@ import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.Tuple; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Root; @@ -41,6 +45,7 @@ import org.springframework.web.context.request.WebRequestInterceptor; import javax.management.InvalidApplicationException; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -61,6 +66,9 @@ public class TenantInterceptor implements WebRequestInterceptor { private final ErrorThesaurusProperties errors; private final QueryUtilsService queryUtilsService; private final LockByKeyManager lockByKeyManager; + private final ConventionService conventionService; + private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler; + private final AuthorizationProperties authorizationProperties; @PersistenceContext public EntityManager entityManager; @@ -74,7 +82,7 @@ public class TenantInterceptor implements WebRequestInterceptor { TenantScopeProperties tenantScopeProperties, UserAllowedTenantCacheService userAllowedTenantCacheService, PlatformTransactionManager transactionManager, - ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager) { + ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager, ConventionService conventionService, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties) { this.tenantScope = tenantScope; this.userScope = userScope; this.currentPrincipalResolver = currentPrincipalResolver; @@ -86,6 +94,9 @@ public class TenantInterceptor implements WebRequestInterceptor { this.errors = errors; this.queryUtilsService = queryUtilsService; this.lockByKeyManager = lockByKeyManager; + this.conventionService = conventionService; + this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler; + this.authorizationProperties = authorizationProperties; } @Override @@ -95,7 +106,7 @@ public class TenantInterceptor implements WebRequestInterceptor { boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant); if (tenantScope.isSet() && this.entityManager != null) { - List currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName); + List currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantCodesClaimName); if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) { logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); @@ -133,6 +144,8 @@ public class TenantInterceptor implements WebRequestInterceptor { throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); } } + + this.syncUserWithClaims(); } else { if (!isAllowedNoTenant) { if (!this.isWhiteListedEndpoint(request)) { @@ -163,7 +176,7 @@ public class TenantInterceptor implements WebRequestInterceptor { if (this.tenantScopeProperties.getAutoCreateTenantUser()) usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(UserEntity.class); Root root = query.from(UserEntity.class); query.where(criteriaBuilder.and( criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active), @@ -188,7 +201,7 @@ public class TenantInterceptor implements WebRequestInterceptor { ) )); query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.isEmpty() && this.tenantScopeProperties.getAutoCreateTenantUser()) { return this.createTenantUser(); } else { @@ -210,7 +223,6 @@ public class TenantInterceptor implements WebRequestInterceptor { user.setIsActive(IsActive.Active); user.setTenantId(this.tenantScope.getTenant()); user.setUserId(userScope.getUserId()); - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); @@ -227,9 +239,122 @@ public class TenantInterceptor implements WebRequestInterceptor { if (status != null) transactionManager.rollback(status); throw ex; } + this.userTouchedIntegrationEventHandler.handle(this.userScope.getUserId()); return true; } + private void syncUserWithClaims() throws InvalidApplicationException, InterruptedException { + boolean usedResource = false; + String lockId = userScope.getUserId().toString().toLowerCase(Locale.ROOT); + boolean hasChanges = false; + try { + usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS); + + 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); + + List existingUserRoles = this.collectUserRoles(); + if (!this.userRolesSynced(existingUserRoles)) { + this.syncRoles(); + hasChanges = true; + } + + this.entityManager.flush(); + transactionManager.commit(status); + } catch (Exception ex) { + if (status != null) transactionManager.rollback(status); + throw ex; + } + } finally { + if (usedResource) this.lockByKeyManager.unlock(lockId); + } + if (hasChanges){ + this.userTouchedIntegrationEventHandler.handle(this.userScope.getUserId()); + } + } + + private List getRolesFromClaims() { + List claimsRoles = this.claimExtractor.asStrings(currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantRolesClaimName); + if (claimsRoles == null) claimsRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank() && (this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) || this.authorizationProperties.getAllowedTenantRoles().contains(x))).distinct().toList(); + return claimsRoles; + } + + private List collectUserRoles() throws InvalidApplicationException { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedTenantRoles()) inRolesClause.value(item); + + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userScope.getUserId()), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + this.tenantScope.isDefaultTenant() ? criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) : criteriaBuilder.equal(root.get(UserRoleEntity._tenantId), this.tenantScope.getTenant()) + )).multiselect(root.get(UserRoleEntity._role).alias(UserRoleEntity._role)); + List results = this.entityManager.createQuery(query).getResultList(); + + return results.stream().map(UserRoleEntity::getRole).toList(); + } + + private boolean userRolesSynced(List existingUserRoles) { + List claimsRoles = this.getRolesFromClaims(); + if (existingUserRoles == null) existingUserRoles = new ArrayList<>(); + existingUserRoles = existingUserRoles.stream().filter(x -> x != null && !x.isBlank()).distinct().toList(); + if (claimsRoles.size() != existingUserRoles.size()) return false; + + for (String claim : claimsRoles) { + if (existingUserRoles.stream().noneMatch(claim::equalsIgnoreCase)) return false; + } + return true; + } + + private void syncRoles() throws InvalidApplicationException { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedTenantRoles()) inRolesClause.value(item); + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userScope.getUserId()), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + this.tenantScope.isDefaultTenant() ? criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) : criteriaBuilder.equal(root.get(UserRoleEntity._tenantId), this.tenantScope.getTenant()) + )); + List existingUserRoles = this.entityManager.createQuery(query).getResultList(); + + List foundRoles = new ArrayList<>(); + for (String claimRole : this.getRolesFromClaims()) { + UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null); + if (roleEntity == null) { + roleEntity = this.buildRole(claimRole); + this.entityManager.persist(roleEntity); + } + foundRoles.add(roleEntity.getId()); + } + for (UserRoleEntity existing : existingUserRoles) { + if (!foundRoles.contains(existing.getId())) { + this.entityManager.remove(existing); + } + } + } + + private UserRoleEntity buildRole(String role) throws InvalidApplicationException { + UserRoleEntity data = new UserRoleEntity(); + data.setId(UUID.randomUUID()); + data.setUserId( userScope.getUserId()); + data.setRole(role); + if (this.tenantScope.isDefaultTenant()) data.setTenantId(this.tenantScope.getTenant()); + data.setCreatedAt(Instant.now()); + return data; + } + @Override public void postHandle(@NonNull WebRequest request, ModelMap model) { this.tenantScope.setTenant(null, null); diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java index 38236eba7..a1661c9d7 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java @@ -1,10 +1,12 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.convention.ConventionService; import eu.eudat.data.TenantEntity; +import eu.eudat.data.UserEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.MyPrincipal; @@ -68,7 +70,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { this.claimExtractorContext = claimExtractorContext; this.tenantByCodeCacheService = tenantByCodeCacheService; this.tenantByIdCacheService = tenantByIdCacheService; - this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName; + this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName; } @Override @@ -140,7 +142,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { private UUID getTenantIdFromDatabase(String tenantCode) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(UserEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -148,27 +150,16 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._id); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (UUID) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getId(); } return null; } private String getTenantCodeFromDatabase(UUID tenantId) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -176,20 +167,9 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._code); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (String) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getCode(); } return null; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java index b301abea7..9f5c98919 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java @@ -1,6 +1,7 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.convention.ConventionService; @@ -60,7 +61,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.tenantScope.isMultitenant()) return; - String tenantCode = request.getHeader(TenantScope.TenantClaimName); + String tenantCode = request.getHeader(ClaimNames.TenantClaimName); logger.debug("retrieved request tenant header is: {}", tenantCode); if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return; @@ -101,7 +102,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { private UUID getTenantIdFromDatabase(String tenantCode) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -109,27 +110,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._id); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (UUID) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getId(); } return null; } private String getTenantCodeFromDatabase(UUID tenantId) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -137,20 +127,9 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._code); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (String) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getCode(); } return null; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java index 543278f51..282413e60 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java @@ -1,6 +1,7 @@ package eu.eudat.interceptors.user; +import eu.eudat.authorization.AuthorizationProperties; import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.enums.ContactInfoType; @@ -10,17 +11,13 @@ import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.user.AdditionalInfoEntity; import eu.eudat.commons.types.usercredential.UserCredentialDataEntity; import eu.eudat.commons.locale.LocaleProperties; -import eu.eudat.data.UserContactInfoEntity; -import eu.eudat.data.UserCredentialEntity; -import eu.eudat.data.UserEntity; -import eu.eudat.data.UserRoleEntity; +import eu.eudat.convention.ConventionService; +import eu.eudat.data.*; import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.model.UserContactInfo; import eu.eudat.model.UserCredential; -import eu.eudat.model.UserRole; import eu.eudat.query.UserContactInfoQuery; import eu.eudat.query.UserCredentialQuery; -import eu.eudat.query.UserRoleQuery; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.tools.data.query.QueryFactory; @@ -29,6 +26,10 @@ import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.apache.commons.validator.routines.EmailValidator; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -61,6 +62,8 @@ public class UserInterceptor implements WebRequestInterceptor { private final LockByKeyManager lockByKeyManager; private final LocaleProperties localeProperties; private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler; + private final AuthorizationProperties authorizationProperties; + private final ConventionService conventionService; @PersistenceContext public EntityManager entityManager; @@ -74,7 +77,7 @@ public class UserInterceptor implements WebRequestInterceptor { JsonHandlingService jsonHandlingService, QueryFactory queryFactory, LockByKeyManager lockByKeyManager, - LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler) { + LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties, ConventionService conventionService) { this.userScope = userScope; this.currentPrincipalResolver = currentPrincipalResolver; this.claimExtractor = claimExtractor; @@ -85,6 +88,8 @@ public class UserInterceptor implements WebRequestInterceptor { this.lockByKeyManager = lockByKeyManager; this.localeProperties = localeProperties; this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler; + this.authorizationProperties = authorizationProperties; + this.conventionService = conventionService; } @Override @@ -173,11 +178,11 @@ public class UserInterceptor implements WebRequestInterceptor { } } -// List existingUserRoles = this.collectUserRoles(userId); -// if (!this.userRolesSynced(existingUserRoles)) { -// this.syncRoles(userId); -// hasChanges = true; -// } + List existingUserRoles = this.collectUserRoles(userId); + if (!this.userRolesSynced(existingUserRoles)) { + this.syncRoles(userId); + hasChanges = true; + } UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first(); if (userCredential == null) { @@ -230,14 +235,27 @@ public class UserInterceptor implements WebRequestInterceptor { } private List getRolesFromClaims() { - List claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + List claimsRoles = this.claimExtractor.asStrings(currentPrincipalResolver.currentPrincipal(), ClaimNames.GlobalRolesClaimName); if (claimsRoles == null) claimsRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank() && (this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) || this.authorizationProperties.getAllowedGlobalRoles().contains(x))).distinct().toList(); claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank()).distinct().toList(); return claimsRoles; } private void syncRoles(UUID userId) { - List existingUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collect(); + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedGlobalRoles()) inRolesClause.value(item); + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userId), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) + )); + List existingUserRoles = this.entityManager.createQuery(query).getResultList(); + List foundRoles = new ArrayList<>(); for (String claimRole : this.getRolesFromClaims()) { UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null); @@ -255,8 +273,21 @@ public class UserInterceptor implements WebRequestInterceptor { } 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).toList(); + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedGlobalRoles()) inRolesClause.value(item); + + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userId), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) + )).multiselect(root.get(UserRoleEntity._role).alias(UserRoleEntity._role)); + List results = this.entityManager.createQuery(query).getResultList(); + + return results.stream().map(UserRoleEntity::getRole).toList(); } private List collectUserEmails(UUID userId) { diff --git a/dmp-backend/web/src/main/resources/config/application.yml b/dmp-backend/web/src/main/resources/config/application.yml index a4b175e10..686f12bc6 100644 --- a/dmp-backend/web/src/main/resources/config/application.yml +++ b/dmp-backend/web/src/main/resources/config/application.yml @@ -31,6 +31,7 @@ spring: optional:classpath:config/public-api.yml[.yml], optional:classpath:config/public-api-${spring.profiles.active}.yml[.yml], optional:file:../config/public-api-${spring.profiles.active}.yml[.yml], optional:classpath:config/dashboard.yml[.yml], optional:classpath:config/dashboard-${spring.profiles.active}.yml[.yml], optional:file:../config/dashboard-${spring.profiles.active}.yml[.yml], optional:classpath:config/transformer.yml[.yml], optional:classpath:config/transformer-${spring.profiles.active}.yml[.yml], optional:file:../config/transformer-${spring.profiles.active}.yml[.yml], + optional:classpath:config/authorization.yml[.yml], optional:classpath:config/authorization-${spring.profiles.active}.yml[.yml], optional:file:../config/authorization-${spring.profiles.active}.yml[.yml], optional:classpath:config/lock.yml[.yml], optional:classpath:config/lock-${spring.profiles.active}.yml[.yml], optional:file:../config/lock-${spring.profiles.active}.yml[.yml] diff --git a/dmp-backend/web/src/main/resources/config/authorization.yml b/dmp-backend/web/src/main/resources/config/authorization.yml new file mode 100644 index 000000000..8fde55603 --- /dev/null +++ b/dmp-backend/web/src/main/resources/config/authorization.yml @@ -0,0 +1,9 @@ +authorization: + allowedTenantRoles: + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor + allowedGlobalRoles: + - Admin + - User \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/config/idpclaims.yml b/dmp-backend/web/src/main/resources/config/idpclaims.yml index 94e843a48..26e00f2a1 100644 --- a/dmp-backend/web/src/main/resources/config/idpclaims.yml +++ b/dmp-backend/web/src/main/resources/config/idpclaims.yml @@ -24,6 +24,14 @@ idpclient: filterBy: "(.*):::TenantCode::" extractByExpression: "(.*):(.*)" extractExpressionValue: "[[g1]]" + GlobalRoles: + - type: resource_access + path: dmp_web.roles + TenantRoles: + - type: tenant_roles + filterBy: "(.*):::TenantCode::" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g1]]" Scope: - type: scope AccessToken: