Add features (additional filter condition / disable mail resolve) to LDAP authentication.

This commit is contained in:
yjkony
2014-02-28 20:48:09 +09:00
parent 521d15219c
commit 639e7e0b3f
9 changed files with 304 additions and 230 deletions

View File

@@ -56,6 +56,11 @@ trait IndexControllerBase extends ControllerBase {
session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName)
if(AccountUtil.hasLdapDummyMailAddress(account)) {
session.remove(Keys.Session.Redirect)
redirect("/" + account.userName + "/_edit")
}
session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl =>
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
redirect("/")

View File

@@ -33,8 +33,10 @@ 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))),
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
"mailAttribute" -> trim(label("Mail address attribute", text(required))),
"disableMailResolve" -> trim(label("Disable Mail Resolve", optional(boolean()))),
"tls" -> trim(label("Enable TLS", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply))

View File

@@ -39,7 +39,13 @@ trait AccountService {
case Right(ldapUserInfo) => {
// Create or update account by LDAP information
getAccountByUserName(userName, true) match {
case Some(x) if(!x.isRemoved) => updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
case Some(x) if(!x.isRemoved) => {
if(settings.ldap.get.disableMailResolve.getOrElse(false)) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
} else {
updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
}
}
case Some(x) if(x.isRemoved) => {
logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..")
defaultAuthentication(userName, password)

View File

@@ -31,8 +31,10 @@ trait SystemSettingsService {
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
ldap.disableMailResolve.foreach(x => props.setProperty(LdapDisableMailResolve, x.toString))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
}
@@ -72,8 +74,10 @@ trait SystemSettingsService {
getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapAdditionalFilterCondition, None),
getOptionValue(props, LdapFullNameAttribute, None),
getValue(props, LdapMailAddressAttribute, ""),
getOptionValue[Boolean](props, LdapDisableMailResolve, None),
getOptionValue[Boolean](props, LdapTls, None),
getOptionValue(props, LdapKeystore, None)))
} else {
@@ -103,8 +107,10 @@ object SystemSettingsService {
bindPassword: Option[String],
baseDN: String,
userNameAttribute: String,
additionalFilterCondition: Option[String],
fullNameAttribute: Option[String],
mailAttribute: String,
disableMailResolve: Option[Boolean],
tls: Option[Boolean],
keystore: Option[String])
@@ -137,8 +143,10 @@ object SystemSettingsService {
private val LdapBindPassword = "ldap.bind_password"
private val LdapBaseDN = "ldap.baseDN"
private val LdapUserNameAttribute = "ldap.username_attribute"
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
private val LdapFullNameAttribute = "ldap.fullname_attribute"
private val LdapMailAddressAttribute = "ldap.mail_attribute"
private val LdapDisableMailResolve = "ldap.disable_mail_resolve"
private val LdapTls = "ldap.tls"
private val LdapKeystore = "ldap.keystore"

View File

@@ -0,0 +1,18 @@
package util
import model.Account
/**
* Utility for account model.
*/
object AccountUtil {
private val LDAP_DUMMY_MAL = "@ldap-devnull"
def hasLdapDummyMailAddress(account: Account): Boolean = {
account.mailAddress.endsWith(LDAP_DUMMY_MAL)
}
def getLdapDummyMailAddress(userName: String): String = {
userName + LDAP_DUMMY_MAL
}
}

View File

@@ -30,7 +30,7 @@ object LDAPUtil {
keystore = ldapSettings.keystore.getOrElse(""),
error = "System LDAP authentication failed."
){ conn =>
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute) match {
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute, ldapSettings.additionalFilterCondition) match {
case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
case None => Left("User does not exist.")
}
@@ -47,6 +47,14 @@ object LDAPUtil {
keystore = ldapSettings.keystore.getOrElse(""),
error = "User LDAP Authentication Failed."
){ conn =>
if(ldapSettings.disableMailResolve.getOrElse(false)) {
Right(LDAPUserInfo(
userName = userName,
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
findFullName(conn, userDN, fullNameAttribute)
}.getOrElse(userName),
mailAddress = AccountUtil.getLdapDummyMailAddress(userName)))
} else {
findMailAddress(conn, userDN, ldapSettings.mailAttribute) match {
case Some(mailAddress) => Right(LDAPUserInfo(
userName = userName,
@@ -58,6 +66,7 @@ object LDAPUtil {
}
}
}
}
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] = {
@@ -105,7 +114,7 @@ object LDAPUtil {
/**
* Search a specified user and returns userDN if exists.
*/
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = {
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String, additionalFilterCondition: Option[String]): Option[String] = {
@tailrec
def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
if(results.hasMore){
@@ -118,7 +127,13 @@ object LDAPUtil {
entries.flatten
}
}
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, userNameAttribute + "=" + userName, null, false)).collectFirst {
val filterCond = additionalFilterCondition.getOrElse("") match {
case "" => userNameAttribute + "=" + userName
case x => "(&(" + x + ")(" + userNameAttribute + "=" + userName + "))"
}
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, filterCond, null, false)).collectFirst {
case x => x.getDN
}
}

View File

@@ -27,7 +27,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
)
.distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) filterNot (AccountUtil.hasLdapDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
}

View File

@@ -1,13 +1,17 @@
@(account: Option[model.Account], info: Option[Any])(implicit context: app.Context)
@import context._
@import view.helpers._
@import util.AccountUtil
@html.main((if(account.isDefined) "Edit your profile" else "Create your account")){
@helper.html.information(info)
@if(account.isDefined && AccountUtil.hasLdapDummyMailAddress(account.get)) {
<div class="alert alert-danger">Please register your mail address.</div>
}
@if(account.isDefined){
<h3>Edit your profile</h3>
} else {
<h3>Create your account</h3>
}
@helper.html.information(info)
<form action="@if(account.isDefined){@url(account.get.userName)/_edit}else{@path/register}" method="POST" validate="true">
<div class="row-fluid">
<div class="span6">
@@ -38,7 +42,7 @@
</fieldset>
<fieldset>
<label for="mailAddress" class="strong">Mail Address:</label>
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
<input type="text" name="mailAddress" id="mailAddress" value="@if(account.isDefined && !AccountUtil.hasLdapDummyMailAddress(account.get)){@account.map(_.mailAddress)}"/>
<span id="error-mailAddress" class="error"></span>
</fieldset>
<fieldset>
@@ -60,7 +64,9 @@
<a href="@path/@account.get.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
</div>
<input type="submit" class="btn btn-success" value="Save"/>
@if(!AccountUtil.hasLdapDummyMailAddress(account.get)){
<a href="@url(account.get.userName)" class="btn">Cancel</a>
}
} else {
<input type="submit" class="btn btn-success" value="Create account"/>
}

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="ldapAdditionalFilterCondition">Additional filter condition</label>
<div class="controls">
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
<span id="error-ldap_additionalFilterCondition" class="error"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
<div class="controls">
@@ -108,6 +115,13 @@
<span id="error-ldap_mailAttribute" class="error"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="ldap.disableMailResolve"@if(settings.ldap.flatMap(_.disableMailResolve).getOrElse(false)){ checked}/> Disable Mail Resolve
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">