Skip to content

Commit

Permalink
feat(FSADT1-1279): Set submission limits for users (#1140)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamartinezmejia committed Sep 11, 2024
1 parent 7f9d0b5 commit 3e8d20f
Show file tree
Hide file tree
Showing 28 changed files with 379 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public class ForestClientConfiguration {
@NestedConfigurationProperty
private SecurityConfiguration security;
private Duration submissionLimit;
private Duration idirSubmissionTimeWindow;
private int idirMaxSubmissions;
private Duration otherSubmissionTimeWindow;
private int otherMaxSubmissions;
@NestedConfigurationProperty
private OpenDataConfiguration openData;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ public Mono<Void> submit(
new ClientSubmissionDto(
request.businessInformation(),
request.location(),
JwtPrincipalUtil.getUserId(principal).replaceFirst("^BCSC\\\\", ""),
JwtPrincipalUtil.getLastName(principal)
JwtPrincipalUtil.getUserId(principal)
)
)
.switchIfEmpty(
Expand Down Expand Up @@ -158,29 +157,31 @@ public Mono<Void> submitStaff(
request.businessInformation().businessName()
);

return
validator.validate(request, ValidationSourceEnum.STAFF)
.doOnNext(sub -> log.info("Staff submission is valid: {}",
sub.businessInformation().businessName()))
.doOnError(e -> log.error("Staff submission is invalid: {}", e.getMessage()))
.flatMap(submission -> clientService.staffSubmit(submission, principal))
.doOnNext(clientId ->
serverResponse
.getHeaders()
.addAll(
CollectionUtils
.toMultiValueMap(
Map.of(
"Location",
List.of(String.format("/api/clients/details/%s", clientId)),
"x-client-id", List.of(String.valueOf(clientId))
)
)
)
)
.then();
}
return Mono.justOrEmpty(request)
.switchIfEmpty(
Mono.error(new InvalidRequestObjectException("no request body was provided")))
.map(req -> new ClientSubmissionDto(
req.businessInformation(),
req.location(),
JwtPrincipalUtil.getUserId(principal)))
.flatMap(sub -> validator.validate(sub, ValidationSourceEnum.STAFF))
.doOnNext(sub -> log.info("Staff submission is valid: {}",
sub.businessInformation().businessName()))
.doOnError(e -> log.error("Staff submission is invalid: {}",
e.getMessage()))
.flatMap(submission -> clientService.staffSubmit(submission, principal))
.doOnNext(clientId -> serverResponse
.getHeaders()
.addAll(CollectionUtils.toMultiValueMap(
Map.of(
"Location", List.of(String.format("/api/clients/details/%s", clientId)),
"x-client-id", List.of(String.valueOf(clientId))
)
))
)
.then();

}

@GetMapping("/{id}")
public Mono<SubmissionDetailsDto> getSubmissionDetail(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ca.bc.gov.app.controller.client;

import ca.bc.gov.app.exception.ValidationException;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ca.bc.gov.app.validator.SubmissionValidatorService;
import io.micrometer.observation.annotation.Observed;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping(value = "/api/submission-limit", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
@Observed
public class ClientSubmissionLimitController {

private final SubmissionValidatorService submissionValidatorService;

/**
* Validates the submission limit for the authenticated user.
* <p>
* This endpoint checks whether the authenticated user has exceeded their allowed number of submissions
* within a specified time frame. The time frame and submission limits are configurable parameters.
*
* @param principal the authentication token containing the details of the currently authenticated user.
* This token is used to extract the user ID and determine the submission limits applicable to them.
* @return a {@link Mono<Void>} that completes when the validation process is finished. If the user has
* exceeded their submission limit, an error will be emitted; otherwise, the Mono will complete
* successfully.
*
* @throws ValidationException if the user exceeds the maximum number of submissions allowed within
* the specified time frame.
*/
@GetMapping
public Mono<Void> validateSubmissionLimit(
JwtAuthenticationToken principal
) {
return submissionValidatorService.validateSubmissionLimit(principal);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
public record ClientSubmissionDto(
ClientBusinessInformationDto businessInformation,
ClientLocationDto location,
String userId,
String userLastName) {
String userId) {
/**
* Returns a map containing the description of the client's business information.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package ca.bc.gov.app.repository.client;

import ca.bc.gov.app.entity.client.SubmissionEntity;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SubmissionRepository extends ReactiveCrudRepository<SubmissionEntity, Integer> {

Mono<Long> countBySubmissionDateBetweenAndCreatedByIgnoreCase(
LocalDateTime startTime,
LocalDateTime endTime,
String createdBy);

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ public void customize(AuthorizeExchangeSpec authorize) {
// Only Admins and Editors are able to use the match endpoint
authorize
.pathMatchers(HttpMethod.POST, "/api/clients/matches/**")
.hasAnyRole(ApplicationConstant.ROLE_EDITOR,
.hasAnyRole(
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN,
ApplicationConstant.USERTYPE_SERVICE_USER);
authorize
.pathMatchers(HttpMethod.OPTIONS, "/api/clients/matches/**")
.hasAnyRole(ApplicationConstant.ROLE_EDITOR,
.hasAnyRole(
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN,
ApplicationConstant.USERTYPE_SERVICE_USER);

Expand All @@ -53,27 +55,31 @@ public void customize(AuthorizeExchangeSpec authorize) {
// Only BCeIDBusiness and BCSC users can POST to the duplicate endpoint
authorize
.pathMatchers(HttpMethod.POST, "/api/ches/duplicate")
.hasAnyRole(ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
.hasAnyRole(
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER);

// Only BCeIDBusiness and BCSC users can send OPTIONS request to the duplicate endpoint
authorize
.pathMatchers(HttpMethod.OPTIONS, "/api/ches/duplicate")
.hasAnyRole(ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
.hasAnyRole(
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER);

// Only BCeIDBusiness and BCSC users can GET from the addresses endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/addresses/**")
.hasAnyRole(ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
.hasAnyRole(
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN);

// Added a separate rule for the districts endpoint due to the processor service
authorize
.pathMatchers(HttpMethod.GET, "/api/codes/districts/**")
.hasAnyRole(ApplicationConstant.ROLE_VIEWER,
.hasAnyRole(
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN,
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
Expand All @@ -84,7 +90,8 @@ public void customize(AuthorizeExchangeSpec authorize) {
// Viewer, editor, admin, BCeIDBusiness and BCSC users can GET from the codes endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/codes/**")
.hasAnyRole(ApplicationConstant.ROLE_VIEWER,
.hasAnyRole(
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN,
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
Expand All @@ -93,19 +100,32 @@ public void customize(AuthorizeExchangeSpec authorize) {
// Viewer, editor, admin can GET from First Nation data endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/opendata/**")
.hasAnyRole(ApplicationConstant.ROLE_EDITOR,
.hasAnyRole(
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN);

// Viewer, editor, admin, BCeIDBusiness and BCSC users can GET from the submission limit endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/submission-limit")
.hasAnyRole(
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN,
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER);

// Only editor and admin can POST to the clients submissions endpoint with a specific id
authorize
.pathMatchers(HttpMethod.POST, "/api/clients/submissions/{id:[0-9]+}")
.hasAnyRole(ApplicationConstant.ROLE_EDITOR,
.hasAnyRole(
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN);

// Viewer, editor and admin can GET from the clients submissions endpoint with a specific id
authorize
.pathMatchers(HttpMethod.GET, "/api/clients/submissions/{id:[0-9]+}")
.hasAnyRole(ApplicationConstant.ROLE_VIEWER,
.hasAnyRole(
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN);

Expand All @@ -128,20 +148,23 @@ public void customize(AuthorizeExchangeSpec authorize) {
// Only BCeIDBusiness and BCSC users can POST to the clients submissions endpoint
authorize
.pathMatchers(HttpMethod.POST, "/api/clients/submissions/**")
.hasAnyRole(ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
.hasAnyRole(
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER);

// Viewer, editor and admin can GET from the clients submissions endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/clients/submissions/**")
.hasAnyRole(ApplicationConstant.ROLE_VIEWER,
.hasAnyRole(
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
ApplicationConstant.ROLE_ADMIN);

// BCeIDBusiness, BCSC, viewer, editor and admin users can GET from the clients endpoint
authorize
.pathMatchers(HttpMethod.GET, "/api/clients/**")
.hasAnyRole(ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
.hasAnyRole(
ApplicationConstant.USERTYPE_BCEIDBUSINESS_USER,
ApplicationConstant.USERTYPE_BCSC_USER,
ApplicationConstant.ROLE_VIEWER,
ApplicationConstant.ROLE_EDITOR,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ca.bc.gov.app.service.client.matches;

import ca.bc.gov.app.dto.client.ClientAddressDto;
import ca.bc.gov.app.dto.client.ClientLocationDto;
import ca.bc.gov.app.dto.client.ClientSubmissionDto;
import ca.bc.gov.app.dto.client.MatchResult;
import ca.bc.gov.app.dto.client.StepMatchEnum;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import ca.bc.gov.app.dto.bcregistry.ClientDetailsDto;
import ca.bc.gov.app.dto.client.ClientAddressDto;
import ca.bc.gov.app.dto.client.ClientLookUpDto;
import ca.bc.gov.app.dto.client.ClientValueTextDto;
import ca.bc.gov.app.dto.opendata.Feature;
import ca.bc.gov.app.dto.opendata.OpenData;
Expand Down
Loading

0 comments on commit 3e8d20f

Please sign in to comment.