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 @@ +
+ Additional Mail Address: + @extraMailAddresses.zipWithIndex.map { case (mail, idx) => + + + } +
@@ -62,6 +69,8 @@ } } diff --git a/src/main/twirl/gitbucket/core/admin/user.scala.html b/src/main/twirl/gitbucket/core/admin/user.scala.html index 7603415..ae791bb 100644 --- a/src/main/twirl/gitbucket/core/admin/user.scala.html +++ b/src/main/twirl/gitbucket/core/admin/user.scala.html @@ -1,4 +1,4 @@ -@(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context) +@(account: Option[gitbucket.core.model.Account], extraMailAddresses: List[String], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context) @gitbucket.core.html.main(if(account.isEmpty) "New user" else "Update user"){ @gitbucket.core.admin.html.menu("users"){ @gitbucket.core.helper.html.error(error) @@ -50,6 +50,13 @@
+
+ Additional Mail Address: + @extraMailAddresses.zipWithIndex.map { case (mail, idx) => + + + } +