Skip to content

Commit

Permalink
PI-2556 Added LAO endpoints for allocations service (#4318)
Browse files Browse the repository at this point in the history
* PI-2556 Added LAO endpoints for allocations service

* Formatting changes

---------

Co-authored-by: probation-integration-bot[bot] <177347787+probation-integration-bot[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 8a40c64 commit 91159b9
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ generic-service:
common:
SPRING_DATASOURCE_URL: DB_URL
SPRING_LDAP_URLS: LDAP_URL
SPRING_LDAP_USERNAME: LDAP_USERNAME
SPRING_LDAP_USERNAME: LDAP_ROOT_USERNAME
SPRING_LDAP_PASSWORD: LDAP_PASSWORD
workforce-allocations-to-delius-client-credentials:
INTEGRATIONS_WORKFORCE-ALLOCATIONS_CLIENT-ID: CLIENT_ID
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package uk.gov.justice.digital.hmpps.data

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.data.generator.LimitedAccessGenerator
import uk.gov.justice.digital.hmpps.data.generator.LimitedAccessGenerator.generateExclusion
import uk.gov.justice.digital.hmpps.data.generator.LimitedAccessGenerator.generateRestriction
import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
import uk.gov.justice.digital.hmpps.data.generator.UserGenerator
import uk.gov.justice.digital.hmpps.entity.Exclusion
import uk.gov.justice.digital.hmpps.entity.Restriction
import uk.gov.justice.digital.hmpps.integrations.delius.person.PersonRepository
import uk.gov.justice.digital.hmpps.integrations.delius.user.ExclusionRepository
import uk.gov.justice.digital.hmpps.integrations.delius.user.RestrictionRepository
import uk.gov.justice.digital.hmpps.user.AuditUserRepository

@Component
Expand All @@ -37,6 +36,3 @@ class LimitedAccessDataLoader(
restrictionRepository.save(generateRestriction(person = PersonGenerator.RESTRICTION_EXCLUSION))
}
}

interface ExclusionRepository : JpaRepository<Exclusion, Long>
interface RestrictionRepository : JpaRepository<Restriction, Long>
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ objectclass: inetOrgPerson
cn: JoeBloggs
sn: Bloggs
mail: [email protected]

dn: cn=MAABT001,cn=JoeBloggs,ou=Users,dc=moj,dc=com
objectclass: NDRoleAssociation
objectclass: alias
cn: MAABT001
aliasedObjectName: cn=MAABT001,cn=NDRoleAssociation,ou=Users,dc=moj,dc=com
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,40 @@ package uk.gov.justice.digital.hmpps

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.equalToIgnoringCase
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import uk.gov.justice.digital.hmpps.api.model.CaseAccess
import uk.gov.justice.digital.hmpps.api.model.CaseAccessList
import uk.gov.justice.digital.hmpps.api.model.User
import uk.gov.justice.digital.hmpps.api.model.UserAccess
import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
import uk.gov.justice.digital.hmpps.data.generator.UserGenerator
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.andExpectJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class LimitedAccessIntegrationTest {
class UserIntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Test
fun `get all users`() {
mockMvc.perform(get("/users").withToken())
.andExpect(status().is2xxSuccessful)
.andExpect(jsonPath("$[0].username", equalToIgnoringCase("JoeBloggs")))
}

@Test
fun `limited access controls are correctly returned`() {
val result = mockMvc.perform(
Expand Down Expand Up @@ -110,4 +124,49 @@ class LimitedAccessIntegrationTest {
equalTo(CaseAccess(PersonGenerator.DEFAULT.crn, userExcluded = false, userRestricted = false))
)
}

@Test
fun `get all access limitations`() {
mockMvc.perform(get("/person/${PersonGenerator.RESTRICTION_EXCLUSION.crn}/limited-access/all").withToken())
.andExpectJson(
CaseAccessList(
crn = PersonGenerator.RESTRICTION_EXCLUSION.crn,
excludedFrom = listOf(User(UserGenerator.LIMITED_ACCESS_USER.username)),
restrictedTo = listOf(User(UserGenerator.AUDIT_USER.username)),
exclusionMessage = PersonGenerator.RESTRICTION_EXCLUSION.exclusionMessage,
restrictionMessage = PersonGenerator.RESTRICTION_EXCLUSION.restrictionMessage,
)
)
}

@Test
fun `get all access limitations filtered by username`() {
mockMvc.perform(
post("/person/${PersonGenerator.RESTRICTION_EXCLUSION.crn}/limited-access")
.withToken()
.withJson(listOf(UserGenerator.AUDIT_USER.username))
).andExpectJson(
CaseAccessList(
crn = PersonGenerator.RESTRICTION_EXCLUSION.crn,
excludedFrom = emptyList(),
restrictedTo = listOf(User(UserGenerator.AUDIT_USER.username)),
exclusionMessage = PersonGenerator.RESTRICTION_EXCLUSION.exclusionMessage,
restrictionMessage = PersonGenerator.RESTRICTION_EXCLUSION.restrictionMessage,
)
)

mockMvc.perform(
post("/person/${PersonGenerator.RESTRICTION_EXCLUSION.crn}/limited-access")
.withToken()
.withJson(listOf("SomeOtherUsername"))
).andExpectJson(
CaseAccessList(
crn = PersonGenerator.RESTRICTION_EXCLUSION.crn,
excludedFrom = emptyList(),
restrictedTo = emptyList(),
exclusionMessage = PersonGenerator.RESTRICTION_EXCLUSION.exclusionMessage,
restrictionMessage = PersonGenerator.RESTRICTION_EXCLUSION.restrictionMessage,
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ data class Name(
data class Event(val number: String, val manager: Manager? = null)
data class Sentence(val type: String, val date: LocalDate, val length: String)

data class User(val username: String)

data class StaffMember(
val code: String,
val name: Name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ data class CaseAccess(
val exclusionMessage: String? = null,
val restrictionMessage: String? = null
)

data class CaseAccessList(
val crn: String,
val excludedFrom: List<User>,
val restrictedTo: List<User>,
val exclusionMessage: String? = null,
val restrictionMessage: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package uk.gov.justice.digital.hmpps.api.resource
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.constraints.Size
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*
import uk.gov.justice.digital.hmpps.api.model.User
import uk.gov.justice.digital.hmpps.service.LdapService
import uk.gov.justice.digital.hmpps.service.UserAccessService
import uk.gov.justice.digital.hmpps.service.UserService

@RestController
@RequestMapping("/users")
class UserResource(private val userAccessService: UserAccessService) {
class UserResource(
private val userAccessService: UserAccessService,
private val userService: UserService,
private val ldapService: LdapService,
) {

@PreAuthorize("hasRole('PROBATION_API__WORKFORCE_ALLOCATIONS__CASE_DETAIL')")
@Operation(
Expand All @@ -27,9 +29,25 @@ class UserResource(private val userAccessService: UserAccessService) {
has a restriction in place
"""
)
@RequestMapping("/limited-access", method = [RequestMethod.GET, RequestMethod.POST])
@RequestMapping("/users/limited-access", method = [RequestMethod.GET, RequestMethod.POST])
fun limitedAccessCheck(
@Size(min = 1, max = 500, message = "Please provide between 1 and 500 crns") @RequestBody crns: List<String>,
@RequestParam(required = false) username: String?
) = username?.let { userAccessService.userAccessFor(it, crns) } ?: userAccessService.checkLimitedAccessFor(crns)

@GetMapping("/users")
@Operation(summary = "Returns all users with the Delius `MAABT001` role")
fun allUsers() = ldapService.findAllUsersWithRole().map { User(it) }

@GetMapping("/person/{crn}/limited-access/all")
@Operation(summary = "Returns all limited access information (restrictions and exclusions) for a Delius CRN")
fun allAccessLimitationsForCrn(@PathVariable crn: String) = userService.getAllAccessLimitations(crn)

@PostMapping("/person/{crn}/limited-access")
@Operation(summary = "Returns limited access information (restrictions and exclusions) for a Delius CRN, given a list of staff codes")
fun allAccessLimitationsForCrnAndUserList(
@PathVariable crn: String,
@Size(min = 0, max = 500, message = "Please provide up to 500 usernames to filter by")
@RequestBody(required = false) usernames: List<String>? = null,
) = userService.getAllAccessLimitations(crn, usernames)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package uk.gov.justice.digital.hmpps.integrations.delius.user

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import uk.gov.justice.digital.hmpps.entity.Exclusion
import uk.gov.justice.digital.hmpps.entity.Restriction

interface ExclusionRepository : JpaRepository<Exclusion, Long> {
@Query("select e from Exclusion e where e.person.id = :personId and (e.end is null or e.end > current_date)")
fun findByPersonId(personId: Long): List<Exclusion>
}

interface RestrictionRepository : JpaRepository<Restriction, Long> {
@Query("select r from Restriction r where r.person.id = :personId and (r.end is null or r.end > current_date)")
fun findByPersonId(personId: Long): List<Restriction>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package uk.gov.justice.digital.hmpps.service

import io.opentelemetry.instrumentation.annotations.SpanAttribute
import io.opentelemetry.instrumentation.annotations.WithSpan
import org.springframework.ldap.core.AttributesMapper
import org.springframework.ldap.core.LdapTemplate
import org.springframework.ldap.filter.EqualsFilter
import org.springframework.ldap.filter.OrFilter
import org.springframework.ldap.query.LdapQueryBuilder
import org.springframework.ldap.query.SearchScope
import org.springframework.ldap.support.LdapUtils
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.integrations.delius.provider.StaffWithUser
import uk.gov.justice.digital.hmpps.integrations.delius.user.LdapUser
Expand All @@ -23,16 +25,28 @@ class LdapService(private val ldapTemplate: LdapTemplate) {
staff?.user?.username?.let { ldapTemplate.findEmailByUsername(it) }

@WithSpan
fun findEmailsForStaffIn(@SpanAttribute staff: List<StaffWithUser>) = staff.mapNotNull { it.user?.username }
.distinct()
.chunked(LDAP_MAX_RESULTS_PER_QUERY)
.flatMap {
val filter = it.map { username -> EqualsFilter("cn", username) }.fold(OrFilter()) { a, b -> a.or(b) }
val query = LdapQueryBuilder.query()
.attributes("mail")
.searchScope(SearchScope.ONELEVEL)
.filter(filter)
ldapTemplate.find(query, LdapUser::class.java)
fun findEmailsForStaffIn(@SpanAttribute staff: List<StaffWithUser>) =
staff.asSequence().mapNotNull { it.user?.username }
.distinct()
.chunked(LDAP_MAX_RESULTS_PER_QUERY)
.flatMap {
val filter = it.map { username -> EqualsFilter("cn", username) }.fold(OrFilter()) { a, b -> a.or(b) }
val query = LdapQueryBuilder.query()
.attributes("mail")
.searchScope(SearchScope.ONELEVEL)
.filter(filter)
ldapTemplate.find(query, LdapUser::class.java)
}
.associate { it.username to it.email }

@WithSpan
fun findAllUsersWithRole(role: String = "MAABT001"): List<String> = ldapTemplate.search(LdapQueryBuilder.query()
.attributes("entryDN")
.searchScope(SearchScope.SUBTREE)
.where("cn").`is`(role)
.and("objectclass").`is`("NDRoleAssociation"),
AttributesMapper {
LdapUtils.getStringValue(LdapUtils.newLdapName(it["entryDN"]?.get()?.toString()), 3)
}
.associate { it.username to it.email }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.api.model.CaseAccessList
import uk.gov.justice.digital.hmpps.api.model.User
import uk.gov.justice.digital.hmpps.exception.NotFoundException
import uk.gov.justice.digital.hmpps.integrations.delius.person.PersonRepository
import uk.gov.justice.digital.hmpps.integrations.delius.user.ExclusionRepository
import uk.gov.justice.digital.hmpps.integrations.delius.user.RestrictionRepository

@Service
class UserService(
private val personRepository: PersonRepository,
private val exclusionRepository: ExclusionRepository,
private val restrictionRepository: RestrictionRepository
) {
fun getAllAccessLimitations(crn: String, usernamesFilter: List<String>? = null): CaseAccessList {
val person = personRepository.findByCrnAndSoftDeletedFalse(crn) ?: throw NotFoundException("Person", "crn", crn)
return CaseAccessList(
crn = crn,
exclusionMessage = person.exclusionMessage,
restrictionMessage = person.restrictionMessage,
excludedFrom = exclusionRepository.findByPersonId(person.id).map { it.user.username }
.filter { usernamesFilter == null || it in usernamesFilter }
.map { User(it) },
restrictedTo = restrictionRepository.findByPersonId(person.id).map { it.user.username }
.filter { usernamesFilter == null || it in usernamesFilter }
.map { User(it) },
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ server.shutdown: immediate
spring:
datasource.url: jdbc:h2:file:./dev;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH;AUTO_SERVER=true;AUTO_SERVER_PORT=9092
jpa.hibernate.ddl-auto: create-drop
ldap.embedded.base-dn: ${spring.ldap.base}
ldap.embedded:
base-dn: ${spring.ldap.base}
validation.enabled: false

seed.database: true
context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser
Expand Down

0 comments on commit 91159b9

Please sign in to comment.