]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13426 add api/project_badges/token endpoint
authorPierre <pierre.guillot@sonarsource.com>
Wed, 10 Nov 2021 16:43:31 +0000 (17:43 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 15 Nov 2021 20:04:34 +0000 (20:04 +0000)
18 files changed:
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java
server/sonar-db-dao/src/main/java/org/sonar/db/audit/NoOpAuditPersister.java
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/ProjectBadgeTokenNewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWsModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/badge/ws/token-example.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/ProjectBadgesWsModuleTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenActionTest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-project_badge_token.proto [new file with mode: 0644]

index 15c4ff522754a1906e3058a63afc1e546e8f0ae9..33e58ca87162a0b8b540a6820b8831971093d3b1 100644 (file)
@@ -78,6 +78,7 @@ public final class SqTables {
     "portfolio_references",
     "projects",
     "project_alm_settings",
+    "project_badge_token",
     "project_branches",
     "project_links",
     "project_mappings",
index ea48041aed22660b3ccbe05ef66b7baaea938cce..cc088c10d1247e9f431fe01eb1f04de9fe345486 100644 (file)
@@ -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,
index 99fa1daa6fa32bb12b6c7bd37422ba435abb1b01..32fb4f656283b05c1c06e7a8ec022b8d58c95fe9 100644 (file)
@@ -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;
+  }
 }
index 298be87c731443828f8e6dd08c697b5455cc7dea..4db01625cceb98e665ae38e443449a9605335eb1 100644 (file)
@@ -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,
index 60987dbf424ba81686adad4091390641a6c31bdf..ac41ffe6e364b8743d2da3451b13453639f720e3 100644 (file)
@@ -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);
index 2de7874e9f83f2471fa8715bc16c691f0bc7793a..1923b0cca139d3f1c93eafd011ac79c84f6334a0 100644 (file)
@@ -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 (file)
index 0000000..9d53395
--- /dev/null
@@ -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 (file)
index 0000000..8c9758f
--- /dev/null
@@ -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 (file)
index 0000000..1611ab0
--- /dev/null
@@ -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 (file)
index 0000000..3f8cda7
--- /dev/null
@@ -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 (file)
index 0000000..59967a1
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.project.ProjectBadgeTokenMapper">
+
+    <sql id="projectBadgeTokenColumns">
+      p.uuid as uuid,
+      p.token as token,
+      p.project_uuid as projectUuid,
+      p.created_at as createdAt,
+      p.updated_at as updatedAt
+    </sql>
+
+  <insert id="insert" parameterType="ProjectBadgeToken">
+    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}
+    )
+  </insert>
+
+  <select id="selectTokenByProjectUuid" parameterType="String" resultType="ProjectBadgeToken">
+    select
+      <include refid="projectBadgeTokenColumns"/>
+    from project_badge_token p
+    where
+    p.project_uuid = #{projectUuid,jdbcType=VARCHAR}
+  </select>
+
+</mapper>
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 (file)
index 0000000..6f99c2c
--- /dev/null
@@ -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<ProjectBadgeTokenNewValue> 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);
+  }
+
+}
index eb86931bda3a7eb280220d77b72e4962ed01f0e5..bb695a08db8d9939b73ea79698a327b87d5bf4fc 100644 (file)
@@ -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 (file)
index 0000000..51b8ada
--- /dev/null
@@ -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.<br/>" +
+        "This token can be used to authenticate with api/project_badges/quality_gate and api/project_badges/measure endpoints.<br/>" +
+        "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 (file)
index 0000000..6775568
--- /dev/null
@@ -0,0 +1 @@
+{"token": "XXXXXXXXXXXXXXX"}
index d2d564e59acf2bf9336745a7631cbbe4a64fc302..ed470ad73f68d9b5e8983b3bd81203f425e73e46 100644 (file)
@@ -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 (file)
index 0000000..44ba81f
--- /dev/null
@@ -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 (file)
index 0000000..7be0aca
--- /dev/null
@@ -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;
+}