diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/TenantsResource.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/TenantsResource.java index 5defd4a..f29ec24 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/TenantsResource.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/TenantsResource.java @@ -4,11 +4,13 @@ import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import com.fasterxml.jackson.databind.JsonNode; @@ -33,6 +35,13 @@ public interface TenantsResource { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) void createTenant(@RequestBody TenantRequest tenant); + @ResponseBody + @ResponseStatus(value = HttpStatus.NO_CONTENT) + @Operation(summary = "Deletes given tenant", description = "None") + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."), @ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")}) + @DeleteMapping(value = "/tenants/{tenantId}") + void deleteTenant(@PathVariable("tenantId") String tenantId); + @GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Gets all existing tenants", description = "None") diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/TenantsController.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/TenantsController.java index be1fde2..470f392 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/TenantsController.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/TenantsController.java @@ -47,6 +47,17 @@ public class TenantsController implements TenantsResource, PublicResource { } + public void deleteTenant(String tenantId) { + + try { + tenantManagementService.deleteTenant(tenantId); + } catch (IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e); + } + + } + + @PreAuthorize("hasAuthority('" + GET_TENANTS + "')") public List getTenants() { @@ -70,8 +81,7 @@ public class TenantsController implements TenantsResource, PublicResource { @PreAuthorize("hasAuthority('" + UPDATE_TENANT + "')") - public TenantResponse updateTenant(String tenantId, - @RequestBody TenantRequest tenantRequest) { + public TenantResponse updateTenant(String tenantId, @RequestBody TenantRequest tenantRequest) { TenantResponse tenantResponse = tenantManagementService.updateTenant(tenantId, tenantRequest); return tenantManagementService.removePasswords(tenantResponse); diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/messaging/MessagingConfiguration.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/messaging/MessagingConfiguration.java index c493820..f5b23ce 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/messaging/MessagingConfiguration.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/messaging/MessagingConfiguration.java @@ -20,7 +20,6 @@ public class MessagingConfiguration { return new TopicExchange(tenantExchangeName); } - @Bean TopicExchange userExchange(@Value("${fforesight.user-exchange.name}") String userExchangeName) { diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/UserManagementPermissions.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/UserManagementPermissions.java index 7d02967..ef41447 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/UserManagementPermissions.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/UserManagementPermissions.java @@ -19,6 +19,8 @@ public class UserManagementPermissions { public static final String CREATE_TENANT = "fforesight-create-tenant"; public static final String GET_TENANTS = "fforesight-get-tenants"; public static final String UPDATE_TENANT = "fforesight-update-tenant"; + + public static final String DELETE_TENANT="fforesight-delete-tenant"; public static final String DEPLOYMENT_INFO = "fforesight-deployment-info"; // SMTP diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java index 943cb44..fb0b56b 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java @@ -33,6 +33,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import org.springframework.web.server.ResponseStatusException; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.blob.models.BlobItem; import com.fasterxml.jackson.databind.JsonNode; import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService; import com.knecon.fforesight.tenantcommons.TenantContext; @@ -59,6 +64,12 @@ import com.knecon.fforesight.tenantusermanagement.utils.JDBCUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsResponse; +import software.amazon.awssdk.services.s3.model.S3Object; @Slf4j @Service @@ -85,7 +96,6 @@ public class TenantManagementService implements TenantProvider { @Value("${fforesight.tenant-exchange.name}") private String tenantExchangeName; - @SneakyThrows public TenantResponse createTenant(TenantRequest tenantRequest) { @@ -182,6 +192,61 @@ public class TenantManagementService implements TenantProvider { } } + @SneakyThrows + public void deleteTenant(String tenantId) { + + TenantResponse tenant = this.getTenant(tenantId); + log.info("Requested to delete tenant for: {}", tenant.getTenantId()); + log.info("Deleting tenant: {}", tenant.getTenantId()); + log.info("Deleting search index for tenant: {}", tenant.getTenantId()); + TenantContext.setTenantId(tenant.getTenantId()); + rabbitTemplate.convertAndSend(tenantExchangeName, "tenant.deleted", tenant); + TenantContext.clear(); + + log.info("Dispatched delete index message for tenant: {}", tenant.getTenantId()); + deleteSchema(tenant); + if(tenant.getAzureStorageConnection() != null ) { + log.info("Deleting azure blob for tenant: {}",tenantId); + String connectionString = tenant.getAzureStorageConnection().getConnectionString(); + String containerName = tenant.getAzureStorageConnection().getContainerName(); + + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder().connectionString(connectionString).buildClient(); + BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName); + + // Delete all blobs within the container + for (BlobItem blobItem : containerClient.listBlobs()) { + BlobClient blobClient = containerClient.getBlobClient(blobItem.getName()); + blobClient.delete(); + } + containerClient.delete(); + } + + if(tenant.getS3StorageConnection() != null) { + var s3StorageConnectionTemplate = tenant.getS3StorageConnection(); + com.iqser.red.storage.commons.model.S3StorageConnection s3StorageConnection = new com.iqser.red.storage.commons.model.S3StorageConnection(s3StorageConnectionTemplate.getKey(), encryptionService.decrypt(s3StorageConnectionTemplate.getSecret()), s3StorageConnectionTemplate.getSignerType(), s3StorageConnectionTemplate.getBucketName(), s3StorageConnectionTemplate.getRegion(),s3StorageConnectionTemplate.getEndpoint()); + log.info("Deleting s3 bucket for tenant: {}",tenantId); + S3Client client = storageConfiguration.getS3StorageService().initAmazonS3(s3StorageConnection); + String bucketName = s3StorageConnection.getBucketName(); + ListObjectsRequest listObjects = ListObjectsRequest + .builder() + .bucket(bucketName) + .build(); + ListObjectsResponse objectList = client.listObjects(listObjects); + for (S3Object object : objectList.contents()) { + // Delete each object + client.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(object.key()).build()); + } + DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder() + .bucket(bucketName) + .expectedBucketOwner(tenantId) + .build(); + client.deleteBucket(deleteBucketRequest); + } + deleteRealm(tenantId); + tenantRepository.deleteById(tenant.getTenantId()); + + } + private String buildIndexPrefix(String tenantId) { @@ -257,6 +322,24 @@ public class TenantManagementService implements TenantProvider { } + private void deleteSchema(TenantResponse tenant) { + log.info("Deleting schema for tenant: {}", tenant.getTenantId()); + var jdbcUrl = JDBCUtils.buildJdbcUrl(tenant.getDatabaseConnection()); + try (Connection connection = DriverManager.getConnection(jdbcUrl, + tenant.getDatabaseConnection().getUsername(), + this.encryptionService.decrypt(tenant.getDatabaseConnection().getPassword()))) + { + DataSource tenantDataSource = new SingleConnectionDataSource(connection, false); + JdbcTemplate jdbcTemplate = new JdbcTemplate(tenantDataSource); + String deleteStatement = "DROP SCHEMA IF EXISTS \"" + tenant.getDatabaseConnection().getSchema() + "\" CASCADE;"; + jdbcTemplate.execute(deleteStatement); + } catch (Exception e) { + log.warn("Could not delete schema:", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Tenant deletion failed: " + e.getMessage(), e); + } + } + + public void createOrUpdateRealm(String tenantId, List users) { if (syncRealmIfExists(tenantId)) { @@ -281,6 +364,20 @@ public class TenantManagementService implements TenantProvider { keycloak.getAdminClient().realms().create(realm); } + public void deleteRealm(String tenantId) { + + try { + log.info("Deleting existing realms for tenant: {}", tenantId); + keycloak.getAdminClient().realm(tenantId).remove(); + } + + catch (Exception e) { + log.warn("Could not delete realm:", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Tenant deletion failed: " + e.getMessage(), e); + } + + } + private boolean syncRealmIfExists(String tenantId) {