From 582df3239f55d8c56f23b937a8a17c5d5455860c Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 16 Aug 2013 03:45:50 +0900 Subject: [PATCH 01/10] (refs #78)Implementing LDAP authentication. --- project/build.scala | 1 + .../scala/app/SystemSettingsController.scala | 20 ++++-- .../scala/service/SystemSettingsService.scala | 39 +++++++++- src/main/scala/util/LDAPUtil.scala | 49 +++++++++++++ src/main/twirl/admin/system.scala.html | 72 ++++++++++++++++++- 5 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 src/main/scala/util/LDAPUtil.scala diff --git a/project/build.scala b/project/build.scala index 3b0cefb29..c5cdd89f8 100644 --- a/project/build.scala +++ b/project/build.scala @@ -36,6 +36,7 @@ object MyBuild extends Build { "org.pegdown" % "pegdown" % "1.3.0", "org.apache.commons" % "commons-compress" % "1.5", "com.typesafe.slick" %% "slick" % "1.0.1", + "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.3.171", "ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container", diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 0e824c4d2..60f370086 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -17,12 +17,20 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { "gravatar" -> trim(label("Gravatar", boolean())), "notification" -> trim(label("Notification", boolean())), "smtp" -> optionalIfNotChecked("notification", mapping( - "host" -> trim(label("SMTP Host", text(required))), - "port" -> trim(label("SMTP Port", optional(number()))), - "user" -> trim(label("SMTP User", optional(text()))), - "password" -> trim(label("SMTP Password", optional(text()))), - "ssl" -> trim(label("Enable SSL", optional(boolean()))) - )(Smtp.apply)) + "host" -> trim(label("SMTP Host", text(required))), + "port" -> trim(label("SMTP Port", optional(number()))), + "user" -> trim(label("SMTP User", optional(text()))), + "password" -> trim(label("SMTP Password", optional(text()))), + "ssl" -> trim(label("Enable SSL", optional(boolean()))) + )(Smtp.apply)), + "authType" -> trim(label("Auth Type", text(required))), + "ldap" -> optional(_.get("authType") == Some("LDAP"), mapping( + "host" -> trim(label("LDAP host", text(required))), + "port" -> trim(label("LDAP port", number(required))), + "baseDN" -> trim(label("BaseDN", text(required))), + "userNameAttribute" -> trim(label("User name attribute", text(required))), + "mailAttribute" -> trim(label("Mail address attribute", text(required))) + )(Ldap.apply)) )(SystemSettings.apply) diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index 6837f70ad..d63952625 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -19,6 +19,15 @@ trait SystemSettingsService { smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString)) } } + if(settings.authType == "LDAP"){ + settings.ldap.map { ldap => + props.setProperty(LdapHost, ldap.host) + props.setProperty(LdapPort, ldap.port.toString) + props.setProperty(LdapBaseDN, ldap.baseDN) + props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) + props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) + } + } props.store(new java.io.FileOutputStream(GitBucketConf), null) } @@ -41,6 +50,17 @@ trait SystemSettingsService { getOptionValue[Boolean](props, SmtpSsl, None))) } else { None + }, + getValue(props, AuthType, ""), + if(getValue(props, AuthType, "") == "LDAP"){ + Some(Ldap( + getValue(props, LdapHost, ""), + getValue(props, LdapPort, 389), + getValue(props, LdapBaseDN, ""), + getValue(props, LdapUserNameAttribute, "uid"), + getValue(props, LdapUserNameAttribute, "mail"))) + } else { + None } ) } @@ -54,8 +74,17 @@ object SystemSettingsService { allowAccountRegistration: Boolean, gravatar: Boolean, notification: Boolean, - smtp: Option[Smtp] - ) + smtp: Option[Smtp], + authType: String, + ldap: Option[Ldap]) + + case class Ldap( + host: String, + port: Int, + baseDN: String, + userNameAttribute: String, + mailAttribute: String) + case class Smtp( host: String, port: Option[Int], @@ -65,12 +94,18 @@ object SystemSettingsService { private val AllowAccountRegistration = "allow_account_registration" private val Gravatar = "gravatar" + private val AuthType = "auth_type" private val Notification = "notification" private val SmtpHost = "smtp.host" private val SmtpPort = "smtp.port" private val SmtpUser = "smtp.user" private val SmtpPassword = "smtp.password" private val SmtpSsl = "smtp.ssl" + private val LdapHost = "ldap.host" + private val LdapPort = "ldap.port" + private val LdapBaseDN = "ldap.baseDN" + private val LdapUserNameAttribute = "ldap.username_attribute" + private val LdapMailAddressAttribute = "ldap.mail_attribute" private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = { val value = props.getProperty(key) diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala new file mode 100644 index 000000000..21454ef1e --- /dev/null +++ b/src/main/scala/util/LDAPUtil.scala @@ -0,0 +1,49 @@ +package util + +import service.SystemSettingsService.Ldap +import com.novell.ldap.LDAPConnection + +/** + * Utility for LDAP authentication. + */ +object LDAPUtil extends App { + + /** + * Try authentication by LDAP using given configuration. + * Returns Right(mailAddress) if authentication is successful, otherwise Left(errorMessage). + */ + def authenticate(ldapSettings: Ldap, userName: String, password: String): Either[String, String] = { + var conn: LDAPConnection = null + try { + conn = new LDAPConnection() + conn.connect(ldapSettings.host, ldapSettings.port) + val userDN = ldapSettings.userNameAttribute + "=" + userName + ",ou=Users," + ldapSettings.baseDN + conn.bind(3, userDN, password.getBytes) + if(conn.isBound){ + val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, "", Array[String](ldapSettings.mailAttribute), false) + var mailAddress: String = null + while(results.hasMore){ + mailAddress = results.next.getAttribute(ldapSettings.mailAttribute).getStringValue + } + if(mailAddress != null){ + Right(mailAddress) + } else { + Left("Can't find mail address.") + } + } else { + Left("Authentication failed.") + } + } catch { + case ex: Exception => Left(ex.getMessage) + } finally { + if(conn != null){ + conn.disconnect() + } + } + } + +// val ldapSettings = Ldap("192.168.159.128", 389, "dc=unix-power,dc=net", "uid", "mail") +// +// println(authenticate(ldapSettings, "tanaka", "password")) + +} diff --git a/src/main/twirl/admin/system.scala.html b/src/main/twirl/admin/system.scala.html index 9d8c9b6ff..3e77d0e1b 100644 --- a/src/main/twirl/admin/system.scala.html +++ b/src/main/twirl/admin/system.scala.html @@ -8,6 +8,9 @@
System Settings
+ + +
+ + +
@@ -27,6 +33,63 @@ Gravatar
+ + + +
+ +
+ +
+
+ +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + +
@@ -35,7 +98,7 @@ Send notifications
-
+
@@ -81,7 +144,12 @@ \ No newline at end of file From 231fd268df85e995140c9c3786a500ad7776d9e6 Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 16 Aug 2013 11:46:16 +0900 Subject: [PATCH 02/10] (refs #78)LDAP authentication is completed? (not tested yet) --- src/main/scala/app/SignInController.scala | 61 ++++++++++++++++++----- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/main/scala/app/SignInController.scala b/src/main/scala/app/SignInController.scala index d7e824820..06f11cc2e 100644 --- a/src/main/scala/app/SignInController.scala +++ b/src/main/scala/app/SignInController.scala @@ -3,6 +3,8 @@ package app import service._ import util.StringUtil._ import jp.sf.amateras.scalatra.forms._ +import util.LDAPUtil +import service.SystemSettingsService.SystemSettings class SignInController extends SignInControllerBase with SystemSettingsService with AccountService @@ -24,19 +26,11 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService } post("/signin", form){ form => - getAccountByUserName(form.userName).collect { - case account if(!account.isGroupAccount && account.password == sha1(form.password)) => { - session.setAttribute("LOGIN_ACCOUNT", account) - updateLastLoginDate(account.userName) - - session.get("REDIRECT").map { redirectUrl => - session.removeAttribute("REDIRECT") - redirect(redirectUrl.asInstanceOf[String]) - }.getOrElse { - redirect("/") - } - } - } getOrElse redirect("/signin") + val settings = loadSystemSettings() + settings.authType match { + case "LDAP" => ldapAuthentication(form, settings) + case _ => defaultAuthentication(form) + } } get("/signout"){ @@ -44,4 +38,45 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService redirect("/") } + /** + * Authenticate by internal database. + */ + private def defaultAuthentication(form: SignInForm) = { + getAccountByUserName(form.userName).collect { + case account if(!account.isGroupAccount && account.password == sha1(form.password)) => signin(account) + } getOrElse redirect("/signin") + } + + /** + * Authenticate by LDAP. + */ + private def ldapAuthentication(form: SignInForm, settings: SystemSettings) = { + LDAPUtil.authenticate(settings.ldap.get, form.userName, form.password) match { + case Right(mailAddress) => { + // Create or update account by LDAP information + getAccountByUserName(form.userName) match { + case Some(x) => updateAccount(x.copy(mailAddress = mailAddress)) + case None => createAccount(form.userName, "", mailAddress, false, None) + } + signin(getAccountByUserName(form.userName).get) + } + case Left(errorMessage) => defaultAuthentication(form) + } + } + + /** + * Set account information into HttpSession and redirect. + */ + private def signin(account: model.Account) = { + session.setAttribute("LOGIN_ACCOUNT", account) + updateLastLoginDate(account.userName) + + session.get("REDIRECT").map { redirectUrl => + session.removeAttribute("REDIRECT") + redirect(redirectUrl.asInstanceOf[String]) + }.getOrElse { + redirect("/") + } + } + } \ No newline at end of file From df5600f03fc82b78eecbcf7d683705117ac1ace4 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sat, 17 Aug 2013 01:10:06 +0900 Subject: [PATCH 03/10] (refs #78)Fix for LDAP authentication. --- src/main/scala/app/SignInController.scala | 7 ++++--- src/main/scala/app/SystemSettingsController.scala | 4 ++-- .../scala/service/SystemSettingsService.scala | 13 +++++++------ src/main/twirl/admin/system.scala.html | 15 ++++----------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/main/scala/app/SignInController.scala b/src/main/scala/app/SignInController.scala index 06f11cc2e..e238ccf63 100644 --- a/src/main/scala/app/SignInController.scala +++ b/src/main/scala/app/SignInController.scala @@ -27,9 +27,10 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService post("/signin", form){ form => val settings = loadSystemSettings() - settings.authType match { - case "LDAP" => ldapAuthentication(form, settings) - case _ => defaultAuthentication(form) + if(settings.ldapAuthentication){ + ldapAuthentication(form, settings) + } else { + defaultAuthentication(form) } } diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 60f370086..03df40398 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -23,8 +23,8 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { "password" -> trim(label("SMTP Password", optional(text()))), "ssl" -> trim(label("Enable SSL", optional(boolean()))) )(Smtp.apply)), - "authType" -> trim(label("Auth Type", text(required))), - "ldap" -> optional(_.get("authType") == Some("LDAP"), mapping( + "ldapAuthentication" -> trim(label("LDAP", boolean())), + "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( "host" -> trim(label("LDAP host", text(required))), "port" -> trim(label("LDAP port", number(required))), "baseDN" -> trim(label("BaseDN", text(required))), diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index d63952625..ee83a12cf 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -19,7 +19,8 @@ trait SystemSettingsService { smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString)) } } - if(settings.authType == "LDAP"){ + props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString) + if(settings.ldapAuthentication){ settings.ldap.map { ldap => props.setProperty(LdapHost, ldap.host) props.setProperty(LdapPort, ldap.port.toString) @@ -51,14 +52,14 @@ trait SystemSettingsService { } else { None }, - getValue(props, AuthType, ""), - if(getValue(props, AuthType, "") == "LDAP"){ + getValue(props, LdapAuthentication, false), + if(getValue(props, LdapAuthentication, false)){ Some(Ldap( getValue(props, LdapHost, ""), getValue(props, LdapPort, 389), getValue(props, LdapBaseDN, ""), getValue(props, LdapUserNameAttribute, "uid"), - getValue(props, LdapUserNameAttribute, "mail"))) + getValue(props, LdapMailAddressAttribute, "mail"))) } else { None } @@ -75,7 +76,7 @@ object SystemSettingsService { gravatar: Boolean, notification: Boolean, smtp: Option[Smtp], - authType: String, + ldapAuthentication: Boolean, ldap: Option[Ldap]) case class Ldap( @@ -94,13 +95,13 @@ object SystemSettingsService { private val AllowAccountRegistration = "allow_account_registration" private val Gravatar = "gravatar" - private val AuthType = "auth_type" private val Notification = "notification" private val SmtpHost = "smtp.host" private val SmtpPort = "smtp.port" private val SmtpUser = "smtp.user" private val SmtpPassword = "smtp.password" private val SmtpSsl = "smtp.ssl" + private val LdapAuthentication = "ldap_authentication" private val LdapHost = "ldap.host" private val LdapPort = "ldap.port" private val LdapBaseDN = "ldap.baseDN" diff --git a/src/main/twirl/admin/system.scala.html b/src/main/twirl/admin/system.scala.html index 3e77d0e1b..daebbc718 100644 --- a/src/main/twirl/admin/system.scala.html +++ b/src/main/twirl/admin/system.scala.html @@ -40,13 +40,7 @@
-
-
-
@@ -147,9 +141,8 @@ $(function(){ $('.notification input').prop('disabled', !$(this).prop('checked')); }).change(); - $('input[name=authType]').click(function(){ - $('.ldap input').prop('disabled', $('input[name=authType]:checked').val() != "LDAP"); - }); - $('input[name=authType]:checked').click(); + $('#ldapAuthentication').change(function(){ + $('.ldap input').prop('disabled', !$(this).prop('checked')); + }).change(); }); \ No newline at end of file From cdfdff5c32fae40e677fe167468440b26bb36202 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sat, 17 Aug 2013 01:16:22 +0900 Subject: [PATCH 04/10] (refs #78)LDAP authenticated user can't set password. --- src/main/twirl/account/edit.scala.html | 20 +++++++++++--------- src/main/twirl/admin/users/user.scala.html | 22 ++++++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/twirl/account/edit.scala.html b/src/main/twirl/account/edit.scala.html index 2eeb22aac..24e0eaf04 100644 --- a/src/main/twirl/account/edit.scala.html +++ b/src/main/twirl/account/edit.scala.html @@ -18,15 +18,17 @@ } -
- - - -
+ @if(account.map(_.password.nonEmpty).getOrElse(true)){ +
+ + + +
+ }
diff --git a/src/main/twirl/admin/users/user.scala.html b/src/main/twirl/admin/users/user.scala.html index 98ae9edb9..713d45951 100644 --- a/src/main/twirl/admin/users/user.scala.html +++ b/src/main/twirl/admin/users/user.scala.html @@ -10,16 +10,18 @@
-
- - - -
+ @if(account.map(_.password.nonEmpty).getOrElse(true)){ +
+ + + +
+ }
From 7e26b4695d2c807b42a2a9e0b8858dacd85f84ff Mon Sep 17 00:00:00 2001 From: takezoe Date: Sat, 17 Aug 2013 01:48:01 +0900 Subject: [PATCH 05/10] (refs #78)LDAP port is optional. --- src/main/scala/app/SystemSettingsController.scala | 2 +- src/main/scala/service/SystemSettingsService.scala | 12 +++++++----- src/main/scala/util/LDAPUtil.scala | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 03df40398..b688d1262 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -26,7 +26,7 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { "ldapAuthentication" -> trim(label("LDAP", boolean())), "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( "host" -> trim(label("LDAP host", text(required))), - "port" -> trim(label("LDAP port", number(required))), + "port" -> trim(label("LDAP port", optional(number()))), "baseDN" -> trim(label("BaseDN", text(required))), "userNameAttribute" -> trim(label("User name attribute", text(required))), "mailAttribute" -> trim(label("Mail address attribute", text(required))) diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index ee83a12cf..a36e62b1b 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -23,7 +23,7 @@ trait SystemSettingsService { if(settings.ldapAuthentication){ settings.ldap.map { ldap => props.setProperty(LdapHost, ldap.host) - props.setProperty(LdapPort, ldap.port.toString) + ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) props.setProperty(LdapBaseDN, ldap.baseDN) props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) @@ -56,10 +56,10 @@ trait SystemSettingsService { if(getValue(props, LdapAuthentication, false)){ Some(Ldap( getValue(props, LdapHost, ""), - getValue(props, LdapPort, 389), + getOptionValue(props, LdapPort, Some(DefaultLdapPort)), getValue(props, LdapBaseDN, ""), - getValue(props, LdapUserNameAttribute, "uid"), - getValue(props, LdapMailAddressAttribute, "mail"))) + getValue(props, LdapUserNameAttribute, ""), + getValue(props, LdapMailAddressAttribute, ""))) } else { None } @@ -81,7 +81,7 @@ object SystemSettingsService { case class Ldap( host: String, - port: Int, + port: Option[Int], baseDN: String, userNameAttribute: String, mailAttribute: String) @@ -93,6 +93,8 @@ object SystemSettingsService { password: Option[String], ssl: Option[Boolean]) + val DefaultLdapPort = 389 + private val AllowAccountRegistration = "allow_account_registration" private val Gravatar = "gravatar" private val Notification = "notification" diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala index 21454ef1e..edd75feb1 100644 --- a/src/main/scala/util/LDAPUtil.scala +++ b/src/main/scala/util/LDAPUtil.scala @@ -1,6 +1,7 @@ package util import service.SystemSettingsService.Ldap +import service.SystemSettingsService import com.novell.ldap.LDAPConnection /** @@ -16,7 +17,7 @@ object LDAPUtil extends App { var conn: LDAPConnection = null try { conn = new LDAPConnection() - conn.connect(ldapSettings.host, ldapSettings.port) + conn.connect(ldapSettings.host, ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort)) val userDN = ldapSettings.userNameAttribute + "=" + userName + ",ou=Users," + ldapSettings.baseDN conn.bind(3, userDN, password.getBytes) if(conn.isBound){ From b9aa6a234b08d8ad38c1854c4e2e802e300c432c Mon Sep 17 00:00:00 2001 From: takezoe Date: Sat, 17 Aug 2013 11:05:11 +0900 Subject: [PATCH 06/10] (refs #78)Authentication moved to AccountService. --- src/main/scala/app/SignInController.scala | 36 ++--------------- src/main/scala/service/AccountService.scala | 39 +++++++++++++++++++ .../servlet/BasicAuthenticationFilter.scala | 17 ++++---- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/main/scala/app/SignInController.scala b/src/main/scala/app/SignInController.scala index e238ccf63..dea995a5a 100644 --- a/src/main/scala/app/SignInController.scala +++ b/src/main/scala/app/SignInController.scala @@ -1,10 +1,7 @@ package app import service._ -import util.StringUtil._ import jp.sf.amateras.scalatra.forms._ -import util.LDAPUtil -import service.SystemSettingsService.SystemSettings class SignInController extends SignInControllerBase with SystemSettingsService with AccountService @@ -27,10 +24,9 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService post("/signin", form){ form => val settings = loadSystemSettings() - if(settings.ldapAuthentication){ - ldapAuthentication(form, settings) - } else { - defaultAuthentication(form) + authenticate(loadSystemSettings(), form.userName, form.password) match { + case Some(account) => signin(account) + case None => redirect("/signin") } } @@ -39,32 +35,6 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService redirect("/") } - /** - * Authenticate by internal database. - */ - private def defaultAuthentication(form: SignInForm) = { - getAccountByUserName(form.userName).collect { - case account if(!account.isGroupAccount && account.password == sha1(form.password)) => signin(account) - } getOrElse redirect("/signin") - } - - /** - * Authenticate by LDAP. - */ - private def ldapAuthentication(form: SignInForm, settings: SystemSettings) = { - LDAPUtil.authenticate(settings.ldap.get, form.userName, form.password) match { - case Right(mailAddress) => { - // Create or update account by LDAP information - getAccountByUserName(form.userName) match { - case Some(x) => updateAccount(x.copy(mailAddress = mailAddress)) - case None => createAccount(form.userName, "", mailAddress, false, None) - } - signin(getAccountByUserName(form.userName).get) - } - case Left(errorMessage) => defaultAuthentication(form) - } - } - /** * Set account information into HttpSession and redirect. */ diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index 3f828e3ed..39ddf6b4f 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -3,9 +3,48 @@ package service import model._ import scala.slick.driver.H2Driver.simple._ import Database.threadLocalSession +import service.SystemSettingsService.SystemSettings +import util.StringUtil._ +import model.GroupMember +import scala.Some +import model.Account +import util.LDAPUtil trait AccountService { + def authenticate(settings: SystemSettings, userName: String, password: String): Option[Account] = + if(settings.ldapAuthentication){ + ldapAuthentication(settings, userName, password) + } else { + defaultAuthentication(userName, password) + } + + /** + * Authenticate by internal database. + */ + private def defaultAuthentication(userName: String, password: String) = { + getAccountByUserName(userName).collect { + case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account) + } getOrElse None + } + + /** + * Authenticate by LDAP. + */ + private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) = { + LDAPUtil.authenticate(settings.ldap.get, userName, password) match { + case Right(mailAddress) => { + // Create or update account by LDAP information + getAccountByUserName(userName) match { + case Some(x) => updateAccount(x.copy(mailAddress = mailAddress)) + case None => createAccount(userName, "", mailAddress, false, None) + } + getAccountByUserName(userName) + } + case Left(errorMessage) => defaultAuthentication(userName, password) + } + } + def getAccountByUserName(userName: String): Option[Account] = Query(Accounts) filter(_.userName is userName.bind) firstOption diff --git a/src/main/scala/servlet/BasicAuthenticationFilter.scala b/src/main/scala/servlet/BasicAuthenticationFilter.scala index 51d6618b0..6dda92324 100644 --- a/src/main/scala/servlet/BasicAuthenticationFilter.scala +++ b/src/main/scala/servlet/BasicAuthenticationFilter.scala @@ -2,14 +2,13 @@ package servlet import javax.servlet._ import javax.servlet.http._ -import util.StringUtil._ -import service.{AccountService, RepositoryService} +import service.{SystemSettingsService, AccountService, RepositoryService} import org.slf4j.LoggerFactory /** * Provides BASIC Authentication for [[servlet.GitRepositoryServlet]]. */ -class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService { +class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService { private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter]) @@ -58,12 +57,12 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou } } - private def isWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo): Boolean = { - getAccountByUserName(username).map { account => - account.password == sha1(password) && hasWritePermission(repository.owner, repository.name, Some(account)) - } getOrElse false - } - + private def isWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo): Boolean = + authenticate(loadSystemSettings(), username, password) match { + case Some(account) => hasWritePermission(repository.owner, repository.name, Some(account)) + case None => false + } + private def requireAuth(response: HttpServletResponse): Unit = { response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"") response.sendError(HttpServletResponse.SC_UNAUTHORIZED) From bfc1d1d6b01f22b3afdc3daf451f68c4e9ae1b34 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Wed, 21 Aug 2013 16:37:10 +0900 Subject: [PATCH 07/10] LDAP authentication by using bind account --- .../scala/app/SystemSettingsController.scala | 8 +- .../scala/service/SystemSettingsService.scala | 8 ++ src/main/scala/util/LDAPUtil.scala | 112 +++++++++++++----- src/main/twirl/admin/system.scala.html | 17 ++- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index b688d1262..dc2065d2c 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -27,9 +27,11 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( "host" -> trim(label("LDAP host", text(required))), "port" -> trim(label("LDAP port", optional(number()))), - "baseDN" -> trim(label("BaseDN", text(required))), - "userNameAttribute" -> trim(label("User name attribute", text(required))), - "mailAttribute" -> trim(label("Mail address attribute", text(required))) + "bindDN" -> trim(label("Bind DN", text(required))), + "bindPassword" -> trim(label("Bind Password", text(required))), + "baseDN" -> trim(label("Base DN", text(required))), + "userNameAttribute" -> trim(label("User name attribute", text(required))), + "mailAttribute" -> trim(label("Mail address attribute", text(required))) )(Ldap.apply)) )(SystemSettings.apply) diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index a36e62b1b..4c01135a2 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -24,6 +24,8 @@ trait SystemSettingsService { settings.ldap.map { ldap => props.setProperty(LdapHost, ldap.host) ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) + props.setProperty(LdapBindDN, ldap.bindDN) + props.setProperty(LdapBindPassword, ldap.bindPassword) props.setProperty(LdapBaseDN, ldap.baseDN) props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) @@ -57,6 +59,8 @@ trait SystemSettingsService { Some(Ldap( getValue(props, LdapHost, ""), getOptionValue(props, LdapPort, Some(DefaultLdapPort)), + getValue(props, LdapBindDN, ""), + getValue(props, LdapBindPassword, ""), getValue(props, LdapBaseDN, ""), getValue(props, LdapUserNameAttribute, ""), getValue(props, LdapMailAddressAttribute, ""))) @@ -82,6 +86,8 @@ object SystemSettingsService { case class Ldap( host: String, port: Option[Int], + bindDN: String, + bindPassword: String, baseDN: String, userNameAttribute: String, mailAttribute: String) @@ -106,6 +112,8 @@ object SystemSettingsService { private val LdapAuthentication = "ldap_authentication" private val LdapHost = "ldap.host" private val LdapPort = "ldap.port" + private val LdapBindDN = "ldap.bindDN" + private val LdapBindPassword = "ldap.bind_password" private val LdapBaseDN = "ldap.baseDN" private val LdapUserNameAttribute = "ldap.username_attribute" private val LdapMailAddressAttribute = "ldap.mail_attribute" diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala index edd75feb1..6ad1aa0a8 100644 --- a/src/main/scala/util/LDAPUtil.scala +++ b/src/main/scala/util/LDAPUtil.scala @@ -2,49 +2,107 @@ package util import service.SystemSettingsService.Ldap import service.SystemSettingsService -import com.novell.ldap.LDAPConnection +import com.novell.ldap.{LDAPReferralException, LDAPEntry, LDAPConnection} /** * Utility for LDAP authentication. */ -object LDAPUtil extends App { +object LDAPUtil { + + private val LDAP_VERSION: Int = 3 /** * Try authentication by LDAP using given configuration. * Returns Right(mailAddress) if authentication is successful, otherwise Left(errorMessage). */ def authenticate(ldapSettings: Ldap, userName: String, password: String): Either[String, String] = { - var conn: LDAPConnection = null - try { - conn = new LDAPConnection() - conn.connect(ldapSettings.host, ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort)) - val userDN = ldapSettings.userNameAttribute + "=" + userName + ",ou=Users," + ldapSettings.baseDN - conn.bind(3, userDN, password.getBytes) - if(conn.isBound){ - val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, "", Array[String](ldapSettings.mailAttribute), false) - var mailAddress: String = null - while(results.hasMore){ - mailAddress = results.next.getAttribute(ldapSettings.mailAttribute).getStringValue + bind( + ldapSettings.host, + ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), + ldapSettings.bindDN, + ldapSettings.bindPassword + ) match { + case Some(conn) => { + withConnection(conn) { conn => + findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute) match { + case Some(userDN) => userAuthentication(ldapSettings, userDN, password) + case None => Left("User does not exist") + } } - if(mailAddress != null){ - Right(mailAddress) - } else { - Left("Can't find mail address.") - } - } else { - Left("Authentication failed.") } + case None => Left("System LDAP authentication failed.") + } + } + + private def userAuthentication(ldapSettings: Ldap, userDN: String, password: String): Either[String, String] = { + bind( + ldapSettings.host, + ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), + userDN, + password + ) match { + case Some(conn) => { + withConnection(conn) { conn => + findMailAddress(conn, userDN, ldapSettings.mailAttribute) match { + case Some(mailAddress) => Right(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): Option[LDAPConnection] = { + val conn: LDAPConnection = new LDAPConnection + try { + conn.connect(host, port) + conn.bind(LDAP_VERSION, dn, password.getBytes) + Some(conn) } catch { - case ex: Exception => Left(ex.getMessage) - } finally { - if(conn != null){ - conn.disconnect() + case e: Exception => { + if (conn.isConnected) conn.disconnect() + None } } } -// val ldapSettings = Ldap("192.168.159.128", 389, "dc=unix-power,dc=net", "uid", "mail") -// -// println(authenticate(ldapSettings, "tanaka", "password")) + private def withConnection[T](conn: LDAPConnection)(f: LDAPConnection => T): T = { + try { + f(conn) + } finally { + conn.disconnect() + } + } + private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = { + val results = conn.search(baseDN, LDAPConnection.SCOPE_SUB, userNameAttribute + "=" + userName, null, false) + while (results.hasMore) { + var entry: LDAPEntry = null + try { + entry = results.next + } catch { + case lre: LDAPReferralException => // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD) + } + if (entry != null) { + return Some(entry.getDN) + } + } + None + } + + private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] = { + val attributes = Array[String](mailAttribute) + val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, null, attributes, false) + if (results.hasMore) { + val attr = results.next.getAttribute(mailAttribute) + if (attr != null) { + Some(attr.getStringValue) + } else { + None + } + } else { + None + } + } } diff --git a/src/main/twirl/admin/system.scala.html b/src/main/twirl/admin/system.scala.html index daebbc718..40be7bf53 100644 --- a/src/main/twirl/admin/system.scala.html +++ b/src/main/twirl/admin/system.scala.html @@ -60,7 +60,22 @@
- + +
+ + +
+
+
+ +
+ + +
+
+ +
+
From 078ed868fb6af0cc49506998bca5496db5334e13 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Wed, 21 Aug 2013 20:08:27 +0900 Subject: [PATCH 08/10] Fix indent --- src/main/twirl/admin/system.scala.html | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/twirl/admin/system.scala.html b/src/main/twirl/admin/system.scala.html index 40be7bf53..5e9e794cf 100644 --- a/src/main/twirl/admin/system.scala.html +++ b/src/main/twirl/admin/system.scala.html @@ -60,20 +60,19 @@
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
-
From 2b2bf88a37385dfa245a6d0d8c973f8bdfd48d28 Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 22 Aug 2013 02:27:45 +0900 Subject: [PATCH 09/10] Scalized :-) --- src/main/scala/util/LDAPUtil.scala | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala index 6ad1aa0a8..bb31f32c1 100644 --- a/src/main/scala/util/LDAPUtil.scala +++ b/src/main/scala/util/LDAPUtil.scala @@ -77,30 +77,19 @@ object LDAPUtil { private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = { val results = conn.search(baseDN, LDAPConnection.SCOPE_SUB, userNameAttribute + "=" + userName, null, false) - while (results.hasMore) { - var entry: LDAPEntry = null - try { - entry = results.next - } catch { - case lre: LDAPReferralException => // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD) - } - if (entry != null) { - return Some(entry.getDN) - } + (for(i <- 0 to results.getCount) yield try { + Some(results.next) + } catch { + case ex: LDAPReferralException => None // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD) + }).flatten.collectFirst { + case x if(x != null) => x.getDN } - None } private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] = { - val attributes = Array[String](mailAttribute) - val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, null, attributes, false) + val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false) if (results.hasMore) { - val attr = results.next.getAttribute(mailAttribute) - if (attr != null) { - Some(attr.getStringValue) - } else { - None - } + Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue) } else { None } From e0bd5a24f4bae95f5278b80c36712e9414b9a8ba Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 22 Aug 2013 02:29:05 +0900 Subject: [PATCH 10/10] Fix indent. --- .../scala/app/SystemSettingsController.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index dc2065d2c..d1a813e08 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -23,15 +23,15 @@ trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport { "password" -> trim(label("SMTP Password", optional(text()))), "ssl" -> trim(label("Enable SSL", optional(boolean()))) )(Smtp.apply)), - "ldapAuthentication" -> trim(label("LDAP", boolean())), - "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( - "host" -> trim(label("LDAP host", text(required))), - "port" -> trim(label("LDAP port", optional(number()))), - "bindDN" -> trim(label("Bind DN", text(required))), - "bindPassword" -> trim(label("Bind Password", text(required))), - "baseDN" -> trim(label("Base DN", text(required))), - "userNameAttribute" -> trim(label("User name attribute", text(required))), - "mailAttribute" -> trim(label("Mail address attribute", text(required))) + "ldapAuthentication" -> trim(label("LDAP", boolean())), + "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( + "host" -> trim(label("LDAP host", text(required))), + "port" -> trim(label("LDAP port", optional(number()))), + "bindDN" -> trim(label("Bind DN", text(required))), + "bindPassword" -> trim(label("Bind Password", text(required))), + "baseDN" -> trim(label("Base DN", text(required))), + "userNameAttribute" -> trim(label("User name attribute", text(required))), + "mailAttribute" -> trim(label("Mail address attribute", text(required))) )(Ldap.apply)) )(SystemSettings.apply)