diff --git a/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/ldap/LdapTemplateExtensions.kt b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/ldap/LdapTemplateExtensions.kt index dce66e9668..ea6158f780 100644 --- a/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/ldap/LdapTemplateExtensions.kt +++ b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/ldap/LdapTemplateExtensions.kt @@ -23,17 +23,31 @@ inline fun LdapTemplate.findByUsername(@SpanAttribute username: Stri find(query().byUsername(username), T::class.java).singleOrNull() @WithSpan -fun LdapTemplate.findEmailByUsername(@SpanAttribute username: String) = search( +fun LdapTemplate.findEmailByUsername(@SpanAttribute username: String) = findAttributeByUsername(username, "mail") + +@WithSpan +fun LdapTemplate.findAttributeByUsername(@SpanAttribute username: String, @SpanAttribute attribute: String) = search( query() - .attributes("mail") + .attributes(attribute) .base(ldapBase) .searchScope(SearchScope.ONELEVEL) .where("objectclass").`is`("inetOrgPerson") .and("objectclass").`is`("top") .and("cn").`is`(username), - AttributesMapper { it["mail"]?.get()?.toString() } + AttributesMapper { it[attribute]?.get()?.toString() } ).singleOrNull() +@WithSpan +fun LdapTemplate.getRoles(@SpanAttribute username: String) = search( + query() + .attributes("cn") + .base(LdapNameBuilder.newInstance(ldapBase).add("cn", username).build()) + .searchScope(SearchScope.ONELEVEL) + .where("objectclass").`is`("NDRole") + .or("objectclass").`is`("NDRoleAssociation"), + AttributesMapper { it["cn"]?.get()?.toString() } +).filterNotNull() + @WithSpan fun LdapTemplate.addRole(@SpanAttribute username: String, @SpanAttribute role: DeliusRole) { val roleContext = lookupContext(role.context()) diff --git a/projects/hdc-licences-and-delius/.trivyignore b/projects/hdc-licences-and-delius/.trivyignore index e69de29bb2..84a663c97c 100644 --- a/projects/hdc-licences-and-delius/.trivyignore +++ b/projects/hdc-licences-and-delius/.trivyignore @@ -0,0 +1,3 @@ +# Suppressed as we do not process any untrusted YML content +# Note: this will be resolved in Spring Boot 3.2: https://github.com/spring-projects/spring-boot/issues/35982 +CVE-2022-1471 exp:2023-12-01 diff --git a/projects/hdc-licences-and-delius/build.gradle.kts b/projects/hdc-licences-and-delius/build.gradle.kts index 687f79a7a0..c9e23131df 100644 --- a/projects/hdc-licences-and-delius/build.gradle.kts +++ b/projects/hdc-licences-and-delius/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-data-ldap") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") @@ -19,6 +20,7 @@ dependencies { implementation(libs.springdoc) dev(project(":libs:dev-tools")) + dev("com.unboundid:unboundid-ldapsdk") dev("com.h2database:h2") dev("org.testcontainers:oracle-xe") diff --git a/projects/hdc-licences-and-delius/deploy/values-dev.yml b/projects/hdc-licences-and-delius/deploy/values-dev.yml index a67af329b8..669f3f5a09 100644 --- a/projects/hdc-licences-and-delius/deploy/values-dev.yml +++ b/projects/hdc-licences-and-delius/deploy/values-dev.yml @@ -1,5 +1,3 @@ -enabled: false # TODO set this to true when you're ready to deploy your service - generic-service: ingress: host: hdc-licences-and-delius-dev.hmpps.service.justice.gov.uk diff --git a/projects/hdc-licences-and-delius/deploy/values-preprod.yml b/projects/hdc-licences-and-delius/deploy/values-preprod.yml index bec1160e69..83e83c1952 100644 --- a/projects/hdc-licences-and-delius/deploy/values-preprod.yml +++ b/projects/hdc-licences-and-delius/deploy/values-preprod.yml @@ -1,5 +1,3 @@ -enabled: false # TODO set this to true when you're ready to deploy your service - generic-service: ingress: host: hdc-licences-and-delius-preprod.hmpps.service.justice.gov.uk diff --git a/projects/hdc-licences-and-delius/deploy/values-prod.yml b/projects/hdc-licences-and-delius/deploy/values-prod.yml index 3c47e13ec2..554b1a9e4b 100644 --- a/projects/hdc-licences-and-delius/deploy/values-prod.yml +++ b/projects/hdc-licences-and-delius/deploy/values-prod.yml @@ -1,5 +1,3 @@ -enabled: false # TODO set this to true when you're ready to deploy your service - generic-service: ingress: host: hdc-licences-and-delius.hmpps.service.justice.gov.uk diff --git a/projects/hdc-licences-and-delius/deploy/values.yaml b/projects/hdc-licences-and-delius/deploy/values.yaml index 6eb6dcf556..82ff948e25 100644 --- a/projects/hdc-licences-and-delius/deploy/values.yaml +++ b/projects/hdc-licences-and-delius/deploy/values.yaml @@ -11,6 +11,9 @@ generic-service: namespace_secrets: common: SPRING_DATASOURCE_URL: DB_URL + SPRING_LDAP_URLS: LDAP_URL + SPRING_LDAP_USERNAME: LDAP_USERNAME + SPRING_LDAP_PASSWORD: LDAP_PASSWORD hdc-licences-and-delius-database: SPRING_DATASOURCE_USERNAME: DB_USERNAME SPRING_DATASOURCE_PASSWORD: DB_PASSWORD diff --git a/projects/hdc-licences-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/hdc-licences-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index f6ea94c9a7..526c4d2ad2 100644 --- a/projects/hdc-licences-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/hdc-licences-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -1,17 +1,30 @@ package uk.gov.justice.digital.hmpps.data import jakarta.annotation.PostConstruct +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component +import uk.gov.justice.digital.hmpps.data.generator.IdGenerator import uk.gov.justice.digital.hmpps.data.generator.UserGenerator +import uk.gov.justice.digital.hmpps.entity.Borough +import uk.gov.justice.digital.hmpps.entity.CommunityManagerEntity +import uk.gov.justice.digital.hmpps.entity.District +import uk.gov.justice.digital.hmpps.entity.Person +import uk.gov.justice.digital.hmpps.entity.ProbationArea +import uk.gov.justice.digital.hmpps.entity.StaffEntity +import uk.gov.justice.digital.hmpps.entity.Team +import uk.gov.justice.digital.hmpps.entity.User +import uk.gov.justice.digital.hmpps.set import uk.gov.justice.digital.hmpps.user.AuditUserRepository @Component @ConditionalOnProperty("seed.database") class DataLoader( - private val auditUserRepository: AuditUserRepository + private val auditUserRepository: AuditUserRepository, + private val entityManager: EntityManager ) : ApplicationListener { @PostConstruct @@ -19,7 +32,27 @@ class DataLoader( auditUserRepository.save(UserGenerator.AUDIT_USER) } + @Transactional override fun onApplicationEvent(are: ApplicationReadyEvent) { - // Perform dev/test database setup here, using JPA repositories and generator classes... + val probationArea = ProbationArea(id = id(), code = "TST", description = "Test") + val borough = Borough(id = id(), code = "PDU", description = "Probation Delivery Unit", probationArea = probationArea) + .also { probationArea.set(ProbationArea::boroughs, setOf(it)) } + val district = District(id = id(), code = "LAU", description = "Local Admin Unit", borough = borough) + .also { borough.set(Borough::districts, setOf(it)) } + val team1 = Team(id = id(), code = "TEAM01", description = "Team 1", district = district, probationArea = probationArea) + val team2 = Team(id = id(), code = "TEAM02", description = "Team 2", district = district, probationArea = probationArea) + val staff = StaffEntity(id = id(), code = "STAFF01", forename = "Test", surname = "Staff", teams = listOf(team1, team2)) + val user = User(id = id(), username = "test.user", staff = staff) + .also { staff.set(StaffEntity::user, it) } + val person = Person(id = id(), nomsNumber = "PERSON1") + val previousManager = CommunityManagerEntity(id = id(), person = person, staff = staff, team = team1, active = false) + val currentManager = CommunityManagerEntity(id = id(), person = person, staff = staff, team = team2, active = true) + .also { staff.set(StaffEntity::communityManagers, setOf(it)) } + .also { person.set(Person::communityManagers, listOf(it)) } + + listOf(probationArea, borough, district, team1, team2, staff, user, person, previousManager, currentManager) + .forEach(entityManager::persist) } + + private fun id() = IdGenerator.getAndIncrement() } diff --git a/projects/hdc-licences-and-delius/src/dev/resources/local-public-key.pub b/projects/hdc-licences-and-delius/src/dev/resources/local-public-key.pub index c0b70f3172..99c388c454 100644 --- a/projects/hdc-licences-and-delius/src/dev/resources/local-public-key.pub +++ b/projects/hdc-licences-and-delius/src/dev/resources/local-public-key.pub @@ -1,6 +1,6 @@ -----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo3hw1/oChbttEOxEH4NUDrH+Y -n2x0DavAmDjMbhcSiQ6+/t8Nz/N03BauWzFOGBtftnQrHfnF+O7RAKj8zMjcbIq4 -QrYeXEpnaFCGEwTtOBpxvSEWPrLEpr1gCarBQZDp67ag+SYqrDgkn2Vme/dMvMUQ -xUO3DT6jg9921J6TlwIDAQAB +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMwjPPJWNVkkbz1rvp5gfIgikJ +tiar18XcBScET2wOs7nAviD0nBzLByMGfexX0LQz3J9+S0Ut/bSqPyrgPe9Wc5s9 +6JwH6biAYzuLOVhLXBpmwGP6FpZIoSA+VkwApLN9j8qPBvAYrIftpTWsEq090VUE +WHvs4bqvS01yN7IPTwIDAQAB -----END PUBLIC KEY----- \ No newline at end of file diff --git a/projects/hdc-licences-and-delius/src/dev/resources/schema.ldif b/projects/hdc-licences-and-delius/src/dev/resources/schema.ldif new file mode 100644 index 0000000000..22f1d6ada0 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/dev/resources/schema.ldif @@ -0,0 +1,49 @@ +dn: dc=moj,dc=com +dc: moj +objectclass: top +objectclass: domain + +dn: ou=Users,dc=moj,dc=com +ou: Users +objectclass: top +objectclass: organizationalUnit + +dn: cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com +cn: ndRoleCatalogue +objectclass: top +objectclass: javaContainer + +dn: cn=LHDCBT002,cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com +cn: LHDCBT002 +description: Digital Licence Create +objectclass: top +objectClass: NDRole + +dn: cn=LHDCBT003,cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com +cn: LHDCBT003 +description: Digital Licence Vary +objectclass: top +objectClass: NDRole + +dn: cn=test.user,ou=Users,dc=moj,dc=com +cn: test.user +objectclass: NDUser +objectclass: inetOrgPerson +objectclass: top +givenName: Test +sn: User +mail: test.user@example.com + +dn: cn=LHDCBT002,cn=test.user,ou=Users,dc=moj,dc=com +cn: LHDCBT002 +objectclass: top +objectClass: NDRoleAssociation + +dn: cn=inactive.user,ou=Users,dc=moj,dc=com +cn: inactive.user +objectclass: NDUser +objectclass: inetOrgPerson +objectclass: top +givenName: Inactive +sn: User +endDate: 20000101 diff --git a/projects/hdc-licences-and-delius/src/dev/resources/simulations/__files/hmpps-auth-token-body.json b/projects/hdc-licences-and-delius/src/dev/resources/simulations/__files/hmpps-auth-token-body.json index 33e1aa358c..358501dc3d 100644 --- a/projects/hdc-licences-and-delius/src/dev/resources/simulations/__files/hmpps-auth-token-body.json +++ b/projects/hdc-licences-and-delius/src/dev/resources/simulations/__files/hmpps-auth-token-body.json @@ -1,5 +1,5 @@ { - "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInVzZXJfbmFtZSI6InByb2JhdGlvbi1pbnRlZ3JhdGlvbi1kZXYiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXV0aF9zb3VyY2UiOiJub25lIiwiaXNzIjoiaHR0cHM6Ly9zaWduLWluLWRldi5obXBwcy5zZXJ2aWNlLmp1c3RpY2UuZ292LnVrL2F1dGgvaXNzdWVyIiwiZXhwIjo5OTk5OTk5OTk5LCJhdXRob3JpdGllcyI6WyJST0xFX0VYQU1QTEUiLCJST0xFX1dPUktMT0FEX1JFQUQiLCJST0xFX0FMTE9DQVRJT05fQ09OVEVYVCJdLCJqdGkiOiIyNUR1Um4xLWh5SFpld0xjZEpKeHdWTDAzS1UiLCJjbGllbnRfaWQiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiaWF0IjoxNjYzNzU3MzExfQ.5FTCUjA7QZMPxO_EMzkGNSM-IkPk2hfPXyzuNiAa7uuqYva_yCducrC5FdetAiC1W6XpUB7wfoMNDmbW2xepj5oRhcxDx18r92aLPYnKkxaA68hLQF90euMtTzfBzOPg-rKDTNIJKrUC-YoQlFKuCauw0Z5cw1XT6R9GIfi5Yx4", + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInVzZXJfbmFtZSI6InByb2JhdGlvbi1pbnRlZ3JhdGlvbi1kZXYiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXV0aF9zb3VyY2UiOiJub25lIiwiaXNzIjoiaHR0cHM6Ly9zaWduLWluLWRldi5obXBwcy5zZXJ2aWNlLmp1c3RpY2UuZ292LnVrL2F1dGgvaXNzdWVyIiwiZXhwIjo5OTk5OTk5OTk5LCJhdXRob3JpdGllcyI6WyJST0xFX1BST0JBVElPTl9BUElfX0hEQ19fU1RBRkYiLCJST0xFX1BST0JBVElPTl9BUElfX0hEQ19fVVNFUl9ST0xFUyJdLCJqdGkiOiIyNUR1Um4xLWh5SFpld0xjZEpKeHdWTDAzS1UiLCJjbGllbnRfaWQiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiaWF0IjoxNjYzNzU3MzExfQ.l90PSVBcVIRA7sgNkVG4UZElkSm1eUqc6N8XId5sIz7mfmcY3IqV3ksErknw0oSRvPb3ZbzCZ4Pc7KoWdfrXouYHgzrYQZfIYCJp7PqyHbfrdr_B46X2Q_QtzhN2Ec7ku6A4NNSj4yp6tW8i4c_xuaw3NwcpgT-s_xZ39nT4Mg4", "token_type": "bearer", "expires_in": 9999999999, "scope": "read write", diff --git a/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt deleted file mode 100644 index d2382a469a..0000000000 --- a/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package uk.gov.justice.digital.hmpps - -import com.github.tomakehurst.wiremock.WireMockServer -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.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import uk.gov.justice.digital.hmpps.security.withOAuth2Token -import uk.gov.justice.digital.hmpps.telemetry.TelemetryService - -@AutoConfigureMockMvc -@SpringBootTest(webEnvironment = RANDOM_PORT) -internal class IntegrationTest { - @Autowired lateinit var mockMvc: MockMvc - - @Autowired lateinit var wireMockServer: WireMockServer - - @MockBean lateinit var telemetryService: TelemetryService - - @Test - fun `API call retuns a success response`() { - mockMvc - .perform(get("/example/123").withOAuth2Token(wireMockServer)) - .andExpect(status().is2xxSuccessful) - } -} diff --git a/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProviderIntegrationTest.kt b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProviderIntegrationTest.kt new file mode 100644 index 0000000000..e54c8a45ae --- /dev/null +++ b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProviderIntegrationTest.kt @@ -0,0 +1,74 @@ +package uk.gov.justice.digital.hmpps + +import com.github.tomakehurst.wiremock.WireMockServer +import org.hamcrest.Matchers.equalTo +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.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.security.withOAuth2Token + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +internal class ProviderIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var wireMockServer: WireMockServer + + @Test + fun `providers are returned successfully`() { + mockMvc + .perform(get("/providers").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("length()", equalTo(1))) + .andExpect(jsonPath("[0].code", equalTo("TST"))) + .andExpect(jsonPath("[0].description", equalTo("Test"))) + } + + @Test + fun `single provider is returned successfully`() { + mockMvc + .perform(get("/providers/TST").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("code", equalTo("TST"))) + .andExpect(jsonPath("description", equalTo("Test"))) + .andExpect(jsonPath("localAdminUnits.length()", equalTo(1))) + .andExpect(jsonPath("localAdminUnits[0].code", equalTo("LAU"))) + .andExpect(jsonPath("localAdminUnits[0].description", equalTo("Local Admin Unit"))) + } + + @Test + fun `non-existent provider returns 404`() { + mockMvc + .perform(get("/providers/DOESNOTEXIST").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + } + + @Test + fun `local admin unit is returned successfully`() { + mockMvc + .perform(get("/providers/TST/localAdminUnits/LAU").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("code", equalTo("LAU"))) + .andExpect(jsonPath("description", equalTo("Local Admin Unit"))) + .andExpect(jsonPath("teams.length()", equalTo(2))) + .andExpect(jsonPath("teams[0].code", equalTo("TEAM01"))) + .andExpect(jsonPath("teams[0].description", equalTo("Team 1"))) + .andExpect(jsonPath("teams[1].code", equalTo("TEAM02"))) + .andExpect(jsonPath("teams[1].description", equalTo("Team 2"))) + } + + @Test + fun `non-existent local admin unit returns 404`() { + mockMvc + .perform(get("/providers/TST/localAdminUnits/DOESNOTEXIST").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + } +} diff --git a/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffIntegrationTest.kt b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffIntegrationTest.kt new file mode 100644 index 0000000000..644b301047 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffIntegrationTest.kt @@ -0,0 +1,97 @@ +package uk.gov.justice.digital.hmpps + +import com.github.tomakehurst.wiremock.WireMockServer +import org.hamcrest.Matchers.equalTo +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.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.security.withOAuth2Token + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +internal class StaffIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var wireMockServer: WireMockServer + + @Test + fun `get staff by code`() { + mockMvc + .perform(get("/staff/STAFF01").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("code", equalTo("STAFF01"))) + .andExpect(jsonPath("username", equalTo("test.user"))) + .andExpect(jsonPath("name.forenames", equalTo("Test"))) + .andExpect(jsonPath("name.surname", equalTo("Staff"))) + .andExpect(jsonPath("teams[*].code", equalTo(listOf("TEAM01", "TEAM02")))) + } + + @Test + fun `get staff by username`() { + mockMvc + .perform(get("/staff?username=test.user").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("code", equalTo("STAFF01"))) + .andExpect(jsonPath("username", equalTo("test.user"))) + } + + @Test + fun `staff by code not found`() { + mockMvc.perform(get("/staff/DOESNOTEXIST").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + .andExpect(jsonPath("message", equalTo("Staff with code of DOESNOTEXIST not found"))) + } + + @Test + fun `staff by username not found`() { + mockMvc.perform(get("/staff?username=DOESNOTEXIST").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + .andExpect(jsonPath("message", equalTo("Staff with username of DOESNOTEXIST not found"))) + } + + @Test + fun `staff by id not found`() { + mockMvc + .perform(get("/staff?id=-1").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + .andExpect(jsonPath("message", equalTo("Staff with staffId of -1 not found"))) + } + + @Test + fun `get managed prisoners`() { + mockMvc + .perform(get("/staff/STAFF01/managedPrisonerIds").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("[*]", equalTo(listOf("PERSON1")))) + } + + @Test + fun `get community manager`() { + mockMvc + .perform(get("/case/PERSON1/communityManager").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("code", equalTo("STAFF01"))) + .andExpect(jsonPath("name.forenames", equalTo("Test"))) + .andExpect(jsonPath("name.surname", equalTo("Staff"))) + .andExpect(jsonPath("team.code", equalTo("TEAM02"))) + .andExpect(jsonPath("localAdminUnit.code", equalTo("LAU"))) + .andExpect(jsonPath("provider.code", equalTo("TST"))) + .andExpect(jsonPath("isUnallocated", equalTo(false))) + } + + @Test + fun `community manager not found`() { + mockMvc + .perform(get("/case/DOESNOTEXIST/communityManager").withOAuth2Token(wireMockServer)) + .andExpect(status().isNotFound) + .andExpect(jsonPath("message", equalTo("Community manager for case with nomsNumber of DOESNOTEXIST not found"))) + } +} diff --git a/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt new file mode 100644 index 0000000000..6851651cf1 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt @@ -0,0 +1,68 @@ +package uk.gov.justice.digital.hmpps + +import com.github.tomakehurst.wiremock.WireMockServer +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasItems +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.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.security.withOAuth2Token + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +internal class UserIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var wireMockServer: WireMockServer + + @Test + fun `user details are returned successfully`() { + mockMvc + .perform(get("/users/test.user/details").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("username", equalTo("test.user"))) + .andExpect(jsonPath("enabled", equalTo(true))) + .andExpect(jsonPath("roles.length()", equalTo(1))) + .andExpect(jsonPath("roles[0]", equalTo("LHDCBT002"))) + } + + @Test + fun `user with end date is returned with enabled=false`() { + mockMvc + .perform(get("/users/inactive.user/details").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + .andExpect(jsonPath("username", equalTo("inactive.user"))) + .andExpect(jsonPath("enabled", equalTo(false))) + } + + @Test + fun `invalid role is rejected`() { + mockMvc + .perform(put("/users/test.user/roles/INVALID").withOAuth2Token(wireMockServer)) + .andExpect(status().isBadRequest) + mockMvc + .perform(delete("/users/test.user/roles/INVALID").withOAuth2Token(wireMockServer)) + .andExpect(status().isBadRequest) + } + + @Test + fun `role can be added and removed`() { + mockMvc.perform(put("/users/test.user/roles/LHDCBT003").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + mockMvc + .perform(get("/users/test.user/details").withOAuth2Token(wireMockServer)) + .andExpect(jsonPath("roles", hasItems("LHDCBT002", "LHDCBT003"))) + mockMvc.perform(delete("/users/test.user/roles/LHDCBT003").withOAuth2Token(wireMockServer)) + .andExpect(status().isOk) + } +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt deleted file mode 100644 index e5f139965c..0000000000 --- a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package uk.gov.justice.digital.hmpps.controller - -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -class ApiController { - @PreAuthorize("hasRole('ROLE_EXAMPLE')") - @GetMapping(value = ["/example/{inputId}"]) - fun handle( - @PathVariable("inputId") inputId: String - ) { - // TODO Not yet implemented - } -} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ProviderController.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ProviderController.kt new file mode 100644 index 0000000000..3b820f169a --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ProviderController.kt @@ -0,0 +1,47 @@ +package uk.gov.justice.digital.hmpps.controller + +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import uk.gov.justice.digital.hmpps.exception.NotFoundException +import uk.gov.justice.digital.hmpps.model.LocalAdminUnit +import uk.gov.justice.digital.hmpps.model.LocalAdminUnitWithTeams +import uk.gov.justice.digital.hmpps.model.Provider +import uk.gov.justice.digital.hmpps.model.ProviderWithLaus +import uk.gov.justice.digital.hmpps.model.Team +import uk.gov.justice.digital.hmpps.repository.DistrictRepository +import uk.gov.justice.digital.hmpps.repository.ProbationAreaRepository + +@RestController +@RequestMapping(value = ["/providers"]) +@PreAuthorize("hasRole('PROBATION_API__HDC__STAFF')") +class ProviderController( + private val probationAreaRepository: ProbationAreaRepository, + private val districtRepository: DistrictRepository +) { + + @GetMapping + fun getProviders() = probationAreaRepository.findSelectableProbationAreas().map { Provider(it.code, it.description) } + + @GetMapping("/{code}") + fun getProvider(@PathVariable code: String) = probationAreaRepository.findByCodeWithSelectableDistricts(code) + ?.let { probationArea -> + ProviderWithLaus( + code = probationArea.code, + description = probationArea.description, + localAdminUnits = probationArea.boroughs.flatMap { it.districts }.map { LocalAdminUnit(it.code, it.description) } + ) + } ?: throw NotFoundException("Provider", "code", code) + + @GetMapping("/{providerCode}/localAdminUnits/{lauCode}") + fun getLocalAdminUnit(@PathVariable providerCode: String, @PathVariable lauCode: String) = districtRepository.findByProbationAreaAndCode(providerCode, lauCode) + ?.let { district -> + LocalAdminUnitWithTeams( + code = district.code, + description = district.description, + teams = district.teams.map { Team(it.code, it.description) } + ) + } ?: throw NotFoundException("Local Admin Unit not found for provider '$providerCode' and code '$lauCode'") +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt new file mode 100644 index 0000000000..ef6acb9134 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt @@ -0,0 +1,90 @@ +package uk.gov.justice.digital.hmpps.controller + +import org.springframework.ldap.core.LdapTemplate +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import uk.gov.justice.digital.hmpps.entity.CommunityManagerEntity +import uk.gov.justice.digital.hmpps.entity.StaffEntity +import uk.gov.justice.digital.hmpps.exception.NotFoundException +import uk.gov.justice.digital.hmpps.ldap.findEmailByUsername +import uk.gov.justice.digital.hmpps.model.CommunityManager +import uk.gov.justice.digital.hmpps.model.LocalAdminUnit +import uk.gov.justice.digital.hmpps.model.Name +import uk.gov.justice.digital.hmpps.model.ProbationDeliveryUnit +import uk.gov.justice.digital.hmpps.model.Provider +import uk.gov.justice.digital.hmpps.model.Staff +import uk.gov.justice.digital.hmpps.model.Team +import uk.gov.justice.digital.hmpps.model.TeamDetails +import uk.gov.justice.digital.hmpps.repository.CommunityManagerRepository +import uk.gov.justice.digital.hmpps.repository.PersonRepository +import uk.gov.justice.digital.hmpps.repository.StaffRepository + +@RestController +@PreAuthorize("hasRole('PROBATION_API__HDC__STAFF')") +class StaffController( + private val staffRepository: StaffRepository, + private val communityManagerRepository: CommunityManagerRepository, + private val personRepository: PersonRepository, + private val ldapTemplate: LdapTemplate +) { + @GetMapping("/staff/{code}") + fun getStaffByCode(@PathVariable code: String) = staffRepository.findByCode(code)?.toModel() + ?: throw NotFoundException("Staff", "code", code) + + @GetMapping("/staff", params = ["username"]) + fun getStaffByUsername(@RequestParam username: String) = staffRepository.findByUserUsername(username)?.toModel() + ?: throw NotFoundException("Staff", "username", username) + + @GetMapping("/staff", params = ["id"]) + @Deprecated("Use `/staff/{code}` or `/staff?username={username}`") + fun getStaffById(@RequestParam id: Long) = staffRepository.findStaffById(id)?.toModel() + ?: throw NotFoundException("Staff", "staffId", id) + + @GetMapping("/staff/{code}/managedPrisonerIds") + fun getManagedPrisonersByStaffCode(@PathVariable code: String) = personRepository.findManagedPrisonerIdentifiersByStaffCode(code) + + @GetMapping("/managedPrisonerIds", params = ["staffId"]) + @Deprecated("Use `/staff/{code}/managedPrisonerIds`") + fun getManagedPrisonersByStaffId(@RequestParam staffId: Long) = personRepository.findManagedPrisonerIdentifiersByStaffId(staffId) + + @GetMapping("/case/{nomsNumber}/communityManager") + fun getCommunityManager(@PathVariable nomsNumber: String) = communityManagerRepository.findCommunityManager(nomsNumber)?.toModel() + ?: throw NotFoundException("Community manager for case", "nomsNumber", nomsNumber) + + private fun StaffEntity.toModel() = Staff( + code = code, + staffId = id, + name = Name(forenames(), surname), + teams = teams.map { team -> + TeamDetails( + code = team.code, + description = team.description, + telephone = team.telephone, + emailAddress = team.emailAddress, + probationDeliveryUnit = ProbationDeliveryUnit( + code = team.district.borough.code, + description = team.district.borough.description + ), + localAdminUnit = LocalAdminUnit( + code = team.district.code, + description = team.district.description + ) + ) + }, + username = user?.username, + email = user?.username?.let { username -> ldapTemplate.findEmailByUsername(username) } + ) + + private fun CommunityManagerEntity.toModel() = CommunityManager( + code = staff.code, + staffId = staff.id, + name = Name(staff.forenames(), staff.surname), + team = Team(team.code, team.description), + localAdminUnit = LocalAdminUnit(team.district.code, team.district.description), + provider = Provider(team.probationArea.code, team.probationArea.description), + isUnallocated = staff.code.endsWith("U") + ) +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/UserController.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/UserController.kt new file mode 100644 index 0000000000..83e6d320ae --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/UserController.kt @@ -0,0 +1,38 @@ +package uk.gov.justice.digital.hmpps.controller + +import org.springframework.ldap.core.LdapTemplate +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import uk.gov.justice.digital.hmpps.ldap.addRole +import uk.gov.justice.digital.hmpps.ldap.findAttributeByUsername +import uk.gov.justice.digital.hmpps.ldap.getRoles +import uk.gov.justice.digital.hmpps.ldap.removeRole +import uk.gov.justice.digital.hmpps.model.LicencesRole +import uk.gov.justice.digital.hmpps.model.UserDetails +import java.time.LocalDate +import java.time.LocalDate.now +import java.time.format.DateTimeFormatter + +@RestController +@RequestMapping(value = ["/users/{username}"]) +@PreAuthorize("hasRole('PROBATION_API__HDC__USER_ROLES')") +class UserController(private val ldapTemplate: LdapTemplate) { + @GetMapping("/details") + fun getUserDetails(@PathVariable username: String) = UserDetails( + username = username, + roles = ldapTemplate.getRoles(username).filter { role -> LicencesRole.entries.any { it.name == role } }, + enabled = ldapTemplate.findAttributeByUsername(username, "endDate") + .let { it == null || LocalDate.parse(it.substring(0, 8), DateTimeFormatter.ofPattern("yyyyMMdd")).isAfter(now()) } + ) + + @PutMapping("/roles/{role}") + fun addRole(@PathVariable username: String, @PathVariable role: LicencesRole) = ldapTemplate.addRole(username, role) + + @DeleteMapping("/roles/{role}") + fun removeRole(@PathVariable username: String, @PathVariable role: LicencesRole) = ldapTemplate.removeRole(username, role) +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Borough.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Borough.kt new file mode 100644 index 0000000000..318454ef21 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Borough.kt @@ -0,0 +1,30 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +class Borough( + @Id + @Column(name = "borough_id") + val id: Long, + + @Column + val code: String, + + @Column + val description: String, + + @ManyToOne + @JoinColumn(name = "probation_area_id") + val probationArea: ProbationArea, + + @OneToMany(mappedBy = "borough") + val districts: Set = setOf() +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CommunityManagerEntity.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CommunityManagerEntity.kt new file mode 100644 index 0000000000..9f41f269bf --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CommunityManagerEntity.kt @@ -0,0 +1,36 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +@Table(name = "offender_manager") +class CommunityManagerEntity( + @Id + @Column(name = "offender_manager_id") + val id: Long, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @ManyToOne + @JoinColumn(name = "allocation_staff_id") + val staff: StaffEntity, + + @ManyToOne + @JoinColumn(name = "team_id") + val team: Team, + + @Column(columnDefinition = "number", nullable = false) + val softDeleted: Boolean = false, + + @Column(name = "active_flag", columnDefinition = "number", nullable = false) + val active: Boolean = true +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/District.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/District.kt new file mode 100644 index 0000000000..225d1632df --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/District.kt @@ -0,0 +1,38 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Convert +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.OneToMany +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.Where +import org.hibernate.type.YesNoConverter + +@Entity +@Immutable +@Where(clause = "selectable = 'Y' or code like '%UAT' or code like '%UNA' or code like '%IVA'") +class District( + @Id + @Column(name = "district_id") + val id: Long, + + @Column + val code: String, + + @Column + val description: String, + + @Column + @Convert(converter = YesNoConverter::class) + val selectable: Boolean = true, + + @ManyToOne + @JoinColumn(name = "borough_id") + val borough: Borough, + + @OneToMany(mappedBy = "district") + val teams: List = listOf() +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt new file mode 100644 index 0000000000..21a7aac0ea --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt @@ -0,0 +1,29 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.OneToMany +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.Where + +@Entity +@Immutable +@Table(name = "offender") +class Person( + @Id + @Column(name = "offender_id") + val id: Long, + + @Column(name = "noms_number", columnDefinition = "char(7)") + val nomsNumber: String, + + @OneToMany(mappedBy = "person") + @Where(clause = "active_flag = 1 and soft_deleted = 0") + val communityManagers: List = listOf(), + + @OneToMany(mappedBy = "person") + @Where(clause = "active_flag = 1 and soft_deleted = 0") + val prisonManagers: List = listOf() +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonManager.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonManager.kt new file mode 100644 index 0000000000..a45dd3238d --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonManager.kt @@ -0,0 +1,32 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +@Table(name = "prison_offender_manager") +class PrisonManager( + @Id + @Column(name = "prison_offender_manager_id") + val id: Long, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @ManyToOne + @JoinColumn(name = "allocation_staff_id") + val staff: StaffEntity, + + @Column(columnDefinition = "number", nullable = false) + val softDeleted: Boolean = false, + + @Column(name = "active_flag", columnDefinition = "number", nullable = false) + val active: Boolean = true +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ProbationArea.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ProbationArea.kt new file mode 100644 index 0000000000..78162c99be --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ProbationArea.kt @@ -0,0 +1,36 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Convert +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.OneToMany +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.Where +import org.hibernate.type.YesNoConverter + +@Entity +@Immutable +@Where(clause = "selectable = 'Y' and (establishment is null or establishment <> 'Y')") +class ProbationArea( + @Id + @Column(name = "probation_area_id") + val id: Long, + + @Column(columnDefinition = "char(3)") + val code: String, + + @Column + val description: String, + + @OneToMany(mappedBy = "probationArea") + val boroughs: Set = setOf(), + + @Column + @Convert(converter = YesNoConverter::class) + val establishment: Boolean? = null, + + @Column + @Convert(converter = YesNoConverter::class) + val selectable: Boolean = true +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffEntity.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffEntity.kt new file mode 100644 index 0000000000..ce4f236f3f --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffEntity.kt @@ -0,0 +1,49 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.JoinTable +import jakarta.persistence.ManyToMany +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +@Table(name = "staff") +class StaffEntity( + @Id + @Column(name = "staff_id") + val id: Long, + + @Column(name = "officer_code", columnDefinition = "char(7)") + val code: String, + + @Column + val forename: String, + + @Column + val forename2: String? = null, + + @Column + val surname: String, + + @OneToOne(mappedBy = "staff") + val user: User? = null, + + @OneToMany(mappedBy = "staff") + val communityManagers: Set = setOf(), + + @ManyToMany + @JoinTable( + name = "staff_team", + joinColumns = [JoinColumn(name = "staff_id")], + inverseJoinColumns = [JoinColumn(name = "team_id")] + ) + val teams: List +) { + fun forenames() = listOfNotNull(forename, forename2).joinToString(" ") +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Team.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Team.kt new file mode 100644 index 0000000000..68fe52aaff --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Team.kt @@ -0,0 +1,36 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +class Team( + @Id + @Column(name = "team_id") + val id: Long, + + @Column(columnDefinition = "char(6)") + val code: String, + + @Column + val description: String, + + @Column + val telephone: String? = null, + + @Column + val emailAddress: String? = null, + + @ManyToOne + @JoinColumn(name = "district_id") + val district: District, + + @ManyToOne + @JoinColumn(name = "probation_area_id") + val probationArea: ProbationArea +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/User.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/User.kt new file mode 100644 index 0000000000..db40e8f568 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/User.kt @@ -0,0 +1,25 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable + +@Entity +@Immutable +@Table(name = "user_") +class User( + @Id + @Column(name = "user_id") + val id: Long, + + @OneToOne + @JoinColumn(name = "staff_id") + val staff: StaffEntity? = null, + + @Column(name = "distinguished_name") + val username: String +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CommunityManager.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CommunityManager.kt new file mode 100644 index 0000000000..665c1777d7 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CommunityManager.kt @@ -0,0 +1,11 @@ +package uk.gov.justice.digital.hmpps.model + +data class CommunityManager( + val code: String, + val staffId: Long, + val name: Name, + val team: Team, + val localAdminUnit: LocalAdminUnit, + val provider: Provider, + val isUnallocated: Boolean +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LicencesRole.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LicencesRole.kt new file mode 100644 index 0000000000..cc7fdfca95 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LicencesRole.kt @@ -0,0 +1,11 @@ +package uk.gov.justice.digital.hmpps.model + +import uk.gov.justice.digital.hmpps.ldap.DeliusRole + +enum class LicencesRole( + override val description: String, + override val mappedRole: String +) : DeliusRole { + LHDCBT002("Digital Licence Create", "LHDCBT002"), + LHDCBT003("Digital Licence Vary", "LHDCBT003"); +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LocalAdminUnit.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LocalAdminUnit.kt new file mode 100644 index 0000000000..eebe1d6fbc --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/LocalAdminUnit.kt @@ -0,0 +1,12 @@ +package uk.gov.justice.digital.hmpps.model + +data class LocalAdminUnit( + val code: String, + val description: String +) + +data class LocalAdminUnitWithTeams( + val code: String, + val description: String, + val teams: List +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Name.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Name.kt new file mode 100644 index 0000000000..e6cff52bfb --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Name.kt @@ -0,0 +1,6 @@ +package uk.gov.justice.digital.hmpps.model + +data class Name( + val forenames: String, + val surname: String +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/ProbationDeliveryUnit.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/ProbationDeliveryUnit.kt new file mode 100644 index 0000000000..b939974c28 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/ProbationDeliveryUnit.kt @@ -0,0 +1,6 @@ +package uk.gov.justice.digital.hmpps.model + +data class ProbationDeliveryUnit( + val code: String, + val description: String +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Provider.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Provider.kt new file mode 100644 index 0000000000..d050129d32 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Provider.kt @@ -0,0 +1,12 @@ +package uk.gov.justice.digital.hmpps.model + +data class Provider( + val code: String, + val description: String +) + +data class ProviderWithLaus( + val code: String, + val description: String, + val localAdminUnits: List +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Staff.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Staff.kt new file mode 100644 index 0000000000..fbc9741d9d --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Staff.kt @@ -0,0 +1,11 @@ +package uk.gov.justice.digital.hmpps.model + +data class Staff( + val code: String, + @Deprecated("Use `code` instead") + val staffId: Long, + val name: Name, + val teams: List, + val username: String?, + val email: String? +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Team.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Team.kt new file mode 100644 index 0000000000..2f20c1f6ec --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Team.kt @@ -0,0 +1,15 @@ +package uk.gov.justice.digital.hmpps.model + +data class Team( + val code: String, + val description: String +) + +data class TeamDetails( + val code: String, + val description: String, + val telephone: String?, + val emailAddress: String?, + val probationDeliveryUnit: ProbationDeliveryUnit, + val localAdminUnit: LocalAdminUnit +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/UserDetails.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/UserDetails.kt new file mode 100644 index 0000000000..eb272ee9bd --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/UserDetails.kt @@ -0,0 +1,7 @@ +package uk.gov.justice.digital.hmpps.model + +data class UserDetails( + val username: String, + val enabled: Boolean, + val roles: List +) diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/CommunityManagerRepository.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/CommunityManagerRepository.kt new file mode 100644 index 0000000000..43c4c8dd09 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/CommunityManagerRepository.kt @@ -0,0 +1,18 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.entity.CommunityManagerEntity + +interface CommunityManagerRepository : JpaRepository { + @Query( + """ + select cm + from CommunityManagerEntity cm + join fetch cm.staff + where cm.person.nomsNumber = :nomsNumber + and cm.active = true and cm.softDeleted = false + """ + ) + fun findCommunityManager(nomsNumber: String): CommunityManagerEntity? +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/DistrictRepository.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/DistrictRepository.kt new file mode 100644 index 0000000000..f61e28f581 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/DistrictRepository.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.entity.District + +interface DistrictRepository : JpaRepository { + @Query( + """ + select d + from District d + join fetch d.borough b + join fetch b.probationArea pa + left join fetch d.teams + where pa.code = :probationAreaCode + and d.code = :code + and d.selectable + """ + ) + fun findByProbationAreaAndCode(probationAreaCode: String, code: String): District? +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PersonRepository.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PersonRepository.kt new file mode 100644 index 0000000000..6b9582ecbc --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PersonRepository.kt @@ -0,0 +1,37 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.entity.Person + +interface PersonRepository : JpaRepository { + @Query( + """ + select distinct p.nomsNumber + from Person p + left join p.communityManagers cm + left join cm.staff cmStaff + left join p.prisonManagers pm + left join pm.staff pmStaff + where ((cmStaff is not null and cmStaff.id = :staffId) + or (pmStaff is not null and pmStaff.id = :staffId)) + and p.nomsNumber is not null + """ + ) + fun findManagedPrisonerIdentifiersByStaffId(staffId: Long): List + + @Query( + """ + select distinct p.nomsNumber + from Person p + left join p.communityManagers cm + left join cm.staff cmStaff + left join p.prisonManagers pm + left join pm.staff pmStaff + where ((cmStaff is not null and cmStaff.code = :code) + or (pmStaff is not null and pmStaff.code = :code)) + and p.nomsNumber is not null + """ + ) + fun findManagedPrisonerIdentifiersByStaffCode(code: String): List +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/ProbationAreaRepository.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/ProbationAreaRepository.kt new file mode 100644 index 0000000000..bc4a8917d2 --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/ProbationAreaRepository.kt @@ -0,0 +1,29 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.entity.ProbationArea + +interface ProbationAreaRepository : JpaRepository { + @Query( + """ + select pa + from ProbationArea pa + where pa.selectable = true + and (pa.establishment is null or pa.establishment) + """ + ) + fun findSelectableProbationAreas(): List + + @Query( + """ + select pa + from ProbationArea pa + left join fetch pa.boroughs b + left join fetch b.districts d + where pa.code = :code + and (d is null or d.selectable) + """ + ) + fun findByCodeWithSelectableDistricts(code: String): ProbationArea? +} diff --git a/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/StaffRepository.kt b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/StaffRepository.kt new file mode 100644 index 0000000000..db4a75141d --- /dev/null +++ b/projects/hdc-licences-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/StaffRepository.kt @@ -0,0 +1,16 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.EntityGraph +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.entity.StaffEntity + +interface StaffRepository : JpaRepository { + @EntityGraph(attributePaths = ["user", "teams.district.borough"]) + fun findByCode(code: String): StaffEntity? + + @EntityGraph(attributePaths = ["user", "teams.district.borough"]) + fun findByUserUsername(username: String): StaffEntity? + + @EntityGraph(attributePaths = ["user", "teams.district.borough"]) + fun findStaffById(id: Long): StaffEntity? +} diff --git a/projects/hdc-licences-and-delius/src/main/resources/application.yml b/projects/hdc-licences-and-delius/src/main/resources/application.yml index 12e83ea9e7..b70bef8c99 100644 --- a/projects/hdc-licences-and-delius/src/main/resources/application.yml +++ b/projects/hdc-licences-and-delius/src/main/resources/application.yml @@ -16,6 +16,10 @@ spring: global_temporary: create_tables: false drop_tables: false + ldap: + base: dc=moj,dc=com + base-environment: + java.naming.ldap.derefAliases: never security.oauth2.client: registration: hdc-licences-and-delius: @@ -43,6 +47,9 @@ spring.config.activate.on-profile: [ "dev", "integration-test" ] 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: + validation.enabled: false + base-dn: ${spring.ldap.base} security.oauth2.resourceserver.jwt.public-key-location: classpath:local-public-key.pub seed.database: true