(refs #245)Add full name attribute for LDAP authentication.

This commit is contained in:
takezoe
2014-01-25 05:07:32 +09:00
parent 94bd1c6a93
commit 5a1f541e13
5 changed files with 65 additions and 48 deletions

View File

@@ -33,6 +33,7 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport {
"bindPassword" -> trim(label("Bind Password", optional(text()))),
"baseDN" -> trim(label("Base DN", text(required))),
"userNameAttribute" -> trim(label("User name attribute", text(required))),
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
"mailAttribute" -> trim(label("Mail address attribute", text(required))),
"tls" -> trim(label("Enable TLS", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text())))

View File

@@ -36,11 +36,11 @@ trait AccountService {
*/
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) = {
LDAPUtil.authenticate(settings.ldap.get, userName, password) match {
case Right(mailAddress) => {
case Right(ldapUserInfo) => {
// Create or update account by LDAP information
getAccountByUserName(userName) match {
case Some(x) => updateAccount(x.copy(mailAddress = mailAddress))
case None => createAccount(userName, "", userName, mailAddress, false, None)
case Some(x) => updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
case None => createAccount(userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None)
}
getAccountByUserName(userName)
}

View File

@@ -31,6 +31,7 @@ trait SystemSettingsService {
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
@@ -71,6 +72,7 @@ trait SystemSettingsService {
getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapFullNameAttribute, None),
getValue(props, LdapMailAddressAttribute, ""),
getOptionValue[Boolean](props, LdapTls, None),
getOptionValue(props, LdapKeystore, None)))
@@ -101,6 +103,7 @@ object SystemSettingsService {
bindPassword: Option[String],
baseDN: String,
userNameAttribute: String,
fullNameAttribute: Option[String],
mailAttribute: String,
tls: Option[Boolean],
keystore: Option[String])
@@ -134,6 +137,7 @@ object SystemSettingsService {
private val LdapBindPassword = "ldap.bind_password"
private val LdapBaseDN = "ldap.baseDN"
private val LdapUserNameAttribute = "ldap.username_attribute"
private val LdapFullNameAttribute = "ldap.fullname_attribute"
private val LdapMailAddressAttribute = "ldap.mail_attribute"
private val LdapTls = "ldap.tls"
private val LdapKeystore = "ldap.keystore"

View File

@@ -18,51 +18,49 @@ object LDAPUtil {
/**
* Try authentication by LDAP using given configuration.
* Returns Right(mailAddress) if authentication is successful, otherwise Left(errorMessage).
* Returns Right(LDAPUserInfo) if authentication is successful, otherwise Left(errorMessage).
*/
def authenticate(ldapSettings: Ldap, userName: String, password: String): Either[String, String] = {
def authenticate(ldapSettings: Ldap, userName: String, password: String): Either[String, LDAPUserInfo] = {
bind(
ldapSettings.host,
ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort),
ldapSettings.bindDN.getOrElse(""),
ldapSettings.bindPassword.getOrElse(""),
ldapSettings.tls.getOrElse(false),
ldapSettings.keystore.getOrElse("")
) match {
case Some(conn) => {
withConnection(conn) { conn =>
host = ldapSettings.host,
port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort),
dn = ldapSettings.bindDN.getOrElse(""),
password = ldapSettings.bindPassword.getOrElse(""),
tls = ldapSettings.tls.getOrElse(false),
keystore = ldapSettings.keystore.getOrElse(""),
error = "System LDAP authentication failed."
){ conn =>
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute) match {
case Some(userDN) => userAuthentication(ldapSettings, userDN, password)
case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
case None => Left("User does not exist.")
}
}
}
case None => Left("System LDAP authentication failed.")
}
}
private def userAuthentication(ldapSettings: Ldap, userDN: String, password: String): Either[String, String] = {
private def userAuthentication(ldapSettings: Ldap, userDN: String, userName: String, password: String): Either[String, LDAPUserInfo] = {
bind(
ldapSettings.host,
ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort),
userDN,
password,
ldapSettings.tls.getOrElse(false),
ldapSettings.keystore.getOrElse("")
) match {
case Some(conn) => {
withConnection(conn) { conn =>
host = ldapSettings.host,
port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort),
dn = userDN,
password = password,
tls = ldapSettings.tls.getOrElse(false),
keystore = ldapSettings.keystore.getOrElse(""),
error = "User LDAP Authentication Failed."
){ conn =>
findMailAddress(conn, userDN, ldapSettings.mailAttribute) match {
case Some(mailAddress) => Right(mailAddress)
case Some(mailAddress) => Right(LDAPUserInfo(
userName = userName,
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
findFullName(conn, userDN, fullNameAttribute)
}.getOrElse(userName),
mailAddress = mailAddress))
case None => Left("Can't find mail address.")
}
}
}
case None => Left("User LDAP Authentication Failed.")
}
}
private def bind(host: String, port: Int, dn: String, password: String, tls: Boolean, keystore: String): Option[LDAPConnection] = {
private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, keystore: String, error: String)
(f: LDAPConnection => Either[String, A]): Either[String, A] = {
if (tls) {
// Dynamically set Sun as the security provider
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider())
@@ -87,7 +85,9 @@ object LDAPUtil {
// Bind to the server
conn.bind(LDAP_VERSION, dn, password.getBytes)
Some(conn)
// Execute a given function and returns a its result
f(conn)
} catch {
case e: Exception => {
// Provide more information if something goes wrong
@@ -96,20 +96,15 @@ object LDAPUtil {
if (conn.isConnected) {
conn.disconnect()
}
None
// Returns an error message
Left(error)
}
}
}
private def withConnection[T](conn: LDAPConnection)(f: LDAPConnection => T): T = {
try {
f(conn)
} finally {
conn.disconnect()
}
}
/**
* Search a specified user and returns userDN if exists.
*/
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = {
@tailrec
def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
@@ -134,4 +129,14 @@ object LDAPUtil {
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
} else None
}
private def findFullName(conn: LDAPConnection, userDN: String, nameAttribute: String): Option[String] =
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](nameAttribute), false)){ results =>
if(results.hasMore) {
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
} else None
}
case class LDAPUserInfo(userName: String, fullName: String, mailAddress: String)
}

View File

@@ -94,6 +94,13 @@
<span id="error-ldap_userNameAttribute" class="error"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
<div class="controls">
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" value="@settings.ldap.map(_.fullNameAttribute)"/>
<span id="error-ldap_fullNameAttribute" class="error"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="ldapMailAttribute">Mail address attribute</label>
<div class="controls">