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) session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName) updateLastLoginDate(account.userName)
if(AccountUtil.hasLdapDummyMailAddress(account)) {
session.remove(Keys.Session.Redirect)
redirect("/" + account.userName + "/_edit")
}
session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl => session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl =>
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){ if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
redirect("/") redirect("/")

View File

@@ -7,7 +7,7 @@ import jp.sf.amateras.scalatra.forms._
import org.scalatra.FlashMapSupport import org.scalatra.FlashMapSupport
class SystemSettingsController extends SystemSettingsControllerBase class SystemSettingsController extends SystemSettingsControllerBase
with SystemSettingsService with AccountService with AdminAuthenticator with SystemSettingsService with AccountService with AdminAuthenticator
trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport {
self: SystemSettingsService with AccountService with AdminAuthenticator => self: SystemSettingsService with AccountService with AdminAuthenticator =>
@@ -33,8 +33,10 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport {
"bindPassword" -> trim(label("Bind Password", optional(text()))), "bindPassword" -> trim(label("Bind Password", optional(text()))),
"baseDN" -> trim(label("Base DN", text(required))), "baseDN" -> trim(label("Base DN", text(required))),
"userNameAttribute" -> trim(label("User name attribute", 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()))), "fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
"mailAttribute" -> trim(label("Mail address attribute", text(required))), "mailAttribute" -> trim(label("Mail address attribute", text(required))),
"disableMailResolve" -> trim(label("Disable Mail Resolve", optional(boolean()))),
"tls" -> trim(label("Enable TLS", optional(boolean()))), "tls" -> trim(label("Enable TLS", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text()))) "keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)) )(Ldap.apply))

View File

@@ -39,7 +39,13 @@ trait AccountService {
case Right(ldapUserInfo) => { case Right(ldapUserInfo) => {
// Create or update account by LDAP information // Create or update account by LDAP information
getAccountByUserName(userName, true) match { 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) => { case Some(x) if(x.isRemoved) => {
logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..") logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..")
defaultAuthentication(userName, password) defaultAuthentication(userName, password)

View File

@@ -31,8 +31,10 @@ trait SystemSettingsService {
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x)) ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN) props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
ldap.disableMailResolve.foreach(x => props.setProperty(LdapDisableMailResolve, x.toString))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString)) ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x)) ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
} }
@@ -72,8 +74,10 @@ trait SystemSettingsService {
getOptionValue(props, LdapBindPassword, None), getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""), getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""), getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapAdditionalFilterCondition, None),
getOptionValue(props, LdapFullNameAttribute, None), getOptionValue(props, LdapFullNameAttribute, None),
getValue(props, LdapMailAddressAttribute, ""), getValue(props, LdapMailAddressAttribute, ""),
getOptionValue[Boolean](props, LdapDisableMailResolve, None),
getOptionValue[Boolean](props, LdapTls, None), getOptionValue[Boolean](props, LdapTls, None),
getOptionValue(props, LdapKeystore, None))) getOptionValue(props, LdapKeystore, None)))
} else { } else {
@@ -103,8 +107,10 @@ object SystemSettingsService {
bindPassword: Option[String], bindPassword: Option[String],
baseDN: String, baseDN: String,
userNameAttribute: String, userNameAttribute: String,
additionalFilterCondition: Option[String],
fullNameAttribute: Option[String], fullNameAttribute: Option[String],
mailAttribute: String, mailAttribute: String,
disableMailResolve: Option[Boolean],
tls: Option[Boolean], tls: Option[Boolean],
keystore: Option[String]) keystore: Option[String])
@@ -137,8 +143,10 @@ object SystemSettingsService {
private val LdapBindPassword = "ldap.bind_password" private val LdapBindPassword = "ldap.bind_password"
private val LdapBaseDN = "ldap.baseDN" private val LdapBaseDN = "ldap.baseDN"
private val LdapUserNameAttribute = "ldap.username_attribute" private val LdapUserNameAttribute = "ldap.username_attribute"
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
private val LdapFullNameAttribute = "ldap.fullname_attribute" private val LdapFullNameAttribute = "ldap.fullname_attribute"
private val LdapMailAddressAttribute = "ldap.mail_attribute" private val LdapMailAddressAttribute = "ldap.mail_attribute"
private val LdapDisableMailResolve = "ldap.disable_mail_resolve"
private val LdapTls = "ldap.tls" private val LdapTls = "ldap.tls"
private val LdapKeystore = "ldap.keystore" 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(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "System LDAP authentication failed." error = "System LDAP authentication failed."
){ conn => ){ 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 Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
case None => Left("User does not exist.") case None => Left("User does not exist.")
} }
@@ -47,6 +47,14 @@ object LDAPUtil {
keystore = ldapSettings.keystore.getOrElse(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "User LDAP Authentication Failed." error = "User LDAP Authentication Failed."
){ conn => ){ 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 { findMailAddress(conn, userDN, ldapSettings.mailAttribute) match {
case Some(mailAddress) => Right(LDAPUserInfo( case Some(mailAddress) => Right(LDAPUserInfo(
userName = userName, 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) 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] = { (f: LDAPConnection => Either[String, A]): Either[String, A] = {
@@ -105,7 +114,7 @@ object LDAPUtil {
/** /**
* Search a specified user and returns userDN if exists. * 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 @tailrec
def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = { def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
if(results.hasMore){ if(results.hasMore){
@@ -118,7 +127,13 @@ object LDAPUtil {
entries.flatten 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 case x => x.getDN
} }
} }

View File

@@ -27,7 +27,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
) )
.distinct .distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded .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) @(account: Option[model.Account], info: Option[Any])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import util.AccountUtil
@html.main((if(account.isDefined) "Edit your profile" else "Create your account")){ @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){ @if(account.isDefined){
<h3>Edit your profile</h3> <h3>Edit your profile</h3>
} else { } else {
<h3>Create your account</h3> <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"> <form action="@if(account.isDefined){@url(account.get.userName)/_edit}else{@path/register}" method="POST" validate="true">
<div class="row-fluid"> <div class="row-fluid">
<div class="span6"> <div class="span6">
@@ -38,7 +42,7 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="mailAddress" class="strong">Mail Address:</label> <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> <span id="error-mailAddress" class="error"></span>
</fieldset> </fieldset>
<fieldset> <fieldset>
@@ -60,7 +64,9 @@
<a href="@path/@account.get.userName/_delete" class="btn btn-danger" id="delete">Delete account</a> <a href="@path/@account.get.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
</div> </div>
<input type="submit" class="btn btn-success" value="Save"/> <input type="submit" class="btn btn-success" value="Save"/>
@if(!AccountUtil.hasLdapDummyMailAddress(account.get)){
<a href="@url(account.get.userName)" class="btn">Cancel</a> <a href="@url(account.get.userName)" class="btn">Cancel</a>
}
} else { } else {
<input type="submit" class="btn btn-success" value="Create account"/> <input type="submit" class="btn btn-success" value="Create account"/>
} }

View File

@@ -3,9 +3,9 @@
@import util.Directory._ @import util.Directory._
@import view.helpers._ @import view.helpers._
@html.main("System Settings"){ @html.main("System Settings"){
@menu("system"){ @menu("system"){
@helper.html.information(info) @helper.html.information(info)
<form action="@path/admin/system" method="POST" validate="true"> <form action="@path/admin/system" method="POST" validate="true">
<div class="box"> <div class="box">
<div class="box-header">System Settings</div> <div class="box-header">System Settings</div>
<div class="box-content"> <div class="box-content">
@@ -94,6 +94,13 @@
<span id="error-ldap_userNameAttribute" class="error"></span> <span id="error-ldap_userNameAttribute" class="error"></span>
</div> </div>
</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"> <div class="control-group">
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label> <label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
<div class="controls"> <div class="controls">
@@ -108,6 +115,13 @@
<span id="error-ldap_mailAttribute" class="error"></span> <span id="error-ldap_mailAttribute" class="error"></span>
</div> </div>
</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="control-group">
<div class="controls"> <div class="controls">
<label class="checkbox"> <label class="checkbox">
@@ -186,8 +200,8 @@
<fieldset> <fieldset>
<input type="submit" class="btn btn-success" value="Apply changes"/> <input type="submit" class="btn btn-success" value="Apply changes"/>
</fieldset> </fieldset>
</form> </form>
} }
} }
<script> <script>
$(function(){ $(function(){