Skip to content

Commit

Permalink
PI-1569 add onboarding endpoints for Pathfinder (#2432)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-britton-moj authored Oct 23, 2023
1 parent 0b8bb2e commit 8b5a548
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 7 deletions.
2 changes: 2 additions & 0 deletions projects/pathfinder-and-delius/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-ldap")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
Expand All @@ -20,6 +21,7 @@ dependencies {
dev(project(":libs:dev-tools"))
dev("com.h2database:h2")
dev("org.testcontainers:oracle-xe")
dev("com.unboundid:unboundid-ldapsdk")

runtimeOnly("com.oracle.database.jdbc:ojdbc11")

Expand Down
3 changes: 3 additions & 0 deletions projects/pathfinder-and-delius/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
pathfinder-and-delius-database:
SPRING_DATASOURCE_USERNAME: DB_USERNAME
SPRING_DATASOURCE_PASSWORD: DB_PASSWORD
Expand Down
34 changes: 34 additions & 0 deletions projects/pathfinder-and-delius/src/dev/resources/schema.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
dn: dc=moj,dc=com
objectclass: top
objectclass: domain
dc: moj

dn: ou=Users,dc=moj,dc=com
objectclass: top
objectclass: organizationalUnit
ou: Users

dn: cn=john-smith,ou=Users,dc=moj,dc=com
objectclass: top
objectclass: inetOrgPerson
cn: john-smith
sn: Smith
givenname: John
mail: [email protected]
telephoneNumber: 07321165373

dn: cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com
description: Role Catalogue
objectclass: top
cn: ndRoleCatalogue

dn: cn=CTRBT001,cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com
description: Pathfinder CT Probation
Sector: public
Level1: FALSE
Level2: FALSE
Level3: FALSE
UIBusinessInteractionCollection: UPBI003
objectclass: NDRole
objectclass: top
cn: CTRBT001
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInVzZXJfbmFtZSI6InByb2JhdGlvbi1pbnRlZ3JhdGlvbi1kZXYiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXV0aF9zb3VyY2UiOiJub25lIiwiaXNzIjoiaHR0cHM6Ly9zaWduLWluLWRldi5obXBwcy5zZXJ2aWNlLmp1c3RpY2UuZ292LnVrL2F1dGgvaXNzdWVyIiwiZXhwIjo5OTk5OTk5OTk5LCJhdXRob3JpdGllcyI6WyJST0xFX1BBVEhGSU5ERVJfUFJPQkFUSU9OX0NBU0UiXSwianRpIjoiMjVEdVJuMS1oeUhaZXdMY2RKSnh3VkwwM0tVIiwiY2xpZW50X2lkIjoicHJvYmF0aW9uLWludGVncmF0aW9uLWRldiIsImlhdCI6MTY2Mzc1NzMxMX0.HHzOUbZGvhQwcSs-ZJinXOYHProob0N0d_zlE9bCBcae5J7l0EhvlpSvfohN5gJg39RNcu-f1wZZqgBIFMuljWs909FbubVDPyEOpQxY2bim6nenFZcUGCOU8SAz_Tf2yxFf6vTgiwV1TEoasrsKnGcCnVuo47JYuvpjwcSluAY",
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwcm9iYXRpb24taW50ZWdyYXRpb24tZGV2IiwiZ3JhbnRfdHlwZSI6ImNsaWVudF9jcmVkZW50aWFscyIsInVzZXJfbmFtZSI6InByb2JhdGlvbi1pbnRlZ3JhdGlvbi1kZXYiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXV0aF9zb3VyY2UiOiJub25lIiwiaXNzIjoiaHR0cHM6Ly9zaWduLWluLWRldi5obXBwcy5zZXJ2aWNlLmp1c3RpY2UuZ292LnVrL2F1dGgvaXNzdWVyIiwiZXhwIjo5OTk5OTk5OTk5LCJhdXRob3JpdGllcyI6WyJST0xFX1BBVEhGSU5ERVJfUFJPQkFUSU9OX0NBU0UiLCJST0xFX1BST0JBVElPTl9BUElfX1BBVEhGSU5ERVJfX1VTRVJfUk9MRVMiXSwianRpIjoiMjVEdVJuMS1oeUhaZXdMY2RKSnh3VkwwM0tVIiwiY2xpZW50X2lkIjoicHJvYmF0aW9uLWludGVncmF0aW9uLWRldiIsImlhdCI6MTY2Mzc1NzMxMX0.SUwMkVn868al1ry9P69KLz0pk1ayrxprnEW-6xMAD0nJaJN1gW4acCzwCguHrn7o8jX18gXD9dZAy5rJvcsA4hX2XKWav8FdUmOXI7ePqkgbNOMhjzzb5FVm_6GO_QNIwQUAWSi6o-69sjbaNpYZOvgkwYjJ1GhZy6WcVgaQgjU",
"token_type": "bearer",
"expires_in": 9999999999,
"scope": "read write",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package uk.gov.justice.digital.hmpps

import com.github.tomakehurst.wiremock.WireMockServer
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.api.assertThrows
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.ldap.NameNotFoundException
import org.springframework.ldap.core.LdapTemplate
import org.springframework.ldap.support.LdapNameBuilder
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import uk.gov.justice.digital.hmpps.model.DeliusRole
import uk.gov.justice.digital.hmpps.security.withOAuth2Token

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
internal class UserRoleIntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Autowired
lateinit var wireMockServer: WireMockServer

@Autowired
lateinit var ldapTemplate: LdapTemplate

@Order(1)
@Test
fun `successfully updates ldap role`() {
mockMvc.perform(
MockMvcRequestBuilders.put("/users/john-smith/roles/pf_std_probation")
.withOAuth2Token(wireMockServer)
).andExpect(status().is2xxSuccessful).andReturn()

val res = ldapTemplate.lookupContext(
LdapNameBuilder.newInstance("ou=Users")
.add("cn", "john-smith")
.add("cn", DeliusRole.CTRBT001.name)
.build()
)
assertThat(res.dn.toString(), equalTo("cn=CTRBT001,cn=john-smith,ou=Users"))
}

@Order(2)
@Test
fun `successfully removes ldap role`() {
mockMvc.perform(
MockMvcRequestBuilders.delete("/users/john-smith/roles/pf_std_probation")
.withOAuth2Token(wireMockServer)
).andExpect(status().is2xxSuccessful).andReturn()

val res = assertThrows<NameNotFoundException> {
ldapTemplate.lookupContext(
LdapNameBuilder.newInstance("ou=Users")
.add("cn", "john-smith")
.add("cn", DeliusRole.CTRBT001.name)
.build()
)
}
assertThat(res.message, equalTo("[LDAP: error code 32 - Unable to perform the search because base entry 'cn=CTRBT001,cn=john-smith,ou=Users,dc=moj,dc=com' does not exist in the server.]"))
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package uk.gov.justice.digital.hmpps

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration
import org.springframework.boot.runApplication

@SpringBootApplication
@SpringBootApplication(exclude = [LdapRepositoriesAutoConfiguration::class])
class App

fun main(args: Array<String>) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package uk.gov.justice.digital.hmpps.controller

import org.springframework.http.HttpStatus
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.DeleteMapping
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 org.springframework.web.server.ResponseStatusException
import uk.gov.justice.digital.hmpps.model.DeliusRole
import uk.gov.justice.digital.hmpps.service.UserService

@RestController
@RequestMapping("users")
class UserController(private val userService: UserService) {
@PreAuthorize("hasRole('PROBATION_API__PATHFINDER__USER_ROLES')")
@PutMapping(value = ["/{username}/roles/{roleName}"])
fun addRole(@PathVariable username: String, @PathVariable roleName: String) =
userService.addRole(username, roleName.deliusRole())

@PreAuthorize("hasRole('PROBATION_API__PATHFINDER__USER_ROLES')")
@DeleteMapping(value = ["/{username}/roles/{roleName}"])
fun removeRole(@PathVariable username: String, @PathVariable roleName: String) =
userService.removeRole(username, roleName.deliusRole())
}

private fun String.deliusRole() =
DeliusRole.from(this) ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Role Not Acceptable")
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package uk.gov.justice.digital.hmpps.model

enum class DeliusRole(val description: String, val role: String) {
CTRBT001("Pathfinder CT Probation", "PF_STD_PROBATION"),
CTRBT002("Pathfinder CT Approval", "PF_APPROVAL"),
CTRBT003("Pathfinder National Reader", "PF_NATIONAL_READER"),
CTRBT004("Pathfinder HQ User", "PF_HQ"),
CTRBT005("Pathfinder User", "PF_USER"),
CTRBT006("Pathfinder Admin", "PF_ADMIN");

companion object {
fun from(role: String): DeliusRole? = entries.firstOrNull { it.role == role.uppercase() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.ldap.core.LdapTemplate
import org.springframework.ldap.support.LdapNameBuilder
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.exception.NotFoundException
import uk.gov.justice.digital.hmpps.model.DeliusRole
import javax.naming.directory.Attributes
import javax.naming.directory.BasicAttribute
import javax.naming.directory.BasicAttributes

@Service
class UserService(private val ldapTemplate: LdapTemplate) {
private val ldapBase = "ou=Users"

fun addRole(username: String, role: DeliusRole) {
val roleContext = ldapTemplate.lookupContext(role.context())
?: throw NotFoundException("NDeliusRole of ${role.name} not found")
val attributes: Attributes = BasicAttributes(true).apply {
put(roleContext.asAttribute("aliasedObjectName"))
put(role.name.asAttribute("cn"))
put(listOf("NDRoleAssociation", "Alias", "top").asAttribute("objectclass"))
}
val userRole = role.context(username)
ldapTemplate.rebind(userRole, null, attributes)
}

fun removeRole(username: String, role: DeliusRole) =
ldapTemplate.unbind(role.context(username))

private fun DeliusRole.context(username: String? = null) =
LdapNameBuilder.newInstance(ldapBase)
.add("cn", username ?: "ndRoleCatalogue")
.add("cn", name)
.build()

fun Any.asAttribute(key: String) = BasicAttribute(key, this.toString())
fun List<Any>.asAttribute(key: String): BasicAttribute =
BasicAttribute(key).apply { forEach(this::add) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
pathfinder-and-delius:
Expand All @@ -42,6 +46,9 @@ 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
security.oauth2.resourceserver.jwt.public-key-location: classpath:local-public-key.pub
ldap.embedded:
validation.enabled: false
base-dn: ${spring.ldap.base}

seed.database: true
wiremock.enabled: true
Expand Down

0 comments on commit 8b5a548

Please sign in to comment.