Compare commits

..

6 Commits

Author SHA1 Message Date
Christoph Schabert
e1d673d31a Merge branch 'RED-9225-permissions' into 'release/1.111.x'
RED-9225: Fixed wrong user permissions for customer api

See merge request fforesight/tenant-user-management-service!107
2024-06-07 12:43:35 +02:00
Dominique Eifländer
ff58ec32e1 RED-9225: Fixed wrong user permissions for customer api 2024-06-07 12:24:08 +02:00
Dominique Eifländer
dd4ccb71f1 Merge branch 'RED-9225-4.0' into 'release/1.111.x'
RED-9225: Added first customer api endpoints

See merge request fforesight/tenant-user-management-service!102
2024-05-29 13:11:34 +02:00
Dominique Eifländer
46f9ca0734 RED-9225: Added first customer api endpoints 2024-05-29 12:59:39 +02:00
maverickstuder
7e60a66a68 RED-8477: SSO settings endpoint for SAML
* added more tests
* refactoring
* added check for existing idp with same displayName on update

(cherry picked from commit 12e8c0f53f57fea724402dc8be039f785a821f2d)
2024-02-21 13:15:52 +01:00
Maverick Studer
da59130a7e RED-8477: SSO settings endpoint for SAML
(cherry picked from commit f98394c1366a7ab5266fa07749e51f5c20798185)
2024-02-21 13:15:52 +01:00
60 changed files with 1104 additions and 3406 deletions

View File

@ -94,21 +94,17 @@ configurations {
}
}
val persistenceServiceVersion = "2.589.1-RED10196.2"
dependencies {
implementation("com.iqser.red.service:persistence-service-internal-api-v1:${persistenceServiceVersion}")
implementation("com.knecon.fforesight:database-tenant-commons:0.28.0-RED10196.0")
implementation("com.knecon.fforesight:keycloak-commons:0.28.0")
implementation("com.knecon.fforesight:swagger-commons:0.7.0")
implementation("com.knecon.fforesight:database-tenant-commons:0.21.0")
implementation("com.knecon.fforesight:keycloak-commons:0.25.0")
implementation("com.knecon.fforesight:swagger-commons:0.5.0")
implementation("com.knecon.fforesight:tracing-commons:0.5.0")
implementation("com.knecon.fforesight:lifecycle-commons:0.6.0")
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
implementation("ch.qos.logback:logback-classic")
implementation("org.postgresql:postgresql:42.5.4")
implementation("com.google.guava:guava:33.0.0-jre")
implementation("org.liquibase:liquibase-core:4.20.0")
implementation("org.liquibase:liquibase-core:4.17.2")
implementation("org.keycloak:keycloak-admin-client:23.0.6")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation("org.springframework.boot:spring-boot-starter-validation")
@ -118,7 +114,6 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("commons-validator:commons-validator:1.8.0")
implementation("org.springframework.boot:spring-boot-configuration-processor")
@ -131,14 +126,14 @@ dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.amqp:spring-rabbit-test")
testImplementation("org.testcontainers:postgresql:1.19.7")
testImplementation("org.testcontainers:testcontainers:1.19.7")
testImplementation("org.testcontainers:junit-jupiter:1.19.7")
testImplementation("org.testcontainers:postgresql:1.19.4")
testImplementation("org.testcontainers:testcontainers:1.19.4")
testImplementation("org.testcontainers:junit-jupiter:1.19.4")
testImplementation("com.github.dasniko:testcontainers-keycloak:3.2.0")
}
extra["springCloudVersion"] = "2022.0.4"
extra["testcontainersVersion"] = "1.19.7"
extra["springCloudVersion"] = "2022.0.2"
extra["testcontainersVersion"] = "1.17.6"
group = "com.knecon.fforesight"

View File

@ -4,26 +4,18 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.knecon.fforesight.keycloakcommons.DefaultKeyCloakCommonsAutoConfiguration;
import com.knecon.fforesight.lifecyclecommons.LifecycleAutoconfiguration;
import com.knecon.fforesight.swaggercommons.SpringDocAutoConfiguration;
import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
import com.knecon.fforesight.tenantusermanagement.client.LicenseClient;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ImportAutoConfiguration({MultiTenancyAutoConfiguration.class, LiquibaseAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, SpringDocAutoConfiguration.class, LifecycleAutoconfiguration.class})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, CassandraAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@EnableAspectJAutoProxy
@EnableFeignClients(basePackageClasses = LicenseClient.class)
@ImportAutoConfiguration({MultiTenancyAutoConfiguration.class, LiquibaseAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, SpringDocAutoConfiguration.class})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, CassandraAutoConfiguration.class,})
public class TenantUserManagementServiceApplication {
/**

View File

@ -17,8 +17,8 @@ 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.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -36,13 +36,14 @@ public interface TenantsResource {
@PostMapping(value = TENANTS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Create a new tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void createTenant(@RequestBody CreateTenantRequest tenant);
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 = "403", description = "Forbidden access, you dont have rights to delete tenants"), @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 = "403", description = "Forbidden access, you dont have rights to delete tenants"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."),
@ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")})
@DeleteMapping(value = TENANTS_TENANT_ID_PATH)
void deleteTenant(@PathVariable("tenantId") String tenantId);
@ -62,7 +63,7 @@ public interface TenantsResource {
@PutMapping(value = TENANTS_TENANT_ID_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Update existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest);
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/details", consumes = MediaType.APPLICATION_JSON_VALUE)
@ -71,6 +72,12 @@ public interface TenantsResource {
void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request);
@GetMapping(value = TENANTS_PATH + "/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")})
List<SimpleTenantResponse> getSimpleTenants();
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Returns the deployment key for a tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})

View File

@ -10,6 +10,7 @@ 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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ -60,7 +61,7 @@ public interface UserResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Update a user profile", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail cannot be empty")})
@PostMapping(value = UPDATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
User updateProfile(@PathVariable(USER_ID) String userId, @RequestBody UpdateProfileRequest updateProfileRequest);
@ -68,7 +69,7 @@ public interface UserResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Update your user profile", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail cannot be empty")})
@PostMapping(value = UPDATE_MY_USER_PROFILE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
User updateMyProfile(@RequestBody UpdateMyProfileRequest updateProfileRequest);
@ -91,14 +92,14 @@ public interface UserResource {
@ResponseBody
@Operation(summary = "Create a new user", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid Data.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid Data."), @ApiResponse(responseCode = "409", description = "User already exists.")})
@PostMapping(value = USER_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
User createUser(@RequestBody CreateUserRequest user);
@ResponseBody
@Operation(summary = "Gets the user in realm including role info", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or null.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The " + "userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or " + "null.")})
@GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
User getUserById(@PathVariable(USER_ID) String userId);
@ -119,7 +120,8 @@ public interface UserResource {
@ResponseBody
@Operation(summary = "Activate/deactivate a user profile", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile"), @ApiResponse(responseCode = "403", description = "Cannot activate/deactivate users with higher rank roles"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile"),
@ApiResponse(responseCode = "403", description = "Cannot activate/deactivate users with higher rank roles")})
@PostMapping(value = ACTIVATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
User activateProfile(@PathVariable(USER_ID) String userId, @RequestParam(IS_ACTIVE_PARAM) boolean isActive);

View File

@ -12,12 +12,11 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.fasterxml.jackson.databind.JsonNode;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
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.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -39,17 +38,17 @@ public interface InternalTenantsResource {
@PostMapping(value = "/tenants", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Creates a new Tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantResponse createTenant(@RequestBody CreateTenantRequest tenant);
TenantResponse createTenant(@RequestBody TenantRequest tenant);
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets all existing tenants", description = "None")
@Operation(summary = "Gets all existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<TenantResponse> getTenants();
@GetMapping(value = "/tenants/{tenantId}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the given tenant", description = "None")
@Operation(summary = "Gets all existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantResponse getTenant(@PathVariable("tenantId") String tenantId);
@ -57,7 +56,13 @@ public interface InternalTenantsResource {
@PutMapping(value = "/tenants/{tenantId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Update existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest);
@GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets all existing tenant in a simplified format", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<SimpleTenantResponse> getSimpleTenants();
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
@ -71,10 +76,4 @@ public interface InternalTenantsResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void syncTenant(@PathVariable("tenantId") String tenantId, @RequestBody JsonNode payload);
@GetMapping(value = {"/tenants/{tenantId}/application-type"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the application type of the given tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantApplicationType getTenantApplicationType(@PathVariable("tenantId") String tenantId);
}

View File

@ -1,10 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.client;
import org.springframework.cloud.openfeign.FeignClient;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.LicenseResource;
@FeignClient(name = "LicenseResource", url = "${persistence-service.url}")
public interface LicenseClient extends LicenseResource {
}

View File

@ -7,12 +7,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.api.external.GeneralSettingsResource;
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
import com.knecon.fforesight.tenantusermanagement.model.GeneralConfigurationModel;
import com.knecon.fforesight.tenantusermanagement.service.GeneralConfigurationService;
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -23,14 +21,13 @@ import lombok.extern.slf4j.Slf4j;
public class GeneralSettingsController implements GeneralSettingsResource, PublicResource {
private final GeneralConfigurationService generalConfigurationService;
private final TenantManagementService tenantManagementService;
@Override
@PreAuthorize("hasAuthority('" + READ_GENERAL_CONFIGURATION + "')")
public GeneralConfigurationModel getGeneralConfigurations() {
return generalConfigurationService.getGeneralConfigurations(tenantManagementService.getTenantApplicationType(TenantContext.getTenantId()));
return generalConfigurationService.getGeneralConfigurations();
}
@ -38,7 +35,7 @@ public class GeneralSettingsController implements GeneralSettingsResource, Publi
@PreAuthorize("hasAuthority('" + WRITE_GENERAL_CONFIGURATION + "')")
public void updateGeneralConfigurations(@RequestBody GeneralConfigurationModel generalConfigurationModel) {
generalConfigurationService.updateGeneralConfigurations(generalConfigurationModel, tenantManagementService.getTenantApplicationType(TenantContext.getTenantId()));
generalConfigurationService.updateGeneralConfigurations(generalConfigurationModel);
}
}

View File

@ -21,10 +21,9 @@ import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
import com.knecon.fforesight.tenantusermanagement.service.EmailService;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import com.knecon.fforesight.tenantusermanagement.service.SMTPService;
import jakarta.ws.rs.BadRequestException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ -32,16 +31,21 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class SMTPConfigurationController implements SMTPConfigurationResource, PublicResource {
private final static String SMTP_PASSWORD_KEY = "FFORESIGHT_SMTP_PASSWORD";
private final static String DEFAULT_PASSWORD = "**********";
private final RealmService realmService;
private final ObjectMapper objectMapper;
private final EncryptionDecryptionService encryptionDecryptionService;
private final EmailService emailService;
private final SMTPService smtpService;
@Override
@PreAuthorize("hasAuthority('" + READ_SMTP_CONFIGURATION + "')")
public SMTPConfiguration getCurrentSMTPConfiguration() {
return smtpService.getSMTPConfiguration();
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
return objectMapper.convertValue(realm.getSmtpServer(), SMTPConfiguration.class);
}
@ -49,14 +53,17 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public void updateSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
if (!smtpService.canUpdateSMTPConfig()) {
throw new BadRequestException("Current license does not allow updating the SMTP configuration!");
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
realmRepresentation.setSmtpServer(propertiesMap);
if (!smtpConfigurationModel.getPassword().matches("\\**")) {
realmRepresentation.getAttributesOrEmpty().put(SMTP_PASSWORD_KEY, encryptionDecryptionService.encrypt(smtpConfigurationModel.getPassword()));
}
smtpService.updateSMTPConfiguration(smtpConfigurationModel);
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public SMTPResponse testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfiguration) {
@ -70,22 +77,53 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
adminEmail = false;
}
SMTPResponse.SMTPResponseBuilder smtpResponseBuilder = emailService.send(smtpService.convertSMTPConfigToMap(smtpConfiguration),
targetEmail,
"Redaction Test message",
"This is a test message");
updatePassword(smtpConfiguration);
smtpConfiguration.setPassword(encryptionDecryptionService.decrypt(smtpConfiguration.getPassword()));
SMTPResponse.SMTPResponseBuilder smtpResponseBuilder = emailService.send(convertSMTPConfigurationModelToMap(smtpConfiguration), targetEmail, "Redaction Test message", "This is a test message");
SMTPResponse smtpResponse = smtpResponseBuilder.adminEmail(adminEmail).recipientEmail(targetEmail).build();
log.info("Test SMTP Configuration status: {}, reason: {}, recipient: {}", smtpResponse.getStatusCode(), smtpResponse.getReasonPhrase(), smtpResponse.getRecipientEmail());
return smtpResponse;
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public void clearSMTPConfiguration() {
smtpService.createDefaultSMTPConfiguration();
// also update in KC
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
realmRepresentation.setSmtpServer(new HashMap<>());
realmRepresentation.getAttributesOrEmpty().remove(SMTP_PASSWORD_KEY);
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
}
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, new TypeReference<>() {});
Map<String, String> stringPropertiesMap = new HashMap<>();
propertiesMap.forEach((key, value) -> {
if (value != null) {
stringPropertiesMap.put(key, value.toString());
} else {
stringPropertiesMap.put(key, "");
}
});
return stringPropertiesMap;
}
private void updatePassword(SMTPConfiguration smtpConfiguration) {
if (DEFAULT_PASSWORD.equals(smtpConfiguration.getPassword())) {
try {
var password = realmService.realm(TenantContext.getTenantId()).toRepresentation().getAttributesOrEmpty().get(SMTP_PASSWORD_KEY);
smtpConfiguration.setPassword(password);
} catch (Exception e) {
log.info("No current SMTP Config exists", e);
}
} else {
smtpConfiguration.setPassword(encryptionDecryptionService.encrypt(smtpConfiguration.getPassword()));
}
}
}

View File

@ -22,8 +22,8 @@ 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;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.service.DeploymentKeyService;
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
@ -39,7 +39,7 @@ public class TenantsController implements TenantsResource, PublicResource {
@PreAuthorize("hasAuthority('" + CREATE_TENANT + "')")
public void createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
public void createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
try {
tenantManagementService.createTenant(tenantRequest);
@ -76,8 +76,14 @@ public class TenantsController implements TenantsResource, PublicResource {
}
public List<SimpleTenantResponse> getSimpleTenants() {
return tenantManagementService.getTenants().stream().map(t -> new SimpleTenantResponse(t.getTenantId(), t.getDisplayName(), t.getGuid())).toList();
}
@PreAuthorize("hasAuthority('" + UPDATE_TENANT + "')")
public TenantResponse updateTenant(String tenantId, @RequestBody UpdateTenantRequest tenantRequest) {
public TenantResponse updateTenant(String tenantId, @RequestBody TenantRequest tenantRequest) {
TenantResponse tenantResponse = tenantManagementService.updateTenant(tenantId, tenantRequest);
return tenantManagementService.removePasswords(tenantResponse);

View File

@ -1,6 +1,6 @@
package com.knecon.fforesight.tenantusermanagement.controller.external;
import static com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles.KNECON_ROLE_FILTER;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.DELETE_TENANT;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_ALL_USERS;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_USERS;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_MY_PROFILE;
@ -26,8 +26,7 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.User;
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
import com.knecon.fforesight.tenantusermanagement.service.TenantApplicationTypeService;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.service.UserService;
import jakarta.validation.Valid;
@ -39,9 +38,8 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class UserController implements UserResource, PublicResource {
private final UserService userService;
private final TenantApplicationTypeService tenantApplicationTypeService;
private final TenantUserManagementProperties tenantUserManagementProperties;
@Override
@ -51,15 +49,9 @@ public class UserController implements UserResource, PublicResource {
if (bypassCache) {
userService.evictUserCache();
}
var mappedRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
return userService.getAllUsers()
.stream()
.filter(user -> user.getRoles()
.stream()
.anyMatch(mappedRoles::contains))
.filter(KNECON_ROLE_FILTER)
.toList();
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
return userService.getAllUsers().stream().filter(user -> user.getRoles().stream().anyMatch(allRoles::contains)).collect(Collectors.toList());
}
@ -71,10 +63,21 @@ public class UserController implements UserResource, PublicResource {
userService.evictUserCache();
}
return userService.getAllUsers()
.stream()
.filter(KNECON_ROLE_FILTER)
.toList();
var kneconAdminRole = "KNECON_ADMIN";
return userService.getAllUsers().stream().filter(user -> {
if(user.getRoles().contains(kneconAdminRole))
{
//user should be filtered out because he has only role knecon_admin
if(user.getRoles().size() == 1) {
return false;
}
//remove knecon_admin role
user.getRoles().remove(kneconAdminRole);
return true;
}
return true;
}).toList();
}
@ -125,19 +128,14 @@ public class UserController implements UserResource, PublicResource {
if (StringUtils.isEmpty(userId)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The userId should not be empty.");
}
var user = userService.getUserById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
Set<String> filteredRoles = user.getRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found");
var kneconAdminRole = "KNECON_ADMIN";
var user = userService.getUserById(userId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
if (user.getRoles().contains(kneconAdminRole)) {
if(user.getRoles().size() == 1) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found");
}
user.getRoles().remove(kneconAdminRole);
}
user.setRoles(filteredRoles);
return user;
}

View File

@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Tag(name = "6. Users endpoints", description = "Operations related to users.")
public class UserControllerV2 implements UserResourceV2, PublicResourceV2 {
public class UserContollerV2 implements UserResourceV2, PublicResourceV2 {
private final UserController userController;

View File

@ -9,14 +9,16 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import com.fasterxml.jackson.databind.JsonNode;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
import com.knecon.fforesight.tenantusermanagement.api.internal.InternalResource;
import com.knecon.fforesight.tenantusermanagement.api.internal.InternalTenantsResource;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.events.TenantCreatedEvent;
import com.knecon.fforesight.tenantusermanagement.events.TenantSyncEvent;
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.service.DeploymentKeyService;
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
@ -38,7 +40,7 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
}
public TenantResponse createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
public TenantResponse createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
try {
return tenantManagementService.createTenant(tenantRequest);
@ -61,12 +63,18 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
@Override
public TenantResponse updateTenant(String tenantId, UpdateTenantRequest tenantRequest) {
public TenantResponse updateTenant(String tenantId, TenantRequest tenantRequest) {
return tenantManagementService.updateTenant(tenantId, tenantRequest);
}
public List<SimpleTenantResponse> getSimpleTenants() {
return tenantManagementService.getTenants().stream().map(t -> new SimpleTenantResponse(t.getTenantId(), t.getDisplayName(), t.getGuid())).toList();
}
public DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId) {
return new DeploymentKeyResponse(deploymentKeyService.getDeploymentKey(tenantId));
@ -80,10 +88,4 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
}
public TenantApplicationType getTenantApplicationType(@PathVariable(TENANT_ID_PARAM) String tenantId) {
return tenantManagementService.getTenantApplicationType(tenantId);
}
}

View File

@ -15,12 +15,14 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
@ -72,7 +74,7 @@ public class DevTestTenantService {
createDatabase(tenantsDBName, tenantsDBPassword);
createSchema(jdbcUrl, tenantId, tenantsDBName, tenantsDBPassword);
var tenantRequest = CreateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId(tenantId)
.displayName(tenantId)
.guid(UUID.randomUUID().toString())

View File

@ -1,60 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "global_smtp_configuration")
public class GlobalSMTPConfigurationEntity {
@Id
@Column(nullable = false, updatable = false)
private String id = "singleton";
@Column
private Boolean auth;
@Column
private String envelopeFrom;
@Column
private String fromEmail;
@Column
private String fromDisplayName;
@Column
private String host;
@Column
private String password;
@Column
private Integer port;
@Column
private String replyTo;
@Column
private String replyToDisplayName;
@Column
private Boolean ssl;
@Column
private Boolean starttls;
@Column
private String userName;
}

View File

@ -1,30 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class MongoDBConnectionEntity {
@Column(name = "mongodb_prefix")
private String prefix;
@Column(name = "mongodb_username")
private String username;
@Column(name = "mongodb_password")
private String password;
@Column(name = "mongodb_address")
private String address;
@Column(name = "mongodb_database")
private String database;
@Column(name = "mongodb_options")
private String options;
}

View File

@ -3,7 +3,6 @@ package com.knecon.fforesight.tenantusermanagement.entity;
import java.util.HashMap;
import java.util.Map;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantusermanagement.utils.JSONMapConverter;
import jakarta.persistence.Basic;
@ -11,8 +10,6 @@ import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@ -48,17 +45,9 @@ public class TenantEntity {
@Embedded
private S3StorageConnectionEntity s3StorageConnection;
@Embedded
private MongoDBConnectionEntity mongoDBConnection;
@Basic(fetch = FetchType.EAGER)
@Column(columnDefinition = "text")
@Convert(converter = JSONMapConverter.class)
@Builder.Default
private Map<String, Object> details = new HashMap<>();
@Column
@Enumerated(EnumType.STRING)
private TenantApplicationType applicationType;
}

View File

@ -34,7 +34,9 @@ public class MigrateOnlyHook {
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
tenantManagementService.getTenants().forEach(tenant -> keyCloakRoleManagerService.updateRoles(tenant.getTenantId(), tenant.getApplicationType()));
tenantManagementService.getTenants().forEach(tenant -> {
keyCloakRoleManagerService.updateRoles(tenant.getTenantId());
});
// This should only run in post upgrade hook
if (isMigrateOnly) {

View File

@ -25,7 +25,4 @@ public class CreateUserRequest {
@Schema(description = "Roles to assign to user.")
private Set<String> roles = new HashSet<>();
@Schema(description = "Whether a set password mail should be sent")
private boolean sendSetPasswordMail;
}

View File

@ -0,0 +1,23 @@
package com.knecon.fforesight.tenantusermanagement.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Object containing a simplified version of the tenant data.")
public class SimpleTenantResponse {
@Schema(description = "Parameter containing the ID of the tenant.")
private String tenantId;
@Schema(description = "Parameter containing the display name of the tenant.")
private String displayName;
@Schema(description = "Parameter containing the global unique ID of the tenant.")
private String guid;
}

View File

@ -3,11 +3,10 @@ package com.knecon.fforesight.tenantusermanagement.model;
import java.util.ArrayList;
import java.util.List;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@ -21,8 +20,8 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Object containing the request to create a tenant.")
public class CreateTenantRequest {
@Schema(description = "Object containing the request to create or update a tenant.")
public class TenantRequest {
@NotBlank
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Tenant Id must match [A-Za-z0-9_-]")
@ -42,14 +41,9 @@ public class CreateTenantRequest {
private AzureStorageConnection azureStorageConnection;
@Schema(description = "Parameter containing data of the S3 storage connection.")
private S3StorageConnection s3StorageConnection;
@Schema(description = "Parameter containing data of the MongoDB connection.")
private MongoDBConnection mongoDBConnection;
@Builder.Default
@Schema(description = "Parameter containing a list of users of the tenant.")
private List<TenantUser> defaultUsers = new ArrayList<>();
@Schema(description = "Parameter containing the application type of the tenant.")
private TenantApplicationType applicationType;
}

View File

@ -1,43 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import java.util.ArrayList;
import java.util.List;
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Object containing the request to update a tenant.")
public class UpdateTenantRequest {
@NotBlank
@Schema(description = "Parameter containing the display name of the tenant.")
private String displayName;
@Schema(description = "Parameter containing the global unique ID of the tenant.")
private String guid;
@Schema(description = "Parameter containing data of the database connection.")
private DatabaseConnection databaseConnection;
@Schema(description = "Parameter containing data of the search connection.")
private SearchConnectionRequest searchConnection;
@Schema(description = "Parameter containing data of the Azure storage connection.")
private AzureStorageConnection azureStorageConnection;
@Schema(description = "Parameter containing data of the S3 storage connection.")
private S3StorageConnection s3StorageConnection;
@Schema(description = "Parameter containing data of the MongoDB connection.")
private MongoDBConnection mongoDBConnection;
}

View File

@ -1,60 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.permissions;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.knecon.fforesight.tenantusermanagement.model.User;
public final class ApplicationRoles {
public static final String KNECON_ADMIN_ROLE = "KNECON_ADMIN";
public static final String KNECON_SUPPORT_ROLE = "KNECON_SUPPORT";
public static final String RED_USER_ROLE = "RED_USER";
public static final String RED_MANAGER_ROLE = "RED_MANAGER";
public static final String RED_ADMIN_ROLE = "RED_ADMIN";
public static final String RED_USER_ADMIN_ROLE = "RED_USER_ADMIN";
public static final Set<String> KNECON_ROLES = Set.of(KNECON_ADMIN_ROLE, KNECON_SUPPORT_ROLE);
public static final Set<String> RED_ROLES = Set.of(RED_USER_ROLE, RED_MANAGER_ROLE, RED_ADMIN_ROLE, RED_USER_ADMIN_ROLE);
public static final Predicate<User> KNECON_ROLE_FILTER = user -> {
Set<String> filteredRoles = user.getRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) {
return false;
}
user.setRoles(filteredRoles);
return true;
};
public static boolean isNoKneconRole(String role) {
return !KNECON_ROLES.contains(role);
}
public static boolean isKneconRole(String role) {
return KNECON_ROLES.contains(role);
}
public static boolean isNoREDRole(String role) {
return !RED_ROLES.contains(role);
}
public static boolean isREDRole(String role) {
return RED_ROLES.contains(role);
}
}

View File

@ -1,25 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.properties;
import java.util.ArrayList;
import java.util.List;
import com.knecon.fforesight.tenantusermanagement.model.KCRoleMapping;
import lombok.Data;
@Data
public class ApplicationTypeProperties {
private String applicationClientId;
private String applicationName;
private Integer tenantAccessTokenLifeSpan = 300;
private Integer accessTokenLifeSpan = 1800;
private Integer ssoSessionIdleTimeout = 86400;
private Integer refreshTokenMaxReuse;
private String defaultTheme = "redaction";
private List<String> validRedirectUris = new ArrayList<>();
private KCRoleMapping kcRoleMapping = new KCRoleMapping();
private String loginTheme;
private String appPrefix;
}

View File

@ -1,13 +1,11 @@
package com.knecon.fforesight.tenantusermanagement.properties;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.model.KCRoleMapping;
import lombok.Data;
@ -16,17 +14,23 @@ import lombok.Data;
public class TenantUserManagementProperties {
private String serverUrl;
private String realm = "master";
private String swaggerClientId = "swagger-ui-client";
private String publicServerUrl;
private String realm;
private String applicationClientId;
private String swaggerClientId ="swagger-ui-client";
private String swaggerClientSecret;
private String clientId;
private String clientSecret;
private String publicServerUrl;
private String basePath = "/";
private String basePathV2 = "/api";
private int connectionPoolSize = 10;
private Map<String, ApplicationTypeProperties> applicationTypes = new HashMap<>();
private String applicationName;
private Integer accessTokenLifeSpan = 1800;
private Integer ssoSessionIdleTimeout = 86400;
private String defaultTheme = "redaction";
private List<String> validRedirectUris = new ArrayList<>();
private KCRoleMapping kcRoleMapping = new KCRoleMapping();
private String loginTheme;
private String appPrefix;
}

View File

@ -1,15 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
public interface GlobalSMTPConfigurationRepository extends JpaRepository<GlobalSMTPConfigurationEntity, String> {
default Optional<GlobalSMTPConfigurationEntity> findSingleton() {
return findById("singleton");
}
}

View File

@ -2,14 +2,11 @@ package com.knecon.fforesight.tenantusermanagement.repository;
import java.util.Optional;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
import jakarta.transaction.Transactional;
@ -19,16 +16,10 @@ public interface TenantRepository extends JpaRepository<TenantEntity, String> {
@Query("select t from TenantEntity t where t.tenantId = :tenantId")
Optional<TenantEntity> findByTenantId(@Param("tenantId") String tenantId);
@CacheEvict(value = "tenantApplicationType", key = "#tenantId")
@Transactional
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("delete from TenantEntity t where t.id = :tenantId ")
void deleteByQuery(String tenantId);
@Cacheable(value = "tenantApplicationType", key = "#tenantId")
@Query("select t.applicationType from TenantEntity t where t.tenantId = :tenantId")
Optional<TenantApplicationType> findApplicationTypeByTenantId(@Param("tenantId") String tenantId);
}

View File

@ -1,52 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class EnvironmentSMTPConfigurationProvider {
private final Environment environment;
public static final Integer KEYCLOAK_DEFAULT_PORT = 25;
public static final String SMTP_DEFAULT_HOST = "SMTP_DEFAULT_HOST";
public static final String SMTP_DEFAULT_PORT = "SMTP_DEFAULT_PORT";
public static final String SMTP_DEFAULT_SENDER_EMAIL = "SMTP_DEFAULT_SENDER_EMAIL";
public static final String SMTP_DEFAULT_SENDER_NAME = "SMTP_DEFAULT_SENDER_NAME";
public static final String SMTP_DEFAULT_REPLY_EMAIL = "SMTP_DEFAULT_REPLY_EMAIL";
public static final String SMTP_DEFAULT_REPLY_NAME = "SMTP_DEFAULT_REPLY_NAME";
public static final String SMTP_DEFAULT_SENDER_ENVELOPE = "SMTP_DEFAULT_SENDER_ENVELOPE";
public static final String SMTP_DEFAULT_SSL = "SMTP_DEFAULT_SSL";
public static final String SMTP_DEFAULT_STARTTLS = "SMTP_DEFAULT_STARTTLS";
public static final String SMTP_DEFAULT_AUTH = "SMTP_DEFAULT_AUTH";
public static final String SMTP_DEFAULT_AUTH_USER = "SMTP_DEFAULT_AUTH_USER";
public static final String SMTP_DEFAULT_AUTH_PASSWORD = "SMTP_DEFAULT_AUTH_PASSWORD";
public SMTPConfiguration get() {
String port = environment.getProperty(SMTP_DEFAULT_PORT, "");
return SMTPConfiguration.builder()
.id("singleton")
.host(environment.getProperty(SMTP_DEFAULT_HOST, ""))
.port(StringUtils.isEmpty(port) || !StringUtils.isNumeric(port) ? KEYCLOAK_DEFAULT_PORT : Integer.parseInt(port))
.from(environment.getProperty(SMTP_DEFAULT_SENDER_EMAIL, ""))
.fromDisplayName(environment.getProperty(SMTP_DEFAULT_SENDER_NAME, ""))
.replyTo(environment.getProperty(SMTP_DEFAULT_REPLY_EMAIL, ""))
.replyToDisplayName(environment.getProperty(SMTP_DEFAULT_REPLY_NAME, ""))
.envelopeFrom(environment.getProperty(SMTP_DEFAULT_SENDER_ENVELOPE, ""))
.ssl(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_SSL, "false")))
.starttls(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_STARTTLS, "false")))
.auth(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_AUTH, "false")))
.user(environment.getProperty(SMTP_DEFAULT_AUTH_USER, ""))
.password(environment.getProperty(SMTP_DEFAULT_AUTH_PASSWORD, ""))
.build();
}
}

View File

@ -4,10 +4,8 @@ import org.apache.commons.lang3.StringUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.model.GeneralConfigurationModel;
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import lombok.RequiredArgsConstructor;
@ -20,30 +18,28 @@ public class GeneralConfigurationService {
private final RealmService realmService;
private final TenantUserManagementProperties tenantUserManagementProperties;
private final TenantApplicationTypeService tenantApplicationTypeService;
public void initGeneralConfiguration(String tenantId, TenantApplicationType tenantApplicationType) {
public void initGeneralConfiguration(String tenantId) {
TenantContext.setTenantId(tenantId);
var generalConfiguration = getGeneralConfigurations(tenantApplicationType);
log.info("Currently Configured Application Name: {}, default name: {}", generalConfiguration.getDisplayName(), tenantApplicationTypeService.getProperties(tenantApplicationType).getApplicationName());
updateGeneralConfigurations(getGeneralConfigurations(tenantApplicationType), tenantApplicationType);
var generalConfiguration = getGeneralConfigurations();
log.info("Currently Configured Application Name: {}, default name: {}", generalConfiguration.getDisplayName(), tenantUserManagementProperties.getApplicationName());
updateGeneralConfigurations(getGeneralConfigurations());
TenantContext.clear();
}
public GeneralConfigurationModel getGeneralConfigurations(TenantApplicationType tenantApplicationType) {
public GeneralConfigurationModel getGeneralConfigurations() {
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
var auxiliaryName = realm.getDisplayNameHtml();
String computedAuxiliaryName = null;
ApplicationTypeProperties currentAppTypeProperties = tenantApplicationTypeService.getProperties(tenantApplicationType);
if (!currentAppTypeProperties.getApplicationName().equals(auxiliaryName)) {
if (!tenantUserManagementProperties.getApplicationName().equals(auxiliaryName)) {
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, currentAppTypeProperties.getApplicationName(), "");
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, tenantUserManagementProperties.getApplicationName(), "");
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, " (", "");
auxiliaryName = StringUtils.reverse(StringUtils.replaceOnce(StringUtils.reverse(auxiliaryName), ")", ""));
@ -58,7 +54,7 @@ public class GeneralConfigurationService {
}
public void updateGeneralConfigurations(GeneralConfigurationModel generalConfigurationModel, TenantApplicationType tenantApplicationType) {
public void updateGeneralConfigurations(GeneralConfigurationModel generalConfigurationModel) {
var realm = realmService.realm(TenantContext.getTenantId());
@ -71,16 +67,10 @@ public class GeneralConfigurationService {
var realmRepresentation = realm.toRepresentation();
realmRepresentation.setResetPasswordAllowed(generalConfigurationModel.isForgotPasswordFunctionEnabled());
realmRepresentation.setRevokeRefreshToken(true);
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(tenantApplicationType);
realmRepresentation.setRefreshTokenMaxReuse(applicationTypeProperties.getRefreshTokenMaxReuse());
realmRepresentation.getAttributes().put("actionTokenGeneratedByUserLifespan.idp-verify-account-via-email", Integer.toString(86400));
if (!StringUtils.isEmpty(generalConfigurationModel.getAuxiliaryName())) {
setDisplayName(realmRepresentation, applicationTypeProperties.getApplicationName() + " (" + generalConfigurationModel.getAuxiliaryName() + ")");
setDisplayName(realmRepresentation, tenantUserManagementProperties.getApplicationName() + " (" + generalConfigurationModel.getAuxiliaryName() + ")");
} else {
setDisplayName(realmRepresentation, applicationTypeProperties.getApplicationName());
setDisplayName(realmRepresentation, tenantUserManagementProperties.getApplicationName());
}
try {

View File

@ -1,37 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.service;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
import com.knecon.fforesight.tenantusermanagement.repository.GlobalSMTPConfigurationRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@Service
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class GlobalSMTPConfigurationPersistenceService {
GlobalSMTPConfigurationRepository smtpConfigurationRepository;
@Transactional(readOnly = true)
public Optional<GlobalSMTPConfigurationEntity> get() {
return smtpConfigurationRepository.findSingleton();
}
@Transactional
public GlobalSMTPConfigurationEntity createUpdate(GlobalSMTPConfigurationEntity smtpConfiguration) {
smtpConfiguration.setId("singleton");
return smtpConfigurationRepository.save(smtpConfiguration);
}
}

View File

@ -9,8 +9,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.springframework.stereotype.Component;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import lombok.RequiredArgsConstructor;
@ -23,26 +21,24 @@ public class KeyCloakRoleManagerService {
private final RealmService realmService;
private final TenantUserManagementProperties tenantUserManagementProperties;
private final TenantApplicationTypeService tenantApplicationTypeService;
public void updateRoles(String tenantId, TenantApplicationType applicationType) {
public void updateRoles(String tenantId) {
var realm = realmService.realm(tenantId);
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(applicationType);
log.info("Running KeyCloak Role Manager, managing client: {} with system client {}",
applicationTypeProperties.getApplicationClientId(),
tenantUserManagementProperties.getClientId());
tenantUserManagementProperties.getApplicationClientId(),
tenantUserManagementProperties.getClientId());
var existingRoles = realm.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
log.info("Existing KC roles: {}", existingRoles);
var redactionClientRepresentation = getRedactionClientRepresentation(tenantId, applicationTypeProperties.getApplicationClientId());
var redactionClientRepresentation = getRedactionClientRepresentation(tenantId);
var redactionClient = realm.clients().get(redactionClientRepresentation.getId());
var clientRoles = redactionClient.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
var allPermissions = applicationTypeProperties.getKcRoleMapping().getPermissions();
var allPermissions = tenantUserManagementProperties.getKcRoleMapping().getPermissions();
log.info("Existing KC client roles: {}", clientRoles);
log.info("Current Application KC client roles: {}", allPermissions);
@ -69,8 +65,8 @@ public class KeyCloakRoleManagerService {
var allClientRoles = redactionClient.roles().list();
var allRoles = applicationTypeProperties.getKcRoleMapping().getAllRoles();
var rolePermissionMappings = applicationTypeProperties.getKcRoleMapping().getRolePermissionMapping();
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
var rolePermissionMappings = tenantUserManagementProperties.getKcRoleMapping().getRolePermissionMapping();
// if an application-role doesn't exist, create it
for (String applicationRole : allRoles) {
@ -97,7 +93,7 @@ public class KeyCloakRoleManagerService {
log.info("Finished application role {}", applicationRole);
}
var composites = applicationTypeProperties.getKcRoleMapping().getRoleComposites();
var composites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
for (var key : composites.keySet()) {
var realmRole = realm.roles().get(key).toRepresentation();
@ -115,8 +111,9 @@ public class KeyCloakRoleManagerService {
}
private ClientRepresentation getRedactionClientRepresentation(String tenantId, String applicationClientId) {
private ClientRepresentation getRedactionClientRepresentation(String tenantId) {
String applicationClientId = tenantUserManagementProperties.getApplicationClientId();
var clientRepresentationIterator = realmService.realm(tenantId).clients().findByClientId(applicationClientId).iterator();
if (clientRepresentationIterator.hasNext()) {

View File

@ -1,258 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.client.LicenseClient;
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
import com.knecon.fforesight.tenantusermanagement.utils.SMTPConfigurationMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@RequiredArgsConstructor
public class SMTPService {
private final GlobalSMTPConfigurationPersistenceService smtpConfigurationPersistenceService;
private final EnvironmentSMTPConfigurationProvider environmentSMTPConfigurationProvider;
private final LicenseClient licenseClient;
private final RealmService realmService;
private final TenantRepository tenantRepository;
private final EncryptionDecryptionService encryptionDecryptionService;
private final ObjectMapper objectMapper;
private final static String SMTP_PASSWORD_KEY = "FFORESIGHT_SMTP_PASSWORD";
private final static String DEFAULT_PASSWORD = "**********";
public static final String CONFIGURABLE_SMTP_SERVER_FEATURE = "configurableSMTPServer";
@EventListener(ApplicationReadyEvent.class)
public void synchronizeSMTPConfigurations() {
log.info("Starting SMTP configuration synchronization...");
try {
List<TenantEntity> tenants = tenantRepository.findAll();
log.info("Retrieved {} tenants for SMTP synchronization.", tenants.size());
Optional<GlobalSMTPConfigurationEntity> optionalLatestGlobalConfigEntity = smtpConfigurationPersistenceService.get();
if (optionalLatestGlobalConfigEntity.isEmpty()) {
log.info("Global SMTP configuration was newly added. Initializing all tenant SMTP configurations.");
initializeGlobalSmtpConfig(tenants);
} else {
updateGlobalSmtpConfig(optionalLatestGlobalConfigEntity.get(), tenants);
}
log.info("SMTP configuration synchronization completed successfully.");
} catch (Exception e) {
log.error("Failed to synchronize SMTP configurations: {}", e.getMessage(), e);
}
}
private void initializeGlobalSmtpConfig(List<TenantEntity> tenants) {
SMTPConfiguration currentGlobalConfig = environmentSMTPConfigurationProvider.get();
initializeTenantsSmtpConfig(tenants, currentGlobalConfig);
currentGlobalConfig.setPassword(encryptionDecryptionService.encrypt(currentGlobalConfig.getPassword()));
GlobalSMTPConfigurationEntity updatedGlobalConfig = SMTPConfigurationMapper.toEntity(currentGlobalConfig);
smtpConfigurationPersistenceService.createUpdate(updatedGlobalConfig);
}
private void updateGlobalSmtpConfig(GlobalSMTPConfigurationEntity latestGlobalConfigEntity, List<TenantEntity> tenants) {
SMTPConfiguration latestGlobalConfig = SMTPConfigurationMapper.toModel(latestGlobalConfigEntity);
latestGlobalConfig.setPassword(encryptionDecryptionService.decrypt(latestGlobalConfig.getPassword()));
log.info("Existing global SMTP configuration was loaded.");
// Generate the latest SMTP config from environment variables and compare it to the last/saved global config
SMTPConfiguration currentGlobalConfig = environmentSMTPConfigurationProvider.get();
if (!currentGlobalConfig.equals(latestGlobalConfig)) {
log.info("Environment SMTP configuration has changed. Updating global SMTP configuration.");
updateTenantsSmtpConfig(tenants, latestGlobalConfig, currentGlobalConfig);
currentGlobalConfig.setPassword(encryptionDecryptionService.encrypt(currentGlobalConfig.getPassword()));
GlobalSMTPConfigurationEntity updatedGlobalConfig = SMTPConfigurationMapper.toEntity(currentGlobalConfig);
smtpConfigurationPersistenceService.createUpdate(updatedGlobalConfig);
} else {
log.info("No changes detected in environment SMTP configuration.");
}
}
private void initializeTenantsSmtpConfig(List<TenantEntity> tenants, SMTPConfiguration currentGlobalConfig) {
tenants.forEach(tenant -> processTenantSmtpConfig(tenant, currentGlobalConfig, null, true));
}
private void updateTenantsSmtpConfig(List<TenantEntity> tenants, SMTPConfiguration latestGlobalConfig, SMTPConfiguration currentGlobalConfig) {
tenants.forEach(tenant -> processTenantSmtpConfig(tenant, currentGlobalConfig, latestGlobalConfig, false));
}
private void processTenantSmtpConfig(TenantEntity tenant, SMTPConfiguration currentGlobalConfig, SMTPConfiguration latestGlobalConfig, boolean isInitialization) {
String tenantId = tenant.getTenantId();
log.info("Processing SMTP configuration for tenant: {}", tenantId);
try {
TenantContext.setTenantId(tenantId);
SMTPConfiguration tenantSMTPConfig = getSMTPConfiguration();
tenantSMTPConfig.setId("singleton");
updatePassword(tenantSMTPConfig);
if (!StringUtils.isBlank(tenantSMTPConfig.getPassword())) {
tenantSMTPConfig.setPassword(encryptionDecryptionService.decrypt(tenantSMTPConfig.getPassword()));
}
if (isInitialization) {
if (StringUtils.isBlank(tenantSMTPConfig.getHost())) {
log.info("Tenant '{}' SMTP configuration has not been yet set.", tenantId);
updateSMTPConfiguration(currentGlobalConfig);
log.info("Tenant '{}' SMTP configuration set successfully.", tenantId);
} else {
log.info("Tenant '{}' SMTP configuration was already set. No action taken.", tenantId);
}
} else {
if (tenantSMTPConfig.equals(latestGlobalConfig)) {
log.info("Tenant '{}' SMTP configuration matches global. Updating to latest environment configuration.", tenantId);
updateSMTPConfiguration(currentGlobalConfig);
log.info("Tenant '{}' SMTP configuration updated successfully.", tenantId);
} else {
log.info("Tenant '{}' SMTP configuration differs from global. No action taken.", tenantId);
}
}
} catch (Exception e) {
log.error("Error processing SMTP configuration for tenant '{}': {}", tenantId, e.getMessage());
} finally {
TenantContext.clear();
}
}
public SMTPConfiguration getSMTPConfiguration() {
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
return objectMapper.convertValue(realm.getSmtpServer(), SMTPConfiguration.class);
}
public Map<String, String> convertSMTPConfigToMap(SMTPConfiguration smtpConfiguration) {
updatePassword(smtpConfiguration);
smtpConfiguration.setPassword(encryptionDecryptionService.decrypt(smtpConfiguration.getPassword()));
return convertSMTPConfigurationModelToMap(smtpConfiguration);
}
public boolean canUpdateSMTPConfig() {
RedactionLicenseModel licenseModel = licenseClient.getLicense();
String activeLicenseId = licenseModel.getActiveLicense();
return licenseModel.getLicenses()
.stream()
.filter(license -> license.getId().equals(activeLicenseId))
.flatMap(license -> license.getFeatures()
.stream())
.filter(feature -> CONFIGURABLE_SMTP_SERVER_FEATURE.equals(feature.getName()))
.map(Feature::getValue)
.filter(Boolean.class::isInstance)
.map(Boolean.class::cast)
.findFirst()
.orElse(false);
}
public void createDefaultSMTPConfiguration() {
SMTPConfiguration defaultConfig = environmentSMTPConfigurationProvider.get();
updateSMTPConfiguration(defaultConfig);
}
public void updateSMTPConfiguration(SMTPConfiguration smtpConfigurationModel) {
String tenantId = TenantContext.getTenantId();
var realmRepresentation = realmService.realm(tenantId).toRepresentation();
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
realmRepresentation.setSmtpServer(propertiesMap);
if (smtpConfigurationModel.getPassword() != null && !smtpConfigurationModel.getPassword().matches("\\**")) {
realmRepresentation.getAttributesOrEmpty().put(SMTP_PASSWORD_KEY, encryptionDecryptionService.encrypt(smtpConfigurationModel.getPassword()));
}
realmService.realm(tenantId).update(realmRepresentation);
}
public void clearSMTPConfiguration() {
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
realmRepresentation.setSmtpServer(new HashMap<>());
realmRepresentation.getAttributesOrEmpty().remove(SMTP_PASSWORD_KEY);
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
}
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, new TypeReference<>() {
});
Map<String, String> stringPropertiesMap = new HashMap<>();
propertiesMap.forEach((key, value) -> {
if (value != null) {
stringPropertiesMap.put(key, value.toString());
} else {
stringPropertiesMap.put(key, "");
}
});
return stringPropertiesMap;
}
private void updatePassword(SMTPConfiguration smtpConfiguration) {
if (DEFAULT_PASSWORD.equals(smtpConfiguration.getPassword())) {
try {
var password = realmService.realm(TenantContext.getTenantId()).toRepresentation().getAttributesOrEmpty().getOrDefault(SMTP_PASSWORD_KEY, "");
smtpConfiguration.setPassword(password);
} catch (Exception e) {
log.info("No current SMTP Config exists", e);
}
} else {
smtpConfiguration.setPassword(encryptionDecryptionService.encrypt(smtpConfiguration.getPassword()));
}
}
}

View File

@ -1,54 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.service;
import java.util.Locale;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@Service
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class TenantApplicationTypeService {
TenantUserManagementProperties tenantUserManagementProperties;
TenantRepository tenantRepository;
public TenantApplicationType getCurrent() {
return get(TenantContext.getTenantId());
}
public TenantApplicationType get(String tenantId) {
return tenantRepository.findApplicationTypeByTenantId(tenantId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist"));
}
public ApplicationTypeProperties getProperties(TenantApplicationType applicationType) {
return tenantUserManagementProperties.getApplicationTypes()
.get(applicationType.name().toLowerCase(Locale.ROOT));
}
public ApplicationTypeProperties getCurrentProperties() {
TenantApplicationType applicationType = get(TenantContext.getTenantId());
return getProperties(applicationType);
}
}

View File

@ -15,17 +15,16 @@ import java.util.stream.Collectors;
import javax.sql.DataSource;
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
import jakarta.ws.rs.NotFoundException;
import org.apache.commons.lang3.StringUtils;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.keycloak.admin.client.resource.RealmResource;
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.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
@ -43,48 +42,35 @@ 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.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantProvider;
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
import com.knecon.fforesight.tenantcommons.utils.MongoConnectionStringHelper;
import com.knecon.fforesight.tenantusermanagement.config.StorageConfiguration;
import com.knecon.fforesight.tenantusermanagement.entity.AzureStorageConnectionEntity;
import com.knecon.fforesight.tenantusermanagement.entity.DatabaseConnectionEntity;
import com.knecon.fforesight.tenantusermanagement.entity.MongoDBConnectionEntity;
import com.knecon.fforesight.tenantusermanagement.entity.S3StorageConnectionEntity;
import com.knecon.fforesight.tenantusermanagement.entity.SearchConnectionEntity;
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
import com.knecon.fforesight.tenantusermanagement.events.TenantCreatedEvent;
import com.knecon.fforesight.tenantusermanagement.events.TenantSyncEvent;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
import com.knecon.fforesight.tenantusermanagement.utils.JDBCUtils;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import jakarta.ws.rs.NotFoundException;
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.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.S3Object;
@Slf4j
@ -108,128 +94,95 @@ public class TenantManagementService implements TenantProvider {
private final RealmService realmService;
private final RabbitTemplate rabbitTemplate;
private final StorageConfiguration storageConfiguration;
private final SMTPService smtpService;
private final TenantApplicationTypeService tenantApplicationTypeService;
@Value("${fforesight.tenant-exchange.name}")
private String tenantExchangeName;
@SneakyThrows
public TenantResponse createTenant(CreateTenantRequest tenantRequest) {
public TenantResponse createTenant(TenantRequest tenantRequest) {
// For now we update the master realm theme whenever we create the tenant
updateMasterTheme(tenantApplicationTypeService.getProperties(tenantRequest.getApplicationType()).getLoginTheme());
updateMasterDisplayName(tenantApplicationTypeService.getProperties(tenantRequest.getApplicationType()).getApplicationName());
updateMasterTheme(tenantUserManagementProperties.getLoginTheme());
updateMasterDisplayName(tenantUserManagementProperties.getApplicationName());
log.info("Tenants are: {}",
tenantRepository.findAll()
.stream()
.map(TenantEntity::getTenantId)
.toList());
log.info("Tenants are: {}", tenantRepository.findAll().stream().map(TenantEntity::getTenantId).toList());
log.info("Requested to create tenant for: {}", tenantRequest.getTenantId());
try {
if (tenantRepository.findById(tenantRequest.getTenantId()).isEmpty()) {
log.info("Creating tenant: {}", tenantRequest.getTenantId());
DatabaseConnection databaseConnection = tenantRequest.getDatabaseConnection();
var jdbcUrl = JDBCUtils.buildJdbcUrlWithSchema(databaseConnection);
var jdbcUrl = JDBCUtils.buildJdbcUrlWithSchema(tenantRequest.getDatabaseConnection());
validateJdbcUrl(jdbcUrl);
SearchConnectionRequest searchConnection = tenantRequest.getSearchConnection();
var tenantEntityBuilder = TenantEntity.builder()
TenantEntity tenantEntity = TenantEntity.builder()
.tenantId(tenantRequest.getTenantId())
.displayName(tenantRequest.getDisplayName())
.guid(UUID.randomUUID().toString())
.applicationType(tenantRequest.getApplicationType() != null ? tenantRequest.getApplicationType() : TenantApplicationType.RedactManager)
.databaseConnection(DatabaseConnectionEntity.builder()
.driver(databaseConnection.getDriver())
.host(databaseConnection.getHost())
.port(databaseConnection.getPort())
.database(databaseConnection.getDatabase())
.schema(databaseConnection.getSchema())
.username(databaseConnection.getUsername())
.password(encryptionService.encrypt(databaseConnection.getPassword()))
.build())
.driver(tenantRequest.getDatabaseConnection().getDriver())
.host(tenantRequest.getDatabaseConnection().getHost())
.port(tenantRequest.getDatabaseConnection().getPort())
.database(tenantRequest.getDatabaseConnection().getDatabase())
.schema(tenantRequest.getDatabaseConnection().getSchema())
.username(tenantRequest.getDatabaseConnection().getUsername())
.password(encryptionService.encrypt(tenantRequest.getDatabaseConnection().getPassword()))
.build())
.searchConnection(SearchConnectionEntity.builder()
.hosts(searchConnection.getHosts())
.port(searchConnection.getPort())
.scheme(searchConnection.getScheme())
.username(searchConnection.getUsername())
.password(encryptionService.encrypt(searchConnection.getPassword()))
.numberOfShards(searchConnection.getNumberOfShards())
.numberOfReplicas(searchConnection.getNumberOfReplicas())
.indexPrefix(buildIndexPrefix(tenantRequest.getTenantId(), tenantRequest.getApplicationType()))
.build());
MongoDBConnection mongoDBConnection = tenantRequest.getMongoDBConnection();
if (mongoDBConnection != null) {
tenantEntityBuilder.mongoDBConnection(MongoDBConnectionEntity.builder()
.prefix(mongoDBConnection.getPrefix())
.username(mongoDBConnection.getUsername())
.password(encryptionService.encrypt(mongoDBConnection.getPassword()))
.address(mongoDBConnection.getAddress())
.database(mongoDBConnection.getDatabase())
.options(mongoDBConnection.getOptions())
.build());
}
TenantEntity tenantEntity = tenantEntityBuilder.build();
.hosts(tenantRequest.getSearchConnection().getHosts())
.port(tenantRequest.getSearchConnection().getPort())
.scheme(tenantRequest.getSearchConnection().getScheme())
.username(tenantRequest.getSearchConnection().getUsername())
.password(encryptionService.encrypt(tenantRequest.getSearchConnection().getPassword()))
.numberOfShards(tenantRequest.getSearchConnection().getNumberOfShards())
.numberOfReplicas(tenantRequest.getSearchConnection().getNumberOfReplicas())
.indexPrefix(buildIndexPrefix(tenantRequest.getTenantId()))
.build())
.build();
AzureStorageConnection azureStorageConnection = tenantRequest.getAzureStorageConnection();
if (azureStorageConnection != null) {
testAzureConnection(azureStorageConnection.getConnectionString(), azureStorageConnection.getContainerName());
if (tenantRequest.getAzureStorageConnection() != null) {
testAzureConnection(tenantRequest.getAzureStorageConnection().getConnectionString(), tenantRequest.getAzureStorageConnection().getContainerName());
tenantEntity.setAzureStorageConnection(AzureStorageConnectionEntity.builder()
.connectionString(encryptionService.encrypt(azureStorageConnection.getConnectionString()))
.containerName(azureStorageConnection.getContainerName())
.build());
.connectionString(encryptionService.encrypt(tenantRequest.getAzureStorageConnection().getConnectionString()))
.containerName(tenantRequest.getAzureStorageConnection().getContainerName())
.build());
}
S3StorageConnection s3StorageConnection = tenantRequest.getS3StorageConnection();
if (s3StorageConnection != null) {
testS3Connection(s3StorageConnection);
if (tenantRequest.getS3StorageConnection() != null) {
testS3Connection(tenantRequest.getS3StorageConnection());
tenantEntity.setS3StorageConnection(S3StorageConnectionEntity.builder()
.key(s3StorageConnection.getKey())
.secret(encryptionService.encrypt(s3StorageConnection.getSecret()))
.signerType(s3StorageConnection.getSignerType())
.bucketName(s3StorageConnection.getBucketName())
.region(s3StorageConnection.getRegion())
.endpoint(s3StorageConnection.getEndpoint())
.build());
.key(tenantRequest.getS3StorageConnection().getKey())
.secret(encryptionService.encrypt(tenantRequest.getS3StorageConnection().getSecret()))
.signerType(tenantRequest.getS3StorageConnection().getSignerType())
.bucketName(tenantRequest.getS3StorageConnection().getBucketName())
.region(tenantRequest.getS3StorageConnection().getRegion())
.endpoint(tenantRequest.getS3StorageConnection().getEndpoint())
.build());
}
createSchema(tenantRequest);
log.info("Created schema for tenant: {}", tenantRequest.getTenantId());
if (mongoDBConnection != null) {
createMongoDBDatabase(tenantRequest);
log.info("Created mongodb database for tenant: {}", tenantRequest.getTenantId());
} else {
log.info("Skipping creation of mongo database for this tenant");
}
propagateTenantToKeyCloak(tenantRequest.getTenantId(), tenantRequest.getApplicationType(), tenantRequest.getDefaultUsers());
propagateTenantToKeyCloak(tenantRequest.getTenantId(), tenantRequest.getDefaultUsers());
log.info("Updated roles for tenant: {}", tenantRequest.getTenantId());
TenantContext.setTenantId(tenantEntity.getTenantId());
smtpService.createDefaultSMTPConfiguration();
log.info("Created default SMTP configuration.");
var saved = tenantPersistenceService.save(tenantEntity);
JsonNode node;
log.info("Persisted tenant: {}", tenantRequest.getTenantId());
TenantContext.setTenantId(tenantEntity.getTenantId());
rabbitTemplate.convertAndSend(tenantExchangeName, "tenant.created", new TenantCreatedEvent(tenantEntity.getTenantId()));
TenantContext.clear();
log.info("Dispatched message for tenant: {}", tenantRequest.getTenantId());
return convert(saved);
} else {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Tenant exists");
}
@ -255,7 +208,6 @@ public class TenantManagementService implements TenantProvider {
TenantContext.clear();
log.info("Dispatched delete index message for tenant: {}", tenant.getTenantId());
log.info("Deleting schema for tenant: {}", tenant.getTenantId());
deleteSchema(tenant);
if (tenant.getAzureStorageConnection() != null) {
log.info("Deleting azure blob for tenant: {}", tenantId);
@ -275,48 +227,49 @@ public class TenantManagementService implements TenantProvider {
if (tenant.getS3StorageConnection() != null) {
var s3StorageConnectionTemplate = tenant.getS3StorageConnection();
com.iqser.red.storage.commons.model.S3StorageConnection s3StorageConnection;
s3StorageConnection = new com.iqser.red.storage.commons.model.S3StorageConnection(s3StorageConnectionTemplate.getKey(),
encryptionService.decrypt(s3StorageConnectionTemplate.getSecret()),
s3StorageConnectionTemplate.getSignerType(),
s3StorageConnectionTemplate.getBucketName(),
s3StorageConnectionTemplate.getRegion(),
s3StorageConnectionTemplate.getEndpoint());
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();
try {
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);
} catch (NoSuchBucketException noSuchBucketException) {
log.info("The bucket did not exist and therefore did not need to be deleted.");
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);
}
}
log.info("Deleting mongodb database for tenant: {}", tenant.getTenantId());
deleteMongoDBDatabase(tenant);
deleteRealm(tenantId);
tenantRepository.deleteByQuery(tenant.getTenantId());
tenantRepository.deleteById(tenant.getTenantId());
}
private String buildIndexPrefix(String tenantId, TenantApplicationType applicationType) {
private String buildIndexPrefix(String tenantId) {
return tenantApplicationTypeService.getProperties(applicationType).getAppPrefix() + "_" + tenantId;
return tenantUserManagementProperties.getAppPrefix() + "_" + tenantId;
}
private void propagateTenantToKeyCloak(String tenantId, TenantApplicationType applicationType, List<TenantUser> usersToCreate) throws InterruptedException {
private void propagateTenantToKeyCloak(String tenantId, List<TenantUser> usersToCreate) throws InterruptedException {
log.info("Creating or updating realm for tenant: {}", tenantId);
createOrUpdateRealm(tenantId, applicationType, usersToCreate);
log.info("Creating realm for tenant: {}", tenantId);
createOrUpdateRealm(tenantId, usersToCreate);
log.info("Created realm for tenant: {}", tenantId);
var waitTime = 0;
boolean realmReady;
@ -332,13 +285,13 @@ public class TenantManagementService implements TenantProvider {
} while (waitTime < MAX_WAIT_TIME);
if (!realmReady) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create or update KC realm");
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create KC realm");
}
setPasswordPolicyForRealm(tenantId);
generalConfigurationService.initGeneralConfiguration(tenantId, applicationType);
keyCloakRoleManagerService.updateRoles(tenantId, applicationType);
generalConfigurationService.initGeneralConfiguration(tenantId);
keyCloakRoleManagerService.updateRoles(tenantId);
}
@ -360,12 +313,12 @@ public class TenantManagementService implements TenantProvider {
}
private void createSchema(CreateTenantRequest tenantRequest) {
private void createSchema(TenantRequest tenantRequest) {
var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection());
try (Connection connection = DriverManager.getConnection(jdbcUrl,
tenantRequest.getDatabaseConnection().getUsername(),
tenantRequest.getDatabaseConnection().getPassword())) {
tenantRequest.getDatabaseConnection().getUsername(),
tenantRequest.getDatabaseConnection().getPassword())) {
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
JdbcTemplate jdbcTemplate = new JdbcTemplate(tenantDataSource);
String createStatement = "CREATE SCHEMA IF NOT EXISTS \"" + tenantRequest.getDatabaseConnection().getSchema() + "\"";
@ -385,8 +338,8 @@ public class TenantManagementService implements TenantProvider {
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()))) {
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;";
@ -398,51 +351,9 @@ public class TenantManagementService implements TenantProvider {
}
private void createMongoDBDatabase(CreateTenantRequest tenant) {
public void createOrUpdateRealm(String tenantId, List<TenantUser> users) {
MongoDBConnection mongoDBConnection = tenant.getMongoDBConnection();
try (MongoClient mongoClient = MongoClients.create(MongoConnectionStringHelper.buildGenericMongoConnectionString(mongoDBConnection))) {
String databaseName = mongoDBConnection.getDatabase();
String username = mongoDBConnection.getUsername();
String password = mongoDBConnection.getPassword();
MongoDatabase database = mongoClient.getDatabase(databaseName);
BsonDocument createUserCommand = new BsonDocument();
createUserCommand.append("createUser", new BsonString(username));
createUserCommand.append("pwd", new BsonString(password));
BsonArray roles = new BsonArray();
roles.add(new BsonDocument("role", new BsonString("dbOwner")).append("db", new BsonString(databaseName)));
createUserCommand.append("roles", roles);
try {
database.runCommand(createUserCommand);
} catch (MongoCommandException mongoCommandException) {
// ignore user already exists (51003) because of possibly already created users being present
// and command not supported (115) because of azure deployment having a different user management
if (mongoCommandException.getErrorCode() != 51003 && mongoCommandException.getErrorCode() != 115) {
throw mongoCommandException;
}
}
}
}
private void deleteMongoDBDatabase(TenantResponse tenant) {
MongoDBConnection mongoDBConnection = tenant.getMongoDBConnection();
mongoDBConnection.setPassword(encryptionService.decrypt(mongoDBConnection.getPassword()));
try (MongoClient mongoClient = MongoClients.create(MongoConnectionStringHelper.buildGenericMongoConnectionString(mongoDBConnection))) {
String databaseName = mongoDBConnection.getDatabase();
mongoClient.getDatabase(databaseName).drop();
}
}
public void createOrUpdateRealm(String tenantId, TenantApplicationType applicationType, List<TenantUser> users) {
if (syncRealmIfExists(tenantId, applicationType, users)) {
log.info("Updated realm for tenant: {}", tenantId);
if (syncRealmIfExists(tenantId)) {
return;
}
@ -450,22 +361,18 @@ public class TenantManagementService implements TenantProvider {
realm.setId(tenantId);
realm.setRealm(tenantId);
realm.setEnabled(true);
setRealmProperties(realm, applicationType);
setRealmProperties(realm);
var clients = getRealmClients(applicationType);
var clients = getRealmClients();
realm.setClients(clients);
realm.setRoles(getRealmRoles(applicationType));
realm.setRoles(getRealmRoles());
if (users != null) {
realm.setUsers(users.stream()
.map(this::toUserRepresentation)
.toList());
realm.setUsers(users.stream().map(this::toUserRepresentation).toList());
}
keycloak.getAdminClient().realms().create(realm);
log.info("Created realm for tenant: {}", tenantId);
}
@ -473,7 +380,7 @@ public class TenantManagementService implements TenantProvider {
try {
log.info("Deleting existing realms for tenant: {}", tenantId);
getRealmResource(tenantId).remove();
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);
@ -482,61 +389,32 @@ public class TenantManagementService implements TenantProvider {
}
private boolean syncRealmIfExists(String tenantId, TenantApplicationType applicationType, List<TenantUser> users) {
private boolean syncRealmIfExists(String tenantId) {
try {
var existingRealm = getRealmResource(tenantId).toRepresentation();
var existingRealm = keycloak.getAdminClient().realm(tenantId).toRepresentation();
if (existingRealm != null) {
log.info("Updating existing realm: {}", tenantId);
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(applicationType);
existingRealm.setLoginTheme(applicationTypeProperties.getDefaultTheme());
existingRealm.setEmailTheme(applicationTypeProperties.getDefaultTheme());
existingRealm.setAccountTheme(applicationTypeProperties.getDefaultTheme());
existingRealm.setAccessTokenLifespan(applicationTypeProperties.getAccessTokenLifeSpan());
existingRealm.setSsoSessionIdleTimeout(applicationTypeProperties.getSsoSessionIdleTimeout());
var clients = getRealmClients(applicationType);
var relevantClientNames = clients.stream()
.map(c -> c.getClientId().toLowerCase(Locale.getDefault()))
.collect(Collectors.toSet());
var existingClients = getRealmResource(tenantId).clients().findAll();
existingRealm.setLoginTheme(tenantUserManagementProperties.getDefaultTheme());
existingRealm.setEmailTheme(tenantUserManagementProperties.getDefaultTheme());
existingRealm.setAccountTheme(tenantUserManagementProperties.getDefaultTheme());
existingRealm.setAccessTokenLifespan(tenantUserManagementProperties.getAccessTokenLifeSpan());
existingRealm.setSsoSessionIdleTimeout(tenantUserManagementProperties.getSsoSessionIdleTimeout());
var clients = getRealmClients();
var relevantClientNames = clients.stream().map(c -> c.getClientId().toLowerCase(Locale.getDefault())).collect(Collectors.toSet());
var existingClients = keycloak.getAdminClient().realm(tenantId).clients().findAll();
existingClients.forEach(ec -> {
if (relevantClientNames.contains(ec.getClientId().toLowerCase(Locale.getDefault()))) {
log.info("Removing client: {}", ec.getName());
getRealmResource(tenantId).clients()
.get(ec.getId()).remove();
keycloak.getAdminClient().realm(tenantId).clients().get(ec.getId()).remove();
}
});
clients.forEach(c -> getRealmResource(tenantId).clients().create(c));
clients.forEach(c -> keycloak.getAdminClient().realm(tenantId).clients().create(c));
existingRealm.setClients(clients);
existingRealm.setRoles(getRealmRoles(applicationType));
if (users != null) {
var userRepresentationlist = users.stream()
.map(this::toUserRepresentation)
.toList();
if (existingRealm.getUsers() == null) {
existingRealm.setUsers(new ArrayList<>());
}
List<UserRepresentation> toUpdate = userRepresentationlist.stream()
.filter(existingRealm.getUsers()::contains)
.toList();
var toAdd = new ArrayList<>(userRepresentationlist);
toAdd.removeAll(toUpdate);
toAdd.forEach(user -> getRealmResource(tenantId).users().create(user));
toUpdate.forEach(user -> getRealmResource(tenantId).users().searchByUsername(user.getUsername(), true)
.stream()
.findFirst()
.ifPresent(userRepresentation -> {
getRealmResource(tenantId).users()
.get(userRepresentation.getId()).update(user);
}));
existingRealm.getUsers().addAll(toAdd);
}
getRealmResource(tenantId).update(existingRealm);
existingRealm.setRoles(getRealmRoles());
keycloak.getAdminClient().realm(tenantId).update(existingRealm);
return true;
}
} catch (NotFoundException e) {
@ -549,23 +427,16 @@ public class TenantManagementService implements TenantProvider {
}
private RealmResource getRealmResource(String tenantId) {
private RolesRepresentation getRealmRoles() {
return keycloak.getAdminClient().realm(tenantId);
}
private RolesRepresentation getRealmRoles(TenantApplicationType applicationType) {
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(applicationType);
var allRoles = applicationTypeProperties.getKcRoleMapping().getAllRoles();
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
var roles = new ArrayList<RoleRepresentation>();
for (String applicationRole : allRoles) {
var role = new RoleRepresentation();
role.setComposite(true);
role.setName(applicationRole);
role.setContainerId(applicationTypeProperties.getApplicationClientId());
role.setContainerId(tenantUserManagementProperties.getApplicationClientId());
roles.add(role);
}
@ -575,18 +446,16 @@ public class TenantManagementService implements TenantProvider {
}
private List<ClientRepresentation> getRealmClients(TenantApplicationType applicationType) {
ApplicationTypeProperties appTypeProperties = tenantApplicationTypeService.getProperties(applicationType);
private List<ClientRepresentation> getRealmClients() {
var applicationClient = new ClientRepresentation();
applicationClient.setEnabled(true);
applicationClient.setName(appTypeProperties.getApplicationClientId());
applicationClient.setClientId(appTypeProperties.getApplicationClientId());
applicationClient.setName(tenantUserManagementProperties.getApplicationClientId());
applicationClient.setClientId(tenantUserManagementProperties.getApplicationClientId());
applicationClient.setStandardFlowEnabled(true);
applicationClient.setImplicitFlowEnabled(true);
applicationClient.setDirectAccessGrantsEnabled(true);
applicationClient.setRedirectUris(appTypeProperties.getValidRedirectUris());
applicationClient.setRedirectUris(tenantUserManagementProperties.getValidRedirectUris());
applicationClient.setWebOrigins(List.of("+"));
applicationClient.setPublicClient(true);
setPostLogoutRedirectUriForClient(applicationClient);
@ -597,11 +466,11 @@ public class TenantManagementService implements TenantProvider {
swaggerClient.setClientId(tenantUserManagementProperties.getSwaggerClientId());
swaggerClient.setSecret(tenantUserManagementProperties.getSwaggerClientSecret());
swaggerClient.setStandardFlowEnabled(true);
swaggerClient.setImplicitFlowEnabled(true);
swaggerClient.setImplicitFlowEnabled(false);
swaggerClient.setDirectAccessGrantsEnabled(false);
swaggerClient.setServiceAccountsEnabled(true);
swaggerClient.setAuthorizationServicesEnabled(true);
swaggerClient.setRedirectUris(appTypeProperties.getValidRedirectUris());
swaggerClient.setRedirectUris(tenantUserManagementProperties.getValidRedirectUris());
swaggerClient.setWebOrigins(List.of("+"));
setPostLogoutRedirectUriForClient(swaggerClient);
@ -609,16 +478,13 @@ public class TenantManagementService implements TenantProvider {
}
private void setRealmProperties(RealmRepresentation realm, TenantApplicationType tenantApplicationType) {
private void setRealmProperties(RealmRepresentation realm) {
ApplicationTypeProperties currentAppTypeProperties = tenantApplicationTypeService.getProperties(tenantApplicationType);
realm.setLoginTheme(currentAppTypeProperties.getDefaultTheme());
realm.setEmailTheme(currentAppTypeProperties.getDefaultTheme());
realm.setAccountTheme(currentAppTypeProperties.getDefaultTheme());
realm.setAccessTokenLifespan(currentAppTypeProperties.getAccessTokenLifeSpan());
realm.setSsoSessionIdleTimeout(currentAppTypeProperties.getSsoSessionIdleTimeout());
realm.setRevokeRefreshToken(true);
realm.setRefreshTokenMaxReuse(currentAppTypeProperties.getRefreshTokenMaxReuse());
realm.setLoginTheme(tenantUserManagementProperties.getDefaultTheme());
realm.setEmailTheme(tenantUserManagementProperties.getDefaultTheme());
realm.setAccountTheme(tenantUserManagementProperties.getDefaultTheme());
realm.setAccessTokenLifespan(tenantUserManagementProperties.getAccessTokenLifeSpan());
realm.setSsoSessionIdleTimeout(tenantUserManagementProperties.getSsoSessionIdleTimeout());
if (!ObjectUtils.isEmpty(tenantUserManagementProperties.getPublicServerUrl())) {
Map<String, String> attributes = new HashMap<>();
@ -650,9 +516,7 @@ public class TenantManagementService implements TenantProvider {
private boolean tryToAccessRealm(String tenantId) {
try {
return keycloak.getAdminClient().realms().findAll()
.stream()
.anyMatch(r -> r.getRealm().equals(tenantId));
return keycloak.getAdminClient().realms().findAll().stream().anyMatch(r -> r.getRealm().equals(tenantId));
} catch (Exception e) {
return false;
}
@ -673,7 +537,7 @@ public class TenantManagementService implements TenantProvider {
user.setLastName(redUser.getLastName());
user.setEmailVerified(true);
var roles = new ArrayList<>(redUser.getRoles() != null ? redUser.getRoles() : new ArrayList<>());
var roles = new ArrayList<String>(redUser.getRoles() != null ? redUser.getRoles() : new ArrayList<>());
roles.add("uma_authorization");
roles.add("offline_access");
@ -708,14 +572,11 @@ public class TenantManagementService implements TenantProvider {
public TenantResponse getTenant(String tenantId) {
return tenantRepository.findById(tenantId)
.map(this::convert)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist"));
return tenantRepository.findById(tenantId).map(this::convert).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist"));
}
@SneakyThrows
public TenantResponse updateTenant(String tenantId, UpdateTenantRequest tenantRequest) {
public TenantResponse updateTenant(String tenantId, TenantRequest tenantRequest) {
if (tenantRequest.getS3StorageConnection() != null && tenantRequest.getAzureStorageConnection() != null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Not possible to set both azure and s3 connection, please only specify one");
@ -730,29 +591,29 @@ public class TenantManagementService implements TenantProvider {
var databaseConnection = tenantRequest.getDatabaseConnection();
if (databaseConnection != null) {
tenantEntity.setDatabaseConnection(DatabaseConnectionEntity.builder()
.driver(databaseConnection.getDriver())
.host(databaseConnection.getHost())
.port(databaseConnection.getPort())
.database(databaseConnection.getDatabase())
.schema(databaseConnection.getSchema())
.username(databaseConnection.getUsername())
.password(encryptionService.encrypt(databaseConnection.getPassword()))
.params(databaseConnection.getParams())
.build());
.driver(databaseConnection.getDriver())
.host(databaseConnection.getHost())
.port(databaseConnection.getPort())
.database(databaseConnection.getDatabase())
.schema(databaseConnection.getSchema())
.username(databaseConnection.getUsername())
.password(encryptionService.encrypt(databaseConnection.getPassword()))
.params(databaseConnection.getParams())
.build());
}
var searchConnection = tenantRequest.getSearchConnection();
if (searchConnection != null) {
tenantEntity.setSearchConnection(SearchConnectionEntity.builder()
.hosts(searchConnection.getHosts())
.port(searchConnection.getPort())
.scheme(searchConnection.getScheme())
.username(searchConnection.getUsername())
.password(encryptionService.encrypt(searchConnection.getPassword()))
.numberOfShards(searchConnection.getNumberOfShards())
.numberOfReplicas(searchConnection.getNumberOfReplicas())
.indexPrefix(tenantEntity.getSearchConnection().getIndexPrefix())
.build());
.hosts(searchConnection.getHosts())
.port(searchConnection.getPort())
.scheme(searchConnection.getScheme())
.username(searchConnection.getUsername())
.password(encryptionService.encrypt(searchConnection.getPassword()))
.numberOfShards(searchConnection.getNumberOfShards())
.numberOfReplicas(searchConnection.getNumberOfReplicas())
.indexPrefix(tenantEntity.getSearchConnection().getIndexPrefix())
.build());
}
var azureStorageConnection = tenantRequest.getAzureStorageConnection();
@ -762,9 +623,9 @@ public class TenantManagementService implements TenantProvider {
}
testAzureConnection(azureStorageConnection.getConnectionString(), azureStorageConnection.getContainerName());
tenantEntity.setAzureStorageConnection(AzureStorageConnectionEntity.builder()
.connectionString(encryptionService.encrypt(azureStorageConnection.getConnectionString()))
.containerName(azureStorageConnection.getContainerName())
.build());
.connectionString(encryptionService.encrypt(azureStorageConnection.getConnectionString()))
.containerName(azureStorageConnection.getContainerName())
.build());
} else {
tenantEntity.setAzureStorageConnection(null);
}
@ -776,43 +637,18 @@ public class TenantManagementService implements TenantProvider {
}
testS3Connection(s3StorageConnection);
tenantEntity.setS3StorageConnection(S3StorageConnectionEntity.builder()
.key(s3StorageConnection.getKey())
.secret(encryptionService.encrypt(s3StorageConnection.getSecret()))
.signerType(s3StorageConnection.getSignerType())
.bucketName(s3StorageConnection.getBucketName())
.region(s3StorageConnection.getRegion())
.endpoint(s3StorageConnection.getEndpoint())
.build());
.key(s3StorageConnection.getKey())
.secret(encryptionService.encrypt(s3StorageConnection.getSecret()))
.signerType(s3StorageConnection.getSignerType())
.bucketName(s3StorageConnection.getBucketName())
.region(s3StorageConnection.getRegion())
.endpoint(s3StorageConnection.getEndpoint())
.build());
} else {
tenantEntity.setS3StorageConnection(null);
}
var mongoDBConnection = tenantRequest.getMongoDBConnection();
if (mongoDBConnection != null) {
tenantEntity.setMongoDBConnection(MongoDBConnectionEntity.builder()
.prefix(mongoDBConnection.getPrefix())
.username(mongoDBConnection.getUsername())
.password(encryptionService.encrypt(mongoDBConnection.getPassword()))
.address(mongoDBConnection.getAddress())
.database(mongoDBConnection.getDatabase())
.options(mongoDBConnection.getOptions())
.build());
}
propagateTenantToKeyCloak(tenantId, tenantEntity.getApplicationType(), null);
TenantResponse tenantResponse = convert(tenantRepository.save(tenantEntity));
log.info("Persisted tenant update: {}", tenantId);
TenantContext.setTenantId(tenantEntity.getTenantId());
rabbitTemplate.convertAndSend(tenantExchangeName, "tenant.updated", tenantResponse);
TenantContext.clear();
log.info("Dispatched message for tenant: {}", tenantId);
return tenantResponse;
return convert(tenantRepository.save(tenantEntity));
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist");
}
@ -821,17 +657,7 @@ public class TenantManagementService implements TenantProvider {
public List<TenantResponse> getTenants() {
return tenantRepository.findAll()
.stream()
.map(this::convert)
.toList();
}
@Override
public TenantApplicationType getTenantApplicationType(String tenantId) {
return tenantApplicationTypeService.get(tenantId);
return tenantRepository.findAll().stream().map(this::convert).toList();
}
@ -853,10 +679,6 @@ public class TenantManagementService implements TenantProvider {
tenantResponse.getS3StorageConnection().setSecret(PASSWORD);
}
if (tenantResponse.getMongoDBConnection() != null) {
tenantResponse.getMongoDBConnection().setPassword(PASSWORD);
}
return tenantResponse;
}
@ -864,68 +686,57 @@ public class TenantManagementService implements TenantProvider {
private TenantResponse convert(TenantEntity entity) {
var authDetails = realmService.getOpenIdConnectDetails(entity.getTenantId());
var roleMapping = tenantApplicationTypeService.getProperties(entity.getApplicationType()).getKcRoleMapping();
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
authDetails.setClientRoles(roleMapping.getPermissions());
authDetails.setRealmRoles(roleMapping.getAllRoles());
var tenantResponseBuilder = TenantResponse.builder()
var tenantResponse = TenantResponse.builder()
.tenantId(entity.getTenantId())
.displayName(entity.getDisplayName())
.guid(entity.getGuid())
.authDetails(authDetails)
.details(entity.getDetails())
.applicationType(entity.getApplicationType())
.databaseConnection(DatabaseConnection.builder()
.driver(entity.getDatabaseConnection().getDriver())
.host(entity.getDatabaseConnection().getHost())
.port(entity.getDatabaseConnection().getPort())
.database(entity.getDatabaseConnection().getDatabase())
.schema(entity.getDatabaseConnection().getSchema())
.username(entity.getDatabaseConnection().getUsername())
.params(entity.getDatabaseConnection().getParams())
.password(entity.getDatabaseConnection().getPassword())
.build())
.driver(entity.getDatabaseConnection().getDriver())
.host(entity.getDatabaseConnection().getHost())
.port(entity.getDatabaseConnection().getPort())
.database(entity.getDatabaseConnection().getDatabase())
.schema(entity.getDatabaseConnection().getSchema())
.username(entity.getDatabaseConnection().getUsername())
.params(entity.getDatabaseConnection().getParams())
.password(entity.getDatabaseConnection().getPassword())
.build())
.searchConnection(SearchConnection.builder()
.hosts(entity.getSearchConnection().getHosts())
.port(entity.getSearchConnection().getPort())
.scheme(entity.getSearchConnection().getScheme())
.username(entity.getSearchConnection().getUsername())
.numberOfShards(entity.getSearchConnection().getNumberOfShards())
.numberOfReplicas(entity.getSearchConnection().getNumberOfReplicas())
.password(entity.getSearchConnection().getPassword())
.indexPrefix(entity.getSearchConnection().getIndexPrefix())
.build());
if (entity.getMongoDBConnection() != null) {
tenantResponseBuilder.mongoDBConnection(MongoDBConnection.builder()
.prefix(entity.getMongoDBConnection().getPrefix())
.username(entity.getMongoDBConnection().getUsername())
.password(entity.getMongoDBConnection().getPassword())
.address(entity.getMongoDBConnection().getAddress())
.database(entity.getMongoDBConnection().getDatabase())
.options(entity.getMongoDBConnection().getOptions())
.build());
}
.hosts(entity.getSearchConnection().getHosts())
.port(entity.getSearchConnection().getPort())
.scheme(entity.getSearchConnection().getScheme())
.username(entity.getSearchConnection().getUsername())
.numberOfShards(entity.getSearchConnection().getNumberOfShards())
.numberOfReplicas(entity.getSearchConnection().getNumberOfReplicas())
.password(entity.getSearchConnection().getPassword())
.indexPrefix(entity.getSearchConnection().getIndexPrefix())
.build())
.build();
if (entity.getAzureStorageConnection() != null) {
tenantResponseBuilder.azureStorageConnection(AzureStorageConnection.builder()
.connectionString(entity.getAzureStorageConnection().getConnectionString())
.containerName(entity.getAzureStorageConnection().getContainerName())
.build());
tenantResponse.setAzureStorageConnection(AzureStorageConnection.builder()
.connectionString(entity.getAzureStorageConnection().getConnectionString())
.containerName(entity.getAzureStorageConnection().getContainerName())
.build());
}
if (entity.getS3StorageConnection() != null) {
tenantResponseBuilder.s3StorageConnection(S3StorageConnection.builder()
.key(entity.getS3StorageConnection().getKey())
.secret(entity.getS3StorageConnection().getSecret())
.signerType(entity.getS3StorageConnection().getSignerType())
.bucketName(entity.getS3StorageConnection().getBucketName())
.region(entity.getS3StorageConnection().getRegion())
.endpoint(entity.getS3StorageConnection().getEndpoint())
.build());
tenantResponse.setS3StorageConnection(S3StorageConnection.builder()
.key(entity.getS3StorageConnection().getKey())
.secret(entity.getS3StorageConnection().getSecret())
.signerType(entity.getS3StorageConnection().getSignerType())
.bucketName(entity.getS3StorageConnection().getBucketName())
.region(entity.getS3StorageConnection().getRegion())
.endpoint(entity.getS3StorageConnection().getEndpoint())
.build());
}
return tenantResponseBuilder.build();
return tenantResponse;
}
@ -943,11 +754,11 @@ public class TenantManagementService implements TenantProvider {
var connection = storageConfiguration.getS3StorageService()
.testConnection(s3StorageConnection.getKey(),
s3StorageConnection.getSecret(),
s3StorageConnection.getSignerType(),
s3StorageConnection.getBucketName(),
s3StorageConnection.getRegion(),
s3StorageConnection.getEndpoint());
s3StorageConnection.getSecret(),
s3StorageConnection.getSignerType(),
s3StorageConnection.getBucketName(),
s3StorageConnection.getRegion(),
s3StorageConnection.getEndpoint());
if (!connection) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not connect to S3 storage");
@ -969,15 +780,14 @@ public class TenantManagementService implements TenantProvider {
public void syncTenant(String tenantId, JsonNode payload) {
log.info("Syncing Realm: {}", tenantId);
TenantContext.setTenantId(tenantId);
TenantApplicationType tenantApplicationType = getTenantApplicationType(tenantId);
syncRealmIfExists(tenantId, tenantApplicationType, null);
syncRealmIfExists(tenantId);
setPasswordPolicyForRealm(tenantId);
generalConfigurationService.initGeneralConfiguration(tenantId, tenantApplicationType);
keyCloakRoleManagerService.updateRoles(tenantId, tenantApplicationType);
propagateTenantToKeyCloak(tenantId, tenantApplicationType, null);
generalConfigurationService.initGeneralConfiguration(tenantId);
keyCloakRoleManagerService.updateRoles(tenantId);
propagateTenantToKeyCloak(tenantId, null);
log.info("Realm: {} synced", tenantId);
TenantContext.setTenantId(tenantId);
rabbitTemplate.convertAndSend(tenantExchangeName, "tenant.sync", new TenantSyncEvent(tenantId, payload));
TenantContext.clear();

View File

@ -14,6 +14,7 @@ import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantusermanagement.model.User;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -25,7 +26,7 @@ public class UserListingService {
private final RealmService realmService;
private final TenantApplicationTypeService tenantApplicationTypeService;
private final TenantUserManagementProperties tenantUserManagementProperties;
private final RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).exponentialBackoff(1000, 2, 5000).build();
@ -40,7 +41,7 @@ public class UserListingService {
Map<String, Set<String>> usersByRole = new HashMap<>();
if (!allUsers.isEmpty()) {
var realmRoles = realm.roles().list().stream().map(r -> r.getName().toUpperCase(Locale.ROOT)).collect(Collectors.toSet());
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
for (var role : allRoles) {
if (realmRoles.contains(role)) {
List<UserRepresentation> users = realm.roles().get(role).getUserMembers(0, 500);
@ -70,7 +71,7 @@ public class UserListingService {
users.add(user);
}
var roleComposites = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getRoleComposites();
var roleComposites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
users.forEach(user -> {
for (var parentRole : roleComposites.keySet()) {
if (user.getRoles().contains(parentRole)) {

View File

@ -8,6 +8,11 @@ import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.apache.commons.validator.routines.EmailValidator;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.internal.ResteasyClientBuilderImpl;
@ -18,6 +23,7 @@ import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
@ -39,14 +45,9 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.User;
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import io.micrometer.common.util.StringUtils;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -59,7 +60,6 @@ public class UserService {
private final TenantUserManagementProperties tenantUserManagementProperties;
private final UserListingService userListingService;
private final RabbitTemplate rabbitTemplate;
private final TenantApplicationTypeService tenantApplicationTypeService;
@Value("${fforesight.user-exchange.name}")
private String userExchangeName;
@ -80,14 +80,14 @@ public class UserService {
String username = StringUtils.isEmpty(user.getUsername()) ? user.getEmail() : user.getUsername();
if (!this.getTenantUsersResource().search(username, true).isEmpty() || !this.getTenantUsersResource().searchByEmail(user.getEmail(), true).isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Requested username or email address not available");
throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this username or email address already exists");
}
if (!EmailValidator.getInstance().isValid(user.getEmail())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email address format is not valid");
throw new ResponseStatusException(HttpStatus.CONFLICT, "Email address format is not valid");
}
tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().validateRoles(user.getRoles());
tenantUserManagementProperties.getKcRoleMapping().validateRoles(user.getRoles());
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setUsername(username);
@ -102,7 +102,7 @@ public class UserService {
if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
if (response.getStatusInfo().getStatusCode() == 409) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, response.getStatusInfo().getReasonPhrase());
throw new ResponseStatusException(HttpStatus.CONFLICT, response.getStatusInfo().getReasonPhrase());
}
if (response.getStatusInfo().getStatusCode() == 400) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, response.getStatusInfo().getReasonPhrase());
@ -112,12 +112,10 @@ public class UserService {
var createdUser = getUserByUsername(username);
if (user.isSendSetPasswordMail()) {
try {
sendResetPasswordEmail(createdUser.getUserId());
} catch (Exception e) {
log.debug("Set Password E-mail could not be sent!", e);
}
try {
sendResetPasswordEmail(createdUser.getUserId());
} catch (Exception e) {
log.debug("Activation E-mail could not be sent!", e);
}
this.rabbitTemplate.convertAndSend(userExchangeName, "user.created", new UserCreatedEvent(createdUser, KeycloakSecurity.getUserId()));
@ -131,20 +129,12 @@ public class UserService {
}
public void checkRankOrderForAssigningRole(Set<String> newRoles, Set<String> currentUserRoles) {
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
var maxRank = currentUserRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
var newRolesRank = newRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.toList();
var maxNewRolesRank = newRolesRank.stream()
.max(Integer::compare)
.orElse(-1);
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
var maxRank = currentUserRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList();
var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1);
if (maxNewRolesRank > maxRank) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights");
@ -155,10 +145,12 @@ public class UserService {
public Set<String> getUserRoles(String userId) {
var userResource = getUserResource(userId);
return userResource.roles().realmLevel().listEffective()
return userResource.roles()
.realmLevel()
.listEffective()
.stream()
.map(RoleRepresentation::getName)
.filter(r -> tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().isValidRole(r))
.filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r))
.collect(Collectors.toSet());
}
@ -168,138 +160,64 @@ public class UserService {
var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId());
Set<String> oldRoles = getRoles(userId);
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && !oldRoles.isEmpty() && oldRoles.stream()
.allMatch(ApplicationRoles::isKneconRole)) {
throw new NotFoundException("User with id: " + userId + " does not exist");
}
return setRoles(userId, roles, currentUserRoles);
}
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
public User setRoles(String userId, Set<String> newRoles, Set<String> currentUserRoles) {
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
newRoles.forEach(role -> {
if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
}
});
var userResource = getUserResource(userId);
var oldRoles = userResource.roles().realmLevel().listEffective()
.stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
validateSufficientRoles(userId, oldRoles, newRoles, currentUserRoles);
var currentRolesAsRoleRepresentation = allRoles.stream()
.map(this::getRoleRepresentation)
.collect(Collectors.toList());
var newMappedRoles = newRoles.stream()
.map(this::getRoleRepresentation)
.collect(Collectors.toList());
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
userResource.roles().realmLevel().add(newMappedRoles);
var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername());
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, oldRoles, newRoles, KeycloakSecurity.getUserId())));
var userWithNewRoles = setRoles(userId, roles, currentUserRoles);
return userWithNewRoles;
}
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
public void removeRolesForDeletion(String userId, Set<String> roles) {
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
roles.forEach(role -> {
public User setRoles(String userId, Set<String> newRoles, Set<String> currentUserRoles) {
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
newRoles.forEach(role -> {
if (!allRoles.contains(role)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
}
});
var userResource = getUserResource(userId);
var userRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet());
var currentRolesAsRoleRepresentation = roles.stream()
.map(this::getRoleRepresentation)
.collect(Collectors.toList());
validateSufficientRoles(userId, userRoles, newRoles, currentUserRoles);
var currentRolesAsRoleRepresentation = allRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList());
var newMappedRoles = newRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList());
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
userResource.roles().realmLevel().add(newMappedRoles);
var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername());
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId())));
return userWithNewRoles;
}
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
public void validateSufficientRoles(String userId, Set<String> userRoles, Set<String> newRoles, Set<String> currentUserRoles) {
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
var maxRank = currentUserRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList();
var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1);
int maxCurrentUserRank = currentUserRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
Set<String> untouchableRoles = userRoles.stream()
var untouchableRoles = userRoles.stream()
.filter(roleMapping::isValidRole)
.map(roleMapping::getRole)
.filter(r -> r.getRank() > maxCurrentUserRank && !ApplicationRoles.isKneconRole(r.getName()))
.filter(r -> r.getRank() > maxRank)
.map(KCRole::getName)
.collect(Collectors.toSet());
Set<String> kneconRoles = userRoles.stream()
.filter(roleMapping::isValidRole)
.map(roleMapping::getRole)
.map(KCRole::getName)
.filter(ApplicationRoles::isKneconRole)
.collect(Collectors.toSet());
int maxNewRolesRank = newRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
newRoles.addAll(kneconRoles);
int maxNewRolesRankIncludingKnecon = newRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
ensureNoHigherRankAssigned(maxCurrentUserRank, maxNewRolesRank);
ensureUntouchableRolesPreserved(untouchableRoles, newRoles);
ensureHighestRankNotRemovedFromSelf(userId, maxCurrentUserRank, maxNewRolesRankIncludingKnecon, roleMapping.getMaxRank());
}
private void ensureNoHigherRankAssigned(int maxCurrentUserRank, int maxNewRolesRank) {
if (maxNewRolesRank > maxCurrentUserRank) {
if (maxNewRolesRank > maxRank) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights");
}
}
private void ensureUntouchableRolesPreserved(Set<String> untouchableRoles, Set<String> newRoles) {
if (!newRoles.containsAll(untouchableRoles)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights");
}
}
private void ensureHighestRankNotRemovedFromSelf(String userId, int maxCurrentUserRank, int maxNewRolesRankIncludingKnecon, int overallMaxRank) {
boolean isSelf = userId.equalsIgnoreCase(KeycloakSecurity.getUserId());
boolean isUserHighestRank = maxCurrentUserRank == overallMaxRank;
boolean highestRankRemoved = !Integer.valueOf(maxNewRolesRankIncludingKnecon).equals(maxCurrentUserRank);
if (isSelf && isUserHighestRank && highestRankRemoved) {
if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
}
}
@ -307,10 +225,7 @@ public class UserService {
public Optional<User> getUserById(String userId) {
return userListingService.getAllUsers(TenantContext.getTenantId())
.stream()
.filter(u -> u.getUserId().equalsIgnoreCase(userId))
.findAny();
return userListingService.getAllUsers(TenantContext.getTenantId()).stream().filter(u -> u.getUserId().equalsIgnoreCase(userId)).findAny();
}
@ -338,16 +253,12 @@ public class UserService {
user.update(userRepresentation);
} catch (ClientErrorException e) {
if (e.getResponse().getStatus() == 409) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "E-mail is not available");
throw new ResponseStatusException(HttpStatus.CONFLICT, "E-mail already in use");
}
throw e;
}
var updatedProfile = getUserByUsername(userRepresentation.getUsername());
updatedProfile.setRoles(updatedProfile.getRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet()));
this.rabbitTemplate.convertAndSend(userExchangeName, "user.ownProfileUpdated", (new UserUpdatedOwnProfileEvent(updatedProfile)));
@ -361,8 +272,7 @@ public class UserService {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No id provided.");
}
try {
return this.getTenantUsersResource()
.get(userId);
return this.getTenantUsersResource().get(userId);
} catch (NotFoundException e) {
throw new NotFoundException("User with id: " + userId + " does not exist", e);
}
@ -381,13 +291,13 @@ public class UserService {
.realm(TenantContext.getTenantId())
.username(username)
.password(password)
.clientId(tenantApplicationTypeService.getCurrentProperties().getApplicationClientId())
.clientId(tenantUserManagementProperties.getApplicationClientId())
.grantType(OAuth2Constants.PASSWORD)
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
.disableTrustManager()
.build())
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
.disableTrustManager()
.build())
.build();
try {
@ -413,7 +323,7 @@ public class UserService {
var userList = this.getTenantUsersResource().search(username, true);
if (userList.isEmpty()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this username already exists");
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User with this username already exists");
}
return convert(userList.iterator().next());
@ -442,7 +352,7 @@ public class UserService {
var user = userListingService.convertBasicUser(userRepresentation);
user.setRoles(getRoles(user.getUserId()));
var roleComposites = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getRoleComposites();
var roleComposites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
for (var parentRole : roleComposites.keySet()) {
if (user.getRoles().contains(parentRole)) {
user.getRoles().addAll(roleComposites.get(parentRole));
@ -454,17 +364,13 @@ public class UserService {
private Set<String> getRoles(String id) {
List<RoleRepresentation> realmMappings = this.getTenantUsersResource()
.get(id).roles().getAll().getRealmMappings();
List<RoleRepresentation> realmMappings = this.getTenantUsersResource().get(id).roles().getAll().getRealmMappings();
if (realmMappings == null) {
log.warn("User with id=" + id + " contains null role mappings.");
return new TreeSet<>();
}
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
return realmMappings.stream()
.map(RoleRepresentation::getName)
.filter(allRoles::contains)
.collect(Collectors.toSet());
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
return realmMappings.stream().map(RoleRepresentation::getName).filter(allRoles::contains).collect(Collectors.toSet());
}
@ -479,25 +385,16 @@ public class UserService {
return;
}
var status = validateExecutionForDeletion(KeycloakSecurity.getUserId(), userId);
var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId);
if (status.equals(ValidationResult.FORBIDDEN)) {
if (!executionValid) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles");
} else if (status.equals(ValidationResult.INVALID)) {
return;
}
var userResource = getUserResource(userId);
var userToBeRemoved = getUserByUsername(userResource.toRepresentation().getUsername());
if (status.equals(ValidationResult.ROLE_REMOVAL)) {
removeRolesForDeletion(userId,
getRoles(userId).stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet()));
} else {
userResource.remove();
}
var userToBeRemoved = getUserByUsername(userResource.toRepresentation().getUsername());
userResource.remove();
this.rabbitTemplate.convertAndSend(userExchangeName, "user.deleted", (new UserRemovedEvent(userToBeRemoved, KeycloakSecurity.getUserId())));
@ -510,12 +407,6 @@ public class UserService {
var user = this.getUserResource(userId);
var userRepresentation = user.toRepresentation();
Set<String> currentRoles = getRoles(userId);
if (!userExists(userId) || !currentRoles.isEmpty() && currentRoles.stream()
.allMatch(ApplicationRoles::isKneconRole)) {
throw new NotFoundException("User with id: " + userId + " does not exist");
}
if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail()
.equals(userRepresentation.getEmail())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to change the email from a federated identity");
@ -535,10 +426,6 @@ public class UserService {
setRoles(userId, updateProfileRequest.getRoles());
var updatedUser = getUserByUsername(userRepresentation.getUsername());
updatedUser.setRoles(updatedUser.getRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet()));
this.rabbitTemplate.convertAndSend(userExchangeName, "user.updated", (new UserUpdatedEvent(updatedUser, KeycloakSecurity.getUserId())));
@ -548,14 +435,10 @@ public class UserService {
public User activateProfile(String userId, boolean isActive) {
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId);
if (status.equals(ValidationResult.FORBIDDEN)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles");
} else if (status.equals(ValidationResult.INVALID)) {
throw new NotFoundException("User with id: " + userId + " does not exist");
}
if (!executionValid) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles");
}
var user = this.getUserResource(userId);
@ -567,31 +450,23 @@ public class UserService {
var currentRoles = getRoles(userId);
if (isActive && currentRoles.isEmpty()) { // add RED_USER role
setRoles(userId, tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getDefaultRoles());
setRoles(userId, tenantUserManagementProperties.getKcRoleMapping().getDefaultRoles());
}
var toggledUser = getUserByUsername(userRepresentation.getUsername());
toggledUser.setRoles(toggledUser.getRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet()));
this.rabbitTemplate.convertAndSend(userExchangeName, "user.statusChanged", (new UserStatusToggleEvent(toggledUser, KeycloakSecurity.getUserId())));
return toggledUser;
return convert(this.getTenantUsersResource().get(userId).toRepresentation());
}
public void resetPassword(String userId, ResetPasswordRequest resetPasswordRequest) {
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId);
if (status.equals(ValidationResult.FORBIDDEN)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles");
} else if (status.equals(ValidationResult.INVALID)) {
throw new NotFoundException("User with id: " + userId + " does not exist");
}
if (!executionValid) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles");
}
try {
@ -599,8 +474,7 @@ public class UserService {
request.setType(CredentialRepresentation.PASSWORD);
request.setTemporary(resetPasswordRequest.isTemporary());
request.setValue(resetPasswordRequest.getPassword());
realmService.realm(TenantContext.getTenantId()).users()
.get(userId).resetPassword(request);
realmService.realm(TenantContext.getTenantId()).users().get(userId).resetPassword(request);
log.info("User {} resetted password for user {}", KeycloakSecurity.getUserId(), userId);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not reset password. It does not match the password policy.", e);
@ -618,8 +492,7 @@ public class UserService {
RoleRepresentation realmRole;
try {
realmRole = realmService.realm(TenantContext.getTenantId()).roles()
.get(role).toRepresentation();
realmRole = realmService.realm(TenantContext.getTenantId()).roles().get(role).toRepresentation();
} catch (NotFoundException e) {
log.warn("The realm role {} is not found.", role);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "The realm role " + role + " is not found.", e);
@ -631,74 +504,26 @@ public class UserService {
private void sendResetPasswordEmail(String userId) {
try {
this.getTenantUsersResource()
.get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
this.getTenantUsersResource().get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to send email.", e);
}
}
private enum ValidationResult {
ALLOWED,
FORBIDDEN,
INVALID,
ROLE_REMOVAL
}
private ValidationResult validateExecution(String executingUserId, String targetUserId) {
private boolean isExecutionRankValid(String executingUserId, String targetUserId) {
var currentUserResource = getUserResource(executingUserId);
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
var userRoles = getRoles(targetUserId);
return validateRoleRanks(currentRoles, userRoles);
}
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
var maxRank = currentRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
var targetRank = userRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
return targetRank <= maxRank;
private ValidationResult validateExecutionForDeletion(String executingUserId, String targetUserId) {
var currentUserResource = getUserResource(executingUserId);
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
var userRoles = getRoles(targetUserId);
ValidationResult validationResult = validateRoleRanks(currentRoles, userRoles);
if (validationResult == ValidationResult.ALLOWED) {
if (userRoles.stream()
.anyMatch(ApplicationRoles::isKneconRole) && userRoles.stream()
.anyMatch(ApplicationRoles::isNoKneconRole)) {
return ValidationResult.ROLE_REMOVAL;
}
}
return validationResult;
}
private ValidationResult validateRoleRanks(Set<String> currentRoles, Set<String> userRoles) {
if (!userRoles.isEmpty() && userRoles.stream()
.allMatch(ApplicationRoles::isKneconRole)) {
return ValidationResult.INVALID;
}
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
var maxRank = currentRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
var targetRank = userRoles.stream()
.filter(ApplicationRoles::isNoKneconRole)
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
if (targetRank <= maxRank) {
return ValidationResult.ALLOWED;
} else {
return ValidationResult.FORBIDDEN;
}
}
}

View File

@ -1,60 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.utils;
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import lombok.experimental.UtilityClass;
@UtilityClass
public final class SMTPConfigurationMapper {
public static GlobalSMTPConfigurationEntity toEntity(SMTPConfiguration smtpConfig) {
if (smtpConfig == null) {
return null;
}
return GlobalSMTPConfigurationEntity.builder()
.id("singleton")
.auth(smtpConfig.isAuth())
.envelopeFrom(smtpConfig.getEnvelopeFrom())
.fromEmail(smtpConfig.getFrom())
.fromDisplayName(smtpConfig.getFromDisplayName())
.host(smtpConfig.getHost())
.password(smtpConfig.getPassword())
.port(smtpConfig.getPort())
.replyTo(smtpConfig.getReplyTo())
.replyToDisplayName(smtpConfig.getReplyToDisplayName())
.ssl(smtpConfig.isSsl())
.starttls(smtpConfig.isStarttls())
.userName(smtpConfig.getUser())
.build();
}
public static SMTPConfiguration toModel(GlobalSMTPConfigurationEntity entity) {
if (entity == null) {
return null;
}
SMTPConfiguration smtpConfig = new SMTPConfiguration();
smtpConfig.setId(entity.getId());
smtpConfig.setAuth(entity.getAuth() != null && entity.getAuth());
smtpConfig.setEnvelopeFrom(entity.getEnvelopeFrom());
smtpConfig.setFrom(entity.getFromEmail());
smtpConfig.setFromDisplayName(entity.getFromDisplayName());
smtpConfig.setHost(entity.getHost());
smtpConfig.setPassword(entity.getPassword());
smtpConfig.setPort(entity.getPort());
smtpConfig.setReplyTo(entity.getReplyTo());
smtpConfig.setReplyToDisplayName(entity.getReplyToDisplayName());
smtpConfig.setSsl(entity.getSsl() != null && entity.getSsl());
smtpConfig.setStarttls(entity.getStarttls() != null && entity.getStarttls());
smtpConfig.setUser(entity.getUserName());
return smtpConfig;
}
}

View File

@ -0,0 +1,89 @@
fforesight:
tenant-user-management:
application-client-id: 'fforesight'
application-name: 'Clarifynd'
client-id: 'manager'
accessTokenLifeSpan: 3600
ssoSessionIdleTimeout: 86400
realm: master
default-theme: 'clarifynd'
valid-redirect-uris: [ '/search/*', '/bdr-connector/*', '/tenant-user-management/*','http://localhost:4200/*', 'http://localhost:4300/*', '/ui/*' ,'/auth/*','/persistence/*', '/audit/*', '/contextual-query/*' ]
kc-role-mapping:
roles:
- name: FF_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-update-my-profile'
- 'fforesight-get-tenants'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-search'
- 'fforesight-view-document'
- 'fforesight-user-upload-files'
- 'fforesight-user-manage-files'
- 'fforesight-user-view-files'
- 'fforesight-user-manage-analysis'
- 'fforesight-user-view-analysis'
- 'fforesight-download-file'
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions:
- "red-read-license"
- "red-update-license"
- "fforesight-get-tenants"
- "fforesight-create-tenant"
- "fforesight-update-tenant"
- "fforesight-delete-tenant"
- "fforesight-read-users"
- "fforesight-read-all-users"
- "fforesight-write-users"
- "fforesight-read-smtp-configuration"
- "fforesight-write-smtp-configuration"
- "fforesight-read-identity-provider-config"
- "fforesight-write-identity-provider-config"
- "red-unarchive-dossier"
- name: FF_ADMIN
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-update-tenant'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- "fforesight-read-identity-provider-config"
- "fforesight-write-identity-provider-config"
- 'fforesight-search'
- 'fforesight-search-audit-log'
- 'fforesight-view-document'
- 'fforesight-user-upload-files'
- 'fforesight-user-manage-files'
- 'fforesight-user-view-files'
- 'fforesight-user-manage-analysis'
- 'fforesight-user-view-analysis'
- 'fforesight-system-upload-files'
- 'fforesight-system-manage-files'
- 'fforesight-system-view-files'
- 'fforesight-system-manage-analysis'
- 'fforesight-system-view-analysis'
- 'fforesight-download-file'
- 'taas-bdr-connector-view-tasks'
- 'taas-bdr-connector-start-task'
- 'taas-bdr-connector-stop-task'
springdoc:
default-tenant: 'fforesight'

View File

@ -0,0 +1,53 @@
server:
port: 8091
fforesight:
tenant-user-management:
server-url: http://localhost:8080
client-secret: p2InUtjQUDSlwsXyEUFuYrSWi1BeZD1P
client-id: manager
realm: master
kc-role-mapping:
roles:
- name: SUPER_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-delete-tenant'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
application-name: "redaction"
springdoc:
auth-server-url: http://localhost:8080
dev.tenant.storage:
mode: 'S3'
s3:
key: minioadmin
secret: minioadmin
bucket: redaction
endpoint: http://localhost:9000
dev.tenant.db:
port: 5432
host: localhost
database: master
schema: public
username: fforesight
password: fforesight
cors.enabled: true

View File

@ -1,93 +0,0 @@
server:
port: 8091
fforesight:
tenant-user-management:
server-url: http://localhost:8080
client-secret: p2InUtjQUDSlwsXyEUFuYrSWi1BeZD1P
client-id: manager
realm: master
application-types:
redactmanager:
kc-role-mapping:
roles:
- name: SUPER_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-delete-tenant'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: KNECON_ADMIN
set-by-default: true
rank: 1000
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: KNECON_SUPPORT
set-by-default: true
rank: 1000
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
application-name: "redaction"
springdoc:
auth-server-url: http://localhost:8080
dev.tenant.storage:
mode: 'S3'
s3:
key: minioadmin
secret: minioadmin
bucket: redaction
endpoint: http://localhost:9000
dev.tenant.db:
port: 5432
host: localhost
database: master
schema: public
username: fforesight
password: fforesight
cors.enabled: true

View File

@ -0,0 +1,57 @@
fforesight:
tenant-user-management:
application-client-id: 'redaction'
application-name: 'Documine'
client-id: 'manager'
tenant-access-token-life-span: 300
realm: master
default-theme: 'scm'
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
kc-role-mapping:
unmappedPermissions: [ "red-get-tables", "red-unarchive-dossier", "red-update-license", "fforesight-create-tenant", "fforesight-update-tenant", "red-experimental" ]
compositeRoles:
- name: RED_MANAGER
composites:
- name: RED_USER
- name: RED_ADMIN
composites:
- name: RED_USER_ADMIN
roles:
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions: [ "red-read-license", "red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration", "fforesight-read-identity-provider-config","fforesight-write-identity-provider-config", "red-unarchive-dossier" ]
- name: RED_USER
set-by-default: true
rank: 100
permissions: [ "red-get-rss", "red-add-comment", "red-read-license", "red-read-app-configuration", "red-read-dossier-status", "red-add-dossier-dictionary-entry", "red-add-redaction", "red-add-update-dossier-dictionary-type",
"red-delete-comment", "red-delete-dossier-dictionary-entry", "red-delete-dossier-dictionary-type", "red-delete-file", "red-delete-manual-redaction", "red-download-annotated-file",
"red-download-original-file", "red-download-redacted-file", "red-download-redaction-preview-file", "red-download-report-template", "red-exclude-include-file",
"red-exclude-include-pages", "red-get-report-templates", "fforesight-manage-user-preferences", "red-manage-viewed-pages", "red-process-download", "red-process-manual-redaction-request",
"red-read-colors", "red-read-dictionary-types", "red-read-digital-signature", "red-read-dossier", "red-read-dossier-attributes", "red-read-dossier-attributes-config",
"red-read-dossier-templates", "red-read-download-status", "red-read-file-attributes-config", "red-read-file-status", "fforesight-read-general-configuration", "red-read-legal-basis",
"red-read-manual-redactions", "red-read-notification", "red-read-redaction-log", "red-read-rules", "fforesight-read-users", "red-read-versions", "red-reanalyze-dossier",
"red-reanalyze-file", "red-request-redaction", "red-rotate-page", "red-search", "red-search-audit-log", "red-set-reviewer", "red-set-status-approved", "red-set-status-under-approval",
"fforesight-update-my-profile", "red-update-notification", "red-upload-file", "red-write-file-attributes", "red-process-texthighlights", "red-get-highlights", "red-convert-highlights", "red-delete-highlights", "red-delete-imported-redactions" ]
- name: RED_ADMIN
set-by-default: false
rank: 800
permissions: [ "red-add-dictionary-entry", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
- name: RED_MANAGER
set-by-default: false
rank: 200
permissions: [ "red-add-update-dossier", "red-archived-dossier", "red-delete-dossier", "red-write-dossier-attributes" ]
- name: RED_USER_ADMIN
set-by-default: false
rank: 400
permissions: [ "fforesight-manage-user-preferences", "fforesight-read-all-users", "red-read-dossier", "red-read-app-configuration", "fforesight-read-general-configuration",
"red-read-notification", "fforesight-read-users", "fforesight-update-my-profile", "red-update-notification", "fforesight-write-users", "red-read-license" ]
springdoc:
default-tenant: 'redaction'

View File

@ -0,0 +1,54 @@
server:
port: 8091
fforesight:
tenant-user-management:
server-url: http://localhost:8080
client-secret: muEZIuVsAr57KsjFi4WpGJuw54RiJE0q
client-id: manager
realm: master
springdoc:
auth-server-url: http://localhost:8080
spring:
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:25432}/${PSQL_DATABASE:tenantmanager}?ApplicationName=${spring.application.name:}&cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
driverClassName: org.postgresql.Driver
username: ${PSQL_USERNAME:tenantmanager}
password: ${PSQL_PASSWORD:r3dact3d}
platform: org.hibernate.dialect.PostgreSQL95Dialect
cors.enabled: true
#dev.tenant.db:
# port: 15432
# host: localhost
# database: red-tenant
# schema: public
# username: tenant
# password: r3dact3d
dev.tenant.recreateTenant: true
dev.tenant.db:
port: 5432
host: syngenta-training-clone.postgres.database.azure.com
database: syngenta-training-<YOUR_DB_COPY_NAME>
schema: syngentatraining
username: db_connection
password: <DB_PASSWORD_FROM_WIKI>
dev.tenant.storage:
mode: 'S3'
s3:
key: minioadmin
secret: minioadmin
bucket: redaction
endpoint: http://localhost:9000
# mode: 'AZURE'
azure:
containerName: syngenta-training-<YOUR_BLOB_COPY_NAME>
connectionString: <AZURE_BLOB_CONNECTION_STRING_FROM_WIKI>

View File

@ -0,0 +1,59 @@
fforesight:
tenant-user-management:
application-client-id: 'redaction'
application-name: 'RedactManager'
client-id: 'manager'
tenant-access-token-life-span: 300
realm: master
default-theme: 'redaction'
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
kc-role-mapping:
unmappedPermissions: [ "red-unarchive-dossier", "red-update-license", "red-get-rss","fforesight-create-tenant", "fforesight-update-tenant", "red-experimental" ]
compositeRoles:
- name: RED_MANAGER
composites:
- name: RED_USER
- name: RED_ADMIN
composites:
- name: RED_USER_ADMIN
roles:
- name: RED_USER
set-by-default: true
rank: 100
permissions: [ "red-add-comment", "red-read-license", "red-read-app-configuration", "red-read-dossier-status", "red-add-dossier-dictionary-entry", "red-add-redaction", "red-add-update-dossier-dictionary-type",
"red-delete-comment", "red-delete-dossier-dictionary-entry", "red-delete-dossier-dictionary-type", "red-delete-file", "red-delete-manual-redaction", "red-download-annotated-file",
"red-download-original-file", "red-download-redacted-file", "red-download-redaction-preview-file", "red-download-report-template", "red-exclude-include-file",
"red-exclude-include-pages", "red-get-report-templates", "fforesight-manage-user-preferences", "red-manage-viewed-pages", "red-process-download", "red-process-manual-redaction-request",
"red-read-colors", "red-read-dictionary-types", "red-read-digital-signature", "red-read-dossier", "red-read-dossier-attributes", "red-read-dossier-attributes-config",
"red-read-dossier-templates", "red-read-download-status", "red-read-file-attributes-config", "red-read-file-status", "fforesight-read-general-configuration", "red-read-legal-basis",
"red-read-manual-redactions", "red-read-notification", "red-read-redaction-log", "red-read-rules", "fforesight-read-users", "red-read-versions", "red-read-watermark", "red-reanalyze-dossier",
"red-reanalyze-file", "red-request-redaction", "red-rotate-page", "red-search", "red-search-audit-log", "red-set-reviewer", "red-set-status-approved", "red-set-status-under-approval",
"fforesight-update-my-profile", "red-update-notification", "red-upload-file", "red-write-file-attributes", "red-process-texthighlights", "red-get-highlights", "red-convert-highlights", "red-delete-highlights", "red-delete-imported-redactions" ]
- name: RED_ADMIN
set-by-default: false
rank: 800
permissions: [ "red-add-dictionary-entry", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-read-watermark",
"red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config",
"red-write-watermark", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
- name: RED_MANAGER
set-by-default: false
rank: 200
permissions: [ "red-add-update-dossier", "red-archived-dossier", "red-delete-dossier", "red-write-dossier-attributes" ]
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions: [ "red-read-license", "red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration","red-unarchive-dossier" ]
- name: RED_USER_ADMIN
set-by-default: false
rank: 400
permissions: [ "fforesight-manage-user-preferences", "fforesight-read-all-users", "red-read-app-configuration", "fforesight-read-general-configuration",
"red-read-notification", "fforesight-read-users", "fforesight-update-my-profile", "red-update-notification", "fforesight-write-users", "red-read-license" ]
springdoc:
default-tenant: 'redaction'

View File

@ -4,7 +4,6 @@ logging.type: ${LOGGING_TYPE:CONSOLE}
kubernetes.namespace: ${NAMESPACE:default}
project.version: 1.0-SNAPSHOT
persistence-service.url: "http://persistence-service-v1:8080"
management:
endpoint:
@ -27,9 +26,6 @@ info:
server:
port: 8080
lifecycle:
base-package: com.knecon.fforesight.tenantusermanagement
spring:
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:5432}/${PSQL_DATABASE:master}?ApplicationName=${spring.application.name:}&cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
@ -38,7 +34,7 @@ spring:
password: ${PSQL_PASSWORD:fforesight}
platform: org.hibernate.dialect.PostgreSQL10Dialect
hikari:
maximumPoolSize: 10
maximumPoolSize: 2
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
@ -101,259 +97,21 @@ spring:
password: ${REDIS_PASSWORD:}
fforesight:
keycloak:
ignored-endpoints: [ '/actuator/health', '/actuator/health/**', '/tenant-user-management', '/tenant-user-management/', '/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs','/actuator/prometheus' ]
ignored-endpoints: [ '/actuator/health', '/actuator/health/**', '/tenant-user-management', '/tenant-user-management/', '/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs','/tenant-user-management/tenants/simple' ]
enabled: true
springdoc:
base-path: '/tenant-user-management'
auth-server-url: '/auth'
enabled: true
default-client-id: 'swagger-ui-client'
default-tenant: 'fforesight'
tenant-user-management:
base-path: '/tenant-user-management'
login-theme: 'redaction'
app-prefix: 'fforesight'
tenant-exchange:
name: 'tenants-exchange'
user-exchange:
name: 'users-exchange'
tenant-user-management:
base-path: '/tenant-user-management'
client-id: 'manager'
realm: master
application-types:
redactmanager:
application-client-id: 'redaction'
application-name: 'RedactManager'
login-theme: 'redaction'
default-theme: 'redaction'
app-prefix: 'redaction'
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
kc-role-mapping:
unmappedPermissions: [ "red-get-similiar-images","red-unarchive-dossier", "red-update-license", "red-get-rss","fforesight-create-tenant", "fforesight-update-tenant", "red-experimental" ]
compositeRoles:
- name: RED_MANAGER
composites:
- name: RED_USER
- name: RED_ADMIN
composites:
- name: RED_USER_ADMIN
roles:
- name: RED_USER
set-by-default: true
rank: 100
permissions: [ "red-add-comment", "red-get-similar-images", "red-read-license", "red-read-app-configuration", "red-read-dossier-status", "red-add-dossier-dictionary-entry", "red-add-redaction", "red-add-update-dossier-dictionary-type",
"red-delete-comment", "red-delete-dossier-dictionary-entry", "red-delete-dossier-dictionary-type", "red-delete-file", "red-delete-manual-redaction", "red-download-annotated-file",
"red-download-original-file", "red-download-redacted-file", "red-download-redaction-preview-file", "red-download-report-template", "red-exclude-include-file",
"red-exclude-include-pages", "red-get-report-templates", "fforesight-manage-user-preferences", "red-manage-viewed-pages", "red-process-download", "red-process-manual-redaction-request",
"red-read-colors", "red-read-dictionary-types", "red-read-digital-signature", "red-read-dossier", "red-read-dossier-attributes", "red-read-dossier-attributes-config",
"red-read-dossier-templates", "red-read-download-status", "red-read-file-attributes-config", "red-read-file-status", "fforesight-read-general-configuration", "red-read-legal-basis",
"red-read-manual-redactions", "red-read-notification", "red-read-redaction-log", "red-read-rules", "red-read-data-formats", "fforesight-read-users", "red-read-versions", "red-read-watermark", "red-reanalyze-dossier",
"red-reanalyze-file", "red-request-redaction", "red-rotate-page", "red-search", "red-search-audit-log", "red-set-reviewer", "red-set-status-approved", "red-set-status-under-approval",
"fforesight-update-my-profile", "red-update-notification", "red-upload-file", "red-write-file-attributes", "red-process-texthighlights", "red-get-highlights", "red-convert-highlights", "red-delete-highlights", "red-delete-imported-redactions" ]
- name: RED_ADMIN
set-by-default: false
rank: 800
permissions: [ "red-add-dictionary-entry","red-get-similar-images", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
"red-read-legal-basis", "red-get-user-stats","red-read-license-report", "red-read-notification", "red-read-rules", "red-read-data-formats", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-read-watermark",
"red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "red-write-data-formats", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config",
"red-write-watermark", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
- name: RED_MANAGER
set-by-default: false
rank: 200
permissions: [ "red-add-update-dossier", "red-archived-dossier", "red-delete-dossier", "red-write-dossier-attributes" ]
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions: [ "red-read-license", "red-get-similar-images","red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration","red-unarchive-dossier", "red-use-support-controller", "red-import-files", "red-process-download", "red-read-download-status" ]
- name: KNECON_SUPPORT
set-by-default: false
rank: 1000
permissions: [ "red-read-license", "red-get-similar-images","red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration","red-unarchive-dossier", "red-use-support-controller", "red-process-download", "red-read-download-status" ]
- name: RED_USER_ADMIN
set-by-default: false
rank: 400
permissions: [ "fforesight-manage-user-preferences", "fforesight-read-all-users", "red-read-app-configuration", "fforesight-read-general-configuration",
"red-read-notification", "red-get-user-stats", "fforesight-read-users", "fforesight-update-my-profile", "red-update-notification", "fforesight-write-users", "red-read-license" ]
documine:
application-client-id: 'redaction'
application-name: 'Documine'
login-theme: 'scm'
default-theme: 'scm'
app-prefix: 'documine'
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
kc-role-mapping:
unmappedPermissions: [ "red-get-tables", "red-unarchive-dossier", "red-update-license", "fforesight-create-tenant", "fforesight-update-tenant", "red-experimental" ]
compositeRoles:
- name: RED_MANAGER
composites:
- name: RED_USER
- name: RED_ADMIN
composites:
- name: RED_USER_ADMIN
roles:
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions: [ "red-read-license","red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration", "fforesight-read-identity-provider-config","fforesight-write-identity-provider-config", "red-unarchive-dossier", "red-use-support-controller", "red-import-files", "red-process-download", "red-read-download-status" ]
- name: KNECON_SUPPORT
set-by-default: false
rank: 1000
permissions: [ "red-read-license","red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration", "fforesight-read-identity-provider-config","fforesight-write-identity-provider-config", "red-unarchive-dossier", "red-use-support-controller", "red-process-download", "red-read-download-status" ]
- name: RED_USER
set-by-default: true
rank: 100
permissions: [ "red-get-rss", "red-add-comment", "red-get-similar-images", "red-read-license", "red-read-app-configuration", "red-read-dossier-status", "red-add-dossier-dictionary-entry", "red-add-redaction", "red-add-update-dossier-dictionary-type",
"red-delete-comment", "red-delete-dossier-dictionary-entry", "red-delete-dossier-dictionary-type", "red-delete-file", "red-delete-manual-redaction", "red-download-annotated-file",
"red-download-original-file", "red-download-redacted-file", "red-download-redaction-preview-file", "red-download-report-template", "red-exclude-include-file",
"red-exclude-include-pages", "red-get-report-templates", "fforesight-manage-user-preferences", "red-manage-viewed-pages", "red-process-download", "red-process-manual-redaction-request",
"red-read-colors", "red-read-dictionary-types", "red-read-digital-signature", "red-read-dossier", "red-read-dossier-attributes", "red-read-dossier-attributes-config",
"red-read-dossier-templates", "red-read-download-status", "red-read-file-attributes-config", "red-read-file-status", "fforesight-read-general-configuration", "red-read-legal-basis",
"red-read-manual-redactions", "red-read-notification", "red-read-redaction-log", "red-read-rules", "red-read-data-formats", "fforesight-read-users", "red-read-versions", "red-reanalyze-dossier",
"red-reanalyze-file", "red-request-redaction", "red-rotate-page", "red-search", "red-search-audit-log", "red-set-reviewer", "red-set-status-approved", "red-set-status-under-approval",
"fforesight-update-my-profile", "red-update-notification", "red-upload-file", "red-write-file-attributes", "red-process-texthighlights", "red-get-highlights", "red-convert-highlights", "red-delete-highlights", "red-delete-imported-redactions" ]
- name: RED_ADMIN
set-by-default: false
rank: 800
permissions: [ "red-add-dictionary-entry", "red-get-similar-images","red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "red-read-data-formats", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "red-write-data-formats", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
- name: RED_MANAGER
set-by-default: false
rank: 200
permissions: [ "red-add-update-dossier", "red-archived-dossier", "red-delete-dossier", "red-write-dossier-attributes" ]
- name: RED_USER_ADMIN
set-by-default: false
rank: 400
permissions: [ "fforesight-manage-user-preferences", "fforesight-read-all-users", "red-read-app-configuration", "fforesight-read-general-configuration",
"red-read-notification", "fforesight-read-users", "fforesight-update-my-profile", "red-update-notification", "fforesight-write-users", "red-read-license" ]
clarifynd:
application-client-id: 'fforesight'
application-name: 'Clarifynd'
accessTokenLifeSpan: 3600
ssoSessionIdleTimeout: 86400
default-theme: 'clarifynd'
valid-redirect-uris: [ '/search/*', '/bdr-connector/*', '/sdi/*', '/tenant-user-management/*','http://localhost:4200/*', 'http://localhost:4300/*', '/ui/*' ,'/auth/*','/persistence/*', '/audit/*', '/contextual-query/*' ]
kc-role-mapping:
roles:
- name: FF_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-update-my-profile'
- 'fforesight-get-tenants'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-search'
- 'fforesight-view-document'
- 'fforesight-user-upload-files'
- 'fforesight-user-manage-files'
- 'fforesight-user-view-files'
- 'fforesight-user-manage-analysis'
- 'fforesight-user-view-analysis'
- 'fforesight-user-view-favourites'
- 'fforesight-user-add-to-favourites'
- 'fforesight-user-delete-from-favourites'
- 'fforesight-user-view-paragraph-tags'
- 'fforesight-user-add-to-paragraph-tags'
- 'fforesight-user-delete-from-paragraph-tags'
- 'fforesight-read-tag'
- 'fforesight-write-tag'
- 'fforesight-download-file'
- name: KNECON_ADMIN
set-by-default: false
rank: 1000
permissions:
- "red-read-license"
- "red-update-license"
- "red-get-similiar-images"
- "fforesight-get-tenants"
- "fforesight-create-tenant"
- "fforesight-update-tenant"
- "fforesight-delete-tenant"
- "fforesight-read-users"
- "fforesight-read-all-users"
- "fforesight-write-users"
- "fforesight-read-smtp-configuration"
- "fforesight-write-smtp-configuration"
- "fforesight-read-identity-provider-config"
- "fforesight-write-identity-provider-config"
- "red-unarchive-dossier"
- name: KNECON_SUPPORT
set-by-default: false
rank: 1000
permissions:
- "red-read-license"
- "red-update-license"
- "red-get-similiar-images"
- "fforesight-get-tenants"
- "fforesight-create-tenant"
- "fforesight-update-tenant"
- "fforesight-delete-tenant"
- "fforesight-read-users"
- "fforesight-read-all-users"
- "fforesight-write-users"
- "fforesight-read-smtp-configuration"
- "fforesight-write-smtp-configuration"
- "fforesight-read-identity-provider-config"
- "fforesight-write-identity-provider-config"
- "red-unarchive-dossier"
- name: FF_ADMIN
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-update-tenant'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- "fforesight-read-identity-provider-config"
- "fforesight-write-identity-provider-config"
- 'fforesight-search'
- 'fforesight-search-audit-log'
- 'fforesight-view-document'
- 'fforesight-user-upload-files'
- 'fforesight-user-manage-files'
- 'fforesight-user-view-files'
- 'fforesight-user-manage-analysis'
- 'fforesight-user-view-analysis'
- 'fforesight-user-view-favourites'
- 'fforesight-user-add-to-favourites'
- 'fforesight-user-view-paragraph-tags'
- 'fforesight-user-add-to-paragraph-tags'
- 'fforesight-user-delete-from-paragraph-tags'
- 'fforesight-user-delete-from-favourites'
- 'fforesight-system-upload-files'
- 'fforesight-system-manage-files'
- 'fforesight-system-view-files'
- 'fforesight-system-manage-analysis'
- 'fforesight-system-view-analysis'
- 'fforesight-download-file'
- 'fforesight-sdi-view-task'
- 'fforesight-sdi-start-task'
- 'fforesight-sdi-stop-task'
- 'fforesight-read-tag'
- 'fforesight-write-tag'
- 'taas-bdr-connector-view-task'
- 'taas-bdr-connector-start-task'
- 'taas-bdr-connector-stop-task'
springdoc:
swagger-ui:
path: ${fforesight.springdoc.base-path}/docs/swagger-ui

View File

@ -13,9 +13,3 @@ databaseChangeLog:
file: db/changelog/master/7-change-index-name-to-index-prefix.changelog.yaml
- include:
file: db/changelog/master/8-tenant-connection-data-string-length.changelog.yaml
- include:
file: db/changelog/master/9-add-mongodb-connection-columns.yaml
- include:
file: db/changelog/master/10-add-application-type-to-tenant.yaml
- include:
file: db/changelog/master/11-add-global-smtp-config.yaml

View File

@ -1,20 +0,0 @@
databaseChangeLog:
- property: # this will be used as a default if no environment variable is present
name: FFORESIGHT_TENANT_USER_MANAGEMENT_APPLICATION_NAME
value: "RedactManager"
- changeSet:
id: add-application-type-to-tenant
author: maverick
changes:
- addColumn:
columns:
- column:
name: application_type
type: VARCHAR(255)
tableName: tenant
- update:
tableName: tenant
columns:
- column:
name: application_type
value: ${FFORESIGHT_TENANT_USER_MANAGEMENT_APPLICATION_NAME}

View File

@ -1,51 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-global-smtp-config-table
author: dom
changes:
- createTable:
tableName: global_smtp_configuration
columns:
- column:
name: id
type: VARCHAR(255)
constraints:
nullable: false
primaryKey: true
primaryKeyName: global_smtp_configuration_pkey
- column:
name: auth
type: BOOLEAN
- column:
name: envelope_from
type: VARCHAR(255)
- column:
name: from_email
type: VARCHAR(255)
- column:
name: from_display_name
type: VARCHAR(255)
- column:
name: host
type: VARCHAR(255)
- column:
name: password
type: VARCHAR(255)
- column:
name: port
type: INTEGER
- column:
name: reply_to
type: VARCHAR(255)
- column:
name: reply_to_display_name
type: VARCHAR(255)
- column:
name: ssl
type: BOOLEAN
- column:
name: starttls
type: BOOLEAN
- column:
name: user_name
type: VARCHAR(255)

View File

@ -1,26 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-mongodb-connection-columns-to-tenant
author: maverick
changes:
- addColumn:
tableName: tenant
columns:
- column:
name: mongodb_prefix
type: VARCHAR(255)
- column:
name: mongodb_username
type: VARCHAR(255)
- column:
name: mongodb_password
type: VARCHAR(255)
- column:
name: mongodb_address
type: VARCHAR(255)
- column:
name: mongodb_database
type: VARCHAR(255)
- column:
name: mongodb_options
type: VARCHAR(255)

View File

@ -1,14 +1,6 @@
package com.knecon.fforesight.tenantusermanagement;
import static org.mockito.Mockito.when;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
@ -24,27 +16,21 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.FeatureType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.License;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
import com.knecon.fforesight.tenantcommons.queue.TenantMessagingConfiguration;
import com.knecon.fforesight.tenantusermanagement.client.LicenseClient;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.TenantsClient;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.internal.InternalTenantsClient;
import com.knecon.fforesight.tenantusermanagement.testcontainers.KeyCloakTestContainer;
import com.knecon.fforesight.tenantusermanagement.testcontainers.MinioTestContainer;
import com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer;
import com.knecon.fforesight.tenantusermanagement.testcontainers.RedisTestContainer;
import com.knecon.fforesight.tenantusermanagement.testcontainers.SpringPostgreSQLTestContainer;
import com.knecon.fforesight.tenantusermanagement.utils.TestTenantService;
import com.knecon.fforesight.tenantusermanagement.utils.TokenService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SpringExtension.class)
@EnableFeignClients(basePackageClasses = {TenantsClient.class, InternalTenantsClient.class, LicenseClient.class})
@EnableFeignClients(basePackageClasses = {TenantsClient.class, InternalTenantsClient.class})
@Import(AbstractTenantUserManagementIntegrationTest.TestConfiguration.class)
@ContextConfiguration(initializers = {AbstractTenantUserManagementIntegrationTest.Initializer.class})
@SpringBootTest(classes = TenantUserManagementServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ -58,37 +44,12 @@ public class AbstractTenantUserManagementIntegrationTest {
protected TestTenantService testTenantService;
@Autowired
protected TokenService tokenService;
@MockBean
protected LicenseClient licenseClient;
@MockBean
protected RabbitAdmin rabbitAdmin;
@MockBean
protected RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry;
@MockBean
protected TenantMessagingConfiguration tenantMessagingConfiguration;
@BeforeEach
public void createTestTenant() {
testTenantService.createTestTenantIfNotExists(TEST_TENANT_ID, minioPort);
prepareLicense();
}
private void prepareLicense() {
RedactionLicenseModel redactionLicenseModel = new RedactionLicenseModel();
redactionLicenseModel.setActiveLicense("1");
License license = new License();
license.setId("1");
Feature feature = new Feature();
feature.setName("configurableSMTPServer");
feature.setType(FeatureType.BOOLEAN);
feature.setValue(true);
license.setFeatures(List.of(feature));
redactionLicenseModel.setLicenses(List.of(license));
when(licenseClient.getLicense()).thenReturn(redactionLicenseModel);
}
@ -109,21 +70,14 @@ public class AbstractTenantUserManagementIntegrationTest {
var kcInstance = KeyCloakTestContainer.getInstance();
var mongoInstance = MongoDBTestContainer.getInstance();
mongoInstance.start();
TestPropertyValues.of("spring.datasource.url=" + postgreSQLContainerMaster.getJdbcUrl() + connectionStringDetails,
"spring.datasource.username=" + postgreSQLContainerMaster.getUsername(),
"spring.datasource.password=" + postgreSQLContainerMaster.getPassword(),
"fforesight.tenant-user-management.serverUrl=" + kcInstance.getAuthServerUrl(),
"REDIS_PORT=" + redisContainer.getFirstMappedPort(),
"REDIS_HOST=" + redisContainer.getHost(),
"MONGODB_HOST=" + mongoInstance.getHost(),
"MONGODB_PORT=" + mongoInstance.getFirstMappedPort(),
"MONGODB_USER=" + MongoDBTestContainer.MONGO_USERNAME,
"MONGODB_PASSWORD=" + MongoDBTestContainer.MONGO_PASSWORD,
"fforesight.jobs.enabled=false",
"fforesight.keycloak.enabled=true").applyTo(configurableApplicationContext.getEnvironment());
"spring.datasource.username=" + postgreSQLContainerMaster.getUsername(),
"spring.datasource.password=" + postgreSQLContainerMaster.getPassword(),
"fforesight.tenant-user-management.serverUrl=" + kcInstance.getAuthServerUrl(),
"REDIS_PORT=" + redisContainer.getFirstMappedPort(),
"REDIS_HOST=" + redisContainer.getHost(),
"fforesight.jobs.enabled=false",
"fforesight.keycloak.enabled=true").applyTo(configurableApplicationContext.getEnvironment());
var minioInstance = MinioTestContainer.getInstance();
minioInstance.start();

View File

@ -1,35 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.testcontainers;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;
public final class MongoDBTestContainer extends GenericContainer<MongoDBTestContainer> {
private static final String IMAGE_VERSION = "mongo:7.0.2";
public static final Integer MONGO_PORT = 27017;
public static final String MONGO_DATABASE = "mongo_database";
public static final String MONGO_PASSWORD = "mongo_password";
public static final String MONGO_USERNAME = "mongo_username";
private static MongoDBTestContainer mongoDB;
private MongoDBTestContainer() {
super(DockerImageName.parse(IMAGE_VERSION));
}
public static MongoDBTestContainer getInstance() {
if (mongoDB == null) {
mongoDB = new MongoDBTestContainer().withEnv("MONGO_INITDB_ROOT_USERNAME", MONGO_USERNAME)
.withEnv("MONGO_INITDB_ROOT_PASSWORD", MONGO_PASSWORD)
.withEnv("MONGO_INITDB_DATABASE", MONGO_DATABASE)
.withExposedPorts(MONGO_PORT);
}
return mongoDB;
}
}

View File

@ -2,13 +2,13 @@ package com.knecon.fforesight.tenantusermanagement.tests;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.GeneralSettingsClient;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.model.GeneralConfigurationModel;
import org.junit.jupiter.api.Test;
public class GeneralSettingsTest extends AbstractTenantUserManagementIntegrationTest {

View File

@ -1,27 +1,15 @@
package com.knecon.fforesight.tenantusermanagement.tests;
import static com.knecon.fforesight.tenantusermanagement.service.EnvironmentSMTPConfigurationProvider.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.FeatureType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.License;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.SMTPConfigurationClient;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import feign.FeignException;
import org.junit.jupiter.api.Test;
public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrationTest {
@ -29,22 +17,6 @@ public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrati
private SMTPConfigurationClient smtpConfigurationClient;
private void prepareLicense() {
RedactionLicenseModel redactionLicenseModel = new RedactionLicenseModel();
redactionLicenseModel.setActiveLicense("1");
License license = new License();
license.setId("1");
Feature feature = new Feature();
feature.setName("configurableSMTPServer");
feature.setType(FeatureType.BOOLEAN);
feature.setValue(false);
license.setFeatures(List.of(feature));
redactionLicenseModel.setLicenses(List.of(license));
when(licenseClient.getLicense()).thenReturn(redactionLicenseModel);
}
@Test
public void testSMTPConfiguration() {
@ -87,58 +59,6 @@ public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrati
}
@Test
public void testDefaultSMTPConfiguration() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
smtpConfigurationClient.clearSMTPConfiguration();
System.setProperty(SMTP_DEFAULT_HOST, "smtp.example.com");
System.setProperty(SMTP_DEFAULT_PORT, "587");
System.setProperty(SMTP_DEFAULT_SENDER_EMAIL, "no-reply@example.com");
System.setProperty(SMTP_DEFAULT_SENDER_NAME, "No Reply");
System.setProperty(SMTP_DEFAULT_REPLY_EMAIL, "support@example.com");
System.setProperty(SMTP_DEFAULT_REPLY_NAME, "Support Team");
System.setProperty(SMTP_DEFAULT_SENDER_ENVELOPE, "envelope@example.com");
System.setProperty(SMTP_DEFAULT_SSL, "true");
System.setProperty(SMTP_DEFAULT_STARTTLS, "true");
System.setProperty(SMTP_DEFAULT_AUTH, "true");
String newTenant = "NEW_TENANT";
testTenantService.createTestTenantWithoutStorageIfNotExist(newTenant);
TenantContext.setTenantId(newTenant);
var currentSMTPConfiguration = smtpConfigurationClient.getCurrentSMTPConfiguration();
assertThat(currentSMTPConfiguration.getHost()).isEqualTo("smtp.example.com");
assertThat(currentSMTPConfiguration.getPort()).isEqualTo(587);
assertThat(currentSMTPConfiguration.getReplyTo()).isEqualTo("support@example.com");
assertThat(currentSMTPConfiguration.getReplyToDisplayName()).isEqualTo("Support Team");
assertThat(currentSMTPConfiguration.getFrom()).isEqualTo("no-reply@example.com");
assertThat(currentSMTPConfiguration.getFromDisplayName()).isEqualTo("No Reply");
assertThat(currentSMTPConfiguration.getEnvelopeFrom()).isEqualTo("envelope@example.com");
assertThat(currentSMTPConfiguration.isAuth()).isTrue();
assertThat(currentSMTPConfiguration.isSsl()).isTrue();
assertThat(currentSMTPConfiguration.isStarttls()).isTrue();
TenantContext.clear();
}
@Test
public void testChangeSMTPConfigurationWhenLicenseFlagIsFalse() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
prepareLicense();
var error = assertThrows(FeignException.class, () -> smtpConfigurationClient.updateSMTPConfiguration(provideTestSMTPConfiguration()));
assertTrue(error.getMessage().contains("Current license does not allow updating the SMTP configuration!"));
TenantContext.clear();
}
@Test
public void testSMTPConnection() {
@ -152,23 +72,4 @@ public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrati
TenantContext.clear();
}
@Test
public void testSMTPwithoutPassword() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
SMTPConfiguration smtpConfiguration = new SMTPConfiguration();
smtpConfiguration.setFrom("from@knecon.com");
smtpConfiguration.setHost("test.knecon.com");
smtpConfigurationClient.updateSMTPConfiguration(smtpConfiguration);
var current = smtpConfigurationClient.getCurrentSMTPConfiguration();
assertThat(current.getPassword()).matches("\\**");
TenantContext.clear();
}
}

View File

@ -1,353 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.quality.Strictness.LENIENT;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
import com.knecon.fforesight.tenantusermanagement.service.EnvironmentSMTPConfigurationProvider;
import com.knecon.fforesight.tenantusermanagement.service.GlobalSMTPConfigurationPersistenceService;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import com.knecon.fforesight.tenantusermanagement.service.SMTPService;
@MockitoSettings(strictness = LENIENT)
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
public class SMTPServiceTest {
public static final String NEW_SMTP_HOST = "new.smtp.host";
public static final int NEW_SMTP_PORT = 465;
public static final String NEW_SMTP_USER = "newUser";
public static final String NEW_SMTP_PASSWORD = "newPassword";
public static final String OLD_SMTP_HOST = "old.smtp.host";
public static final int OLD_SMTP_PORT = 587;
public static final String OLD_SMTP_USER = "globalUser";
public static final String OLD_SMTP_PASSWORD = "oldPassword";
public static final String FFORESIGHT_SMTP_PASSWORD = "FFORESIGHT_SMTP_PASSWORD";
@Mock
private GlobalSMTPConfigurationPersistenceService smtpConfigurationPersistenceService;
@Mock
private EnvironmentSMTPConfigurationProvider environmentSMTPConfigurationProvider;
@Mock
private RealmService realmService;
@Mock
private TenantRepository tenantRepository;
@Mock
private EncryptionDecryptionService encryptionDecryptionService;
@InjectMocks
private SMTPService smtpService;
private SMTPConfiguration oldGlobalConfig;
private SMTPConfiguration overriddenConfig;
private List<TenantEntity> tenantEntities;
private final static String DEFAULT_PASSWORD = "**********";
@BeforeEach
public void setUp() {
smtpService = new SMTPService(smtpConfigurationPersistenceService,
environmentSMTPConfigurationProvider,
null,
realmService,
tenantRepository,
encryptionDecryptionService,
new ObjectMapper());
oldGlobalConfig = new SMTPConfiguration();
oldGlobalConfig.setId("singleton");
oldGlobalConfig.setHost(OLD_SMTP_HOST);
oldGlobalConfig.setPort(OLD_SMTP_PORT);
oldGlobalConfig.setUser(OLD_SMTP_USER);
oldGlobalConfig.setPassword(OLD_SMTP_PASSWORD);
overriddenConfig = new SMTPConfiguration();
overriddenConfig.setHost("custom.smtp.host");
overriddenConfig.setPort(2525);
overriddenConfig.setUser("customUser");
overriddenConfig.setPassword("encrypted_customPassword");
TenantEntity tenant1 = new TenantEntity();
tenant1.setTenantId("tenant1");
TenantEntity tenant2 = new TenantEntity();
tenant2.setTenantId("tenant2");
TenantEntity tenant3 = new TenantEntity();
tenant3.setTenantId("tenant3");
tenantEntities = Arrays.asList(tenant1, tenant2, tenant3);
when(tenantRepository.findAll()).thenReturn(tenantEntities);
GlobalSMTPConfigurationEntity globalEntity = new GlobalSMTPConfigurationEntity();
globalEntity.setHost(oldGlobalConfig.getHost());
globalEntity.setPort(oldGlobalConfig.getPort());
globalEntity.setUserName(oldGlobalConfig.getUser());
globalEntity.setPassword(oldGlobalConfig.getPassword());
when(smtpConfigurationPersistenceService.get()).thenReturn(Optional.of(globalEntity));
for (TenantEntity tenant : tenantEntities) {
RealmResource tenantRealmResource = mock(RealmResource.class);
when(realmService.realm(tenant.getTenantId())).thenReturn(tenantRealmResource);
}
when(encryptionDecryptionService.encrypt(ArgumentMatchers.<String>any())).thenAnswer(invocation -> {
String argument = invocation.getArgument(0);
return argument != null ? "encrypted_" + argument : null;
});
when(encryptionDecryptionService.decrypt(anyString())).thenAnswer(invocation -> {
String encrypted = invocation.getArgument(0);
if (encrypted.startsWith("encrypted_")) {
return encrypted.substring("encrypted_".length());
}
return encrypted;
});
}
@Test
public void testSynchronizeSMTPConfigurations() {
for (TenantEntity tenant : tenantEntities) {
RealmResource tenantRealmResource = mock(RealmResource.class);
when(realmService.realm(tenant.getTenantId())).thenReturn(tenantRealmResource);
// for this test also mock toRepresentation
RealmRepresentation realm = getRealmRepresentation(tenant, overriddenConfig, oldGlobalConfig);
when(tenantRealmResource.toRepresentation()).thenReturn(realm);
}
SMTPConfiguration newEnvConfig = new SMTPConfiguration();
newEnvConfig.setId("singleton");
newEnvConfig.setHost(NEW_SMTP_HOST);
newEnvConfig.setPort(NEW_SMTP_PORT);
newEnvConfig.setUser(NEW_SMTP_USER);
newEnvConfig.setPassword(NEW_SMTP_PASSWORD);
when(environmentSMTPConfigurationProvider.get()).thenReturn(newEnvConfig);
smtpService.synchronizeSMTPConfigurations();
ArgumentCaptor<GlobalSMTPConfigurationEntity> globalConfigCaptor = ArgumentCaptor.forClass(GlobalSMTPConfigurationEntity.class);
verify(smtpConfigurationPersistenceService).createUpdate(globalConfigCaptor.capture());
GlobalSMTPConfigurationEntity updatedGlobalConfig = globalConfigCaptor.getValue();
assertEquals(NEW_SMTP_HOST, updatedGlobalConfig.getHost());
assertEquals(NEW_SMTP_PORT, updatedGlobalConfig.getPort());
assertEquals(NEW_SMTP_USER, updatedGlobalConfig.getUserName());
assertEquals("encrypted_" + NEW_SMTP_PASSWORD, updatedGlobalConfig.getPassword());
// Verify that tenant1 and tenant3 were not updated, and tenant2 was
for (TenantEntity tenant : tenantEntities) {
RealmResource tenantRealmResource = realmService.realm(tenant.getTenantId());
if ("tenant1".equals(tenant.getTenantId()) || "tenant3".equals(tenant.getTenantId())) {
// Verify that update() was called once for tenant1 and tenant3
verify(tenantRealmResource, times(1)).update(any(RealmRepresentation.class));
ArgumentCaptor<RealmRepresentation> realmCaptor = ArgumentCaptor.forClass(RealmRepresentation.class);
verify(tenantRealmResource).update(realmCaptor.capture());
RealmRepresentation updatedRealm = realmCaptor.getValue();
Map<String, String> smtpServer = updatedRealm.getSmtpServer();
smtpServer.putAll(updatedRealm.getAttributes());
assertEquals(NEW_SMTP_HOST, smtpServer.get("host"));
assertEquals(String.valueOf(NEW_SMTP_PORT), smtpServer.get("port"));
assertEquals(NEW_SMTP_USER, smtpServer.get("user"));
assertEquals("encrypted_" + NEW_SMTP_PASSWORD, smtpServer.get(FFORESIGHT_SMTP_PASSWORD));
assertEquals(NEW_SMTP_PASSWORD, smtpServer.get("password"));
} else if ("tenant2".equals(tenant.getTenantId())) {
// Verify that update() was never called for tenant2
verify(tenantRealmResource, never()).update(any(RealmRepresentation.class));
}
}
}
@Test
public void testSynchronizeSMTPConfigurationsWithoutGlobalChanges() {
SMTPConfiguration newEnvConfig = new SMTPConfiguration();
newEnvConfig.setId("singleton");
newEnvConfig.setHost(OLD_SMTP_HOST);
newEnvConfig.setPort(OLD_SMTP_PORT);
newEnvConfig.setUser(OLD_SMTP_USER);
newEnvConfig.setPassword(OLD_SMTP_PASSWORD);
when(environmentSMTPConfigurationProvider.get()).thenReturn(newEnvConfig);
smtpService.synchronizeSMTPConfigurations();
verify(smtpConfigurationPersistenceService, never()).createUpdate(any(GlobalSMTPConfigurationEntity.class));
// Verify that all tenants are unchanged
for (TenantEntity tenant : tenantEntities) {
RealmResource tenantRealmResource = realmService.realm(tenant.getTenantId());
verify(tenantRealmResource, never()).update(any(RealmRepresentation.class));
}
}
@Test
public void testInitializeGlobalSMTPConfiguration() {
TenantEntity tenant1 = new TenantEntity();
tenant1.setTenantId("tenant1");
TenantEntity tenant2 = new TenantEntity();
tenant2.setTenantId("tenant2");
List<TenantEntity> tenantEntities = Arrays.asList(tenant1, tenant2);
when(tenantRepository.findAll()).thenReturn(tenantEntities);
when(smtpConfigurationPersistenceService.get()).thenReturn(Optional.empty());
String host = "global.smtp.host";
int port = 25;
String globalUser = "globalUser";
String globalPassword = "globalPassword";
SMTPConfiguration currentGlobalConfig = new SMTPConfiguration();
currentGlobalConfig.setId("singleton");
currentGlobalConfig.setHost(host);
currentGlobalConfig.setPort(port);
currentGlobalConfig.setUser(globalUser);
currentGlobalConfig.setPassword(globalPassword);
when(environmentSMTPConfigurationProvider.get()).thenReturn(currentGlobalConfig);
// Tenant1: Predefined SMTP configuration
RealmResource tenantRealmResource1 = mock(RealmResource.class);
when(realmService.realm("tenant1")).thenReturn(tenantRealmResource1);
RealmRepresentation realmRepresentation1 = getRealmRepresentationPredefined();
when(tenantRealmResource1.toRepresentation()).thenReturn(realmRepresentation1);
// Tenant2: No SMTP configuration
RealmResource tenantRealmResource2 = mock(RealmResource.class);
when(realmService.realm("tenant2")).thenReturn(tenantRealmResource2);
RealmRepresentation realmRepresentation2 = new RealmRepresentation();
Map<String, String> smtpServer2 = new HashMap<>();
smtpServer2.put("host", "");
realmRepresentation2.setSmtpServer(smtpServer2);
realmRepresentation2.setAttributes(new HashMap<>());
when(tenantRealmResource2.toRepresentation()).thenReturn(realmRepresentation2);
smtpService.synchronizeSMTPConfigurations();
ArgumentCaptor<GlobalSMTPConfigurationEntity> globalConfigCaptor = ArgumentCaptor.forClass(GlobalSMTPConfigurationEntity.class);
verify(smtpConfigurationPersistenceService).createUpdate(globalConfigCaptor.capture());
GlobalSMTPConfigurationEntity updatedGlobalConfig = globalConfigCaptor.getValue();
assertEquals(host, updatedGlobalConfig.getHost());
assertEquals(port, updatedGlobalConfig.getPort());
assertEquals(globalUser, updatedGlobalConfig.getUserName());
String encryptedGlobalPassword = "encrypted_" + globalPassword;
assertEquals(encryptedGlobalPassword, updatedGlobalConfig.getPassword());
// Verify that tenant1's SMTP configuration remains unchanged
verify(tenantRealmResource1, never()).update(any(RealmRepresentation.class));
// Verify that tenant2's SMTP configuration is updated
ArgumentCaptor<RealmRepresentation> realmCaptor2 = ArgumentCaptor.forClass(RealmRepresentation.class);
verify(tenantRealmResource2, times(1)).update(realmCaptor2.capture());
RealmRepresentation updatedRealm2 = realmCaptor2.getValue();
Map<String, String> updatedSmtpServer2 = updatedRealm2.getSmtpServer();
assertEquals(host, updatedSmtpServer2.get("host"));
assertEquals(String.valueOf(port), updatedSmtpServer2.get("port"));
assertEquals(globalUser, updatedSmtpServer2.get("user"));
assertEquals(globalPassword, updatedSmtpServer2.get("password"));
Map<String, String> updatedAttributes2 = updatedRealm2.getAttributes();
assertEquals(encryptedGlobalPassword, updatedAttributes2.get(FFORESIGHT_SMTP_PASSWORD));
}
@NotNull
private static RealmRepresentation getRealmRepresentationPredefined() {
RealmRepresentation realmRepresentation1 = new RealmRepresentation();
Map<String, String> smtpServer1 = new HashMap<>();
smtpServer1.put("host", "predefined.smtp.host");
smtpServer1.put("port", "587");
smtpServer1.put("user", "user1");
smtpServer1.put("password", DEFAULT_PASSWORD);
Map<String, String> attributes = new HashMap<>();
attributes.put(FFORESIGHT_SMTP_PASSWORD, "encrypted_predefined_pass");
realmRepresentation1.setSmtpServer(smtpServer1);
realmRepresentation1.setAttributes(attributes);
return realmRepresentation1;
}
@NotNull
private static RealmRepresentation getRealmRepresentation(TenantEntity tenant, SMTPConfiguration overriddenConfig, SMTPConfiguration oldGlobalConfig) {
RealmRepresentation realm = new RealmRepresentation();
Map<String, String> smtpMap = new HashMap<>();
smtpMap.put("password", DEFAULT_PASSWORD);
Map<String, String> attributes = new HashMap<>();
if ("tenant2".equals(tenant.getTenantId())) {
// tenant2 has overridden SMTP config
smtpMap.put("host", overriddenConfig.getHost());
smtpMap.put("port", String.valueOf(overriddenConfig.getPort()));
smtpMap.put("user", overriddenConfig.getUser());
attributes.put(FFORESIGHT_SMTP_PASSWORD, overriddenConfig.getPassword());
} else {
// tenant1 and tenant3 have SMTP config matching global
smtpMap.put("host", oldGlobalConfig.getHost());
smtpMap.put("port", String.valueOf(oldGlobalConfig.getPort()));
smtpMap.put("user", oldGlobalConfig.getUser());
attributes.put(FFORESIGHT_SMTP_PASSWORD, oldGlobalConfig.getPassword());
}
realm.setSmtpServer(smtpMap);
realm.setAttributes(attributes);
return realm;
}
}

View File

@ -17,8 +17,8 @@ public class StartupTest extends AbstractTenantUserManagementIntegrationTest {
@Test
public void testStartup() {
var tenants = internalTenantsClient.getTenants();
assertThat(tenants).isNotEmpty();
var simpleTenants = internalTenantsClient.getSimpleTenants();
assertThat(simpleTenants).isNotEmpty();
}

View File

@ -6,24 +6,21 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.TenantsClient;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.TenantsClient;
import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import com.knecon.fforesight.tenantusermanagement.utils.TestTenantService;
import feign.FeignException;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.regions.Region;
public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
@ -50,9 +47,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
var tenant = tenantsClient.getTenant("new_tenant");
assertThat(tenant.getGuid()).isNotBlank();
assertThat(tenantsClient.getTenants()
.stream()
.anyMatch(t -> t.getTenantId().equals("new_tenant"))).isTrue();
assertThat(tenantsClient.getTenants().stream().anyMatch(t -> t.getTenantId().equals("new_tenant"))).isTrue();
var realm = realmService.realm("master").toRepresentation();
assertThat(realm.getLoginTheme()).isEqualTo("redaction");
@ -69,55 +64,46 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
testTenantService.createTestTenantIfNotExists(tenantId, minioPort);
TenantContext.setTenantId(tenantId);
var tenantRequest = UpdateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId(tenantId)
.displayName("updated_display_name")
.searchConnection(SearchConnectionRequest.builder()
.numberOfReplicas("1")
.hosts(Set.of("updated_host"))
.numberOfShards("2")
.password("updated_pwd")
.port(123)
.username("updated_username")
.scheme("updated_scheme")
.build())
.numberOfReplicas("1")
.hosts(Set.of("updated_host"))
.numberOfShards("2")
.password("updated_pwd")
.port(123)
.username("updated_username")
.scheme("updated_scheme")
.build())
.databaseConnection(DatabaseConnection.builder()
.database("updated_db")
.driver("updated_driver")
.host("updated_host")
.port("123")
.schema("updated_schema")
.params(Map.of("key", "value"))
.password("updated_pwd")
.username("updated_username")
.build())
.database("updated_db")
.driver("updated_driver")
.host("updated_host")
.port("123")
.schema("updated_schema")
.params(Map.of("key", "value"))
.password("updated_pwd")
.username("updated_username")
.build())
.s3StorageConnection(S3StorageConnection.builder()
.key("minioadmin")
.secret("minioadmin")
.bucketName("redaction2")
.region(Region.AWS_GLOBAL.id())
.endpoint("http://localhost:" + minioPort)
.build())
.mongoDBConnection(MongoDBConnection.builder()
.prefix("mongodb")
.username("updated_username")
.password("updated_password")
.address("updated_host:12345")
.database("updated_db")
.options("")
.build())
.key("minioadmin")
.secret("minioadmin")
.bucketName("redaction2")
.region(Region.AWS_GLOBAL.id())
.endpoint("http://localhost:" + minioPort)
.build())
.build();
var updatedTenant = tenantsClient.updateTenant(tenantId, tenantRequest);
tenantRequest.getSearchConnection().setPassword(PASSWORD);
tenantRequest.getDatabaseConnection().setPassword(PASSWORD);
tenantRequest.getMongoDBConnection().setPassword(PASSWORD);
assertThat(updatedTenant.getDisplayName()).isEqualTo(tenantRequest.getDisplayName());
assertThat(updatedTenant.getSearchConnection()).isEqualTo(convert(tenantRequest.getSearchConnection(), tenantId));
assertThat(updatedTenant.getDatabaseConnection()).isEqualTo(tenantRequest.getDatabaseConnection());
assertThat(updatedTenant.getS3StorageConnection().getBucketName()).isEqualTo("redaction2");
assertThat(updatedTenant.getMongoDBConnection()).isEqualTo(tenantRequest.getMongoDBConnection());
var tenantEntity = tenantsClient.getTenant(tenantId);
@ -126,7 +112,6 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
assertThat(tenantEntity.getDatabaseConnection()).isEqualTo(tenantRequest.getDatabaseConnection());
assertThat(tenantEntity.getS3StorageConnection().getBucketName()).isEqualTo("redaction2");
assertThat(tenantEntity.getS3StorageConnection().getSecret()).isEqualTo(PASSWORD);
assertThat(updatedTenant.getMongoDBConnection()).isEqualTo(tenantRequest.getMongoDBConnection());
tenantRequest.setS3StorageConnection(null);
@ -159,15 +144,16 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
testTenantService.createTestTenantIfNotExists("new_tenant", minioPort);
TenantContext.setTenantId("new_tenant");
var tenantRequest = UpdateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId("new_tenant")
.s3StorageConnection(S3StorageConnection.builder()
.key("updated_key")
.bucketName("updated_bucket")
.endpoint("updated_endpoint")
.region("updated_region")
.secret("updated_secret")
.signerType("updated_signer")
.build())
.key("updated_key")
.bucketName("updated_bucket")
.endpoint("updated_endpoint")
.region("updated_region")
.secret("updated_secret")
.signerType("updated_signer")
.build())
.build();
var exception = assertThrows(FeignException.BadRequest.class, () -> tenantsClient.updateTenant("new_tenant", tenantRequest));
@ -183,7 +169,8 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
testTenantService.createTestTenantWithoutStorageIfNotExist("new_tenant_without_storage");
TenantContext.setTenantId("new_tenant_without_storage");
var tenantRequest = UpdateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId("new_tenant_without_storage")
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
.build();
@ -200,16 +187,17 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
testTenantService.createTestTenantIfNotExists("new_tenant", minioPort);
TenantContext.setTenantId("new_tenant");
var tenantRequest = UpdateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId("new_tenant")
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
.s3StorageConnection(S3StorageConnection.builder()
.key("updated_key")
.bucketName("updated_bucket")
.endpoint("updated_endpoint")
.region("updated_region")
.secret("updated_secret")
.signerType("updated_signer")
.build())
.key("updated_key")
.bucketName("updated_bucket")
.endpoint("updated_endpoint")
.region("updated_region")
.secret("updated_secret")
.signerType("updated_signer")
.build())
.build();
var exception = assertThrows(FeignException.BadRequest.class, () -> tenantsClient.updateTenant("new_tenant", tenantRequest));
@ -225,7 +213,8 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
testTenantService.createTestTenantIfNotExists("new_tenant_with_s3", minioPort);
TenantContext.setTenantId("new_tenant_with_s3");
var tenantRequest = UpdateTenantRequest.builder()
var tenantRequest = TenantRequest.builder()
.tenantId("new_tenant_with_s3")
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
.build();
@ -250,8 +239,6 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
assertThat(tenant.getSearchConnection().getPassword()).isEqualTo(PASSWORD);
assertThat(tenant.getS3StorageConnection()).isNotNull();
assertThat(tenant.getS3StorageConnection().getSecret()).isEqualTo(PASSWORD);
assertThat(tenant.getMongoDBConnection()).isNotNull();
assertThat(tenant.getMongoDBConnection().getPassword()).isEqualTo(PASSWORD);
TenantContext.clear();
}

View File

@ -10,12 +10,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.springframework.beans.factory.annotation.Autowired;
import com.knecon.fforesight.tenantcommons.TenantContext;
@ -25,15 +21,9 @@ import com.knecon.fforesight.tenantusermanagement.model.CreateUserRequest;
import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest;
import com.knecon.fforesight.tenantusermanagement.model.User;
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import com.knecon.fforesight.tenantusermanagement.service.TenantApplicationTypeService;
import com.knecon.fforesight.tenantusermanagement.service.UserService;
import feign.FeignException;
import lombok.NonNull;
import lombok.SneakyThrows;
public class UserTest extends AbstractTenantUserManagementIntegrationTest {
@ -41,18 +31,9 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
@Autowired
private UserClient userClient;
@Autowired
private RealmService realmService;
@Autowired
private TenantUserManagementProperties tenantUserManagementProperties;
@Autowired
private TenantApplicationTypeService tenantApplicationTypeService;
@Autowired
private UserService userService;
@Test
public void testUsers() {
@ -61,43 +42,33 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
tokenService.setUser("test@fforesight.com", "secret");
var allUsers = userClient.getAllUsers(true);
var testUserFound = allUsers.stream()
.anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com"));
var testUserFound = allUsers.stream().anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com"));
assertThat(allUsers).isNotEmpty();
assertThat(testUserFound).isTrue();
allUsers = userClient.getApplicationSpecificUsers(true);
testUserFound = allUsers.stream()
.anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com"));
testUserFound = allUsers.stream().anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com"));
assertThat(allUsers).isNotEmpty();
assertThat(testUserFound).isTrue();
var optionalUser = allUsers.stream()
.filter(user -> user.getUsername().equalsIgnoreCase("test@fforesight.com"))
.findFirst();
assert (optionalUser.isPresent());
var testUser = userClient.getUserById(optionalUser.get().getUserId());
var testUserId = allUsers.iterator().next().getUserId();
var testUser = userClient.getUserById(testUserId);
assertThat(testUser).isNotNull();
testUser = userClient.updateMyProfile(UpdateMyProfileRequest.builder()
.email("test@fforesight.com")
.firstName("updateTestFirstName")
.lastName("updateTestLastName")
.build());
.email("test@fforesight.com")
.firstName("updateTestFirstName")
.lastName("updateTestLastName")
.build());
assertThat(testUser.getLastName()).isEqualTo("updateTestLastName");
assertThat(testUser.getFirstName()).isEqualTo("updateTestFirstName");
Set<String> allButKneconRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
CreateUserRequest createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("test.new.user@knecon.com");
createUserRequest.setFirstName("Test");
createUserRequest.setLastName("New User");
createUserRequest.setUsername(createUserRequest.getEmail());
createUserRequest.setRoles(allButKneconRoles);
createUserRequest.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
var createdUser = userClient.createUser(createUserRequest);
allUsers = userClient.getAllUsers(true);
@ -113,13 +84,13 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
assertThat(createdUser.getRoles()).isEmpty();
createdUser = userClient.updateProfile(createdUser.getUserId(),
UpdateProfileRequest.builder()
.email("test.new.user@knecon.com")
.firstName("update test")
.lastName("update test")
.roles(allButKneconRoles)
.build());
assertThat(createdUser.getRoles()).containsExactly(allButKneconRoles.toArray(new String[0]));
UpdateProfileRequest.builder()
.email("test.new.user@knecon.com")
.firstName("update test")
.lastName("update test")
.roles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles())
.build());
assertThat(createdUser.getRoles()).containsExactly(tenantUserManagementProperties.getKcRoleMapping().getAllRoles().toArray(new String[0]));
userClient.deleteUsers(List.of(createdUser.getUserId()));
@ -162,10 +133,7 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
userClient.resetPassword(createdUser2.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!2345").build());
var allUsers = userClient.getAllUsers(true);
var initialSuperUser = allUsers.stream()
.filter(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com"))
.findAny()
.get();
var initialSuperUser = allUsers.stream().filter(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")).findAny().get();
try {
//reset password as less-super-user for super-user
userClient.resetPassword(initialSuperUser.getUserId(), ResetPasswordRequest.builder().password("ShouldNotWork@123").build());
@ -255,7 +223,6 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
TenantContext.clear();
}
@Test
@SneakyThrows
public void testActivateUserWithSameRole() {
@ -305,7 +272,7 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
createUserRequest.setFirstName("Test");
createUserRequest.setLastName("New User");
createUserRequest.setUsername("TestUserName");
createUserRequest.setRoles(tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles());
createUserRequest.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
FeignException e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
@ -316,17 +283,13 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
public void testCreateUserWithExistingUser() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
Set<String> allButKneconRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles()
.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
var createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("existinguser@knecon.com");
createUserRequest.setFirstName("Existing");
createUserRequest.setLastName("User");
createUserRequest.setUsername("ExistingUser");
createUserRequest.setRoles(allButKneconRoles);
createUserRequest.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
userClient.createUser(createUserRequest);
var createUserRequest2 = new CreateUserRequest();
@ -334,523 +297,47 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
createUserRequest2.setFirstName("New");
createUserRequest2.setLastName("User");
createUserRequest2.setUsername("NewUser");
createUserRequest2.setRoles(allButKneconRoles);
createUserRequest2.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
FeignException e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest2));
assertEquals(400, e.status());
assertEquals(409, e.status());
var createUserRequest3 = new CreateUserRequest();
createUserRequest3.setEmail("newuser@knecon.com");
createUserRequest3.setFirstName("New");
createUserRequest3.setLastName("User");
createUserRequest3.setUsername("ExistingUser");
createUserRequest3.setRoles(allButKneconRoles);
createUserRequest3.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest3));
assertEquals(400, e.status());
assertEquals(409, e.status());
var createUserRequest4 = new CreateUserRequest();
createUserRequest4.setEmail("existinguser@knecon.com");
createUserRequest4.setFirstName("New");
createUserRequest4.setLastName("User");
createUserRequest4.setUsername("ExistingUser");
createUserRequest4.setRoles(allButKneconRoles);
createUserRequest4.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest4));
assertEquals(400, e.status());
assertEquals(409, e.status());
var createUserRequest5 = new CreateUserRequest();
createUserRequest5.setEmail("anotherexistinguser@knecon.com");
createUserRequest5.setFirstName("Another existing");
createUserRequest5.setLastName("User");
createUserRequest5.setRoles(allButKneconRoles);
createUserRequest5.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
userClient.createUser(createUserRequest5);
var createUserRequest6 = new CreateUserRequest();
createUserRequest6.setEmail("anotherexistinguser@knecon.com");
createUserRequest6.setFirstName("Another new");
createUserRequest6.setLastName("User");
createUserRequest6.setRoles(allButKneconRoles);
createUserRequest6.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles());
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest6));
assertEquals(400, e.status());
assertEquals(409, e.status());
}
@Test
public void testHiddenKneconRoles() {
// set context and user
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
// different role sets and subsets
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
Set<String> allButKneconRoles = allRoles.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
Set<String> onlyKneconRoles = allRoles.stream()
.filter(ApplicationRoles::isKneconRole)
.collect(Collectors.toSet());
// create several users with different roles for testing
var createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("allroles@knecon.com");
createUserRequest.setFirstName("All");
createUserRequest.setLastName("Roles");
createUserRequest.setUsername("AllRoles");
User user = userClient.createUser(createUserRequest);
addRoles(user.getUserId(), allRoles);
var createUserRequest2 = new CreateUserRequest();
createUserRequest2.setEmail("nokneconroles@notknecon.com");
createUserRequest2.setFirstName("No Knecon");
createUserRequest2.setLastName("Roles");
createUserRequest2.setUsername("NoKneconRoles");
User noKneconUser = userClient.createUser(createUserRequest2);
addRoles(noKneconUser.getUserId(), allButKneconRoles);
var createUserRequest3 = new CreateUserRequest();
createUserRequest3.setEmail("onlykneconroles@notknecon.com");
createUserRequest3.setFirstName("Only Knecon");
createUserRequest3.setLastName("Roles");
createUserRequest3.setUsername("OnlyKneconRoles");
User onlyKneconUser = userClient.createUser(createUserRequest3);
addRoles(onlyKneconUser.getUserId(), onlyKneconRoles);
var allUsers = userClient.getAllUsers(true);
// should not contain the knecon user but the others
assertTrue(allUsers.stream()
.anyMatch(u -> u.getUserId().equals(user.getUserId())));
assertTrue(allUsers.stream()
.anyMatch(u -> u.getUserId().equals(noKneconUser.getUserId())));
assertFalse(allUsers.stream()
.anyMatch(u -> u.getUserId().equals(onlyKneconUser.getUserId())));
var foundUser = allUsers.stream()
.filter(u -> u.getUserId().equals(user.getUserId()))
.findFirst();
assertTrue(foundUser.isPresent());
assertFalse(foundUser.get().getRoles()
.stream()
.anyMatch(ApplicationRoles::isKneconRole));
// should not be found
var e = assertThrows(FeignException.class, () -> userClient.getUserById(onlyKneconUser.getUserId()));
assertEquals(404, e.status());
// should not show any knecon roles
User userById = userClient.getUserById(user.getUserId());
assertTrue(userById.getRoles()
.stream()
.noneMatch(ApplicationRoles::isKneconRole));
List<User> applicationSpecificUsers = userClient.getApplicationSpecificUsers(true);
// should not contain the knecon user but the others
assertTrue(applicationSpecificUsers.stream()
.anyMatch(u -> u.getUserId().equals(user.getUserId())));
assertTrue(applicationSpecificUsers.stream()
.anyMatch(u -> u.getUserId().equals(noKneconUser.getUserId())));
assertFalse(applicationSpecificUsers.stream()
.anyMatch(u -> u.getUserId().equals(onlyKneconUser.getUserId())));
foundUser = applicationSpecificUsers.stream()
.filter(u -> u.getUserId().equals(user.getUserId()))
.findFirst();
assertTrue(foundUser.isPresent());
assertFalse(foundUser.get().getRoles()
.stream()
.anyMatch(ApplicationRoles::isKneconRole));
// no knecon roles are visible
assertEquals(allUsers.stream()
.filter(u -> u.getRoles()
.stream()
.anyMatch(ApplicationRoles::isKneconRole))
.count(), 0);
// we can get this user because of other roles being present
userClient.getUserById(noKneconUser.getUserId());
// but not this one as he only has knecon roles
e = assertThrows(FeignException.class, () -> userClient.getUserById(onlyKneconUser.getUserId()));
assertEquals(404, e.status());
// switch token to the user without knecon roles
tokenService.setUser("test@fforesight.com", "secret");
// create another dummy user with only SUPER_USER
var redUserAdminUserRequest = new CreateUserRequest();
redUserAdminUserRequest.setEmail("red-user-admin@knecon.com");
redUserAdminUserRequest.setUsername(redUserAdminUserRequest.getEmail());
redUserAdminUserRequest.setRoles(Set.of("SUPER_USER"));
var redUserAdmin = userClient.createUser(redUserAdminUserRequest);
// reset password for authentication
userClient.resetPassword(redUserAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
// authenticate with the newly created user
tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23");
// we should not be able to set roles of this user at all as it is not visible to us resulting in a 404
e = assertThrows(FeignException.class, () -> userClient.setRoles(onlyKneconUser.getUserId(), allButKneconRoles));
assertEquals(404, e.status());
// we can not assign a knecon rule as it is not visible to us
e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), onlyKneconRoles));
assertEquals(400, e.status());
// we should not be able to assign ourselves a knecon role as it is not visible to us
e = assertThrows(FeignException.class, () -> userClient.setRoles(redUserAdmin.getUserId(), allRoles));
assertEquals(400, e.status());
// authenticate as knecon admin again
tokenService.setUser("admin@knecon.com", "secret");
// this should still not be possible
e = assertThrows(FeignException.class, () -> userClient.setRoles(onlyKneconUser.getUserId(), allRoles));
assertEquals(404, e.status());
// and also not this
e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), allRoles));
assertEquals(400, e.status());
// we can also poll the user
userClient.getUserById(user.getUserId());
e = assertThrows(FeignException.class, () -> userClient.getUserById(onlyKneconUser.getUserId()));
assertEquals(404, e.status());
// back to having no rights
tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23");
// we can not call update profile
e = assertThrows(FeignException.class, () -> userClient.updateProfile(onlyKneconUser.getUserId(), new UpdateProfileRequest()));
assertEquals(404, e.status());
// or reset password
e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), new ResetPasswordRequest()));
assertEquals(404, e.status());
// now as a knecon admin again
tokenService.setUser("admin@knecon.com", "secret");
// we can also not see another knecon account and change their password
e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), new ResetPasswordRequest()));
assertEquals(404, e.status());
// or activate the profile
e = assertThrows(FeignException.class, () -> userClient.activateProfile(onlyKneconUser.getUserId(), true));
assertEquals(404, e.status());
// but the user with all roles can be processed
userClient.resetPassword(user.getUserId(), new ResetPasswordRequest("Secret@secured!23", false));
User activated = userClient.activateProfile(user.getUserId(), true);
activated.getRoles()
.stream()
.noneMatch(ApplicationRoles::isKneconRole);
// we create a new user with all roles
var createUserRequest4 = new CreateUserRequest();
createUserRequest4.setEmail("allroles2@knecon.com");
createUserRequest4.setFirstName("All");
createUserRequest4.setLastName("Roles2");
createUserRequest4.setUsername("AllRoles2");
User user4 = userClient.createUser(createUserRequest4);
addRoles(user4.getUserId(), allRoles);
// we attempt to delete it, should not be possible but still return 204
userClient.deleteUser(onlyKneconUser.getUserId());
// and again using the bulk call
userClient.deleteUsers(List.of(onlyKneconUser.getUserId()));
// no rights again ...
tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23");
// with this user we expect a 204 as well
userClient.deleteUser(onlyKneconUser.getUserId());
userClient.deleteUsers(List.of(onlyKneconUser.getUserId()));
// check users as knecon admin again
tokenService.setUser("admin@knecon.com", "secret");
// with this user we expect a 204 as well but the user should have removed red roles
userClient.deleteUser(user4.getUserId());
addRoles(onlyKneconUser.getUserId(), allRoles);
allUsers = userClient.getAllUsers(true);
assertTrue(allUsers.stream()
.anyMatch(u -> u.getUserId().equals(onlyKneconUser.getUserId())));
// hence, 404 when trying to get the user now
e = assertThrows(FeignException.class, () -> userClient.getUserById(user4.getUserId()));
assertEquals(404, e.status());
// give the user the old roles back
addRoles(user4.getUserId(), allButKneconRoles);
allUsers = userClient.getAllUsers(true);
var user4AfterShenanigansOpt = allUsers.stream()
.filter(u -> u.getUserId().equals(user4.getUserId()))
.findFirst();
assertTrue(user4AfterShenanigansOpt.isPresent());
user4AfterShenanigansOpt.get().setRoles(new HashSet<>());
assertEquals(user4AfterShenanigansOpt.get(), user4);
var stillOnlyKneconUserOpt = allUsers.stream()
.filter(u -> u.getUserId().equals(onlyKneconUser.getUserId()))
.findFirst();
assertTrue(stillOnlyKneconUserOpt.isPresent());
stillOnlyKneconUserOpt.get().setRoles(new HashSet<>());
assertEquals(stillOnlyKneconUserOpt.get(), onlyKneconUser);
}
@Test
public void testShowUserWithoutRoles() {
// set context and user
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
// different role sets and subsets
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
// create several users with different roles for testing
var createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("someotheruser@knecon.com");
createUserRequest.setFirstName("Some Other");
createUserRequest.setLastName("User");
createUserRequest.setUsername("SomeOtherUser");
User user = userClient.createUser(createUserRequest);
addRoles(user.getUserId(), allRoles);
var createUserRequest2 = new CreateUserRequest();
createUserRequest2.setEmail("noroles@notknecon.com");
createUserRequest2.setFirstName("No");
createUserRequest2.setLastName("Roles");
createUserRequest2.setUsername("NoRolesAtAll");
createUserRequest2.setRoles(new HashSet<>());
User noRolesUser = userClient.createUser(createUserRequest2);
User userById = userClient.getUserById(noRolesUser.getUserId());
List<User> allUsers = userClient.getAllUsers(true);
assertTrue(allUsers.stream()
.anyMatch(u -> u.getUserId().equals(noRolesUser.getUserId())));
List<User> applicationSpecificUsers = userClient.getApplicationSpecificUsers(true);
assertFalse(applicationSpecificUsers.stream()
.anyMatch(u -> u.getUserId().equals(noRolesUser.getUserId())));
}
@Test
public void testOperationsOnUserWithoutRoles() {
// set context and user
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
var createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("noroles@notknecon.com");
createUserRequest.setFirstName("No");
createUserRequest.setLastName("Roles");
createUserRequest.setUsername("NoRolesAtAll");
createUserRequest.setRoles(new HashSet<>());
User noRolesUser = userClient.createUser(createUserRequest);
userClient.resetPassword(noRolesUser.getUserId(), ResetPasswordRequest.builder().password("SuperSecret42!!").build());
userClient.activateProfile(noRolesUser.getUserId(), false);
noRolesUser = userClient.getUserById(noRolesUser.getUserId());
assertFalse(noRolesUser.isActive());
var allUsers = userClient.getAllUsers(true);
var sizeBefore = allUsers.size();
userClient.deleteUser(noRolesUser.getUserId());
allUsers = userClient.getAllUsers(true);
assertThat(allUsers).hasSize(sizeBefore - 1);
}
@Test
public void testCreateUserWithInvalidEmailFormat() {
// set context and user
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
// create several users with different roles for testing
var createUserRequest = new CreateUserRequest();
createUserRequest.setFirstName("My nice");
createUserRequest.setLastName("User");
createUserRequest.setUsername("MyNiceUser");
createUserRequest.setRoles(new HashSet<>());
createUserRequest.setEmail(null);
var e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
createUserRequest.setEmail("");
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
createUserRequest.setEmail("myniceuser@notknecon");
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
createUserRequest.setEmail("myniceuser.com");
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
createUserRequest.setEmail("myniceuser@notknecon@at.com");
e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest));
assertEquals(400, e.status());
}
@Test
public void testUpdateProfileForUserWithAllRoles() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
Set<String> allButKneconRoles = allRoles.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
CreateUserRequest createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("all.roles.user@knecon.com");
createUserRequest.setUsername("all.roles.user@knecon.com");
createUserRequest.setFirstName("All");
createUserRequest.setLastName("Roles");
var allRolesuser = userClient.createUser(createUserRequest);
addRoles(allRolesuser.getUserId(), allRoles);
assertThat(allRolesuser).isNotNull();
UpdateProfileRequest updateProfileRequest = UpdateProfileRequest.builder()
.email("all.roles.user@knecon.com")
.firstName("All")
.lastName("NewLastName")
.roles(allButKneconRoles)
.build();
var updatedUser = userClient.updateProfile(allRolesuser.getUserId(), updateProfileRequest);
assertThat(updatedUser).isNotNull();
assertThat(updatedUser.getLastName()).isEqualTo("NewLastName");
tokenService.setUser("test@fforesight.com", "secret");
updateProfileRequest.setLastName("AnotherNewLastName");
updatedUser = userClient.updateProfile(allRolesuser.getUserId(), updateProfileRequest);
assertThat(updatedUser).isNotNull();
assertThat(updatedUser.getLastName()).isEqualTo("AnotherNewLastName");
createUserRequest.setEmail("less.super.user.1@knecon.com");
createUserRequest.setUsername(createUserRequest.getEmail());
createUserRequest.setRoles(Set.of("LESS_SUPER_USER"));
var lessSuperUser = userClient.createUser(createUserRequest);
userClient.resetPassword(lessSuperUser.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
tokenService.setUser("less.super.user.1@knecon.com", "Secret@secured!23");
FeignException feignException = assertThrows(FeignException.class, () -> userClient.updateProfile(allRolesuser.getUserId(), updateProfileRequest));
assertEquals(400, feignException.status());
assertTrue(feignException.getMessage().contains("Cannot assign this role to that user. Insufficient rights"));
tokenService.setUser("admin@knecon.com", "secret");
userClient.deleteUser(lessSuperUser.getUserId());
userClient.deleteUser(allRolesuser.getUserId());
}
@Test
public void testDeleteKneconRolesUserAsNormalAdmin() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("admin@knecon.com", "secret");
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
Set<String> allButKneconRoles = allRoles.stream()
.filter(ApplicationRoles::isNoKneconRole)
.collect(Collectors.toSet());
CreateUserRequest createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("normalAdmin@knecon.com");
createUserRequest.setUsername("normalAdmin@knecon.com");
createUserRequest.setFirstName("Mister");
createUserRequest.setLastName("Admin");
var adminUser = userClient.createUser(createUserRequest);
addRoles(adminUser.getUserId(), allButKneconRoles);
assertThat(adminUser).isNotNull();
createUserRequest = new CreateUserRequest();
createUserRequest.setEmail("kneconAdmin@knecon.com");
createUserRequest.setUsername("kneconAdmin@knecon.com");
createUserRequest.setFirstName("Knecon");
createUserRequest.setLastName("Admin");
var kneconAdminuser = userClient.createUser(createUserRequest);
addRoles(kneconAdminuser.getUserId(), allRoles);
assertThat(kneconAdminuser).isNotNull();
userClient.resetPassword(adminUser.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
tokenService.setUser("normalAdmin@knecon.com", "Secret@secured!23");
userClient.deleteUser(kneconAdminuser.getUserId());
List<User> allUsers = userClient.getAllUsers(true);
assertTrue(allUsers.stream()
.noneMatch(u -> u.getUserId().equals(kneconAdminuser.getUserId())));
List<User> unfilteredUsers = userService.getAllUsers();
assertTrue(unfilteredUsers.stream()
.anyMatch(u -> u.getUserId().equals(kneconAdminuser.getUserId())));
tokenService.setUser("admin@knecon.com", "secret");
userClient.deleteUser(adminUser.getUserId());
}
private UsersResource getTenantUsersResource() {
return realmService.realm(TenantContext.getTenantId()).users();
}
private UserResource getUserResource(String userId) {
return this.getTenantUsersResource()
.get(userId);
}
private RoleRepresentation getRoleRepresentation(String role) {
return realmService.realm(TenantContext.getTenantId()).roles()
.get(role).toRepresentation();
}
private void addRoles(String userId, Set<String> roles) {
getUserResource(userId).roles()
.realmLevel()
.add(roles.stream()
.map(this::getRoleRepresentation)
.toList());
}
}

View File

@ -4,8 +4,6 @@ import static org.mockito.Mockito.when;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -15,21 +13,18 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.TenantsClient;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.internal.InternalTenantsClient;
import com.knecon.fforesight.tenantusermanagement.TenantUserManagementServiceApplication;
import com.knecon.fforesight.tenantusermanagement.service.KeyCloakRoleManagerService;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.internal.ResteasyClientBuilderImpl;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.KeycloakBuilder;
@Disabled
@ActiveProfiles(profiles = "taas")
@ExtendWith(SpringExtension.class)
@EnableFeignClients(basePackageClasses = {TenantsClient.class, InternalTenantsClient.class})
@ -52,7 +47,8 @@ public class TenantSyncUtils {
KeyCloakRoleManagerService keyCloakRoleManagerService;
@Test
// @Test
// @Disabled
public void syncTenant() {
var adminClient = KeycloakBuilder.builder()
@ -62,17 +58,17 @@ public class TenantSyncUtils {
.clientSecret(CLIENT_SECRET)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(10)
.disableTrustManager()
.build())
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(10)
.disableTrustManager()
.build())
.build();
var realm = adminClient.realm(REALM);
when(realmService.realm(REALM)).thenReturn(realm);
keyCloakRoleManagerService.updateRoles(REALM, TenantApplicationType.RedactManager);
keyCloakRoleManagerService.updateRoles(REALM);
}
}

View File

@ -1,8 +1,5 @@
package com.knecon.fforesight.tenantusermanagement.utils;
import static com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer.MONGO_DATABASE;
import static com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer.MONGO_PASSWORD;
import static com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer.MONGO_USERNAME;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
@ -11,20 +8,15 @@ import java.util.UUID;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.TenantsClient;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantusermanagement.api.internal.InternalTenantsResource;
import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest;
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
import com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer;
import com.knecon.fforesight.tenantusermanagement.testcontainers.SpringPostgreSQLTestContainer;
import lombok.RequiredArgsConstructor;
import software.amazon.awssdk.regions.Region;
@ -46,58 +38,45 @@ public class TestTenantService {
} catch (Exception e) {
// not found
createTenant(testTenantId, actualPort, true);
createUser(testTenantId, actualPort, true);
}
}
private void createTenant(String testTenantId, int actualPort, boolean withStorage) {
private void createUser(String testTenantId, int actualPort, boolean withStorage) {
// not found
CreateTenantRequest tenantRequest;
var tenantRequestBuilder = CreateTenantRequest.builder()
TenantRequest tenantRequest;
var tenantRequestBuilder = TenantRequest.builder()
.tenantId(testTenantId)
.displayName(testTenantId)
.guid(UUID.randomUUID().toString())
.defaultUsers(List.of(TenantUser.builder()
.roles(Set.of("SUPER_USER"))
.username("test@fforesight.com")
.password("secret")
.email("test@fforesight.com")
.build(),
TenantUser.builder()
.roles(Set.of(ApplicationRoles.KNECON_ADMIN_ROLE, ApplicationRoles.KNECON_SUPPORT_ROLE))
.username("admin@knecon.com")
.password("secret")
.email("admin@knecon.com")
.build()))
.applicationType(TenantApplicationType.RedactManager)
.defaultUsers(List.of(TenantUser.builder().roles(Set.of("SUPER_USER")).username("test@fforesight.com").password("secret").email("test@fforesight.com").build()))
.databaseConnection(DatabaseConnection.builder()
.driver("postgresql")
.host(SpringPostgreSQLTestContainer.getInstance().getHost())
.port(String.valueOf(SpringPostgreSQLTestContainer.getInstance().getFirstMappedPort()))
.database(SpringPostgreSQLTestContainer.getInstance().getDatabaseName())
.schema(testTenantId)
.username(SpringPostgreSQLTestContainer.getInstance().getUsername())
.password(SpringPostgreSQLTestContainer.getInstance().getPassword())
.build())
.searchConnection(SearchConnectionRequest.builder().hosts(Set.of("localhost")).port(9200).scheme("http").numberOfShards("1").numberOfReplicas("5").build())
.mongoDBConnection(MongoDBConnection.builder()
.prefix("mongodb")
.username(MONGO_USERNAME)
.password(MONGO_PASSWORD)
.address(MongoDBTestContainer.getInstance().getHost() + ":" + MongoDBTestContainer.getInstance().getFirstMappedPort())
.database(MONGO_DATABASE)
.options("")
.build());
.driver("postgresql")
.host(SpringPostgreSQLTestContainer.getInstance().getHost())
.port(String.valueOf(SpringPostgreSQLTestContainer.getInstance().getFirstMappedPort()))
.database(SpringPostgreSQLTestContainer.getInstance().getDatabaseName())
.schema(testTenantId)
.username(SpringPostgreSQLTestContainer.getInstance().getUsername())
.password(SpringPostgreSQLTestContainer.getInstance().getPassword())
.build())
.searchConnection(SearchConnectionRequest.builder()
.hosts(Set.of("localhost"))
.port(9200)
.scheme("http")
.numberOfShards("1")
.numberOfReplicas("5")
.build());
if (withStorage) {
tenantRequest = tenantRequestBuilder.s3StorageConnection(S3StorageConnection.builder()
.key("minioadmin")
.secret("minioadmin")
.bucketName(testTenantId.replaceAll("-", "").replaceAll("_", ""))
.endpoint("http://localhost:" + actualPort)
.region(Region.AWS_GLOBAL.id())
.build()).build();
.key("minioadmin")
.secret("minioadmin")
.bucketName(testTenantId.replaceAll("-", "").replaceAll("_", ""))
.endpoint("http://localhost:" + actualPort)
.region(Region.AWS_GLOBAL.id())
.build()).build();
} else {
tenantRequest = tenantRequestBuilder.build();
}
@ -121,7 +100,7 @@ public class TestTenantService {
} catch (Exception e) {
// not found
createTenant(testTenantId, 0, false);
createUser(testTenantId, 0, false);
}
}

View File

@ -9,8 +9,6 @@ import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.service.TenantApplicationTypeService;
import jakarta.ws.rs.BadRequestException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -26,8 +24,6 @@ public class TokenService {
@Autowired
private TenantUserManagementProperties tenantUserManagementProperties;
@Autowired
private TenantApplicationTypeService tenantApplicationTypeService;
private String username;
private String password;
private String accessToken;
@ -58,7 +54,7 @@ public class TokenService {
.realm(TenantContext.getTenantId())
.username(username)
.password(password)
.clientId(tenantApplicationTypeService.getCurrentProperties().getApplicationClientId())
.clientId(tenantUserManagementProperties.getApplicationClientId())
.grantType(OAuth2Constants.PASSWORD)
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)

View File

@ -0,0 +1,158 @@
server:
port: 28181
management:
endpoint:
metrics.enabled: ${monitoring.enabled:false}
prometheus.enabled: ${monitoring.enabled:false}
health.enabled: true
endpoints.web.exposure.include: prometheus, health, metrics
metrics.export.prometheus.enabled: ${monitoring.enabled:false}
info:
description: Tenant User Management Service
spring:
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:5432}/${PSQL_DATABASE:master}?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
driverClassName: org.postgresql.Driver
username: ${PSQL_USERNAME:fforesight}
password: ${PSQL_PASSWORD:fforesight}
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
maximumPoolSize: 2
minimum-idle: 2
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
accept-single-value-as-array: true
main:
allow-bean-definition-overriding: true
allow-circular-references: true
jpa:
open-in-view: true
database-platform: org.hibernate.dialect.PostgreSQL95Dialect
hibernate:
ddl-auto: none
naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
jdbc:
batch_size: 1000
order_inserts: true
order_updates: true
cache:
type: redis
mvc:
pathmatch:
matching-strategy: ant-path-matcher
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:user}
password: ${RABBITMQ_PASSWORD:rabbitmq}
listener:
simple:
acknowledge-mode: AUTO
concurrency: 5
retry:
enabled: true
max-attempts: 3
max-interval: 15000
prefetch: 1
liquibase:
change-log: classpath:/db/changelog/db.changelog-master.yaml
enabled: true
application:
name: tenant-user-management
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
fforesight:
keycloak:
ignored-endpoints: [ '/actuator/health', '/tenant-user-management','/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs','/tenant-user-management/tenants/simple' ]
enabled: true
springdoc:
base-path: '/tenant-user-management'
auth-server-url: '/auth'
enabled: true
default-client-id: 'swagger-ui-client'
default-tenant: 'fforesight'
tenant-exchange:
name: 'tenants-exchange'
user-exchange:
name: 'users-exchange'
tenant-user-management:
base-path: '/tenant-user-management'
realm: master
server-url: http://localhost:28181
client-secret: adminClientSecret
client-id: adminClient
login-theme: redaction
kc-role-mapping:
roles:
- name: SUPER_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: LESS_SUPER_USER
set-by-default: true
rank: 10
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
access-token-life-span: 86400
application-name: tenant-user-management
application-client-id: tenant-user-management
swagger-client-secret: 'testSecret123!'
app-prefix: 'fforesight'
storage:
backend: both
cors.enabled: true
springdoc:
packages-to-scan: [ 'com.knecon.fforesight.tenantusermanagement.controller.external' ]

View File

@ -1,203 +0,0 @@
server:
port: 28181
persistence-service.url: "http://persistence-service-v1:8090"
management:
endpoint:
metrics.enabled: ${monitoring.enabled:false}
prometheus.enabled: ${monitoring.enabled:false}
health.enabled: true
endpoints.web.exposure.include: prometheus, health, metrics
metrics.export.prometheus.enabled: ${monitoring.enabled:false}
info:
description: Tenant User Management Service
lifecycle:
base-package: com.knecon.fforesight.tenantusermanagement
spring:
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:5432}/${PSQL_DATABASE:master}?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
driverClassName: org.postgresql.Driver
username: ${PSQL_USERNAME:fforesight}
password: ${PSQL_PASSWORD:fforesight}
platform: org.hibernate.dialect.PostgreSQL10Dialect
hikari:
maximumPoolSize: 10
minimum-idle: 2
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
accept-single-value-as-array: true
main:
allow-bean-definition-overriding: true
allow-circular-references: true
jpa:
open-in-view: true
database-platform: org.hibernate.dialect.PostgreSQL10Dialect
hibernate:
ddl-auto: none
naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
jdbc:
batch_size: 1000
order_inserts: true
order_updates: true
cache:
type: redis
mvc:
pathmatch:
matching-strategy: ant-path-matcher
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:user}
password: ${RABBITMQ_PASSWORD:rabbitmq}
listener:
simple:
acknowledge-mode: AUTO
concurrency: 5
retry:
enabled: true
max-attempts: 3
max-interval: 15000
prefetch: 1
liquibase:
change-log: classpath:/db/changelog/db.changelog-master.yaml
enabled: true
application:
name: tenant-user-management
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
fforesight:
keycloak:
ignored-endpoints: [ '/actuator/health', '/tenant-user-management','/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs' ]
enabled: true
springdoc:
base-path: '/tenant-user-management'
auth-server-url: '/auth'
enabled: true
default-client-id: 'swagger-ui-client'
default-tenant: 'fforesight'
tenant-exchange:
name: 'tenants-exchange'
user-exchange:
name: 'users-exchange'
tenant-user-management:
base-path: '/tenant-user-management'
server-url: http://localhost:28181
realm: master
client-secret: adminClientSecret
client-id: adminClient
swagger-client-secret: 'testSecret123!'
application-types:
redactmanager:
login-theme: redaction
kc-role-mapping:
roles:
- name: SUPER_USER
set-by-default: true
rank: 100
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: LESS_SUPER_USER
set-by-default: true
rank: 10
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: KNECON_ADMIN
set-by-default: true
rank: 1000
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
- name: KNECON_SUPPORT
set-by-default: true
rank: 1000
permissions:
- 'fforesight-read-general-configuration'
- 'fforesight-write-general-configuration'
- 'fforesight-manage-user-preferences'
- 'fforesight-read-users'
- 'fforesight-read-all-users'
- 'fforesight-write-users'
- 'fforesight-update-my-profile'
- 'fforesight-create-tenant'
- 'fforesight-get-tenants'
- 'fforesight-update-tenant'
- 'fforesight-deployment-info'
- 'fforesight-read-smtp-configuration'
- 'fforesight-write-smtp-configuration'
- 'fforesight-read-identity-provider-config'
- 'fforesight-write-identity-provider-config'
access-token-life-span: 86400
application-name: tenant-user-management
application-client-id: tenant-user-management
app-prefix: 'fforesight'
storage:
backend: both
cors.enabled: true
springdoc:
packages-to-scan: [ 'com.knecon.fforesight.tenantusermanagement.controller.external' ]