Merge branch 'RED-6668' into 'main'

RED-6888: Add delete tenant endpoint

See merge request fforesight/tenant-user-management-service!66
This commit is contained in:
Yannik Hampe 2024-01-26 11:55:01 +01:00
commit 95778f7926
5 changed files with 121 additions and 4 deletions

View File

@ -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")

View File

@ -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<TenantResponse> 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);

View File

@ -20,7 +20,6 @@ public class MessagingConfiguration {
return new TopicExchange(tenantExchangeName);
}
@Bean
TopicExchange userExchange(@Value("${fforesight.user-exchange.name}") String userExchangeName) {

View File

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

View File

@ -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<TenantUser> 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) {