diff --git a/build.gradle.kts b/build.gradle.kts index e9415ce..c229fed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,10 @@ plugins { jacoco } +pmd { + isConsoleOutput = true +} + configurations { compileOnly { extendsFrom(configurations.annotationProcessor.get()) diff --git a/config/pmd/pmd.xml b/config/pmd/pmd.xml index 503991a..6c558c0 100644 --- a/config/pmd/pmd.xml +++ b/config/pmd/pmd.xml @@ -1,15 +1,19 @@ - + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> - Knecon main pmd rules + + Knecon ruleset checks the code for bad stuff + - - + + + + diff --git a/config/pmd/test_pmd.xml b/config/pmd/test_pmd.xml index 66fd97c..be4aa92 100644 --- a/config/pmd/test_pmd.xml +++ b/config/pmd/test_pmd.xml @@ -1,16 +1,22 @@ - + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + + + Knecon test ruleset checks the code for bad stuff + - Knecon test pmd rules - - + + + + + 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 f29ec24..735505f 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 @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import com.fasterxml.jackson.databind.JsonNode; import com.knecon.fforesight.tenantcommons.model.TenantResponse; +import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest; import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse; import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse; import com.knecon.fforesight.tenantusermanagement.model.TenantRequest; @@ -35,10 +36,12 @@ 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.")}) + @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); @@ -61,6 +64,12 @@ public interface TenantsResource { TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest); + @PostMapping(value = "/tenants/{tenantId}/details", consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Update details", description = "None") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request); + + @GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Gets all existing tenants in a simplified format", description = "None") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) 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 470f392..ed91a9a 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 @@ -17,6 +17,7 @@ import org.springframework.web.server.ResponseStatusException; import com.fasterxml.jackson.databind.JsonNode; import com.knecon.fforesight.tenantcommons.model.TenantResponse; +import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest; import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource; import com.knecon.fforesight.tenantusermanagement.api.external.TenantsResource; import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse; @@ -88,6 +89,13 @@ public class TenantsController implements TenantsResource, PublicResource { } + @PreAuthorize("hasAuthority('" + UPDATE_TENANT + "')") + public void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request) { + + tenantManagementService.updateDetails(tenantId, request); + } + + @PreAuthorize("hasAuthority('" + DEPLOYMENT_INFO + "')") public DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId) { 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 fb0b56b..1469eec 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/TenantManagementService.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import javax.sql.DataSource; import javax.ws.rs.NotFoundException; +import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest; import org.apache.commons.lang3.StringUtils; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -23,6 +24,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.UserRepresentation; + import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -96,6 +98,7 @@ public class TenantManagementService implements TenantProvider { @Value("${fforesight.tenant-exchange.name}") private String tenantExchangeName; + @SneakyThrows public TenantResponse createTenant(TenantRequest tenantRequest) { @@ -192,6 +195,7 @@ public class TenantManagementService implements TenantProvider { } } + @SneakyThrows public void deleteTenant(String tenantId) { @@ -205,8 +209,8 @@ public class TenantManagementService implements TenantProvider { log.info("Dispatched delete index message for tenant: {}", tenant.getTenantId()); deleteSchema(tenant); - if(tenant.getAzureStorageConnection() != null ) { - log.info("Deleting azure blob for tenant: {}",tenantId); + if (tenant.getAzureStorageConnection() != null) { + log.info("Deleting azure blob for tenant: {}", tenantId); String connectionString = tenant.getAzureStorageConnection().getConnectionString(); String containerName = tenant.getAzureStorageConnection().getContainerName(); @@ -221,26 +225,33 @@ public class TenantManagementService implements TenantProvider { containerClient.delete(); } - if(tenant.getS3StorageConnection() != null) { + 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()); + 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); + try (var 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); } - DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder() - .bucket(bucketName) - .expectedBucketOwner(tenantId) - .build(); - client.deleteBucket(deleteBucketRequest); } deleteRealm(tenantId); tenantRepository.deleteById(tenant.getTenantId()); @@ -323,16 +334,16 @@ 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()))) - { + 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); + 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); @@ -364,14 +375,13 @@ 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) { + } catch (Exception e) { log.warn("Could not delete realm:", e); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Tenant deletion failed: " + e.getMessage(), e); } @@ -483,15 +493,18 @@ public class TenantManagementService implements TenantProvider { } } - private void setPostLogoutRedirectUriForClient(ClientRepresentation client){ - if(client.getAttributes() == null){ + + private void setPostLogoutRedirectUriForClient(ClientRepresentation client) { + + if (client.getAttributes() == null) { client.setAttributes(new HashMap<>()); - }else{ + } else { client.setAttributes(new HashMap<>(client.getAttributes())); } - client.getAttributes().put("post.logout.redirect.uris","*"); + client.getAttributes().put("post.logout.redirect.uris", "*"); } + private void setPasswordPolicyForRealm(String tenantId) { var realm = realmService.realm(tenantId).toRepresentation(); @@ -542,7 +555,7 @@ public class TenantManagementService implements TenantProvider { @Override @Transactional - public void updateDetails(String tenantId, com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest request) { + public void updateDetails(String tenantId, UpdateDetailsRequest request) { var tenant = tenantRepository.findById(tenantId); if (tenant.isEmpty()) { @@ -551,6 +564,8 @@ public class TenantManagementService implements TenantProvider { Map details = tenant.get().getDetails() != null ? new HashMap<>(tenant.get().getDetails()) : new HashMap<>(); details.put(request.getKey(), request.getValue()); + details.entrySet().removeIf(ent -> ent.getValue() == null); + tenant.get().setDetails(details); } @@ -778,6 +793,7 @@ public class TenantManagementService implements TenantProvider { } + private void updateMasterDisplayName(String displayName) { log.info("Updating master realm display name: {}", displayName);