]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13427 Added a new endpoint to renew the project badge token
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>
Mon, 15 Nov 2021 15:06:52 +0000 (16:06 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 16 Nov 2021 20:03:54 +0000 (20:03 +0000)
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/project/ProjectBadgeTokenDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectBadgeTokenMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectBadgeTokenMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectBadgeTokenDaoTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesWs.java
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/TokenRenewAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenRenewActionTest.java [new file with mode: 0644]

index ac41ffe6e364b8743d2da3451b13453639f720e3..03657a8016f92fb7553540a1d07c8b186be01e9d 100644 (file)
@@ -74,6 +74,8 @@ public interface AuditPersister {
 
   void addProjectBadgeToken(DbSession dbSession, ProjectBadgeTokenNewValue newValue);
 
+  void updateProjectBadgeToken(DbSession session, ProjectBadgeTokenNewValue projectBadgeTokenNewValue);
+
   void updateUserToken(DbSession dbSession, UserTokenNewValue newValue);
 
   void deleteUserToken(DbSession dbSession, UserTokenNewValue newValue);
index 1923b0cca139d3f1c93eafd011ac79c84f6334a0..30c16d31a956b837b079b49c28c38f2df15837e6 100644 (file)
@@ -119,6 +119,11 @@ public class NoOpAuditPersister implements AuditPersister {
     // no op
   }
 
+  @Override
+  public void updateProjectBadgeToken(DbSession session, ProjectBadgeTokenNewValue projectBadgeTokenNewValue) {
+    // no op
+  }
+
   @Override
   public void updateUserToken(DbSession dbSession, UserTokenNewValue newValue) {
     // no op
index 8c9758f1845c8d0834dbb0923c83a59afa2350d5..9565bf0a0a7666cfaa83e5d7ad2a25c11d6f64d2 100644 (file)
@@ -49,6 +49,15 @@ public class ProjectBadgeTokenDao implements Dao {
     return projectBadgeTokenDto;
   }
 
+  public void upsert(DbSession session, String token, ProjectDto projectDto, String userUuid, String userLogin) {
+    if(selectTokenByProject(session, projectDto) == null) {
+      insert(session, token, projectDto, userUuid, userLogin);
+    } else {
+      mapper(session).update(token, projectDto.getUuid(), system2.now());
+      auditPersister.updateProjectBadgeToken(session, new ProjectBadgeTokenNewValue(projectDto.getKey(), userUuid, userLogin));
+    }
+  }
+
   private static ProjectBadgeTokenMapper mapper(DbSession session) {
     return session.getMapper(ProjectBadgeTokenMapper.class);
   }
@@ -56,6 +65,5 @@ public class ProjectBadgeTokenDao implements Dao {
   @CheckForNull
   public ProjectBadgeTokenDto selectTokenByProject(DbSession session, ProjectDto projectDto) {
     return mapper(session).selectTokenByProjectUuid(projectDto.getUuid());
-
   }
 }
index 3f8cda7408c793d0a9fa9512017b73bfc7be2fa2..55dd6106267839a9f9398442fd1bbf55338f31ed 100644 (file)
@@ -26,6 +26,8 @@ public interface ProjectBadgeTokenMapper {
 
   void insert(ProjectBadgeTokenDto projectBadgeTokenDto);
 
+  int update(@Param("token") String token, @Param("projectUuid") String projectUuid, @Param("updatedAt") long updatedAt);
+
   @CheckForNull
   ProjectBadgeTokenDto selectTokenByProjectUuid(@Param("projectUuid") String projectUuid);
 }
index 59967a17741474cedecf2fbcdaf81c9221d4897a..739fd28f5ab118a9bbb4e1fc916686d6937a2372 100644 (file)
       p.updated_at as updatedAt
     </sql>
 
+  <update id="update" parameterType="map" useGeneratedKeys="false">
+    update project_badge_token
+    set
+      token = #{token, jdbcType=VARCHAR},
+      updated_at = #{updatedAt, jdbcType=BIGINT}
+    where
+      project_uuid = #{projectUuid, jdbcType=VARCHAR}
+  </update>
+
   <insert id="insert" parameterType="ProjectBadgeToken">
     INSERT INTO project_badge_token (
       uuid,
index 6f99c2cdb1303c1ec67609bdc36415a6d83ae14d..1e50c8c337becd05f47a09d2a30454f940d8ea24 100644 (file)
@@ -51,17 +51,16 @@ public class ProjectBadgeTokenDaoTest {
 
   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);
+    assertProjectBadgeToken(insertedProjectBadgeToken, "token");
 
     ProjectBadgeTokenDto selectedProjectBadgeToken = projectBadgeTokenDao.selectTokenByProject(db.getSession(), projectDto);
-    assertProjectBadgeToken(selectedProjectBadgeToken);
+    assertProjectBadgeToken(selectedProjectBadgeToken, "token");
   }
 
   @Test
@@ -70,7 +69,7 @@ public class ProjectBadgeTokenDaoTest {
     ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1");
 
     ProjectBadgeTokenDto insertedProjectBadgeToken = projectBadgeTokenDao.insert(db.getSession(), "token", projectDto, "user-uuid", "user-login");
-    assertProjectBadgeToken(insertedProjectBadgeToken);
+    assertProjectBadgeToken(insertedProjectBadgeToken, "token");
 
     ArgumentCaptor<ProjectBadgeTokenNewValue> captor = ArgumentCaptor.forClass(ProjectBadgeTokenNewValue.class);
 
@@ -80,9 +79,57 @@ public class ProjectBadgeTokenDaoTest {
     Assertions.assertThat(captor.getValue()).hasToString("{\"userUuid\": \"user-uuid\", \"userLogin\": \"user-login\" }");
   }
 
-  private void assertProjectBadgeToken(@Nullable ProjectBadgeTokenDto projectBadgeTokenDto) {
+  @Test
+  public void upsert_existing_token_and_select_by_project_uuid() {
+    when(uuidFactory.create()).thenReturn("generated_uuid_1");
+    ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1");
+
+    // first insert
+    ProjectBadgeTokenDto insertedProjectBadgeToken = projectBadgeTokenDao.insert(db.getSession(), "token", projectDto, "user-uuid", "user-login");
+    assertProjectBadgeToken(insertedProjectBadgeToken, "token");
+
+    // renew
+    projectBadgeTokenDao.upsert(db.getSession(), "new-token", projectDto, "user-uuid", "user-login");
+    ProjectBadgeTokenDto selectedProjectBadgeToken = projectBadgeTokenDao.selectTokenByProject(db.getSession(), projectDto);
+    assertProjectBadgeToken(selectedProjectBadgeToken, "new-token");
+  }
+
+  @Test
+  public void upsert_non_existing_token_and_select_by_project_uuid() {
+    when(uuidFactory.create()).thenReturn("generated_uuid_1");
+    ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1");
+
+    // renew
+    projectBadgeTokenDao.upsert(db.getSession(), "new-token", projectDto, "user-uuid", "user-login");
+    ProjectBadgeTokenDto selectedProjectBadgeToken = projectBadgeTokenDao.selectTokenByProject(db.getSession(), projectDto);
+    assertProjectBadgeToken(selectedProjectBadgeToken, "new-token");
+  }
+
+
+  @Test
+  public void token_upsert_is_log_in_audit() {
+    when(uuidFactory.create()).thenReturn("generated_uuid_1");
+    ProjectDto projectDto = new ProjectDto().setUuid("project_uuid_1");
+
+    // fist insert
+    projectBadgeTokenDao.insert(db.getSession(), "token", projectDto, "user-uuid", "user-login");
+    ArgumentCaptor<ProjectBadgeTokenNewValue> captor = ArgumentCaptor.forClass(ProjectBadgeTokenNewValue.class);
+    verify(auditPersister).addProjectBadgeToken(eq(db.getSession()), captor.capture());
+
+    // upsert
+    projectBadgeTokenDao.upsert(db.getSession(), "new-token", projectDto, "user-uuid", "user-login");
+    ProjectBadgeTokenDto selectedProjectBadgeToken = projectBadgeTokenDao.selectTokenByProject(db.getSession(), projectDto);
+    assertProjectBadgeToken(selectedProjectBadgeToken, "new-token");
+
+    verify(auditPersister).updateProjectBadgeToken(eq(db.getSession()), captor.capture());
+    verifyNoMoreInteractions(auditPersister);
+
+    Assertions.assertThat(captor.getValue()).hasToString("{\"userUuid\": \"user-uuid\", \"userLogin\": \"user-login\" }");
+  }
+
+  private void assertProjectBadgeToken(@Nullable ProjectBadgeTokenDto projectBadgeTokenDto, String expectedToken) {
     assertThat(projectBadgeTokenDto).isNotNull();
-    assertThat(projectBadgeTokenDto.getToken()).isEqualTo("token");
+    assertThat(projectBadgeTokenDto.getToken()).isEqualTo(expectedToken);
     assertThat(projectBadgeTokenDto.getProjectUuid()).isEqualTo("project_uuid_1");
     assertThat(projectBadgeTokenDto.getUuid()).isEqualTo("generated_uuid_1");
     assertThat(projectBadgeTokenDto.getCreatedAt()).isEqualTo(1000L);
index 6661522c4da09d2761356e63cec4518d8dccbdcb..a8edb3d09e17a201a902ad42af1b06db0bb15861 100644 (file)
@@ -32,7 +32,7 @@ public class ProjectBadgesWs implements WebService {
 
   @Override
   public void define(Context context) {
-    NewController controller = context.createController("api/project_badges");
+      NewController controller = context.createController("api/project_badges");
     controller.setDescription("Generate badges based on quality gates or measures");
     controller.setSince("7.1");
     actions.forEach(action -> action.define(controller));
index bb695a08db8d9939b73ea79698a327b87d5bf4fc..c9cf545d6f720cd03189d9e6eb15dd095d7c926d 100644 (file)
@@ -31,6 +31,7 @@ public class ProjectBadgesWsModule extends Module {
       MeasureAction.class,
       TokenAction.class,
       SvgGenerator.class,
-      ProjectBadgesSupport.class);
+      ProjectBadgesSupport.class,
+      TokenRenewAction.class);
   }
 }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenRenewAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/TokenRenewAction.java
new file mode 100644 (file)
index 0000000..0b915a7
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.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.server.user.UserSession;
+import org.sonar.server.usertoken.TokenGenerator;
+
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+
+public class TokenRenewAction implements ProjectBadgesWsAction {
+
+  private static final String PROJECT_KEY_PARAM = "project";
+  private final DbClient dbClient;
+  private final TokenGenerator tokenGenerator;
+  private final UserSession userSession;
+
+  public TokenRenewAction(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("renew_token")
+      .setHandler(this)
+      .setSince("9.2")
+      .setPost(true)
+      .setDescription("Creates new token replacing any existing token 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 'Administer' permission on the specified project.");
+    action.createParam(PROJECT_KEY_PARAM)
+      .setDescription("Project key")
+      .setRequired(true)
+      .setExampleValue(KEY_PROJECT_EXAMPLE_001);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    doHandle(request);
+    response.noContent();
+  }
+
+  private void 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.ADMIN, projectDto);
+      String newGeneratedToken = tokenGenerator.generate();
+      dbClient.projectBadgeTokenDao().upsert(dbSession, newGeneratedToken, projectDto, userSession.getUuid(), userSession.getLogin());
+      dbSession.commit();
+    }
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenRenewActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/TokenRenewActionTest.java
new file mode 100644 (file)
index 0000000..9fa6074
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.project.ProjectBadgeTokenDto;
+import org.sonar.db.project.ProjectDto;
+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.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TokenRenewActionTest {
+
+  private final System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private final TokenGenerator tokenGenerator = Mockito.mock(TokenGenerator.class);
+
+  private final WsActionTester ws = new WsActionTester(
+    new TokenRenewAction(
+      db.getDbClient(),
+      tokenGenerator, userSession));
+
+  @Before
+  public void before(){
+    when(system2.now()).thenReturn(1000L);
+  }
+
+  @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_admin_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_add_token_when_no_token_yet_and_return_204() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+    when(tokenGenerator.generate()).thenReturn("generated_token");
+
+    TestResponse response = ws.newRequest().setParam("project", project.getKey()).execute();
+
+    ProjectBadgeTokenDto projectBadgeTokenDto = db.getDbClient().projectBadgeTokenDao().selectTokenByProject(db.getSession(), project);
+    assertThat(projectBadgeTokenDto).isNotNull();
+    assertThat(projectBadgeTokenDto.getToken()).isEqualTo("generated_token");
+    response.assertNoContent();
+  }
+
+  @Test
+  public void should_replace_existing_token_when__token_already_present_and_update_update_at() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+    when(tokenGenerator.generate()).thenReturn("generated_token");
+
+    ws.newRequest().setParam("project", project.getKey()).execute(); //inserting first token with updated at 1000
+
+    when(system2.now()).thenReturn(2000L);
+    ws.newRequest().setParam("project", project.getKey()).execute(); //replacing first token with updated at 2000
+
+    ProjectBadgeTokenDto projectBadgeTokenDto = db.getDbClient().projectBadgeTokenDao().selectTokenByProject(db.getSession(), project);
+    assertThat(projectBadgeTokenDto).isNotNull();
+    assertThat(projectBadgeTokenDto.getToken()).isEqualTo("generated_token");
+    assertThat(projectBadgeTokenDto.getUpdatedAt()).isEqualTo(2000L);
+  }
+
+}