diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java index 04d1106ea..bbdeae8e2 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/TenantManagementService.java @@ -12,6 +12,7 @@ import java.sql.DriverManager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -86,6 +87,7 @@ public class TenantManagementService { private final GeneralConfigurationService generalConfigurationService; private final KeyCloakRoleManagerService keyCloakRoleManagerService; private final KeyCloakAdminClientService keycloak; + private final Map tenantsHostAndSchemaMap = new HashMap<>(); public TenantManagementService(EncryptionDecryptionService encryptionService, @@ -201,8 +203,16 @@ public class TenantManagementService { } + private void checkDuplicateHostAndSchema(String hostAndSchemaName) { + + if (tenantsHostAndSchemaMap.containsValue(hostAndSchemaName)) { + throw ConflictException.withObjectName("host and schema"); + } + } + private void createSchema(TenantRequest tenantRequest) { + checkDuplicateHostAndSchema(buildHostAndSchemaName(tenantRequest.getDatabaseConnection())); var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection()); try (Connection connection = DriverManager.getConnection(jdbcUrl, tenantRequest.getDatabaseConnection().getUsername(), @@ -212,11 +222,20 @@ public class TenantManagementService { jdbcTemplate.execute((StatementCallback) stmt -> stmt.execute("CREATE SCHEMA " + tenantRequest.getDatabaseConnection().getSchema())); jdbcTemplate.execute((StatementCallback) stmt -> stmt.execute("GRANT USAGE ON SCHEMA " + tenantRequest.getDatabaseConnection() .getSchema() + " TO " + tenantRequest.getDatabaseConnection().getUsername())); + + tenantsHostAndSchemaMap.put(tenantRequest.getTenantId(), buildHostAndSchemaName(tenantRequest.getDatabaseConnection())); } catch (Exception e) { log.info("Could not create schema, ignoring"); } } + private String buildHostAndSchemaName(DatabaseConnection databaseConnection) { + StringBuilder sb = new StringBuilder(databaseConnection.getHost()) + .append("currentSchema=") + .append(databaseConnection.getSchema()); + return sb.toString(); + } + private boolean tryToAccessRealm(String tenantId) { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml index 8a632ee7b..d1fa43356 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-master.yaml @@ -5,3 +5,5 @@ databaseChangeLog: file: db/changelog/master/2-quartz.changelog.yaml - include: file: db/changelog/master/3-detailed-db-connection.changelog.yaml + - include: + file: db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml new file mode 100644 index 000000000..27baab89c --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/master/4-add-unique-constraint-for-tenants-table.yaml @@ -0,0 +1,17 @@ +databaseChangeLog: + - changeSet: + id: add-unique-constraint-for-tenants-table + author: corinaolariu + changes: + - addUniqueConstraint: + columnNames: db_host, db_schema + constraintName: unique_constraint_tenant_host_shema + tableName: tenant + - addUniqueConstraint: + columnNames: storage_s3_endpoint, storage_s3_region, storage_s3_bucket_name + constraintName: unique_constraint_tenant_s3_storage + tableName: tenant + - addUniqueConstraint: + columnNames: storage_azure_connection_string, storage_azure_container_name + constraintName: unique_constraint_tenant_azure_storage + tableName: tenant diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java new file mode 100644 index 000000000..7c5564a79 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/TenantsTest.java @@ -0,0 +1,71 @@ +package com.iqser.red.service.peristence.v1.server.integration.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.iqser.red.keycloak.commons.roles.ApplicationRoles; +import com.iqser.red.service.peristence.v1.server.integration.client.TenantsClient; +import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.DatabaseConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.RedUser; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.S3StorageConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.SearchConnection; +import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.TenantRequest; + +import feign.FeignException; + +public class TenantsTest extends AbstractPersistenceServerServiceTest { + + @Autowired + private TenantsClient tenantsClient; + + @Test + public void testCreateTenantWithSameHostAndSchema() { + + var tenantRequest = TenantRequest.builder() + .tenantId("redaction2") + .displayName("Redaction default2") + .guid(UUID.randomUUID().toString()) + .databaseConnection(DatabaseConnection.builder() + .driver("postgresql") + .host("localhost") + .port("port") + .database("redaction") + .schema("myschema") + .username("redaction") + .password("redaction") + .build()) + .searchConnection(SearchConnection.builder() + .hosts(Set.of("elasticsearchHost2")) + .port(9200) + .scheme("https2") + .username("elastic") + .numberOfShards("1") + .numberOfReplicas("5") + .build()) + .s3StorageConnection(S3StorageConnection.builder() + .key("key") + .secret("secret") + .signerType("signerType") + .bucketName("bucketName2") + .region("eu") + .endpoint("endpoint2") + .build()) + .redUsers(List.of(RedUser.builder().username("user").password("password").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin1@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build(), + RedUser.builder().username("manageradmin2@test.com").password("secret").redRoles(ApplicationRoles.ROLE_DATA.keySet()).build())) + .build(); + + try { + tenantsClient.createTenant(tenantRequest); + } catch(FeignException e) { + assertThat(e.status()).isEqualTo(409); + } + } +}