Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PI-1569 add onboarding endpoints for Pathfinder #2432

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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