]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5927 Improve permission checking and error reporting
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 28 Jan 2015 17:10:05 +0000 (18:10 +0100)
committerJulien HENRY <julien.henry@sonarsource.com>
Wed, 28 Jan 2015 17:26:25 +0000 (18:26 +0100)
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java
server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java

index a4e9302865226ec597c0a93825e63a73d8af9044..d8849f1940006547d82a624e8bbd220d3b1ff8a5 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.api.web.UserRole;
 import org.sonar.batch.protocol.input.FileData;
 import org.sonar.batch.protocol.input.ProjectRepositories;
 import org.sonar.core.UtcDateUtils;
@@ -53,6 +54,7 @@ import org.sonar.server.user.UserSession;
 import javax.annotation.Nullable;
 
 import java.util.Collections;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -88,6 +90,9 @@ public class ProjectRepositoryLoader implements ServerComponent {
       ComponentDto module = dbClient.componentDao().getNullableByKey(session, query.getModuleKey());
       // Current project/module can be null when analysing a new project
       if (module != null) {
+        UserSession.get().checkComponentPermission(UserRole.USER, query.getModuleKey(),
+          "You're not authorized to access to project '" + module.name() + "', please contact your SonarQube administrator.");
+
         ComponentDto project = getProject(module, session);
         if (!project.key().equals(module.key())) {
           addSettings(ref, module.getKey(), getSettingsFromParents(module, hasScanPerm, session));
@@ -103,6 +108,12 @@ public class ProjectRepositoryLoader implements ServerComponent {
 
         addSettingsToChildrenModules(ref, query.getModuleKey(), Maps.<String, String>newHashMap(), treeModuleSettings, hasScanPerm, session);
         addFileData(session, ref, modulesTree, module.uuid());
+
+        // FIXME need real value but actually only used to know if there is a previous analysis in local issue tracking mode so any value is
+        // ok
+        ref.setLastAnalysisDate(new Date());
+      } else {
+        ref.setLastAnalysisDate(null);
       }
 
       addProfiles(ref, projectKey, query.getProfileName(), session);
@@ -265,9 +276,12 @@ public class ProjectRepositoryLoader implements ServerComponent {
       throw new ForbiddenException("You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator.");
     }
     if (!preview && !hasScanPerm) {
-      throw new ForbiddenException("You're only authorized to execute a local (dry run) SonarQube analysis without pushing the results to the SonarQube server. " +
+      throw new ForbiddenException("You're only authorized to execute a local (preview) SonarQube analysis without pushing the results to the SonarQube server. " +
         "Please contact your SonarQube administrator.");
     }
+    if (preview && !hasPreviewPerm) {
+      throw new ForbiddenException("You're not authorized to execute a preview analysis. Please contact your SonarQube administrator.");
+    }
   }
 
   private Map<String, String> moduleUuidsByKey(ComponentDto module, List<ComponentDto> moduleChildren) {
index cdaf45cc9629062742896b631050ff06a5ea3e6b..2c2275ba836e6eb2b23476d187d27a5b38db0091 100644 (file)
@@ -36,7 +36,13 @@ import org.sonar.server.platform.Platform;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Maps.newHashMap;
@@ -66,7 +72,8 @@ public class UserSession {
   List<String> projectPermissions = newArrayList();
 
   UserSession() {
-    // Do not forget that when forceAuthentication is set to true, the Anyone group should not be set (but this will be check when authentication will be done in Java)
+    // Do not forget that when forceAuthentication is set to true, the Anyone group should not be set (but this will be check when
+    // authentication will be done in Java)
     userGroups = newHashSet(DefaultGroups.ANYONE);
   }
 
@@ -135,8 +142,12 @@ public class UserSession {
    * Ensures that user implies the specified global permission. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown.
    */
   public UserSession checkGlobalPermission(String globalPermission) {
+    return checkGlobalPermission(globalPermission, null);
+  }
+
+  public UserSession checkGlobalPermission(String globalPermission, @Nullable String errorMessage) {
     if (!hasGlobalPermission(globalPermission)) {
-      throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
+      throw new ForbiddenException(errorMessage != null ? errorMessage : INSUFFICIENT_PRIVILEGES_MESSAGE);
     }
     return this;
   }
@@ -187,7 +198,6 @@ public class UserSession {
     return projectKeyByPermission.get(permission).contains(projectKey);
   }
 
-
   /**
    * Does the user have the given project permission ?
    */
@@ -206,8 +216,12 @@ public class UserSession {
    * Ensures that user implies the specified project permission on a component. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown.
    */
   public UserSession checkComponentPermission(String projectPermission, String componentKey) {
+    return checkComponentPermission(projectPermission, componentKey, INSUFFICIENT_PRIVILEGES_MESSAGE);
+  }
+
+  public UserSession checkComponentPermission(String projectPermission, String componentKey, @Nullable String errorMessage) {
     if (!hasComponentPermission(projectPermission, componentKey)) {
-      throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
+      throw new ForbiddenException(errorMessage);
     }
     return this;
   }
index 7f282681b09e78c48523e34f25bcb396cca0d39f..aa7a7c4d2087870653062be7204d226f79e2acf3 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.api.utils.DateUtils;
+import org.sonar.api.web.UserRole;
 import org.sonar.batch.protocol.input.ActiveRule;
 import org.sonar.batch.protocol.input.FileData;
 import org.sonar.batch.protocol.input.ProjectRepositories;
@@ -84,9 +85,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_project_settings() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -109,9 +109,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void not_returned_secured_settings_with_only_preview_permission() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PREVIEW_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PREVIEW_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -133,9 +132,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_project_with_module_settings() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -170,9 +168,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_project_with_module_settings_inherited_from_project() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -202,9 +199,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_project_with_module_with_sub_module() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -251,9 +247,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_project_with_two_modules() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -296,10 +291,9 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_provisioned_project_settings() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     // No snapshot attached on the project -> provisioned project
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -318,7 +312,6 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_sub_module_settings() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
 
     ComponentDto project = ComponentTesting.newProjectDto();
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
@@ -330,6 +323,7 @@ public class ProjectRepositoryLoaderMediumTest {
     // No module properties
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
 
     // Sub module properties
@@ -351,8 +345,6 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_sub_module_settings_including_settings_from_parent_modules() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
@@ -367,6 +359,7 @@ public class ProjectRepositoryLoaderMediumTest {
     tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(module.getId()), dbSession);
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
 
     // Sub module properties
@@ -386,8 +379,6 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_sub_module_settings_only_inherited_from_project() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
@@ -402,6 +393,7 @@ public class ProjectRepositoryLoaderMediumTest {
     // No module property
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
     // No sub module property
 
@@ -419,8 +411,6 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_sub_module_settings_inherited_from_project_and_module() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
@@ -436,6 +426,7 @@ public class ProjectRepositoryLoaderMediumTest {
     tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()), dbSession);
 
     ComponentDto subModule = ComponentTesting.newModuleDto(module);
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, subModule);
     // No sub module property
 
@@ -453,10 +444,10 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_quality_profile_from_project_profile() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100");
 
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
 
     QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt(
@@ -477,10 +468,10 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_quality_profile_from_default_profile() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100");
 
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
 
     QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt(
@@ -501,10 +492,10 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_quality_profile_from_given_profile_name() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100");
 
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
 
     QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt(
@@ -546,11 +537,11 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_provisioned_project_profile() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100");
 
     // No snapshot attached on the project -> provisioned project
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
 
     QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt(
@@ -571,9 +562,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void fail_when_no_quality_profile_for_a_language() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto().setKey("org.codehaus.sonar:sonar");
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     dbSession.commit();
 
@@ -587,10 +577,10 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_active_rules() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100");
 
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
 
     QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt(
@@ -625,9 +615,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_manual_rules() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -674,16 +663,15 @@ public class ProjectRepositoryLoaderMediumTest {
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(ForbiddenException.class).hasMessage(
-        "You're only authorized to execute a local (dry run) SonarQube analysis without pushing the results to the SonarQube server. " +
+        "You're only authorized to execute a local (preview) SonarQube analysis without pushing the results to the SonarQube server. " +
           "Please contact your SonarQube administrator.");
     }
   }
 
   @Test
   public void return_file_data_from_single_project() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -701,9 +689,8 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_file_data_from_multi_modules() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
 
@@ -729,8 +716,6 @@ public class ProjectRepositoryLoaderMediumTest {
 
   @Test
   public void return_file_data_from_module() throws Exception {
-    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
-
     ComponentDto project = ComponentTesting.newProjectDto();
     tester.get(DbClient.class).componentDao().insert(dbSession, project);
     addDefaultProfile();
@@ -741,6 +726,7 @@ public class ProjectRepositoryLoaderMediumTest {
     tester.get(FileSourceDao.class).insert(newFileSourceDto(projectFile).setSrcHash("123456"));
 
     ComponentDto module = ComponentTesting.newModuleDto(project);
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), module.getKey());
     tester.get(DbClient.class).componentDao().insert(dbSession, module);
 
     // File on module
index 11114ec0dc8d6acf9b85e70eb9b1c957f640235a..f0c22e52c688d3e306e845b0ea0c5d2787f8818c 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.batch.issue.tracking;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.sonar.api.BatchComponent;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
@@ -35,6 +37,7 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.batch.index.BatchResource;
 import org.sonar.batch.index.ResourceCache;
 import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.protocol.input.ProjectRepositories;
 import org.sonar.batch.scan.LastLineHashes;
 import org.sonar.batch.scan.filesystem.InputPathCache;
 import org.sonar.core.issue.IssueUpdater;
@@ -45,6 +48,8 @@ import java.util.Collection;
 
 public class LocalIssueTracking implements BatchComponent {
 
+  private static final Logger LOG = LoggerFactory.getLogger(LocalIssueTracking.class);
+
   private final IssueCache issueCache;
   private final IssueTracking tracking;
   private final LastLineHashes lastLineHashes;
@@ -55,10 +60,11 @@ public class LocalIssueTracking implements BatchComponent {
   private final InputPathCache inputPathCache;
   private final ResourceCache resourceCache;
   private final PreviousIssueRepository previousIssueCache;
+  private final ProjectRepositories projectRepositories;
 
   public LocalIssueTracking(ResourceCache resourceCache, IssueCache issueCache, IssueTracking tracking,
     LastLineHashes lastLineHashes, IssueWorkflow workflow, IssueUpdater updater,
-    ActiveRules activeRules, InputPathCache inputPathCache, PreviousIssueRepository previousIssueCache) {
+    ActiveRules activeRules, InputPathCache inputPathCache, PreviousIssueRepository previousIssueCache, ProjectRepositories projectRepositories) {
     this.resourceCache = resourceCache;
     this.issueCache = issueCache;
     this.tracking = tracking;
@@ -67,11 +73,17 @@ public class LocalIssueTracking implements BatchComponent {
     this.updater = updater;
     this.inputPathCache = inputPathCache;
     this.previousIssueCache = previousIssueCache;
+    this.projectRepositories = projectRepositories;
     this.changeContext = IssueChangeContext.createScan(((Project) resourceCache.getRoot().resource()).getAnalysisDate());
     this.activeRules = activeRules;
   }
 
   public void execute() {
+    if (projectRepositories.lastAnalysisDate() == null) {
+      LOG.debug("No previous analysis, skipping issue tracking");
+      return;
+    }
+
     previousIssueCache.load();
 
     for (BatchResource component : resourceCache.all()) {