@@ -857,6 +857,7 @@ CREATE TABLE "WEBHOOKS" ( | |||
"URL" VARCHAR(2000) NOT NULL, | |||
"ORGANIZATION_UUID" VARCHAR(40), | |||
"PROJECT_UUID" VARCHAR(40), | |||
"SECRET" VARCHAR(200), | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"UPDATED_AT" BIGINT NOT NULL, | |||
@@ -36,6 +36,12 @@ public class WebhookDto { | |||
@Nullable | |||
private String projectUuid; | |||
/** | |||
* The optional secret used to generate payload signature | |||
*/ | |||
@Nullable | |||
private String secret; | |||
private long createdAt; | |||
private long updatedAt; | |||
@@ -64,6 +70,11 @@ public class WebhookDto { | |||
return this; | |||
} | |||
public WebhookDto setSecret(@Nullable String s) { | |||
this.secret = s; | |||
return this; | |||
} | |||
WebhookDto setCreatedAt(long createdAt) { | |||
this.createdAt = createdAt; | |||
return this; | |||
@@ -96,6 +107,11 @@ public class WebhookDto { | |||
return projectUuid; | |||
} | |||
@Nullable | |||
public String getSecret() { | |||
return secret; | |||
} | |||
public long getCreatedAt() { | |||
return createdAt; | |||
} |
@@ -10,11 +10,11 @@ | |||
url, | |||
organization_uuid as organizationUuid, | |||
project_uuid as projectUuid, | |||
secret, | |||
created_at as createdAt, | |||
updated_at as updatedAt | |||
</sql> | |||
<select id="selectByUuid" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto"> | |||
select <include refid="sqlColumns" /> | |||
from webhooks | |||
@@ -42,6 +42,7 @@ | |||
url, | |||
organization_uuid, | |||
project_uuid, | |||
secret, | |||
created_at, | |||
updated_at | |||
) values ( | |||
@@ -50,6 +51,7 @@ | |||
#{url,jdbcType=VARCHAR}, | |||
#{organizationUuid,jdbcType=VARCHAR}, | |||
#{projectUuid,jdbcType=VARCHAR}, | |||
#{secret,jdbcType=VARCHAR}, | |||
#{createdAt,jdbcType=BIGINT}, | |||
#{updatedAt,jdbcType=BIGINT} | |||
) | |||
@@ -57,10 +59,11 @@ | |||
<update id="update" parameterType="org.sonar.db.webhook.WebhookDto"> | |||
update webhooks set | |||
name=#{name}, | |||
url=#{url}, | |||
updated_at=#{updatedAt, jdbcType=BIGINT} | |||
where uuid=#{uuid, jdbcType=VARCHAR} | |||
name=#{name,jdbcType=VARCHAR}, | |||
url=#{url,jdbcType=VARCHAR}, | |||
secret=#{secret,jdbcType=VARCHAR}, | |||
updated_at=#{updatedAt,jdbcType=BIGINT} | |||
where uuid=#{uuid,jdbcType=VARCHAR} | |||
</update> | |||
<delete id="delete" parameterType="String"> |
@@ -60,12 +60,12 @@ public class WebhookDaoTest { | |||
@Test | |||
public void insert_row_with_organization() { | |||
WebhookDto dto = new WebhookDto() | |||
.setUuid("UUID_1") | |||
.setName("NAME_1") | |||
.setUrl("URL_1") | |||
.setOrganizationUuid("UUID_2"); | |||
.setOrganizationUuid("UUID_2") | |||
.setSecret("a_secret"); | |||
underTest.insert(dbSession, dto); | |||
@@ -76,18 +76,19 @@ public class WebhookDaoTest { | |||
assertThat(stored.getUrl()).isEqualTo(dto.getUrl()); | |||
assertThat(stored.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); | |||
assertThat(stored.getProjectUuid()).isNull(); | |||
assertThat(stored.getSecret()).isEqualTo(dto.getSecret()); | |||
assertThat(new Date(stored.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
assertThat(new Date(stored.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
} | |||
@Test | |||
public void insert_row_with_project() { | |||
WebhookDto dto = new WebhookDto() | |||
.setUuid("UUID_1") | |||
.setName("NAME_1") | |||
.setUrl("URL_1") | |||
.setProjectUuid("UUID_2"); | |||
.setProjectUuid("UUID_2") | |||
.setSecret("a_secret"); | |||
underTest.insert(dbSession, dto); | |||
@@ -98,17 +99,41 @@ public class WebhookDaoTest { | |||
assertThat(reloaded.getUrl()).isEqualTo(dto.getUrl()); | |||
assertThat(reloaded.getOrganizationUuid()).isNull(); | |||
assertThat(reloaded.getProjectUuid()).isEqualTo(dto.getProjectUuid()); | |||
assertThat(reloaded.getSecret()).isEqualTo(dto.getSecret()); | |||
assertThat(new Date(reloaded.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
} | |||
@Test | |||
public void update() { | |||
public void update_with_only_required_fields() { | |||
OrganizationDto organization = organizationDbTester.insert(); | |||
WebhookDto dto = webhookDbTester.insertWebhook(organization); | |||
underTest.update(dbSession, dto | |||
.setName("a-fancy-webhook") | |||
.setUrl("http://www.fancy-webhook.io") | |||
.setSecret(null)); | |||
WebhookDto reloaded = underTest.selectByUuid(dbSession, dto.getUuid()).get(); | |||
assertThat(reloaded.getUuid()).isEqualTo(dto.getUuid()); | |||
assertThat(reloaded.getName()).isEqualTo("a-fancy-webhook"); | |||
assertThat(reloaded.getUrl()).isEqualTo("http://www.fancy-webhook.io"); | |||
assertThat(reloaded.getProjectUuid()).isNull(); | |||
assertThat(reloaded.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); | |||
assertThat(reloaded.getSecret()).isNull(); | |||
assertThat(reloaded.getCreatedAt()).isEqualTo(dto.getCreatedAt()); | |||
assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
} | |||
@Test | |||
public void update_with_all_fields() { | |||
OrganizationDto organization = organizationDbTester.insert(); | |||
WebhookDto dto = webhookDbTester.insertWebhook(organization); | |||
underTest.update(dbSession, dto.setName("a-fancy-webhook").setUrl("http://www.fancy-webhook.io")); | |||
underTest.update(dbSession, dto | |||
.setName("a-fancy-webhook") | |||
.setUrl("http://www.fancy-webhook.io") | |||
.setSecret("a_new_secret")); | |||
WebhookDto reloaded = underTest.selectByUuid(dbSession, dto.getUuid()).get(); | |||
assertThat(reloaded.getUuid()).isEqualTo(dto.getUuid()); | |||
@@ -116,13 +141,13 @@ public class WebhookDaoTest { | |||
assertThat(reloaded.getUrl()).isEqualTo("http://www.fancy-webhook.io"); | |||
assertThat(reloaded.getProjectUuid()).isNull(); | |||
assertThat(reloaded.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); | |||
assertThat(reloaded.getSecret()).isEqualTo("a_new_secret"); | |||
assertThat(reloaded.getCreatedAt()).isEqualTo(dto.getCreatedAt()); | |||
assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); | |||
} | |||
@Test | |||
public void cleanWebhooksOfAProject() { | |||
OrganizationDto organization = organizationDbTester.insert(); | |||
ComponentDto componentDto = componentDbTester.insertPrivateProject(organization); | |||
webhookDbTester.insertWebhook(componentDto); | |||
@@ -138,7 +163,6 @@ public class WebhookDaoTest { | |||
@Test | |||
public void cleanWebhooksOfAnOrganization() { | |||
OrganizationDto organization = organizationDbTester.insert(); | |||
webhookDbTester.insertWebhook(organization); | |||
webhookDbTester.insertWebhook(organization); | |||
@@ -153,9 +177,7 @@ public class WebhookDaoTest { | |||
@Test | |||
public void delete() { | |||
OrganizationDto organization = organizationDbTester.insert(); | |||
WebhookDto dto = webhookDbTester.insertWebhook(organization); | |||
underTest.delete(dbSession, dto.getUuid()); | |||
@@ -164,10 +186,8 @@ public class WebhookDaoTest { | |||
assertThat(reloaded).isEmpty(); | |||
} | |||
@Test | |||
public void fail_if_webhook_does_not_have_an_organization_nor_a_project() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("A webhook can not be created if not linked to an organization or a project."); | |||
@@ -177,12 +197,10 @@ public class WebhookDaoTest { | |||
.setUrl("URL_1"); | |||
underTest.insert(dbSession, dto); | |||
} | |||
@Test | |||
public void fail_if_webhook_have_both_an_organization_nor_a_project() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("A webhook can not be linked to both an organization and a project."); | |||
@@ -194,7 +212,6 @@ public class WebhookDaoTest { | |||
.setProjectUuid("UUID_3"); | |||
underTest.insert(dbSession, dto); | |||
} | |||
private WebhookDto selectByUuid(String uuid) { |
@@ -62,6 +62,7 @@ public class WebhookTesting { | |||
.setUuid(randomAlphanumeric(40)) | |||
.setName(randomAlphanumeric(64)) | |||
.setUrl("https://www.random-site/" + randomAlphanumeric(256)) | |||
.setSecret(randomAlphanumeric(10)) | |||
.setCreatedAt(Calendar.getInstance().getTimeInMillis()); | |||
Arrays.stream(consumers).forEach(consumer -> consumer.accept(res)); | |||
return res; |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.platform.db.migration.version.v78; | |||
import java.sql.SQLException; | |||
import org.sonar.db.Database; | |||
import org.sonar.server.platform.db.migration.SupportsBlueGreen; | |||
import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; | |||
@SupportsBlueGreen | |||
public class AddWebhooksSecret extends DdlChange { | |||
public AddWebhooksSecret(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
context.execute(new AddColumnsBuilder(getDialect(), "webhooks") | |||
.addColumn(newVarcharColumnDefBuilder() | |||
.setColumnName("secret") | |||
.setIsNullable(true) | |||
.setLimit(200) | |||
.build()) | |||
.build()); | |||
} | |||
} |
@@ -28,6 +28,7 @@ public class DbVersion78 implements DbVersion { | |||
public void addSteps(MigrationStepRegistry registry) { | |||
registry | |||
.add(2700, "Drop overall subscriptions on notifications about new and resolved issues", DeleteOverallSubscriptionsOnNewAndResolvedIssuesNotifications.class) | |||
.add(2701, "Add index to org_qprofile.parent_uuid", AddIndexToOrgQProfileParentUuid.class); | |||
.add(2701, "Add index to org_qprofile.parent_uuid", AddIndexToOrgQProfileParentUuid.class) | |||
.add(2702, "Add column webhooks.secret", AddWebhooksSecret.class); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.platform.db.migration.version.v78; | |||
import java.sql.SQLException; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.db.CoreDbTester; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
import static java.sql.Types.VARCHAR; | |||
public class AddWebhooksSecretTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public final CoreDbTester db = CoreDbTester.createForSchema(AddWebhooksSecretTest.class, "webhooks.sql"); | |||
private DdlChange underTest = new AddWebhooksSecret(db.database()); | |||
@Test | |||
public void creates_table_on_empty_db() throws SQLException { | |||
underTest.execute(); | |||
db.assertColumnDefinition("webhooks", "secret", VARCHAR, 200, true); | |||
} | |||
@Test | |||
public void migration_is_not_reentrant() throws SQLException { | |||
underTest.execute(); | |||
expectedException.expect(IllegalStateException.class); | |||
underTest.execute(); | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
CREATE TABLE "WEBHOOKS" ( | |||
"UUID" VARCHAR(40) NOT NULL, | |||
"NAME" VARCHAR(100) NOT NULL, | |||
"URL" VARCHAR(2000) NOT NULL, | |||
"ORGANIZATION_UUID" VARCHAR(40), | |||
"PROJECT_UUID" VARCHAR(40), | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"UPDATED_AT" BIGINT NOT NULL, | |||
CONSTRAINT "PK_WEBHOOKS" PRIMARY KEY ("UUID") | |||
); | |||
CREATE INDEX "ORGANIZATION_WEBHOOK" ON "WEBHOOKS" ("ORGANIZATION_UUID"); | |||
CREATE INDEX "PROJECT_WEBHOOK" ON "WEBHOOKS" ("PROJECT_UUID"); |