]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5402 Fix issue on provisionned projects
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 4 Aug 2014 12:28:41 +0000 (14:28 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 4 Aug 2014 12:33:19 +0000 (14:33 +0200)
15 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/persistence/ComponentDao.java
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileProjectOperations.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileService.java
server/sonar-server/src/test/java/org/sonar/server/component/persistence/ComponentDaoTest.java
server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileProjectOperationsMediumTest.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/component/AuthorizedComponentDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java
sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/user/UserDao.java
sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml

index 657287999b7acee92d55eacaf08000f5560be740..5b6ff6d7ec8539e11c71f406501726b742a0ffc6 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.component.persistence;
 
 import org.sonar.api.ServerComponent;
 import org.sonar.api.utils.System2;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.component.db.ComponentMapper;
 import org.sonar.core.persistence.DaoComponent;
@@ -43,6 +44,15 @@ public class ComponentDao extends BaseDao<ComponentMapper, ComponentDto, String>
   }
 
   public ComponentDto getById(Long id, DbSession session) {
+    ComponentDto componentDto = getNullableById(id, session);
+    if (componentDto == null) {
+      throw new NotFoundException(String.format("Project with id '%s' not found", id));
+    }
+    return componentDto;
+  }
+
+  @CheckForNull
+  public ComponentDto getNullableById(Long id, DbSession session) {
     return mapper(session).selectById(id);
   }
 
@@ -67,7 +77,21 @@ public class ComponentDao extends BaseDao<ComponentMapper, ComponentDto, String>
     return mapper(session).findModulesByProject(projectKey);
   }
 
+  @CheckForNull
+  public AuthorizedComponentDto getNullableAuthorizedComponentById(Long id, DbSession session) {
+    return mapper(session).selectAuthorizedComponentById(id);
+  }
+
+  public AuthorizedComponentDto getAuthorizedComponentById(Long id, DbSession session) {
+    AuthorizedComponentDto componentDto = getNullableAuthorizedComponentById(id, session);
+    if (componentDto == null) {
+      throw new NotFoundException(String.format("Project with id '%s' not found", id));
+    }
+    return componentDto;
+  }
+
   @Override
+  @CheckForNull
   protected ComponentDto doGetNullableByKey(DbSession session, String key) {
     return mapper(session).selectByKey(key);
   }
index ccc2afb70320c8de02124bc640e8d9a2dc4b08c8..2c6addbbfb461254add9b6d595e007f17cb98a8e 100644 (file)
@@ -125,7 +125,7 @@ public class DuplicationsJsonWriter implements ServerComponent {
   private ComponentDto getProject(@Nullable Long projectId, Map<Long, ComponentDto> projectsById, DbSession session) {
     ComponentDto project = projectsById.get(projectId);
     if (project == null && projectId != null) {
-      project = componentDao.getById(projectId, session);
+      project = componentDao.getNullableById(projectId, session);
       if (project != null) {
         projectsById.put(project.getId(), project);
       }
index e627be18a6c9d450a2c9b8fedfa9582860d58695..8ce814ba46cfcab83a58c05b5f46ccdaac58bd4e 100644 (file)
@@ -29,6 +29,8 @@ import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metric.ValueType;
 import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
@@ -207,12 +209,10 @@ public class QualityGates {
   }
 
   public void associateProject(Long qGateId, Long projectId) {
-    checkPermission(UserSession.get());
-
     DbSession session = myBatis.openSession(false);
     try {
       getNonNullQgate(qGateId);
-      checkNonNullProject(projectId, session);
+      checkPermission(UserSession.get(), projectId, session);
       propertiesDao.setProperty(new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY).setResourceId(projectId).setValue(qGateId.toString()));
     } finally {
       MyBatis.closeQuietly(session);
@@ -220,12 +220,10 @@ public class QualityGates {
   }
 
   public void dissociateProject(Long qGateId, Long projectId) {
-    checkPermission(UserSession.get());
-
     DbSession session = myBatis.openSession(false);
     try {
       getNonNullQgate(qGateId);
-      checkNonNullProject(projectId, session);
+      checkPermission(UserSession.get(), projectId, session);
       propertiesDao.deleteProjectProperty(SONAR_QUALITYGATE_PROPERTY, projectId);
     } finally {
       MyBatis.closeQuietly(session);
@@ -338,12 +336,6 @@ public class QualityGates {
     return condition;
   }
 
-  private void checkNonNullProject(long projectId, DbSession session) {
-    if (!componentDao.existsById(projectId, session)) {
-      throw new NotFoundException("There is no project with id=" + projectId);
-    }
-  }
-
   private void validateQualityGate(@Nullable Long updatingQgateId, @Nullable String name) {
     Errors errors = new Errors();
     if (Strings.isNullOrEmpty(name)) {
@@ -366,4 +358,11 @@ public class QualityGates {
   private void checkPermission(UserSession userSession) {
     userSession.checkGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN);
   }
+
+  private void checkPermission(UserSession userSession, Long projectId, DbSession session) {
+    AuthorizedComponentDto project = componentDao.getAuthorizedComponentById(projectId, session);
+    if (!userSession.hasGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN) && !userSession.hasProjectPermission(UserRole.ADMIN, project.key())) {
+      throw new ForbiddenException("Insufficient privileges");
+    }
+  }
 }
index 429b192a336f456aee68539a5664d65ada565b37..2d5724b93836d251edbf80bf8cea05b927a45186 100644 (file)
 package org.sonar.server.qualityprofile;
 
 import org.sonar.api.ServerComponent;
-import org.sonar.api.component.Component;
-import org.sonar.core.component.ComponentDto;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.properties.PropertyDto;
 import org.sonar.core.qualityprofile.db.QualityProfileDto;
 import org.sonar.server.db.DbClient;
-import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.user.UserSession;
 
+/**
+ * Should be refactored in order to use project key. Mabye should it be move to {@link QProfileFactory}
+ * Permission checks should also be done in the upper service.
+ */
 public class QProfileProjectOperations implements ServerComponent {
 
   private final DbClient db;
@@ -41,7 +45,6 @@ public class QProfileProjectOperations implements ServerComponent {
   }
 
   public void addProject(int profileId, long projectId, UserSession userSession) {
-    checkPermission(userSession);
     DbSession session = db.openSession(false);
     try {
       addProject(profileId, projectId, userSession, session);
@@ -52,8 +55,8 @@ public class QProfileProjectOperations implements ServerComponent {
   }
 
   void addProject(int profileId, long projectId, UserSession userSession, DbSession session) {
-    checkPermission(userSession);
-    ComponentDto project = (ComponentDto) findProjectNotNull(projectId, session);
+    AuthorizedComponentDto project = db.componentDao().getAuthorizedComponentById(projectId, session);
+    checkPermission(userSession, project.key());
     QualityProfileDto qualityProfile = findNotNull(profileId, session);
 
     db.propertiesDao().setProperty(new PropertyDto().setKey(
@@ -62,10 +65,10 @@ public class QProfileProjectOperations implements ServerComponent {
   }
 
   public void removeProject(int profileId, long projectId, UserSession userSession) {
-    checkPermission(userSession);
     DbSession session = db.openSession(false);
     try {
-      ComponentDto project = (ComponentDto) findProjectNotNull(projectId, session);
+      AuthorizedComponentDto project = db.componentDao().getAuthorizedComponentById(projectId, session);
+      checkPermission(userSession, project.key());
       QualityProfileDto qualityProfile = findNotNull(profileId, session);
 
       db.propertiesDao().deleteProjectProperty(QProfileProjectLookup.PROFILE_PROPERTY_PREFIX + qualityProfile.getLanguage(), project.getId(), session);
@@ -76,10 +79,10 @@ public class QProfileProjectOperations implements ServerComponent {
   }
 
   public void removeProject(String language, long projectId, UserSession userSession) {
-    checkPermission(userSession);
     DbSession session = db.openSession(false);
     try {
-      ComponentDto project = (ComponentDto) findProjectNotNull(projectId, session);
+      AuthorizedComponentDto project = db.componentDao().getAuthorizedComponentById(projectId, session);
+      checkPermission(userSession, project.key());
 
       db.propertiesDao().deleteProjectProperty(QProfileProjectLookup.PROFILE_PROPERTY_PREFIX + language, project.getId(), session);
       session.commit();
@@ -106,16 +109,14 @@ public class QProfileProjectOperations implements ServerComponent {
     return qualityProfile;
   }
 
-  private Component findProjectNotNull(long projectId, DbSession session) {
-    Component component = db.resourceDao().findById(projectId, session);
-    if (component == null) {
-      throw new NotFoundException("This project does not exist");
-    }
-    return component;
-  }
-
   private void checkPermission(UserSession userSession) {
     userSession.checkGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN);
   }
 
+  private void checkPermission(UserSession userSession, String projectKey) {
+    if (!userSession.hasGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN) && !userSession.hasProjectPermission(UserRole.ADMIN, projectKey)) {
+      throw new ForbiddenException("Insufficient privileges");
+    }
+  }
+
 }
index 2fb0b470d1939a2744f7e7443ccd171dcb80fda1..fde5a90ed2f7344d44725992d9b8cdfdd55ff85f 100644 (file)
@@ -193,8 +193,6 @@ public class QProfileService implements ServerComponent {
 
   /**
    * Used in /api/profiles and in /profiles/export
-   * @param language
-   * @return
    */
   @CheckForNull
   public QualityProfileDto getDefault(String language) {
index 190127ecca3cd19c0bbf0527001d045c785e8214..4ff1d97a5568ae00d994045332b594c676a33422 100644 (file)
@@ -24,6 +24,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.persistence.AbstractDaoTestCase;
 import org.sonar.core.persistence.DbSession;
@@ -97,7 +98,21 @@ public class ComponentDaoTest extends AbstractDaoTestCase {
     setupData("shared");
 
     assertThat(dao.getById(4L, session)).isNotNull();
-    assertThat(dao.getById(111L, session)).isNull();
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void fail_to_get_by_id_when_project_not_found() {
+    setupData("shared");
+
+    dao.getById(111L, session);
+  }
+
+  @Test
+  public void get_nullable_by_id() {
+    setupData("shared");
+
+    assertThat(dao.getNullableById(4L, session)).isNotNull();
+    assertThat(dao.getNullableById(111L, session)).isNull();
   }
 
   @Test
@@ -152,6 +167,31 @@ public class ComponentDaoTest extends AbstractDaoTestCase {
     assertThat(dao.getParentModuleByKey("unknown", session)).isNull();
   }
 
+  @Test
+  public void get_nullable_authorized_component_by_id() {
+    setupData("shared");
+
+    AuthorizedComponentDto result = dao.getNullableAuthorizedComponentById(4L, session);
+    assertThat(result).isNotNull();
+    assertThat(result.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java");
+
+    assertThat(dao.getNullableAuthorizedComponentById(111L, session)).isNull();
+  }
+
+  @Test
+  public void get_authorized_component_by_id() {
+    setupData("shared");
+
+    assertThat(dao.getAuthorizedComponentById(4L, session)).isNotNull();
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void fail_to_get_authorized_component_by_id_when_project_not_found() {
+    setupData("shared");
+
+    dao.getAuthorizedComponentById(111L, session);
+  }
+
   @Test
   public void insert() {
     when(system2.now()).thenReturn(DateUtils.parseDate("2014-06-18").getTime());
index 6a26a07187193c362afbc0caf832e8eb91bb8779..226f7d4ecec0bcf96ec587d651928b36c8e45f84 100644 (file)
@@ -66,8 +66,8 @@ public class DuplicationsJsonWriterTest {
 
     when(componentDao.getNullableByKey(session, key1)).thenReturn(file1);
     when(componentDao.getNullableByKey(session, key2)).thenReturn(file2);
-    when(componentDao.getById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
-    when(componentDao.getById(5L, session)).thenReturn(new ComponentDto().setId(5L).setKey("org.codehaus.sonar:sonar-ws-client").setLongName("SonarQube :: Web Service Client"));
+    when(componentDao.getNullableById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
+    when(componentDao.getNullableById(5L, session)).thenReturn(new ComponentDto().setId(5L).setKey("org.codehaus.sonar:sonar-ws-client").setLongName("SonarQube :: Web Service Client"));
 
     List<DuplicationsParser.Block> blocks = newArrayList();
     blocks.add(new DuplicationsParser.Block(newArrayList(
@@ -112,8 +112,8 @@ public class DuplicationsJsonWriterTest {
 
     verify(componentDao, times(2)).getNullableByKey(eq(session), anyString());
     // Verify call to dao is cached when searching for project / sub project
-    verify(componentDao, times(1)).getById(eq(1L), eq(session));
-    verify(componentDao, times(1)).getById(eq(5L), eq(session));
+    verify(componentDao, times(1)).getNullableById(eq(1L), eq(session));
+    verify(componentDao, times(1)).getNullableById(eq(5L), eq(session));
   }
 
   @Test
@@ -125,7 +125,7 @@ public class DuplicationsJsonWriterTest {
 
     when(componentDao.getNullableByKey(session, key1)).thenReturn(file1);
     when(componentDao.getNullableByKey(session, key2)).thenReturn(file2);
-    when(componentDao.getById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
+    when(componentDao.getNullableById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
 
     List<DuplicationsParser.Block> blocks = newArrayList();
     blocks.add(new DuplicationsParser.Block(newArrayList(
@@ -171,7 +171,7 @@ public class DuplicationsJsonWriterTest {
     ComponentDto file1 = new ComponentDto().setId(10L).setQualifier("FIL").setKey(key1).setLongName("PropertyDeleteQuery").setProjectId(1L);
 
     when(componentDao.getNullableByKey(session, key1)).thenReturn(file1);
-    when(componentDao.getById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
+    when(componentDao.getNullableById(1L, session)).thenReturn(new ComponentDto().setId(1L).setKey("org.codehaus.sonar:sonar").setLongName("SonarQube"));
 
     List<DuplicationsParser.Block> blocks = newArrayList();
 
index bc0f6d85dc0566602e2e3c3d8fdf4e19a75ae4a9..99d5b8319e0b7f0a825b2b528023a1ec4dd99a34 100644 (file)
@@ -34,6 +34,8 @@ import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metric.ValueType;
 import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
@@ -60,10 +62,7 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
 public class QualityGatesTest {
@@ -91,15 +90,20 @@ public class QualityGatesTest {
 
   QualityGates qGates;
 
-  UserSession authorizedUserSession = MockUserSession.create().setLogin("gaudol").setName("Olivier").setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
-  UserSession unauthenticatedUserSession = MockUserSession.create();
+  static final String PROJECT_KEY = "SonarQube";
+
+  UserSession authorizedProfileAdminUserSession = MockUserSession.create().setLogin("gaudol").setName("Olivier").setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+  UserSession authorizedProjectAdminUserSession = MockUserSession.create().setLogin("gaudol").setName("Olivier").addProjectPermissions(UserRole.ADMIN, PROJECT_KEY);
   UserSession unauthorizedUserSession = MockUserSession.create().setLogin("polop").setName("Polop");
+  UserSession unauthenticatedUserSession = MockUserSession.create();
 
   @Before
   public void initialize() {
+    when(componentDao.getAuthorizedComponentById(anyLong(), eq(session))).thenReturn(new AuthorizedComponentDto().setId(1L).setKey(PROJECT_KEY));
+
     when(myBatis.openSession(false)).thenReturn(session);
     qGates = new QualityGates(dao, conditionDao, metricFinder, propertiesDao, componentDao, myBatis);
-    UserSessionTestUtils.setUserSession(authorizedUserSession);
+    UserSessionTestUtils.setUserSession(authorizedProfileAdminUserSession);
   }
 
   @Test
@@ -214,7 +218,7 @@ public class QualityGatesTest {
   public void should_select_default_qgate() throws Exception {
     long defaultId = 42L;
     String defaultName = "Default Name";
-    when(dao.selectById(defaultId)).thenReturn(new QualityGateDto().setId(defaultId).setName(defaultName ));
+    when(dao.selectById(defaultId)).thenReturn(new QualityGateDto().setId(defaultId).setName(defaultName));
     qGates.setDefault(defaultId);
     verify(dao).selectById(defaultId);
     ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
@@ -460,7 +464,7 @@ public class QualityGatesTest {
     QualityGateConditionDto cond1 = new QualityGateConditionDto().setMetricId(metric1Id);
     QualityGateConditionDto cond2 = new QualityGateConditionDto().setMetricId(metric2Id);
     Collection<QualityGateConditionDto> conditions = ImmutableList.of(cond1, cond2);
-    when(conditionDao.selectForQualityGate(qGateId)).thenReturn(conditions );
+    when(conditionDao.selectForQualityGate(qGateId)).thenReturn(conditions);
     Metric metric1 = mock(Metric.class);
     when(metric1.getKey()).thenReturn(metric1Key);
     when(metricFinder.findById((int) metric1Id)).thenReturn(metric1);
@@ -482,7 +486,7 @@ public class QualityGatesTest {
     QualityGateConditionDto cond1 = new QualityGateConditionDto().setMetricId(metric1Id);
     QualityGateConditionDto cond2 = new QualityGateConditionDto().setMetricId(metric2Id);
     Collection<QualityGateConditionDto> conditions = ImmutableList.of(cond1, cond2);
-    when(conditionDao.selectForQualityGate(qGateId)).thenReturn(conditions );
+    when(conditionDao.selectForQualityGate(qGateId)).thenReturn(conditions);
     Metric metric1 = mock(Metric.class);
     when(metric1.getKey()).thenReturn(metric1Key);
     when(metricFinder.findById((int) metric1Id)).thenReturn(metric1);
@@ -509,7 +513,6 @@ public class QualityGatesTest {
     Long qGateId = 42L;
     Long projectId = 24L;
     when(dao.selectById(qGateId)).thenReturn(new QualityGateDto().setId(qGateId));
-    when(componentDao.existsById(projectId, session)).thenReturn(true);
     qGates.associateProject(qGateId, projectId);
     verify(dao).selectById(qGateId);
     ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
@@ -520,12 +523,21 @@ public class QualityGatesTest {
     assertThat(property.getValue()).isEqualTo("42");
   }
 
-  @Test(expected = NotFoundException.class)
-  public void should_fail_associate_project_on_not_existing_project() {
+  @Test
+  public void associate_project_with_project_admin_permission() {
+    UserSessionTestUtils.setUserSession(authorizedProjectAdminUserSession);
+
     Long qGateId = 42L;
     Long projectId = 24L;
     when(dao.selectById(qGateId)).thenReturn(new QualityGateDto().setId(qGateId));
-    qGates.associateProject(qGateId , projectId);
+    qGates.associateProject(qGateId, projectId);
+    verify(dao).selectById(qGateId);
+    ArgumentCaptor<PropertyDto> propertyCaptor = ArgumentCaptor.forClass(PropertyDto.class);
+    verify(propertiesDao).setProperty(propertyCaptor.capture());
+    PropertyDto property = propertyCaptor.getValue();
+    assertThat(property.getKey()).isEqualTo("sonar.qualitygate");
+    assertThat(property.getResourceId()).isEqualTo(projectId);
+    assertThat(property.getValue()).isEqualTo("42");
   }
 
   @Test
@@ -533,7 +545,18 @@ public class QualityGatesTest {
     Long qGateId = 42L;
     Long projectId = 24L;
     when(dao.selectById(qGateId)).thenReturn(new QualityGateDto().setId(qGateId));
-    when(componentDao.existsById(projectId, session)).thenReturn(true);
+    qGates.dissociateProject(qGateId, projectId);
+    verify(dao).selectById(qGateId);
+    verify(propertiesDao).deleteProjectProperty("sonar.qualitygate", projectId);
+  }
+
+  @Test
+  public void dissociate_project_with_project_admin_permission() {
+    UserSessionTestUtils.setUserSession(authorizedProjectAdminUserSession);
+
+    Long qGateId = 42L;
+    Long projectId = 24L;
+    when(dao.selectById(qGateId)).thenReturn(new QualityGateDto().setId(qGateId));
     qGates.dissociateProject(qGateId, projectId);
     verify(dao).selectById(qGateId);
     verify(propertiesDao).deleteProjectProperty("sonar.qualitygate", projectId);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileProjectOperationsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileProjectOperationsMediumTest.java
new file mode 100644 (file)
index 0000000..36e8b53
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.qualityprofile;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.permission.PermissionFacade;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.qualityprofile.db.QualityProfileDto;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.search.IndexClient;
+import org.sonar.server.tester.ServerTester;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.user.UserSession;
+
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class QProfileProjectOperationsMediumTest {
+
+  @ClassRule
+  public static ServerTester tester = new ServerTester();
+
+  DbClient db;
+  DbSession dbSession;
+  IndexClient index;
+  QProfileFactory factory;
+  QProfileProjectOperations projectOperations;
+
+  ComponentDto project;
+  QualityProfileDto profile;
+  static final String PROJECT_KEY = "SonarQube";
+
+  UserSession authorizedProfileAdminUserSession = MockUserSession.create().setLogin("john").setName("John").setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+  UserSession authorizedProjectAdminUserSession = MockUserSession.create().setLogin("john").setName("John").addProjectPermissions(UserRole.ADMIN, PROJECT_KEY);
+
+  @Before
+  public void before() {
+    tester.clearDbAndIndexes();
+    db = tester.get(DbClient.class);
+    dbSession = db.openSession(false);
+    index = tester.get(IndexClient.class);
+    factory = tester.get(QProfileFactory.class);
+    projectOperations = tester.get(QProfileProjectOperations.class);
+
+    project = new ComponentDto()
+      .setKey(PROJECT_KEY)
+      .setName("SonarQube")
+      .setLongName("SonarQube")
+      .setQualifier("TRK")
+      .setScope("PRJ")
+      .setEnabled(true);
+    db.componentDao().insert(dbSession, project);
+
+    profile = QProfileTesting.newXooP1();
+    db.qualityProfileDao().insert(dbSession, profile);
+
+    dbSession.commit();
+  }
+
+  @After
+  public void after() throws Exception {
+    dbSession.close();
+  }
+
+  @Test
+  public void add_project() throws Exception {
+    projectOperations.addProject(profile.getId(), project.getId(), authorizedProfileAdminUserSession);
+
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNotNull();
+  }
+
+  @Test
+  public void add_project_with_only_project_admin_permission() throws Exception {
+    projectOperations.addProject(profile.getId(), project.getId(), authorizedProjectAdminUserSession);
+
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNotNull();
+  }
+
+  @Test
+  public void remove_project_from_project_id() throws Exception {
+    projectOperations.addProject(profile.getId(), project.getId(), authorizedProfileAdminUserSession);
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNotNull();
+
+    projectOperations.removeProject(profile.getId(), project.getId(), authorizedProfileAdminUserSession);
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNull();
+  }
+
+  @Test
+  public void remove_project_from_language() throws Exception {
+    projectOperations.addProject(profile.getId(), project.getId(), authorizedProfileAdminUserSession);
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNotNull();
+
+    projectOperations.removeProject(profile.getLanguage(), project.getId(), authorizedProfileAdminUserSession);
+    assertThat(factory.getByProjectAndLanguage(PROJECT_KEY, profile.getLanguage())).isNull();
+  }
+
+  @Test
+  public void remove_all_projects() throws Exception {
+    ComponentDto project1 = new ComponentDto()
+      .setKey("project1")
+      .setName("project1")
+      .setLongName("project1")
+      .setQualifier("TRK")
+      .setScope("PRJ")
+      .setEnabled(true);
+    ComponentDto project2 = new ComponentDto()
+      .setKey("project2")
+      .setName("project2")
+      .setLongName("project2")
+      .setQualifier("TRK")
+      .setScope("PRJ")
+      .setEnabled(true);
+    db.componentDao().insert(dbSession, project1);
+    db.componentDao().insert(dbSession, project2);
+
+    // Create a user having user permission on the two projects and the global quality profile admin permission
+    UserDto user = new UserDto().setLogin("john").setName("John").setEmail("jo@hn.com").setCreatedAt(new Date()).setUpdatedAt(new Date());
+    db.userDao().insert(dbSession, user);
+    tester.get(PermissionFacade.class).insertUserPermission(project1.getId(), user.getId(), UserRole.USER, dbSession);
+    tester.get(PermissionFacade.class).insertUserPermission(project2.getId(), user.getId(), UserRole.USER, dbSession);
+    UserSession userSession = MockUserSession.set().setUserId(user.getId().intValue()).setLogin("john").setName("John")
+      .setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+
+    dbSession.commit();
+
+    projectOperations.addProject(profile.getId(), project1.getId(), userSession);
+    projectOperations.addProject(profile.getId(), project2.getId(), userSession);
+    assertThat(tester.get(QProfileProjectLookup.class).projects(profile.getId())).hasSize(2);
+
+    projectOperations.removeAllProjects(profile.getId(), userSession);
+    assertThat(tester.get(QProfileProjectLookup.class).projects(profile.getId())).isEmpty();
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/component/AuthorizedComponentDto.java b/sonar-core/src/main/java/org/sonar/core/component/AuthorizedComponentDto.java
new file mode 100644 (file)
index 0000000..9ad6a40
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.core.component;
+
+import org.sonar.core.persistence.Dto;
+
+/**
+ * Used to check that a project exists. Can return provisionned projects and projects from analysis.
+ * Warning, this component should not be retrieve from db using a join on snapshots, otherwise provisionned projects will not be returned anymore.
+ */
+public class AuthorizedComponentDto extends Dto<String> {
+
+  private Long id;
+  private String kee;
+
+  public Long getId() {
+    return id;
+  }
+
+  public AuthorizedComponentDto setId(Long id) {
+    this.id = id;
+    return this;
+  }
+
+  public String key() {
+    return kee;
+  }
+
+  public AuthorizedComponentDto setKey(String key) {
+    this.kee = key;
+    return this;
+  }
+
+  @Override
+  public String getKey() {
+    return kee;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    AuthorizedComponentDto that = (AuthorizedComponentDto) o;
+
+    if (!id.equals(that.id)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return id.hashCode();
+  }
+
+}
index 1460e2034bca754d2418ecabba66640a3e765f86..569e8646269c0fde912d3243dad637c214048eab 100644 (file)
 package org.sonar.core.component;
 
 import org.sonar.api.component.Component;
-import org.sonar.core.persistence.Dto;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-public class ComponentDto extends Dto<String> implements Component {
+public class ComponentDto extends AuthorizedComponentDto implements Component {
 
-  private Long id;
-  private String kee;
   private String path;
   private String name;
   private String longName;
@@ -39,22 +36,13 @@ public class ComponentDto extends Dto<String> implements Component {
   private Long subProjectId;
   private boolean enabled = true;
 
-  public Long getId() {
-    return id;
-  }
-
   public ComponentDto setId(Long id) {
-    this.id = id;
+    super.setId(id);
     return this;
   }
 
-  @Override
-  public String key() {
-    return kee;
-  }
-
   public ComponentDto setKey(String key) {
-    this.kee = key;
+    super.setKey(key);
     return this;
   }
 
@@ -146,32 +134,4 @@ public class ComponentDto extends Dto<String> implements Component {
     return this;
   }
 
-  @Override
-  public String getKey() {
-    return kee;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-
-    ComponentDto that = (ComponentDto) o;
-
-    if (!id.equals(that.id)) {
-      return false;
-    }
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    return id.hashCode();
-  }
-
 }
index 8e925d67ca0cd3eaa2cdd36e4328a935cd4ca38a..5561948dc8ca65cb824127503dea20e56db4f512 100644 (file)
 package org.sonar.core.component.db;
 
 import org.apache.ibatis.annotations.Param;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.component.ComponentDto;
 
+import javax.annotation.CheckForNull;
+
 import java.util.List;
 
 /**
@@ -29,12 +32,16 @@ import java.util.List;
  */
 public interface ComponentMapper {
 
+  @CheckForNull
   ComponentDto selectByKey(String key);
 
+  @CheckForNull
   ComponentDto selectById(long id);
 
+  @CheckForNull
   ComponentDto selectRootProjectByKey(String key);
 
+  @CheckForNull
   ComponentDto selectParentModuleByKey(String key);
 
   /**
@@ -44,5 +51,8 @@ public interface ComponentMapper {
 
   long countById(long id);
 
+  @CheckForNull
+  AuthorizedComponentDto selectAuthorizedComponentById(long id);
+
   void insert(ComponentDto rule);
 }
index e8a97e02518583ec40783008fe1f5245210bf969..3e9c9d2f5f4a4c43e7958431d5ede9bded6d5e99 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.database.model.MeasureModel;
 import org.sonar.core.activity.db.ActivityDto;
 import org.sonar.core.activity.db.ActivityMapper;
 import org.sonar.core.cluster.WorkQueue;
+import org.sonar.core.component.AuthorizedComponentDto;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.component.db.ComponentMapper;
 import org.sonar.core.config.Logback;
@@ -104,6 +105,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
     loadAlias(conf, "ActiveDashboard", ActiveDashboardDto.class);
     loadAlias(conf, "Author", AuthorDto.class);
     loadAlias(conf, "Component", ComponentDto.class);
+    loadAlias(conf, "AuthorizedComponent", AuthorizedComponentDto.class);
     loadAlias(conf, "Dashboard", DashboardDto.class);
     loadAlias(conf, "Dependency", DependencyDto.class);
     loadAlias(conf, "DuplicationUnit", DuplicationUnitDto.class);
index afcda180beae45b6763f8a4a0999b8c16bec5f51..c87c4eba04656ef9449ae08eb805e8ed20babb12 100644 (file)
@@ -102,7 +102,6 @@ public class UserDao implements BatchComponent, ServerComponent, DaoComponent {
     }
   }
 
-  @CheckForNull
   public UserDto insert(SqlSession session, UserDto dto) {
     session.getMapper(UserMapper.class).insert(dto);
     return dto;
index b0fd329b0700d23e76eec93e79600dd1682cd86c..77a9ad0a35d646174c510f505e58489780f09ad6 100644 (file)
     p.path as path
   </sql>
 
+  <sql id="authorizedComponentColumns">
+    p.id,
+    p.kee as kee
+  </sql>
+
   <select id="selectByKey" parameterType="String" resultType="Component">
     SELECT <include refid="componentColumns"/>
     FROM projects p
     </where>
   </select>
 
+  <select id="selectAuthorizedComponentById" parameterType="long" resultType="AuthorizedComponent">
+    SELECT <include refid="authorizedComponentColumns"/>
+    FROM projects p
+    <where>
+      AND p.enabled=${_true}
+      AND p.id=#{id}
+    </where>
+  </select>
+
   <sql id="insertColumns">
     (kee, name, long_name, qualifier, scope, language, root_id, path, created_at)
   </sql>