]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7254 Create task to feed user.local
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 15 Mar 2016 12:55:57 +0000 (13:55 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 21 Mar 2016 14:03:28 +0000 (15:03 +0100)
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/startup/FeedUsersLocalStartupTask.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/test/java/org/sonar/server/startup/FeedUsersLocalStartupTaskTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java

index 6451012791d4f0b9a8f65ca0b9ce66d0048ea487..a7c7b04eb7e71542554611f1346774ba3380116d 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.server.rule.RegisterRules;
 import org.sonar.server.search.IndexSynchronizer;
 import org.sonar.server.startup.ClearRulesOverloadedDebt;
 import org.sonar.server.startup.DisplayLogOnDeprecatedProjects;
+import org.sonar.server.startup.FeedUsersLocalStartupTask;
 import org.sonar.server.startup.GeneratePluginIndex;
 import org.sonar.server.startup.LogServerId;
 import org.sonar.server.startup.RegisterDashboards;
@@ -65,7 +66,8 @@ public class PlatformLevelStartup extends PlatformLevel {
       ServerLifecycleNotifier.class,
       PurgeCeActivities.class,
       DisplayLogOnDeprecatedProjects.class,
-      ClearRulesOverloadedDebt.class
+      ClearRulesOverloadedDebt.class,
+      FeedUsersLocalStartupTask.class
     );
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/FeedUsersLocalStartupTask.java b/server/sonar-server/src/main/java/org/sonar/server/startup/FeedUsersLocalStartupTask.java
new file mode 100644 (file)
index 0000000..7cb080e
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.startup;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.picocontainer.Startable;
+import org.sonar.api.config.Settings;
+import org.sonar.api.user.UserQuery;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.loadedtemplate.LoadedTemplateDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.user.UserUpdater;
+
+import static java.util.Arrays.asList;
+import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_REALM;
+import static org.sonar.db.loadedtemplate.LoadedTemplateDto.ONE_SHOT_TASK_TYPE;
+
+/**
+ * Feed users local property.
+ * If a realm is defined, then users are set as local only if their login are found in the property "sonar.security.localUsers",
+ * otherwise user are all set as local.
+ *
+ * See <a href="https://jira.sonarsource.com/browse/SONAR-7254">SONAR-7254</a>.
+ *
+ * Should be removed after LTS 5.X
+ *
+ * @since 5.5
+ */
+public class FeedUsersLocalStartupTask implements Startable {
+
+  private static final Logger LOG = Loggers.get(FeedUsersLocalStartupTask.class);
+
+  private static final String TEMPLATE_KEY = "UpdateUsersLocal";
+  private static final String LOCAL_USERS_PROPERTY = "sonar.security.localUsers";
+
+  private final System2 system2;
+
+  private final DbClient dbClient;
+  private final Settings settings;
+
+  public FeedUsersLocalStartupTask(System2 system2, DbClient dbClient, Settings settings) {
+    this.system2 = system2;
+    this.dbClient = dbClient;
+    this.settings = settings;
+  }
+
+  @Override
+  public void start() {
+    DbSession session = dbClient.openSession(false);
+    try {
+      if (hasAlreadyBeenExecuted(session)) {
+        return;
+      }
+      updateUsersLocal(session);
+      markAsExecuted(session);
+      session.commit();
+
+      if (settings.hasKey(LOCAL_USERS_PROPERTY)) {
+        LOG.info("NOTE : The property '{}' is now no more needed, you can safely remove it.", LOCAL_USERS_PROPERTY);
+      }
+    } finally {
+      dbClient.closeSession(session);
+    }
+  }
+
+  private void updateUsersLocal(DbSession session) {
+    long now = system2.now();
+    Set<String> localUsers = new HashSet<>(asList(settings.getStringArray(LOCAL_USERS_PROPERTY)));
+    boolean isRealmExist = settings.getString(CORE_AUTHENTICATOR_REALM) != null;
+    for (UserDto user : dbClient.userDao().selectUsers(session, UserQuery.ALL_ACTIVES)) {
+      if (user.getExternalIdentityProvider().equals(UserUpdater.SQ_AUTHORITY)) {
+        user.setLocal(!isRealmExist || localUsers.contains(user.getLogin()));
+      } else {
+        user.setLocal(false);
+      }
+      user.setUpdatedAt(now);
+      dbClient.userDao().update(session, user);
+    }
+  }
+
+  private boolean hasAlreadyBeenExecuted(DbSession session) {
+    return dbClient.loadedTemplateDao().countByTypeAndKey(ONE_SHOT_TASK_TYPE, TEMPLATE_KEY, session) > 0;
+  }
+
+  private void markAsExecuted(DbSession session) {
+    dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(TEMPLATE_KEY, ONE_SHOT_TASK_TYPE), session);
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+}
index e7fc83ec6bfb2fdc1481f1245b2248d3dfd1f69f..63438b40112621ad2cc3befe3c9466bd8c10d596 100644 (file)
@@ -55,7 +55,7 @@ import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_LOCAL_USERS;
 @ServerSide
 public class UserUpdater {
 
-  private static final String SQ_AUTHORITY = "sonarqube";
+  public static final String SQ_AUTHORITY = "sonarqube";
 
   private static final String LOGIN_PARAM = "Login";
   private static final String PASSWORD_PARAM = "Password";
diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/FeedUsersLocalStartupTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/FeedUsersLocalStartupTaskTest.java
new file mode 100644 (file)
index 0000000..7b0531c
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.startup;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.loadedtemplate.LoadedTemplateDto.ONE_SHOT_TASK_TYPE;
+
+public class FeedUsersLocalStartupTaskTest {
+
+  static final long NOW = 20000000L;
+  static final long PAST = 10000000L;
+
+  static final String USER1_LOGIN = "USER1";
+  static final String USER2_LOGIN = "USER2";
+
+  System2 system2 = mock(System2.class);
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+
+  DbClient dbClient = dbTester.getDbClient();
+
+  DbSession dbSession = dbTester.getSession();
+
+  UserDao userDao = dbClient.userDao();
+
+  Settings settings = new Settings();
+
+  FeedUsersLocalStartupTask underTest = new FeedUsersLocalStartupTask(system2, dbTester.getDbClient(), settings);
+
+  @Before
+  public void setUp() throws Exception {
+    when(system2.now()).thenReturn(NOW);
+  }
+
+  @Test
+  public void set_user_local_when_id_provider_is_sonarqube_and_realm_exists_and_local_users_property_contains_user_login() throws Exception {
+    settings.setProperty("sonar.security.realm", "LDAP");
+    settings.setProperty("sonar.security.localUsers", USER1_LOGIN);
+    UserDto user = addUser(USER1_LOGIN, false, "sonarqube");
+
+    underTest.start();
+
+    verifyUserIsUpdated(user.getId(), true);
+    verifyLogAboutRemovalOfLocalUsersProperty();
+  }
+
+  @Test
+  public void set_user_as_not_local_when_id_provider_is_sonarqube_and_ream_exists_and_no_local_users_property() throws Exception {
+    settings.setProperty("sonar.security.realm", "LDAP");
+    UserDto user = addUser(USER1_LOGIN, false, "sonarqube");
+
+    underTest.start();
+
+    verifyUserIsUpdated(user.getId(), false);
+    verifyEmptyLog();
+  }
+
+  @Test
+  public void set_user_as_not_local_when_id_provider_is_sonarqube_and_ream_exists_and_local_users_property_does_not_contain_user_login() throws Exception {
+    settings.setProperty("sonar.security.realm", "LDAP");
+    settings.setProperty("sonar.security.localUsers", USER2_LOGIN);
+    UserDto user = addUser(USER1_LOGIN, true, "sonarqube");
+
+    underTest.start();
+
+    verifyUserIsUpdated(user.getId(), false);
+    verifyLogAboutRemovalOfLocalUsersProperty();
+  }
+
+  @Test
+  public void set_user_as_local_when_id_provider_is_sonarqube_and_no_realm() throws Exception {
+    settings.setProperty("sonar.security.realm", (String) null);
+    UserDto user = addUser(USER1_LOGIN, false, "sonarqube");
+
+    underTest.start();
+
+    verifyUserIsUpdated(user.getId(), true);
+    verifyEmptyLog();
+  }
+
+  @Test
+  public void set_user_as_not_local_when_external_identiy_is_not_sonarqube() throws Exception {
+    UserDto user = addUser(USER1_LOGIN, false, "github");
+
+    underTest.start();
+
+    verifyUserIsUpdated(user.getId(), false);
+    verifyEmptyLog();
+  }
+
+  @Test
+  public void does_not_update_removed_user() throws Exception {
+    settings.setProperty("sonar.security.realm", "LDAP");
+
+    UserDto user = UserTesting.newUserDto()
+      .setLogin(USER1_LOGIN)
+      .setActive(false)
+      .setLocal(false)
+      .setCreatedAt(PAST)
+      .setUpdatedAt(PAST);
+    userDao.insert(dbSession, user);
+    dbSession.commit();
+
+    underTest.start();
+
+    UserDto userReloaded = userDao.selectUserById(dbSession, user.getId());
+    assertThat(userReloaded.isLocal()).isFalse();
+    assertThat(userReloaded.getUpdatedAt()).isEqualTo(PAST);
+    verifyTaskIsRegistered();
+  }
+
+  @Test
+  public void does_nothing_when_task_has_already_been_executed() throws Exception {
+    settings.setProperty("sonar.security.realm", "LDAP");
+    settings.setProperty("sonar.security.localUsers", USER1_LOGIN);
+    UserDto user = addUser(USER1_LOGIN, false, "github");
+
+    underTest.start();
+    verifyLogAboutRemovalOfLocalUsersProperty();
+
+    logTester.clear();
+    UserDto userReloaded = userDao.selectUserById(dbSession, user.getId());
+    assertThat(userReloaded.getUpdatedAt()).isEqualTo(NOW);
+    verifyTaskIsRegistered();
+
+    when(system2.now()).thenReturn(NOW + 1000L);
+
+    underTest.start();
+    userReloaded = userDao.selectUserById(dbSession, user.getId());
+    assertThat(userReloaded.getUpdatedAt()).isEqualTo(NOW);
+    verifyEmptyLog();
+  }
+
+  private UserDto addUser(String login, boolean local, String externalIdentityProvider) {
+    UserDto user = UserTesting.newUserDto()
+      .setLogin(login)
+      .setActive(true)
+      .setLocal(local)
+      .setExternalIdentityProvider(externalIdentityProvider)
+      .setExternalIdentity(login)
+      .setCreatedAt(PAST)
+      .setUpdatedAt(PAST);
+    userDao.insert(dbSession, user);
+    dbSession.commit();
+    return user;
+  }
+
+  private void verifyUserIsUpdated(long userId, boolean expectedLocal) {
+    UserDto userReloaded = userDao.selectUserById(dbSession, userId);
+    assertThat(userReloaded.isLocal()).isEqualTo(expectedLocal);
+    assertThat(userReloaded.getUpdatedAt()).isEqualTo(NOW);
+    verifyTaskIsRegistered();
+  }
+
+  private void verifyTaskIsRegistered() {
+    assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(ONE_SHOT_TASK_TYPE, "UpdateUsersLocal")).isEqualTo(1);
+  }
+
+  private void verifyLogAboutRemovalOfLocalUsersProperty() {
+    assertThat(logTester.logs(LoggerLevel.INFO)).containsOnly(
+      "NOTE : The property 'sonar.security.localUsers' is now no more needed, you can safely remove it.");
+  }
+
+  private void verifyEmptyLog() {
+    assertThat(logTester.logs(LoggerLevel.INFO)).isEmpty();
+  }
+
+}
index 457d19caa021204d0dcbefb456554062c78c5580..71895ffded4e1aa1b1753cc04c4be6e2b2ece792 100644 (file)
@@ -468,7 +468,9 @@ public interface CoreProperties {
 
   /**
    * @since 4.2
+   * @deprecated no more used since 5.5
    */
+  @Deprecated
   String CORE_AUTHENTICATOR_LOCAL_USERS = "sonar.security.localUsers";
 
   /**