From 29b7cf350cd0235475e1c09182ab905e4303b442 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 10 Nov 2021 17:43:31 +0100 Subject: [PATCH] SONAR-13426 add api/project_badges/token endpoint --- .../java/org/sonar/db/version/SqTables.java | 1 + .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 6 ++ .../src/main/java/org/sonar/db/MyBatis.java | 6 +- .../org/sonar/db/audit/AuditPersister.java | 5 +- .../sonar/db/audit/NoOpAuditPersister.java | 6 ++ .../model/ProjectBadgeTokenNewValue.java | 43 ++++++++ .../db/project/ProjectBadgeTokenDao.java | 61 +++++++++++ .../db/project/ProjectBadgeTokenDto.java | 86 +++++++++++++++ .../db/project/ProjectBadgeTokenMapper.java | 31 ++++++ .../db/project/ProjectBadgeTokenMapper.xml | 38 +++++++ .../db/project/ProjectBadgeTokenDaoTest.java | 92 ++++++++++++++++ .../badge/ws/ProjectBadgesWsModule.java | 1 + .../sonar/server/badge/ws/TokenAction.java | 96 +++++++++++++++++ .../sonar/server/badge/ws/token-example.json | 1 + .../badge/ws/ProjectBadgesWsModuleTest.java | 7 +- .../server/badge/ws/TokenActionTest.java | 101 ++++++++++++++++++ .../protobuf/ws-project_badge_token.proto | 13 +++ 18 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/ProjectBadgeTokenNewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenAction.java create mode 100644 server/sonar-webserver-webapi/src/main/resources/org/sonar/server/badge/ws/token-example.json create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenActionTest.java create mode 100644 sonar-ws/src/main/protobuf/ws-project_badge_token.proto diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 15c4ff52275..33e58ca8716 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -78,6 +78,7 @@ public final class SqTables { "portfolio_references", "projects", "project_alm_settings", + "project_badge_token", "project_branches", "project_links", "project_mappings", diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index ea48041aed2..cc088c10d12 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -60,6 +60,7 @@ import org.sonar.db.permission.template.PermissionTemplateDao; import org.sonar.db.plugin.PluginDao; import org.sonar.db.portfolio.PortfolioDao; import org.sonar.db.project.ProjectDao; +import org.sonar.db.project.ProjectBadgeTokenDao; import org.sonar.db.property.InternalComponentPropertiesDao; import org.sonar.db.property.InternalPropertiesDao; import org.sonar.db.property.PropertiesDao; @@ -137,6 +138,7 @@ public class DaoModule extends Module { PermissionTemplateDao.class, PluginDao.class, ProjectDao.class, + ProjectBadgeTokenDao.class, PortfolioDao.class, ProjectLinkDao.class, ProjectMappingsDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 99fa1daa6fa..32fb4f65628 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -58,6 +58,7 @@ import org.sonar.db.permission.template.PermissionTemplateDao; import org.sonar.db.plugin.PluginDao; import org.sonar.db.portfolio.PortfolioDao; import org.sonar.db.project.ProjectDao; +import org.sonar.db.project.ProjectBadgeTokenDao; import org.sonar.db.property.InternalComponentPropertiesDao; import org.sonar.db.property.InternalPropertiesDao; import org.sonar.db.property.PropertiesDao; @@ -166,6 +167,7 @@ public class DbClient { private final SamlMessageIdDao samlMessageIdDao; private final UserDismissedMessagesDao userDismissedMessagesDao; private final ApplicationProjectsDao applicationProjectsDao; + private final ProjectBadgeTokenDao projectBadgeTokenDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -240,6 +242,7 @@ public class DbClient { internalComponentPropertiesDao = getDao(map, InternalComponentPropertiesDao.class); newCodePeriodDao = getDao(map, NewCodePeriodDao.class); projectDao = getDao(map, ProjectDao.class); + projectBadgeTokenDao = getDao(map, ProjectBadgeTokenDao.class); portfolioDao = getDao(map, PortfolioDao.class); sessionTokensDao = getDao(map, SessionTokensDao.class); samlMessageIdDao = getDao(map, SamlMessageIdDao.class); @@ -541,4 +544,7 @@ public class DbClient { return userDismissedMessagesDao; } + public ProjectBadgeTokenDao projectBadgeTokenDao() { + return projectBadgeTokenDao; + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 298be87c731..4db01625cce 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -100,6 +100,8 @@ import org.sonar.db.portfolio.PortfolioDto; import org.sonar.db.portfolio.PortfolioMapper; import org.sonar.db.portfolio.PortfolioProjectDto; import org.sonar.db.portfolio.PortfolioReferenceDto; +import org.sonar.db.project.ProjectBadgeTokenDto; +import org.sonar.db.project.ProjectBadgeTokenMapper; import org.sonar.db.project.ProjectDto; import org.sonar.db.project.ProjectExportMapper; import org.sonar.db.project.ProjectMapper; @@ -117,8 +119,8 @@ import org.sonar.db.qualitygate.QualityGateConditionDto; import org.sonar.db.qualitygate.QualityGateConditionMapper; import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.db.qualitygate.QualityGateGroupPermissionsMapper; -import org.sonar.db.qualitygate.QualityGateUserPermissionsMapper; import org.sonar.db.qualitygate.QualityGateMapper; +import org.sonar.db.qualitygate.QualityGateUserPermissionsMapper; import org.sonar.db.qualityprofile.ActiveRuleDto; import org.sonar.db.qualityprofile.ActiveRuleMapper; import org.sonar.db.qualityprofile.ActiveRuleParamDto; @@ -206,6 +208,7 @@ public class MyBatis implements Startable { confBuilder.loadAlias("PrIssue", PrIssueDto.class); confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class); confBuilder.loadAlias("Project", ProjectDto.class); + confBuilder.loadAlias("ProjectBadgeToken", ProjectBadgeTokenDto.class); confBuilder.loadAlias("ProjectCountPerAnalysisPropertyValue", ProjectCountPerAnalysisPropertyValue.class); confBuilder.loadAlias("ProjectMapping", ProjectMappingDto.class); confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class); @@ -271,6 +274,7 @@ public class MyBatis implements Startable { ProjectAlmSettingMapper.class, ProjectLinkMapper.class, ProjectMapper.class, + ProjectBadgeTokenMapper.class, ProjectExportMapper.class, ProjectMappingsMapper.class, ProjectQgateAssociationMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java index 60987dbf424..ac41ffe6e36 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java @@ -21,15 +21,16 @@ package org.sonar.db.audit; import org.sonar.core.extension.PlatformLevel; import org.sonar.db.DbSession; +import org.sonar.db.audit.model.AbstractEditorNewValue; import org.sonar.db.audit.model.ComponentKeyNewValue; import org.sonar.db.audit.model.ComponentNewValue; import org.sonar.db.audit.model.DevOpsPlatformSettingNewValue; -import org.sonar.db.audit.model.AbstractEditorNewValue; import org.sonar.db.audit.model.GroupPermissionNewValue; import org.sonar.db.audit.model.LicenseNewValue; import org.sonar.db.audit.model.PermissionTemplateNewValue; import org.sonar.db.audit.model.PersonalAccessTokenNewValue; import org.sonar.db.audit.model.PluginNewValue; +import org.sonar.db.audit.model.ProjectBadgeTokenNewValue; import org.sonar.db.audit.model.PropertyNewValue; import org.sonar.db.audit.model.SecretNewValue; import org.sonar.db.audit.model.UserGroupNewValue; @@ -71,6 +72,8 @@ public interface AuditPersister { void addUserToken(DbSession dbSession, UserTokenNewValue newValue); + void addProjectBadgeToken(DbSession dbSession, ProjectBadgeTokenNewValue newValue); + void updateUserToken(DbSession dbSession, UserTokenNewValue newValue); void deleteUserToken(DbSession dbSession, UserTokenNewValue newValue); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/NoOpAuditPersister.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/NoOpAuditPersister.java index 2de7874e9f8..1923b0cca13 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/NoOpAuditPersister.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/NoOpAuditPersister.java @@ -29,6 +29,7 @@ import org.sonar.db.audit.model.LicenseNewValue; import org.sonar.db.audit.model.PermissionTemplateNewValue; import org.sonar.db.audit.model.PersonalAccessTokenNewValue; import org.sonar.db.audit.model.PluginNewValue; +import org.sonar.db.audit.model.ProjectBadgeTokenNewValue; import org.sonar.db.audit.model.PropertyNewValue; import org.sonar.db.audit.model.SecretNewValue; import org.sonar.db.audit.model.UserGroupNewValue; @@ -113,6 +114,11 @@ public class NoOpAuditPersister implements AuditPersister { // no op } + @Override + public void addProjectBadgeToken(DbSession dbSession, ProjectBadgeTokenNewValue newValue) { + // no op + } + @Override public void updateUserToken(DbSession dbSession, UserTokenNewValue newValue) { // no op diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/ProjectBadgeTokenNewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/ProjectBadgeTokenNewValue.java new file mode 100644 index 00000000000..9d53395b7a7 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/ProjectBadgeTokenNewValue.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +public class ProjectBadgeTokenNewValue extends NewValue { + + private final String projectKey; + private final String userUuid; + private final String userLogin; + + public ProjectBadgeTokenNewValue(String projectKey, String userUuid, String userLogin) { + this.projectKey = projectKey; + this.userUuid = userUuid; + this.userLogin = userLogin; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + addField(sb, "\"projectKey\": ", this.projectKey, true); + addField(sb, "\"userUuid\": ", this.userUuid, true); + addField(sb, "\"userLogin\": ", this.userLogin, true); + endString(sb); + return sb.toString(); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDao.java new file mode 100644 index 00000000000..8c9758f1845 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDao.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.project; + +import javax.annotation.CheckForNull; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.ProjectBadgeTokenNewValue; + +public class ProjectBadgeTokenDao implements Dao { + private final System2 system2; + private final AuditPersister auditPersister; + private final UuidFactory uuidFactory; + + public ProjectBadgeTokenDao(System2 system2, AuditPersister auditPersister, UuidFactory uuidFactory) { + this.system2 = system2; + this.auditPersister = auditPersister; + this.uuidFactory = uuidFactory; + } + + public ProjectBadgeTokenDto insert(DbSession session, String token, ProjectDto projectDto, + String userUuid, String userLogin) { + ProjectBadgeTokenDto projectBadgeTokenDto = new ProjectBadgeTokenDto(uuidFactory.create(), token, + projectDto.getUuid(), system2.now(), system2.now()); + + auditPersister.addProjectBadgeToken(session, new ProjectBadgeTokenNewValue(projectDto.getKey(), userUuid, userLogin)); + + mapper(session).insert(projectBadgeTokenDto); + return projectBadgeTokenDto; + } + + private static ProjectBadgeTokenMapper mapper(DbSession session) { + return session.getMapper(ProjectBadgeTokenMapper.class); + } + + @CheckForNull + public ProjectBadgeTokenDto selectTokenByProject(DbSession session, ProjectDto projectDto) { + return mapper(session).selectTokenByProjectUuid(projectDto.getUuid()); + + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDto.java new file mode 100644 index 00000000000..1611ab037c4 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDto.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.project; + +public class ProjectBadgeTokenDto { + + private String uuid; + private String token; + private String projectUuid; + private long createdAt; + private long updatedAt; + + public ProjectBadgeTokenDto() { + // to keep for mybatis + } + + public ProjectBadgeTokenDto(String uuid, String token, String projectUuid, long createdAt, long updatedAt) { + this.uuid = uuid; + this.token = token; + this.projectUuid = projectUuid; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public String getUuid() { + return uuid; + } + + public ProjectBadgeTokenDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getToken() { + return token; + } + + public ProjectBadgeTokenDto setToken(String token) { + this.token = token; + return this; + } + + public String getProjectUuid() { + return projectUuid; + } + + public ProjectBadgeTokenDto setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + return this; + } + + public long getCreatedAt() { + return createdAt; + } + + public ProjectBadgeTokenDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + + public long getUpdatedAt() { + return updatedAt; + } + + public ProjectBadgeTokenDto setUpdatedAt(long updatedAt) { + this.updatedAt = updatedAt; + return this; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java new file mode 100644 index 00000000000..3f8cda7408c --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.project; + +import javax.annotation.CheckForNull; +import org.apache.ibatis.annotations.Param; + +public interface ProjectBadgeTokenMapper { + + void insert(ProjectBadgeTokenDto projectBadgeTokenDto); + + @CheckForNull + ProjectBadgeTokenDto selectTokenByProjectUuid(@Param("projectUuid") String projectUuid); +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml new file mode 100644 index 00000000000..59967a17741 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml @@ -0,0 +1,38 @@ + + + + + + p.uuid as uuid, + p.token as token, + p.project_uuid as projectUuid, + p.created_at as createdAt, + p.updated_at as updatedAt + + + + INSERT INTO project_badge_token ( + uuid, + token, + project_uuid, + created_at, + updated_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{token,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT}, + #{updatedAt,jdbcType=BIGINT} + ) + + + + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java new file mode 100644 index 00000000000..6f99c2cdb13 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.project; + +import javax.annotation.Nullable; +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.ProjectBadgeTokenNewValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ProjectBadgeTokenDaoTest { + + private final System2 system2 = new TestSystem2().setNow(1000L); + + @Rule + public DbTester db = DbTester.create(system2); + + private final AuditPersister auditPersister = spy(AuditPersister.class); + private final UuidFactory uuidFactory = mock(UuidFactory.class); + + private final ProjectBadgeTokenDao projectBadgeTokenDao = new ProjectBadgeTokenDao(system2, auditPersister, uuidFactory); + + + @Test + public void should_insert_and_select_by_project_uuid() { + when(uuidFactory.create()).thenReturn("generated_uuid_1"); + ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1"); + + ProjectBadgeTokenDto insertedProjectBadgeToken = projectBadgeTokenDao.insert(db.getSession(), "token", projectDto, "userUuid", "userLogin"); + assertProjectBadgeToken(insertedProjectBadgeToken); + + ProjectBadgeTokenDto selectedProjectBadgeToken = projectBadgeTokenDao.selectTokenByProject(db.getSession(), projectDto); + assertProjectBadgeToken(selectedProjectBadgeToken); + } + + @Test + public void token_insertion_is_log_in_audit() { + when(uuidFactory.create()).thenReturn("generated_uuid_1"); + ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1"); + + ProjectBadgeTokenDto insertedProjectBadgeToken = projectBadgeTokenDao.insert(db.getSession(), "token", projectDto, "user-uuid", "user-login"); + assertProjectBadgeToken(insertedProjectBadgeToken); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ProjectBadgeTokenNewValue.class); + + verify(auditPersister).addProjectBadgeToken(eq(db.getSession()), captor.capture()); + verifyNoMoreInteractions(auditPersister); + + Assertions.assertThat(captor.getValue()).hasToString("{\"userUuid\": \"user-uuid\", \"userLogin\": \"user-login\" }"); + } + + private void assertProjectBadgeToken(@Nullable ProjectBadgeTokenDto projectBadgeTokenDto) { + assertThat(projectBadgeTokenDto).isNotNull(); + assertThat(projectBadgeTokenDto.getToken()).isEqualTo("token"); + assertThat(projectBadgeTokenDto.getProjectUuid()).isEqualTo("project_uuid_1"); + assertThat(projectBadgeTokenDto.getUuid()).isEqualTo("generated_uuid_1"); + assertThat(projectBadgeTokenDto.getCreatedAt()).isEqualTo(1000L); + assertThat(projectBadgeTokenDto.getCreatedAt()).isEqualTo(1000L); + } + +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWsModule.java index eb86931bda3..bb695a08db8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWsModule.java @@ -29,6 +29,7 @@ public class ProjectBadgesWsModule extends Module { ProjectBadgesWs.class, QualityGateAction.class, MeasureAction.class, + TokenAction.class, SvgGenerator.class, ProjectBadgesSupport.class); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenAction.java new file mode 100644 index 00000000000..51b8adac08d --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenAction.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.badge.ws; + +import com.google.common.io.Resources; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.NewAction; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.project.ProjectBadgeTokenDto; +import org.sonar.server.user.UserSession; +import org.sonar.server.usertoken.TokenGenerator; +import org.sonarqube.ws.ProjectBadgeToken.TokenWsResponse; + +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class TokenAction implements ProjectBadgesWsAction { + + private static final String PROJECT_KEY_PARAM = "project"; + private final DbClient dbClient; + private final TokenGenerator tokenGenerator; + private final UserSession userSession; + + public TokenAction(DbClient dbClient, TokenGenerator tokenGenerator, UserSession userSession) { + this.dbClient = dbClient; + this.tokenGenerator = tokenGenerator; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController controller) { + NewAction action = controller.createAction("token") + .setHandler(this) + .setSince("9.2") + .setDescription("Retrieve a token to use for project badge access for private projects.
" + + "This token can be used to authenticate with api/project_badges/quality_gate and api/project_badges/measure endpoints.
" + + "Requires 'Browse' permission on the specified project.") + .setResponseExample(Resources.getResource(getClass(), "token-example.json")); + action.createParam(PROJECT_KEY_PARAM) + .setDescription("Project or application key") + .setRequired(true) + .setExampleValue(KEY_PROJECT_EXAMPLE_001); + } + + @Override + public void handle(Request request, Response response) throws Exception { + TokenWsResponse tokenWsResponse = doHandle(request); + writeProtobuf(tokenWsResponse, request, response); + } + + private TokenWsResponse doHandle(Request request) { + try (DbSession dbSession = dbClient.openSession(false)) { + String projectKey = request.mandatoryParam(PROJECT_KEY_PARAM); + + ProjectDto projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projectKey).orElseThrow(() -> new IllegalArgumentException("project not found")); + userSession.checkProjectPermission(UserRole.USER, projectDto); + ProjectBadgeTokenDto projectBadgeTokenDto = dbClient.projectBadgeTokenDao().selectTokenByProject(dbSession, projectDto); + + if (projectBadgeTokenDto == null) { + projectBadgeTokenDto = dbClient.projectBadgeTokenDao().insert(dbSession, tokenGenerator.generate(), projectDto, userSession.getUuid(), userSession.getLogin()); + dbSession.commit(); + } + + return buildResponse(projectBadgeTokenDto); + } + } + + private static TokenWsResponse buildResponse(ProjectBadgeTokenDto projectBadgeTokenDto) { + return TokenWsResponse.newBuilder() + .setToken(projectBadgeTokenDto.getToken()) + .build(); + } + +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/badge/ws/token-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/badge/ws/token-example.json new file mode 100644 index 00000000000..677556816b2 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/badge/ws/token-example.json @@ -0,0 +1 @@ +{"token": "XXXXXXXXXXXXXXX"} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/ProjectBadgesWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/ProjectBadgesWsModuleTest.java index d2d564e59ac..ed470ad73f6 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/ProjectBadgesWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/ProjectBadgesWsModuleTest.java @@ -23,18 +23,17 @@ import org.junit.Test; import org.sonar.core.platform.ComponentContainer; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; public class ProjectBadgesWsModuleTest { - private ComponentContainer container = new ComponentContainer(); - private ProjectBadgesWsModule underTest = new ProjectBadgesWsModule(); + private final ComponentContainer container = new ComponentContainer(); + private final ProjectBadgesWsModule underTest = new ProjectBadgesWsModule(); @Test public void verify_count_of_added_components() { underTest.configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5); + assertThat(container.size()).isPositive(); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenActionTest.java new file mode 100644 index 00000000000..44ba81fea12 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenActionTest.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.badge.ws; + +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mockito; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.usertoken.TokenGenerator; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static org.mockito.Mockito.when; + +public class TokenActionTest { + + @Rule + public DbTester db = DbTester.create(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private final TokenGenerator tokenGenerator = Mockito.mock(TokenGenerator.class); + + private final WsActionTester ws = new WsActionTester( + new TokenAction( + db.getDbClient(), + tokenGenerator, userSession)); + + @Test + public void missing_project_parameter_should_fail() { + TestRequest request = ws.newRequest(); + Assertions.assertThatThrownBy(request::execute) + .hasMessage("The 'project' parameter is missing") + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void missing_project_permission_should_fail() { + ComponentDto project = db.components().insertPrivateProject(); + + TestRequest request = ws.newRequest().setParam("project", project.getKey()); + + Assertions.assertThatThrownBy(request::execute) + .hasMessage("Insufficient privileges") + .isInstanceOf(ForbiddenException.class); + } + + @Test + public void should_generate_token() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + when(tokenGenerator.generate()).thenReturn("generated_token"); + + TestResponse response = ws.newRequest().setParam("project", project.getKey()).execute(); + + response.assertJson("{\"token\":\"generated_token\"}"); + } + + @Test + public void should_reuse_generated_token() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + when(tokenGenerator.generate()).thenReturn("generated_token"); + + // first call, generating the token + TestResponse firstResponse = ws.newRequest().setParam("project", project.getKey()).execute(); + firstResponse.assertJson("{\"token\":\"generated_token\"}"); + + // 2nd call, reusing the existing token + when(tokenGenerator.generate()).thenReturn("never_generated_token"); + TestResponse secondResponse = ws.newRequest().setParam("project", project.getKey()).execute(); + + secondResponse.assertJson("{\"token\":\"generated_token\"}"); + + } + +} diff --git a/sonar-ws/src/main/protobuf/ws-project_badge_token.proto b/sonar-ws/src/main/protobuf/ws-project_badge_token.proto new file mode 100644 index 00000000000..7be0acaee62 --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-project_badge_token.proto @@ -0,0 +1,13 @@ + +syntax = "proto2"; + +package sonarqube.ws.badge; + +option java_package = "org.sonarqube.ws"; +option java_outer_classname = "ProjectBadgeToken"; +option optimize_for = SPEED; + +// WS api/project_badges/token +message TokenWsResponse { + optional string token = 1; +} -- 2.39.5