From 1ccaf1504e8d8f8abef80dd1d159a21302d75f7e Mon Sep 17 00:00:00 2001 From: Elizabeth Date: Thu, 16 Feb 2023 13:35:33 -0500 Subject: [PATCH] updates --- build.gradle.kts | 18 ++++ readme.adoc | 3 + .../kotlin/org/veupathdb/lib/ldap/LDAP.kt | 86 +++++++++++++++++++ .../org/veupathdb/lib/ldap/LDAPConfig.kt | 3 + .../kotlin/org/veupathdb/lib/ldap/LDAPHost.kt | 15 ++++ .../org/veupathdb/lib/ldap/OracleNetDesc.kt | 58 +++++++++++++ .../org/veupathdb/lib/ldap/LDAPHostTest.kt | 18 ++++ 7 files changed, 201 insertions(+) create mode 100644 src/main/kotlin/org/veupathdb/lib/ldap/LDAP.kt create mode 100644 src/main/kotlin/org/veupathdb/lib/ldap/LDAPConfig.kt create mode 100644 src/main/kotlin/org/veupathdb/lib/ldap/LDAPHost.kt create mode 100644 src/main/kotlin/org/veupathdb/lib/ldap/OracleNetDesc.kt create mode 100644 src/test/kotlin/org/veupathdb/lib/ldap/LDAPHostTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 18a1d04..917e8de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,4 +4,22 @@ plugins { repositories { mavenCentral() +} + +kotlin { + jvmToolchain(18) +} + +dependencies { + implementation("org.slf4j:slf4j-api:1.7.36") + implementation("com.unboundid:unboundid-ldapsdk:6.0.7") + + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0") + testImplementation("org.mockito:mockito-core:4.8.0") +} + +tasks.test { + useJUnitPlatform() } \ No newline at end of file diff --git a/readme.adoc b/readme.adoc index e69de29..81b287e 100644 --- a/readme.adoc +++ b/readme.adoc @@ -0,0 +1,3 @@ += LDAP Utils + +Utilities for working with LDAP from VEuPathDB containerized services. \ No newline at end of file diff --git a/src/main/kotlin/org/veupathdb/lib/ldap/LDAP.kt b/src/main/kotlin/org/veupathdb/lib/ldap/LDAP.kt new file mode 100644 index 0000000..e797691 --- /dev/null +++ b/src/main/kotlin/org/veupathdb/lib/ldap/LDAP.kt @@ -0,0 +1,86 @@ +package org.veupathdb.lib.ldap + +import com.unboundid.ldap.sdk.* +import org.slf4j.LoggerFactory + +class LDAP(private val config: LDAPConfig) { + private val log = LoggerFactory.getLogger(javaClass) + + private var ldapConnection: LDAPConnection? = null + + init { + if (config.hosts.isEmpty()) + throw IllegalArgumentException("Passed the $javaClass constructor a config with 0 hosts entries") + } + + fun requireSingularOracleNetDesc(commonName: String): OracleNetDesc { + log.trace("requireSingularOracleNetDesc(commonName={})", commonName) + + val tmp = lookupOracleNetDesc(commonName) + + if (tmp.isEmpty()) + throw IllegalStateException("no OracleNetDescs found for common name $commonName") + if (tmp.size > 1) + throw IllegalStateException("multiple OracleNetDescs found for common name $commonName") + + return tmp[0] + } + + fun lookupOracleNetDesc(commonName: String): List { + log.trace("lookupOracleNetDesc(commonName={})", commonName) + + return getConnection() + .search(SearchRequest( + config.oracleBaseDN, + SearchScope.SUB, + Filter.createANDFilter( + Filter.create("cn=$commonName"), + Filter.create("objectClass=orclNetService") + ), + "orclNetDescString" + )) + .searchEntries + .map { OracleNetDesc(it.getAttribute("orclNetDescString").value!!) } + } + + private fun getConnection(): LDAPConnection { + log.trace("getConnection()") + + // Synchronized because this thing is gonna be called from who knows where. + synchronized(this) { + + // If we've already got an LDAP connection + if (ldapConnection != null) { + + // If the LDAP connection we've already got is still connected + if (ldapConnection!!.isConnected) + // then return it + return ldapConnection!! + // else, the LDAP connection we've already got is _not_ still connected + else + // then disregard it + ldapConnection = null + } + + log.debug("Attempting to establish a connection to a configured LDAP server") + for (host in config.hosts) { + log.trace("Trying to connect to {}:{}", host.host, host.port) + + try { + ldapConnection =LDAPConnection(host.host, host.port.toInt()) + .also { log.debug("Connected to {}:{}", host.host, host.port) } + break + } catch (e: Throwable) { + log.debug("Failed to connect to {}:{}", host.host, host.port) + } + } + + if (ldapConnection == null) { + log.error("Failed to establish a connection to any configured LDAP server.") + throw RuntimeException("Failed to establish a connection to any configured LDAP server.") + } + + return ldapConnection!! + } + } +} diff --git a/src/main/kotlin/org/veupathdb/lib/ldap/LDAPConfig.kt b/src/main/kotlin/org/veupathdb/lib/ldap/LDAPConfig.kt new file mode 100644 index 0000000..4c6f528 --- /dev/null +++ b/src/main/kotlin/org/veupathdb/lib/ldap/LDAPConfig.kt @@ -0,0 +1,3 @@ +package org.veupathdb.lib.ldap + +data class LDAPConfig(val hosts: Collection, val oracleBaseDN: String) \ No newline at end of file diff --git a/src/main/kotlin/org/veupathdb/lib/ldap/LDAPHost.kt b/src/main/kotlin/org/veupathdb/lib/ldap/LDAPHost.kt new file mode 100644 index 0000000..e41ca66 --- /dev/null +++ b/src/main/kotlin/org/veupathdb/lib/ldap/LDAPHost.kt @@ -0,0 +1,15 @@ +package org.veupathdb.lib.ldap + +data class LDAPHost(val host: String, val port: UShort) { + companion object { + @JvmStatic + fun ofString(str: String): LDAPHost { + val colonIndex = str.indexOf(':') + + if (colonIndex < 1) + throw IllegalArgumentException("input string $str did not resemble a valid \"host:port\" string") + + return LDAPHost(str.substring(0, colonIndex), str.substring(colonIndex + 1).toUShort()) + } + } +} diff --git a/src/main/kotlin/org/veupathdb/lib/ldap/OracleNetDesc.kt b/src/main/kotlin/org/veupathdb/lib/ldap/OracleNetDesc.kt new file mode 100644 index 0000000..e493d7a --- /dev/null +++ b/src/main/kotlin/org/veupathdb/lib/ldap/OracleNetDesc.kt @@ -0,0 +1,58 @@ +package org.veupathdb.lib.ldap + +import java.math.BigInteger + +private const val HOST_PREFIX = "(HOST=" +private const val PORT_PREFIX = "(PORT=" +private const val SERVICE_NAME_PREFIX = "(SERVICE_NAME=" +private const val VALUE_SUFFIX = ')' + +data class OracleNetDesc( + val host: String, + val port: UShort, + val serviceName: String, +) { + constructor(string: String) : this( + string.requireHostValue(), + string.requirePortValue(), + string.requireServiceNameValue(), + ) +} + +private fun String.requireHostValue() = requireValue(HOST_PREFIX, "HOST") + +private fun String.requirePortValue(): UShort { + val bi = try { + BigInteger(requireValue(PORT_PREFIX, "PORT")) + } catch (e: Throwable) { + throw IllegalArgumentException("given orclNetDescString contained an invalid PORT value") + } + + if (bi > BigInteger.valueOf(65535)) + throw IllegalArgumentException("given orclNetDescString contained a PORT value that was too large to be a valid port") + if (bi < BigInteger.ZERO) + throw IllegalArgumentException("given orclNetDescString contained a PORT value that was less than zero") + + return bi.toInt().toUShort() +} + +private fun String.requireServiceNameValue(): String = requireValue(SERVICE_NAME_PREFIX, "SERVICE_NAME") + +private fun String.requireValue(prefix: String, name: String): String { + val start = indexOf(prefix) + + if (start < 0) + throw IllegalArgumentException("given orclNetDescString did not contain a $name value") + + val end = indexOf(VALUE_SUFFIX, start) + + if (end < 0) + throw IllegalArgumentException("malformed orclNetDescString value") + + val out = substring(start + prefix.length, end) + + if (out.isEmpty()) + throw IllegalArgumentException("given orclNetDescString contained an empty $name value") + + return out +} diff --git a/src/test/kotlin/org/veupathdb/lib/ldap/LDAPHostTest.kt b/src/test/kotlin/org/veupathdb/lib/ldap/LDAPHostTest.kt new file mode 100644 index 0000000..7fca51d --- /dev/null +++ b/src/test/kotlin/org/veupathdb/lib/ldap/LDAPHostTest.kt @@ -0,0 +1,18 @@ +package org.veupathdb.lib.ldap + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("LDAPHost") +class LDAPHostTest { + + @Test + @DisplayName("ofString(String)") + fun t1() { + val tgt = LDAPHost.ofString("something:1234") + + assertEquals("something", tgt.host) + assertEquals(1234.toUShort(), tgt.port) + } +} \ No newline at end of file