Skip to content

Commit

Permalink
BR-12 : Subject Access Report endpoint & Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robertmccormackbconline committed Mar 10, 2025
1 parent f1847af commit 0ddb952
Show file tree
Hide file tree
Showing 6 changed files with 592 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ class OpenApiConfiguration(buildProperties: BuildProperties) {
Components().addSecuritySchemes(
"breach-notice-api-ui-role",
SecurityScheme().addBearerJwtRequirement("ROLE_BREACH_NOTICE"),
).addSecuritySchemes(
"breach-notice-api-sar-role",
SecurityScheme().addBearerJwtRequirement("ROLE_SAR_DATA_ACCESS"),
),
)
.addSecurityItem(SecurityRequirement().addList("breach-notice-api-ui-role", listOf("read")))
.addSecurityItem(SecurityRequirement().addList("breach-notice-api-sar-role", listOf("read")))
}

private fun SecurityScheme.addBearerJwtRequirement(role: String): SecurityScheme = type(SecurityScheme.Type.HTTP)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.breachnoticeapi.exception.NotFoundException
Expand Down Expand Up @@ -161,4 +162,33 @@ class BreachNoticeController(private val breachNoticeService: BreachNoticeServic
],
)
fun deleteBreachNotice(@PathVariable id: UUID) = breachNoticeService.deleteBreachNotice(id)

@GetMapping("/subject-access-request")
@PreAuthorize("hasRole('ROLE_SAR_DATA_ACCESS')")
@Operation(
summary = "API call to retrieve SAR data from a product",
description = "Calls through the breach notice service to retrieve a any SAR related information ",
security = [SecurityRequirement(name = "breach-notice-api-ui-role")],
responses = [
ApiResponse(responseCode = "200", description = "Request successfully processed - content found"),
ApiResponse(responseCode = "204", description = "Request successfully processed - no content found"),
ApiResponse(responseCode = "209", description = "Subject Identifier is not recognised by this service"),
ApiResponse(
responseCode = "400",
description = "The request was not formed correctly",
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorResponse::class))],
),
ApiResponse(
responseCode = "401",
description = "Unauthorized to access this endpoint",
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorResponse::class))],
),
],
)
fun getSAR(
@RequestParam prn: String?,
@RequestParam crn: String?,
@RequestParam fromDate: String?,
@RequestParam toDate: String?,
) = breachNoticeService.getSARDetails(prn, crn, fromDate, toDate)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package uk.gov.justice.digital.hmpps.breachnoticeapi.controller

import io.swagger.v3.oas.annotations.Operation
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 org.springframework.security.access.prepost.PreAuthorize
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.breachnoticeapi.service.BreachNoticeService
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse
import java.util.*

@Validated
@RestController
@PreAuthorize("hasRole('ROLE_BREACH_NOTICE')")
@RequestMapping(value = ["/subject-access-request"], produces = ["application/json"])
class SarController(private val breachNoticeService: BreachNoticeService) {

@GetMapping
@PreAuthorize("hasRole('ROLE_SAR_DATA_ACCESS')")
@Operation(
summary = "API call to retrieve SAR data from a product",
description = "Calls through the breach notice service to retrieve a any SAR related information ",
security = [SecurityRequirement(name = "breach-notice-api-ui-role")],
responses = [
ApiResponse(responseCode = "200", description = "Request successfully processed - content found"),
ApiResponse(responseCode = "204", description = "Request successfully processed - no content found"),
ApiResponse(responseCode = "209", description = "Subject Identifier is not recognised by this service"),
ApiResponse(
responseCode = "400",
description = "The request was not formed correctly",
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorResponse::class))],
),
ApiResponse(
responseCode = "401",
description = "Unauthorized to access this endpoint",
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorResponse::class))],
),
],
)
fun getSAR(
@RequestParam prn: String?,
@RequestParam crn: String?,
@RequestParam fromDate: String?,
@RequestParam toDate: String?,
) = breachNoticeService.getSARDetails(prn, crn, fromDate, toDate)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ package uk.gov.justice.digital.hmpps.breachnoticeapi.repository
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import uk.gov.justice.digital.hmpps.breachnoticeapi.entity.BreachNoticeEntity
import java.time.LocalDate
import java.util.*

@Repository
interface BreachNoticeRepository : JpaRepository<BreachNoticeEntity, UUID> {
fun findByCrn(crn: String): List<BreachNoticeEntity>
fun findByCrnAndDateOfLetterBetweenOrderByDateOfLetterDesc(
crn: String,
dateOfLetter: LocalDate,
dateOfLetter2: LocalDate,
): List<BreachNoticeEntity>
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package uk.gov.justice.digital.hmpps.breachnoticeapi.service

import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.breachnoticeapi.entity.AddressEntity
Expand All @@ -17,6 +20,9 @@ import uk.gov.justice.digital.hmpps.breachnoticeapi.model.BreachNoticeDetails
import uk.gov.justice.digital.hmpps.breachnoticeapi.model.BreachNoticeRequirement
import uk.gov.justice.digital.hmpps.breachnoticeapi.model.CreateResponse
import uk.gov.justice.digital.hmpps.breachnoticeapi.repository.BreachNoticeRepository
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.util.*
import kotlin.jvm.optionals.getOrNull

Expand Down Expand Up @@ -292,4 +298,58 @@ class BreachNoticeService(

return pdfBytes
}

fun getSARDetails(prn: String?, crn: String?, fromDateString: String?, toDateString: String?): ResponseEntity<String> {
val sarParameterDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")

if (crn.isNullOrBlank() || !"^[A-Z][0-9]{6}".toRegex().matches(crn)) {
return ResponseEntity<String>(
"Supplied subject identifier is not recognised by this service",
HttpStatusCode.valueOf(209),
)
} else {
try {
if (!fromDateString.isNullOrBlank()) {
sarParameterDatePattern.parse(fromDateString)
}
if (!toDateString.isNullOrBlank()) {
sarParameterDatePattern.parse(toDateString)
}
} catch (ex: DateTimeParseException) {
return ResponseEntity<String>(
"Supplied date format is not recognised by this service",
HttpStatusCode.valueOf(209),
)
}
}

// If no from/to date supplied, use NDelius max values
val fromDate = fromDateString?.let { LocalDate.parse(fromDateString, sarParameterDatePattern) } ?: LocalDate.of(1900, 1, 1)
val toDate = toDateString?.let { LocalDate.parse(toDateString, sarParameterDatePattern) } ?: LocalDate.of(2099, 12, 31)
val crnData = breachNoticeRepository.findByCrnAndDateOfLetterBetweenOrderByDateOfLetterDesc(crn, fromDate, toDate)

if (crnData.isEmpty()) {
return ResponseEntity(
"Request successfully processed - no content found",
HttpStatus.NO_CONTENT,
)
}

// Clear Identifiable user information from the breach notice
for (bn in crnData) {
bn.titleAndFullName = null
bn.optionalNumber = null
bn.contactNumber = null
bn.responsibleOfficer = null
bn.nextAppointmentOfficer = null
}

val mapper = JsonMapper.builder().addModule(JavaTimeModule()).build()
val jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(crnData)

return ResponseEntity(
jsonString,
HttpStatus.OK,
)
}
}
Loading

0 comments on commit 0ddb952

Please sign in to comment.