diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml
index 0302926e58..6ed25fcb74 100644
--- a/.github/workflows/pr-open.yml
+++ b/.github/workflows/pr-open.yml
@@ -30,7 +30,7 @@ jobs:
matrix:
package: [backend, database, frontend, legacy, legacydb, processor]
steps:
- - uses: bcgov-nr/action-builder-ghcr@v2.3.0
+ - uses: bcgov-nr/action-builder-ghcr@v2.2.0
name: Build (${{ matrix.package }})
with:
package: ${{ matrix.package }}
diff --git a/backend/Dockerfile b/backend/Dockerfile
index c2a086b80d..3519ab4b1e 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -30,7 +30,6 @@ RUN mvn versions:set -DnewVersion=${APP_VERSION} -f pom.xml -DskipTests -Dtests.
# Build
RUN mvn -Pnative native:compile
-
### Deployer
FROM gcr.io/distroless/java-base:nonroot AS deploy
ARG PORT=8080
@@ -44,5 +43,7 @@ USER 1001
EXPOSE ${PORT}
HEALTHCHECK CMD curl -f http://localhost:${PORT}/actuator/health | grep '"status":"UP"'
+ENV SPRING_PROFILES_ACTIVE=container
+
# Startup
-ENTRYPOINT ["/app/nr-forest-client-backend","--spring.profiles.active=container"]
\ No newline at end of file
+ENTRYPOINT ["/app/nr-forest-client-backend"]
diff --git a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java
index 0735350f0b..c1d48d06fd 100644
--- a/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java
+++ b/backend/src/main/java/ca/bc/gov/app/controller/client/ClientController.java
@@ -4,14 +4,15 @@
import ca.bc.gov.app.dto.bcregistry.ClientDetailsDto;
import ca.bc.gov.app.dto.client.ClientListDto;
import ca.bc.gov.app.dto.client.ClientLookUpDto;
+import ca.bc.gov.app.dto.legacy.ForestClientDetailsDto;
import ca.bc.gov.app.exception.NoClientDataFound;
import ca.bc.gov.app.service.client.ClientLegacyService;
import ca.bc.gov.app.service.client.ClientService;
import ca.bc.gov.app.util.JwtPrincipalUtil;
import io.micrometer.observation.annotation.Observed;
+import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import java.util.List;
import org.apache.commons.text.WordUtils;
import org.springframework.data.util.Pair;
import org.springframework.http.MediaType;
@@ -35,8 +36,19 @@ public class ClientController {
private final ClientService clientService;
private final ClientLegacyService clientLegacyService;
+ /**
+ * Retrieves the details of a client based on the provided incorporation number.
+ *
+ *
This endpoint is used to fetch client details by their incorporation number. The request is
+ * authenticated using a JWT, and additional information (such as user ID, business ID, and
+ * provider) is extracted from the token to authorize the request.
+ *
+ * @param clientNumber the incorporation number of the client whose details are being requested
+ * @param principal the JWT authentication token containing user and business information
+ * @return a {@link Mono} emitting the {@link ClientDetailsDto} containing the client's details
+ */
@GetMapping("/{clientNumber}")
- public Mono getClientDetails(
+ public Mono getClientDetailsByIncorporationNumber(
@PathVariable String clientNumber,
JwtAuthenticationToken principal
) {
@@ -45,7 +57,7 @@ public Mono getClientDetails(
JwtPrincipalUtil.getUserId(principal)
);
return clientService
- .getClientDetails(
+ .getClientDetailsByIncorporationNumber(
clientNumber,
JwtPrincipalUtil.getUserId(principal),
JwtPrincipalUtil.getBusinessId(principal),
@@ -53,40 +65,75 @@ public Mono getClientDetails(
);
}
+ /**
+ * Handles HTTP GET requests to retrieve client details based on the provided client number.
+ *
+ * This method fetches the details of a client from the {@code ClientService} using the
+ * specified {@code clientNumber}. The caller's JWT authentication token is used to extract
+ * user-related information such as groups and user ID.
+ *
+ * @param clientNumber the unique identifier of the client whose details are to be retrieved.
+ * @param principal the {@link JwtAuthenticationToken} containing the authenticated user's
+ * information, including their roles and groups.
+ * @return a {@link Mono} emitting the {@link ForestClientDetailsDto} containing the requested
+ * client details, or an error if the client cannot be found or accessed.
+ */
+ @GetMapping("/details/{clientNumber}")
+ public Mono getClientDetailsByClientNumber(
+ @PathVariable String clientNumber,
+ JwtAuthenticationToken principal
+ ) {
+ log.info("Requesting client details for client number {} from the client service. {}",
+ clientNumber,
+ JwtPrincipalUtil.getUserId(principal)
+ );
+ return clientService.getClientDetailsByClientNumber(
+ clientNumber,
+ JwtPrincipalUtil.getGroups(principal));
+ }
+
+ /**
+ * Performs a full-text search for clients based on the provided keyword, with pagination support.
+ *
+ * This endpoint allows searching for clients by a keyword. The results are paginated, and the
+ * total count of matching records is included in the response headers.
+ *
+ * @param page the page number to retrieve (default is 0)
+ * @param size the number of records per page (default is 10)
+ * @param keyword the keyword to search for (default is an empty string, which returns all
+ * records)
+ * @param serverResponse the HTTP response to include the total count of records in the headers
+ * @return a {@link Flux} emitting {@link ClientListDto} objects containing the search results
+ */
@GetMapping("/search")
public Flux fullSearch(
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size,
@RequestParam(required = false, defaultValue = "") String keyword,
- ServerHttpResponse serverResponse) {
-
+ ServerHttpResponse serverResponse
+ ) {
log.info("Listing clients: page={}, size={}, keyword={}", page, size, keyword);
-
+
return clientLegacyService
- .search(
- page,
- size,
- keyword
- )
+ .search(page, size, keyword)
.doOnNext(pair -> {
Long count = pair.getSecond();
serverResponse
- .getHeaders()
- .putIfAbsent(
- ApplicationConstant.X_TOTAL_COUNT,
- List.of(count.toString())
- );
- }
- )
+ .getHeaders()
+ .putIfAbsent(
+ ApplicationConstant.X_TOTAL_COUNT,
+ List.of(count.toString())
+ );
+ })
.map(Pair::getFirst)
- .doFinally(signalType ->
+ .doFinally(signalType ->
serverResponse
- .getHeaders()
- .putIfAbsent(
- ApplicationConstant.X_TOTAL_COUNT,
- List.of("0")
- )
+ .getHeaders()
+ .putIfAbsent(
+ ApplicationConstant.X_TOTAL_COUNT,
+ List.of("0")
+ )
);
}
@@ -104,24 +151,45 @@ public Flux findByClientName(@PathVariable String name) {
.map(client -> client.withName(WordUtils.capitalize(client.name())));
}
+ /**
+ * Finds a client based on their registration number.
+ *
+ * This endpoint retrieves client information by searching for a registration number.
+ * If no client is found, an error is returned.
+ *
+ * @param registrationNumber the registration number of the client to look up
+ * @return a {@link Mono} emitting the {@link ClientLookUpDto} if found, or an error
+ * if no data exists
+ */
@GetMapping(value = "/incorporation/{registrationNumber}")
public Mono findByRegistrationNumber(
@PathVariable String registrationNumber) {
log.info("Requesting a client with registration number {} from the client service.",
- registrationNumber);
+ registrationNumber);
return clientService
.findByClientNameOrIncorporation(registrationNumber)
.next()
.switchIfEmpty(Mono.error(new NoClientDataFound(registrationNumber)));
}
+ /**
+ * Searches for an individual client by user ID and last name.
+ *
+ * This endpoint fetches an individual client using their user ID and last name.
+ * The request is validated against existing records in the system.
+ *
+ * @param userId the unique identifier of the individual to search for
+ * @param lastName the last name of the individual to search for
+ * @return a {@link Mono} indicating completion, or an error if the individual is not found
+ */
@GetMapping(value = "/individual/{userId}")
public Mono findByIndividual(
@PathVariable String userId,
@RequestParam String lastName
) {
- log.info("Receiving request to search individual with id {} and last name {}", userId,
- lastName);
+ log.info("Receiving request to search individual with id {} and last name {}",
+ userId,
+ lastName);
return clientService.findByIndividual(userId, lastName);
}
diff --git a/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientContactDto.java b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientContactDto.java
new file mode 100644
index 0000000000..b7eda00585
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientContactDto.java
@@ -0,0 +1,17 @@
+package ca.bc.gov.app.dto.legacy;
+
+public record ForestClientContactDto(
+ String clientNumber,
+ String clientLocnCode,
+ String contactCode,
+ String contactName,
+ String businessPhone,
+ String secondaryPhone,
+ String faxNumber,
+ String emailAddress,
+ String createdBy,
+ String updatedBy,
+ Long orgUnit
+) {
+
+}
diff --git a/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDetailsDto.java b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDetailsDto.java
new file mode 100644
index 0000000000..2a83f8f709
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDetailsDto.java
@@ -0,0 +1,35 @@
+package ca.bc.gov.app.dto.legacy;
+
+import java.time.LocalDate;
+import java.util.List;
+import lombok.With;
+
+@With
+public record ForestClientDetailsDto(
+ String clientNumber,
+ String clientName,
+ String legalFirstName,
+ String legalMiddleName,
+ String clientStatusCode,
+ String clientStatusDesc,
+ String clientTypeCode,
+ String clientTypeDesc,
+ String clientIdTypeCode,
+ String clientIdTypeDesc,
+ String clientIdentification,
+ String registryCompanyTypeCode,
+ String corpRegnNmbr,
+ String clientAcronym,
+ String wcbFirmNumber,
+ String clientComment,
+ LocalDate clientCommentUpdateDate,
+ String clientCommentUpdateUser,
+ String goodStandingInd,
+ LocalDate birthdate,
+
+ List addresses,
+ List contacts,
+ List doingBusinessAs
+) {
+
+}
diff --git a/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDoingBusinessAsDto.java b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDoingBusinessAsDto.java
new file mode 100644
index 0000000000..fe97663ad7
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientDoingBusinessAsDto.java
@@ -0,0 +1,14 @@
+package ca.bc.gov.app.dto.legacy;
+
+import lombok.With;
+
+@With
+public record ForestClientDoingBusinessAsDto(
+ String clientNumber,
+ String doingBusinessAsName,
+ String createdBy,
+ String updatedBy,
+ Long orgUnit
+) {
+
+}
diff --git a/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientLocationDto.java b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientLocationDto.java
new file mode 100644
index 0000000000..a396f4afc6
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/app/dto/legacy/ForestClientLocationDto.java
@@ -0,0 +1,33 @@
+package ca.bc.gov.app.dto.legacy;
+
+import java.time.LocalDate;
+import lombok.With;
+
+@With
+public record ForestClientLocationDto(
+ String clientNumber,
+ String clientLocnCode,
+ String clientLocnName,
+ String addressOne,
+ String addressTwo,
+ String addressThree,
+ String city,
+ String province,
+ String postalCode,
+ String country,
+ String businessPhone,
+ String homePhone,
+ String cellPhone,
+ String faxNumber,
+ String emailAddress,
+ String locnExpiredInd,
+ LocalDate returnedMailDate,
+ String trustLocationInd,
+ String cliLocnComment,
+ String createdBy,
+ String updatedBy,
+ Long orgUnit
+) {
+
+}
+
diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java
index c56903e793..98f28e4acb 100644
--- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java
+++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientLegacyService.java
@@ -3,6 +3,7 @@
import ca.bc.gov.app.dto.client.ClientListDto;
import ca.bc.gov.app.dto.legacy.AddressSearchDto;
import ca.bc.gov.app.dto.legacy.ContactSearchDto;
+import ca.bc.gov.app.dto.legacy.ForestClientDetailsDto;
import ca.bc.gov.app.dto.legacy.ForestClientDto;
import io.micrometer.observation.annotation.Observed;
import java.time.LocalDate;
@@ -18,17 +19,18 @@
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
/**
- * This class is responsible for interacting with the legacy API to fetch client data. It uses the
- * WebClient to send HTTP requests to the legacy API and converts the responses into Flux of
- * ForestClientDto objects. It provides several methods to search for clients in the legacy system
- * using different search criteria.
- *
- * It is annotated with @Slf4j for logging, @Service to indicate that it's a Spring service bean,
- * and @Observed for metrics.
- *
- * Each method logs the search parameters and the results for debugging purposes.
+ * This class is responsible for interacting with the legacy API to fetch client data.
+ * It uses the WebClient to send HTTP requests to the legacy API and converts the responses
+ * into Flux of ForestClientDto objects. It provides several methods to search for clients
+ * in the legacy system using different search criteria.
+ *
+ *
It is annotated with @Slf4j for logging, @Service to indicate that it's a
+ * Spring service bean, and @Observed for metrics.
+ *
+ *
Each method logs the search parameters and the results for debugging purposes.
*/
@Slf4j
@Service
@@ -87,6 +89,41 @@ public Flux searchLegacy(
registrationNumber, companyName, dto.clientNumber()));
}
+ /**
+ * Searches for client details by client number using the legacy API.
+ *
+ * This method communicates with the legacy API to retrieve client information based on the
+ * provided client number. Optionally, a list of groups can be specified to refine the search
+ * criteria. If a matching record is found, it is returned as a {@link ForestClientDetailsDto}.
+ *
+ * @param clientNumber the client number to search for
+ * @param groups a list of groups to filter the search (optional)
+ * @return a {@link Mono} emitting the {@link ForestClientDetailsDto} if the client is found
+ */
+ public Mono searchByClientNumber(
+ String clientNumber,
+ List groups
+ ) {
+ log.info("Searching for client number {} in legacy", clientNumber);
+
+ return
+ legacyApi
+ .get()
+ .uri(builder ->
+ builder
+ .path("/api/search/clientNumber")
+ .queryParam("clientNumber", clientNumber)
+ .queryParam("groups", groups)
+ .build(Map.of())
+ )
+ .exchangeToMono(response -> response.bodyToMono(ForestClientDetailsDto.class))
+ .doOnNext(
+ dto -> log.info(
+ "Found Legacy data for in legacy with client number {}",
+ dto.clientNumber())
+ );
+ }
+
/**
* This method is used to search for a client in the legacy system using the client's ID and last
* name.
@@ -168,10 +205,11 @@ public Flux searchIndividual(
// Convert the response to a Flux of ForestClientDto objects
.exchangeToFlux(response -> response.bodyToFlux(ForestClientDto.class))
// Log the results for debugging purposes
- .doOnNext(
- dto -> log.info(
- "Found Legacy data for first name {} and last name {} in legacy with client number {}",
- firstName, lastName, dto.clientNumber())
+ .doOnNext(dto ->
+ log.info(
+ "Found data for first {} and last name {} in legacy with client number {}",
+ firstName, lastName, dto.clientNumber()
+ )
);
}
@@ -205,7 +243,7 @@ public Flux searchDocument(
// Log the results for debugging purposes
.doOnNext(
dto -> log.info(
- "Found Legacy data for id type {} and identification {} in legacy with client number {}",
+ "Found data for id type {} and identification {} in legacy with client number {}",
idType, identification, dto.clientNumber())
);
@@ -275,7 +313,7 @@ public Flux searchGeneric(
// Log the results for debugging purposes
.doOnNext(
dto -> log.info(
- "Found Legacy data for {} with {} in legacy with client number {}",
+ "Found data for {} with {} in legacy with client number {}",
searchType,
parameters,
dto.clientNumber()
diff --git a/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java b/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java
index 0766b36644..4ee8e1203f 100644
--- a/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java
+++ b/backend/src/main/java/ca/bc/gov/app/service/client/ClientService.java
@@ -13,6 +13,7 @@
import ca.bc.gov.app.dto.client.ClientValueTextDto;
import ca.bc.gov.app.dto.client.EmailRequestDto;
import ca.bc.gov.app.dto.client.LegalTypeEnum;
+import ca.bc.gov.app.dto.legacy.ForestClientDetailsDto;
import ca.bc.gov.app.dto.legacy.ForestClientDto;
import ca.bc.gov.app.exception.ClientAlreadyExistException;
import ca.bc.gov.app.exception.InvalidAccessTokenException;
@@ -33,6 +34,7 @@
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@@ -58,7 +60,7 @@ public class ClientService {
* @param clientNumber the client number for which to retrieve details
* @return a Mono that emits a ClientDetailsDto object representing the details of the client
*/
- public Mono getClientDetails(
+ public Mono getClientDetailsByIncorporationNumber(
String clientNumber,
String userId,
String businessId,
@@ -131,16 +133,65 @@ public Mono getClientDetails(
})
// If document type is SP and party contains only one entry that is not a person, fail
- .filter(document -> provider.equalsIgnoreCase("idir") ||
- !("SP".equalsIgnoreCase(document.business().legalType()) &&
- document.parties().size() == 1 &&
- !document.parties().get(0).isPerson())
+ .filter(document -> provider.equalsIgnoreCase("idir")
+ || !("SP".equalsIgnoreCase(document.business().legalType())
+ && document.parties().size() == 1
+ && !document.parties().get(0).isPerson())
)
.flatMap(buildDetails())
.switchIfEmpty(Mono.error(new UnableToProcessRequestException(
"Unable to process request. This sole proprietor is not owned by a person"
)));
}
+
+ public Mono getClientDetailsByClientNumber(
+ String clientNumber,
+ List groups
+ ) {
+ log.info("Loading details for {} for user role {}", clientNumber, groups.toString());
+
+ return legacyService
+ .searchByClientNumber(clientNumber, groups)
+ .flatMap(forestClientDetailsDto -> {
+ String corpRegnNmbr = forestClientDetailsDto.corpRegnNmbr();
+
+ if (corpRegnNmbr == null || corpRegnNmbr.isEmpty()) {
+ log.info("Corporation registration number not provided. Returning legacy details.");
+ return Mono.just(forestClientDetailsDto);
+ }
+
+ log.info("Retrieved corporation registration number: {}", corpRegnNmbr);
+
+ return bcRegistryService
+ .requestDocumentData(corpRegnNmbr)
+ .next()
+ .flatMap(documentMono ->
+ populateGoodStandingInd(forestClientDetailsDto,
+ documentMono)
+ );
+ });
+ }
+
+ private Mono populateGoodStandingInd(
+ ForestClientDetailsDto forestClientDetailsDto,
+ BcRegistryDocumentDto document
+ ) {
+ Boolean goodStandingInd = document.business().goodStanding();
+ String goodStanding = BooleanUtils.toString(
+ goodStandingInd,
+ "Y",
+ "N",
+ StringUtils.EMPTY
+ );
+
+ log.info("Setting goodStandingInd for client: {} to {}",
+ forestClientDetailsDto.clientNumber(), goodStanding);
+
+ ForestClientDetailsDto updatedDetails =
+ forestClientDetailsDto.withGoodStandingInd(goodStanding);
+
+ return Mono.just(updatedDetails);
+ }
/**
* Searches the BC Registry API for {@link BcRegistryFacetSearchResultEntryDto} instances matching
@@ -170,7 +221,8 @@ public Mono findByIndividual(String userId, String lastName) {
return legacyService
.searchIdAndLastName(userId, lastName)
.doOnNext(legacy -> log.info("Found legacy entry for {} {}", userId, lastName))
- //If we have result, we return a Mono.error with the exception, otherwise return a Mono.empty
+ //If we have result, we return a Mono.error with the exception,
+ //otherwise return a Mono.empty
.next()
.flatMap(legacy -> Mono
.error(new ClientAlreadyExistException(legacy.clientNumber()))
diff --git a/backend/src/main/java/ca/bc/gov/app/util/JwtPrincipalUtil.java b/backend/src/main/java/ca/bc/gov/app/util/JwtPrincipalUtil.java
index b9d98c9a14..df2147df20 100644
--- a/backend/src/main/java/ca/bc/gov/app/util/JwtPrincipalUtil.java
+++ b/backend/src/main/java/ca/bc/gov/app/util/JwtPrincipalUtil.java
@@ -1,7 +1,10 @@
package ca.bc.gov.app.util;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Stream;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@@ -24,7 +27,7 @@ public class JwtPrincipalUtil {
*
* @param principal JwtAuthenticationToken object from which the provider is to be extracted.
* @return The provider of the JWT token in uppercase, or an empty string if the provider is
- * blank.
+ * blank.
*/
public static String getProvider(JwtAuthenticationToken principal) {
return getProviderValue(principal.getTokenAttributes());
@@ -38,7 +41,7 @@ public static String getProvider(JwtAuthenticationToken principal) {
*
* @param principal Jwt object from which the provider is to be extracted.
* @return The provider of the JWT token in uppercase, or an empty string if the provider is
- * blank.
+ * blank.
*/
public static String getProvider(Jwt principal) {
return getProviderValue(principal.getClaims());
@@ -53,7 +56,7 @@ public static String getProvider(Jwt principal) {
*
* @param principal JwtAuthenticationToken object from which the user ID is to be extracted.
* @return The user ID prefixed with the provider in uppercase and a backslash, or an empty string
- * if the user ID is blank.
+ * if the user ID is blank.
*/
public static String getUserId(JwtAuthenticationToken principal) {
return getUserIdValue(principal.getTokenAttributes());
@@ -67,7 +70,7 @@ public static String getUserId(JwtAuthenticationToken principal) {
*
* @param principal Jwt object from which the user ID is to be extracted.
* @return The user ID prefixed with the provider in uppercase and a backslash, or an empty string
- * if the user ID is blank.
+ * if the user ID is blank.
*/
public static String getUserId(Jwt principal) {
return getUserIdValue(principal.getClaims());
@@ -169,7 +172,7 @@ public static String getName(JwtAuthenticationToken principal) {
*
* @param principal Jwt object from which the display name is to be extracted.
* @return The display name, or the concatenated first and last names, or an empty string if both
- * the display name and the first and last names are blank.
+ * the display name and the first and last names are blank.
*/
public static String getName(Jwt principal) {
return getNameValue(principal.getClaims());
@@ -206,7 +209,7 @@ public static String getLastName(Jwt principal) {
* @param claims The map containing the JWT claims.
* @param claimName The name of the claim to retrieve.
* @return The value of the specified claim as a String, or an empty string if the claim is not
- * present.
+ * present.
*/
private static String getClaimValue(Map claims, String claimName) {
return claims
@@ -222,7 +225,7 @@ private static String getClaimValue(Map claims, String claimName
*
* @param claims The map containing the JWT claims.
* @return The provider's name in uppercase or "BCSC" if it starts with "ca.bc.gov.flnr.fam.", or
- * an empty string if the provider is not specified.
+ * an empty string if the provider is not specified.
*/
private static String getProviderValue(Map claims) {
String provider = getClaimValue(claims, "custom:idp_name");
@@ -256,7 +259,7 @@ private static String getBusinessNameValue(Map claims) {
*
* @param claims The map containing the JWT claims.
* @return The constructed user ID in the format "Provider\Username" or "Provider\UserID", or an
- * empty string if neither the username nor the user ID is present in the claims.
+ * empty string if neither the username nor the user ID is present in the claims.
*/
private static String getUserIdValue(Map claims) {
return
@@ -306,7 +309,7 @@ private static String getEmailValue(Map claims) {
*
* @param claims The map containing the JWT claims.
* @return The display name value as a String, or an empty string if the "custom:idp_display_name"
- * claim is not present.
+ * claim is not present.
*/
private static String getDisplayNameValue(Map claims) {
return getClaimValue(claims, "custom:idp_display_name");
@@ -321,9 +324,10 @@ private static String getDisplayNameValue(Map claims) {
*
* @param claims The map containing the JWT claims from which the name information is to be
* extracted.
- * @return A map with keys "businessName", "firstName", "lastName", and "fullName", containing the
- * extracted and/or computed name information. If specific name components are not found, their
- * values in the map will be empty strings.
+ * @return A map with keys "businessName", "firstName", "lastName", and "fullName",
+ * containing the extracted and/or computed name information.
+ * If specific name components are not found, their values in the map
+ * will be empty strings.
*/
private static Map processName(Map claims) {
Map additionalInfo = new HashMap<>();
@@ -374,10 +378,40 @@ private static String getLastNameValue(Map claims) {
*
* @param claims The map containing the JWT claims.
* @return The full name (concatenation of first and last names) extracted from the JWT claims, or
- * an empty string if not specified.
+ * an empty string if not specified.
*/
private static String getNameValue(Map claims) {
return processName(claims).get("fullName");
}
+
+ /**
+ * Retrieves a list of groups from the given JwtPrincipal.
+ *
+ * This method extracts the token attributes from the provided {@link JwtPrincipal}, then looks for the key "cognito:groups"
+ * in the token attributes. If the value associated with this key is a {@link List}, the method filters the elements to only
+ * include non-null values of type {@link String}. The resulting list of strings is returned.
+ *
+ * @param jwtPrincipal The {@link JwtPrincipal} containing the token attributes. It must have the "cognito:groups" key.
+ * If the key does not exist or the value is not a list of strings, an empty list is returned.
+ * @return A list of group names, or an empty list if the key is missing or the value is not a list of strings.
+ */
+ public static List getGroups(JwtAuthenticationToken jwtPrincipal) {
+ if (jwtPrincipal == null || jwtPrincipal.getTokenAttributes() == null) {
+ return Collections.emptyList();
+ }
+
+ Map tokenAttributes = jwtPrincipal.getTokenAttributes();
+ Object groups = tokenAttributes.get("cognito:groups");
+
+ if (groups instanceof List) {
+ return ((List>) groups).stream()
+ .filter(Objects::nonNull)
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .toList();
+ }
+
+ return Collections.emptyList();
+ }
}
diff --git a/backend/src/test/java/ca/bc/gov/app/service/client/ClientDistrictServiceIntegrationTest.java b/backend/src/test/java/ca/bc/gov/app/service/client/ClientDistrictServiceIntegrationTest.java
index a39f17f6e1..99c14a7c7c 100644
--- a/backend/src/test/java/ca/bc/gov/app/service/client/ClientDistrictServiceIntegrationTest.java
+++ b/backend/src/test/java/ca/bc/gov/app/service/client/ClientDistrictServiceIntegrationTest.java
@@ -10,7 +10,7 @@
@Slf4j
@DisplayName("Integrated Test | FSA Client District Service")
-public class ClientDistrictServiceIntegrationTest extends AbstractTestContainerIntegrationTest {
+class ClientDistrictServiceIntegrationTest extends AbstractTestContainerIntegrationTest {
@Autowired
private ClientDistrictService service;
diff --git a/backend/src/test/java/ca/bc/gov/app/service/client/ClientLegacyServiceIntegrationTest.java b/backend/src/test/java/ca/bc/gov/app/service/client/ClientLegacyServiceIntegrationTest.java
index 181d984d18..09d123c2a8 100644
--- a/backend/src/test/java/ca/bc/gov/app/service/client/ClientLegacyServiceIntegrationTest.java
+++ b/backend/src/test/java/ca/bc/gov/app/service/client/ClientLegacyServiceIntegrationTest.java
@@ -6,10 +6,12 @@
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import ca.bc.gov.app.dto.legacy.AddressSearchDto;
import ca.bc.gov.app.dto.legacy.ContactSearchDto;
+import ca.bc.gov.app.dto.legacy.ForestClientDetailsDto;
import ca.bc.gov.app.extensions.AbstractTestContainerIntegrationTest;
import ca.bc.gov.app.extensions.WiremockLogNotifier;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
@@ -98,7 +100,7 @@ void shouldNotSearchWhenInvalidCasesHitGeneric(Map> paramet
@Test
@DisplayName("searching legacy for location")
- void shouldSearchALocation(){
+ void shouldSearchALocation() {
legacyStub
.stubFor(
@@ -114,7 +116,7 @@ void shouldSearchALocation(){
@Test
@DisplayName("searching legacy for contact")
- void shouldSearchAContact(){
+ void shouldSearchAContact() {
legacyStub
.stubFor(
post(urlPathEqualTo("/api/search/contact"))
@@ -136,7 +138,7 @@ private static Stream invalidValues() {
);
}
- private static Stream