add supportExpansionTenant

This commit is contained in:
Efstratios Giannopoulos 2024-08-23 12:33:58 +03:00
parent cdaf9d43d5
commit cae4003af5
12 changed files with 97 additions and 45 deletions

View File

@ -6,4 +6,14 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@EnableConfigurationProperties(MultitenancyProperties.class) @EnableConfigurationProperties(MultitenancyProperties.class)
public class MultitenancyConfiguration { public class MultitenancyConfiguration {
private final MultitenancyProperties config;
public MultitenancyConfiguration(MultitenancyProperties config) {
this.config = config;
}
public MultitenancyProperties getConfig() {
return this.config;
}
} }

View File

@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tenant.multitenancy") @ConfigurationProperties(prefix = "tenant.multitenancy")
public class MultitenancyProperties { public class MultitenancyProperties {
private boolean isMultitenant; private boolean isMultitenant;
private boolean supportExpansionTenant;
private String defaultTenantCode; private String defaultTenantCode;
public boolean isMultitenant() { public boolean isMultitenant() {
@ -15,6 +16,10 @@ public class MultitenancyProperties {
this.isMultitenant = multitenant; this.isMultitenant = multitenant;
} }
public void setMultitenant(boolean multitenant) {
this.isMultitenant = multitenant;
}
public String getDefaultTenantCode() { public String getDefaultTenantCode() {
return this.defaultTenantCode; return this.defaultTenantCode;
} }
@ -22,4 +27,13 @@ public class MultitenancyProperties {
public void setDefaultTenantCode(String defaultTenantCode) { public void setDefaultTenantCode(String defaultTenantCode) {
this.defaultTenantCode = defaultTenantCode; this.defaultTenantCode = defaultTenantCode;
} }
public boolean getSupportExpansionTenant() {
return this.supportExpansionTenant;
}
public void setSupportExpansionTenant(boolean supportExpansionTenant) {
this.supportExpansionTenant = supportExpansionTenant;
}
} }

View File

@ -13,23 +13,27 @@ 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::";
private final MultitenancyProperties multitenancy; private final MultitenancyConfiguration multitenancyConfiguration;
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<>();
private final AtomicReference<UUID> initialTenant = new AtomicReference<>(); private final AtomicReference<UUID> initialTenant = new AtomicReference<>();
private final AtomicReference<String> initialTenantCode = new AtomicReference<>(); private final AtomicReference<String> initialTenantCode = new AtomicReference<>();
@Autowired @Autowired
public TenantScope(MultitenancyProperties multitenancy) { public TenantScope(MultitenancyConfiguration multitenancyConfiguration) {
this.multitenancy = multitenancy; this.multitenancyConfiguration = multitenancyConfiguration;
} }
public Boolean isMultitenant() { public Boolean isMultitenant() {
return this.multitenancy.isMultitenant(); return this.multitenancyConfiguration.getConfig().isMultitenant();
}
public Boolean supportExpansionTenant() {
return this.multitenancyConfiguration.getConfig().getSupportExpansionTenant();
} }
public String getDefaultTenantCode() { public String getDefaultTenantCode() {
return this.multitenancy.getDefaultTenantCode(); return this.multitenancyConfiguration.getConfig().getDefaultTenantCode();
} }
public Boolean isSet() { public Boolean isSet() {
@ -40,8 +44,8 @@ public class TenantScope {
public Boolean isDefaultTenant() { public Boolean isDefaultTenant() {
if (!this.isMultitenant()) if (!this.isMultitenant())
return Boolean.TRUE; return Boolean.FALSE;
return this.multitenancy.getDefaultTenantCode().equalsIgnoreCase(this.tenantCode.get()); return this.supportExpansionTenant() && this.multitenancyConfiguration.getConfig().getDefaultTenantCode().equalsIgnoreCase(this.tenantCode.get());
} }
public UUID getTenant() throws InvalidApplicationException { public UUID getTenant() throws InvalidApplicationException {

View File

@ -72,7 +72,7 @@ public class TenantEntityManager {
if (!this.tenantFiltersDisabled && this.tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) { if (!this.tenantFiltersDisabled && this.tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) {
if (tenantScopedEntity.getTenantId() != null && !tenantScopedEntity.getTenantId().equals(this.tenantScope.getTenant())) return null; if (tenantScopedEntity.getTenantId() != null && !tenantScopedEntity.getTenantId().equals(this.tenantScope.getTenant())) return null;
} }
if (disableTracking && entity != null) this.entityManager.detach(entity); if (disableTracking) this.entityManager.detach(entity);
return entity; return entity;
} }
@ -95,8 +95,10 @@ public class TenantEntityManager {
} }
public void reloadTenantFilters() throws InvalidApplicationException { public void reloadTenantFilters() throws InvalidApplicationException {
if (!this.entityManager.isOpen()) return; if (!this.entityManager.isOpen()) {
this.tenantFiltersDisabled = false;
return;
}
this.disableTenantFilters(); this.disableTenantFilters();
if (!this.tenantScope.isSet()) return; if (!this.tenantScope.isSet()) return;
@ -115,8 +117,10 @@ public class TenantEntityManager {
} }
public void loadExplicitTenantFilters() throws InvalidApplicationException { public void loadExplicitTenantFilters() throws InvalidApplicationException {
if (!this.entityManager.isOpen()) return; if (!this.entityManager.isOpen()) {
this.tenantFiltersDisabled = false;
return;
}
this.disableTenantFilters(); this.disableTenantFilters();
if (!this.tenantScope.isSet()) return; if (!this.tenantScope.isSet()) return;
@ -135,8 +139,10 @@ public class TenantEntityManager {
} }
public void disableTenantFilters() { public void disableTenantFilters() {
if (!this.entityManager.isOpen()) return; if (!this.entityManager.isOpen()) {
this.tenantFiltersDisabled = true;
return;
}
this.entityManager this.entityManager
.unwrap(Session.class) .unwrap(Session.class)
.disableFilter(TenantScopedBaseEntity.TENANT_FILTER); .disableFilter(TenantScopedBaseEntity.TENANT_FILTER);

View File

@ -74,7 +74,7 @@ public class TenantListener {
} }
} }
} else { } else {
if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(this.tenantScope.getTenant()) != 0)) { if (entity.getTenantId() != null) {
logger.error("somebody tried to change an entries tenant"); logger.error("somebody tried to change an entries tenant");
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage()); throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
} }

View File

@ -90,15 +90,20 @@ public class AnnotationEntityCreatedIntegrationEventHandlerImpl implements Annot
EventProcessingStatus status = EventProcessingStatus.Success; EventProcessingStatus status = EventProcessingStatus.Success;
try { try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) { if (this.tenantScope.isMultitenant()) {
TenantEntity tenant = this.queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); if (properties.getTenantId() != null) {
if (tenant == null) { TenantEntity tenant = this.queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(this.tenantEntityManager, properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.supportExpansionTenant()) {
this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode());
} else {
logger.error("missing tenant from event message"); logger.error("missing tenant from event message");
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
this.tenantScope.setTempTenant(this.tenantEntityManager, properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode());
} }
this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties)); this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties));

View File

@ -90,15 +90,20 @@ public class AnnotationStatusEntityChangedIntegrationEventHandlerImpl implements
EventProcessingStatus status = EventProcessingStatus.Success; EventProcessingStatus status = EventProcessingStatus.Success;
try { try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) { if (this.tenantScope.isMultitenant()) {
TenantEntity tenant = this.queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); if (properties.getTenantId() != null) {
if (tenant == null) { TenantEntity tenant = this.queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(this.tenantEntityManager, properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.supportExpansionTenant()) {
this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode());
} else {
logger.error("missing tenant from event message"); logger.error("missing tenant from event message");
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
this.tenantScope.setTempTenant(this.tenantEntityManager, properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode());
} }
this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties)); this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties));

View File

@ -181,8 +181,13 @@ public class TenantServiceImpl implements TenantService {
existingItems = this.queryFactory.query(UserRoleQuery.class).disableTracking().tenantIsSet(false).roles(this.authorizationConfiguration.getAuthorizationProperties().getGlobalAdminRoles()).collect(); existingItems = this.queryFactory.query(UserRoleQuery.class).disableTracking().tenantIsSet(false).roles(this.authorizationConfiguration.getAuthorizationProperties().getGlobalAdminRoles()).collect();
userCredentialEntities = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()).collect(); userCredentialEntities = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()).collect();
} finally {
List<String> keycloakIdsToAddToTenantGroup = new ArrayList<>(); this.entityManager.reloadTenantFilters();
}
List<String> keycloakIdsToAddToTenantGroup = new ArrayList<>();
try {
this.tenantScope.setTempTenant(this.entityManager, tenant.getId(), tenant.getCode());
for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) { for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) {
this.usageLimitService.checkIncrease(UsageLimitTargetMetric.USER_COUNT); this.usageLimitService.checkIncrease(UsageLimitTargetMetric.USER_COUNT);
TenantUserEntity tenantUserEntity = new TenantUserEntity(); TenantUserEntity tenantUserEntity = new TenantUserEntity();
@ -196,13 +201,13 @@ public class TenantServiceImpl implements TenantService {
this.eventBroker.emit(new UserAddedToTenantEvent(tenantUserEntity.getUserId(), tenantUserEntity.getTenantId())); this.eventBroker.emit(new UserAddedToTenantEvent(tenantUserEntity.getUserId(), tenantUserEntity.getTenantId()));
this.accountingService.increase(UsageLimitTargetMetric.USER_COUNT.getValue()); this.accountingService.increase(UsageLimitTargetMetric.USER_COUNT.getValue());
UserCredentialEntity userCredential = userCredentialEntities.stream().filter(x-> !this.conventionService.isNullOrEmpty(x.getExternalId()) && x.getUserId().equals(userId)).findFirst().orElse(null); UserCredentialEntity userCredential = userCredentialEntities.stream().filter(x -> !this.conventionService.isNullOrEmpty(x.getExternalId()) && x.getUserId().equals(userId)).findFirst().orElse(null);
if (userCredential == null) continue; if (userCredential == null) continue;
UserRoleEntity item = new UserRoleEntity(); UserRoleEntity item = new UserRoleEntity();
item.setId(UUID.randomUUID()); item.setId(UUID.randomUUID());
item.setUserId(userId); item.setUserId(userId);
item.setTenantId(tenant.getId()); item.setTenantId(tenant.getId());
if (existingItems.stream().filter(x -> x.getUserId().equals(userId) && x.getRole().equals(this.authorizationConfiguration.getAuthorizationProperties().getAdminRole())).findFirst().orElse(null) != null){ if (existingItems.stream().filter(x -> x.getUserId().equals(userId) && x.getRole().equals(this.authorizationConfiguration.getAuthorizationProperties().getAdminRole())).findFirst().orElse(null) != null) {
item.setRole(this.authorizationConfiguration.getAuthorizationProperties().getTenantAdminRole()); // admin item.setRole(this.authorizationConfiguration.getAuthorizationProperties().getTenantAdminRole()); // admin
} else { } else {
item.setRole(this.authorizationConfiguration.getAuthorizationProperties().getTenantUserRole()); // installation admin item.setRole(this.authorizationConfiguration.getAuthorizationProperties().getTenantUserRole()); // installation admin
@ -215,21 +220,22 @@ public class TenantServiceImpl implements TenantService {
} }
this.entityManager.flush(); this.entityManager.flush();
for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) { for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) {
this.userTouchedIntegrationEventHandler.handle(userId); this.userTouchedIntegrationEventHandler.handle(userId);
this.eventBroker.emit(new UserTouchedEvent(userId)); this.eventBroker.emit(new UserTouchedEvent(userId));
}
this.entityManager.flush();
for (String externalId : keycloakIdsToAddToTenantGroup) {
this.keycloakService.addUserToTenantRoleGroup(externalId, tenant.getCode(), this.authorizationConfiguration.getAuthorizationProperties().getTenantAdminRole());
} }
this.entityManager.flush();
} finally { } finally {
this.entityManager.reloadTenantFilters(); this.tenantScope.removeTempTenant(this.entityManager);
} }
for (String externalId : keycloakIdsToAddToTenantGroup) {
this.keycloakService.addUserToTenantRoleGroup(externalId, tenant.getCode(), this.authorizationConfiguration.getAuthorizationProperties().getTenantAdminRole());
}
} }
@Override @Override

View File

@ -117,7 +117,7 @@ public class TenantInterceptor implements WebRequestInterceptor {
} }
boolean isUserAllowedTenant = false; boolean isUserAllowedTenant = false;
if (this.tenantScope.isDefaultTenant()){ if (this.tenantScope.supportExpansionTenant() && this.tenantScope.isDefaultTenant()){
isUserAllowedTenant = true; isUserAllowedTenant = true;
} else { } else {
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant())); UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));

View File

@ -90,13 +90,13 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
if (this.conventionService.isNullOrEmpty(tenantCode)) 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 == null || this.conventionService.isNullOrEmpty(tenantCode)) return false;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){ if (this.tenantScope.supportExpansionTenant() && tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())) {
logger.debug("parsed tenant header and set tenant to default tenant"); logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode); this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true; return true;
} }
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode); UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null) { if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode)); TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));

View File

@ -56,15 +56,15 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
} }
@Override @Override
public void preHandle(@NotNull WebRequest request) { public void preHandle(@NotNull WebRequest request) {
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(ClaimNames.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;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){ if (this.tenantScope.supportExpansionTenant() && tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())) {
logger.debug("parsed tenant header and set tenant to default tenant"); logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode); this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
@ -144,3 +144,4 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
public void afterCompletion(@NonNull WebRequest request, Exception ex) { public void afterCompletion(@NonNull WebRequest request, Exception ex) {
} }
} }

View File

@ -4,6 +4,7 @@ tenant:
multitenancy: multitenancy:
is-multitenant: true is-multitenant: true
default-tenant-code: default default-tenant-code: default
support-expansion-tenant: true
interceptor: interceptor:
client-claims-prefix: client_ client-claims-prefix: client_
enforce-trusted-tenant: false enforce-trusted-tenant: false