multi tenant changes

This commit is contained in:
Efstratios Giannopoulos 2024-04-02 18:39:46 +03:00
parent 83b5ec5a80
commit 3e37c91035
12 changed files with 259 additions and 84 deletions

View File

@ -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 {
}

View File

@ -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<String> allowedTenantRoles;
public List<String> getAllowedTenantRoles() {
return allowedTenantRoles;
}
public void setAllowedTenantRoles(List<String> allowedTenantRoles) {
this.allowedTenantRoles = allowedTenantRoles;
}
private List<String> allowedGlobalRoles;
public List<String> getAllowedGlobalRoles() {
return allowedGlobalRoles;
}
public void setAllowedGlobalRoles(List<String> allowedGlobalRoles) {
this.allowedGlobalRoles = allowedGlobalRoles;
}
}

View File

@ -2,4 +2,8 @@ package eu.eudat.authorization;
public class ClaimNames { public class ClaimNames {
public static final String ExternalProviderName = "ExternalProviderName"; 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";
} }

View File

@ -16,9 +16,6 @@ import java.util.concurrent.atomic.AtomicReference;
@RequestScope @RequestScope
public class TenantScope { public class TenantScope {
public static final String TenantReplaceParameter = "::TenantCode::"; 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 MultitenancyProperties multitenancy;
private final AtomicReference<UUID> tenant = new AtomicReference<>(); private final AtomicReference<UUID> tenant = new AtomicReference<>();
private final AtomicReference<String> tenantCode = new AtomicReference<>(); private final AtomicReference<String> tenantCode = new AtomicReference<>();

View File

@ -1,6 +1,7 @@
package eu.eudat.controllers; package eu.eudat.controllers;
import eu.eudat.audit.AuditableAction; import eu.eudat.audit.AuditableAction;
import eu.eudat.authorization.ClaimNames;
import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.models.Account; import eu.eudat.models.Account;
import eu.eudat.models.AccountBuilder; import eu.eudat.models.AccountBuilder;
@ -83,7 +84,7 @@ public class PrincipalController {
logger.debug("my-tenants"); logger.debug("my-tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<String> tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName); List<String> tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName);
this.auditService.track(AuditableAction.Principal_MyTenants); this.auditService.track(AuditableAction.Principal_MyTenants);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action); //auditService.trackIdentity(AuditableAction.IdentityTracking_Action);

View File

@ -1,15 +1,20 @@
package eu.eudat.interceptors.tenant; package eu.eudat.interceptors.tenant;
import eu.eudat.authorization.AuthorizationProperties;
import eu.eudat.authorization.ClaimNames;
import eu.eudat.authorization.Permission; import eu.eudat.authorization.Permission;
import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.lock.LockByKeyManager; import eu.eudat.commons.lock.LockByKeyManager;
import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.scope.user.UserScope;
import eu.eudat.convention.ConventionService;
import eu.eudat.data.TenantUserEntity; import eu.eudat.data.TenantUserEntity;
import eu.eudat.data.UserEntity; import eu.eudat.data.UserEntity;
import eu.eudat.data.UserRoleEntity;
import eu.eudat.data.tenant.TenantScopedBaseEntity; import eu.eudat.data.tenant.TenantScopedBaseEntity;
import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.errorcode.ErrorThesaurusProperties;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
import eu.eudat.query.utils.BuildSubQueryInput; import eu.eudat.query.utils.BuildSubQueryInput;
import eu.eudat.query.utils.QueryUtilsService; import eu.eudat.query.utils.QueryUtilsService;
import gr.cite.commons.web.authz.service.AuthorizationService; 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 gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
@ -41,6 +45,7 @@ import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
@ -61,6 +66,9 @@ public class TenantInterceptor implements WebRequestInterceptor {
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final QueryUtilsService queryUtilsService; private final QueryUtilsService queryUtilsService;
private final LockByKeyManager lockByKeyManager; private final LockByKeyManager lockByKeyManager;
private final ConventionService conventionService;
private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler;
private final AuthorizationProperties authorizationProperties;
@PersistenceContext @PersistenceContext
public EntityManager entityManager; public EntityManager entityManager;
@ -74,7 +82,7 @@ public class TenantInterceptor implements WebRequestInterceptor {
TenantScopeProperties tenantScopeProperties, TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService, UserAllowedTenantCacheService userAllowedTenantCacheService,
PlatformTransactionManager transactionManager, PlatformTransactionManager transactionManager,
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager) { ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager, ConventionService conventionService, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties) {
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.userScope = userScope; this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver; this.currentPrincipalResolver = currentPrincipalResolver;
@ -86,6 +94,9 @@ public class TenantInterceptor implements WebRequestInterceptor {
this.errors = errors; this.errors = errors;
this.queryUtilsService = queryUtilsService; this.queryUtilsService = queryUtilsService;
this.lockByKeyManager = lockByKeyManager; this.lockByKeyManager = lockByKeyManager;
this.conventionService = conventionService;
this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler;
this.authorizationProperties = authorizationProperties;
} }
@Override @Override
@ -95,7 +106,7 @@ public class TenantInterceptor implements WebRequestInterceptor {
boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant); boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant);
if (tenantScope.isSet() && this.entityManager != null) { if (tenantScope.isSet() && this.entityManager != null) {
List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName); List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantCodesClaimName);
if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) { if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) {
logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); 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()); throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
} }
} }
this.syncUserWithClaims();
} else { } else {
if (!isAllowedNoTenant) { if (!isAllowedNoTenant) {
if (!this.isWhiteListedEndpoint(request)) { 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); if (this.tenantScopeProperties.getAutoCreateTenantUser()) usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS);
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<UserEntity> query = criteriaBuilder.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class); Root<UserEntity> root = query.from(UserEntity.class);
query.where(criteriaBuilder.and( query.where(criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active), 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)); query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.isEmpty() && this.tenantScopeProperties.getAutoCreateTenantUser()) { if (results.isEmpty() && this.tenantScopeProperties.getAutoCreateTenantUser()) {
return this.createTenantUser(); return this.createTenantUser();
} else { } else {
@ -212,7 +225,6 @@ public class TenantInterceptor implements WebRequestInterceptor {
user.setUserId(userScope.getUserId()); user.setUserId(userScope.getUserId());
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setName(UUID.randomUUID().toString()); definition.setName(UUID.randomUUID().toString());
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
@ -227,9 +239,122 @@ public class TenantInterceptor implements WebRequestInterceptor {
if (status != null) transactionManager.rollback(status); if (status != null) transactionManager.rollback(status);
throw ex; throw ex;
} }
this.userTouchedIntegrationEventHandler.handle(this.userScope.getUserId());
return true; 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<String> 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<String> getRolesFromClaims() {
List<String> 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<String> collectUserRoles() throws InvalidApplicationException {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<UserRoleEntity> query = criteriaBuilder.createQuery(UserRoleEntity.class);
Root<UserRoleEntity> root = query.from(UserRoleEntity.class);
CriteriaBuilder.In<String> 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<UserRoleEntity> results = this.entityManager.createQuery(query).getResultList();
return results.stream().map(UserRoleEntity::getRole).toList();
}
private boolean userRolesSynced(List<String> existingUserRoles) {
List<String> 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<UserRoleEntity> query = criteriaBuilder.createQuery(UserRoleEntity.class);
Root<UserRoleEntity> root = query.from(UserRoleEntity.class);
CriteriaBuilder.In<String> 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<UserRoleEntity> existingUserRoles = this.entityManager.createQuery(query).getResultList();
List<UUID> 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 @Override
public void postHandle(@NonNull WebRequest request, ModelMap model) { public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null); this.tenantScope.setTenant(null, null);

View File

@ -1,10 +1,12 @@
package eu.eudat.interceptors.tenant; package eu.eudat.interceptors.tenant;
import eu.eudat.authorization.ClaimNames;
import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.convention.ConventionService; import eu.eudat.convention.ConventionService;
import eu.eudat.data.TenantEntity; import eu.eudat.data.TenantEntity;
import eu.eudat.data.UserEntity;
import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.errorcode.ErrorThesaurusProperties;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.MyPrincipal;
@ -68,7 +70,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
this.claimExtractorContext = claimExtractorContext; this.claimExtractorContext = claimExtractorContext;
this.tenantByCodeCacheService = tenantByCodeCacheService; this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService; this.tenantByIdCacheService = tenantByIdCacheService;
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName; this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName;
} }
@Override @Override
@ -140,7 +142,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
private UUID getTenantIdFromDatabase(String tenantCode) { private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<UserEntity> query = criteriaBuilder.createQuery(UserEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -148,27 +150,16 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getId();
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 null; return null;
} }
private String getTenantCodeFromDatabase(UUID tenantId) { private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -176,20 +167,9 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getCode();
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 null; return null;
} }

View File

@ -1,6 +1,7 @@
package eu.eudat.interceptors.tenant; package eu.eudat.interceptors.tenant;
import eu.eudat.authorization.ClaimNames;
import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.convention.ConventionService; import eu.eudat.convention.ConventionService;
@ -60,7 +61,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) 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); logger.debug("retrieved request tenant header is: {}", tenantCode);
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return; if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return;
@ -101,7 +102,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
private UUID getTenantIdFromDatabase(String tenantCode) { private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -109,27 +110,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getId();
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 null; return null;
} }
private String getTenantCodeFromDatabase(UUID tenantId) { private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -137,20 +127,9 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getCode();
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 null; return null;
} }

View File

@ -1,6 +1,7 @@
package eu.eudat.interceptors.user; package eu.eudat.interceptors.user;
import eu.eudat.authorization.AuthorizationProperties;
import eu.eudat.authorization.ClaimNames; import eu.eudat.authorization.ClaimNames;
import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.JsonHandlingService;
import eu.eudat.commons.enums.ContactInfoType; 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.user.AdditionalInfoEntity;
import eu.eudat.commons.types.usercredential.UserCredentialDataEntity; import eu.eudat.commons.types.usercredential.UserCredentialDataEntity;
import eu.eudat.commons.locale.LocaleProperties; import eu.eudat.commons.locale.LocaleProperties;
import eu.eudat.data.UserContactInfoEntity; import eu.eudat.convention.ConventionService;
import eu.eudat.data.UserCredentialEntity; import eu.eudat.data.*;
import eu.eudat.data.UserEntity;
import eu.eudat.data.UserRoleEntity;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
import eu.eudat.model.UserContactInfo; import eu.eudat.model.UserContactInfo;
import eu.eudat.model.UserCredential; import eu.eudat.model.UserCredential;
import eu.eudat.model.UserRole;
import eu.eudat.query.UserContactInfoQuery; import eu.eudat.query.UserContactInfoQuery;
import eu.eudat.query.UserCredentialQuery; 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.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
@ -29,6 +26,10 @@ import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; 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.apache.commons.validator.routines.EmailValidator;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -61,6 +62,8 @@ public class UserInterceptor implements WebRequestInterceptor {
private final LockByKeyManager lockByKeyManager; private final LockByKeyManager lockByKeyManager;
private final LocaleProperties localeProperties; private final LocaleProperties localeProperties;
private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler; private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler;
private final AuthorizationProperties authorizationProperties;
private final ConventionService conventionService;
@PersistenceContext @PersistenceContext
public EntityManager entityManager; public EntityManager entityManager;
@ -74,7 +77,7 @@ public class UserInterceptor implements WebRequestInterceptor {
JsonHandlingService jsonHandlingService, JsonHandlingService jsonHandlingService,
QueryFactory queryFactory, QueryFactory queryFactory,
LockByKeyManager lockByKeyManager, LockByKeyManager lockByKeyManager,
LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler) { LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties, ConventionService conventionService) {
this.userScope = userScope; this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver; this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor; this.claimExtractor = claimExtractor;
@ -85,6 +88,8 @@ public class UserInterceptor implements WebRequestInterceptor {
this.lockByKeyManager = lockByKeyManager; this.lockByKeyManager = lockByKeyManager;
this.localeProperties = localeProperties; this.localeProperties = localeProperties;
this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler; this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler;
this.authorizationProperties = authorizationProperties;
this.conventionService = conventionService;
} }
@Override @Override
@ -173,11 +178,11 @@ public class UserInterceptor implements WebRequestInterceptor {
} }
} }
// List<String> existingUserRoles = this.collectUserRoles(userId); List<String> existingUserRoles = this.collectUserRoles(userId);
// if (!this.userRolesSynced(existingUserRoles)) { if (!this.userRolesSynced(existingUserRoles)) {
// this.syncRoles(userId); this.syncRoles(userId);
// hasChanges = true; hasChanges = true;
// } }
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first(); UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first();
if (userCredential == null) { if (userCredential == null) {
@ -230,14 +235,27 @@ public class UserInterceptor implements WebRequestInterceptor {
} }
private List<String> getRolesFromClaims() { private List<String> getRolesFromClaims() {
List<String> claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); List<String> claimsRoles = this.claimExtractor.asStrings(currentPrincipalResolver.currentPrincipal(), ClaimNames.GlobalRolesClaimName);
if (claimsRoles == null) claimsRoles = new ArrayList<>(); 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(); claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank()).distinct().toList();
return claimsRoles; return claimsRoles;
} }
private void syncRoles(UUID userId) { private void syncRoles(UUID userId) {
List<UserRoleEntity> existingUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collect(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<UserRoleEntity> query = criteriaBuilder.createQuery(UserRoleEntity.class);
Root<UserRoleEntity> root = query.from(UserRoleEntity.class);
CriteriaBuilder.In<String> 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<UserRoleEntity> existingUserRoles = this.entityManager.createQuery(query).getResultList();
List<UUID> foundRoles = new ArrayList<>(); List<UUID> foundRoles = new ArrayList<>();
for (String claimRole : this.getRolesFromClaims()) { for (String claimRole : this.getRolesFromClaims()) {
UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null); UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null);
@ -255,8 +273,21 @@ public class UserInterceptor implements WebRequestInterceptor {
} }
private List<String> collectUserRoles(UUID userId) { private List<String> collectUserRoles(UUID userId) {
List<UserRoleEntity> items = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collectAs(new BaseFieldSet().ensure(UserRole._role)); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
return items == null ? new ArrayList<>() : items.stream().map(UserRoleEntity::getRole).toList(); CriteriaQuery<UserRoleEntity> query = criteriaBuilder.createQuery(UserRoleEntity.class);
Root<UserRoleEntity> root = query.from(UserRoleEntity.class);
CriteriaBuilder.In<String> 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<UserRoleEntity> results = this.entityManager.createQuery(query).getResultList();
return results.stream().map(UserRoleEntity::getRole).toList();
} }
private List<String> collectUserEmails(UUID userId) { private List<String> collectUserEmails(UUID userId) {

View File

@ -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/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/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/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] optional:classpath:config/lock.yml[.yml], optional:classpath:config/lock-${spring.profiles.active}.yml[.yml], optional:file:../config/lock-${spring.profiles.active}.yml[.yml]

View File

@ -0,0 +1,9 @@
authorization:
allowedTenantRoles:
- TenantAdmin
- TenantUser
- TenantManager
- TenantDescriptionTemplateEditor
allowedGlobalRoles:
- Admin
- User

View File

@ -24,6 +24,14 @@ idpclient:
filterBy: "(.*):::TenantCode::" filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)" extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]" extractExpressionValue: "[[g1]]"
GlobalRoles:
- type: resource_access
path: dmp_web.roles
TenantRoles:
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
Scope: Scope:
- type: scope - type: scope
AccessToken: AccessToken: