Skip to content

Commit

Permalink
Added Test LdapProvider just to test kerberosLdapSearch
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLydonKing committed Jun 11, 2024
1 parent 69cab58 commit 0f11ae1
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import za.co.absa.loginsvc.rest.config.provider.AuthConfigProvider
import za.co.absa.loginsvc.rest.provider.kerberos.{KerberosSPNEGOAuthenticationProvider, RestApiKerberosAuthentication}
import za.co.absa.loginsvc.rest.provider.kerberos.{KerberosSPNEGOAuthenticationProvider, RestApiKerberosAuthentication, RestApiKerberosAuthenticationProvider}

@Configuration
@EnableWebSecurity
Expand Down Expand Up @@ -63,9 +63,11 @@ class SecurityConfig@Autowired()(authConfigsProvider: AuthConfigProvider) {
if(KerberosConfig.enableKerberos.isDefined)
{
val kerberos = new KerberosSPNEGOAuthenticationProvider(KerberosConfig)
val provider = new RestApiKerberosAuthenticationProvider(KerberosConfig.url, KerberosConfig.searchFilter, KerberosConfig.domain);
http.addFilterBefore(
RestApiKerberosAuthentication.spnegoAuthenticationProcessingFilter(
new ProviderManager(kerberos.kerberosServiceAuthenticationProvider())), classOf[BasicAuthenticationFilter])
new ProviderManager(provider, kerberos.kerberosServiceAuthenticationProvider())),
classOf[BasicAuthenticationFilter])
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package za.co.absa.loginsvc.rest.provider.kerberos

import org.springframework.ldap.core.DirContextOperations
import org.springframework.ldap.core.support.BaseLdapPathContextSource
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch

class KerberosLdapUserSearch (searchBase: String, searchFilter: String, contextSource: BaseLdapPathContextSource)
extends FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource) {

override def searchForUser(username: String): DirContextOperations = {
val user = if (username.contains("@")) {
username.split("@").head
} else {
username
}
super.searchForUser(user)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package za.co.absa.loginsvc.rest.provider.kerberos

import org.slf4j.LoggerFactory
import org.springframework.security.authentication.{AuthenticationProvider, BadCredentialsException, UsernamePasswordAuthenticationToken}
import org.springframework.security.core.Authentication
import org.springframework.security.ldap.userdetails.LdapUserDetailsService
import sun.security.krb5.KrbException

import java.net.{SocketTimeoutException, UnknownHostException}
import java.security.PrivilegedActionException
import javax.security.auth.Subject
import javax.security.auth.callback.{Callback, CallbackHandler, NameCallback, PasswordCallback}
import javax.security.auth.login.{AppConfigurationEntry, Configuration, LoginContext, LoginException}
import scala.util.control.NonFatal

class RestApiKerberosAuthenticationProvider (adServer: String, searchFilter: String, baseDN: String)
extends AuthenticationProvider {

case class RestApiKerberosLoginResult(loginContext: LoginContext, verifiedName: String)

private val logger = LoggerFactory.getLogger(this.getClass)

override def authenticate(authentication: Authentication): Authentication = {
val output = try {
val auth = authentication.asInstanceOf[UsernamePasswordAuthenticationToken]
val loginResult = login(auth.getName, auth.getCredentials.toString)
val userDetailsService = getUserDetailService(loginResult.loginContext.getSubject)
val userDetails = userDetailsService.loadUserByUsername(loginResult.verifiedName)
loginResult.loginContext.logout()
new UsernamePasswordAuthenticationToken(userDetails, auth.getCredentials, userDetails.getAuthorities)
} catch {
case ex: LoginException =>
ex.getCause match {
// This is thrown when there is an issue contacting a KDC specified in krb5.conf for the realm
case nestedException: SocketTimeoutException =>
throw new Exception(s"Timeout host: ${nestedException.getMessage}", ex)
case nestedException: UnknownHostException =>
throw new Exception(s"Unknown authentication host: ${nestedException.getMessage}", ex)
case nestedException: KrbException =>
throw new BadCredentialsException(s"Invalid credentials: ${nestedException.getMessage}", ex)
case NonFatal(_) =>
throw ex
}
// This is thrown when there is an issue contacting an LDAP server specified in REST API configuration
case ex: PrivilegedActionException =>
throw new Exception(ex.toString, ex)
case NonFatal(ex) =>
throw ex
}
output.setDetails(authentication.getDetails)
output
}

override def supports(authentication: Class[_]): Boolean = {
classOf[UsernamePasswordAuthenticationToken].isAssignableFrom(authentication)
}

private def login(username: String, password: String): RestApiKerberosLoginResult = {
val loginContext = new LoginContext("", null, getSpringCBHandler(username, password), getLoginConfig) // scalastyle:ignore null
loginContext.login()
val loggedInUser = loginContext.getSubject.getPrincipals.iterator.next.toString
logger.debug(s"Logged In User: $loggedInUser")
RestApiKerberosLoginResult(loginContext, loggedInUser)
}

private def getSpringCBHandler(username: String, password: String) = {
new CallbackHandler() {
def handle(callbacks: Array[Callback]): Unit = {
callbacks.foreach({
case ncb: NameCallback => ncb.setName(username)
case pwdcb: PasswordCallback => pwdcb.setPassword(password.toCharArray)
})
}
}
}

private def getUserDetailService(subject: Subject) = {
val contextSource = new RestApiKerberosLdapContextSource(adServer, subject)
contextSource.afterPropertiesSet()
val userSearch = new KerberosLdapUserSearch(baseDN, searchFilter, contextSource)
new LdapUserDetailsService(userSearch, new ActiveDirectoryLdapAuthoritiesPopulator())
}

private def getLoginConfig: Configuration = {
import scala.collection.JavaConverters._

new Configuration {
override def getAppConfigurationEntry(name: String): Array[AppConfigurationEntry] = {
val opts = Map(
"storeKey" -> "true",
"refreshKrb5Config" -> "true",
"isInitiator" -> "true",
"debug" -> "true")
Array(new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts.asJava))
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package za.co.absa.loginsvc.rest.provider.kerberos

import org.springframework.security.ldap.DefaultSpringSecurityContextSource

import java.security.PrivilegedAction
import javax.naming.Context
import javax.naming.directory.DirContext
import javax.security.auth.Subject

class RestApiKerberosLdapContextSource (url: String, subject: Subject) extends DefaultSpringSecurityContextSource(url) {
override def getDirContextInstance(environment: java.util.Hashtable[String, Object]): DirContext = {

environment.put(Context.SECURITY_AUTHENTICATION, "GSSAPI")
val sup = super.getDirContextInstance _

logger.debug(s"Trying to authenticate to LDAP as ${subject.getPrincipals}")
Subject.doAs(subject, new PrivilegedAction[DirContext]() {
override def run(): DirContext = {
sup(environment)
}
})
}
}

0 comments on commit 0f11ae1

Please sign in to comment.