Compare commits
23 Commits
main
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954b8fac33 | ||
|
|
e11413beda | ||
|
|
9290a4ab2f | ||
|
|
293a738567 | ||
|
|
2cbc431606 | ||
|
|
b7b2740760 | ||
|
|
a8bbdca18e | ||
|
|
87e1493de9 | ||
|
|
529669533e | ||
|
|
1ed01ce1ee | ||
|
|
1ce5f3dabf | ||
|
|
58be691b46 | ||
|
|
665eea7393 | ||
|
|
4d8cbfb2a7 | ||
|
|
a248228b68 | ||
|
|
fc3e44b66e | ||
|
|
a1db318adc | ||
|
|
d8c1121766 | ||
|
|
abc1702097 | ||
|
|
6b2ba15f7f | ||
|
|
0c59fdf676 | ||
|
|
5ad75932d8 | ||
|
|
13ff6ced57 |
@ -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.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -36,14 +36,13 @@ public interface TenantsResource {
|
||||
@PostMapping(value = TENANTS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Create a new tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void createTenant(@RequestBody TenantRequest tenant);
|
||||
void createTenant(@RequestBody CreateTenantRequest tenant);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Deletes given tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden access, you dont have rights to delete tenants"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."),
|
||||
@ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")})
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden access, you dont have rights to delete tenants"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."), @ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")})
|
||||
@DeleteMapping(value = TENANTS_TENANT_ID_PATH)
|
||||
void deleteTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
@ -63,7 +62,7 @@ public interface TenantsResource {
|
||||
@PutMapping(value = TENANTS_TENANT_ID_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Update existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest);
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
|
||||
|
||||
|
||||
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/details", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ -72,12 +71,6 @@ public interface TenantsResource {
|
||||
void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request);
|
||||
|
||||
|
||||
@GetMapping(value = TENANTS_PATH + "/simple", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenants in a simplified format", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<SimpleTenantResponse> getSimpleTenants();
|
||||
|
||||
|
||||
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Returns the deployment key for a tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
|
||||
@ -10,7 +10,6 @@ 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;
|
||||
@ -61,7 +60,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")})
|
||||
@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.")})
|
||||
@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);
|
||||
|
||||
@ -99,7 +98,7 @@ public interface UserResource {
|
||||
|
||||
@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);
|
||||
|
||||
@ -120,8 +119,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")})
|
||||
@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.")})
|
||||
@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);
|
||||
|
||||
|
||||
@ -15,8 +15,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.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -38,7 +38,7 @@ 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 TenantRequest tenant);
|
||||
TenantResponse createTenant(@RequestBody CreateTenantRequest tenant);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ -56,13 +56,7 @@ 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 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();
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
|
||||
|
||||
|
||||
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
|
||||
@ -57,7 +57,7 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
|
||||
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
|
||||
realmRepresentation.setSmtpServer(propertiesMap);
|
||||
|
||||
if (!smtpConfigurationModel.getPassword().matches("\\**")) {
|
||||
if (smtpConfigurationModel.getPassword() != null && !smtpConfigurationModel.getPassword().matches("\\**")) {
|
||||
realmRepresentation.getAttributesOrEmpty().put(SMTP_PASSWORD_KEY, encryptionDecryptionService.encrypt(smtpConfigurationModel.getPassword()));
|
||||
}
|
||||
|
||||
|
||||
@ -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.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
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 TenantRequest tenantRequest) {
|
||||
public void createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
|
||||
|
||||
try {
|
||||
tenantManagementService.createTenant(tenantRequest);
|
||||
@ -76,14 +76,8 @@ 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 TenantRequest tenantRequest) {
|
||||
public TenantResponse updateTenant(String tenantId, @RequestBody UpdateTenantRequest tenantRequest) {
|
||||
|
||||
TenantResponse tenantResponse = tenantManagementService.updateTenant(tenantId, tenantRequest);
|
||||
return tenantManagementService.removePasswords(tenantResponse);
|
||||
|
||||
@ -1,5 +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.READ_ALL_USERS;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_USERS;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_MY_PROFILE;
|
||||
@ -39,19 +40,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RequiredArgsConstructor
|
||||
public class UserController implements UserResource, PublicResource {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
private final UserService userService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
|
||||
@ -9,16 +9,13 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
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.events.TenantCreatedEvent;
|
||||
import com.knecon.fforesight.tenantusermanagement.events.TenantSyncEvent;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.DeploymentKeyService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
|
||||
@ -40,7 +37,7 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
|
||||
}
|
||||
|
||||
|
||||
public TenantResponse createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
|
||||
public TenantResponse createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
|
||||
|
||||
try {
|
||||
return tenantManagementService.createTenant(tenantRequest);
|
||||
@ -63,18 +60,12 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
|
||||
|
||||
|
||||
@Override
|
||||
public TenantResponse updateTenant(String tenantId, TenantRequest tenantRequest) {
|
||||
public TenantResponse updateTenant(String tenantId, UpdateTenantRequest 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));
|
||||
|
||||
@ -15,14 +15,12 @@ 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.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
@ -74,7 +72,7 @@ public class DevTestTenantService {
|
||||
createDatabase(tenantsDBName, tenantsDBPassword);
|
||||
createSchema(jdbcUrl, tenantId, tenantsDBName, tenantsDBPassword);
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
var tenantRequest = CreateTenantRequest.builder()
|
||||
.tenantId(tenantId)
|
||||
.displayName(tenantId)
|
||||
.guid(UUID.randomUUID().toString())
|
||||
|
||||
@ -20,8 +20,8 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing the request to create or update a tenant.")
|
||||
public class TenantRequest {
|
||||
@Schema(description = "Object containing the request to create a tenant.")
|
||||
public class CreateTenantRequest {
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Tenant Id must match [A-Za-z0-9_-]")
|
||||
@ -1,23 +0,0 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
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 {
|
||||
|
||||
@ -15,6 +19,21 @@ public final class ApplicationRoles {
|
||||
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);
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import org.bson.BsonArray;
|
||||
import org.bson.BsonDocument;
|
||||
import org.bson.BsonString;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
|
||||
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
|
||||
import com.knecon.fforesight.tenantcommons.utils.MongoConnectionStringHelper;
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.MongoDBConnectionEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
import com.mongodb.MongoCommandException;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
// This is just for migration from 4.0 to 4.1 and can be removed afterward.
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MongoDbOntheFlyMigrationService {
|
||||
|
||||
private final EncryptionDecryptionService encryptionService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final TenantRepository tenantRepository;
|
||||
|
||||
@Value("${default.mongodb.username:}")
|
||||
private String defaultMongoDBUsername;
|
||||
|
||||
@Value("${default.mongodb.password:}")
|
||||
private String defaultMongoDBPassword;
|
||||
|
||||
@Value("${default.mongodb.address:}")
|
||||
private String defaultMongoDBAddress;
|
||||
|
||||
@Value("${default.mongodb.prefix:}")
|
||||
private String defaultMongoDBPrefix;
|
||||
|
||||
@Value("${default.mongodb.options:}")
|
||||
private String defaultMongoDBOptions;
|
||||
|
||||
|
||||
@Transactional
|
||||
public TenantEntity updateMongoDatabaseConnection(TenantEntity tenantEntity) {
|
||||
|
||||
if ((tenantEntity.getMongoDBConnection() == null || tenantEntity.getMongoDBConnection().getUsername() == null) && defaultMongoDBUsername != null && !defaultMongoDBUsername.isEmpty()) {
|
||||
tenantEntity.setMongoDBConnection(MongoDBConnectionEntity.builder()
|
||||
.prefix(defaultMongoDBPrefix)
|
||||
.username(defaultMongoDBUsername)
|
||||
.password(encryptionService.encrypt(defaultMongoDBPassword))
|
||||
.address(defaultMongoDBAddress)
|
||||
.database(buildIndexPrefix(tenantEntity.getTenantId()))
|
||||
.options(defaultMongoDBOptions)
|
||||
.build());
|
||||
tenantRepository.save(tenantEntity);
|
||||
createMongoDBDatabase(convert(tenantEntity.getMongoDBConnection()));
|
||||
}
|
||||
|
||||
return tenantEntity;
|
||||
}
|
||||
|
||||
|
||||
private String buildIndexPrefix(String tenantId) {
|
||||
|
||||
return tenantUserManagementProperties.getAppPrefix() + "_" + tenantId;
|
||||
}
|
||||
|
||||
|
||||
private MongoDBConnection convert(MongoDBConnectionEntity mongoDBConnectionEntity) {
|
||||
|
||||
return MongoDBConnection.builder()
|
||||
.prefix(mongoDBConnectionEntity.getPrefix())
|
||||
.username(mongoDBConnectionEntity.getUsername())
|
||||
.password(mongoDBConnectionEntity.getPassword())
|
||||
.address(mongoDBConnectionEntity.getAddress())
|
||||
.database(mongoDBConnectionEntity.getDatabase())
|
||||
.options(mongoDBConnectionEntity.getOptions())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private void createMongoDBDatabase(MongoDBConnection mongoDBConnection) {
|
||||
|
||||
mongoDBConnection.setPassword(encryptionService.decrypt(mongoDBConnection.getPassword()));
|
||||
|
||||
try (MongoClient mongoClient = MongoClients.create(MongoConnectionStringHelper.buildGenericMongoConnectionString(mongoDBConnection))) {
|
||||
String databaseName = mongoDBConnection.getDatabase();
|
||||
String username = mongoDBConnection.getUsername();
|
||||
|
||||
MongoDatabase database = mongoClient.getDatabase(databaseName);
|
||||
BsonDocument createUserCommand = new BsonDocument();
|
||||
createUserCommand.append("createUser", new BsonString(username));
|
||||
createUserCommand.append("pwd", new BsonString(mongoDBConnection.getPassword()));
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,6 +19,7 @@ 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;
|
||||
@ -62,8 +63,9 @@ 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.SearchConnectionRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.JDBCUtils;
|
||||
@ -104,23 +106,20 @@ public class TenantManagementService implements TenantProvider {
|
||||
private final RealmService realmService;
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
private final StorageConfiguration storageConfiguration;
|
||||
private final MongoDbOntheFlyMigrationService mongoDbOntheFlyMigrationService;
|
||||
|
||||
@Value("${fforesight.tenant-exchange.name}")
|
||||
private String tenantExchangeName;
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public TenantResponse createTenant(TenantRequest tenantRequest) {
|
||||
public TenantResponse createTenant(CreateTenantRequest tenantRequest) {
|
||||
|
||||
// For now we update the master realm theme whenever we create the tenant
|
||||
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 {
|
||||
@ -137,34 +136,34 @@ public class TenantManagementService implements TenantProvider {
|
||||
.displayName(tenantRequest.getDisplayName())
|
||||
.guid(UUID.randomUUID().toString())
|
||||
.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(databaseConnection.getDriver())
|
||||
.host(databaseConnection.getHost())
|
||||
.port(databaseConnection.getPort())
|
||||
.database(databaseConnection.getDatabase())
|
||||
.schema(databaseConnection.getSchema())
|
||||
.username(databaseConnection.getUsername())
|
||||
.password(encryptionService.encrypt(databaseConnection.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()))
|
||||
.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(buildIndexPrefix(tenantRequest.getTenantId()))
|
||||
.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());
|
||||
.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();
|
||||
|
||||
@ -173,9 +172,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());
|
||||
}
|
||||
|
||||
S3StorageConnection s3StorageConnection = tenantRequest.getS3StorageConnection();
|
||||
@ -183,13 +182,13 @@ 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());
|
||||
}
|
||||
|
||||
createSchema(tenantRequest);
|
||||
@ -218,6 +217,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
log.info("Dispatched message for tenant: {}", tenantRequest.getTenantId());
|
||||
|
||||
return convert(saved);
|
||||
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Tenant exists");
|
||||
}
|
||||
@ -265,17 +265,17 @@ public class TenantManagementService implements TenantProvider {
|
||||
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());
|
||||
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);
|
||||
ListObjectsResponse objectList = client.listObjects(listObjects);
|
||||
for (S3Object object : objectList.contents()) {
|
||||
// Delete each object
|
||||
client.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(object.key()).build());
|
||||
@ -303,9 +303,8 @@ public class TenantManagementService implements TenantProvider {
|
||||
|
||||
private void propagateTenantToKeyCloak(String tenantId, List<TenantUser> usersToCreate) throws InterruptedException {
|
||||
|
||||
log.info("Creating realm for tenant: {}", tenantId);
|
||||
log.info("Creating or updating realm for tenant: {}", tenantId);
|
||||
createOrUpdateRealm(tenantId, usersToCreate);
|
||||
log.info("Created realm for tenant: {}", tenantId);
|
||||
|
||||
var waitTime = 0;
|
||||
boolean realmReady;
|
||||
@ -321,7 +320,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
} while (waitTime < MAX_WAIT_TIME);
|
||||
|
||||
if (!realmReady) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create KC realm");
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create or update KC realm");
|
||||
}
|
||||
|
||||
setPasswordPolicyForRealm(tenantId);
|
||||
@ -349,12 +348,12 @@ public class TenantManagementService implements TenantProvider {
|
||||
}
|
||||
|
||||
|
||||
private void createSchema(TenantRequest tenantRequest) {
|
||||
private void createSchema(CreateTenantRequest 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() + "\"";
|
||||
@ -374,8 +373,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;";
|
||||
@ -387,7 +386,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
}
|
||||
|
||||
|
||||
private void createMongoDBDatabase(TenantRequest tenant) {
|
||||
private void createMongoDBDatabase(CreateTenantRequest tenant) {
|
||||
|
||||
MongoDBConnection mongoDBConnection = tenant.getMongoDBConnection();
|
||||
try (MongoClient mongoClient = MongoClients.create(MongoConnectionStringHelper.buildGenericMongoConnectionString(mongoDBConnection))) {
|
||||
@ -430,7 +429,8 @@ public class TenantManagementService implements TenantProvider {
|
||||
|
||||
public void createOrUpdateRealm(String tenantId, List<TenantUser> users) {
|
||||
|
||||
if (syncRealmIfExists(tenantId)) {
|
||||
if (syncRealmIfExists(tenantId, users)) {
|
||||
log.info("Updated realm for tenant: {}", tenantId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -446,12 +446,12 @@ public class TenantManagementService implements TenantProvider {
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -459,7 +459,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
|
||||
try {
|
||||
log.info("Deleting existing realms for tenant: {}", tenantId);
|
||||
keycloak.getAdminClient().realm(tenantId).remove();
|
||||
getRealmResource(tenantId).remove();
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not delete realm:", e);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Tenant deletion failed: " + e.getMessage(), e);
|
||||
@ -468,10 +468,10 @@ public class TenantManagementService implements TenantProvider {
|
||||
}
|
||||
|
||||
|
||||
private boolean syncRealmIfExists(String tenantId) {
|
||||
private boolean syncRealmIfExists(String tenantId, List<TenantUser> users) {
|
||||
|
||||
try {
|
||||
var existingRealm = keycloak.getAdminClient().realm(tenantId).toRepresentation();
|
||||
var existingRealm = getRealmResource(tenantId).toRepresentation();
|
||||
if (existingRealm != null) {
|
||||
log.info("Updating existing realm: {}", tenantId);
|
||||
existingRealm.setLoginTheme(tenantUserManagementProperties.getDefaultTheme());
|
||||
@ -480,23 +480,40 @@ public class TenantManagementService implements TenantProvider {
|
||||
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();
|
||||
var relevantClientNames = clients.stream().map(c -> c.getClientId().toLowerCase(Locale.getDefault())).collect(Collectors.toSet());
|
||||
var existingClients = getRealmResource(tenantId).clients().findAll();
|
||||
existingClients.forEach(ec -> {
|
||||
if (relevantClientNames.contains(ec.getClientId().toLowerCase(Locale.getDefault()))) {
|
||||
log.info("Removing client: {}", ec.getName());
|
||||
keycloak.getAdminClient().realm(tenantId).clients()
|
||||
.get(ec.getId()).remove();
|
||||
getRealmResource(tenantId).clients().get(ec.getId()).remove();
|
||||
}
|
||||
});
|
||||
|
||||
clients.forEach(c -> keycloak.getAdminClient().realm(tenantId).clients().create(c));
|
||||
clients.forEach(c -> getRealmResource(tenantId).clients().create(c));
|
||||
|
||||
existingRealm.setClients(clients);
|
||||
existingRealm.setRoles(getRealmRoles());
|
||||
keycloak.getAdminClient().realm(tenantId).update(existingRealm);
|
||||
|
||||
if (users != null) {
|
||||
|
||||
var userRepresentationlist = users.stream()
|
||||
.map(this::toUserRepresentation)
|
||||
.toList();
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
@ -504,8 +521,13 @@ public class TenantManagementService implements TenantProvider {
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to update realm: {}", tenantId, e);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to update realm: " + tenantId);
|
||||
}
|
||||
return false;
|
||||
} return false;
|
||||
}
|
||||
|
||||
|
||||
private RealmResource getRealmResource(String tenantId) {
|
||||
|
||||
return keycloak.getAdminClient().realm(tenantId);
|
||||
}
|
||||
|
||||
|
||||
@ -598,9 +620,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;
|
||||
}
|
||||
@ -621,7 +641,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
user.setLastName(redUser.getLastName());
|
||||
user.setEmailVerified(true);
|
||||
|
||||
var roles = new ArrayList<String>(redUser.getRoles() != null ? redUser.getRoles() : new ArrayList<>());
|
||||
var roles = new ArrayList<>(redUser.getRoles() != null ? redUser.getRoles() : new ArrayList<>());
|
||||
roles.add("uma_authorization");
|
||||
roles.add("offline_access");
|
||||
|
||||
@ -656,13 +676,14 @@ 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"));
|
||||
var tenantEntity = tenantRepository.findById(tenantId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist"));
|
||||
|
||||
return convert(mongoDbOntheFlyMigrationService.updateMongoDatabaseConnection(tenantEntity));
|
||||
}
|
||||
|
||||
|
||||
public TenantResponse updateTenant(String tenantId, TenantRequest tenantRequest) {
|
||||
@SneakyThrows
|
||||
public TenantResponse updateTenant(String tenantId, UpdateTenantRequest 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");
|
||||
@ -677,29 +698,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();
|
||||
@ -709,9 +730,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);
|
||||
}
|
||||
@ -723,13 +744,13 @@ 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);
|
||||
}
|
||||
@ -737,16 +758,29 @@ public class TenantManagementService implements TenantProvider {
|
||||
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());
|
||||
.prefix(mongoDBConnection.getPrefix())
|
||||
.username(mongoDBConnection.getUsername())
|
||||
.password(encryptionService.encrypt(mongoDBConnection.getPassword()))
|
||||
.address(mongoDBConnection.getAddress())
|
||||
.database(mongoDBConnection.getDatabase())
|
||||
.options(mongoDBConnection.getOptions())
|
||||
.build());
|
||||
}
|
||||
|
||||
return convert(tenantRepository.save(tenantEntity));
|
||||
propagateTenantToKeyCloak(tenantId, 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;
|
||||
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist");
|
||||
}
|
||||
@ -755,10 +789,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
|
||||
public List<TenantResponse> getTenants() {
|
||||
|
||||
return tenantRepository.findAll()
|
||||
.stream()
|
||||
.map(this::convert)
|
||||
.toList();
|
||||
return tenantRepository.findAll().stream().map(mongoDbOntheFlyMigrationService::updateMongoDatabaseConnection).map(this::convert).toList();
|
||||
}
|
||||
|
||||
|
||||
@ -802,53 +833,53 @@ public class TenantManagementService implements TenantProvider {
|
||||
.authDetails(authDetails)
|
||||
.details(entity.getDetails())
|
||||
.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());
|
||||
.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());
|
||||
.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());
|
||||
}
|
||||
|
||||
if (entity.getAzureStorageConnection() != null) {
|
||||
tenantResponseBuilder.azureStorageConnection(AzureStorageConnection.builder()
|
||||
.connectionString(entity.getAzureStorageConnection().getConnectionString())
|
||||
.containerName(entity.getAzureStorageConnection().getContainerName())
|
||||
.build());
|
||||
.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());
|
||||
.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();
|
||||
@ -869,11 +900,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");
|
||||
@ -895,7 +926,7 @@ public class TenantManagementService implements TenantProvider {
|
||||
public void syncTenant(String tenantId, JsonNode payload) {
|
||||
|
||||
log.info("Syncing Realm: {}", tenantId);
|
||||
syncRealmIfExists(tenantId);
|
||||
syncRealmIfExists(tenantId, null);
|
||||
setPasswordPolicyForRealm(tenantId);
|
||||
generalConfigurationService.initGeneralConfiguration(tenantId);
|
||||
keyCloakRoleManagerService.updateRoles(tenantId);
|
||||
|
||||
@ -166,9 +166,9 @@ public class UserService {
|
||||
|
||||
var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId());
|
||||
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && roles.stream()
|
||||
.anyMatch(ApplicationRoles::isKneconRole) && currentUserRoles.stream()
|
||||
.noneMatch(ApplicationRoles::isKneconRole)) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -182,19 +182,18 @@ public class UserService {
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
newRoles.forEach(role -> {
|
||||
|
||||
if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role) && currentUserRoles.stream()
|
||||
.noneMatch(ApplicationRoles::isKneconRole)) {
|
||||
if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
|
||||
}
|
||||
});
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
var userRoles = userResource.roles().realmLevel().listEffective()
|
||||
var oldRoles = userResource.roles().realmLevel().listEffective()
|
||||
.stream()
|
||||
.map(RoleRepresentation::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
validateSufficientRoles(userId, userRoles, newRoles, currentUserRoles);
|
||||
validateSufficientRoles(userId, oldRoles, newRoles, currentUserRoles);
|
||||
|
||||
var currentRolesAsRoleRepresentation = allRoles.stream()
|
||||
.map(this::getRoleRepresentation)
|
||||
@ -207,43 +206,98 @@ public class UserService {
|
||||
userResource.roles().realmLevel().add(newMappedRoles);
|
||||
|
||||
var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername());
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId())));
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, oldRoles, newRoles, KeycloakSecurity.getUserId())));
|
||||
|
||||
return userWithNewRoles;
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public void removeRolesForDeletion(String userId, Set<String> roles) {
|
||||
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
roles.forEach(role -> {
|
||||
|
||||
if (!allRoles.contains(role)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
|
||||
}
|
||||
});
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
|
||||
var currentRolesAsRoleRepresentation = roles.stream()
|
||||
.map(this::getRoleRepresentation)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
|
||||
}
|
||||
|
||||
|
||||
@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 = tenantUserManagementProperties.getKcRoleMapping();
|
||||
var maxRank = currentUserRoles.stream()
|
||||
|
||||
int maxCurrentUserRank = 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 untouchableRoles = userRoles.stream()
|
||||
Set<String> untouchableRoles = userRoles.stream()
|
||||
.filter(roleMapping::isValidRole)
|
||||
.map(roleMapping::getRole)
|
||||
.filter(r -> r.getRank() > maxRank)
|
||||
.filter(r -> r.getRank() > maxCurrentUserRank && !ApplicationRoles.isKneconRole(r.getName()))
|
||||
.map(KCRole::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (maxNewRolesRank > maxRank) {
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
if (userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) {
|
||||
|
||||
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) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
|
||||
}
|
||||
}
|
||||
@ -288,6 +342,10 @@ public class UserService {
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
@ -419,18 +477,25 @@ public class UserService {
|
||||
return;
|
||||
}
|
||||
|
||||
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
|
||||
var status = validateExecutionForDeletion(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationStatus.FORBIDDEN)) {
|
||||
if (status.equals(ValidationResult.FORBIDDEN)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles");
|
||||
} else if (status.equals(ValidationStatus.INVALID)) {
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
|
||||
var userToBeRemoved = getUserByUsername(userResource.toRepresentation().getUsername());
|
||||
userResource.remove();
|
||||
|
||||
if (status.equals(ValidationResult.ROLE_REMOVAL)) {
|
||||
removeRolesForDeletion(userId,
|
||||
getRoles(userId).stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet()));
|
||||
} else {
|
||||
userResource.remove();
|
||||
}
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.deleted", (new UserRemovedEvent(userToBeRemoved, KeycloakSecurity.getUserId())));
|
||||
|
||||
@ -443,8 +508,9 @@ public class UserService {
|
||||
var user = this.getUserResource(userId);
|
||||
var userRepresentation = user.toRepresentation();
|
||||
|
||||
if (getRoles(userId).stream()
|
||||
.anyMatch(ApplicationRoles::isKneconRole)) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -467,6 +533,10 @@ 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())));
|
||||
|
||||
@ -479,9 +549,9 @@ public class UserService {
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
|
||||
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationStatus.FORBIDDEN)) {
|
||||
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(ValidationStatus.INVALID)) {
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
}
|
||||
@ -499,11 +569,14 @@ public class UserService {
|
||||
}
|
||||
|
||||
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 convert(this.getTenantUsersResource()
|
||||
.get(userId).toRepresentation());
|
||||
return toggledUser;
|
||||
}
|
||||
|
||||
|
||||
@ -512,9 +585,9 @@ public class UserService {
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
|
||||
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationStatus.FORBIDDEN)) {
|
||||
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(ValidationStatus.INVALID)) {
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
}
|
||||
@ -564,22 +637,48 @@ public class UserService {
|
||||
}
|
||||
|
||||
|
||||
private enum ValidationStatus {
|
||||
private enum ValidationResult {
|
||||
ALLOWED,
|
||||
FORBIDDEN,
|
||||
INVALID
|
||||
INVALID,
|
||||
ROLE_REMOVAL
|
||||
|
||||
}
|
||||
|
||||
|
||||
private ValidationStatus validateExecution(String executingUserId, String targetUserId) {
|
||||
private ValidationResult validateExecution(String executingUserId, String targetUserId) {
|
||||
|
||||
var currentUserResource = getUserResource(executingUserId);
|
||||
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
|
||||
var userRoles = getRoles(targetUserId);
|
||||
|
||||
if (userRoles.stream()
|
||||
.anyMatch(ApplicationRoles::isKneconRole)) {
|
||||
return ValidationStatus.INVALID;
|
||||
return validateRoleRanks(currentRoles, userRoles);
|
||||
}
|
||||
|
||||
|
||||
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 = tenantUserManagementProperties.getKcRoleMapping();
|
||||
@ -588,14 +687,15 @@ public class UserService {
|
||||
.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 ValidationStatus.ALLOWED;
|
||||
return ValidationResult.ALLOWED;
|
||||
} else {
|
||||
return ValidationStatus.FORBIDDEN;
|
||||
return ValidationResult.FORBIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,11 +22,11 @@ fforesight:
|
||||
- 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" ]
|
||||
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" ]
|
||||
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
|
||||
|
||||
@ -48,11 +48,11 @@ fforesight:
|
||||
- name: KNECON_ADMIN
|
||||
set-by-default: false
|
||||
rank: 1000
|
||||
permissions: [ "red-read-license", "red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration","red-unarchive-dossier", "red-use-support-controller" ]
|
||||
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", "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","red-unarchive-dossier", "red-use-support-controller" ]
|
||||
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", "red-use-support-controller", "red-process-download", "red-read-download-status" ]
|
||||
- name: RED_USER_ADMIN
|
||||
set-by-default: false
|
||||
rank: 400
|
||||
|
||||
@ -97,7 +97,7 @@ 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','/tenant-user-management/tenants/simple' ]
|
||||
ignored-endpoints: [ '/actuator/health', '/actuator/health/**', '/tenant-user-management', '/tenant-user-management/', '/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs' ]
|
||||
enabled: true
|
||||
springdoc:
|
||||
base-path: '/tenant-user-management'
|
||||
|
||||
@ -72,4 +72,22 @@ 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@ public class StartupTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
@Test
|
||||
public void testStartup() {
|
||||
|
||||
var simpleTenants = internalTenantsClient.getSimpleTenants();
|
||||
assertThat(simpleTenants).isNotEmpty();
|
||||
var tenants = internalTenantsClient.getTenants();
|
||||
assertThat(tenants).isNotEmpty();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@ 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.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.TestTenantService;
|
||||
|
||||
@ -68,8 +69,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
testTenantService.createTestTenantIfNotExists(tenantId, minioPort);
|
||||
TenantContext.setTenantId(tenantId);
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
.tenantId(tenantId)
|
||||
var tenantRequest = UpdateTenantRequest.builder()
|
||||
.displayName("updated_display_name")
|
||||
.searchConnection(SearchConnectionRequest.builder()
|
||||
.numberOfReplicas("1")
|
||||
@ -159,8 +159,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
testTenantService.createTestTenantIfNotExists("new_tenant", minioPort);
|
||||
TenantContext.setTenantId("new_tenant");
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
.tenantId("new_tenant")
|
||||
var tenantRequest = UpdateTenantRequest.builder()
|
||||
.s3StorageConnection(S3StorageConnection.builder()
|
||||
.key("updated_key")
|
||||
.bucketName("updated_bucket")
|
||||
@ -184,8 +183,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
testTenantService.createTestTenantWithoutStorageIfNotExist("new_tenant_without_storage");
|
||||
TenantContext.setTenantId("new_tenant_without_storage");
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
.tenantId("new_tenant_without_storage")
|
||||
var tenantRequest = UpdateTenantRequest.builder()
|
||||
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
|
||||
.build();
|
||||
|
||||
@ -202,8 +200,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
testTenantService.createTestTenantIfNotExists("new_tenant", minioPort);
|
||||
TenantContext.setTenantId("new_tenant");
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
.tenantId("new_tenant")
|
||||
var tenantRequest = UpdateTenantRequest.builder()
|
||||
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
|
||||
.s3StorageConnection(S3StorageConnection.builder()
|
||||
.key("updated_key")
|
||||
@ -228,8 +225,7 @@ public class TenantsTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
testTenantService.createTestTenantIfNotExists("new_tenant_with_s3", minioPort);
|
||||
TenantContext.setTenantId("new_tenant_with_s3");
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
.tenantId("new_tenant_with_s3")
|
||||
var tenantRequest = UpdateTenantRequest.builder()
|
||||
.azureStorageConnection(AzureStorageConnection.builder().connectionString("updated_connection").containerName("updated_container").build())
|
||||
.build();
|
||||
|
||||
|
||||
@ -9,11 +9,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
@ -26,8 +28,11 @@ 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.UserService;
|
||||
|
||||
import feign.FeignException;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
@ -35,9 +40,15 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
@Autowired
|
||||
private UserClient userClient;
|
||||
|
||||
@Autowired
|
||||
private RealmService realmService;
|
||||
|
||||
@Autowired
|
||||
private TenantUserManagementProperties tenantUserManagementProperties;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
|
||||
@Test
|
||||
public void testUsers() {
|
||||
@ -386,24 +397,24 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
createUserRequest.setFirstName("All");
|
||||
createUserRequest.setLastName("Roles");
|
||||
createUserRequest.setUsername("AllRoles");
|
||||
createUserRequest.setRoles(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");
|
||||
createUserRequest2.setRoles(allButKneconRoles);
|
||||
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");
|
||||
createUserRequest3.setRoles(onlyKneconRoles);
|
||||
User onlyKneconUser = userClient.createUser(createUserRequest3);
|
||||
addRoles(onlyKneconUser.getUserId(), onlyKneconRoles);
|
||||
|
||||
var allUsers = userClient.getAllUsers(true);
|
||||
|
||||
@ -480,9 +491,13 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
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(user.getUserId(), onlyKneconRoles));
|
||||
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());
|
||||
@ -490,59 +505,103 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
// authenticate as knecon admin again
|
||||
tokenService.setUser("admin@knecon.com", "secret");
|
||||
|
||||
// this should be possible because we now have knecon roles
|
||||
userClient.setRoles(onlyKneconUser.getUserId(), allRoles);
|
||||
assertEquals(userClient.getUserById(onlyKneconUser.getUserId()).getRoles().size(), 2);
|
||||
// this should still not be possible
|
||||
e = assertThrows(FeignException.class, () -> userClient.setRoles(onlyKneconUser.getUserId(), allRoles));
|
||||
assertEquals(404, e.status());
|
||||
|
||||
// and this as well
|
||||
userClient.setRoles(user.getUserId(), allRoles);
|
||||
// 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(user.getUserId(), new UpdateProfileRequest()));
|
||||
e = assertThrows(FeignException.class, () -> userClient.updateProfile(onlyKneconUser.getUserId(), new UpdateProfileRequest()));
|
||||
assertEquals(404, e.status());
|
||||
|
||||
// or reset password
|
||||
e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), new ResetPasswordRequest()));
|
||||
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(user.getUserId(), new ResetPasswordRequest()));
|
||||
e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), new ResetPasswordRequest()));
|
||||
assertEquals(404, e.status());
|
||||
|
||||
// or activate the profile
|
||||
e = assertThrows(FeignException.class, () -> userClient.activateProfile(user.getUserId(), true));
|
||||
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");
|
||||
createUserRequest4.setRoles(allRoles);
|
||||
User user4 = userClient.createUser(createUserRequest4);
|
||||
addRoles(user4.getUserId(), allRoles);
|
||||
|
||||
// we attempt to delete it
|
||||
userClient.deleteUser(user4.getUserId());
|
||||
// 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(user4.getUserId()));
|
||||
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(user4.getUserId())));
|
||||
// and should not have changed
|
||||
var user5 = userClient.getUserById(user4.getUserId());
|
||||
assertEquals(user4, user5);
|
||||
.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);
|
||||
|
||||
}
|
||||
|
||||
@ -563,8 +622,8 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
createUserRequest.setFirstName("Some Other");
|
||||
createUserRequest.setLastName("User");
|
||||
createUserRequest.setUsername("SomeOtherUser");
|
||||
createUserRequest.setRoles(allRoles);
|
||||
User user = userClient.createUser(createUserRequest);
|
||||
addRoles(user.getUserId(), allRoles);
|
||||
|
||||
var createUserRequest2 = new CreateUserRequest();
|
||||
createUserRequest2.setEmail("noroles@notknecon.com");
|
||||
@ -587,6 +646,36 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
}
|
||||
|
||||
|
||||
@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() {
|
||||
|
||||
@ -623,4 +712,141 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateProfileForUserWithAllRoles() {
|
||||
|
||||
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
|
||||
tokenService.setUser("admin@knecon.com", "secret");
|
||||
|
||||
var allRoles = tenantUserManagementProperties.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 = tenantUserManagementProperties.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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ 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.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
|
||||
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
|
||||
import com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer;
|
||||
@ -53,8 +53,8 @@ public class TestTenantService {
|
||||
|
||||
private void createUser(String testTenantId, int actualPort, boolean withStorage) {
|
||||
// not found
|
||||
TenantRequest tenantRequest;
|
||||
var tenantRequestBuilder = TenantRequest.builder()
|
||||
CreateTenantRequest tenantRequest;
|
||||
var tenantRequestBuilder = CreateTenantRequest.builder()
|
||||
.tenantId(testTenantId)
|
||||
.displayName(testTenantId)
|
||||
.guid(UUID.randomUUID().toString())
|
||||
|
||||
@ -85,7 +85,7 @@ spring:
|
||||
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' ]
|
||||
ignored-endpoints: [ '/actuator/health', '/tenant-user-management','/internal/**','/tenant-user-management/docs/**','/tenant-user-management/docs' ]
|
||||
enabled: true
|
||||
springdoc:
|
||||
base-path: '/tenant-user-management'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user