Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

<properties>
<java.version>25</java.version>
<spring.version>4.0.6</spring.version>
<spring.version>4.1.1</spring.version>
<microservice.name>uidam-user-management</microservice.name>
<microservice.version>${project.version}</microservice.version>
<sql.dao.version>1.2.3</sql.dao.version>
Expand Down Expand Up @@ -122,7 +122,7 @@
<!-- Jacoco property -->
<jacoco.ut.execution.data.file>${project.build.directory}/coverage-reports/jacoco-ut.exec
</jacoco.ut.execution.data.file>
<ecsp-utils.version>1.2.0</ecsp-utils.version>
<ecsp-utils.version>1.2.3</ecsp-utils.version>
<mustache.compiler.version>0.9.14</mustache.compiler.version>

<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
Expand All @@ -137,8 +137,12 @@
<skipGpgSign>true</skipGpgSign>
<nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version>
<skipMavenPublishing>true</skipMavenPublishing>
<netty.version>4.2.15.Final</netty.version>
<tomcat.version>11.0.22</tomcat.version>
<netty.version>4.2.18.Final</netty.version>
<tomcat.version>11.0.26</tomcat.version>
<jackson2.version>2.22.2</jackson2.version>
<jackson3.version>3.2.2</jackson3.version>
<mongodb.version>5.12.0</mongodb.version>
<logback.version>1.6.4</logback.version>
</properties>
<dependencyManagement>
<dependencies>
Expand All @@ -149,6 +153,20 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-bom</artifactId>
<version>${mongodb.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>tools.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson3.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
Expand Down Expand Up @@ -181,7 +199,7 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>5.0.3</version>
<version>5.0.5</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
Expand Down Expand Up @@ -455,6 +473,10 @@
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
</exclusion>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-legacy</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2023 - 2024 Harman International
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package org.eclipse.ecsp.uidam.usermanagement.auth.request.dto;

import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.eclipse.ecsp.uidam.usermanagement.enums.ClientStatus;
import java.util.Set;
import static org.eclipse.ecsp.uidam.usermanagement.constants.LocalizationKey.INVALID_ELEMENT_LENGTH;
import static org.eclipse.ecsp.uidam.usermanagement.constants.LocalizationKey.INVALID_LENGTH;
import static org.eclipse.ecsp.uidam.usermanagement.constants.LocalizationKey.INVALID_NULL_ELEMENT;

/**
* Filter criteria used to search registered oauth2 clients.
*/
@NoArgsConstructor
@Getter
@Setter
@ToString
public class ClientFilterDto {

public static final int MIN_PER_SET = 1;
public static final int MAX_PER_SET = 50;
public static final int MIN_ELEMENT_LENGTH = 1;
public static final int MAX_CLIENT_ID_LENGTH = 255;
public static final int MAX_CLIENT_NAME_LENGTH = 255;

@Schema(description = "List of client ids")
@Size(min = MIN_PER_SET, max = MAX_PER_SET, message = INVALID_LENGTH)
@Valid
private Set<@NotNull(message = INVALID_NULL_ELEMENT)
@Size(min = MIN_ELEMENT_LENGTH, max = MAX_CLIENT_ID_LENGTH,
message = INVALID_ELEMENT_LENGTH) String> clientIds;

@Schema(description = "List of client names")
@Size(min = MIN_PER_SET, max = MAX_PER_SET, message = INVALID_LENGTH)
@Valid
private Set<@NotNull(message = INVALID_NULL_ELEMENT)
@Size(min = MIN_ELEMENT_LENGTH, max = MAX_CLIENT_NAME_LENGTH,
message = INVALID_ELEMENT_LENGTH) String> clientNames;

@ArraySchema(schema = @Schema(implementation = ClientStatus.class,
allowableValues = {"approved", "deleted", "registered", "rejected"}))
@Size(min = MIN_PER_SET, max = MAX_PER_SET, message = INVALID_LENGTH)
@Valid
private Set<@NotNull(message = INVALID_NULL_ELEMENT) ClientStatus> statuses;

/**
* Client entity fields available for filtering and sorting.
*/
public enum ClientFilterDtoEnum {
CLIENT_IDS("clientId"),
CLIENT_NAMES("clientName"),
STATUS("status"),
CREATED_DATE("createDate"),
UPDATED_DATE("updateDate");

private final String field;

ClientFilterDtoEnum(String field) {
this.field = field;
}

public String getField() {
return field;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ private ApiConstants() {
public static final String RESULTS = "results";
public static final String USER = "User";
public static final String USERS = "Users";
public static final String USER_ATTRIBUTE = "UserAttribute";
public static final String USER_ATTRIBUTE_VALUE = "UserAttributeValue";
public static final String STATUS_FIELD_DESCRIPTION =
"Status. Allowed values are: [PENDING, REJECTED, ACTIVE, DEACTIVATED]";
public static final String IS_EXTERNAL_USER_DESCRIPTION =
Expand All @@ -126,6 +128,11 @@ private ApiConstants() {
public static final String SUMMARY_GET_EXTERNAL_USER = "Get external user";
public static final String SUMMARY_GET_USER_ATTRIBUTES = "Get user attributes";
public static final String SUMMARY_PUT_USER_ATTRIBUTES = "Add/Modify additional attributes to user";
public static final String SUMMARY_GET_ADDITIONAL_USER_ATTRIBUTES = "Get all additional attribute definitions";
public static final String SUMMARY_DELETE_USER_ATTRIBUTE = "Delete an additional attribute definition";
public static final String SUMMARY_GET_USER_ATTRIBUTE_VALUES = "Get a user's additional attribute values";
public static final String SUMMARY_PUT_USER_ATTRIBUTE_VALUES = "Add/Modify a user's additional attribute values";
public static final String SUMMARY_DELETE_USER_ATTRIBUTE_VALUE = "Delete a user's additional attribute value";
public static final String SUMMARY_GET_SELF_USER = "Get my user data";

public static final String SUMMARY_ADD_SELF_USER = "Create end user.";
Expand All @@ -147,6 +154,12 @@ private ApiConstants() {
public static final String BUILDER_NAME = "custom";
public static final String PATH_VARIABLE_USERNAME = "/{userName}";
public static final String PATH_USER_ATTRIBUTES = "/attributes";
public static final String PATH_VARIABLE_ATTRIBUTE_NAME = "/{attributeName}";
public static final String ATTRIBUTE_NAME = "attributeName";
public static final String PATH_USER_ATTRIBUTES_ADDITIONAL = PATH_USER_ATTRIBUTES + "/additional";
public static final String PATH_USER_ATTRIBUTE_DEFINITION = PATH_USER_ATTRIBUTES + PATH_VARIABLE_ATTRIBUTE_NAME;
public static final String PATH_USER_ATTRIBUTE_VALUES = PATH_VARIABLE_ID + PATH_USER_ATTRIBUTES + "/values";
public static final String PATH_USER_ATTRIBUTE_VALUE = PATH_USER_ATTRIBUTE_VALUES + PATH_VARIABLE_ATTRIBUTE_NAME;
public static final String CAPTCHA_REQUIRED = "required";
public static final String CAPTCHA_ENFORCE_AFTER_NO_OF_FAILURES = "enforceAfterNoOfFailures";
public static final String PATH_BY_USERNAME = "/byUserName";
Expand Down Expand Up @@ -293,6 +306,13 @@ private ApiConstants() {
public static final String ROLES_SCOPES_FILTER_PATH = "/filter";

public static final String CLIENT_RESOURCE_PATH = "/oauth2/client";
public static final String CLIENT_FILTER_PATH = "/filter";
public static final String SORT_BY_DEFAULT_FOR_FILTER_CLIENTS = "CLIENT_NAMES";
public static final String IGNORE_CASE_DEFAULT_FOR_FILTER_CLIENTS = "false";
public static final String SEARCH_TYPE_DEFAULT_FOR_FILTER_CLIENTS = "EQUAL";
public static final int MIN_PAGE_NUMBER = 0;
public static final int MIN_PAGE_SIZE = 1;
public static final int MAX_PAGE_SIZE = 100;

public static final String SPACE = " ";
public static final String COMMA = ",";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public interface LocalizationKey {
String FIELD_NOT_FOUND = "field.not.found";
String ACTION_FORBIDDEN = "action.forbidden";
String DELETE_OPERATION_FAILED = "delete.operation.failed";
String ATTRIBUTE_HAS_REFERENCED_VALUES = "attribute.has.referenced.values";
String USER_ROLES_NOT_FOUND = "user.roles.not.found";
String FIELD_CANNOT_BE_MODIFIED = "field.cannot.be.modified";
String MISSING_CORRELATION_ID = "missing.correlation.id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,33 @@
package org.eclipse.ecsp.uidam.usermanagement.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.eclipse.ecsp.uidam.usermanagement.auth.request.dto.ClientFilterDto;
import org.eclipse.ecsp.uidam.usermanagement.auth.request.dto.RegisteredClientDetails;
import org.eclipse.ecsp.uidam.usermanagement.constants.ApiConstants;
import org.eclipse.ecsp.uidam.usermanagement.constants.LocalizationKey;
import org.eclipse.ecsp.uidam.usermanagement.enums.ClientRegistrationResponseCode;
import org.eclipse.ecsp.uidam.usermanagement.enums.ClientRegistrationResponseMessage;
import org.eclipse.ecsp.uidam.usermanagement.enums.SearchType;
import org.eclipse.ecsp.uidam.usermanagement.enums.SortOrder;
import org.eclipse.ecsp.uidam.usermanagement.service.ClientRegistration;
import org.eclipse.ecsp.uidam.usermanagement.user.response.dto.BaseResponse;
import org.eclipse.ecsp.uidam.usermanagement.user.response.dto.ClientFilterResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -52,6 +62,7 @@
* Rest controller for client registration.
*/
@RestController
@Validated
@RequestMapping(value = ApiConstants.API_VERSION + ApiConstants.CLIENT_RESOURCE_PATH, produces = APPLICATION_JSON_VALUE)
public class ClientRegistrationController {
private static Logger logger = LoggerFactory.getLogger(ClientRegistrationController.class);
Expand Down Expand Up @@ -146,6 +157,63 @@ public ResponseEntity<BaseResponse> deleteClient(@PathVariable("clientId") Strin
return buildResponse(ClientRegistrationResponseCode.SP_SUCCESS.getCode(), response.get(), null, HttpStatus.OK);
}

/**
* Filter clients api.
*
* @param pageNumber zero based index of the page to retrieve
* @param pageSize number of clients to display per page
* @param sortBy client attribute the result is ordered by
* @param sortOrder order results in ASC or DESC order
* @param ignoreCase make search case-sensitive/case-insensitive
* @param searchType match values as PREFIX, SUFFIX, CONTAINS or EQUAL
* @param clientFilterDto filter criteria for each field
* @return response with the matching clients, client secret excluded
*/
@PostMapping(ApiConstants.CLIENT_FILTER_PATH)
@Operation(summary = "Filter clients", description = "Retrieve clients matching defined criteria", responses = {
@ApiResponse(responseCode = "200", description = "Success",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = BaseResponse.class))) })
@SecurityRequirement(name = "JwtAuthValidator", scopes = { "OAuth2ClientMgmt" })
public ResponseEntity<BaseResponse> filterClients(
@RequestParam(name = ApiConstants.PAGE_NUMBER, required = false,
defaultValue = ApiConstants.PAGE_NUMBER_DEFAULT)
@Parameter(description = ApiConstants.PAGE_NUMBER_DESCRIPTION)
@Min(value = ApiConstants.MIN_PAGE_NUMBER, message = LocalizationKey.INVALID_LENGTH) Integer pageNumber,
@RequestParam(name = ApiConstants.PAGE_SIZE, required = false,
defaultValue = ApiConstants.PAGE_SIZE_DEFAULT)
@Parameter(description = ApiConstants.PAGE_SIZE_DESCRIPTION)
@Min(value = ApiConstants.MIN_PAGE_SIZE, message = LocalizationKey.INVALID_LENGTH)
@Max(value = ApiConstants.MAX_PAGE_SIZE, message = LocalizationKey.INVALID_LENGTH) Integer pageSize,
@RequestParam(name = ApiConstants.SORT_BY, required = false,
defaultValue = ApiConstants.SORT_BY_DEFAULT_FOR_FILTER_CLIENTS)
@Parameter(description = ApiConstants.SORT_BY_DESCRIPTION) ClientFilterDto.ClientFilterDtoEnum sortBy,
@RequestParam(name = ApiConstants.SORT_ORDER, required = false, defaultValue = ApiConstants.DESCENDING)
@Parameter(description = ApiConstants.SORT_ORDER_DESCRIPTION, schema = @Schema(
allowableValues = { ApiConstants.DESCENDING, ApiConstants.ASCENDING })) SortOrder sortOrder,
@RequestParam(name = ApiConstants.IGNORE_CASE, required = false,
defaultValue = ApiConstants.IGNORE_CASE_DEFAULT_FOR_FILTER_CLIENTS)
@Parameter(description = ApiConstants.IGNORE_CASE_DESCRIPTION,
schema = @Schema(allowableValues = { "true", "false" })) boolean ignoreCase,
@RequestParam(name = ApiConstants.SEARCH_TYPE, required = false,
defaultValue = ApiConstants.SEARCH_TYPE_DEFAULT_FOR_FILTER_CLIENTS)
@Parameter(description = ApiConstants.SEARCH_TYPE_DESCRIPTION, schema = @Schema(
allowableValues = { "PREFIX", "SUFFIX", "CONTAINS", "EQUAL" })) SearchType searchType,
@Valid @RequestBody @Parameter(name = "Request payload",
description = "Parameters and values by which to filter. To get all clients, leave empty.")
ClientFilterDto clientFilterDto) {
logger.debug("#Filter clients request, page: {}, size: {}", pageNumber, pageSize);
ClientFilterResponse clients = clientRegistrationService.filterClients(clientFilterDto, pageNumber, pageSize,
sortBy.getField(), sortOrder.sortOrderLowerCase(), ignoreCase, searchType);
// Exclude client secret from the response for security reasons
if (clients.getItems() != null) {
clients.getItems().forEach(client -> client.setClientSecret(""));
}
return buildResponse(ClientRegistrationResponseCode.SP_SUCCESS.getCode(),
ClientRegistrationResponseMessage.SP_REGISTRATION_FILTER_SUCCESS_200_MSG.getMessage(), clients,
HttpStatus.OK);
}

/**
* Method to build client registration response.
*
Expand Down
Loading
Loading