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

Bump org.jmailen.kotlinter from 3.16.0 to 4.0.0 #345

Merged
merged 4 commits into from
Oct 9, 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: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
ext.jackson_version = '2.15.2'
ext.spek_version = '2.0.19'
ext.kluent_version = '1.73'
ext.kotlinter_version = '3.16.0'
ext.kotlinter_version = '4.0.0'
ext.shadow_version = '8.1.1'
ext.versions_version = '0.49.0'
ext.sonarqube_version = '4.4.1.3373'
Expand Down
76 changes: 43 additions & 33 deletions src/main/kotlin/com/ultratendency/kafka/ldap/LDAPConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import java.nio.file.Paths
* See test/resources/adconfig.yaml for 1:1 mapping between YAML and data class
*/
object LDAPConfig {

data class Config(
val host: String,
val port: Int,
Expand All @@ -39,25 +38,34 @@ object LDAPConfig {
private val log = LoggerFactory.getLogger(LDAPConfig::class.java)
private val cache: Config

val emptyConfig = Config(
"", 0, 0,
"", "",
"", "",
"", "", "",
0, 0,
)
val emptyConfig =
Config(
"",
0,
0,
"",
"",
"",
"",
"",
"",
"",
0,
0,
)

init {
cache = try {
loadConfig(ClassLoader.getSystemResource("ldapconfig.yaml") ?: URL(""))
.also {
log.info("LDAPConfig for classpath is cached")
log.info("ldap configuration values: $it")
}
} catch (e: Exception) {
log.error("${e.message} - authentication and authorization will fail! ")
emptyConfig
}
cache =
try {
loadConfig(ClassLoader.getSystemResource("ldapconfig.yaml") ?: URL(""))
.also {
log.info("LDAPConfig for classpath is cached")
log.info("ldap configuration values: $it")
}
} catch (e: Exception) {
log.error("${e.message} - authentication and authorization will fail! ")
emptyConfig
}
}

fun getBySource(configFile: String): Config {
Expand All @@ -83,21 +91,22 @@ object LDAPConfig {

val errMsg = "Authentication and authorization will fail - "
val defaultDir = Paths.get("").toAbsolutePath()
val filePath = try {
Paths.get(configFile.toURI())
} catch (e: IllegalArgumentException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: FileSystemNotFoundException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: SecurityException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: Exception) {
log.error(errMsg + e.message)
defaultDir
}
val filePath =
try {
Paths.get(configFile.toURI())
} catch (e: IllegalArgumentException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: FileSystemNotFoundException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: SecurityException) {
log.error(errMsg + e.message)
defaultDir
} catch (e: Exception) {
log.error(errMsg + e.message)
defaultDir
}

if (filePath == defaultDir) return emptyConfig

Expand All @@ -124,6 +133,7 @@ object LDAPConfig {

// A couple of extension functions for Config
fun LDAPConfig.Config.toUserDN(user: String) = "$usrUid=$user,$usrBaseDN".lowercase()

fun LDAPConfig.Config.toAdminDN(user: String) = "$adminUid=$user,$adminBaseDN".lowercase()

fun LDAPConfig.Config.toUserDNNodes(user: String) = listOf(toUserDN(user), toAdminDN(user))
1 change: 0 additions & 1 deletion src/main/kotlin/com/ultratendency/kafka/ldap/Monitoring.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package com.ultratendency.kafka.ldap
*/

enum class Monitoring(val txt: String) {

LDAP_BASE_TIME("LDAP connection time"),
LDAP_BASE_FAILURE("Authentication and authorization will fail! Exception when connecting to"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import org.slf4j.LoggerFactory
* A class verifying username and password through simple LDAP bind
*/
class LDAPAuthentication private constructor(val config: LDAPConfig.Config) : LDAPBase(config) {

private fun bindOk(uDN: String, pwd: String): AuthenResult =
private fun bindOk(
uDN: String,
pwd: String,
): AuthenResult =
try {
if (ldapConnection.bind(uDN, pwd).resultCode == ResultCode.SUCCESS) {
AuthenResult(true, uDN, "")
Expand All @@ -32,7 +34,10 @@ class LDAPAuthentication private constructor(val config: LDAPConfig.Config) : LD
)
}

override fun canUserAuthenticate(userDNs: List<String>, pwd: String): Set<AuthenResult> =
override fun canUserAuthenticate(
userDNs: List<String>,
pwd: String,
): Set<AuthenResult> =
if (!ldapConnection.isConnected) {
log.error(
"${Monitoring.AUTHENTICATION_LDAP_FAILURE.txt} $userDNs and related password!",
Expand All @@ -58,9 +63,10 @@ class LDAPAuthentication private constructor(val config: LDAPConfig.Config) : LD
companion object {
private val log: Logger = LoggerFactory.getLogger(LDAPAuthentication::class.java)

fun init(configFile: String = ""): LDAPAuthentication = when (configFile.isEmpty()) {
true -> LDAPAuthentication(LDAPConfig.getByClasspath())
else -> LDAPAuthentication(LDAPConfig.getBySource(configFile))
}
fun init(configFile: String = ""): LDAPAuthentication =
when (configFile.isEmpty()) {
true -> LDAPAuthentication(LDAPConfig.getByClasspath())
else -> LDAPAuthentication(LDAPConfig.getBySource(configFile))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import javax.security.auth.login.AppConfigurationEntry
* /plain/internals/PlainServerCallbackHandler.java
*/
class SimpleLDAPAuthentication : AuthenticateCallbackHandler {

init {
log.debug("${SimpleLDAPAuthentication::class.java.canonicalName} object created")
}
Expand All @@ -47,7 +46,10 @@ class SimpleLDAPAuthentication : AuthenticateCallbackHandler {
?.let { throw UnsupportedCallbackException(it) }
}

private fun authenticate(username: String, password: String): Boolean {
private fun authenticate(
username: String,
password: String,
): Boolean {
log.debug("Authentication Start - user=$username")

// always check cache before ldap lookup
Expand All @@ -57,16 +59,24 @@ class SimpleLDAPAuthentication : AuthenticateCallbackHandler {
return isAuthenticated
}

private fun userInCache(userDNs: List<String>, password: String): Boolean =
userDNs.any { uDN -> LDAPCache.userExists(uDN, password) }
private fun userInCache(
userDNs: List<String>,
password: String,
): Boolean = userDNs.any { uDN -> LDAPCache.userExists(uDN, password) }

private fun userCanBindInLDAP(userDNs: List<String>, password: String): Boolean =
private fun userCanBindInLDAP(
userDNs: List<String>,
password: String,
): Boolean =
LDAPAuthentication.init()
.use { ldap -> ldap.canUserAuthenticate(userDNs, password) }
.map { LDAPCache.userAdd(it.userDN, password) }
.isNotEmpty()

private fun logAuthenticationResult(isAuthenticated: Boolean, username: String) {
private fun logAuthenticationResult(
isAuthenticated: Boolean,
username: String,
) {
if (isAuthenticated) {
log.info(
"${Monitoring.AUTHENTICATION_SUCCESS.txt} - user=$username, status=authenticated",
Expand All @@ -86,7 +96,10 @@ class SimpleLDAPAuthentication : AuthenticateCallbackHandler {
JAASContext.password = optionValue(jaasOptions, "password")
}

private fun optionValue(jaasOptions: Map<String, *>?, option: String): String {
private fun optionValue(
jaasOptions: Map<String, *>?,
option: String,
): String {
val maybeValue = jaasOptions?.get(option)
if (maybeValue is String) {
return maybeValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import org.apache.kafka.common.security.auth.KafkaPrincipal
* Instance of Kafka SimpleAuthorizer require logging to different server logs
*/
class GroupAuthorizer(private val uuid: String) : AutoCloseable {

private fun userGroupMembershipIsCached(groups: List<String>, userDNs: List<String>): Boolean =
private fun userGroupMembershipIsCached(
groups: List<String>,
userDNs: List<String>,
): Boolean =
userDNs.any { userDN ->
groups.any { groupName ->
LDAPCache.groupAndUserExists(
Expand All @@ -23,13 +25,19 @@ class GroupAuthorizer(private val uuid: String) : AutoCloseable {
}
}

private fun userGroupMembershipInLDAP(groups: List<String>, userDNs: List<String>): Boolean =
private fun userGroupMembershipInLDAP(
groups: List<String>,
userDNs: List<String>,
): Boolean =
LDAPAuthorization.init(uuid)
.use { ldap -> ldap.isUserMemberOfAny(userDNs, groups) }
.map { LDAPCache.groupAndUserAdd(it.groupName, it.userDN, uuid) }
.isNotEmpty()

fun authorize(principal: KafkaPrincipal, acls: Set<Acl>): Boolean =
fun authorize(
principal: KafkaPrincipal,
acls: Set<Acl>,
): Boolean =
LDAPConfig.getByClasspath().toUserDNNodes(principal.name).let { userDNs ->
acls
.map { it.principal().name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@ class LDAPAuthorization private constructor(
private val uuid: String,
val config: LDAPConfig.Config,
) : LDAPBase(config) {

// In authorization context, needs to bind the connection before compare-match between group
// and user due to no anonymous access allowed for LDAP operations like search, compare, ...
private val connectionAndBindIsOk: Boolean

init {
connectionAndBindIsOk = when {
JAASContext.username.isEmpty() || JAASContext.password.isEmpty() -> false
!ldapConnection.isConnected -> false
else -> doBind(config.toAdminDN(JAASContext.username), JAASContext.password)
}
connectionAndBindIsOk =
when {
JAASContext.username.isEmpty() || JAASContext.password.isEmpty() -> false
!ldapConnection.isConnected -> false
else -> doBind(config.toAdminDN(JAASContext.username), JAASContext.password)
}
}

private fun doBind(userDN: String, pwd: String): Boolean =
private fun doBind(
userDN: String,
pwd: String,
): Boolean =
try {
log.debug(
"Binding information for authorization fetched from JAAS config file [$userDN]",
Expand Down Expand Up @@ -102,7 +105,10 @@ class LDAPAuthorization private constructor(
emptyList()
}

override fun isUserMemberOfAny(userDNs: List<String>, groups: List<String>): Set<AuthorResult> {
override fun isUserMemberOfAny(
userDNs: List<String>,
groups: List<String>,
): Set<AuthorResult> {
if (!connectionAndBindIsOk) {
log.error(
"${Monitoring.AUTHORIZATION_LDAP_FAILURE.txt} $userDNs membership in " +
Expand All @@ -111,12 +117,13 @@ class LDAPAuthorization private constructor(
return emptySet()
}

val matching = groups.flatMap { groupName ->
val groupDN = getGroupDN(groupName)
val members = getGroupMembers(groupDN)
log.info("Group $groupDN has members $members, checking for presence of $userDNs")
members.intersect(userDNs).map { uDN -> AuthorResult(groupName, uDN) }
}
val matching =
groups.flatMap { groupName ->
val groupDN = getGroupDN(groupName)
val members = getGroupMembers(groupDN)
log.info("Group $groupDN has members $members, checking for presence of $userDNs")
members.intersect(userDNs).map { uDN -> AuthorResult(groupName, uDN) }
}

log.info("Checking $userDNs for membership in $groups, found: $matching")
return matching.toSet()
Expand All @@ -125,7 +132,10 @@ class LDAPAuthorization private constructor(
companion object {
private val log: Logger = LoggerFactory.getLogger(LDAPAuthorization::class.java)

fun init(uuid: String, configFile: String = ""): LDAPAuthorization =
fun init(
uuid: String,
configFile: String = "",
): LDAPAuthorization =
when (configFile.isEmpty()) {
true -> LDAPAuthorization(uuid, LDAPConfig.getByClasspath())
else -> LDAPAuthorization(uuid, LDAPConfig.getBySource(configFile))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ class SimpleLDAPAuthorizer : SimpleAclAuthorizer() {
val lResource = resource?.toString()

val uuid = java.util.UUID.randomUUID().toString()
val authContext = "principal=$principal, operation=$lOperation, remote_host=$host, " +
"resource=$lResource, uuid=$uuid"
val authContext =
"principal=$principal, operation=$lOperation, remote_host=$host, " +
"resource=$lResource, uuid=$uuid"

log.debug("Authorization Start - $authContext")

Expand All @@ -53,8 +54,8 @@ class SimpleLDAPAuthorizer : SimpleAclAuthorizer() {

// TODO AclPermissionType.ALLOW - under change in minor version - CAREFUL!
// userAdd allow access control lists for resource and given operation
val sacls = getAcls(resource)
.filter {
val sacls =
getAcls(resource).filter {
it.operation() == operation && it.permissionType()
.toJava() == AclPermissionType.ALLOW
}
Expand All @@ -79,15 +80,17 @@ class SimpleLDAPAuthorizer : SimpleAclAuthorizer() {

// verify membership, either cached or through LDAP - see GroupAuthorizer
val anonymous = KafkaPrincipal(KafkaPrincipal.USER_TYPE, "ANONYMOUS")
val isAuthorized = GroupAuthorizer(uuid).use {
it.authorize(session?.principal() ?: anonymous, acls)
}
val isAuthorized =
GroupAuthorizer(uuid).use {
it.authorize(session?.principal() ?: anonymous, acls)
}

when (isAuthorized) {
true -> log.debug("Authorization End - $authContext, status=authorized")
false -> log.error(
"${Monitoring.AUTHORIZATION_FAILED.txt} - $authContext, status=denied",
)
false ->
log.error(
"${Monitoring.AUTHORIZATION_FAILED.txt} - $authContext, status=denied",
)
}

return isAuthorized
Expand Down
Loading