diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/multitenacy/HeaderBasedKeycloakRealmResolver.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/multitenacy/HeaderBasedKeycloakRealmResolver.java index fc60bab83..576088edd 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/multitenacy/HeaderBasedKeycloakRealmResolver.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/multitenacy/HeaderBasedKeycloakRealmResolver.java @@ -49,6 +49,7 @@ public class HeaderBasedKeycloakRealmResolver implements KeycloakConfigResolver var config = MagicConverter.convert(adapterConfig, AdapterConfig.class); config.setRealm(tenant); + config.setResource(tenant); return KeycloakDeploymentBuilder.build(config); } diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/swagger/SwaggerAutoConfiguration.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/swagger/SwaggerAutoConfiguration.java index 0466f3847..6cd3db109 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/swagger/SwaggerAutoConfiguration.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/swagger/SwaggerAutoConfiguration.java @@ -5,8 +5,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.keycloak.adapters.springboot.KeycloakSpringBootProperties; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/KeyCloakRoleManagerService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/KeyCloakRoleManagerService.java index 079efa20b..d0c108bcc 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/KeyCloakRoleManagerService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/KeyCloakRoleManagerService.java @@ -89,6 +89,7 @@ public class KeyCloakRoleManagerService { realm.roles().create(role); } + var applicationRoleResource = realm.roles().get(applicationRole); Set composites = realm.rolesById() .getClientRoleComposites(applicationRoleResource.toRepresentation().getId(), redactionClient.toRepresentation().getId()); 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 454bc1bfc..93ff7c5be 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 @@ -1,9 +1,15 @@ package com.iqser.red.service.persistence.management.v1.processor.service; +import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_ADMIN_ROLE; +import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_MANAGER_ROLE; +import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ADMIN_ROLE; +import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ROLE; + import java.net.URI; import java.net.URISyntaxException; import java.sql.Connection; import java.sql.DriverManager; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.UUID; @@ -11,15 +17,26 @@ import java.util.stream.Collectors; import javax.sql.DataSource; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +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.postgresql.util.PSQLException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.StatementCallback; import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.stereotype.Service; +import com.iqser.red.keycloak.commons.KeyCloakAdminClientService; +import com.iqser.red.keycloak.commons.KeyCloakSettings; import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException; +import com.iqser.red.service.persistence.management.v1.processor.exception.InternalServerErrorException; import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.migration.AsyncMigrationStarterService; import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.AzureStorageConnectionEntity; @@ -46,6 +63,8 @@ import lombok.extern.slf4j.Slf4j; @EnableConfigurationProperties(LiquibaseProperties.class) public class TenantManagementService { + private static final Long MAX_WAIT_TIME = 60_000L; // 60 seconds + private static final Set SUPPORTED_DATABASES = Set.of("postgresql"); private static final Set SQL_CONNECTION_ERROR_CODES = Set.of( // connection_exception @@ -65,13 +84,19 @@ public class TenantManagementService { private final AsyncMigrationStarterService asyncMigrationStarterService; private final GeneralConfigurationService generalConfigurationService; private final KeyCloakRoleManagerService keyCloakRoleManagerService; + private final KeyCloakAdminClientService keycloak; + private final KeyCloakSettings keyCloakSettings; public TenantManagementService(EncryptionDecryptionService encryptionService, @Qualifier("tenantLiquibaseProperties") LiquibaseProperties liquibaseProperties, ResourceLoader resourceLoader, TenantRepository tenantRepository, - AsyncMigrationStarterService asyncMigrationStarterService, GeneralConfigurationService generalConfigurationService, KeyCloakRoleManagerService keyCloakRoleManagerService) { + AsyncMigrationStarterService asyncMigrationStarterService, + GeneralConfigurationService generalConfigurationService, + KeyCloakRoleManagerService keyCloakRoleManagerService, + KeyCloakAdminClientService keycloak, + KeyCloakSettings keyCloakSettings) { this.encryptionService = encryptionService; this.liquibaseProperties = liquibaseProperties; @@ -80,6 +105,8 @@ public class TenantManagementService { this.asyncMigrationStarterService = asyncMigrationStarterService; this.generalConfigurationService = generalConfigurationService; this.keyCloakRoleManagerService = keyCloakRoleManagerService; + this.keycloak = keycloak; + this.keyCloakSettings = keyCloakSettings; } @@ -88,7 +115,9 @@ public class TenantManagementService { if (tenantRepository.findById(tenantRequest.getTenantId()).isEmpty()) { - var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection()); + createSchema(tenantRequest); + + var jdbcUrl = JDBCUtils.buildJdbcUrlWithSchema(tenantRequest.getDatabaseConnection()); validateJdbcUrl(jdbcUrl); try (Connection connection = DriverManager.getConnection(jdbcUrl, @@ -145,6 +174,25 @@ public class TenantManagementService { tenantRepository.save(tenantEntity); + createRealm(tenantRequest.getTenantId()); + + var waitTime = 0; + boolean realmReady; + do { + realmReady = tryToAccessRealm(tenantRequest.getTenantId()); + if (realmReady) { + break; + } else { + Thread.sleep(1_000L); + waitTime += 1_000L; + } + + } while (waitTime < MAX_WAIT_TIME); + + if (!realmReady) { + throw new InternalServerErrorException("Failed to create KC realm"); + } + generalConfigurationService.initGeneralConfiguration(tenantRequest.getTenantId()); keyCloakRoleManagerService.updateRoles(tenantRequest.getTenantId()); asyncMigrationStarterService.runForTenant(tenantRequest.getTenantId()); @@ -155,6 +203,110 @@ public class TenantManagementService { } + + private void createSchema(TenantRequest tenantRequest){ + + var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection()); + try (Connection connection = DriverManager.getConnection(jdbcUrl, + tenantRequest.getDatabaseConnection().getUsername(), + tenantRequest.getDatabaseConnection().getPassword())) { + DataSource tenantDataSource = new SingleConnectionDataSource(connection, false); + JdbcTemplate jdbcTemplate = new JdbcTemplate(tenantDataSource); + 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())); + } catch (Exception e) { + log.info("Could not create schema, ignoring"); + } + } + + + private boolean tryToAccessRealm(String tenantId) { + + try { + return keycloak.getAdminClient().realms().findAll().stream().anyMatch(r -> r.getRealm().equals(tenantId)); + } catch (Exception e) { + return false; + } + } + + + private void createRealm(String tenantId) { + + var redaction = new RealmRepresentation(); + redaction.setId(tenantId); + redaction.setRealm(tenantId); + redaction.setEnabled(true); + redaction.setLoginTheme("redaction"); + redaction.setEmailTheme("redaction"); + redaction.setAccountTheme("redaction"); + + var redactionClient = new ClientRepresentation(); + redactionClient.setEnabled(true); + redactionClient.setName("redaction"); + redactionClient.setClientId("redaction"); + redactionClient.setStandardFlowEnabled(true); + redactionClient.setImplicitFlowEnabled(true); + redactionClient.setDirectAccessGrantsEnabled(true); + redactionClient.setRedirectUris(List.of("/ui/*")); + redactionClient.setPublicClient(true); + + var swaggerClient = new ClientRepresentation(); + swaggerClient.setEnabled(true); + swaggerClient.setName("swagger-ui-client"); + swaggerClient.setClientId("swagger-ui-client"); + swaggerClient.setStandardFlowEnabled(true); + swaggerClient.setImplicitFlowEnabled(false); + swaggerClient.setDirectAccessGrantsEnabled(false); + swaggerClient.setServiceAccountsEnabled(true); + swaggerClient.setAuthorizationServicesEnabled(true); + swaggerClient.setRedirectUris(List.of("/redaction-gateway-v1/*")); + + redaction.setClients(List.of(redactionClient, swaggerClient)); + var redUserRole = new RoleRepresentation(); + redUserRole.setComposite(true); + redUserRole.setName(RED_USER_ROLE); + redUserRole.setContainerId("redaction"); + + var redManagerRole = new RoleRepresentation(); + redManagerRole.setComposite(true); + redManagerRole.setName(RED_MANAGER_ROLE); + redManagerRole.setContainerId("redaction"); + + var redAdminRole = new RoleRepresentation(); + redAdminRole.setComposite(true); + redAdminRole.setName(RED_ADMIN_ROLE); + redAdminRole.setContainerId("redaction"); + + var redUserAdminRole = new RoleRepresentation(); + redUserAdminRole.setComposite(true); + redUserAdminRole.setName(RED_USER_ADMIN_ROLE); + redUserAdminRole.setContainerId("redaction"); + + RolesRepresentation rolesRepresentation = new RolesRepresentation(); + rolesRepresentation.setRealm(List.of(redUserRole, redManagerRole, redAdminRole, redUserAdminRole)); + redaction.setRoles(rolesRepresentation); + + var credentialRepresentation = new CredentialRepresentation(); + credentialRepresentation.setType("password"); + credentialRepresentation.setValue("OsloImWinter!23"); + + var defaultUser = new UserRepresentation(); + defaultUser.setUsername("manageradmin"); + defaultUser.setCredentials(List.of(credentialRepresentation)); + defaultUser.setEmailVerified(true); + defaultUser.setRealmRoles(List.of(RED_USER_ROLE, RED_MANAGER_ROLE, RED_ADMIN_ROLE, RED_USER_ADMIN_ROLE, "uma_authorization", "offline_access")); + + var clientRoles = new HashMap>(); + clientRoles.put("account", List.of("manage-account", "view-profile")); + + defaultUser.setClientRoles(clientRoles); + defaultUser.setEnabled(true); + redaction.setUsers(List.of(defaultUser)); + + keycloak.getAdminClient().realms().create(redaction); + } + + @SneakyThrows private void validateJdbcUrl(String jdbcUrl) { @@ -173,7 +325,6 @@ public class TenantManagementService { } - private void runLiquibase(DataSource dataSource) throws LiquibaseException { SpringLiquibase liquibase = getSpringLiquibase(dataSource); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java index 9d5ed8b16..28e969139 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/jdbc/JDBCUtils.java @@ -28,6 +28,23 @@ public class JDBCUtils { public String buildJdbcUrl(DatabaseConnection databaseConnection){ + StringBuilder sb = new StringBuilder("jdbc:") + .append(databaseConnection.getDriver()) + .append("://") + .append(databaseConnection.getHost()) + .append(':') + .append(databaseConnection.getPort()) + .append('/') + .append(databaseConnection.getDatabase()); + if(databaseConnection.getParams() != null) { + sb.append('?'); + databaseConnection.getParams().forEach((k, v) -> sb.append('&').append(k).append(v)); + } + return sb.toString(); + } + + + public String buildJdbcUrlWithSchema(DatabaseConnection databaseConnection){ StringBuilder sb = new StringBuilder("jdbc:") .append(databaseConnection.getDriver()) .append("://") diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/DevConfiguration.java b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/DevConfiguration.java index 1ca279079..13fee5f79 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/DevConfiguration.java +++ b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/DevConfiguration.java @@ -1,13 +1,23 @@ package com.iqser.red.service.peristence.v1.server; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; import java.util.Set; import java.util.UUID; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import javax.annotation.PostConstruct; import javax.sql.DataSource; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -17,7 +27,11 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.StatementCallback; import org.springframework.jdbc.datasource.SingleConnectionDataSource; +import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateImportService; import com.iqser.red.service.persistence.management.v1.processor.service.TenantManagementService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository; +import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest; 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.S3StorageConnection; import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.SearchConnection; @@ -70,30 +84,81 @@ public class DevConfiguration { .password("redaction") .build()) .searchConnection(SearchConnection.builder() - .hosts(Set.of("elasticsearchHost")) + .hosts(Set.of("localhost")) .port(9200) - .scheme("https") + .scheme("http") .username("elastic") - .password("changeMe") .numberOfShards("1") .numberOfReplicas("5") .build()) .s3StorageConnection(S3StorageConnection.builder() - .key("key") - .secret("secret") - .signerType("signerType") - .bucketName("bucketName") - .region("eu") - .endpoint("endpoint") + .key("minioadmin") + .secret("minioadmin") + .bucketName("redaction") + .endpoint("http://localhost:9000") .build()) .build(); tenantManagementService.createTenant(tenantRequest); } + + TenantContext.setTenantId("redaction"); + if(dossierTemplateRepository.count() == 0) { + testDossierTemplateImport(); + } } + @Autowired + private DossierTemplateImportService dossierTemplateImportService; + + @Autowired + private DossierTemplateRepository dossierTemplateRepository; + + public byte[] pack(String sourceDirPath) throws IOException { + + var bos = new ByteArrayOutputStream(); + var p = Paths.get(sourceDirPath); + try (ZipOutputStream zs = new ZipOutputStream(bos)) { + Stream paths = Files.walk(p); + { + paths.filter(path -> !Files.isDirectory(path)).forEach(path -> { + ZipEntry zipEntry = new ZipEntry(p.relativize(path).toString()); + try { + zs.putNextEntry(zipEntry); + Files.copy(path, zs); + zs.closeEntry(); + } catch (IOException e) { + System.err.println(e); + } + }); + } + } + + return bos.toByteArray(); + + } + + @SneakyThrows + public void testDossierTemplateImport() { + + var importDir = new File("/Users/timobejan/work/dossier-templates-v2/dev"); + + TenantContext.setTenantId("redaction"); + for (var file : importDir.listFiles()) { + if(file.isDirectory()){ + var archive = pack(file.getAbsolutePath()); + log.info("Importing file: " + file.getName() + " " + " with size: " + archive.length); + var request = new ImportDossierTemplateRequest(); + request.setArchive(archive); + request.setUpdateExistingDossierTemplate(false); + request.setUserId("system"); + dossierTemplateImportService.importDossierTemplate(request); + } + } + } + @SneakyThrows public void createSchema(String jdbcUrl, String username, String password) { diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application-dev.yml b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application-dev.yml index 9ecfa872f..6132a6dda 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application-dev.yml +++ b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application-dev.yml @@ -60,7 +60,7 @@ keycloak: commons: keycloak: applicationClientId: redaction - clientId: redaction-system + clientId: redaction clientSecret: G5E1qLU8ZNdDv7HY5BNLPdt5nXdeF7cU realm: redaction serverUrl: http://localhost:8080 diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml index c0a1e0fbb..b92394b2a 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml +++ b/persistence-service-v1/persistence-service-server-v1/src/main/resources/application.yml @@ -131,7 +131,7 @@ bucket4j: keycloak: sslRequired: none auth-server-url: https://red-staging.iqser.cloud/auth - realm: redaction + realm: master resource: redaction disableTrustManager: true useResourceRoleMappings: true @@ -154,7 +154,7 @@ springdoc: operations-sorter: alpha tags-sorter: alpha oauth: - client-id: swagger-ui-client + client-id: redaction doc-expansion: none enabled: ${swagger.enabled} config-url: /redaction-gateway-v1/docs/swagger-config diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTemplateImportTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTemplateImportTest.java index bcbf49ff6..89f7723fd 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTemplateImportTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTemplateImportTest.java @@ -10,7 +10,6 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.junit.Ignore; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -35,7 +34,7 @@ public class DossierTemplateImportTest extends AbstractPersistenceServerServiceT @Disabled public void testDossierTemplateImport() { - var importDir = new File("/path/to/git/repo/dossier-templates-v2/dev"); + var importDir = new File("/Users/timobejan/work/dossier-templates-v2/dev"); TenantContext.setTenantId("redaction"); for (var file : importDir.listFiles()) { diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java index 525d98b73..41afa7040 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java @@ -2,8 +2,6 @@ package com.iqser.red.service.peristence.v1.server.integration.utils; import static org.mockito.Mockito.when; -import java.sql.Connection; -import java.sql.DriverManager; import java.util.ArrayList; import java.util.Set; import java.util.UUID; @@ -16,6 +14,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.keycloak.adapters.springboot.KeycloakSpringBootProperties; +import org.keycloak.representations.idm.ClientRepresentation; import org.mockito.Mockito; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -36,7 +35,6 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.StatementCallback; -import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -102,7 +100,6 @@ import com.iqser.red.storage.commons.StorageAutoConfiguration; import com.iqser.red.storage.commons.service.StorageService; import io.micrometer.prometheus.PrometheusMeterRegistry; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -120,8 +117,6 @@ public abstract class AbstractPersistenceServerServiceTest { @MockBean protected RedactionLogMergeService redactionLogMergeService; @MockBean - protected KeyCloakAdminClientService keyCloakAdminClientService; - @MockBean protected PDFTronClient pdfTronRedactionClient; @Autowired protected ApplicationConfigClient appConfigClient; @@ -212,6 +207,8 @@ public abstract class AbstractPersistenceServerServiceTest { private UserService userService; @Autowired private TokenService tokenService; + @Autowired + private KeyCloakSettings keyCloakSettings; @BeforeEach @@ -232,12 +229,22 @@ public abstract class AbstractPersistenceServerServiceTest { TenantContext.setTenantId("redaction"); - when(keyCloakAdminClientService.getAdminClient()).thenReturn(KeyCloakTestContainer.getInstance().getKeycloakAdminClient()); +// when(keyCloakAdminClientService.getAdminClient()).thenReturn(KeyCloakTestContainer.getInstance().getKeycloakAdminClient()); userService.evictUserCache(); var allUsers = userService.getAllUsers(); - if (allUsers.isEmpty()) { + if (allUsers.size() == 1) { + + var redactionSystemClient = new ClientRepresentation(); + redactionSystemClient.setEnabled(true); + redactionSystemClient.setName(keyCloakSettings.getClientId()); + redactionSystemClient.setClientId(keyCloakSettings.getClientId()); + redactionSystemClient.setSecret(keyCloakSettings.getClientSecret()); + redactionSystemClient.setDirectAccessGrantsEnabled(true); + redactionSystemClient.setServiceAccountsEnabled(true); + + KeyCloakTestContainer.getInstance().getKeycloakAdminClient().realm("redaction").clients().create(redactionSystemClient); var admin1 = createUser("manageradmin1@test.com"); var admin2 = createUser("manageradmin2@test.com"); @@ -301,7 +308,6 @@ public abstract class AbstractPersistenceServerServiceTest { var port = postgreSQLContainerMaster.getJdbcUrl().substring(0, postgreSQLContainerMaster.getJdbcUrl().lastIndexOf('/')).split(":")[3]; createDatabase("redaction", "redaction"); - createSchema(jdbcUrl, "redaction", "redaction"); var tenantRequest = TenantRequest.builder() .tenantId("redaction") @@ -349,18 +355,6 @@ public abstract class AbstractPersistenceServerServiceTest { } - @SneakyThrows - public void createSchema(String jdbcUrl, String username, String password) { - - try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) { - DataSource tenantDataSource = new SingleConnectionDataSource(connection, false); - JdbcTemplate insert = new JdbcTemplate(tenantDataSource); - insert.execute((StatementCallback) stmt -> stmt.execute("CREATE SCHEMA myschema")); - insert.execute((StatementCallback) stmt -> stmt.execute("GRANT USAGE ON SCHEMA myschema TO " + username)); - } - } - - @AfterEach public void cleanupStorage() { @@ -432,14 +426,15 @@ public abstract class AbstractPersistenceServerServiceTest { var connectionStringDetails = "?serverTimezone=UTC&cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true"; + var kcInstance = KeyCloakTestContainer.getInstance(); + TestPropertyValues.of("spring.redis.port=" + redisContainer.getFirstMappedPort(), "multitenancy.master.datasource.url=" + postgreSQLContainerMaster.getJdbcUrl() + connectionStringDetails, "multitenancy.master.datasource.username=" + postgreSQLContainerMaster.getUsername(), - "multitenancy.master.datasource.password=" + postgreSQLContainerMaster.getPassword()).applyTo(configurableApplicationContext.getEnvironment()); + "multitenancy.master.datasource.password=" + postgreSQLContainerMaster.getPassword(), + "keycloak.auth-server-url=" + kcInstance.getAuthServerUrl(), + "commons.keycloak.serverUrl=" + kcInstance.getAuthServerUrl()).applyTo(configurableApplicationContext.getEnvironment()); - var kcInstance = KeyCloakTestContainer.getInstance(); - - TestPropertyValues.of("keycloak.auth-server-url=" + kcInstance.getAuthServerUrl()); } } @@ -464,7 +459,7 @@ public abstract class AbstractPersistenceServerServiceTest { var instance = KeyCloakTestContainer.getInstance(); - when(cloakAdminClientService.getAdminClient()).thenReturn(instance.getKeycloakAdminClient()); +// when(cloakAdminClientService.getAdminClient()).thenReturn(instance.getKeycloakAdminClient()); keycloakSpringBootProperties.setAuthServerUrl(instance.getAuthServerUrl()); keyCloakSettings.setServerUrl(instance.getAuthServerUrl()); diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/KeyCloakTestContainer.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/KeyCloakTestContainer.java index 5b72aa1a1..844214775 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/KeyCloakTestContainer.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/KeyCloakTestContainer.java @@ -1,9 +1,12 @@ package com.iqser.red.service.peristence.v1.server.integration.utils; +import java.util.ArrayList; +import java.util.List; + +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.testcontainers.containers.PostgreSQLContainer; +import org.keycloak.representations.idm.RoleRepresentation; import dasniko.testcontainers.keycloak.KeycloakContainer; @@ -26,35 +29,34 @@ public class KeyCloakTestContainer { keycloak.start(); var adminClient = keycloak.getKeycloakAdminClient(); - var redaction = new RealmRepresentation(); - redaction.setId("redaction"); - redaction.setRealm("redaction"); - redaction.setEnabled(true); - adminClient.realms().create(redaction); + var redaction = adminClient.realm("master"); - var redactionRealm = adminClient.realm("redaction"); var redactionClient = new ClientRepresentation(); + redactionClient.setId("redaction"); redactionClient.setEnabled(true); redactionClient.setName("redaction"); + redactionClient.setSecret("redaction"); + redactionClient.setServiceAccountsEnabled(true); + redactionClient.setDirectAccessGrantsEnabled(true); redactionClient.setStandardFlowEnabled(true); redactionClient.setImplicitFlowEnabled(true); redactionClient.setDirectAccessGrantsEnabled(true); - redactionRealm.clients().create(redactionClient); + redaction.clients().create(redactionClient); + redaction.clients().create(redactionClient); + RealmResource myRealm = adminClient.realm("master"); + String userId = myRealm.clients().get("redaction").getServiceAccountUser().getId(); + UserResource serviceAccountUser = myRealm.users().get(userId); + ClientRepresentation clientThatOwnsRole = myRealm.clients().findByClientId("master-realm").get(0); + String clientIdOfRoleOwner = clientThatOwnsRole.getId(); - var redactionSystemClient = new ClientRepresentation(); - redactionSystemClient.setId("redaction-system"); - redactionSystemClient.setEnabled(true); - redactionSystemClient.setName("redaction-system"); - redactionSystemClient.setSecret("redaction-system"); - redactionSystemClient.setDirectAccessGrantsEnabled(true); - redactionSystemClient.setStandardFlowEnabled(true); - redactionSystemClient.setImplicitFlowEnabled(true); - redactionSystemClient.setDirectAccessGrantsEnabled(true); - redactionRealm.clients().create(redactionSystemClient); + List roles = new ArrayList<>(); + roles.addAll(myRealm.clients().get(clientIdOfRoleOwner).roles().list()); + serviceAccountUser.roles().clientLevel(clientIdOfRoleOwner).add(roles); + serviceAccountUser.roles().realmLevel().add(List.of(myRealm.roles().get("create-realm").toRepresentation())); } diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/TokenService.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/TokenService.java index cb873ee2d..5596c1912 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/TokenService.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/TokenService.java @@ -2,6 +2,7 @@ package com.iqser.red.service.peristence.v1.server.integration.utils; import java.util.concurrent.TimeUnit; +import javax.ws.rs.BadRequestException; import javax.ws.rs.NotAuthorizedException; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; @@ -48,6 +49,10 @@ public class TokenService { try { return tokenClient.tokenManager().getAccessTokenString(); + } catch (BadRequestException e) { + var response = e.getResponse().getEntity(); + System.out.println(response); + return null; } catch (NotAuthorizedException e) { throw new AuthenticationFailedException(e); } finally { diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml b/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml index 6ff024556..564764660 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml +++ b/persistence-service-v1/persistence-service-server-v1/src/test/resources/application.yml @@ -118,15 +118,15 @@ multitenancy: keycloak: enabled: true sslRequired: none - realm: redaction - resource: redaction + realm: master + resource: master disableTrustManager: true useResourceRoleMappings: true commons: keycloak: application-client-id: redaction - realm: redaction - client-id: redaction-system - client-secret: redaction-system + realm: master + client-id: redaction + client-secret: redaction