Merge branch 'RED-8477' into 'main'
RED-8477: SSO settings endpoint for SAML See merge request fforesight/tenant-user-management-service!86
This commit is contained in:
commit
7bec8f98e6
@ -96,6 +96,7 @@ configurations {
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation("com.knecon.fforesight:database-tenant-commons:0.21.0")
|
||||
implementation("com.knecon.fforesight:keycloak-commons:0.25.0")
|
||||
implementation("com.knecon.fforesight:swagger-commons:0.5.0")
|
||||
implementation("com.knecon.fforesight:tracing-commons:0.5.0")
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external;
|
||||
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@Tag(name = "Identity provider configuration endpoints", description = "Provides operations related to the identity providers")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests."), @ApiResponse(responseCode = "403", description = "Forbidden")})
|
||||
public interface IdentityProviderConfigurationResource {
|
||||
|
||||
String API_PATH = "/configuration/identity-providers";
|
||||
String IDENTITY_PROVIDER_ALIAS_PARAM = "providerAlias";
|
||||
String IDENTITY_PROVIDER_ALIAS_PATH_PARAM = "/{" + IDENTITY_PROVIDER_ALIAS_PARAM + "}";
|
||||
String IMPORT_SUB_PATH = "/import";
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@GetMapping(value = API_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing identity providers.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
IdentityProviderList getIdentityProviders();
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@GetMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets an existing identity provider.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
|
||||
IdentityProviderModel getIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
|
||||
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
@ResponseBody
|
||||
@PostMapping(value = API_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProvider(@RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
@ResponseBody
|
||||
@PostMapping(value = API_PATH + IMPORT_SUB_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(@RequestBody IdentityProviderWithDescriptorRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@PutMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Updates an existing identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully updated the identity provider"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body")})
|
||||
ResponseEntity<IdentityProviderModel> updateIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias,
|
||||
@RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@DeleteMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM)
|
||||
@Operation(summary = "Deletes an existing identity provider.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the identity provider."), @ApiResponse(responseCode = "404", description = "Not found")})
|
||||
void deleteIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
|
||||
|
||||
}
|
||||
@ -27,11 +27,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public interface TenantsResource {
|
||||
|
||||
String TENANTS_PATH = "/tenants";
|
||||
String TENANT_ID_PARAM = "tenantId";
|
||||
String TENANT_ID_PATH_PARAM = "/{" + TENANT_ID_PARAM + "}";
|
||||
String TENANTS_TENANT_ID_PATH = TENANTS_PATH + TENANT_ID_PATH_PARAM;
|
||||
|
||||
|
||||
@PostMapping(value = "/tenants", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@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);
|
||||
@ -42,35 +44,35 @@ public interface TenantsResource {
|
||||
@Operation(summary = "Deletes given tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden access, you dont have rights to delete tenants"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."),
|
||||
@ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")})
|
||||
@DeleteMapping(value = "/tenants/{tenantId}")
|
||||
@DeleteMapping(value = TENANTS_TENANT_ID_PATH)
|
||||
void deleteTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = TENANTS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenants", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<TenantResponse> getTenants();
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants/{tenantId}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = TENANTS_TENANT_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets an existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse getTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
|
||||
@PutMapping(value = "/tenants/{tenantId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@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);
|
||||
|
||||
|
||||
@PostMapping(value = "/tenants/{tenantId}/details", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/details", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Update details", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@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();
|
||||
@ -82,7 +84,7 @@ public interface TenantsResource {
|
||||
DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId);
|
||||
|
||||
|
||||
@PostMapping(value = "/tenants/{tenantId}/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Sync existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void syncTenant(@PathVariable("tenantId") String tenantId, @RequestBody JsonNode payload);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller;
|
||||
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
@ -10,6 +9,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.ErrorMessage;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
@ -23,13 +23,17 @@ public class ControllerAdvice {
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
public ResponseEntity<ErrorMessage> handleNotFound(NotFoundException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(ForbiddenException.class)
|
||||
public ResponseEntity<ErrorMessage> handleForbiddenAccess(ForbiddenException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ -39,6 +43,7 @@ public class ControllerAdvice {
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getReason()), e.getStatusCode());
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public ResponseEntity<ErrorMessage> handleBadRequestException(BadRequestException e) {
|
||||
|
||||
|
||||
@ -0,0 +1,223 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external;
|
||||
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_IDENTITY_PROVIDER_CONFIGURATION;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.WRITE_IDENTITY_PROVIDER_CONFIGURATION;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderExistsAlreadyException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderNotFoundException;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.KeyCloakAdminClientService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.IdentityProviderMappingService;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class IdentityProviderConfigurationController implements IdentityProviderConfigurationResource, PublicResource {
|
||||
|
||||
private final RealmService realmService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final KeyCloakAdminClientService keyCloakAdminClientService;
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public IdentityProviderList getIdentityProviders() {
|
||||
|
||||
return new IdentityProviderList(getRealmRepresentation().getIdentityProviders()
|
||||
.stream()
|
||||
.map(IdentityProviderMappingService::toModelFromRepresentation)
|
||||
.toList());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public IdentityProviderModel getIdentityProvider(String identityProviderAlias) {
|
||||
|
||||
return IdentityProviderMappingService.toModelFromRepresentation(getIdentityProviderRepresentation(identityProviderAlias));
|
||||
}
|
||||
|
||||
|
||||
private IdentityProviderRepresentation getIdentityProviderRepresentation(String identityProviderAlias) {
|
||||
|
||||
return getRealmRepresentation().getIdentityProviders()
|
||||
.stream()
|
||||
.filter(identityProviderRepresentation -> identityProviderRepresentation.getAlias().equals(identityProviderAlias))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IdentityProviderNotFoundException(identityProviderAlias));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProvider(IdentityProviderRequest identityProvider) {
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProvider);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(IdentityProviderWithDescriptorRequest identityProvider) {
|
||||
|
||||
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);
|
||||
|
||||
if (configurationsMap == null || configurationsMap.isEmpty()) {
|
||||
throw new BadRequestException("Could not set config from provided descriptor.");
|
||||
}
|
||||
|
||||
configurationsMap.put("entityId", identityProvider.getEntityId());
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromDescriptorRequestAndConfig(identityProvider, configurationsMap);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
}
|
||||
|
||||
|
||||
private ResponseEntity<IdentityProviderModel> callKeyCloakIdentityProvidersCreateForModel(IdentityProviderModel identityProviderModel) {
|
||||
|
||||
identityProviderModel.setSpecificDefaults();
|
||||
|
||||
try (Response response = realmService.realm(getTenantId()).identityProviders().create(IdentityProviderMappingService.toRepresentationFromModel(identityProviderModel))) {
|
||||
var httpStatus = HttpStatus.valueOf(response.getStatus());
|
||||
switch (httpStatus) {
|
||||
case CREATED -> {
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderModel.getAlias());
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.valueOf(response.getStatus()));
|
||||
}
|
||||
case CONFLICT -> throw new IdentityProviderExistsAlreadyException(response.getStatusInfo().getReasonPhrase());
|
||||
default -> {
|
||||
if (httpStatus.is4xxClientError()) {
|
||||
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
|
||||
} else {
|
||||
throw new ResponseStatusException(httpStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> updateIdentityProvider(String identityProviderAlias, IdentityProviderRequest identityProvider) {
|
||||
|
||||
identityProvider.setAlias(identityProviderAlias);
|
||||
getIdentityProviderRepresentation(identityProviderAlias);
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProvider);
|
||||
identityProviderModel.setSpecificDefaults();
|
||||
|
||||
var restTemplate = new RestTemplate();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProvider.getAlias();
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("Authorization", "Bearer " + getToken());
|
||||
HttpEntity<IdentityProviderModel> httpEntity = new HttpEntity<>(identityProviderModel, headers);
|
||||
try {
|
||||
ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
|
||||
var httpStatus = HttpStatus.valueOf(response.getStatusCode().value());
|
||||
if (httpStatus == HttpStatus.NO_CONTENT) {
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProvider.getAlias());
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.OK);
|
||||
} else if (httpStatus.is4xxClientError()) {
|
||||
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
|
||||
} else {
|
||||
throw new ResponseStatusException(httpStatus);
|
||||
}
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public void deleteIdentityProvider(String identityProviderAlias) {
|
||||
|
||||
getIdentityProviderRepresentation(identityProviderAlias);
|
||||
|
||||
var restTemplate = new RestTemplate();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderAlias;
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("Authorization", "Bearer " + getToken());
|
||||
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
|
||||
try {
|
||||
restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String extractKeycloakErrorMessageInfos(String keyCloakErrorMessage) {
|
||||
|
||||
String errorMessageRegex = "\\{\"errorMessage\":\"(.*?)\"}";
|
||||
Pattern pattern = Pattern.compile(errorMessageRegex);
|
||||
Matcher matcher = pattern.matcher(keyCloakErrorMessage);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return keyCloakErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
private String getTenantId() {
|
||||
|
||||
return TenantContext.getTenantId();
|
||||
}
|
||||
|
||||
|
||||
private RealmRepresentation getRealmRepresentation() {
|
||||
|
||||
return realmService.realm(getTenantId()).toRepresentation();
|
||||
}
|
||||
|
||||
|
||||
private String getKeycloakIdentityProviderInstancesUrl() {
|
||||
|
||||
return tenantUserManagementProperties.getServerUrl() + "/admin/realms/" + getTenantId() + "/identity-provider/instances/";
|
||||
}
|
||||
|
||||
|
||||
private String getToken() {
|
||||
|
||||
return keyCloakAdminClientService.getAdminClient().tokenManager().getAccessToken().getToken();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
public class ConflictException extends RuntimeException {
|
||||
|
||||
public ConflictException(String message) {
|
||||
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public ConflictException(String message, Throwable t) {
|
||||
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
|
||||
public static ConflictException withObjectName(String objectName) {
|
||||
|
||||
return new ConflictException(String.format("An object of type %s already exists.", objectName));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.CONFLICT)
|
||||
public class IdentityProviderExistsAlreadyException extends ConflictException {
|
||||
|
||||
private static final String IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE = "Identity provider with alias %s already exists in keycloak.";
|
||||
|
||||
public IdentityProviderExistsAlreadyException(String identityProviderAlias) {
|
||||
super(String.format(IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE, identityProviderAlias));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class IdentityProviderNotFoundException extends NotFoundException {
|
||||
|
||||
private static final String IDENTITY_PROVIDER_NOT_FOUND_MESSAGE = "Identity provider with alias %s not found in keycloak.";
|
||||
|
||||
public IdentityProviderNotFoundException(String identityProviderAlias) {
|
||||
super(String.format(IDENTITY_PROVIDER_NOT_FOUND_MESSAGE, identityProviderAlias));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ExtensibleModel {
|
||||
|
||||
void setNotMappedFields(Map<String, String> notMappedFields);
|
||||
Map<String, String> getNotMappedFields();
|
||||
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing information about an identity provider configuration.")
|
||||
public class IdentityProviderConfigModel implements ExtensibleModel {
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether creation is allowed (default: true)")
|
||||
private Boolean allowCreate = true;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Order for GUI display (default: 0)")
|
||||
private Integer guiOrder = 0;
|
||||
|
||||
@Schema(description = "Entity ID")
|
||||
private String entityId;
|
||||
|
||||
@Schema(description = "Identity Provider Entity ID")
|
||||
private String idpEntityId;
|
||||
|
||||
@Schema(description = "Single Sign-On Service URL")
|
||||
private String singleSignOnServiceUrl;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Single Logout Service URL (default: empty string)")
|
||||
private String singleLogoutServiceUrl = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Attribute Consuming Service Name (default: empty string)")
|
||||
private String attributeConsumingServiceName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating backchannel support (default: false)")
|
||||
private Boolean backchannelSupported = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "NameID Policy Format (default: PERSISTENT)")
|
||||
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Principal Type (default: SUBJECT_NAME_ID)")
|
||||
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating post-binding response support (default: false)")
|
||||
private Boolean postBindingResponse = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating post-binding authentication request support (default: false)")
|
||||
private Boolean postBindingAuthnRequest = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating post-binding logout support (default: false)")
|
||||
private Boolean postBindingLogout = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether Authn requests should be signed (default: false)")
|
||||
private Boolean wantAuthnRequestsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether assertions should be signed (default: false)")
|
||||
private Boolean wantAssertionsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether assertions should be encrypted (default: false)")
|
||||
private Boolean wantAssertionsEncrypted = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether force authentication is required (default: false)")
|
||||
private Boolean forceAuthn = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether signature validation is required (default: false)")
|
||||
private Boolean validateSignature = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether to sign SP metadata (default: false)")
|
||||
private Boolean signSpMetadata = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether login hInteger is supported (default: false)")
|
||||
private Boolean loginHInteger = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Allowed clock skew in seconds (default: 0)")
|
||||
private Integer allowedClockSkew = 0;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Attribute Consuming Service Index (default: 0)")
|
||||
private Integer attributeConsumingServiceIndex = 0;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether login is only possible if requested explicitly (default: false)")
|
||||
private Boolean hideOnLoginPage = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Sync mode of the mapper (default: IMPORT)")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private IdentityProviderSyncMode syncMode = IdentityProviderSyncMode.IMPORT;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Schema(description = "Sync mode of the mapper (only used when wantAuthnRequestsSigned is true)")
|
||||
private IdentityProviderSignatureAlgorithm signatureAlgorithm;
|
||||
|
||||
@Schema(description = "SAML signature key name (only used when wantAuthnRequestsSigned is true)")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "All not mapped configurations")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, String> notMappedFields = new HashMap<>();
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
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 information about an identity provider configuration.")
|
||||
public class IdentityProviderConfigRequest {
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether creation is allowed (default: true)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean allowCreate = true;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Order for GUI display (default: 0)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Integer guiOrder = 0;
|
||||
|
||||
@Schema(description = "Entity ID")
|
||||
private String entityId;
|
||||
|
||||
@Schema(description = "Identity Provider Entity ID")
|
||||
private String idpEntityId;
|
||||
|
||||
@Schema(description = "Single Sign-On Service URL")
|
||||
private String singleSignOnServiceUrl;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "NameID Policy Format (default: PERSISTENT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Principal Type (default: SUBJECT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating post-binding response support (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean postBindingResponse = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating post-binding authentication request support (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean postBindingAuthnRequest = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Flag indicating whether Authn requests should be signed (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean wantAuthnRequestsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Sync mode of the mapper (only used when wantAuthnRequestsSigned is true)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderSignatureAlgorithm signatureAlgorithm = IdentityProviderSignatureAlgorithm.RSA_SHA256;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "SAML signature key name (only used when wantAuthnRequestsSigned is true)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer = IdentityProviderSAMLSignatureKeyName.KEY_ID;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IdentityProviderList {
|
||||
|
||||
@Builder.Default
|
||||
private List<IdentityProviderModel> identityProviders = new ArrayList<>();
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing information about an identity provider.")
|
||||
public class IdentityProviderModel implements ExtensibleModel {
|
||||
|
||||
@Schema(description = "Alias of the identity provider")
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Configuration of the identity provider")
|
||||
private IdentityProviderConfigModel config = new IdentityProviderConfigModel();
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider (optional)")
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Provider ID of the identity provider")
|
||||
private String providerId = "saml";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether tokens are to be stored (optional)")
|
||||
private Boolean storeToken = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether new users can read any stored tokens (optional)")
|
||||
private Boolean addReadTokenRoleOnCreate = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether emails are to be trusted (optional)")
|
||||
private Boolean trustEmail = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether users can only link and not log in through this provider (optional)")
|
||||
private Boolean linkOnly = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Alias of the authentication flow, which is triggered after first login with this identity provider (optional)")
|
||||
private String firstBrokerLoginFlowAlias = "first broker login";
|
||||
|
||||
@Builder.Default
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, String> notMappedFields = new HashMap<>();
|
||||
|
||||
|
||||
public void setSpecificDefaults() {
|
||||
|
||||
this.setStoreToken(true);
|
||||
this.setTrustEmail(true);
|
||||
this.getConfig().setSyncMode(IdentityProviderSyncMode.FORCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum IdentityProviderNameIDPolicyFormat {
|
||||
PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
|
||||
TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
|
||||
EMAIL("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"),
|
||||
KERBEROS("urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"),
|
||||
X_509_SUBJECT_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"),
|
||||
WINDOWS_DOMAIN_QUALIFIED_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName"),
|
||||
UNSPECIFIED("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
|
||||
|
||||
private final String representation;
|
||||
|
||||
|
||||
IdentityProviderNameIDPolicyFormat(String representation) {
|
||||
|
||||
this.representation = representation;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
||||
public static IdentityProviderNameIDPolicyFormat fromRepresentation(String value) {
|
||||
|
||||
for (IdentityProviderNameIDPolicyFormat v : values()) {
|
||||
if (v.getRepresentation().equalsIgnoreCase(value)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw new BadRequestException("Unsupported identity provider name ID policy format");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
|
||||
public enum IdentityProviderPrincipalType {
|
||||
SUBJECT,
|
||||
ATTRIBUTE,
|
||||
FRIENDLY_ATTRIBUTE;
|
||||
|
||||
|
||||
public static IdentityProviderPrincipalType fromStringValue(String value) {
|
||||
|
||||
for (IdentityProviderPrincipalType v : values()) {
|
||||
if (v.toString().equalsIgnoreCase(value)) {
|
||||
return v;
|
||||
}
|
||||
if (value.equalsIgnoreCase("Subject NameID")) {
|
||||
return SUBJECT;
|
||||
}
|
||||
}
|
||||
throw new BadRequestException("Unsupported identity provider principal type");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
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 = "Create or update request for an identity provider.")
|
||||
public class IdentityProviderRequest {
|
||||
|
||||
@Schema(description = "Alias of the identity provider")
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Configuration of the identity provider")
|
||||
private IdentityProviderConfigRequest config = new IdentityProviderConfigRequest();
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider (optional)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Provider ID of the identity provider", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String providerId = "saml";
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSAMLSignatureKeyName {
|
||||
NONE,
|
||||
KEY_ID,
|
||||
CERT_SUBJECT
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSignatureAlgorithm {
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA256_MGF1,
|
||||
RSA_SHA512,
|
||||
RSA_SHA512_MGF1,
|
||||
DSA_SHA1
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSyncMode {
|
||||
INHERIT,
|
||||
IMPORT,
|
||||
LEGACY,
|
||||
FORCE
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
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 = "Create or update request for an identity provider.")
|
||||
public class IdentityProviderWithDescriptorRequest {
|
||||
|
||||
@Schema(description = "Alias of the identity provider", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider (optional)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Provider ID of the identity provider", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String providerId = "saml";
|
||||
|
||||
@Schema(description = "External IDP metadata from a URL", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String samlEntityDescriptorURL;
|
||||
|
||||
@Schema(description = "Display name of the identity provider (optional)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String entityId;
|
||||
|
||||
}
|
||||
@ -27,4 +27,8 @@ public class UserManagementPermissions {
|
||||
public static final String READ_SMTP_CONFIGURATION = "fforesight-read-smtp-configuration";
|
||||
public static final String WRITE_SMTP_CONFIGURATION = "fforesight-write-smtp-configuration";
|
||||
|
||||
// Identity provider
|
||||
public static final String READ_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-read-identity-provider-config";
|
||||
public static final String WRITE_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-write-identity-provider-config";
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,179 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.utils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.ExtensibleModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderConfigModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderNameIDPolicyFormat;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderPrincipalType;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
public class IdentityProviderMappingService {
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentationFromRequest(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
return toRepresentationFromModel(toModelFromRequest(identityProviderRequest));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromRequest(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
if (!identityProviderRequest.getConfig().getWantAuthnRequestsSigned()) {
|
||||
identityProviderRequest.getConfig().setXmlSigKeyInfoKeyNameTransformer(null);
|
||||
identityProviderRequest.getConfig().setSignatureAlgorithm(null);
|
||||
}
|
||||
return MagicConverter.convert(identityProviderRequest, IdentityProviderModel.class, getModelFromRequestDeltaMapper());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderRequest, IdentityProviderModel> getModelFromRequestDeltaMapper() {
|
||||
|
||||
return ((identityProviderRequest, identityProviderModel) -> {
|
||||
var identityProviderConfigModel = MagicConverter.convert(identityProviderRequest.getConfig(), IdentityProviderConfigModel.class);
|
||||
identityProviderModel.setConfig(identityProviderConfigModel);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentationFromModel(IdentityProviderModel identityProviderModel) {
|
||||
|
||||
return MagicConverter.convert(identityProviderModel, IdentityProviderRepresentation.class, getModelToRepresentationDeltaMapper());
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderModel, IdentityProviderRepresentation> getModelToRepresentationDeltaMapper() {
|
||||
|
||||
return ((identityProviderModel, identityProviderRepresentation) -> identityProviderRepresentation.setConfig(convertObjectToMap(identityProviderModel.getConfig())));
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromDescriptorRequestAndConfig(IdentityProviderWithDescriptorRequest identityProvider, Map<String, String> configurationsMap) {
|
||||
|
||||
IdentityProviderModel identityProviderModel = MagicConverter.convert(identityProvider, IdentityProviderModel.class);
|
||||
identityProviderModel.setConfig(convertMapToObject(configurationsMap, IdentityProviderConfigModel.class));
|
||||
return identityProviderModel;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static Map<String, String> convertObjectToMap(Object obj) {
|
||||
|
||||
Map<String, String> resultMap = new HashMap<>();
|
||||
Class<?> clazz = obj.getClass();
|
||||
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
|
||||
Method getter = pd.getReadMethod();
|
||||
Object fieldValue = getter.invoke(obj);
|
||||
if (fieldValue != null) {
|
||||
resultMap.put(field.getName(), fieldValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromRepresentation(IdentityProviderRepresentation identityProviderRepresentation) {
|
||||
|
||||
return MagicConverter.convert(identityProviderRepresentation, IdentityProviderModel.class, getRepresentationToModelDeltaMapper());
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderRepresentation, IdentityProviderModel> getRepresentationToModelDeltaMapper() {
|
||||
|
||||
return ((identityProviderRepresentation, identityProviderModel) -> identityProviderModel.setConfig(convertMapToObject(identityProviderRepresentation.getConfig(),
|
||||
IdentityProviderConfigModel.class)));
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends ExtensibleModel> T convertMapToObject(Map<String, String> map, Class<T> clazz) {
|
||||
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
sanitizeMapKeys(map);
|
||||
for (Field field : fields) {
|
||||
String fieldName = field.getName();
|
||||
if (map.containsKey(fieldName)) {
|
||||
String fieldValue = map.get(fieldName);
|
||||
setFieldValue(obj, field, fieldValue);
|
||||
map.remove(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
obj.setNotMappedFields(map);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
private static void sanitizeMapKeys(Map<String, String> inputMap) {
|
||||
|
||||
var keys = new ArrayList<>(inputMap.keySet());
|
||||
for (String key : keys) {
|
||||
String sanitizedKey = sanitize(key);
|
||||
if (!sanitizedKey.equals(key)) {
|
||||
String value = inputMap.remove(key);
|
||||
inputMap.put(sanitizedKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String sanitize(String input) {
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
String[] segments = input.split("\\.");
|
||||
for (String segment : segments) {
|
||||
if (!result.isEmpty()) {
|
||||
result.append(Character.toUpperCase(segment.charAt(0)));
|
||||
result.append(segment.substring(1));
|
||||
} else {
|
||||
result.append(segment);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private static <T> void setFieldValue(T obj, Field field, String value) {
|
||||
|
||||
Class<?> fieldType = field.getType();
|
||||
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
|
||||
Method setter = pd.getWriteMethod();
|
||||
|
||||
if (fieldType == String.class) {
|
||||
setter.invoke(obj, value);
|
||||
} else if (fieldType == int.class || fieldType == Integer.class) {
|
||||
setter.invoke(obj, Integer.parseInt(value));
|
||||
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
|
||||
setter.invoke(obj, Boolean.parseBoolean(value));
|
||||
} else if (fieldType == IdentityProviderNameIDPolicyFormat.class) {
|
||||
setter.invoke(obj, IdentityProviderNameIDPolicyFormat.fromRepresentation(value));
|
||||
} else if (fieldType == IdentityProviderPrincipalType.class) {
|
||||
setter.invoke(obj, IdentityProviderPrincipalType.fromStringValue(value));
|
||||
} else if (fieldType.isEnum()) {
|
||||
Enum<?> enumValue = Enum.valueOf(fieldType.asSubclass(Enum.class), value);
|
||||
setter.invoke(obj, enumValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -45,6 +45,8 @@ fforesight:
|
||||
- "fforesight-write-users"
|
||||
- "fforesight-read-smtp-configuration"
|
||||
- "fforesight-write-smtp-configuration"
|
||||
- "fforesight-read-identity-provider-config"
|
||||
- "fforesight-write-identity-provider-config"
|
||||
- "red-unarchive-dossier"
|
||||
- name: FF_ADMIN
|
||||
set-by-default: true
|
||||
@ -63,6 +65,8 @@ fforesight:
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- "fforesight-read-identity-provider-config"
|
||||
- "fforesight-write-identity-provider-config"
|
||||
- 'fforesight-search'
|
||||
- 'fforesight-search-audit-log'
|
||||
- 'fforesight-view-document'
|
||||
|
||||
@ -28,6 +28,9 @@ fforesight:
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
application-name: "redaction"
|
||||
springdoc:
|
||||
auth-server-url: http://localhost:8080
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ 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" ]
|
||||
permissions: [ "red-read-license", "red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration", "fforesight-read-identity-provider-config","fforesight-write-identity-provider-config", "red-unarchive-dossier" ]
|
||||
- name: RED_USER
|
||||
set-by-default: true
|
||||
rank: 100
|
||||
@ -41,8 +41,8 @@ fforesight:
|
||||
permissions: [ "red-add-dictionary-entry", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
|
||||
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
|
||||
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
|
||||
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "red-read-versions", "red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
|
||||
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
|
||||
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
|
||||
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
|
||||
- name: RED_MANAGER
|
||||
set-by-default: false
|
||||
rank: 200
|
||||
|
||||
@ -6,7 +6,7 @@ fforesight:
|
||||
tenant-access-token-life-span: 300
|
||||
realm: master
|
||||
default-theme: 'redaction'
|
||||
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*']
|
||||
valid-redirect-uris: [ '/api/*','/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
|
||||
kc-role-mapping:
|
||||
unmappedPermissions: [ "red-unarchive-dossier", "red-update-license", "red-get-rss","fforesight-create-tenant", "fforesight-update-tenant", "red-experimental" ]
|
||||
compositeRoles:
|
||||
@ -37,9 +37,9 @@ fforesight:
|
||||
permissions: [ "red-add-dictionary-entry", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
|
||||
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
|
||||
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
|
||||
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "red-read-versions", "red-read-watermark",
|
||||
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "fforesight-read-identity-provider-config", "red-read-versions", "red-read-watermark",
|
||||
"red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
|
||||
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration",
|
||||
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "fforesight-write-identity-provider-config",
|
||||
"red-write-watermark", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
|
||||
- name: RED_MANAGER
|
||||
set-by-default: false
|
||||
@ -48,7 +48,7 @@ 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"]
|
||||
permissions: [ "red-read-license", "red-update-license","fforesight-get-tenants", "fforesight-create-tenant", "fforesight-update-tenant", "fforesight-delete-tenant","fforesight-read-users", "fforesight-read-all-users", "fforesight-write-users","fforesight-read-smtp-configuration", "fforesight-write-smtp-configuration","red-unarchive-dossier" ]
|
||||
- name: RED_USER_ADMIN
|
||||
set-by-default: false
|
||||
rank: 400
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.feigntestclients.external;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
|
||||
|
||||
@FeignClient(name = "IdentityProviderConfigurationClient", url = "http://localhost:${server.port}", path = "${fforesight.tenant-user-management.base-path:}")
|
||||
public interface IdentityProviderConfigurationClient extends IdentityProviderConfigurationResource {
|
||||
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.AbstractTenantUserManagementIntegrationTest;
|
||||
import com.knecon.fforesight.tenantusermanagement.feigntestclients.external.IdentityProviderConfigurationClient;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderConfigRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderNameIDPolicyFormat;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderPrincipalType;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSAMLSignatureKeyName;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSignatureAlgorithm;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderSyncMode;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
|
||||
import feign.FeignException;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class IdentityProviderConfigurationTest extends AbstractTenantUserManagementIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
IdentityProviderConfigurationClient identityProviderConfigurationClient;
|
||||
|
||||
final String testAlias = "testAlias";
|
||||
final String entityId = "https://sso-test.iqser.cloud";
|
||||
final String idpEntityId = "https://sts.windows.net/b44be368-e4f2-4ade-a089-cd2825458048/";
|
||||
final String ssoServiceUrl = "https://login.microsoftonline.com/b44be368-e4f2-4ade-a089-cd2825458048/saml2";
|
||||
final String testAliasDescriptor = "testAliasDescriptor";
|
||||
|
||||
|
||||
@Test
|
||||
public void testIdentityProviderConfiguration() {
|
||||
|
||||
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
|
||||
|
||||
var createdResponse = identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreate());
|
||||
assertEquals(201, createdResponse.getStatusCode().value());
|
||||
var identityProviderModel = createdResponse.getBody();
|
||||
assert identityProviderModel != null;
|
||||
assertEquals(identityProviderModel.getAlias(), testAlias);
|
||||
assertEquals(identityProviderModel.getConfig().getEntityId(), entityId);
|
||||
assertEquals(identityProviderModel.getConfig().getIdpEntityId(), idpEntityId);
|
||||
assertEquals(identityProviderModel.getConfig().getSingleSignOnServiceUrl(), ssoServiceUrl);
|
||||
assertTrue(identityProviderModel.getTrustEmail());
|
||||
assertTrue(identityProviderModel.getStoreToken());
|
||||
assertEquals(identityProviderModel.getConfig().getSyncMode(), IdentityProviderSyncMode.FORCE);
|
||||
|
||||
var updatedResponse = identityProviderConfigurationClient.updateIdentityProvider(testAlias, provideIdentityProviderRequestUpdate());
|
||||
assertEquals(200, updatedResponse.getStatusCode().value());
|
||||
identityProviderModel = updatedResponse.getBody();
|
||||
assert identityProviderModel != null;
|
||||
assertEquals(identityProviderModel.getConfig().getNameIDPolicyFormat(), IdentityProviderNameIDPolicyFormat.TRANSIENT);
|
||||
assertEquals(identityProviderModel.getConfig().getPrincipalType(), IdentityProviderPrincipalType.ATTRIBUTE);
|
||||
assertFalse(identityProviderModel.getConfig().getAllowCreate());
|
||||
assertTrue(identityProviderModel.getConfig().getWantAuthnRequestsSigned());
|
||||
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
|
||||
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
|
||||
var identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 1);
|
||||
|
||||
var identityProvider = identityProviderConfigurationClient.getIdentityProvider(testAlias);
|
||||
assertEquals(identityProviderModel.getAlias(), testAlias);
|
||||
assertEquals(identityProviderModel.getConfig().getEntityId(), entityId);
|
||||
assertEquals(identityProviderModel.getConfig().getIdpEntityId(), idpEntityId);
|
||||
assertEquals(identityProviderModel.getConfig().getSingleSignOnServiceUrl(), ssoServiceUrl);
|
||||
assertTrue(identityProviderModel.getTrustEmail());
|
||||
assertTrue(identityProviderModel.getStoreToken());
|
||||
assertEquals(identityProviderModel.getConfig().getSyncMode(), IdentityProviderSyncMode.FORCE);
|
||||
assertEquals(identityProviderModel.getConfig().getNameIDPolicyFormat(), IdentityProviderNameIDPolicyFormat.TRANSIENT);
|
||||
assertEquals(identityProviderModel.getConfig().getPrincipalType(), IdentityProviderPrincipalType.ATTRIBUTE);
|
||||
assertFalse(identityProviderModel.getConfig().getAllowCreate());
|
||||
assertTrue(identityProviderModel.getConfig().getWantAuthnRequestsSigned());
|
||||
assertEquals(identityProviderModel.getConfig().getXmlSigKeyInfoKeyNameTransformer(), IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT);
|
||||
assertEquals(identityProviderModel.getConfig().getSignatureAlgorithm(), IdentityProviderSignatureAlgorithm.RSA_SHA512);
|
||||
|
||||
var createdDescriptorResponse = identityProviderConfigurationClient.createIdentityProviderFromDescriptor(provideIdentityProviderWithDescriptorRequest());
|
||||
assertEquals(createdDescriptorResponse.getStatusCode().value(), 201);
|
||||
|
||||
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 2);
|
||||
|
||||
identityProviderConfigurationClient.deleteIdentityProvider(testAliasDescriptor);
|
||||
identityProviderList = identityProviderConfigurationClient.getIdentityProviders();
|
||||
assertEquals(identityProviderList.getIdentityProviders().size(), 1);
|
||||
|
||||
var e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.createIdentityProvider(provideIdentityProviderRequestCreate()));
|
||||
assertEquals(409, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> identityProviderConfigurationClient.getIdentityProvider(testAliasDescriptor));
|
||||
assertEquals(404, e.status());
|
||||
}
|
||||
|
||||
|
||||
private IdentityProviderRequest provideIdentityProviderRequestCreate() {
|
||||
|
||||
return IdentityProviderRequest.builder()
|
||||
.alias(testAlias)
|
||||
.config(IdentityProviderConfigRequest.builder()
|
||||
.entityId(entityId)
|
||||
.idpEntityId(idpEntityId)
|
||||
.singleSignOnServiceUrl(ssoServiceUrl)
|
||||
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.PERSISTENT)
|
||||
.principalType(IdentityProviderPrincipalType.SUBJECT)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private IdentityProviderRequest provideIdentityProviderRequestUpdate() {
|
||||
|
||||
return IdentityProviderRequest.builder()
|
||||
.config(IdentityProviderConfigRequest.builder()
|
||||
.entityId(entityId)
|
||||
.idpEntityId(idpEntityId)
|
||||
.singleSignOnServiceUrl(ssoServiceUrl)
|
||||
.nameIDPolicyFormat(IdentityProviderNameIDPolicyFormat.TRANSIENT)
|
||||
.principalType(IdentityProviderPrincipalType.ATTRIBUTE)
|
||||
.allowCreate(false)
|
||||
.wantAuthnRequestsSigned(true)
|
||||
.xmlSigKeyInfoKeyNameTransformer(IdentityProviderSAMLSignatureKeyName.CERT_SUBJECT)
|
||||
.signatureAlgorithm(IdentityProviderSignatureAlgorithm.RSA_SHA512)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private IdentityProviderWithDescriptorRequest provideIdentityProviderWithDescriptorRequest() {
|
||||
|
||||
return IdentityProviderWithDescriptorRequest.builder()
|
||||
.alias(testAliasDescriptor)
|
||||
.entityId(entityId)
|
||||
.samlEntityDescriptorURL("https://login.microsoftonline.com/b44be368-e4f2-4ade-a089-cd2825458048/federationmetadata/2007-06/federationmetadata.xml")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -123,6 +123,8 @@ fforesight:
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
- name: LESS_SUPER_USER
|
||||
set-by-default: true
|
||||
rank: 10
|
||||
@ -140,6 +142,8 @@ fforesight:
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
access-token-life-span: 86400
|
||||
application-name: tenant-user-management
|
||||
application-client-id: tenant-user-management
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user