Compare commits
6 Commits
main
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d673d31a | ||
|
|
ff58ec32e1 | ||
|
|
dd4ccb71f1 | ||
|
|
46f9ca0734 | ||
|
|
7e60a66a68 | ||
|
|
da59130a7e |
@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@Tag(name = "Identity provider configuration endpoints", description = "Provides operations related to the identity providers")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests."), @ApiResponse(responseCode = "403", description = "Forbidden")})
|
||||
@ -56,7 +57,7 @@ public interface IdentityProviderConfigurationResource {
|
||||
@PostMapping(value = API_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProvider(@io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProvider(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
@ -64,7 +65,7 @@ public interface IdentityProviderConfigurationResource {
|
||||
@PostMapping(value = API_PATH + IMPORT_SUB_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(@io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderWithDescriptorRequest identityProvider);
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderWithDescriptorRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ -73,7 +74,7 @@ public interface IdentityProviderConfigurationResource {
|
||||
@Operation(summary = "Updates an existing identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully updated the identity provider"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body")})
|
||||
ResponseEntity<IdentityProviderModel> updateIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias,
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@RequestMapping("${fforesight.tenant-user-management.base-path-v2:/api}")
|
||||
public interface PublicResourceV2 {
|
||||
|
||||
}
|
||||
38
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/UserResourceV2.java
vendored
Normal file
38
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/UserResourceV2.java
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
public interface UserResourceV2 {
|
||||
|
||||
String USER_REST_PATH = "/users";
|
||||
String USER_ID = "userId";
|
||||
String USER_ID_PATH_VARIABLE = "/{" + USER_ID + "}";
|
||||
String USERNAME_PARAM = "username";
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Get a list of users", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid offset or limit specified.")})
|
||||
@GetMapping(value = USER_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Retrieve a specific user by its identifier.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The " + "userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or " + "null.")})
|
||||
@GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User getUserById(@PathVariable(USER_ID) String userId);
|
||||
|
||||
}
|
||||
29
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/User.java
vendored
Normal file
29
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/User.java
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class User {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private boolean active;
|
||||
|
||||
@Builder.Default
|
||||
private Set<String> roles = new TreeSet<>();
|
||||
|
||||
}
|
||||
18
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/UserList.java
vendored
Normal file
18
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/UserList.java
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserList {
|
||||
|
||||
private List<User> users = new ArrayList<>();
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@ -23,7 +25,9 @@ public class ControllerAdvice {
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorMessage> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
|
||||
var errorList = e.getFieldErrors();
|
||||
String errorListAsString = errorList.stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.joining(", "));
|
||||
return new ResponseEntity<>(new ErrorMessage(String.format("You have empty/wrong formatted parameters: %s", errorListAsString)), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagem
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.WRITE_IDENTITY_PROVIDER_CONFIGURATION;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -25,6 +26,7 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.ConflictException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderExistsAlreadyException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderNotFoundException;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
|
||||
@ -37,6 +39,7 @@ import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.IdentityProviderMappingService;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.InternalServerErrorException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -82,29 +85,50 @@ public class IdentityProviderConfigurationController implements IdentityProvider
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProvider(IdentityProviderRequest identityProvider) {
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProvider(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProvider);
|
||||
if (identityProviderRequest.getAlias() == null) {
|
||||
throw new BadRequestException("Alias must not be null.");
|
||||
}
|
||||
|
||||
if (identityProviderRequest.getDisplayName() == null || identityProviderRequest.getDisplayName().isEmpty()) {
|
||||
identityProviderRequest.setDisplayName(identityProviderRequest.getAlias());
|
||||
}
|
||||
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderRequest.getDisplayName());
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(IdentityProviderWithDescriptorRequest identityProvider) {
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(IdentityProviderWithDescriptorRequest identityProviderWithDescriptorRequest) {
|
||||
|
||||
if (identityProviderWithDescriptorRequest.getDisplayName() == null || identityProviderWithDescriptorRequest.getDisplayName().isEmpty()) {
|
||||
identityProviderWithDescriptorRequest.setDisplayName(identityProviderWithDescriptorRequest.getAlias());
|
||||
}
|
||||
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderWithDescriptorRequest.getDisplayName());
|
||||
|
||||
Map<String, Object> requestMap = new HashMap<>();
|
||||
requestMap.put("providerId", identityProvider.getProviderId());
|
||||
requestMap.put("fromUrl", identityProvider.getSamlEntityDescriptorURL());
|
||||
Map<String, String> configurationsMap = realmService.realm(getTenantId()).identityProviders().importFrom(requestMap);
|
||||
requestMap.put("providerId", identityProviderWithDescriptorRequest.getProviderId());
|
||||
requestMap.put("fromUrl", identityProviderWithDescriptorRequest.getSamlEntityDescriptorURL());
|
||||
|
||||
if (configurationsMap == null || configurationsMap.isEmpty()) {
|
||||
throw new BadRequestException("Could not set config from provided descriptor.");
|
||||
try {
|
||||
Map<String, String> configurationsMap = realmService.realm(getTenantId()).identityProviders().importFrom(requestMap);
|
||||
|
||||
if (configurationsMap == null || configurationsMap.isEmpty()) {
|
||||
throw new BadRequestException("Could not set config from provided descriptor.");
|
||||
}
|
||||
|
||||
configurationsMap.put("entityId", identityProviderWithDescriptorRequest.getEntityId());
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromDescriptorRequestAndConfig(identityProviderWithDescriptorRequest,
|
||||
configurationsMap);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
|
||||
} catch (InternalServerErrorException internalServerErrorException) {
|
||||
throw new BadRequestException("Provided descriptor is malformed.");
|
||||
}
|
||||
|
||||
configurationsMap.put("entityId", identityProvider.getEntityId());
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromDescriptorRequestAndConfig(identityProvider, configurationsMap);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
}
|
||||
|
||||
|
||||
@ -120,13 +144,7 @@ public class IdentityProviderConfigurationController implements IdentityProvider
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.valueOf(response.getStatus()));
|
||||
}
|
||||
case CONFLICT -> throw new IdentityProviderExistsAlreadyException(identityProviderModel.getAlias());
|
||||
default -> {
|
||||
if (httpStatus.is4xxClientError()) {
|
||||
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
|
||||
} else {
|
||||
throw new ResponseStatusException(httpStatus, httpStatus.getReasonPhrase());
|
||||
}
|
||||
}
|
||||
default -> throw new ResponseStatusException(httpStatus, extractKeycloakErrorMessageInfos(response.readEntity(String.class)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,15 +152,18 @@ public class IdentityProviderConfigurationController implements IdentityProvider
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> updateIdentityProvider(String identityProviderAlias, IdentityProviderRequest identityProvider) {
|
||||
public ResponseEntity<IdentityProviderModel> updateIdentityProvider(String identityProviderAlias, IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
identityProvider.setAlias(identityProviderAlias);
|
||||
identityProviderRequest.setAlias(identityProviderAlias);
|
||||
getIdentityProviderRepresentation(identityProviderAlias);
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProvider);
|
||||
|
||||
checkIfOtherIdpHasSameDisplayNameOnUpdate(identityProviderAlias, identityProviderRequest.getDisplayName());
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
|
||||
identityProviderModel.setSpecificDefaults();
|
||||
|
||||
var restTemplate = new RestTemplate();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProvider.getAlias();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderRequest.getAlias();
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("Authorization", "Bearer " + getToken());
|
||||
@ -151,7 +172,7 @@ public class IdentityProviderConfigurationController implements IdentityProvider
|
||||
ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
|
||||
var httpStatus = HttpStatus.valueOf(response.getStatusCode().value());
|
||||
if (httpStatus == HttpStatus.NO_CONTENT) {
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProvider.getAlias());
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderRequest.getAlias());
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.OK);
|
||||
} else if (httpStatus.is4xxClientError()) {
|
||||
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
|
||||
@ -184,6 +205,31 @@ public class IdentityProviderConfigurationController implements IdentityProvider
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayNameOnUpdate(String alias, String displayName) {
|
||||
|
||||
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders()
|
||||
.stream()
|
||||
.filter(identityProviderModel -> !identityProviderModel.getAlias().equals(alias))
|
||||
.toList(), displayName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayNameOnCreate(String displayName) {
|
||||
|
||||
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders(), displayName);
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayName(List<IdentityProviderModel> identityProviderModelList, String displayName) {
|
||||
|
||||
if (identityProviderModelList.stream()
|
||||
.anyMatch(idp -> idp.getDisplayName().isEmpty() ? idp.getAlias().equals(displayName) : idp.getDisplayName().equals(displayName))) {
|
||||
throw new ConflictException("Identity provider with this display name already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String extractKeycloakErrorMessageInfos(String keyCloakErrorMessage) {
|
||||
|
||||
String errorMessageRegex = "\\{\"errorMessage\":\"(.*?)\"}";
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external.v2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.PublicResourceV2;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.UserResourceV2;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
|
||||
import com.knecon.fforesight.tenantusermanagement.controller.external.UserController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "6. Users endpoints", description = "Operations related to users.")
|
||||
public class UserContollerV2 implements UserResourceV2, PublicResourceV2 {
|
||||
|
||||
private final UserController userController;
|
||||
|
||||
|
||||
public List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username) {
|
||||
|
||||
var users = userController.getApplicationSpecificUsers(false)
|
||||
.stream()
|
||||
.map(this::convertUser);
|
||||
|
||||
if (username == null) {
|
||||
return users.toList();
|
||||
}
|
||||
return users.filter(user -> user.getUsername().equals(username)).toList();
|
||||
}
|
||||
|
||||
|
||||
public User getUserById(@PathVariable(USER_ID) String userId) {
|
||||
return convertUser(userController.getUserById(userId));
|
||||
}
|
||||
|
||||
|
||||
private User convertUser(com.knecon.fforesight.tenantusermanagement.model.User user){
|
||||
return User.builder()
|
||||
.id(user.getUserId())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmail())
|
||||
.firstName(user.getFirstName())
|
||||
.lastName(user.getLastName())
|
||||
.active(user.isActive())
|
||||
.roles(user.getRoles())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -22,12 +23,15 @@ public class IdentityProviderConfigRequest {
|
||||
private Integer guiOrder = 0;
|
||||
|
||||
@Schema(description = "Service provider entity ID : It is used to uniquely identify this SAML Service Provider.", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String entityId;
|
||||
|
||||
@Schema(description = "Identity provider entity ID : It is used to validate the Issuer for received SAML assertions. If empty, no Issuer validation is performed.", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String idpEntityId;
|
||||
|
||||
@Schema(description = "The Url that must be used to send authentication requests (SAML AuthnRequest).", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String singleSignOnServiceUrl;
|
||||
|
||||
@Builder.Default
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -18,7 +20,8 @@ public class IdentityProviderRequest {
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Configuration of the identity provider", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private IdentityProviderConfigRequest config = new IdentityProviderConfigRequest();
|
||||
@NotNull
|
||||
private @Valid IdentityProviderConfigRequest config = new IdentityProviderConfigRequest();
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider in keycloak (optional, fallbacks to alias if empty)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -14,6 +15,7 @@ import lombok.NoArgsConstructor;
|
||||
public class IdentityProviderWithDescriptorRequest {
|
||||
|
||||
@Schema(description = "Alias of the identity provider used for identification", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@ -25,9 +27,10 @@ public class IdentityProviderWithDescriptorRequest {
|
||||
private String providerId = "saml";
|
||||
|
||||
@Schema(description = "The SAML entity descriptor as a URL to external IDP metadata, used to fill other fields for a faster identity provider creation process", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String samlEntityDescriptorURL;
|
||||
|
||||
@Schema(description = "Service provider entity ID that will be used to uniquely identify this SAML Service Provider", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String entityId;
|
||||
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ public class TenantUserManagementProperties {
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String basePath = "/";
|
||||
private String basePathV2 = "/api";
|
||||
private int connectionPoolSize = 10;
|
||||
private String applicationName;
|
||||
private Integer accessTokenLifeSpan = 1800;
|
||||
|
||||
@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@ -35,12 +36,32 @@ public class IdentityProviderConfigurationTest extends AbstractTenantUserManagem
|
||||
final String idpEntityId = "https://sts.windows.net/b44be368-e4f2-4ade-a089-cd2825458048/";
|
||||
final String ssoServiceUrl = "https://login.microsoftonline.com/b44be368-e4f2-4ade-a089-cd2825458048/saml2";
|
||||
final String testAliasDescriptor = "testAliasDescriptor";
|
||||
final String invalidUrl = "this is no valid URL :(";
|
||||
|
||||
final String idp1TestAlias = "idp1";
|
||||
final String idp2TestAlias = "idp2";
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEachTest() {
|
||||
|
||||
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
|
||||
setupTestDataIfNotExistent();
|
||||
}
|
||||
|
||||
|
||||
private void setupTestDataIfNotExistent() {
|
||||
|
||||
var identityProviders = identityProviderConfigurationClient.getIdentityProviders().getIdentityProviders();
|
||||
if(identityProviders.stream().noneMatch(identityProviderModel -> identityProviderModel.getAlias().equals(idp1TestAlias))) {
|
||||
identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp1());
|
||||
identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp2());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIdentityProviderConfiguration() {
|
||||
|
||||
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
|
||||
public void testCreateEntityProviderSuccessful() {
|
||||
|
||||
var createdResponse = identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreate());
|
||||
assertEquals(201, createdResponse.getStatusCode().value());
|
||||
@ -54,9 +75,21 @@ public class IdentityProviderConfigurationTest extends AbstractTenantUserManagem
|
||||
assertTrue(identityProviderModel.getStoreToken());
|
||||
assertEquals(identityProviderModel.getConfig().getSyncMode(), IdentityProviderSyncMode.FORCE);
|
||||
|
||||
var updatedResponse = identityProviderConfigurationClient.updateIdentityProvider(testAlias, provideIdentityProviderRequestUpdate());
|
||||
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(3, identityProviderList.getIdentityProviders().size());
|
||||
|
||||
identityProviderConfigurationClient.deleteIdentityProvider(testAlias);
|
||||
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(2, identityProviderList.getIdentityProviders().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateEntityProviderSuccessful() {
|
||||
|
||||
var updatedResponse = identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, provideIdentityProviderRequestUpdate());
|
||||
assertEquals(200, updatedResponse.getStatusCode().value());
|
||||
identityProviderModel = updatedResponse.getBody();
|
||||
var identityProviderModel = updatedResponse.getBody();
|
||||
assert identityProviderModel != null;
|
||||
assertEquals(identityProviderModel.getConfig().getNameIDPolicyFormat(), IdentityProviderNameIDPolicyFormat.TRANSIENT);
|
||||
assertEquals(identityProviderModel.getConfig().getPrincipalType(), IdentityProviderPrincipalType.ATTRIBUTE);
|
||||
@ -65,10 +98,10 @@ public class IdentityProviderConfigurationTest extends AbstractTenantUserManagem
|
||||
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
|
||||
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
|
||||
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 1);
|
||||
assertEquals(2, identityProviderList.getIdentityProviders().size());
|
||||
|
||||
var identityProvider = identityProviderConfigurationClient.getIdentityProvider(testAlias);
|
||||
assertEquals(identityProviderModel.getAlias(), testAlias);
|
||||
identityProviderModel = identityProviderConfigurationClient.getIdentityProvider(idp1TestAlias);
|
||||
assertEquals(identityProviderModel.getAlias(), idp1TestAlias);
|
||||
assertEquals(identityProviderModel.getConfig().getEntityId(), entityId);
|
||||
assertEquals(identityProviderModel.getConfig().getIdpEntityId(), idpEntityId);
|
||||
assertEquals(identityProviderModel.getConfig().getSingleSignOnServiceUrl(), ssoServiceUrl);
|
||||
@ -81,22 +114,188 @@ public class IdentityProviderConfigurationTest extends AbstractTenantUserManagem
|
||||
assertTrue(identityProviderModel.getConfig().getWantAuthnRequestsSigned());
|
||||
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
|
||||
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderFromDescriptorSuccessful() {
|
||||
|
||||
var createdDescriptorResponse = identityProviderConfigurationClient.createIdentityProviderFromDescriptor(provideIdentityProviderWithDescriptorRequest());
|
||||
assertEquals(createdDescriptorResponse.getStatusCode().value(), 201);
|
||||
assertEquals(201, createdDescriptorResponse.getStatusCode().value());
|
||||
|
||||
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 2);
|
||||
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(3, identityProviderList.getIdentityProviders().size());
|
||||
|
||||
identityProviderConfigurationClient.deleteIdentityProvider(testAliasDescriptor);
|
||||
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 1);
|
||||
assertEquals(2, identityProviderList.getIdentityProviders().size());
|
||||
}
|
||||
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreate()));
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderDuplicated() {
|
||||
|
||||
String otherAlias = "otherAlias";
|
||||
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreateForIdp1()));
|
||||
assertEquals(409, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.getIdentityProvider(testAliasDescriptor));
|
||||
var requestWithSameDisplayName = provideIdentityProviderRequestCreate();
|
||||
requestWithSameDisplayName.setAlias(otherAlias);
|
||||
requestWithSameDisplayName.setDisplayName(idp1TestAlias);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithSameDisplayName));
|
||||
assertEquals(409, e.status());
|
||||
|
||||
var descriptorRequestWithSameDisplayName = provideIdentityProviderWithDescriptorRequest();
|
||||
descriptorRequestWithSameDisplayName.setAlias(otherAlias);
|
||||
descriptorRequestWithSameDisplayName.setDisplayName(idp1TestAlias);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithSameDisplayName));
|
||||
assertEquals(409, e.status());
|
||||
|
||||
var updateRequestWithDisplayNameFromOtherIdp = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithDisplayNameFromOtherIdp.setDisplayName(idp1TestAlias);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp2TestAlias, updateRequestWithDisplayNameFromOtherIdp));
|
||||
assertEquals(409, e.status());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateDeleteEntityProviderThatNotExists() {
|
||||
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, provideIdentityProviderRequestUpdate()));
|
||||
assertEquals(404, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.deleteIdentityProvider(testAlias));
|
||||
assertEquals(404, e.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestNoAlias() {
|
||||
|
||||
var requestWithoutAlias = provideIdentityProviderRequestCreate();
|
||||
requestWithoutAlias.setAlias(null);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutAlias));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var descriptorRequestWithoutAlias = provideIdentityProviderWithDescriptorRequest();
|
||||
descriptorRequestWithoutAlias.setAlias(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutAlias));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(null, provideIdentityProviderRequestUpdate()));
|
||||
assertEquals(404, e.status());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestNoProvider() {
|
||||
|
||||
var requestWithoutProvider = provideIdentityProviderRequestCreate();
|
||||
requestWithoutProvider.setProviderId(null);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutProvider));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var descriptorRequestWithoutProvider = provideIdentityProviderWithDescriptorRequest();
|
||||
descriptorRequestWithoutProvider.setProviderId(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutProvider));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var updateRequestWithoutProvider = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithoutProvider.setProviderId(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithoutProvider));
|
||||
assertEquals(400, e.status());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestNoEntityId() {
|
||||
|
||||
var requestWithoutEntityId = provideIdentityProviderRequestCreate();
|
||||
requestWithoutEntityId.getConfig().setEntityId(null);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutEntityId));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var descriptorRequestWithoutEntityId = provideIdentityProviderWithDescriptorRequest();
|
||||
descriptorRequestWithoutEntityId.setEntityId(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithoutEntityId));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var updateRequestWithoutEntityId = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithoutEntityId.getConfig().setEntityId(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, updateRequestWithoutEntityId));
|
||||
assertEquals(400, e.status());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestNoIdpEntityId() {
|
||||
|
||||
var requestWithoutIdpEntityId = provideIdentityProviderRequestCreate();
|
||||
requestWithoutIdpEntityId.getConfig().setIdpEntityId(null);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutIdpEntityId));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var updateRequestWithoutEntityId = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithoutEntityId.getConfig().setIdpEntityId(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(testAlias, updateRequestWithoutEntityId));
|
||||
assertEquals(400, e.status());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestNoSSOUrl() {
|
||||
|
||||
var requestWithoutSSOUrl = provideIdentityProviderRequestCreate();
|
||||
requestWithoutSSOUrl.getConfig().setSingleSignOnServiceUrl(null);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithoutSSOUrl));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var updateRequestWithoutSSOUrl = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithoutSSOUrl.getConfig().setSingleSignOnServiceUrl(null);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithoutSSOUrl));
|
||||
assertEquals(400, e.status());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestMalformedSSOUrl() {
|
||||
|
||||
var requestWithMalformedSingleSignOnURL = provideIdentityProviderRequestCreate();
|
||||
requestWithMalformedSingleSignOnURL.getConfig().setSingleSignOnServiceUrl(invalidUrl);
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(requestWithMalformedSingleSignOnURL));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
var updateRequestWithMalformedSingleSignOnURL = provideIdentityProviderRequestUpdate();
|
||||
updateRequestWithMalformedSingleSignOnURL.getConfig().setSingleSignOnServiceUrl(invalidUrl);
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, updateRequestWithMalformedSingleSignOnURL));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateEntityProviderBadRequestMalformedEntityDescriptorUrl() {
|
||||
|
||||
var descriptorRequestWithMalformedEntityDescriptorUrl = provideIdentityProviderWithDescriptorRequest();
|
||||
descriptorRequestWithMalformedEntityDescriptorUrl.setSamlEntityDescriptorURL(invalidUrl);
|
||||
var e = assertThrows(FeignException.class,
|
||||
() -> identityProviderConfigurationClient.createIdentityProviderFromDescriptor(descriptorRequestWithMalformedEntityDescriptorUrl));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateEntityProviderInvalidOperation() {
|
||||
|
||||
var invalidUpdateRequest = provideIdentityProviderRequestUpdate();
|
||||
invalidUpdateRequest.getConfig().setPrincipalType(IdentityProviderPrincipalType.SUBJECT);
|
||||
var e = assertThrows(FeignException.class,
|
||||
() -> identityProviderConfigurationClient.updateIdentityProvider(idp1TestAlias, invalidUpdateRequest));
|
||||
assertEquals(400, e.status());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -114,6 +313,35 @@ public class IdentityProviderConfigurationTest extends AbstractTenantUserManagem
|
||||
.build();
|
||||
}
|
||||
|
||||
private IdentityProviderRequest provideIdentityProviderRequestCreateForIdp1() {
|
||||
|
||||
return IdentityProviderRequest.builder()
|
||||
.alias(idp1TestAlias)
|
||||
.config(IdentityProviderConfigRequest.builder()
|
||||
.entityId(entityId)
|
||||
.idpEntityId(idpEntityId)
|
||||
.singleSignOnServiceUrl(ssoServiceUrl)
|
||||
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.TRANSIENT)
|
||||
.principalType(IdentityProviderPrincipalType.ATTRIBUTE)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private IdentityProviderRequest provideIdentityProviderRequestCreateForIdp2() {
|
||||
|
||||
return IdentityProviderRequest.builder()
|
||||
.alias(idp2TestAlias)
|
||||
.config(IdentityProviderConfigRequest.builder()
|
||||
.entityId(entityId)
|
||||
.idpEntityId(idpEntityId)
|
||||
.singleSignOnServiceUrl(ssoServiceUrl)
|
||||
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.X_509_SUBJECT_NAME)
|
||||
.principalType(IdentityProviderPrincipalType.SUBJECT)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private IdentityProviderRequest provideIdentityProviderRequestUpdate() {
|
||||
|
||||
return IdentityProviderRequest.builder()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user