Compare commits

..

1 Commits

Author SHA1 Message Date
Christoph Schabert
037056f1e9 Fix KC version 2024-02-06 11:01:41 +01:00
87 changed files with 1105 additions and 5169 deletions

View File

@ -2,7 +2,7 @@ version: '2'
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
image: quay.io/keycloak/keycloak:23.0
command: start-dev
environment:
KEYCLOAK_ADMIN: admin

View File

@ -94,21 +94,16 @@ 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: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 +113,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 +125,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

@ -1,86 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.api.external;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.web.bind.annotation.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
@Tag(name = "Identity provider configuration endpoints", description = "Provides operations related to the identity providers")
@ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests."), @ApiResponse(responseCode = "403", description = "Forbidden")})
public interface IdentityProviderConfigurationResource {
String API_PATH = "/configuration/identity-providers";
String IDENTITY_PROVIDER_ALIAS_PARAM = "providerAlias";
String IDENTITY_PROVIDER_ALIAS_PATH_PARAM = "/{" + IDENTITY_PROVIDER_ALIAS_PARAM + "}";
String IMPORT_SUB_PATH = "/import";
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@GetMapping(value = API_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets all existing identity providers.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
IdentityProviderList getIdentityProviders();
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@GetMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets an existing identity provider.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
IdentityProviderModel getIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
@ResponseStatus(value = HttpStatus.CREATED)
@ResponseBody
@PostMapping(value = API_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Creates a new identity provider", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
ResponseEntity<IdentityProviderModel> createIdentityProvider(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
@ResponseStatus(value = HttpStatus.CREATED)
@ResponseBody
@PostMapping(value = API_PATH + IMPORT_SUB_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Creates a new identity provider", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderWithDescriptorRequest identityProvider);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PutMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Updates an existing identity provider", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully updated the identity provider"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body")})
ResponseEntity<IdentityProviderModel> updateIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias,
@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM)
@Operation(summary = "Deletes an existing identity provider.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the identity provider."), @ApiResponse(responseCode = "404", description = "Not found")})
void deleteIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
}

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;
@ -27,57 +27,62 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.OK)
public interface TenantsResource {
String TENANTS_PATH = "/tenants";
String TENANT_ID_PARAM = "tenantId";
String TENANT_ID_PATH_PARAM = "/{" + TENANT_ID_PARAM + "}";
String TENANTS_TENANT_ID_PATH = TENANTS_PATH + TENANT_ID_PATH_PARAM;
@PostMapping(value = TENANTS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/tenants", 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.")})
@DeleteMapping(value = TENANTS_TENANT_ID_PATH)
@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/{tenantId}")
void deleteTenant(@PathVariable("tenantId") String tenantId);
@GetMapping(value = TENANTS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets all existing tenants", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<TenantResponse> getTenants();
@GetMapping(value = TENANTS_TENANT_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/tenants/{tenantId}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets an existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
TenantResponse getTenant(@PathVariable("tenantId") String tenantId);
@PutMapping(value = TENANTS_TENANT_ID_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@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);
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/details", consumes = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/tenants/{tenantId}/details", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Update details", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request);
@GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets all existing tenants in a simplified format", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
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")})
DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId);
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/tenants/{tenantId}/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Sync existing tenant", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void syncTenant(@PathVariable("tenantId") String tenantId, @RequestBody JsonNode payload);

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

@ -1,8 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("${fforesight.tenant-user-management.base-path-v2:/api}")
public interface PublicResourceV2 {
}

View File

@ -1,38 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
public interface UserResourceV2 {
String USER_REST_PATH = "/users";
String USER_ID = "userId";
String USER_ID_PATH_VARIABLE = "/{" + USER_ID + "}";
String USERNAME_PARAM = "username";
@ResponseBody
@Operation(summary = "Get a list of users", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid offset or limit specified.")})
@GetMapping(value = USER_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username);
@ResponseBody
@Operation(summary = "Retrieve a specific user by its identifier.", 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.")})
@GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
User getUserById(@PathVariable(USER_ID) String userId);
}

View File

@ -1,29 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
import java.util.Set;
import java.util.TreeSet;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class User {
private String id;
private String username;
private String email;
private String firstName;
private String lastName;
private boolean active;
@Builder.Default
private Set<String> roles = new TreeSet<>();
}

View File

@ -1,18 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserList {
private List<User> users = new ArrayList<>();
}

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

@ -1,20 +1,14 @@
package com.knecon.fforesight.tenantusermanagement.controller;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.knecon.fforesight.tenantusermanagement.exception.ConflictException;
import com.knecon.fforesight.tenantusermanagement.model.ErrorMessage;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
@ -25,22 +19,16 @@ public class ControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorMessage> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
var errorList = e.getFieldErrors();
String errorListAsString = errorList.stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.joining(", "));
return new ResponseEntity<>(new ErrorMessage(String.format("You have empty/wrong formatted parameters: %s", errorListAsString)), HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> handleNotFound(NotFoundException e) {
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ErrorMessage> handleForbiddenAccess(ForbiddenException e) {
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.FORBIDDEN);
}
@ -51,42 +39,10 @@ public class ControllerAdvice {
return new ResponseEntity<>(new ErrorMessage(e.getReason()), e.getStatusCode());
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorMessage> handleBadRequestException(BadRequestException e) {
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorMessage> handleAccessDeniedException(AccessDeniedException e) {
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.FORBIDDEN);
}
@ExceptionHandler(ConflictException.class)
public ResponseEntity<ErrorMessage> handleConflictException(ConflictException e) {
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.CONFLICT);
}
@ExceptionHandler({HttpMessageNotReadableException.class})
public ResponseEntity<ErrorMessage> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
String errorMessage = e.getMessage();
var cause = e.getCause();
if (cause instanceof InvalidFormatException invalidFormatException) {
errorMessage = cause.getMessage();
Class<?> targetType = invalidFormatException.getTargetType();
if (targetType != null && targetType.isEnum()) {
errorMessage = String.format("Unsupported value for %s", targetType.getSimpleName());
}
}
return new ResponseEntity<>(new ErrorMessage(errorMessage), HttpStatus.BAD_REQUEST);
}
}

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

@ -1,269 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.controller.external;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_IDENTITY_PROVIDER_CONFIGURATION;
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.WRITE_IDENTITY_PROVIDER_CONFIGURATION;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ResponseStatusException;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
import com.knecon.fforesight.tenantusermanagement.exception.ConflictException;
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderExistsAlreadyException;
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderNotFoundException;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import com.knecon.fforesight.tenantusermanagement.service.KeyCloakAdminClientService;
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
import com.knecon.fforesight.tenantusermanagement.utils.IdentityProviderMappingService;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class IdentityProviderConfigurationController implements IdentityProviderConfigurationResource, PublicResource {
private final RealmService realmService;
private final TenantUserManagementProperties tenantUserManagementProperties;
private final KeyCloakAdminClientService keyCloakAdminClientService;
@Override
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
public IdentityProviderList getIdentityProviders() {
return new IdentityProviderList(getRealmRepresentation().getIdentityProviders()
.stream()
.map(IdentityProviderMappingService::toModelFromRepresentation)
.toList());
}
@Override
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
public IdentityProviderModel getIdentityProvider(String identityProviderAlias) {
return IdentityProviderMappingService.toModelFromRepresentation(getIdentityProviderRepresentation(identityProviderAlias));
}
private IdentityProviderRepresentation getIdentityProviderRepresentation(String identityProviderAlias) {
return getRealmRepresentation().getIdentityProviders()
.stream()
.filter(identityProviderRepresentation -> identityProviderRepresentation.getAlias().equals(identityProviderAlias))
.findFirst()
.orElseThrow(() -> new IdentityProviderNotFoundException(identityProviderAlias));
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
public ResponseEntity<IdentityProviderModel> createIdentityProvider(IdentityProviderRequest identityProviderRequest) {
if (identityProviderRequest.getAlias() == null) {
throw new BadRequestException("Alias must not be null.");
}
if (identityProviderRequest.getDisplayName() == null || identityProviderRequest.getDisplayName().isEmpty()) {
identityProviderRequest.setDisplayName(identityProviderRequest.getAlias());
}
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderRequest.getDisplayName());
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
public ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(IdentityProviderWithDescriptorRequest identityProviderWithDescriptorRequest) {
if (identityProviderWithDescriptorRequest.getDisplayName() == null || identityProviderWithDescriptorRequest.getDisplayName().isEmpty()) {
identityProviderWithDescriptorRequest.setDisplayName(identityProviderWithDescriptorRequest.getAlias());
}
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderWithDescriptorRequest.getDisplayName());
Map<String, Object> requestMap = new HashMap<>();
requestMap.put("providerId", identityProviderWithDescriptorRequest.getProviderId());
requestMap.put("fromUrl", identityProviderWithDescriptorRequest.getSamlEntityDescriptorURL());
try {
Map<String, String> configurationsMap = realmService.realm(getTenantId()).identityProviders().importFrom(requestMap);
if (configurationsMap == null || configurationsMap.isEmpty()) {
throw new BadRequestException("Could not set config from provided descriptor.");
}
configurationsMap.put("entityId", identityProviderWithDescriptorRequest.getEntityId());
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromDescriptorRequestAndConfig(identityProviderWithDescriptorRequest,
configurationsMap);
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
} catch (InternalServerErrorException internalServerErrorException) {
throw new BadRequestException("Provided descriptor is malformed.");
}
}
private ResponseEntity<IdentityProviderModel> callKeyCloakIdentityProvidersCreateForModel(IdentityProviderModel identityProviderModel) {
identityProviderModel.setSpecificDefaults();
try (Response response = realmService.realm(getTenantId()).identityProviders().create(IdentityProviderMappingService.toRepresentationFromModel(identityProviderModel))) {
var httpStatus = HttpStatus.valueOf(response.getStatus());
switch (httpStatus) {
case CREATED -> {
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderModel.getAlias());
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.valueOf(response.getStatus()));
}
case CONFLICT -> throw new IdentityProviderExistsAlreadyException(identityProviderModel.getAlias());
default -> throw new ResponseStatusException(httpStatus, extractKeycloakErrorMessageInfos(response.readEntity(String.class)));
}
}
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
public ResponseEntity<IdentityProviderModel> updateIdentityProvider(String identityProviderAlias, IdentityProviderRequest identityProviderRequest) {
identityProviderRequest.setAlias(identityProviderAlias);
getIdentityProviderRepresentation(identityProviderAlias);
checkIfOtherIdpHasSameDisplayNameOnUpdate(identityProviderAlias, identityProviderRequest.getDisplayName());
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
identityProviderModel.setSpecificDefaults();
var restTemplate = new RestTemplate();
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderRequest.getAlias();
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Authorization", "Bearer " + getToken());
HttpEntity<IdentityProviderModel> httpEntity = new HttpEntity<>(identityProviderModel, headers);
try {
ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
var httpStatus = HttpStatus.valueOf(response.getStatusCode().value());
if (httpStatus == HttpStatus.NO_CONTENT) {
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderRequest.getAlias());
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.OK);
} else if (httpStatus.is4xxClientError()) {
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
} else {
throw new ResponseStatusException(httpStatus, httpStatus.getReasonPhrase());
}
} catch (HttpClientErrorException e) {
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
}
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
public void deleteIdentityProvider(String identityProviderAlias) {
getIdentityProviderRepresentation(identityProviderAlias);
var restTemplate = new RestTemplate();
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderAlias;
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Authorization", "Bearer " + getToken());
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
try {
restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
} catch (HttpClientErrorException e) {
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
}
}
private void checkIfOtherIdpHasSameDisplayNameOnUpdate(String alias, String displayName) {
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders()
.stream()
.filter(identityProviderModel -> !identityProviderModel.getAlias().equals(alias))
.toList(), displayName);
}
private void checkIfOtherIdpHasSameDisplayNameOnCreate(String displayName) {
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders(), displayName);
}
private void checkIfOtherIdpHasSameDisplayName(List<IdentityProviderModel> identityProviderModelList, String displayName) {
if (identityProviderModelList.stream()
.anyMatch(idp -> idp.getDisplayName().isEmpty() ? idp.getAlias().equals(displayName) : idp.getDisplayName().equals(displayName))) {
throw new ConflictException("Identity provider with this display name already exists.");
}
}
private static String extractKeycloakErrorMessageInfos(String keyCloakErrorMessage) {
String errorMessageRegex = "\\{\"errorMessage\":\"(.*?)\"}";
Pattern pattern = Pattern.compile(errorMessageRegex);
Matcher matcher = pattern.matcher(keyCloakErrorMessage);
if (matcher.find()) {
return matcher.group(1);
}
return keyCloakErrorMessage;
}
private String getTenantId() {
return TenantContext.getTenantId();
}
private RealmRepresentation getRealmRepresentation() {
return realmService.realm(getTenantId()).toRepresentation();
}
private String getKeycloakIdentityProviderInstancesUrl() {
return tenantUserManagementProperties.getServerUrl() + "/admin/realms/" + getTenantId() + "/identity-provider/instances/";
}
private String getToken() {
return keyCloakAdminClientService.getAdminClient().tokenManager().getAccessToken().getToken();
}
}

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

@ -1,57 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.controller.external.v2;
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.knecon.fforesight.tenantusermanagement.api.external.v2.PublicResourceV2;
import com.knecon.fforesight.tenantusermanagement.api.external.v2.UserResourceV2;
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
import com.knecon.fforesight.tenantusermanagement.controller.external.UserController;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@Tag(name = "6. Users endpoints", description = "Operations related to users.")
public class UserControllerV2 implements UserResourceV2, PublicResourceV2 {
private final UserController userController;
public List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username) {
var users = userController.getApplicationSpecificUsers(false)
.stream()
.map(this::convertUser);
if (username == null) {
return users.toList();
}
return users.filter(user -> user.getUsername().equals(username)).toList();
}
public User getUserById(@PathVariable(USER_ID) String userId) {
return convertUser(userController.getUserById(userId));
}
private User convertUser(com.knecon.fforesight.tenantusermanagement.model.User user){
return User.builder()
.id(user.getUserId())
.username(user.getUsername())
.email(user.getEmail())
.firstName(user.getFirstName())
.lastName(user.getLastName())
.active(user.isActive())
.roles(user.getRoles())
.build();
}
}

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

@ -1,23 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.exception;
public class ConflictException extends RuntimeException {
public ConflictException(String message) {
super(message);
}
public ConflictException(String message, Throwable t) {
super(message, t);
}
public static ConflictException withObjectName(String objectName) {
return new ConflictException(String.format("An object of type %s already exists.", objectName));
}
}

View File

@ -1,14 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.CONFLICT)
public class IdentityProviderExistsAlreadyException extends ConflictException {
private static final String IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE = "Identity provider with alias %s already exists in keycloak.";
public IdentityProviderExistsAlreadyException(String identityProviderAlias) {
super(String.format(IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE, identityProviderAlias));
}
}

View File

@ -1,16 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import jakarta.ws.rs.NotFoundException;
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class IdentityProviderNotFoundException extends NotFoundException {
private static final String IDENTITY_PROVIDER_NOT_FOUND_MESSAGE = "Identity provider with alias %s not found in keycloak.";
public IdentityProviderNotFoundException(String identityProviderAlias) {
super(String.format(IDENTITY_PROVIDER_NOT_FOUND_MESSAGE, identityProviderAlias));
}
}

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

@ -1,10 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import java.util.Map;
public interface ExtensibleModel {
void setNotMappedFields(Map<String, String> notMappedFields);
Map<String, String> getNotMappedFields();
}

View File

@ -1,128 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
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 information about an identity provider configuration.")
public class IdentityProviderConfigModel implements ExtensibleModel {
@Builder.Default
@Schema(description = "Whether the external identity provider is allowed to create a new identifier to represent the principal")
private Boolean allowCreate = true;
@Builder.Default
@Schema(description = "Display order : It defines the order of the providers in GUI (for example, on the Login page). The lowest number will be applied first.")
private Integer guiOrder = 0;
@Schema(description = "Service provider entity ID : It is used to uniquely identify this SAML Service Provider.")
private String entityId;
@Schema(description = "Identity provider entity ID : It is used to validate the Issuer for received SAML assertions. If empty, no Issuer validation is performed.")
private String idpEntityId;
@Schema(description = "The Url that must be used to send authentication requests (SAML AuthnRequest).")
private String singleSignOnServiceUrl;
@Builder.Default
@Schema(description = "The Url that must be used to send logout requests.")
private String singleLogoutServiceUrl = "";
@Builder.Default
@Schema(description = "Name of the Attribute Consuming Service profile to advertise in the SP metadata.")
private String attributeConsumingServiceName = "";
@Builder.Default
@Schema(description = "Backchannel logout : Does the external IDP support backchannel logout?")
private Boolean backchannelSupported = false;
@Builder.Default
@Schema(description = "Specifies the URI reference corresponding to a name identifier format.")
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
@Builder.Default
@Schema(description = "Used to identify and track external users from the assertion. Default is using Subject NameID, alternatively can be set up as identifying attribute.")
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
@Builder.Default
@Schema(description = "HTTP-POST binding response : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
private Boolean postBindingResponse = false;
@Builder.Default
@Schema(description = "HTTP-POST binding for AuthnRequest : Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
private Boolean postBindingAuthnRequest = false;
@Builder.Default
@Schema(description = "HTTP-POST binding logout : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
private Boolean postBindingLogout = false;
@Builder.Default
@Schema(description = "Indicates whether the identity provider expects a signed AuthnRequest.")
private Boolean wantAuthnRequestsSigned = false;
@Builder.Default
@Schema(description = "Indicates whether this service provider expects a signed Assertion.")
private Boolean wantAssertionsSigned = false;
@Builder.Default
@Schema(description = "Indicates whether this service provider expects an encrypted Assertion.")
private Boolean wantAssertionsEncrypted = false;
@Builder.Default
@Schema(description = "Force authentication : Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.")
private Boolean forceAuthn = false;
@Builder.Default
@Schema(description = "Enable/disable signature validation of external IDP signatures.")
private Boolean validateSignature = false;
@Builder.Default
@Schema(description = "Sign service provider metadata : Enable/disable signature of the provider SAML metadata.")
private Boolean signSpMetadata = false;
@Builder.Default
@Schema(description = "Pass subject : During login phase, forward an optional login_hint query parameter to SAML AuthnRequest's Subject.")
private Boolean loginHint = false;
@Builder.Default
@Schema(description = "Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero.")
private Integer allowedClockSkew = 0;
@Builder.Default
@Schema(description = "Index of the Attribute Consuming Service profile to request during authentication.")
private Integer attributeConsumingServiceIndex = 0;
@Builder.Default
@Schema(description = "If hidden, login with this provider is possible only if requested explicitly, for example using the 'kc_idp_hint' parameter.")
private Boolean hideOnLoginPage = false;
@Builder.Default
@Schema(description = "Default sync mode for all mappers. The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider.")
@JsonInclude(JsonInclude.Include.NON_NULL)
private IdentityProviderSyncMode syncMode = IdentityProviderSyncMode.IMPORT;
@Schema(description = "The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1'.")
@JsonInclude(JsonInclude.Include.NON_NULL)
private IdentityProviderSignatureAlgorithm signatureAlgorithm;
@Schema(description = "SAML signature key name : Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counter-party, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works.")
@JsonInclude(JsonInclude.Include.NON_NULL)
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer;
@Builder.Default
@Schema(description = "All not mapped configurations")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, String> notMappedFields = new HashMap<>();
}

View File

@ -1,65 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Create request for the identity provider configuration.")
public class IdentityProviderConfigRequest {
@Builder.Default
@Schema(description = "Whether the external identity provider is allowed to create a new identifier to represent the principal (default: true)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Boolean allowCreate = true;
@Builder.Default
@Schema(description = "Display order : It defines the order of the providers in GUI (for example, on the Login page). The lowest number will be applied first. (default: 0)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Integer guiOrder = 0;
@Schema(description = "Service provider entity ID : It is used to uniquely identify this SAML Service Provider.", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String entityId;
@Schema(description = "Identity provider entity ID : It is used to validate the Issuer for received SAML assertions. If empty, no Issuer validation is performed.", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String idpEntityId;
@Schema(description = "The Url that must be used to send authentication requests (SAML AuthnRequest).", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String singleSignOnServiceUrl;
@Builder.Default
@Schema(description = "Specifies the URI reference corresponding to a name identifier format. (default: PERSISTENT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
@Builder.Default
@Schema(description = "Used to identify and track external users from the assertion. Default is using Subject NameID, alternatively can be set up as identifying attribute. (default: SUBJECT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
@Builder.Default
@Schema(description = "HTTP-POST binding response : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Boolean postBindingResponse = false;
@Builder.Default
@Schema(description = "HTTP-POST binding for AuthnRequest : Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Boolean postBindingAuthnRequest = false;
@Builder.Default
@Schema(description = "Indicates whether the identity provider expects a signed AuthnRequest. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Boolean wantAuthnRequestsSigned = false;
@Builder.Default
@Schema(description = "The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1'. (only used when wantAuthnRequestsSigned is true, default: RSA_SHA256)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private IdentityProviderSignatureAlgorithm signatureAlgorithm = IdentityProviderSignatureAlgorithm.RSA_SHA256;
@Builder.Default
@Schema(description = "SAML signature key name : Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counter-party, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works. (only used when wantAuthnRequestsSigned is true, default: KEY_ID)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer = IdentityProviderSAMLSignatureKeyName.KEY_ID;
}

View File

@ -1,19 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IdentityProviderList {
@Builder.Default
private List<IdentityProviderModel> identityProviders = new ArrayList<>();
}

View File

@ -1,69 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Builder;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Object containing information about an identity provider.")
public class IdentityProviderModel implements ExtensibleModel {
@Schema(description = "Alias of the identity provider used for identification")
private String alias;
@Builder.Default
@Schema(description = "Configuration of the identity provider")
private IdentityProviderConfigModel config = new IdentityProviderConfigModel();
@Builder.Default
@Schema(description = "Display name of the identity provider in keycloak")
private String displayName = "";
@Builder.Default
@Schema(description = "The ID of the SSO technology provider")
private String providerId = "saml";
@Builder.Default
@Schema(description = "Enable/disable if tokens must be stored after authenticating users.")
private Boolean storeToken = false;
@Builder.Default
@Schema(description = "Stored tokens readable : Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.")
private Boolean addReadTokenRoleOnCreate = false;
@Builder.Default
@Schema(description = "If enabled, email provided by this provider is not verified even if verification is enabled for the realm.")
private Boolean trustEmail = false;
@Builder.Default
@Schema(description = "Account linking only : If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider")
private Boolean linkOnly = false;
@Builder.Default
@Schema(description = "First login flow : Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that no Keycloak account is currently linked to the authenticated identity provider account.")
private String firstBrokerLoginFlowAlias = "first broker login";
@Builder.Default
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, String> notMappedFields = new HashMap<>();
public void setSpecificDefaults() {
this.setStoreToken(true);
this.setTrustEmail(true);
this.getConfig().setSyncMode(IdentityProviderSyncMode.FORCE);
}
}

View File

@ -1,43 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.ws.rs.BadRequestException;
import lombok.Getter;
@Getter
public enum IdentityProviderNameIDPolicyFormat {
PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
EMAIL("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"),
KERBEROS("urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"),
X_509_SUBJECT_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"),
WINDOWS_DOMAIN_QUALIFIED_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName"),
UNSPECIFIED("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
private final String representation;
IdentityProviderNameIDPolicyFormat(String representation) {
this.representation = representation;
}
@Override
@JsonValue
public String toString() {
return representation;
}
public static IdentityProviderNameIDPolicyFormat fromRepresentation(String value) {
for (IdentityProviderNameIDPolicyFormat v : values()) {
if (v.getRepresentation().equalsIgnoreCase(value)) {
return v;
}
}
throw new BadRequestException("Unsupported identity provider name ID policy format");
}
}

View File

@ -1,24 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import jakarta.ws.rs.BadRequestException;
public enum IdentityProviderPrincipalType {
SUBJECT,
ATTRIBUTE,
FRIENDLY_ATTRIBUTE;
public static IdentityProviderPrincipalType fromStringValue(String value) {
for (IdentityProviderPrincipalType v : values()) {
if (v.toString().equalsIgnoreCase(value)) {
return v;
}
if (value.equalsIgnoreCase("Subject NameID")) {
return SUBJECT;
}
}
throw new BadRequestException("Unsupported identity provider principal type");
}
}

View File

@ -1,34 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Create or update request for an identity provider.")
public class IdentityProviderRequest {
@Schema(description = "Alias of the identity provider used for identification", requiredMode = Schema.RequiredMode.REQUIRED)
private String alias;
@Builder.Default
@Schema(description = "Configuration of the identity provider", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private @Valid IdentityProviderConfigRequest config = new IdentityProviderConfigRequest();
@Builder.Default
@Schema(description = "Display name of the identity provider in keycloak (optional, fallbacks to alias if empty)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String displayName = "";
@Builder.Default
@Schema(description = "The ID of the SSO technology provider (default: saml)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String providerId = "saml";
}

View File

@ -1,7 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
public enum IdentityProviderSAMLSignatureKeyName {
NONE,
KEY_ID,
CERT_SUBJECT
}

View File

@ -1,10 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
public enum IdentityProviderSignatureAlgorithm {
RSA_SHA1,
RSA_SHA256,
RSA_SHA256_MGF1,
RSA_SHA512,
RSA_SHA512_MGF1,
DSA_SHA1
}

View File

@ -1,8 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
public enum IdentityProviderSyncMode {
INHERIT,
IMPORT,
LEGACY,
FORCE
}

View File

@ -1,36 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Create request for an identity provider using a SAML entity descriptor for a faster creation process.")
public class IdentityProviderWithDescriptorRequest {
@Schema(description = "Alias of the identity provider used for identification", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String alias;
@Builder.Default
@Schema(description = "Display name of the identity provider in keycloak (optional, fallbacks to alias if empty)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String displayName = "";
@Builder.Default
@Schema(description = "The ID of the SSO technology provider (default: saml)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String providerId = "saml";
@Schema(description = "The SAML entity descriptor as a URL to external IDP metadata, used to fill other fields for a faster identity provider creation process", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String samlEntityDescriptorURL;
@Schema(description = "Service provider entity ID that will be used to uniquely identify this SAML Service Provider", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private String entityId;
}

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

@ -27,8 +27,4 @@ public class UserManagementPermissions {
public static final String READ_SMTP_CONFIGURATION = "fforesight-read-smtp-configuration";
public static final String WRITE_SMTP_CONFIGURATION = "fforesight-write-smtp-configuration";
// Identity provider
public static final String READ_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-read-identity-provider-config";
public static final String WRITE_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-write-identity-provider-config";
}

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,22 @@ 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,10 @@ 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 +22,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 +44,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 +59,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;
@ -79,15 +78,19 @@ 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");
if (!this.getTenantUsersResource().search(username, true).isEmpty()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this username 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");
}
// also search by email in case the username was provided at creation
if (!StringUtils.isEmpty(user.getUsername()) && !this.getTenantUsersResource().searchByEmail(user.getEmail(), true).isEmpty()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this email already exists");
}
tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().validateRoles(user.getRoles());
tenantUserManagementProperties.getKcRoleMapping().validateRoles(user.getRoles());
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setUsername(username);
@ -102,7 +105,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 +115,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 +132,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 +148,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 +163,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 +228,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 +256,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 +275,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 +294,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 +326,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 +355,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 +367,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 +388,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 +410,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 +429,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,16 +438,6 @@ public class UserService {
public User activateProfile(String userId, boolean isActive) {
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
var status = validateExecution(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");
}
}
var user = this.getUserResource(userId);
var userRepresentation = user.toRepresentation();
@ -565,33 +445,24 @@ public class UserService {
user.update(userRepresentation);
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 +470,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 +488,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 +500,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,179 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.utils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantusermanagement.model.ExtensibleModel;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderConfigModel;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderNameIDPolicyFormat;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderPrincipalType;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
import lombok.SneakyThrows;
public class IdentityProviderMappingService {
public static IdentityProviderRepresentation toRepresentationFromRequest(IdentityProviderRequest identityProviderRequest) {
return toRepresentationFromModel(toModelFromRequest(identityProviderRequest));
}
public static IdentityProviderModel toModelFromRequest(IdentityProviderRequest identityProviderRequest) {
if (!identityProviderRequest.getConfig().getWantAuthnRequestsSigned()) {
identityProviderRequest.getConfig().setXmlSigKeyInfoKeyNameTransformer(null);
identityProviderRequest.getConfig().setSignatureAlgorithm(null);
}
return MagicConverter.convert(identityProviderRequest, IdentityProviderModel.class, getModelFromRequestDeltaMapper());
}
private static BiConsumer<IdentityProviderRequest, IdentityProviderModel> getModelFromRequestDeltaMapper() {
return ((identityProviderRequest, identityProviderModel) -> {
var identityProviderConfigModel = MagicConverter.convert(identityProviderRequest.getConfig(), IdentityProviderConfigModel.class);
identityProviderModel.setConfig(identityProviderConfigModel);
});
}
public static IdentityProviderRepresentation toRepresentationFromModel(IdentityProviderModel identityProviderModel) {
return MagicConverter.convert(identityProviderModel, IdentityProviderRepresentation.class, getModelToRepresentationDeltaMapper());
}
private static BiConsumer<IdentityProviderModel, IdentityProviderRepresentation> getModelToRepresentationDeltaMapper() {
return ((identityProviderModel, identityProviderRepresentation) -> identityProviderRepresentation.setConfig(convertObjectToMap(identityProviderModel.getConfig())));
}
public static IdentityProviderModel toModelFromDescriptorRequestAndConfig(IdentityProviderWithDescriptorRequest identityProvider, Map<String, String> configurationsMap) {
IdentityProviderModel identityProviderModel = MagicConverter.convert(identityProvider, IdentityProviderModel.class);
identityProviderModel.setConfig(convertMapToObject(configurationsMap, IdentityProviderConfigModel.class));
return identityProviderModel;
}
@SneakyThrows
public static Map<String, String> convertObjectToMap(Object obj) {
Map<String, String> resultMap = new HashMap<>();
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
Method getter = pd.getReadMethod();
Object fieldValue = getter.invoke(obj);
if (fieldValue != null) {
resultMap.put(field.getName(), fieldValue.toString());
}
}
return resultMap;
}
public static IdentityProviderModel toModelFromRepresentation(IdentityProviderRepresentation identityProviderRepresentation) {
return MagicConverter.convert(identityProviderRepresentation, IdentityProviderModel.class, getRepresentationToModelDeltaMapper());
}
private static BiConsumer<IdentityProviderRepresentation, IdentityProviderModel> getRepresentationToModelDeltaMapper() {
return ((identityProviderRepresentation, identityProviderModel) -> identityProviderModel.setConfig(convertMapToObject(identityProviderRepresentation.getConfig(),
IdentityProviderConfigModel.class)));
}
@SneakyThrows
public static <T extends ExtensibleModel> T convertMapToObject(Map<String, String> map, Class<T> clazz) {
T obj = clazz.getDeclaredConstructor().newInstance();
Field[] fields = clazz.getDeclaredFields();
sanitizeMapKeys(map);
for (Field field : fields) {
String fieldName = field.getName();
if (map.containsKey(fieldName)) {
String fieldValue = map.get(fieldName);
setFieldValue(obj, field, fieldValue);
map.remove(fieldName);
}
}
obj.setNotMappedFields(map);
return obj;
}
private static void sanitizeMapKeys(Map<String, String> inputMap) {
var keys = new ArrayList<>(inputMap.keySet());
for (String key : keys) {
String sanitizedKey = sanitize(key);
if (!sanitizedKey.equals(key)) {
String value = inputMap.remove(key);
inputMap.put(sanitizedKey, value);
}
}
}
private static String sanitize(String input) {
StringBuilder result = new StringBuilder();
String[] segments = input.split("\\.");
for (String segment : segments) {
if (!result.isEmpty()) {
result.append(Character.toUpperCase(segment.charAt(0)));
result.append(segment.substring(1));
} else {
result.append(segment);
}
}
return result.toString();
}
@SneakyThrows
private static <T> void setFieldValue(T obj, Field field, String value) {
Class<?> fieldType = field.getType();
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
Method setter = pd.getWriteMethod();
if (fieldType == String.class) {
setter.invoke(obj, value);
} else if (fieldType == int.class || fieldType == Integer.class) {
setter.invoke(obj, Integer.parseInt(value));
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
setter.invoke(obj, Boolean.parseBoolean(value));
} else if (fieldType == IdentityProviderNameIDPolicyFormat.class) {
setter.invoke(obj, IdentityProviderNameIDPolicyFormat.fromRepresentation(value));
} else if (fieldType == IdentityProviderPrincipalType.class) {
setter.invoke(obj, IdentityProviderPrincipalType.fromStringValue(value));
} else if (fieldType.isEnum()) {
Enum<?> enumValue = Enum.valueOf(fieldType.asSubclass(Enum.class), value);
setter.invoke(obj, enumValue);
}
}
}

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,85 @@
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: 500
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: 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-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,50 @@
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'
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: 500
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
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", "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", "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", "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",
"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: 500
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-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

@ -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,10 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.feigntestclients.external;
import org.springframework.cloud.openfeign.FeignClient;
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
@FeignClient(name = "IdentityProviderConfigurationClient", url = "http://localhost:${server.port}", path = "${fforesight.tenant-user-management.base-path:}")
public interface IdentityProviderConfigurationClient extends IdentityProviderConfigurationResource {
}

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,372 +0,0 @@
package com.knecon.fforesight.tenantusermanagement.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
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.IdentityProviderConfigurationClient;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderConfigRequest;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderNameIDPolicyFormat;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderPrincipalType;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSAMLSignatureKeyName;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSignatureAlgorithm;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSyncMode;
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
import feign.FeignException;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@FieldDefaults(level = AccessLevel.PRIVATE)
public class IdentityProviderConfigurationTest extends AbstractTenantUserManagementIntegrationTest {
@Autowired
IdentityProviderConfigurationClient identityProviderConfigurationClient;
final String testAlias = "testAlias";
final String entityId = "https://sso-test.iqser.cloud";
final String idpEntityId = "https://sts.windows.net/b44be368-e4f2-4ade-a089-cd2825458048/";
final String ssoServiceUrl = "https://login.microsoftonline.com/b44be368-e4f2-4ade-a089-cd2825458048/saml2";
final String testAliasDescriptor = "testAliasDescriptor";
final String invalidUrl = "this is no valid URL :(";
final String idp1TestAlias = "idp1";
final String idp2TestAlias = "idp2";
@BeforeEach
public void beforeEachTest() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
setupTestDataIfNotExistent();
}
private void setupTestDataIfNotExistent() {
var identityProviders = identityProviderConfigurationClient.getIdentityProviders().getIdentityProviders();
if(identityProviders.stream().noneMatch(identityProviderModel -> identityProviderModel.getAlias().equals(idp1TestAlias))) {
identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp1());
identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp2());
}
}
@Test
public void testCreateEntityProviderSuccessful() {
var createdResponse = identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreate());
assertEquals(201, createdResponse.getStatusCode().value());
var identityProviderModel = createdResponse.getBody();
assert identityProviderModel != null;
assertEquals(identityProviderModel.getAlias(), testAlias);
assertEquals(identityProviderModel.getConfig().getEntityId(), entityId);
assertEquals(identityProviderModel.getConfig().getIdpEntityId(), idpEntityId);
assertEquals(identityProviderModel.getConfig().getSingleSignOnServiceUrl(), ssoServiceUrl);
assertTrue(identityProviderModel.getTrustEmail());
assertTrue(identityProviderModel.getStoreToken());
assertEquals(identityProviderModel.getConfig().getSyncMode(), IdentityProviderSyncMode.FORCE);
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
assertEquals(3, identityProviderList.getIdentityProviders().size());
identityProviderConfigurationClient.deleteIdentityProvider(testAlias);
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
assertEquals(2, identityProviderList.getIdentityProviders().size());
}
@Test
public void testUpdateEntityProviderSuccessful() {
var updatedResponse = identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, provideIdentityProviderRequestUpdate());
assertEquals(200, updatedResponse.getStatusCode().value());
var identityProviderModel = updatedResponse.getBody();
assert identityProviderModel != null;
assertEquals(identityProviderModel.getConfig().getNameIDPolicyFormat(), IdentityProviderNameIDPolicyFormat.TRANSIENT);
assertEquals(identityProviderModel.getConfig().getPrincipalType(), IdentityProviderPrincipalType.ATTRIBUTE);
assertFalse(identityProviderModel.getConfig().getAllowCreate());
assertTrue(identityProviderModel.getConfig().getWantAuthnRequestsSigned());
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
assertEquals(2, identityProviderList.getIdentityProviders().size());
identityProviderModel = identityProviderConfigurationClient.getIdentityProvider(idp1TestAlias);
assertEquals(identityProviderModel.getAlias(), idp1TestAlias);
assertEquals(identityProviderModel.getConfig().getEntityId(), entityId);
assertEquals(identityProviderModel.getConfig().getIdpEntityId(), idpEntityId);
assertEquals(identityProviderModel.getConfig().getSingleSignOnServiceUrl(), ssoServiceUrl);
assertTrue(identityProviderModel.getTrustEmail());
assertTrue(identityProviderModel.getStoreToken());
assertEquals(identityProviderModel.getConfig().getSyncMode(), IdentityProviderSyncMode.FORCE);
assertEquals(identityProviderModel.getConfig().getNameIDPolicyFormat(), IdentityProviderNameIDPolicyFormat.TRANSIENT);
assertEquals(identityProviderModel.getConfig().getPrincipalType(), IdentityProviderPrincipalType.ATTRIBUTE);
assertFalse(identityProviderModel.getConfig().getAllowCreate());
assertTrue(identityProviderModel.getConfig().getWantAuthnRequestsSigned());
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
}
@Test
public void testCreateEntityProviderFromDescriptorSuccessful() {
var createdDescriptorResponse = identityProviderConfigurationClient.createIdentityProviderFromDescriptor(provideIdentityProviderWithDescriptorRequest());
assertEquals(201, createdDescriptorResponse.getStatusCode().value());
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
assertEquals(3, identityProviderList.getIdentityProviders().size());
identityProviderConfigurationClient.deleteIdentityProvider(testAliasDescriptor);
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
assertEquals(2, identityProviderList.getIdentityProviders().size());
}
@Test
public void testCreateEntityProviderDuplicated() {
String otherAlias = "otherAlias";
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp1()));
assertEquals(409, e.status());
var requestWithSameDisplayName = provideIdentityProviderRequestCreate();
requestWithSameDisplayName.setAlias(otherAlias);
requestWithSameDisplayName.setDisplayName(idp1TestAlias);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithSameDisplayName));
assertEquals(409, e.status());
var descriptorRequestWithSameDisplayName = provideIdentityProviderWithDescriptorRequest();
descriptorRequestWithSameDisplayName.setAlias(otherAlias);
descriptorRequestWithSameDisplayName.setDisplayName(idp1TestAlias);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithSameDisplayName));
assertEquals(409, e.status());
var updateRequestWithDisplayNameFromOtherIdp = provideIdentityProviderRequestUpdate();
updateRequestWithDisplayNameFromOtherIdp.setDisplayName(idp1TestAlias);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp2TestAlias, updateRequestWithDisplayNameFromOtherIdp));
assertEquals(409, e.status());
}
@Test
public void testUpdateDeleteEntityProviderThatNotExists() {
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, provideIdentityProviderRequestUpdate()));
assertEquals(404, e.status());
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.deleteIdentityProvider(testAlias));
assertEquals(404, e.status());
}
@Test
public void testCreateEntityProviderBadRequestNoAlias() {
var requestWithoutAlias = provideIdentityProviderRequestCreate();
requestWithoutAlias.setAlias(null);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutAlias));
assertEquals(400, e.status());
var descriptorRequestWithoutAlias = provideIdentityProviderWithDescriptorRequest();
descriptorRequestWithoutAlias.setAlias(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutAlias));
assertEquals(400, e.status());
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(null, provideIdentityProviderRequestUpdate()));
assertEquals(404, e.status());
}
@Test
public void testCreateEntityProviderBadRequestNoProvider() {
var requestWithoutProvider = provideIdentityProviderRequestCreate();
requestWithoutProvider.setProviderId(null);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutProvider));
assertEquals(400, e.status());
var descriptorRequestWithoutProvider = provideIdentityProviderWithDescriptorRequest();
descriptorRequestWithoutProvider.setProviderId(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutProvider));
assertEquals(400, e.status());
var updateRequestWithoutProvider = provideIdentityProviderRequestUpdate();
updateRequestWithoutProvider.setProviderId(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithoutProvider));
assertEquals(400, e.status());
}
@Test
public void testCreateEntityProviderBadRequestNoEntityId() {
var requestWithoutEntityId = provideIdentityProviderRequestCreate();
requestWithoutEntityId.getConfig().setEntityId(null);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutEntityId));
assertEquals(400, e.status());
var descriptorRequestWithoutEntityId = provideIdentityProviderWithDescriptorRequest();
descriptorRequestWithoutEntityId.setEntityId(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutEntityId));
assertEquals(400, e.status());
var updateRequestWithoutEntityId = provideIdentityProviderRequestUpdate();
updateRequestWithoutEntityId.getConfig().setEntityId(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, updateRequestWithoutEntityId));
assertEquals(400, e.status());
}
@Test
public void testCreateEntityProviderBadRequestNoIdpEntityId() {
var requestWithoutIdpEntityId = provideIdentityProviderRequestCreate();
requestWithoutIdpEntityId.getConfig().setIdpEntityId(null);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutIdpEntityId));
assertEquals(400, e.status());
var updateRequestWithoutEntityId = provideIdentityProviderRequestUpdate();
updateRequestWithoutEntityId.getConfig().setIdpEntityId(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, updateRequestWithoutEntityId));
assertEquals(400, e.status());
}
@Test
public void testCreateEntityProviderBadRequestNoSSOUrl() {
var requestWithoutSSOUrl = provideIdentityProviderRequestCreate();
requestWithoutSSOUrl.getConfig().setSingleSignOnServiceUrl(null);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutSSOUrl));
assertEquals(400, e.status());
var updateRequestWithoutSSOUrl = provideIdentityProviderRequestUpdate();
updateRequestWithoutSSOUrl.getConfig().setSingleSignOnServiceUrl(null);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithoutSSOUrl));
assertEquals(400, e.status());
}
@Test
public void testCreateEntityProviderBadRequestMalformedSSOUrl() {
var requestWithMalformedSingleSignOnURL = provideIdentityProviderRequestCreate();
requestWithMalformedSingleSignOnURL.getConfig().setSingleSignOnServiceUrl(invalidUrl);
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithMalformedSingleSignOnURL));
assertEquals(400, e.status());
var updateRequestWithMalformedSingleSignOnURL = provideIdentityProviderRequestUpdate();
updateRequestWithMalformedSingleSignOnURL.getConfig().setSingleSignOnServiceUrl(invalidUrl);
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithMalformedSingleSignOnURL));
assertEquals(400, e.status());
}
@Test
public void testCreateEntityProviderBadRequestMalformedEntityDescriptorUrl() {
var descriptorRequestWithMalformedEntityDescriptorUrl = provideIdentityProviderWithDescriptorRequest();
descriptorRequestWithMalformedEntityDescriptorUrl.setSamlEntityDescriptorURL(invalidUrl);
var e = assertThrows(FeignException.class,
() -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithMalformedEntityDescriptorUrl));
assertEquals(400, e.status());
}
@Test
public void testUpdateEntityProviderInvalidOperation() {
var invalidUpdateRequest = provideIdentityProviderRequestUpdate();
invalidUpdateRequest.getConfig().setPrincipalType(IdentityProviderPrincipalType.SUBJECT);
var e = assertThrows(FeignException.class,
() -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, invalidUpdateRequest));
assertEquals(400, e.status());
}
private IdentityProviderRequest provideIdentityProviderRequestCreate() {
return IdentityProviderRequest.builder()
.alias(testAlias)
.config(IdentityProviderConfigRequest.builder()
.entityId(entityId)
.idpEntityId(idpEntityId)
.singleSignOnServiceUrl(ssoServiceUrl)
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.PERSISTENT)
.principalType(IdentityProviderPrincipalType.SUBJECT)
.build())
.build();
}
private IdentityProviderRequest provideIdentityProviderRequestCreateForIdp1() {
return IdentityProviderRequest.builder()
.alias(idp1TestAlias)
.config(IdentityProviderConfigRequest.builder()
.entityId(entityId)
.idpEntityId(idpEntityId)
.singleSignOnServiceUrl(ssoServiceUrl)
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.TRANSIENT)
.principalType(IdentityProviderPrincipalType.ATTRIBUTE)
.build())
.build();
}
private IdentityProviderRequest provideIdentityProviderRequestCreateForIdp2() {
return IdentityProviderRequest.builder()
.alias(idp2TestAlias)
.config(IdentityProviderConfigRequest.builder()
.entityId(entityId)
.idpEntityId(idpEntityId)
.singleSignOnServiceUrl(ssoServiceUrl)
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.X_509_SUBJECT_NAME)
.principalType(IdentityProviderPrincipalType.SUBJECT)
.build())
.build();
}
private IdentityProviderRequest provideIdentityProviderRequestUpdate() {
return IdentityProviderRequest.builder()
.config(IdentityProviderConfigRequest.builder()
.entityId(entityId)
.idpEntityId(idpEntityId)
.singleSignOnServiceUrl(ssoServiceUrl)
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.TRANSIENT)
.principalType(IdentityProviderPrincipalType.ATTRIBUTE)
.allowCreate(false)
.wantAuthnRequestsSigned(true)
.xmlSigKeyInfoKeyNameTransformer(IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT)
.signatureAlgorithm(IdentityProviderSignatureAlgorithm.RSA_SHA512)
.build())
.build();
}
private IdentityProviderWithDescriptorRequest provideIdentityProviderWithDescriptorRequest() {
return IdentityProviderWithDescriptorRequest.builder()
.alias(testAliasDescriptor)
.entityId(entityId)
.samlEntityDescriptorURL("https://login.microsoftonline.com/b44be368-e4f2-4ade-a089-cd2825458048/federationmetadata/2007-06/federationmetadata.xml")
.build();
}
}

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

@ -3,101 +3,69 @@ package com.knecon.fforesight.tenantusermanagement.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
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 org.junit.jupiter.api.Test;
import lombok.SneakyThrows;
import feign.FeignException;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.UserClient;
import com.knecon.fforesight.tenantcommons.TenantContext;
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 {
@Autowired
private UserClient userClient;
@Autowired
private RealmService realmService;
@Autowired
private TenantUserManagementProperties tenantUserManagementProperties;
@Autowired
private TenantApplicationTypeService tenantApplicationTypeService;
@Autowired
private UserService userService;
@Test
public void testUsers() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
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,20 +81,18 @@ 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()));
allUsers = userClient.getAllUsers(true);
assertThat(allUsers).hasSize(1);
TenantContext.clear();
}
@ -162,10 +128,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());
@ -175,124 +138,6 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
assertThat(e.status()).isEqualTo(403);
}
TenantContext.clear();
}
@Test
@SneakyThrows
public void testActivateUserWithLowerRole() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("test@fforesight.com", "secret");
var redAdminUserRequest = new CreateUserRequest();
redAdminUserRequest.setEmail("red-admin@knecon.com");
redAdminUserRequest.setUsername(redAdminUserRequest.getEmail());
redAdminUserRequest.setRoles(Set.of("SUPER_USER"));
var redAdmin = userClient.createUser(redAdminUserRequest);
var redUserAdminUserRequest = new CreateUserRequest();
redUserAdminUserRequest.setEmail("red-user-admin@knecon.com");
redUserAdminUserRequest.setUsername(redUserAdminUserRequest.getEmail());
redUserAdminUserRequest.setRoles(Set.of("LESS_SUPER_USER"));
var redUserAdmin = userClient.createUser(redUserAdminUserRequest);
userClient.resetPassword(redAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
userClient.resetPassword(redUserAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
// authenticate as red-admin
tokenService.setUser("red-admin@knecon.com", "Secret@secured!23");
// activate red-user-admin should work since the user has a lower rank
var user = userClient.activateProfile(redUserAdmin.getUserId(), true);
assertTrue(user.isActive());
// deactivate red-user-admin should work since the user has a lower rank
user = userClient.activateProfile(redUserAdmin.getUserId(), false);
assertFalse(user.isActive());
tokenService.setUser("test@fforesight.com", "secret");
userClient.deleteUser(redUserAdmin.getUserId());
userClient.deleteUser(redAdmin.getUserId());
TenantContext.clear();
}
@Test
@SneakyThrows
public void testActivateUserWithHigherRole() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("test@fforesight.com", "secret");
var redAdminUserRequest = new CreateUserRequest();
redAdminUserRequest.setEmail("red-admin@knecon.com");
redAdminUserRequest.setUsername(redAdminUserRequest.getEmail());
redAdminUserRequest.setRoles(Set.of("SUPER_USER"));
var redAdmin = userClient.createUser(redAdminUserRequest);
var redUserAdminUserRequest = new CreateUserRequest();
redUserAdminUserRequest.setEmail("red-user-admin@knecon.com");
redUserAdminUserRequest.setUsername(redUserAdminUserRequest.getEmail());
redUserAdminUserRequest.setRoles(Set.of("LESS_SUPER_USER"));
var redUserAdmin = userClient.createUser(redUserAdminUserRequest);
userClient.resetPassword(redAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
userClient.resetPassword(redUserAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
// authenticate as red-user-admin
tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23");
// activate red-admin should not work since the user has a higher rank
FeignException feignException = assertThrows(FeignException.class, () -> userClient.activateProfile(redAdmin.getUserId(), true));
assertEquals(feignException.status(), 403);
assertTrue(feignException.getMessage().contains("It is not allowed to activate/deactivate a user with higher ranking role"));
tokenService.setUser("test@fforesight.com", "secret");
userClient.deleteUser(redUserAdmin.getUserId());
userClient.deleteUser(redAdmin.getUserId());
TenantContext.clear();
}
@Test
@SneakyThrows
public void testActivateUserWithSameRole() {
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
tokenService.setUser("test@fforesight.com", "secret");
var redAdminUserRequest = new CreateUserRequest();
redAdminUserRequest.setEmail("red-admin@knecon.com");
redAdminUserRequest.setUsername(redAdminUserRequest.getEmail());
redAdminUserRequest.setRoles(Set.of("SUPER_USER"));
var redAdmin = userClient.createUser(redAdminUserRequest);
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);
userClient.resetPassword(redAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
userClient.resetPassword(redUserAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
// authenticate as red-user-admin
tokenService.setUser("red-admin@knecon.com", "Secret@secured!23");
// activate red-user-admin should work since the user has the same rank
var user = userClient.activateProfile(redUserAdmin.getUserId(), true);
assertTrue(user.isActive());
// deactivate red-user-admin should work since the user has the same rank
user = userClient.activateProfile(redUserAdmin.getUserId(), false);
assertFalse(user.isActive());
tokenService.setUser("test@fforesight.com", "secret");
userClient.deleteUser(redUserAdmin.getUserId());
userClient.deleteUser(redAdmin.getUserId());
TenantContext.clear();
}
@ -305,7 +150,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 +161,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 +175,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,154 @@
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'
- 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'
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' ]