diff --git a/src/main/resources/update/gitbucket-core_4.24.xml b/src/main/resources/update/gitbucket-core_4.24.xml
new file mode 100644
index 0000000..03249f5
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.24.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index bcca992..8b58e07 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -51,5 +51,6 @@
new Version("4.21.2"),
new Version("4.22.0", new LiquibaseMigration("update/gitbucket-core_4.22.xml")),
new Version("4.23.0", new LiquibaseMigration("update/gitbucket-core_4.23.xml")),
- new Version("4.23.1")
+ new Version("4.23.1"),
+ new Version("4.24.0", new LiquibaseMigration("update/gitbucket-core_4.24.xml"))
)
diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala
index 6223eb5..3f12900 100644
--- a/src/main/scala/gitbucket/core/controller/AccountController.scala
+++ b/src/main/scala/gitbucket/core/controller/AccountController.scala
@@ -2,7 +2,7 @@
import gitbucket.core.account.html
import gitbucket.core.helper
-import gitbucket.core.model.{AccountWebHook, GroupMember, RepositoryWebHook, Role, WebHook, WebHookContentType}
+import gitbucket.core.model._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service._
import gitbucket.core.service.WebHookService._
@@ -54,6 +54,7 @@
password: String,
fullName: String,
mailAddress: String,
+ extraMailAddresses: List[String],
description: Option[String],
url: Option[String],
fileId: Option[String]
@@ -63,6 +64,7 @@
password: Option[String],
fullName: String,
mailAddress: String,
+ extraMailAddresses: List[String],
description: Option[String],
url: Option[String],
fileId: Option[String],
@@ -78,6 +80,9 @@
"password" -> trim(label("Password", text(required, maxlength(20), password))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
+ "extraMailAddresses" -> list(
+ trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress())))
+ ),
"description" -> trim(label("bio", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID", optional(text())))
@@ -87,6 +92,9 @@
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
+ "extraMailAddresses" -> list(
+ trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
+ ),
"description" -> trim(label("bio", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID", optional(text()))),
@@ -295,26 +303,29 @@
get("/:userName/_edit")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
- html.edit(x, flash.get("info"), flash.get("error"))
+ val extraMails = getAccountExtraMailAddresses(userName)
+ html.edit(x, extraMails, flash.get("info"), flash.get("error"))
} getOrElse NotFound()
})
post("/:userName/_edit", editForm)(oneselfOnly { form =>
val userName = params("userName")
- getAccountByUserName(userName).map { account =>
- updateAccount(
- account.copy(
- password = form.password.map(sha1).getOrElse(account.password),
- fullName = form.fullName,
- mailAddress = form.mailAddress,
- description = form.description,
- url = form.url
+ getAccountByUserName(userName).map {
+ account =>
+ updateAccount(
+ account.copy(
+ password = form.password.map(sha1).getOrElse(account.password),
+ fullName = form.fullName,
+ mailAddress = form.mailAddress,
+ description = form.description,
+ url = form.url
+ )
)
- )
- updateImage(userName, form.fileId, form.clearImage)
- flash += "info" -> "Account information has been updated."
- redirect(s"/${userName}/_edit")
+ updateImage(userName, form.fileId, form.clearImage)
+ updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
+ flash += "info" -> "Account information has been updated."
+ redirect(s"/${userName}/_edit")
} getOrElse NotFound()
})
@@ -552,6 +563,7 @@
form.url
)
updateImage(form.userName, form.fileId, false)
+ updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses)
redirect("/signin")
} else NotFound()
}
diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala
index a2310b3..b80fa1e 100644
--- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala
+++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala
@@ -359,13 +359,42 @@
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
- getAccountByMailAddress(value, true)
- .filter { x =>
- if (paramName.isEmpty) true else Some(x.userName) != params.optionValue(paramName)
- }
- .map { _ =>
- "Mail address is already registered."
- }
+ val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
+ if (extraMailAddresses.exists {
+ case (k, v) =>
+ v.contains(value)
+ }) {
+ Some("These mail addresses are duplicated.")
+ } else {
+ getAccountByMailAddress(value, true)
+ .collect {
+ case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
+ "Mail address is already registered."
+ }
+ }
+ }
+ }
+
+ protected def uniqueExtraMailAddress(paramName: String = ""): Constraint = new Constraint() {
+ override def validate(
+ name: String,
+ value: String,
+ params: Map[String, Seq[String]],
+ messages: Messages
+ ): Option[String] = {
+ val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
+ if (Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count {
+ case (k, v) =>
+ v.contains(value)
+ } > 1) {
+ Some("These mail addresses are duplicated.")
+ } else {
+ getAccountByMailAddress(value, true)
+ .collect {
+ case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
+ "Mail address is already registered."
+ }
+ }
}
}
diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
index d2d0649..513affc 100644
--- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
@@ -124,6 +124,7 @@
password: String,
fullName: String,
mailAddress: String,
+ extraMailAddresses: List[String],
isAdmin: Boolean,
description: Option[String],
url: Option[String],
@@ -135,6 +136,7 @@
password: Option[String],
fullName: String,
mailAddress: String,
+ extraMailAddresses: List[String],
isAdmin: Boolean,
description: Option[String],
url: Option[String],
@@ -166,6 +168,9 @@
"password" -> trim(label("Password", text(required, maxlength(20), password))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
+ "extraMailAddresses" -> list(
+ trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
+ ),
"isAdmin" -> trim(label("User Type", boolean())),
"description" -> trim(label("bio", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -177,6 +182,9 @@
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
+ "extraMailAddresses" -> list(
+ trim(label("Additional Mail Address", text(maxlength(100), uniqueExtraMailAddress("userName"))))
+ ),
"isAdmin" -> trim(label("User Type", boolean())),
"description" -> trim(label("bio", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -400,7 +408,7 @@
})
get("/admin/users/_newuser")(adminOnly {
- html.user(None)
+ html.user(None, Nil)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
@@ -414,12 +422,14 @@
form.url
)
updateImage(form.userName, form.fileId, false)
+ updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
- html.user(getAccountByUserName(userName, true), flash.get("error"))
+ val extraMails = getAccountExtraMailAddresses(userName)
+ html.user(getAccountByUserName(userName, true), extraMails, flash.get("error"))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
@@ -455,6 +465,7 @@
)
updateImage(userName, form.fileId, form.clearImage)
+ updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
// call hooks
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
diff --git a/src/main/scala/gitbucket/core/model/AccountExtraMailAddress.scala b/src/main/scala/gitbucket/core/model/AccountExtraMailAddress.scala
new file mode 100644
index 0000000..88cc2fe
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/AccountExtraMailAddress.scala
@@ -0,0 +1,19 @@
+package gitbucket.core.model
+
+trait AccountExtraMailAddressComponent { self: Profile =>
+ import profile.api._
+
+ lazy val AccountExtraMailAddresses = TableQuery[AccountExtraMailAddresses]
+
+ class AccountExtraMailAddresses(tag: Tag) extends Table[AccountExtraMailAddress](tag, "ACCOUNT_EXTRA_MAIL_ADDRESS") {
+ val userName = column[String]("USER_NAME", O PrimaryKey)
+ val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
+ def * =
+ (userName, extraMailAddress) <> (AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply)
+ }
+}
+
+case class AccountExtraMailAddress(
+ userName: String,
+ extraMailAddress: String
+)
diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala
index f35dec9..577c3ee 100644
--- a/src/main/scala/gitbucket/core/model/Profile.scala
+++ b/src/main/scala/gitbucket/core/model/Profile.scala
@@ -68,5 +68,6 @@
with DeployKeyComponent
with ReleaseTagComponent
with ReleaseAssetComponent
+ with AccountExtraMailAddressComponent
object Profile extends CoreProfile
diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala
index 65c7ae0..8f0ba63 100644
--- a/src/main/scala/gitbucket/core/service/AccountService.scala
+++ b/src/main/scala/gitbucket/core/service/AccountService.scala
@@ -1,11 +1,11 @@
package gitbucket.core.service
import org.slf4j.LoggerFactory
-import gitbucket.core.model.{GroupMember, Account}
+import gitbucket.core.model.{Account, AccountExtraMailAddress, GroupMember}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
-import gitbucket.core.util.{StringUtil, LDAPUtil}
+import gitbucket.core.util.{LDAPUtil, StringUtil}
import StringUtil._
import gitbucket.core.service.SystemSettingsService.SystemSettings
@@ -121,9 +121,16 @@
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(
implicit s: Session
): Option[Account] =
- Accounts filter (
- t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)
- ) firstOption
+ (Accounts joinLeft AccountExtraMailAddresses on { case (a, e) => a.userName === e.userName })
+ .filter {
+ case (a, x) =>
+ ((a.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) ||
+ (x.map { e =>
+ e.extraMailAddress.toLowerCase === mailAddress.toLowerCase.bind
+ }
+ .getOrElse(false.bind))) && (a.removed === false.bind, !includeRemoved)
+ }
+ .map { case (a, e) => a } firstOption
def getAllUsers(includeRemoved: Boolean = true, includeGroups: Boolean = true)(implicit s: Session): List[Account] = {
Accounts filter { t =>
@@ -199,6 +206,15 @@
def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image)
+ def getAccountExtraMailAddresses(userName: String)(implicit s: Session): List[String] = {
+ AccountExtraMailAddresses.filter(_.userName === userName.bind).map(_.extraMailAddress) list
+ }
+
+ def updateAccountExtraMailAddresses(userName: String, mails: List[String])(implicit s: Session): Unit = {
+ AccountExtraMailAddresses.filter(_.userName === userName.bind).delete
+ mails.map(AccountExtraMailAddresses insert AccountExtraMailAddress(userName, _))
+ }
+
def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
diff --git a/src/main/twirl/gitbucket/core/account/edit.scala.html b/src/main/twirl/gitbucket/core/account/edit.scala.html
index 398c173..25c3dc0 100644
--- a/src/main/twirl/gitbucket/core/account/edit.scala.html
+++ b/src/main/twirl/gitbucket/core/account/edit.scala.html
@@ -1,4 +1,4 @@
-@(account: gitbucket.core.model.Account, info: Option[Any], error: Option[Any])(implicit context: gitbucket.core.controller.Context)
+@(account: gitbucket.core.model.Account, extraMailAddresses: List[String], info: Option[Any], error: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.LDAPUtil
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Edit your profile"){
@@ -31,6 +31,13 @@
+
+