From fc48f0fb03bc4e73cc96b358d518ddb42c3ffe34 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 15 Mar 2016 13:55:57 +0100 Subject: [PATCH] SONAR-7254 Create task to feed user.local --- .../platformlevel/PlatformLevelStartup.java | 4 +- .../startup/FeedUsersLocalStartupTask.java | 115 ++++++++++ .../org/sonar/server/user/UserUpdater.java | 2 +- .../FeedUsersLocalStartupTaskTest.java | 205 ++++++++++++++++++ .../java/org/sonar/api/CoreProperties.java | 2 + 5 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/startup/FeedUsersLocalStartupTask.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/startup/FeedUsersLocalStartupTaskTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 6451012791d..a7c7b04eb7e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -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 index 00000000000..7cb080eb22c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/FeedUsersLocalStartupTask.java @@ -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 SONAR-7254. + * + * 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 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 + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java index e7fc83ec6bf..63438b40112 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java @@ -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 index 00000000000..7b0531cfe37 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/FeedUsersLocalStartupTaskTest.java @@ -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(); + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index 457d19caa02..71895ffded4 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -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"; /** -- 2.39.5