support default tenant as null

This commit is contained in:
Efstratios Giannopoulos 2024-04-02 13:07:00 +03:00
parent 77d2dcdc39
commit cc407c202a
12 changed files with 191 additions and 118 deletions

View File

@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tenant.multitenancy")
public class MultitenancyProperties {
private boolean isMultitenant;
private String defaultTenantCode;
public boolean isMultitenant() {
return isMultitenant;
@ -13,4 +14,12 @@ public class MultitenancyProperties {
public void setIsMultitenant(boolean multitenant) {
isMultitenant = multitenant;
}
public String getDefaultTenantCode() {
return defaultTenantCode;
}
public void setDefaultTenantCode(String defaultTenantCode) {
this.defaultTenantCode = defaultTenantCode;
}
}

View File

@ -1,10 +1,8 @@
package eu.eudat.commons.scope.tenant;
import eu.eudat.data.tenant.TenantScopedBaseEntity;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@ -22,7 +20,6 @@ public class TenantScope {
public static final String TenantClaimName = "x-tenant";
private final MultitenancyProperties multitenancy;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScope.class));
private final AtomicReference<UUID> tenant = new AtomicReference<>();
private final AtomicReference<String> tenantCode = new AtomicReference<>();
private final AtomicReference<UUID> initialTenant = new AtomicReference<>();
@ -37,45 +34,70 @@ public class TenantScope {
return multitenancy.isMultitenant();
}
public String getDefaultTenantCode() {
return multitenancy.getDefaultTenantCode();
}
public Boolean isSet() {
if (!this.isMultitenant())
return Boolean.TRUE;
return this.tenant.get() != null;
return this.tenant.get() != null || this.isDefaultTenant();
}
public Boolean isDefaultTenant() {
if (!this.isMultitenant())
return Boolean.TRUE;
return this.multitenancy.getDefaultTenantCode().equalsIgnoreCase(this.tenantCode.get());
}
public UUID getTenant() throws InvalidApplicationException {
if (!this.isMultitenant())
return null;
if (this.tenant.get() == null)
if (this.tenant.get() == null && !this.isDefaultTenant())
throw new InvalidApplicationException("tenant not set");
return this.tenant.get();
return this.isDefaultTenant() ? null : this.tenant.get();
}
public String getTenantCode() throws InvalidApplicationException {
if (!this.isMultitenant())
return null;
if (this.tenant.get() == null)
if (this.tenantCode.get() == null)
throw new InvalidApplicationException("tenant not set");
return this.tenantCode.get();
}
public void setTempTenant(EntityManager entityManager, UUID tenant) {
public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) {
this.tenant.set(tenant);
this.tenantCode.set(tenantCode);
if (this.tenant.get() != null) {
if (this.tenant.get() != null && !this.isDefaultTenant()) {
if(!this.isDefaultTenant()) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.tenant.get().toString());
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
} else {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
}
}
public void removeTempTenant(EntityManager entityManager) {
this.tenant.set(this.initialTenant.get());
this.tenantCode.set(this.initialTenantCode.get());
if (this.initialTenant.get() != null) {
if (this.initialTenant.get() != null && !this.isDefaultTenant()) {
if(!this.isDefaultTenant()) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.initialTenant.get().toString());
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
} else {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
}
}

View File

@ -41,7 +41,7 @@ public class TenantEntityManager {
// this.claimExtractor.subjectUUID(this.currentPrincipalResolver.currentPrincipal());
// boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant);
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant();
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant();
final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null;
if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering");
}
@ -50,7 +50,7 @@ public class TenantEntityManager {
public void remove(Object entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) {
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant();
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant();
final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null;
if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering");
}
@ -65,7 +65,7 @@ public class TenantEntityManager {
// this.claimExtractor.subjectUUID(this.currentPrincipalResolver.currentPrincipal());
// boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant);
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant();
boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant();
final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null;
if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) return null;
}

View File

@ -29,11 +29,16 @@ public class TenantFilterAspect {
pointcut = "bean(entityManagerFactory) && execution(* createEntityManager(..))",
returning = "retVal")
public void getSessionAfter(JoinPoint joinPoint, Object retVal) throws InvalidApplicationException {
if (retVal != null && retVal instanceof EntityManager && tenantScope.isSet()) {
if (retVal instanceof EntityManager && tenantScope.isSet()) {
Session session = ((EntityManager) retVal).unwrap(Session.class);
if(!tenantScope.isDefaultTenant()) {
session
.enableFilter(TenantScopedBaseEntity.tenantFilter)
.setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString());
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
} else {
session
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
}
}

View File

@ -29,11 +29,11 @@ public class TenantListener {
@PrePersist
public void setTenantOnCreate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) {
if (entity.getTenantId() != null && entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) {
logger.error("somebody tried to set not login tenat");
if (entity.getTenantId() != null && (this.tenantScope.isDefaultTenant() || entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
logger.error("somebody tried to set not login tenant");
throw new MyForbiddenException("tenant tampering");
}
if (!entity.allowNullTenant()) {
if (!entity.allowNullTenant() && !tenantScope.isDefaultTenant()) {
final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId);
}
@ -47,6 +47,7 @@ public class TenantListener {
public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) {
if (!entity.allowNullTenant()){
if (!tenantScope.isDefaultTenant()) {
if (entity.getTenantId() == null) {
logger.error("somebody tried to set null tenant");
throw new MyForbiddenException("tenant tampering");
@ -59,7 +60,13 @@ public class TenantListener {
final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId);
} else {
if (entity.getTenantId() != null && entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) {
if (entity.getTenantId() != null) {
logger.error("somebody tried to set null tenant");
throw new MyForbiddenException("tenant tampering");
}
}
} else {
if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
logger.error("somebody tried to change an entries tenant");
throw new MyForbiddenException("tenant tampering");
}

View File

@ -14,13 +14,16 @@ import java.util.UUID;
//@Getter
//@Setter
//@NoArgsConstructor
@FilterDef(name = TenantScopedBaseEntity.tenantFilter, parameters = {@ParamDef(name = TenantScopedBaseEntity.tenantFilterTenantParam, type = String.class)})
@Filter(name = "tenantFilter", condition = "(tenant = (cast(:tenantId as uuid)) or tenant is null)")
@FilterDef(name = TenantScopedBaseEntity.TENANT_FILTER, parameters = {@ParamDef(name = TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, type = String.class)})
@FilterDef(name = TenantScopedBaseEntity.DEFAULT_TENANT_FILTER)
@Filter(name = TenantScopedBaseEntity.DEFAULT_TENANT_FILTER, condition = "(tenant = tenant is null)")
@Filter(name = TenantScopedBaseEntity.TENANT_FILTER, condition = "(tenant = (cast(:tenantId as uuid)) or tenant is null)")
@EntityListeners(TenantListener.class)
public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable {
private static final long serialVersionUID = 1L;
public static final String tenantFilter = "tenantFilter";
public static final String tenantFilterTenantParam = "tenantId";
public static final String TENANT_FILTER = "tenantFilter";
public static final String DEFAULT_TENANT_FILTER = "defaultTenantFilter";
public static final String TENANT_FILTER_TENANT_PARAM = "tenantId";
@Column(name = "tenant", columnDefinition = "uuid", nullable = true)
private UUID tenantId;

View File

@ -38,15 +38,6 @@ public class DescriptionTemplateTypePersist {
private DescriptionTemplateTypeStatus status;
private Map<UUID, DescriptionTemplateTypePersist> test;
public final static String _test = "test";
public Map<UUID, DescriptionTemplateTypePersist> getTest() {
return test;
}
public void setTest(Map<UUID, DescriptionTemplateTypePersist> test) {
this.test = test;
}
public UUID getId() {
return id;
@ -86,12 +77,10 @@ public class DescriptionTemplateTypePersist {
public static final String ValidatorName = "DescriptionTemplateTypePersistValidator";
private final MessageSource messageSource;
private final ValidatorFactory validatorFactory;
public DescriptionTemplateTypePersistValidator(MessageSource messageSource, ConventionService conventionService, ErrorThesaurusProperties errors, ValidatorFactory validatorFactory) {
public DescriptionTemplateTypePersistValidator(MessageSource messageSource, ConventionService conventionService, ErrorThesaurusProperties errors) {
super(conventionService, errors);
this.messageSource = messageSource;
this.validatorFactory = validatorFactory;
}
@Override

View File

@ -3,10 +3,9 @@ package eu.eudat.interceptors.tenant;
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.data.DmpReferenceEntity;
import eu.eudat.data.ReferenceEntity;
import eu.eudat.data.TenantUserEntity;
import eu.eudat.data.UserEntity;
import eu.eudat.data.tenant.TenantScopedBaseEntity;
@ -24,7 +23,6 @@ import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import org.hibernate.Session;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
@ -46,6 +44,7 @@ import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class TenantInterceptor implements WebRequestInterceptor {
@ -61,6 +60,7 @@ public class TenantInterceptor implements WebRequestInterceptor {
private final PlatformTransactionManager transactionManager;
private final ErrorThesaurusProperties errors;
private final QueryUtilsService queryUtilsService;
private final LockByKeyManager lockByKeyManager;
@PersistenceContext
public EntityManager entityManager;
@ -74,7 +74,7 @@ public class TenantInterceptor implements WebRequestInterceptor {
TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService,
PlatformTransactionManager transactionManager,
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService) {
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager) {
this.tenantScope = tenantScope;
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
@ -85,10 +85,11 @@ public class TenantInterceptor implements WebRequestInterceptor {
this.transactionManager = transactionManager;
this.errors = errors;
this.queryUtilsService = queryUtilsService;
this.lockByKeyManager = lockByKeyManager;
}
@Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException, InterruptedException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
@ -101,6 +102,9 @@ public class TenantInterceptor implements WebRequestInterceptor {
}
boolean isUserAllowedTenant = false;
if (this.tenantScope.isDefaultTenant()){
isUserAllowedTenant = true;
} else {
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
if (cacheValue != null) {
isUserAllowedTenant = cacheValue.isAllowed();
@ -108,11 +112,19 @@ public class TenantInterceptor implements WebRequestInterceptor {
isUserAllowedTenant = this.isUserAllowedTenant();
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant));
}
}
if (isUserAllowedTenant) {
if(!tenantScope.isDefaultTenant()) {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString());
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
} else {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
} else {
if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) {
tenantScope.setTenant(null, null);
@ -143,8 +155,12 @@ public class TenantInterceptor implements WebRequestInterceptor {
return false;
}
private boolean isUserAllowedTenant() throws InvalidApplicationException {
private boolean isUserAllowedTenant() throws InvalidApplicationException, InterruptedException {
if (userScope.isSet()) {
boolean usedResource = false;
String lockId = userScope.getUserId().toString().toLowerCase(Locale.ROOT);
try {
if (this.tenantScopeProperties.getAutoCreateTenantUser()) usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS);
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
@ -178,6 +194,9 @@ public class TenantInterceptor implements WebRequestInterceptor {
} else {
return !results.isEmpty();
}
} finally {
if (usedResource) this.lockByKeyManager.unlock(lockId);
}
}
return false;

View File

@ -78,21 +78,26 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
Boolean scoped = this.scopeByPrincipal(this.tenantScope, principal);
if (!scoped) scoped = this.scopeByClient(this.tenantScope, principal);
boolean scoped = this.scopeByPrincipal(principal);
if (!scoped) scoped = this.scopeByClient(principal);
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant())
throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
}
}
private Boolean scopeByPrincipal(TenantScope scope, MyPrincipal principal) {
private boolean scopeByPrincipal(MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal);
if (tenantCode == null || tenantCode.isBlank())
tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
if (this.conventionService.isNullOrEmpty(tenantCode)) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return false;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null && tenantCode == null)
return Boolean.FALSE;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
@ -115,21 +120,22 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true;
}
return tenantId != null;
return false;
}
private Boolean scopeByClient(TenantScope scope, MyPrincipal principal) throws InvalidApplicationException {
private boolean scopeByClient(MyPrincipal principal) throws InvalidApplicationException {
String client = this.claimExtractor.client(principal);
Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client);
logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, scope.isSet(), (scope.isSet() ? scope.getTenant() : null));
logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, this.tenantScope.isSet(), (this.tenantScope.isSet() ? this.tenantScope.getTenant() : null));
return isWhiteListed && scope.isSet();
return isWhiteListed && this.tenantScope.isSet();
}
private UUID getTenantIdFromDatabase(String tenantCode) {

View File

@ -62,10 +62,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
String tenantCode = request.getHeader(TenantScope.TenantClaimName);
logger.debug("retrieved request tenant header is: {}", tenantCode);
if (this.conventionService.isNullOrEmpty(tenantCode)) return;
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null && tenantCode == null) return;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
@ -86,7 +92,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);

View File

@ -0,0 +1,10 @@
tenant:
configEncryptionAesKey: rmpTvZnRWzyisUtFADBcZCn0q7Z75Xdz
configEncryptionAesIv: ec05d521a23f80ad
multitenancy:
is-multitenant: true
default-tenant-code: default
interceptor:
client-claims-prefix: client_
enforce-trusted-tenant: false
auto-create-tenant-user: true

View File

@ -1,11 +1,8 @@
tenant:
configEncryptionAesKey: rmpTvZnRWzyisUtFADBcZCn0q7Z75Xdz
configEncryptionAesIv: ec05d521a23f80ad
multitenancy:
is-multitenant: true
is-multitenant: false
interceptor:
client-claims-prefix: client_
white-listed-clients: [ ]
enforce-trusted-tenant: false
auto-create-tenant-user: true
auto-create-tenant-user: false
white-listed-endpoints: [ '/api/principal/my-tenants', '/api/principal/me' ]