diff --git a/build.sbt b/build.sbt index 77a683d..7186285 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ val Organization = "io.github.gitbucket" val Name = "gitbucket" val GitBucketVersion = "4.19.0-SNAPSHOT" -val ScalatraVersion = "2.5.3" +val ScalatraVersion = "2.6.0-SNAPSHOT" val JettyVersion = "9.4.7.v20170914" lazy val root = (project in file(".")).enablePlugins(SbtTwirl, ScalatraPlugin, JRebelPlugin).settings( @@ -30,8 +30,8 @@ "org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.9.0.201710071750-r", "org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion, + "org.scalatra" %% "scalatra-forms" % ScalatraVersion, "org.json4s" %% "json4s-jackson" % "3.5.1", - "io.github.gitbucket" %% "scalatra-forms" % "1.1.0", "commons-io" % "commons-io" % "2.5", "io.github.gitbucket" % "solidbase" % "1.0.2", "io.github.gitbucket" % "markedj" % "1.0.15", diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index d0b22db..f2959ca 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -12,10 +12,10 @@ import gitbucket.core.util.Implicits._ import gitbucket.core.util.StringUtil._ import gitbucket.core.util._ -import io.github.gitbucket.scalatra.forms._ import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages import org.scalatra.BadRequest +import org.scalatra.forms._ class AccountController extends AccountControllerBase with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService @@ -137,16 +137,17 @@ } private def accountWebhookEvents = new ValueType[Set[WebHook.Event]]{ - def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = { + def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { WebHook.Event.values.flatMap { t => params.get(name + "." + t.name).map(_ => t) }.toSet } - def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){ - Seq(name -> messages("error.required").format(name)) - } else { - Nil - } + def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = + if(convert(name, params, messages).isEmpty){ + Seq(name -> messages("error.required").format(name)) + } else { + Nil + } } @@ -635,9 +636,9 @@ } private def uniqueRepository: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = params.get("owner").flatMap { userName => - getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.") + getRepositoryNamesOfUser(userName.head).find(_ == value).map(_ => "Repository already exists.") } } diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index 928e5f6..4948c43 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -9,12 +9,11 @@ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ import gitbucket.core.util._ -import gitbucket.core.util.JGitUtil._ -import io.github.gitbucket.scalatra.forms._ import org.json4s._ import org.scalatra._ import org.scalatra.i18n._ import org.scalatra.json._ +import org.scalatra.forms._ import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import javax.servlet.{FilterChain, ServletRequest, ServletResponse} @@ -32,7 +31,7 @@ * Provides generic features for controller implementations. */ abstract class ControllerBase extends ScalatraFilter - with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations + with ValidationSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations with SystemSettingsService { private val logger = LoggerFactory.getLogger(getClass) @@ -177,7 +176,7 @@ protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T](){ def convert(value: String, messages: Messages): T = valueType.convert(trim(value), messages) - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = valueType.validate(name, trim(value), params, messages) private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim @@ -315,7 +314,7 @@ } protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = getAccountByMailAddress(value, true) .filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) } .map { _ => "Mail address is already registered." } diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index 7bf8408..6a45d9d 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -1,7 +1,6 @@ package gitbucket.core.controller import gitbucket.core.model.Account -import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.servlet.Database import gitbucket.core.util._ diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index a4cf49f..9209ab4 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -6,7 +6,7 @@ import gitbucket.core.util.Implicits._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, UsersAuthenticator} -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.scalatra.Ok diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index e24cc55..4472863 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -8,7 +8,7 @@ import gitbucket.core.util._ import gitbucket.core.view import gitbucket.core.view.Markdown -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.scalatra.{BadRequest, Ok} diff --git a/src/main/scala/gitbucket/core/controller/LabelsController.scala b/src/main/scala/gitbucket/core/controller/LabelsController.scala index 08c0aaa..d0d82d4 100644 --- a/src/main/scala/gitbucket/core/controller/LabelsController.scala +++ b/src/main/scala/gitbucket/core/controller/LabelsController.scala @@ -4,7 +4,7 @@ import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.Implicits._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.scalatra.i18n.Messages import org.scalatra.Ok @@ -82,11 +82,11 @@ } private def uniqueLabelName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = { - val owner = params("owner") - val repository = params("repository") + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { + val owner = params("owner").head + val repository = params("repository").head params.get("labelId").map { labelId => - getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.") + getLabel(owner, repository, value).filter(_.labelId != labelId.head.toInt).map(_ => "Name has already been taken.") }.getOrElse { getLabel(owner, repository, value).map(_ => "Name has already been taken.") } diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala index de81c73..b373b1b 100644 --- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala +++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala @@ -4,7 +4,7 @@ import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.Implicits._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ class MilestonesController extends MilestonesControllerBase with MilestonesService with RepositoryService with AccountService diff --git a/src/main/scala/gitbucket/core/controller/PrioritiesController.scala b/src/main/scala/gitbucket/core/controller/PrioritiesController.scala index e0e010a..340065f 100644 --- a/src/main/scala/gitbucket/core/controller/PrioritiesController.scala +++ b/src/main/scala/gitbucket/core/controller/PrioritiesController.scala @@ -4,7 +4,7 @@ import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, PrioritiesService} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.Implicits._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.scalatra.i18n.Messages import org.scalatra.Ok @@ -98,11 +98,11 @@ } private def uniquePriorityName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = { - val owner = params("owner") - val repository = params("repository") + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { + val owner = params("owner").head + val repository = params("repository").head params.get("priorityId").map { priorityId => - getPriority(owner, repository, value).filter(_.priorityId != priorityId.toInt).map(_ => "Name has already been taken.") + getPriority(owner, repository, value).filter(_.priorityId != priorityId.head.toInt).map(_ => "Name has already been taken.") }.getOrElse { getPriority(owner, repository, value).map(_ => "Name has already been taken.") } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 839da3f..e8c7208 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -13,7 +13,7 @@ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ import gitbucket.core.util._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.PersonIdent diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index a61776b..df8a0a9 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -9,7 +9,7 @@ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Directory._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages import org.eclipse.jgit.api.Git @@ -425,12 +425,12 @@ } private def webhookEvents = new ValueType[Set[WebHook.Event]]{ - def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = { + def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { WebHook.Event.values.flatMap { t => params.get(name + "." + t.name).map(_ => t) }.toSet } - def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){ + def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){ Seq(name -> messages("error.required").format(name)) } else { Nil @@ -456,10 +456,10 @@ * Duplicate check for the rename repository name. */ private def renameRepositoryName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = params.get("repository").filter(_ != value).flatMap { _ => params.get("owner").flatMap { userName => - getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.") + getRepositoryNamesOfUser(userName.head).find(_ == value).map(_ => "Repository already exists.") } } } @@ -468,7 +468,7 @@ * */ private def featureOption: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = if(Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") } diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index bf529cf..c425064 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -17,7 +17,7 @@ import gitbucket.core.service.WebHookService._ import gitbucket.core.view import gitbucket.core.view.helpers -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.apache.commons.io.FileUtils import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 1485d56..e306017 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -12,7 +12,7 @@ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ import gitbucket.core.util.StringUtil._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.apache.commons.io.{FileUtils, IOUtils} import org.scalatra.i18n.Messages import com.github.zafarkhaja.semver.{Version => Semver} diff --git a/src/main/scala/gitbucket/core/controller/ValidationSupport.scala b/src/main/scala/gitbucket/core/controller/ValidationSupport.scala new file mode 100644 index 0000000..9c3e02b --- /dev/null +++ b/src/main/scala/gitbucket/core/controller/ValidationSupport.scala @@ -0,0 +1,91 @@ +package gitbucket.core.controller + +import org.json4s.{JField, JObject, JString} +import org.scalatra._ +import org.scalatra.json._ +import org.scalatra.forms._ +import org.scalatra.i18n.I18nSupport +import org.scalatra.servlet.ServletBase + +/** + * Extends scalatra-forms to support the client-side validation and Ajax requests as well. + */ +trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJsonSupport with I18nSupport => + + def get[T](path: String, form: ValueType[T])(action: T => Any): Route = { + registerValidate(path, form) + get(path){ + validate(form)(errors => BadRequest(), form => action(form)) + } + } + + def post[T](path: String, form: ValueType[T])(action: T => Any): Route = { + registerValidate(path, form) + post(path){ + validate(form)(errors => BadRequest(), form => action(form)) + } + } + + def put[T](path: String, form: ValueType[T])(action: T => Any): Route = { + registerValidate(path, form) + put(path){ + validate(form)(errors => BadRequest(), form => action(form)) + } + } + + def delete[T](path: String, form: ValueType[T])(action: T => Any): Route = { + registerValidate(path, form) + delete(path){ + validate(form)(errors => BadRequest(), form => action(form)) + } + } + + def ajaxGet[T](path: String, form: ValueType[T])(action: T => Any): Route = { + get(path){ + validate(form)(errors => ajaxError(errors), form => action(form)) + } + } + + def ajaxPost[T](path: String, form: ValueType[T])(action: T => Any): Route = { + post(path){ + validate(form)(errors => ajaxError(errors), form => action(form)) + } + } + + def ajaxDelete[T](path: String, form: ValueType[T])(action: T => Any): Route = { + delete(path){ + validate(form)(errors => ajaxError(errors), form => action(form)) + } + } + + def ajaxPut[T](path: String, form: ValueType[T])(action: T => Any): Route = { + put(path){ + validate(form)(errors => ajaxError(errors), form => action(form)) + } + } + + private def registerValidate[T](path: String, form: ValueType[T]) = { + post(path.replaceFirst("/$", "") + "/validate"){ + contentType = "application/json" + toJson(form.validate("", multiParams, messages)) + } + } + + /** + * Responds errors for ajax requests. + */ + private def ajaxError(errors: Seq[(String, String)]): JObject = { + status = 400 + contentType = "application/json" + toJson(errors) + } + + /** + * Converts errors to JSON. + */ + private def toJson(errors: Seq[(String, String)]): JObject = + JObject(errors.map { case (key, value) => + JField(key, JString(value)) + }.toList) + +} diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index f034602..b212b81 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -10,7 +10,7 @@ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Directory._ -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.eclipse.jgit.api.Git import org.scalatra.i18n.Messages @@ -226,8 +226,8 @@ }) private def unique: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = - getWikiPageList(params("owner"), params("repository")).find(_ == value).map(_ => "Page already exists.") + override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = + getWikiPageList(params("owner").head, params("repository").head).find(_ == value).map(_ => "Page already exists.") } private def pagename: Constraint = new Constraint(){ diff --git a/src/main/scala/gitbucket/core/util/Validations.scala b/src/main/scala/gitbucket/core/util/Validations.scala index 6cd693c..be580d7 100644 --- a/src/main/scala/gitbucket/core/util/Validations.scala +++ b/src/main/scala/gitbucket/core/util/Validations.scala @@ -1,6 +1,6 @@ package gitbucket.core.util -import io.github.gitbucket.scalatra.forms._ +import org.scalatra.forms._ import org.scalatra.i18n.Messages trait Validations {