diff --git a/src/main/resources/update/3_13.sql b/src/main/resources/update/3_13.sql index 8efe1a3..aa7d006 100644 --- a/src/main/resources/update/3_13.sql +++ b/src/main/resources/update/3_13.sql @@ -1 +1,4 @@ -ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100); \ No newline at end of file +ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100); +ALTER TABLE WEB_HOOK ADD COLUMN CTYPE VARCHAR(10); + +UPDATE WEB_HOOK SET CTYPE = 'form'; \ No newline at end of file diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 7867334..ac54435 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -15,6 +15,7 @@ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.ObjectId +import gitbucket.core.model.WebHookContentType class RepositorySettingsController extends RepositorySettingsControllerBase @@ -49,13 +50,16 @@ )(CollaboratorForm.apply) // for web hook url addition - case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String]) + case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String]) def webHookForm(update:Boolean) = mapping( "url" -> trim(label("url", text(required, webHook(update)))), "events" -> webhookEvents, + "ctype" -> label("ctype", text()), "token" -> optional(trim(label("token", text(maxlength(100))))) - )(WebHookForm.apply) + )( + (url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token) + ) // for transfer ownership case class TransferOwnerShipForm(newOwner: String) @@ -199,7 +203,7 @@ * Display the web hook edit page. */ get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository => - val webhook = WebHook(repository.owner, repository.name, "", None) + val webhook = WebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None) html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true) }) @@ -207,7 +211,7 @@ * Add the web hook URL. */ post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) => - addWebHook(repository.owner, repository.name, form.url, form.events, form.token) + addWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token) flash += "info" -> s"Webhook ${form.url} created" redirect(s"/${repository.owner}/${repository.name}/settings/hooks") }) @@ -237,7 +241,8 @@ val url = params("url") val token = Some(params("token")) - val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token) + val ctype = WebHookContentType.valueOf(params("ctype")) + val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token) val dummyPayload = { val ownerAccount = getAccountByUserName(repository.owner).get val commits = if(repository.commitCount == 0) List.empty else git.log @@ -296,7 +301,7 @@ * Update web hook settings. */ post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) => - updateWebHook(repository.owner, repository.name, form.url, form.events, form.token) + updateWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token) flash += "info" -> s"webhook ${form.url} updated" redirect(s"/${repository.owner}/${repository.name}/settings/hooks") }) diff --git a/src/main/scala/gitbucket/core/model/WebHook.scala b/src/main/scala/gitbucket/core/model/WebHook.scala index 9be55a8..d87f9cb 100644 --- a/src/main/scala/gitbucket/core/model/WebHook.scala +++ b/src/main/scala/gitbucket/core/model/WebHook.scala @@ -3,21 +3,42 @@ trait WebHookComponent extends TemplateComponent { self: Profile => import profile.simple._ + implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code)) + lazy val WebHooks = TableQuery[WebHooks] class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate { val url = column[String]("URL") val token = column[Option[String]]("TOKEN", O.Nullable) - def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply) + val ctype = column[WebHookContentType]("CTYPE", O.NotNull) + def * = (userName, repositoryName, url, ctype, token) <> ((WebHook.apply _).tupled, WebHook.unapply) def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) } } +case class WebHookContentType(val code: String, val ctype: String) + +object WebHookContentType { + object JSON extends WebHookContentType("json", "application/json") + + object FORM extends WebHookContentType("form", "application/x-www-form-urlencoded") + + val values: Vector[WebHookContentType] = Vector(JSON, FORM) + + private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap + + def apply(code: String): WebHookContentType = map(code) + + def valueOf(code: String): WebHookContentType = map(code) + def valueOpt(code: String): Option[WebHookContentType] = map.get(code) +} + case class WebHook( userName: String, repositoryName: String, url: String, + ctype: WebHookContentType, token: Option[String] ) diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index 4af5ec3..1ece4b3 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -1,7 +1,6 @@ package gitbucket.core.service import java.io.ByteArrayInputStream - import fr.brouillard.oss.security.xhub.XHub import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter} import gitbucket.core.api._ @@ -12,7 +11,6 @@ import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.RepositoryName import gitbucket.core.service.RepositoryService.RepositoryInfo - import org.apache.http.NameValuePair import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.message.BasicNameValuePair @@ -22,6 +20,8 @@ import scala.concurrent._ import org.apache.http.HttpRequest import org.apache.http.HttpResponse +import gitbucket.core.model.WebHookContentType +import org.apache.http.client.entity.EntityBuilder trait WebHookService { @@ -52,15 +52,15 @@ .map{ case (w,t) => w -> t.event } .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption - def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = { - WebHooks insert WebHook(owner, repository, url, token) + def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { + WebHooks insert WebHook(owner, repository, url, ctype, token) events.toSet.map{ event: WebHook.Event => WebHookEvents insert WebHookEvent(owner, repository, url, event) } } - def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = { - WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token) + def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { + WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token)) WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete events.toSet.map{ event: WebHook.Event => WebHookEvents insert WebHookEvent(owner, repository, url, event) @@ -100,19 +100,29 @@ val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build logger.debug(s"start web hook invocation for ${webHook.url}") val httpPost = new HttpPost(webHook.url) - httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded") + logger.info(s"Content-Type: ${webHook.ctype.ctype}") + httpPost.addHeader("Content-Type", webHook.ctype.ctype) httpPost.addHeader("X-Github-Event", event.name) httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString) - val params: java.util.List[NameValuePair] = new java.util.ArrayList() - params.add(new BasicNameValuePair("payload", json)) - def postContent = new UrlEncodedFormEntity(params, "UTF-8") - httpPost.setEntity(postContent) - - if (!webHook.token.isEmpty) { - // TODO find a better way and see how to extract content from postContent - val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8") - httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes)) + webHook.ctype match { + case WebHookContentType.FORM => { + val params: java.util.List[NameValuePair] = new java.util.ArrayList() + params.add(new BasicNameValuePair("payload", json)) + def postContent = new UrlEncodedFormEntity(params, "UTF-8") + httpPost.setEntity(postContent) + if (!webHook.token.isEmpty) { + // TODO find a better way and see how to extract content from postContent + val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8") + httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes)) + } + } + case WebHookContentType.JSON => { + httpPost.setEntity(EntityBuilder.create().setText(json).build()) + if (!webHook.token.isEmpty) { + httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8"))) + } + } } val res = httpClient.execute(httpPost) diff --git a/src/main/twirl/gitbucket/core/settings/edithooks.scala.html b/src/main/twirl/gitbucket/core/settings/edithooks.scala.html index fc4f530..19e447b 100644 --- a/src/main/twirl/gitbucket/core/settings/edithooks.scala.html +++ b/src/main/twirl/gitbucket/core/settings/edithooks.scala.html @@ -6,6 +6,7 @@ @import context._ @import gitbucket.core.view.helpers._ @import gitbucket.core.model.WebHook._ +@import gitbucket.core.model.WebHookContentType @check(name: String, event: Event)={ name="@(name).@event.name" value="on" @if(events(event)){checked} } @@ -31,6 +32,14 @@
+ +
+ +
+
@@ -129,6 +138,7 @@ e.preventDefault(); var url = this.form.url.value; var token = this.form.token.value; + var ctype = this.form.ctype.value; if(!/^https?:\/\/.+/.test(url)){ alert("invalid url"); return; @@ -138,7 +148,7 @@ $("#test-report").hide(); $.ajax({ method:'POST', - url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=' + encodeURIComponent(token), + url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=' + encodeURIComponent(token) + '&ctype=' + encodeURIComponent(ctype), success: function(e){ //console.log(e); $('#test-report-tab a:first').tab('show'); diff --git a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala index 09b1e31..c4c2c7d 100644 --- a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala @@ -2,6 +2,7 @@ import gitbucket.core.model.WebHook import org.scalatest.FunSuite +import gitbucket.core.model.WebHookContentType class WebHookServiceSpec extends FunSuite with ServiceSpecBase { @@ -16,12 +17,12 @@ val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root") val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root") generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2") - service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest), Some("key")) + service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) + service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) + service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) + service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) + service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) + service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key")) assert(service.getPullRequestsByRequestForWebhook("user1","repo1","master1") == Map.empty) @@ -43,33 +44,36 @@ test("add and get and update and delete") { withTestDB { implicit session => val user1 = generateNewUserWithDBRepository("user1","repo1") - service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), Some("key")) - assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest)))) - assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest)))) - assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", Some("key"))))) + val formType = WebHookContentType.FORM + val jsonType = WebHookContentType.JSON + service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), formType, Some("key")) + assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest)))) + assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest)))) + assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", formType, Some("key"))))) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil) assert(service.getWebHook("user1", "repo1", "http://example.com2") == None) assert(service.getWebHook("user2", "repo1", "http://example.com") == None) assert(service.getWebHook("user1", "repo2", "http://example.com") == None) - service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), Some("key")) - assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.Push, WebHook.Issues)))) + service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), jsonType, Some("key")) + assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", jsonType, Some("key")),Set(WebHook.Push, WebHook.Issues)))) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil) - assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", Some("key"))))) + assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", jsonType, Some("key"))))) service.deleteWebHook("user1", "repo1", "http://example.com") assert(service.getWebHook("user1", "repo1", "http://example.com") == None) } } test("getWebHooks, getWebHooksByEvent") { withTestDB { implicit session => val user1 = generateNewUserWithDBRepository("user1","repo1") - service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest), Some("key")) - service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), Some("key")) - service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), Some("key")) + val ctype = WebHookContentType.FORM + service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest), ctype, Some("key")) + service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), ctype, Some("key")) + service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), ctype, Some("key")) assert(service.getWebHooks("user1", "repo1") == List( - WebHook("user1","repo1","http://example.com/1", Some("key"))->Set(WebHook.PullRequest), - WebHook("user1","repo1","http://example.com/2", Some("key"))->Set(WebHook.Push), - WebHook("user1","repo1","http://example.com/3", Some("key"))->Set(WebHook.PullRequest,WebHook.Push))) + WebHook("user1","repo1","http://example.com/1", ctype, Some("key"))->Set(WebHook.PullRequest), + WebHook("user1","repo1","http://example.com/2", ctype, Some("key"))->Set(WebHook.Push), + WebHook("user1","repo1","http://example.com/3", ctype, Some("key"))->Set(WebHook.PullRequest,WebHook.Push))) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List( - WebHook("user1","repo1","http://example.com/1", Some("key")), - WebHook("user1","repo1","http://example.com/3", Some("key")))) + WebHook("user1","repo1","http://example.com/1", ctype, Some("key")), + WebHook("user1","repo1","http://example.com/3", ctype, Some("key")))) } } } \ No newline at end of file