diff options
27 files changed, 627 insertions, 110 deletions
diff --git a/it/it-tests/src/test/java/it/plugins/PluginsTest.java b/it/it-tests/src/test/java/it/plugins/PluginsTest.java index d9a42ec41c7..8203f31b473 100644 --- a/it/it-tests/src/test/java/it/plugins/PluginsTest.java +++ b/it/it-tests/src/test/java/it/plugins/PluginsTest.java @@ -48,6 +48,7 @@ import org.junit.Test; import org.junit.rules.ErrorCollector; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.Version; import static org.assertj.core.api.Assertions.fail; @@ -98,10 +99,11 @@ public class PluginsTest { // install latest compatible releases of plugins org.sonar.updatecenter.common.Version sonarVersion = org.sonar.updatecenter.common.Version.create(builder.getSonarVersion()); - builder.getUpdateCenter().setInstalledSonarVersion(sonarVersion); + Version sonarVersionWithoutPatch = Version.create(sonarVersion.getMajor() + "." + sonarVersion.getMinor()); + builder.getUpdateCenter().setInstalledSonarVersion(sonarVersionWithoutPatch); for (Plugin plugin : builder.getUpdateCenter().findAllCompatiblePlugins()) { if (!DISABLED_PLUGINS.contains(plugin.getKey())) { - Release release = plugin.getLastCompatibleRelease(sonarVersion); + Release release = plugin.getLastCompatibleRelease(sonarVersionWithoutPatch); if (release != null) { builder.setOrchestratorProperty(plugin.getKey() + "Version", release.getVersion().toString()); builder.addPlugin(plugin.getKey()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java index 1fada70fc31..47d03fab07b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java @@ -28,9 +28,11 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.user.UserSession; import org.sonar.updatecenter.common.PluginUpdate; import org.sonar.updatecenter.common.UpdateCenter; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_UPDATE_ORDERING; public class AvailableAction implements PluginsWsAction { @@ -38,10 +40,12 @@ public class AvailableAction implements PluginsWsAction { private static final boolean DO_NOT_FORCE_REFRESH = false; private static final String ARRAY_PLUGINS = "plugins"; + private final UserSession userSession; private final UpdateCenterMatrixFactory updateCenterFactory; private final PluginWSCommons pluginWSCommons; - public AvailableAction(UpdateCenterMatrixFactory updateCenterFactory, PluginWSCommons pluginWSCommons) { + public AvailableAction(UserSession userSession, UpdateCenterMatrixFactory updateCenterFactory, PluginWSCommons pluginWSCommons) { + this.userSession = userSession; this.updateCenterFactory = updateCenterFactory; this.pluginWSCommons = pluginWSCommons; } @@ -59,7 +63,8 @@ public class AvailableAction implements PluginsWsAction { "<li>INCOMPATIBLE: plugin is not compatible with current SonarQube instance.</li>" + "<li>REQUIRES_SYSTEM_UPGRADE: plugin requires SonarQube to be upgraded before being installed.</li>" + "<li>DEPS_REQUIRE_SYSTEM_UPGRADE: at least one plugin on which the plugin is dependent requires SonarQube to be upgraded.</li>" + - "</ul>") + "</ul>.<br/>" + + "Require 'Administer System' permission.") .setSince("5.2") .setHandler(this) .setResponseExample(Resources.getResource(this.getClass(), "example-available_plugins.json")); @@ -67,6 +72,7 @@ public class AvailableAction implements PluginsWsAction { @Override public void handle(Request request, Response response) throws Exception { + userSession.checkPermission(SYSTEM_ADMIN); JsonWriter jsonWriter = response.newJsonWriter(); jsonWriter.beginObject(); @@ -93,7 +99,7 @@ public class AvailableAction implements PluginsWsAction { return ImmutableSortedSet.copyOf( NAME_KEY_PLUGIN_UPDATE_ORDERING, updateCenter.findAvailablePlugins() - ); + ); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java index 4c1034713b6..bd81e9dd6d4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java @@ -32,11 +32,13 @@ import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.platform.PluginInfo; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.user.UserSession; import org.sonar.updatecenter.common.Plugin; import static com.google.common.collect.ImmutableSortedSet.copyOf; import static java.lang.String.format; import static java.util.Collections.singleton; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR; import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey; @@ -47,11 +49,13 @@ public class InstalledAction implements PluginsWsAction { private static final String ARRAY_PLUGINS = "plugins"; private static final String FIELD_CATEGORY = "category"; + private final UserSession userSession; private final ServerPluginRepository pluginRepository; private final PluginWSCommons pluginWSCommons; private final UpdateCenterMatrixFactory updateCenterMatrixFactory; - public InstalledAction(ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { + public InstalledAction(UserSession userSession, ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { + this.userSession = userSession; this.pluginRepository = pluginRepository; this.pluginWSCommons = pluginWSCommons; this.updateCenterMatrixFactory = updateCenterMatrixFactory; @@ -60,7 +64,8 @@ public class InstalledAction implements PluginsWsAction { @Override public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction("installed") - .setDescription("Get the list of all the plugins installed on the SonarQube instance, sorted by plugin name") + .setDescription("Get the list of all the plugins installed on the SonarQube instance, sorted by plugin name.<br/>" + + "Require 'Administer System' permission.") .setSince("5.2") .setHandler(this) .setResponseExample(Resources.getResource(this.getClass(), "example-installed_plugins.json")); @@ -75,6 +80,7 @@ public class InstalledAction implements PluginsWsAction { @Override public void handle(Request request, Response response) throws Exception { + userSession.checkPermission(SYSTEM_ADMIN); Collection<PluginInfo> pluginInfoList = searchPluginInfoList(); JsonWriter jsonWriter = response.newJsonWriter(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java index 8a79908e9aa..5ee8a989ce8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java @@ -35,11 +35,13 @@ import org.sonar.core.platform.PluginInfo; import org.sonar.server.plugins.PluginDownloader; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.user.UserSession; import org.sonar.updatecenter.common.Plugin; import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.ImmutableSet.copyOf; import static com.google.common.io.Resources.getResource; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey; /** @@ -51,14 +53,16 @@ public class PendingAction implements PluginsWsAction { private static final String ARRAY_REMOVING = "removing"; private static final String ARRAY_UPDATING = "updating"; + private final UserSession userSession; private final PluginDownloader pluginDownloader; private final ServerPluginRepository installer; private final PluginWSCommons pluginWSCommons; private final UpdateCenterMatrixFactory updateCenterMatrixFactory; - public PendingAction(PluginDownloader pluginDownloader, - ServerPluginRepository installer, - PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { + public PendingAction(UserSession userSession, PluginDownloader pluginDownloader, + ServerPluginRepository installer, + PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { + this.userSession = userSession; this.pluginDownloader = pluginDownloader; this.installer = installer; this.pluginWSCommons = pluginWSCommons; @@ -68,7 +72,8 @@ public class PendingAction implements PluginsWsAction { @Override public void define(WebService.NewController controller) { controller.createAction("pending") - .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name") + .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name.<br/>" + + "Require 'Administer System' permission.") .setSince("5.2") .setHandler(this) .setResponseExample(getResource(this.getClass(), "example-pending_plugins.json")); @@ -76,6 +81,7 @@ public class PendingAction implements PluginsWsAction { @Override public void handle(Request request, Response response) throws Exception { + userSession.checkPermission(SYSTEM_ADMIN); ImmutableMap<String, Plugin> compatiblePluginsByKey = compatiblePluginsByKey(updateCenterMatrixFactory); JsonWriter jsonWriter = response.newJsonWriter(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java index 152c564ac02..d4834033145 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java @@ -33,10 +33,13 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.plugins.ws.PluginUpdateAggregator.PluginUpdateAggregate; +import org.sonar.server.user.UserSession; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; import org.sonar.updatecenter.common.UpdateCenter; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; + /** * Implementation of the {@code updates} action for the Plugins WebService. */ @@ -51,13 +54,15 @@ public class UpdatesAction implements PluginsWsAction { private static final Ordering<PluginUpdate> PLUGIN_UPDATE_BY_VERSION_ORDERING = Ordering.natural() .onResultOf(input -> input.getRelease().getVersion().toString()); + private final UserSession userSession; private final UpdateCenterMatrixFactory updateCenterMatrixFactory; private final PluginWSCommons pluginWSCommons; private final PluginUpdateAggregator aggregator; - public UpdatesAction(UpdateCenterMatrixFactory updateCenterMatrixFactory, - PluginWSCommons pluginWSCommons, - PluginUpdateAggregator aggregator) { + public UpdatesAction(UserSession userSession, UpdateCenterMatrixFactory updateCenterMatrixFactory, + PluginWSCommons pluginWSCommons, + PluginUpdateAggregator aggregator) { + this.userSession = userSession; this.updateCenterMatrixFactory = updateCenterMatrixFactory; this.pluginWSCommons = pluginWSCommons; this.aggregator = aggregator; @@ -72,7 +77,8 @@ public class UpdatesAction implements PluginsWsAction { "<br/>" + "Plugin information is retrieved from Update Center. Date and time at which Update Center was last refreshed is provided in the response." + "<br/>" + - "Update status values are: [COMPATIBLE, INCOMPATIBLE, REQUIRES_UPGRADE, DEPS_REQUIRE_UPGRADE]") + "Update status values are: [COMPATIBLE, INCOMPATIBLE, REQUIRES_UPGRADE, DEPS_REQUIRE_UPGRADE]<br/>." + + "Require 'Administer System' permission.") .setSince("5.2") .setHandler(this) .setResponseExample(Resources.getResource(this.getClass(), "example-updates_plugins.json")); @@ -80,6 +86,7 @@ public class UpdatesAction implements PluginsWsAction { @Override public void handle(Request request, Response response) throws Exception { + userSession.checkPermission(SYSTEM_ADMIN); JsonWriter jsonWriter = response.newJsonWriter(); jsonWriter.beginObject(); 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 index 5be1fdc2cc5..a5316f9b383 100644 --- 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 @@ -19,11 +19,16 @@ */ package org.sonar.server.startup; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.picocontainer.Startable; import org.sonar.api.config.Settings; +import org.sonar.api.security.SecurityRealm; import org.sonar.api.user.UserQuery; +import org.sonar.api.utils.MessageException; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -41,9 +46,9 @@ 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. - * + * <p> * See <a href="https://jira.sonarsource.com/browse/SONAR-7254">SONAR-7254</a>. - * + * <p> * Should be removed after LTS 5.X * * @since 5.5 @@ -59,11 +64,23 @@ public class FeedUsersLocalStartupTask implements Startable { private final DbClient dbClient; private final Settings settings; + private final SecurityRealm[] realms; + /** + * Used when no realm plugin + */ public FeedUsersLocalStartupTask(System2 system2, DbClient dbClient, Settings settings) { this.system2 = system2; this.dbClient = dbClient; this.settings = settings; + this.realms = new SecurityRealm[]{}; + } + + public FeedUsersLocalStartupTask(System2 system2, DbClient dbClient, Settings settings, SecurityRealm[] realms) { + this.system2 = system2; + this.dbClient = dbClient; + this.settings = settings; + this.realms = realms; } @Override @@ -86,12 +103,13 @@ public class FeedUsersLocalStartupTask implements Startable { } private void updateUsersLocal(DbSession dbSession) { + boolean realmConfExists = settings.getString(CORE_AUTHENTICATOR_REALM) != null; + checkConfigurationIfRealmExists(realmConfExists); 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(dbSession, UserQuery.ALL_ACTIVES)) { if (user.getExternalIdentityProvider().equals(UserUpdater.SQ_AUTHORITY)) { - user.setLocal(!isRealmExist || localUsers.contains(user.getLogin())); + user.setLocal(!realmConfExists || localUsers.contains(user.getLogin())); } else { user.setLocal(false); } @@ -100,6 +118,14 @@ public class FeedUsersLocalStartupTask implements Startable { } } + private void checkConfigurationIfRealmExists(boolean realmConfExists) { + List<String> realmNames = Arrays.stream(realms).map(SecurityRealm::getName).collect(Collectors.toList()); + if (!realmNames.isEmpty() && !realmConfExists) { + throw MessageException.of(String.format("External authentication plugin %s has been found, but no related configuration has been set. " + + "Either update your configuration or remove the plugin", realmNames)); + } + } + private boolean hasAlreadyBeenExecuted(DbSession dbSession) { return dbClient.loadedTemplateDao().countByTypeAndKey(ONE_SHOT_TASK_TYPE, TEMPLATE_KEY, dbSession) > 0; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java index 7f9d97d310b..b09e04e5d42 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java @@ -58,7 +58,9 @@ public class SearchAction implements UsersWsAction { @Override public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction("search") - .setDescription("Get a list of active users. Administer System permission is required to show the 'groups' field.") + .setDescription("Get a list of active users. <br/>" + + "Administer System permission is required to show the 'groups' field.<br/>" + + "When accessed anonymously, only logins and names are returned.") .setSince("3.6") .setHandler(this) .setResponseExample(getClass().getResource("search-example.json")); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java index f77a2fb985f..990d4db28ff 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java @@ -69,14 +69,16 @@ public class UserJsonWriter { json.beginObject(); json.prop(FIELD_LOGIN, user.getLogin()); writeIfNeeded(json, user.getName(), FIELD_NAME, fields); - writeIfNeeded(json, user.getEmail(), FIELD_EMAIL, fields); - writeIfNeeded(json, user.isActive(), FIELD_ACTIVE, fields); - writeIfNeeded(json, user.isLocal(), FIELD_LOCAL, fields); - writeIfNeeded(json, user.getExternalIdentity(), FIELD_EXTERNAL_IDENTITY, fields); - writeIfNeeded(json, user.getExternalIdentityProvider(), FIELD_EXTERNAL_PROVIDER, fields); - writeGroupsIfNeeded(json, groups, fields); - writeScmAccountsIfNeeded(json, fields, user); - writeTokensCount(json, tokensCount); + if (userSession.isLoggedIn()) { + writeIfNeeded(json, user.getEmail(), FIELD_EMAIL, fields); + writeIfNeeded(json, user.isActive(), FIELD_ACTIVE, fields); + writeIfNeeded(json, user.isLocal(), FIELD_LOCAL, fields); + writeIfNeeded(json, user.getExternalIdentity(), FIELD_EXTERNAL_IDENTITY, fields); + writeIfNeeded(json, user.getExternalIdentityProvider(), FIELD_EXTERNAL_PROVIDER, fields); + writeGroupsIfNeeded(json, groups, fields); + writeScmAccountsIfNeeded(json, fields, user); + writeTokensCount(json, tokensCount); + } json.endObject(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java index 1519c60cf7c..8c2df2adf86 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java @@ -38,6 +38,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.GroupDto; import org.sonar.server.es.SearchOptions; +import org.sonar.server.user.UserSession; import static org.sonar.server.es.SearchOptions.MAX_LIMIT; @@ -49,16 +50,19 @@ public class SearchAction implements UserGroupsWsAction { private static final String FIELD_MEMBERS_COUNT = "membersCount"; private static final List<String> ALL_FIELDS = Arrays.asList(FIELD_NAME, FIELD_DESCRIPTION, FIELD_MEMBERS_COUNT); - private DbClient dbClient; + private final DbClient dbClient; + private final UserSession userSession; - public SearchAction(DbClient dbClient) { + public SearchAction(DbClient dbClient, UserSession userSession) { this.dbClient = dbClient; + this.userSession = userSession; } @Override public void define(NewController context) { context.createAction("search") - .setDescription("Search for user groups") + .setDescription("Search for user groups <br>." + + "Require to be logged.") .setHandler(this) .setResponseExample(getClass().getResource("example-search.json")) .setSince("5.2") @@ -69,6 +73,7 @@ public class SearchAction implements UserGroupsWsAction { @Override public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); int page = request.mandatoryParamAsInt(Param.PAGE); int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE); SearchOptions options = new SearchOptions() diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java index f5d10cec26f..2371af692d5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java @@ -20,9 +20,13 @@ package org.sonar.server.plugins.ws; import com.google.common.base.Optional; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.DateUtils; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; @@ -33,6 +37,8 @@ import static com.google.common.collect.ImmutableList.of; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE; import static org.sonar.updatecenter.common.PluginUpdate.Status.DEPENDENCIES_REQUIRE_SONAR_UPGRADE; @@ -57,10 +63,17 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio .addOutgoingDependency(release(PLUGIN_1, "0.3.6")) .addOutgoingDependency(release(PLUGIN_2, "1.0.0")); - private AvailableAction underTest = new AvailableAction(updateCenterFactory, new PluginWSCommons()); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private AvailableAction underTest = new AvailableAction(userSession, updateCenterFactory, new PluginWSCommons()); @Test public void action_available_is_defined() { + loggedAsAdmin(); WsTester wsTester = new WsTester(); WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY); @@ -78,6 +91,7 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio @Test public void empty_array_is_returned_when_there_is_no_plugin_available() throws Exception { + loggedAsAdmin(); underTest.handle(request, response); assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST); @@ -85,6 +99,7 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio @Test public void empty_array_is_returned_when_update_center_is_not_accessible() throws Exception { + loggedAsAdmin(); when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent()); underTest.handle(request, response); @@ -94,9 +109,10 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio @Test public void verify_properties_displayed_in_json_per_plugin() throws Exception { + loggedAsAdmin(); when(updateCenter.findAvailablePlugins()).thenReturn(of( pluginUpdate(FULL_PROPERTIES_PLUGIN_RELEASE, COMPATIBLE) - )); + )); underTest.handle(request, response); @@ -105,28 +121,40 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio @Test public void status_COMPATIBLE_is_displayed_COMPATIBLE_in_JSON() throws Exception { + loggedAsAdmin(); checkStatusDisplayedInJson(COMPATIBLE, "COMPATIBLE"); } @Test public void status_INCOMPATIBLE_is_displayed_INCOMPATIBLE_in_JSON() throws Exception { + loggedAsAdmin(); checkStatusDisplayedInJson(INCOMPATIBLE, "INCOMPATIBLE"); } @Test public void status_REQUIRE_SONAR_UPGRADE_is_displayed_REQUIRES_SYSTEM_UPGRADE_in_JSON() throws Exception { + loggedAsAdmin(); checkStatusDisplayedInJson(REQUIRE_SONAR_UPGRADE, "REQUIRES_SYSTEM_UPGRADE"); } @Test public void status_DEPENDENCIES_REQUIRE_SONAR_UPGRADE_is_displayed_DEPS_REQUIRE_SYSTEM_UPGRADE_in_JSON() throws Exception { + loggedAsAdmin(); checkStatusDisplayedInJson(DEPENDENCIES_REQUIRE_SONAR_UPGRADE, "DEPS_REQUIRE_SYSTEM_UPGRADE"); } + @Test + public void fail_when_user_is_not_admin() throws Exception { + userSession.login("user").setGlobalPermissions(DASHBOARD_SHARING); + + expectedException.expect(ForbiddenException.class); + underTest.handle(request, response); + } + private void checkStatusDisplayedInJson(PluginUpdate.Status status, String expectedValue) throws Exception { when(updateCenter.findAvailablePlugins()).thenReturn(of( pluginUpdate(release(PLUGIN_1, "1.0.0"), status) - )); + )); underTest.handle(request, response); @@ -140,17 +168,11 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio " }" + " ]" + "}" - ); + ); } - @Test - public void plugins_are_sorted_by_name_then_key_and_made_unique() { - when(updateCenter.findAvailablePlugins()).thenReturn(of( - pluginUpdate("key2", "name2"), - pluginUpdate("key1", "name2"), - pluginUpdate("key2", "name2"), - pluginUpdate("key0", "name0"), - pluginUpdate("key1", "name1") - )); + private void loggedAsAdmin() { + userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java index 58c1dd49a57..96d8c30655c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java @@ -24,13 +24,16 @@ import java.io.File; import java.util.Arrays; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.core.platform.PluginInfo; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.UpdateCenter; @@ -43,6 +46,8 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.test.JsonAssert.assertJson; public class InstalledActionTest { @@ -55,16 +60,25 @@ public class InstalledActionTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS); - InstalledAction underTest = new InstalledAction(pluginRepository, new PluginWSCommons(), updateCenterMatrixFactory); + InstalledAction underTest = new InstalledAction(userSession, pluginRepository, new PluginWSCommons(), updateCenterMatrixFactory); Request request = mock(Request.class); WsTester.TestResponse response = new WsTester.TestResponse(); + + @Test public void action_installed_is_defined() { + loggedAsSystemAdmin(); WsTester wsTester = new WsTester(); WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY); @@ -82,6 +96,7 @@ public class InstalledActionTest { @Test public void empty_array_is_returned_when_there_is_not_plugin_installed() throws Exception { + loggedAsSystemAdmin(); underTest.handle(request, response); assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST); @@ -89,6 +104,7 @@ public class InstalledActionTest { @Test public void empty_array_when_update_center_is_unavailable() throws Exception { + loggedAsSystemAdmin(); when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.<UpdateCenter>absent()); underTest.handle(request, response); @@ -98,6 +114,7 @@ public class InstalledActionTest { @Test public void empty_fields_are_not_serialized_to_json() throws Exception { + loggedAsSystemAdmin(); when(pluginRepository.getPluginInfos()).thenReturn( of(new PluginInfo("").setName(""))); @@ -108,6 +125,7 @@ public class InstalledActionTest { @Test public void verify_properties_displayed_in_json_per_plugin() throws Exception { + loggedAsSystemAdmin(); String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; when(pluginRepository.getPluginInfos()).thenReturn(of( new PluginInfo("plugKey") @@ -122,7 +140,7 @@ public class InstalledActionTest { .setImplementationBuild("sou_rev_sha1") .setJarFile(new File(getClass().getResource(jarFilename).toURI())) ) - ); + ); underTest.handle(request, response); @@ -145,11 +163,12 @@ public class InstalledActionTest { " }" + " ]" + "}" - ); + ); } @Test public void category_is_returned_when_in_additional_fields() throws Exception { + loggedAsSystemAdmin(); String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; when(pluginRepository.getPluginInfos()).thenReturn(of( new PluginInfo("plugKey") @@ -201,6 +220,7 @@ public class InstalledActionTest { @Test public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws Exception { + loggedAsSystemAdmin(); when(pluginRepository.getPluginInfos()).thenReturn( of( plugin("A", "name2"), @@ -208,7 +228,7 @@ public class InstalledActionTest { plugin("C", "name0"), plugin("D", "name0") ) - ); + ); underTest.handle(request, response); @@ -222,17 +242,18 @@ public class InstalledActionTest { " {\"key\": \"A\"}" + " ]" + "}" - ); + ); } @Test public void only_one_plugin_can_have_a_specific_name_and_key() throws Exception { + loggedAsSystemAdmin(); when(pluginRepository.getPluginInfos()).thenReturn( of( plugin("A", "name2"), plugin("A", "name2") ) - ); + ); underTest.handle(request, response); @@ -243,11 +264,24 @@ public class InstalledActionTest { " {\"key\": \"A\"}" + " ]" + "}" - ); + ); assertThat(response.outputAsString()).containsOnlyOnce("name2"); } + @Test + public void fail_when_user_is_not_sys_admin() throws Exception { + userSession.login("user").setGlobalPermissions(DASHBOARD_SHARING); + + expectedException.expect(ForbiddenException.class); + underTest.handle(request, response); + } + private PluginInfo plugin(String key, String name) { return new PluginInfo(key).setName(name).setVersion(Version.create("1.0")); } + + private void loggedAsSystemAdmin() { + userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java index 6a2cf6d56fd..233af975e3f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java @@ -22,13 +22,17 @@ package org.sonar.server.plugins.ws; import com.google.common.base.Optional; import java.util.ArrayList; import java.util.List; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.core.platform.PluginInfo; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.plugins.PluginDownloader; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.UpdateCenter; @@ -39,21 +43,30 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.test.JsonAssert.assertJson; public class PendingActionTest { private static final String DUMMY_CONTROLLER_KEY = "dummy"; + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + PluginDownloader pluginDownloader = mock(PluginDownloader.class); ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class); UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS); - PendingAction underTest = new PendingAction(pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory); + PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory); Request request = mock(Request.class); WsTester.TestResponse response = new WsTester.TestResponse(); @Test public void action_pending_is_defined() { + loggedAsSystemAdmin(); WsTester wsTester = new WsTester(); WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY); @@ -71,6 +84,7 @@ public class PendingActionTest { @Test public void empty_arrays_are_returned_when_there_nothing_pending() throws Exception { + loggedAsSystemAdmin(); underTest.handle(request, response); assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo( @@ -79,11 +93,12 @@ public class PendingActionTest { " \"removing\": []," + " \"updating\": []" + "}" - ); + ); } @Test public void empty_arrays_are_returned_when_update_center_is_unavailable() throws Exception { + loggedAsSystemAdmin(); when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.<UpdateCenter>absent()); underTest.handle(request, response); @@ -94,11 +109,12 @@ public class PendingActionTest { " \"removing\": []," + " \"updating\": []" + "}" - ); + ); } @Test public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception { + loggedAsSystemAdmin(); newUpdateCenter("scmgit"); when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(newScmGitPluginInfo())); @@ -125,11 +141,12 @@ public class PendingActionTest { " \"removing\": []," + " \"updating\": []" + "}" - ); + ); } @Test public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception { + loggedAsSystemAdmin(); when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo())); underTest.handle(request, response); @@ -154,11 +171,12 @@ public class PendingActionTest { " }" + " ]" + "}" - ); + ); } @Test public void verify_properties_displayed_in_json_per_updating_plugin() throws Exception { + loggedAsSystemAdmin(); newUpdateCenter("scmgit"); when(serverPluginRepository.getPluginInfos()).thenReturn(of(newScmGitPluginInfo())); when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(newScmGitPluginInfo())); @@ -181,6 +199,7 @@ public class PendingActionTest { @Test public void verify_properties_displayed_in_json_per_installing_removing_and_updating_plugins() throws Exception { + loggedAsSystemAdmin(); PluginInfo installed = newPluginInfo("java"); PluginInfo removedPlugin = newPluginInfo("js"); PluginInfo newPlugin = newPluginInfo("php"); @@ -218,11 +237,12 @@ public class PendingActionTest { @Test public void installing_plugins_are_sorted_by_name_then_key_and_are_unique() throws Exception { + loggedAsSystemAdmin(); when(pluginDownloader.getDownloadedPlugins()).thenReturn(of( newPluginInfo(0).setName("Foo"), newPluginInfo(3).setName("Bar"), newPluginInfo(2).setName("Bar") - )); + )); underTest.handle(request, response); @@ -246,16 +266,17 @@ public class PendingActionTest { " \"removing\": []," + " \"updating\": []" + "}" - ); + ); } @Test public void removing_plugins_are_sorted_and_unique() throws Exception { + loggedAsSystemAdmin(); when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of( newPluginInfo(0).setName("Foo"), newPluginInfo(3).setName("Bar"), newPluginInfo(2).setName("Bar") - )); + )); underTest.handle(request, response); @@ -279,10 +300,18 @@ public class PendingActionTest { " }" + " ]" + "}" - ); + ); } - public PluginInfo newScmGitPluginInfo() { + @Test + public void fail_when_user_is_not_sys_admin() throws Exception { + userSession.login("user").setGlobalPermissions(DASHBOARD_SHARING); + + expectedException.expect(ForbiddenException.class); + underTest.handle(request, response); + } + + private PluginInfo newScmGitPluginInfo() { return new PluginInfo("scmgit") .setName("Git") .setDescription("Git SCM Provider.") @@ -295,11 +324,11 @@ public class PendingActionTest { .setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07"); } - public PluginInfo newPluginInfo(String key){ + private PluginInfo newPluginInfo(String key) { return new PluginInfo(key); } - private UpdateCenter newUpdateCenter(String... pluginKeys){ + private UpdateCenter newUpdateCenter(String... pluginKeys) { UpdateCenter updateCenter = mock(UpdateCenter.class); when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter)); List<Plugin> plugins = new ArrayList<>(); @@ -310,7 +339,11 @@ public class PendingActionTest { return updateCenter; } - public PluginInfo newPluginInfo(int id) { + private PluginInfo newPluginInfo(int id) { return new PluginInfo("key" + id).setName("name" + id); } + + private void loggedAsSystemAdmin() { + userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginsWsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginsWsMediumTest.java index b0ba63bf886..da84e11fd59 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginsWsMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginsWsMediumTest.java @@ -28,11 +28,12 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.core.permission.GlobalPermissions; import org.sonar.server.tester.ServerTester; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; + public class PluginsWsMediumTest { private static final String ENCODING = "UTF-8"; @@ -52,6 +53,7 @@ public class PluginsWsMediumTest { WsTester wsTester = new WsTester(serverTester.get(PluginsWs.class)); // 1 - check what's installed, available and pending + userSessionRule.login().setGlobalPermissions(SYSTEM_ADMIN); wsTester.newGetRequest("api/plugins", "installed").execute().assertJson("{" + " \"plugins\": [" + " {" + @@ -79,8 +81,7 @@ public class PluginsWsMediumTest { wsTester.newGetRequest("api/plugins", "pending").execute().assertJson("{\"installing\":[],\"removing\":[],\"updating\":[]}"); - // 2 - login as admin and install one plugin, update another, verify pending status in the process - userSessionRule.login().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + // 2 - install one plugin, update another, verify pending status in the process wsTester.newPostRequest("api/plugins", "update").setParam("key", "decoy").execute().assertNoContent(); wsTester.newGetRequest("api/plugins", "pending").execute().assertJson("{" + @@ -116,6 +117,7 @@ public class PluginsWsMediumTest { wsTester = restartServerTester(); // 4 - make sure plugin is installed + userSessionRule.login().setGlobalPermissions(SYSTEM_ADMIN); wsTester.newGetRequest("api/plugins", "installed").execute().assertJson("{" + " \"plugins\": [" + " {" + @@ -130,8 +132,7 @@ public class PluginsWsMediumTest { "}" ); - // 5 - login as admin again and uninstall a plugin, verify pending status in the process - userSessionRule.login().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + // 5 - uninstall a plugin, verify pending status in the process wsTester.newPostRequest("api/plugins", "uninstall").setParam("key", "foo").execute().assertNoContent(); wsTester.newGetRequest("api/plugins", "pending").execute().assertJson("{" + @@ -149,6 +150,7 @@ public class PluginsWsMediumTest { wsTester = restartServerTester(); // 7 - make sure plugin has been uninstalled + userSessionRule.login().setGlobalPermissions(SYSTEM_ADMIN); wsTester.newGetRequest("api/plugins", "installed").execute().assertJson("{" + " \"plugins\": [" + " {" + diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesActionTest.java index 64589dbe2cb..8fc66f8ac1d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdatesActionTest.java @@ -19,9 +19,13 @@ */ package org.sonar.server.plugins.ws; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.DateUtils; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.Release; @@ -29,6 +33,8 @@ import org.sonar.updatecenter.common.Release; import static com.google.common.collect.ImmutableList.of; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE; import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE; @@ -70,12 +76,19 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT .addOutgoingDependency(release(JAVA_PLUGIN, "1.0")); - private UpdatesAction underTest = new UpdatesAction(updateCenterFactory, + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private UpdatesAction underTest = new UpdatesAction(userSession, updateCenterFactory, new PluginWSCommons(), new PluginUpdateAggregator() ); @Test public void action_updatable_is_defined() { + loggedAsSystemAdmin(); WsTester wsTester = new WsTester(); WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY); @@ -93,6 +106,7 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT @Test public void empty_array_is_returned_when_there_is_no_plugin_available() throws Exception { + loggedAsSystemAdmin(); underTest.handle(request, response); assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST); @@ -100,6 +114,7 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT @Test public void verify_response_against_example() throws Exception { + loggedAsSystemAdmin(); when(updateCenter.findPluginUpdates()).thenReturn(of( pluginUpdate(ABAP_32, COMPATIBLE), pluginUpdate(ABAP_31, INCOMPATIBLE), @@ -113,6 +128,7 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT @Test public void status_COMPATIBLE_is_displayed_COMPATIBLE_in_JSON() throws Exception { + loggedAsSystemAdmin(); when(updateCenter.findPluginUpdates()).thenReturn(of( pluginUpdate(release(PLUGIN_1, "1.0.0"), COMPATIBLE) )); @@ -136,6 +152,7 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT @Test public void plugins_are_sorted_by_name_and_made_unique() throws Exception { + loggedAsSystemAdmin(); when(updateCenter.findPluginUpdates()).thenReturn(of( pluginUpdate("key2", "name2"), pluginUpdate("key2", "name2"), @@ -164,4 +181,16 @@ public class UpdatesActionTest extends AbstractUpdateCenterBasedPluginsWsActionT "}" ); } + + @Test + public void fail_when_user_is_not_sys_admin() throws Exception { + userSession.login("user").setGlobalPermissions(DASHBOARD_SHARING); + + expectedException.expect(ForbiddenException.class); + underTest.handle(request, response); + } + + private void loggedAsSystemAdmin() { + userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); + } } 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 index 7b0531cfe37..3de4c5f72e5 100644 --- 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 @@ -22,7 +22,10 @@ package org.sonar.server.startup; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.config.Settings; +import org.sonar.api.security.SecurityRealm; +import org.sonar.api.utils.MessageException; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; @@ -46,6 +49,11 @@ public class FeedUsersLocalStartupTaskTest { static final String USER1_LOGIN = "USER1"; static final String USER2_LOGIN = "USER2"; + static final String REALM_NAME = "LDAP"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + System2 system2 = mock(System2.class); @Rule @@ -62,7 +70,7 @@ public class FeedUsersLocalStartupTaskTest { Settings settings = new Settings(); - FeedUsersLocalStartupTask underTest = new FeedUsersLocalStartupTask(system2, dbTester.getDbClient(), settings); + FeedUsersLocalStartupTask underTest; @Before public void setUp() throws Exception { @@ -71,7 +79,8 @@ public class FeedUsersLocalStartupTaskTest { @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"); + initTaskWithRealm(); + settings.setProperty("sonar.security.realm", REALM_NAME); settings.setProperty("sonar.security.localUsers", USER1_LOGIN); UserDto user = addUser(USER1_LOGIN, false, "sonarqube"); @@ -83,7 +92,8 @@ public class FeedUsersLocalStartupTaskTest { @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"); + initTaskWithRealm(); + settings.setProperty("sonar.security.realm", REALM_NAME); UserDto user = addUser(USER1_LOGIN, false, "sonarqube"); underTest.start(); @@ -94,7 +104,8 @@ public class FeedUsersLocalStartupTaskTest { @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"); + initTaskWithRealm(); + settings.setProperty("sonar.security.realm", REALM_NAME); settings.setProperty("sonar.security.localUsers", USER2_LOGIN); UserDto user = addUser(USER1_LOGIN, true, "sonarqube"); @@ -106,6 +117,7 @@ public class FeedUsersLocalStartupTaskTest { @Test public void set_user_as_local_when_id_provider_is_sonarqube_and_no_realm() throws Exception { + initTaskWithNoRealm(); settings.setProperty("sonar.security.realm", (String) null); UserDto user = addUser(USER1_LOGIN, false, "sonarqube"); @@ -116,7 +128,8 @@ public class FeedUsersLocalStartupTaskTest { } @Test - public void set_user_as_not_local_when_external_identiy_is_not_sonarqube() throws Exception { + public void set_user_as_not_local_when_external_identity_is_not_sonarqube() throws Exception { + initTaskWithNoRealm(); UserDto user = addUser(USER1_LOGIN, false, "github"); underTest.start(); @@ -127,7 +140,8 @@ public class FeedUsersLocalStartupTaskTest { @Test public void does_not_update_removed_user() throws Exception { - settings.setProperty("sonar.security.realm", "LDAP"); + initTaskWithRealm(); + settings.setProperty("sonar.security.realm", REALM_NAME); UserDto user = UserTesting.newUserDto() .setLogin(USER1_LOGIN) @@ -148,7 +162,8 @@ public class FeedUsersLocalStartupTaskTest { @Test public void does_nothing_when_task_has_already_been_executed() throws Exception { - settings.setProperty("sonar.security.realm", "LDAP"); + initTaskWithRealm(); + settings.setProperty("sonar.security.realm", REALM_NAME); settings.setProperty("sonar.security.localUsers", USER1_LOGIN); UserDto user = addUser(USER1_LOGIN, false, "github"); @@ -168,6 +183,16 @@ public class FeedUsersLocalStartupTaskTest { verifyEmptyLog(); } + @Test + public void fail_when_realm_found_but_no_configuration() throws Exception { + initTask(createRealm("REALM1"), createRealm("REALM2")); + + expectedException.expect(MessageException.class); + expectedException.expectMessage("External authentication plugin [REALM1, REALM2] has been found, but no related configuration has been set. " + + "Either update your configuration or remove the plugin"); + underTest.start(); + } + private UserDto addUser(String login, boolean local, String externalIdentityProvider) { UserDto user = UserTesting.newUserDto() .setLogin(login) @@ -202,4 +227,25 @@ public class FeedUsersLocalStartupTaskTest { assertThat(logTester.logs(LoggerLevel.INFO)).isEmpty(); } + private void initTask(SecurityRealm... realms) { + if (realms.length == 0) { + underTest = new FeedUsersLocalStartupTask(system2, dbTester.getDbClient(), settings); + } else { + underTest = new FeedUsersLocalStartupTask(system2, dbTester.getDbClient(), settings, realms); + } + } + + private void initTaskWithRealm() { + initTask(createRealm(REALM_NAME)); + } + + private void initTaskWithNoRealm() { + initTask(); + } + + private static SecurityRealm createRealm(String name) { + SecurityRealm realm = mock(SecurityRealm.class); + when(realm.getName()).thenReturn(name); + return realm; + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java index bbd00030f9c..bb72587def8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.user.ws; import com.google.common.collect.Lists; import java.util.List; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonar.api.config.Settings; @@ -36,11 +37,13 @@ import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDbTester; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserGroupDto; +import org.sonar.db.user.UserTesting; import org.sonar.server.es.EsTester; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.user.index.UserDoc; import org.sonar.server.user.index.UserIndex; import org.sonar.server.user.index.UserIndexDefinition; +import org.sonar.server.user.index.UserIndexer; import org.sonar.server.ws.WsTester; import static com.google.common.collect.Lists.newArrayList; @@ -68,14 +71,9 @@ public class SearchActionTest { DbClient dbClient = db.getDbClient(); DbSession dbSession = db.getSession(); - WsTester ws; - UserIndex index; - - @Before - public void setUp() { - index = new UserIndex(esTester.client()); - ws = new WsTester(new UsersWs(new SearchAction(index, dbClient, new UserJsonWriter(userSession)))); - } + UserIndex index = new UserIndex(esTester.client()); + UserIndexer userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true); + WsTester ws = new WsTester(new UsersWs(new SearchAction(index, dbClient, new UserJsonWriter(userSession)))); @Test public void search_json_example() throws Exception { @@ -100,7 +98,7 @@ public class SearchActionTest { } dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(fmallet.getLogin())); db.commit(); - esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, toUserDoc(fmallet), toUserDoc(simon)); + userIndexer.index(); loginAsAdmin(); String response = ws.newGetRequest("api/users", "search").execute().outputAsString(); @@ -110,11 +108,13 @@ public class SearchActionTest { @Test public void search_empty() throws Exception { + loginAsSimpleUser(); ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "empty.json"); } @Test public void search_without_parameters() throws Exception { + loginAsSimpleUser(); injectUsers(5); ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "five_users.json"); @@ -122,6 +122,7 @@ public class SearchActionTest { @Test public void search_with_query() throws Exception { + loginAsSimpleUser(); injectUsers(5); UserDto user = userDb.insertUser( newUserDto("user-%_%-login", "user-name", "user@mail.com") @@ -141,6 +142,7 @@ public class SearchActionTest { @Test public void search_with_paging() throws Exception { + loginAsSimpleUser(); injectUsers(10); ws.newGetRequest("api/users", "search").setParam(Param.PAGE_SIZE, "5").execute().assertJson(getClass(), "page_one.json"); @@ -149,6 +151,7 @@ public class SearchActionTest { @Test public void search_with_fields() throws Exception { + loginAsSimpleUser(); injectUsers(1); assertThat(ws.newGetRequest("api/users", "search").execute().outputAsString()) @@ -198,6 +201,7 @@ public class SearchActionTest { @Test public void search_with_groups() throws Exception { + loginAsAdmin(); List<UserDto> users = injectUsers(1); GroupDto group1 = dbClient.groupDao().insert(dbSession, new GroupDto().setName("sonar-users")); @@ -206,14 +210,31 @@ public class SearchActionTest { dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(group2.getId()).setUserId(users.get(0).getId())); dbSession.commit(); - loginAsAdmin(); ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "user_with_groups.json"); } + @Test + public void only_return_login_and_name_when_not_logged() throws Exception { + userSession.anonymous(); + + dbClient.userDao().insert(dbSession, UserTesting.newUserDto("john", "John", "john@email.com")); + dbSession.commit(); + userIndexer.index(); + + ws.newGetRequest("api/users", "search").execute().assertJson( + "{" + + " \"users\": [" + + " {" + + " \"login\": \"john\"," + + " \"name\": \"John\"" + + " }" + + " ]" + + "}"); + } + private List<UserDto> injectUsers(int numberOfUsers) throws Exception { List<UserDto> userDtos = Lists.newArrayList(); long createdAt = System.currentTimeMillis(); - UserDoc[] users = new UserDoc[numberOfUsers]; for (int index = 0; index < numberOfUsers; index++) { String email = String.format("user-%d@mail.com", index); String login = String.format("user-%d", index); @@ -233,8 +254,6 @@ public class SearchActionTest { .setUpdatedAt(createdAt)); userDtos.add(userDto); - users[index] = toUserDoc(userDto); - for (int tokenIndex = 0; tokenIndex < index; tokenIndex++) { dbClient.userTokenDao().insert(dbSession, newUserToken() .setLogin(login) @@ -242,22 +261,16 @@ public class SearchActionTest { } } dbSession.commit(); - esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, users); + userIndexer.index(); return userDtos; } - private static UserDoc toUserDoc(UserDto dto) { - return new UserDoc() - .setActive(dto.isActive()) - .setCreatedAt(dto.getCreatedAt()) - .setEmail(dto.getEmail()) - .setLogin(dto.getLogin()) - .setName(dto.getName()) - .setScmAccounts(dto.getScmAccountsAsList()) - .setUpdatedAt(dto.getUpdatedAt()); - } - private void loginAsAdmin() { userSession.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); } + + private void loginAsSimpleUser() { + userSession.login("user"); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java index 605db91747d..b8c65d582db 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java @@ -23,15 +23,17 @@ import org.apache.commons.lang.StringUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.user.GroupDao; -import org.sonar.db.user.GroupMembershipDao; import org.sonar.db.user.UserGroupDao; import org.sonar.db.user.UserGroupDto; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import static org.assertj.core.api.Assertions.assertThat; @@ -42,10 +44,16 @@ public class SearchActionTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private WsTester ws; private GroupDao groupDao; - private GroupMembershipDao groupMembershipDao; private UserGroupDao userGroupDao; private DbSession dbSession; @@ -53,21 +61,22 @@ public class SearchActionTest { public void setUp() { DbClient dbClient = db.getDbClient(); groupDao = dbClient.groupDao(); - groupMembershipDao = dbClient.groupMembershipDao(); userGroupDao = dbClient.userGroupDao(); - ws = new WsTester(new UserGroupsWs(new SearchAction(dbClient))); + ws = new WsTester(new UserGroupsWs(new SearchAction(dbClient, userSession))); dbSession = dbClient.openSession(false); } @Test public void search_empty() throws Exception { + loginAsSimpleUser(); newRequest().execute().assertJson(getClass(), "empty.json"); } @Test public void search_without_parameters() throws Exception { + loginAsSimpleUser(); insertGroups("users", "admins", "customer1", "customer2", "customer3"); dbSession.commit(); @@ -76,6 +85,7 @@ public class SearchActionTest { @Test public void search_with_members() throws Exception { + loginAsSimpleUser(); insertGroups("users", "admins", "customer1", "customer2", "customer3"); insertMembers("users", 5); insertMembers("admins", 1); @@ -87,6 +97,7 @@ public class SearchActionTest { @Test public void search_with_query() throws Exception { + loginAsSimpleUser(); insertGroups("users", "admins", "customer%_%/1", "customer%_%/2", "customer%_%/3"); dbSession.commit(); @@ -95,6 +106,7 @@ public class SearchActionTest { @Test public void search_with_paging() throws Exception { + loginAsSimpleUser(); insertGroups("users", "admins", "customer1", "customer2", "customer3"); dbSession.commit(); @@ -108,6 +120,7 @@ public class SearchActionTest { @Test public void search_with_fields() throws Exception { + loginAsSimpleUser(); insertGroups("sonar-users"); dbSession.commit(); @@ -142,6 +155,14 @@ public class SearchActionTest { .contains("membersCount"); } + @Test + public void fail_when_not_logged() throws Exception { + userSession.anonymous(); + + expectedException.expect(UnauthorizedException.class); + newRequest().execute(); + } + private WsTester.TestRequest newRequest() { return ws.newGetRequest("api/user_groups", "search"); } @@ -160,4 +181,9 @@ public class SearchActionTest { userGroupDao.insert(dbSession, new UserGroupDto().setGroupId(groupId).setUserId((long) i + 1)); } } + + private void loginAsSimpleUser() { + userSession.login("user"); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UserGroupsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UserGroupsWsTest.java index ba1a32bacd7..2e8b7a6755e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UserGroupsWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UserGroupsWsTest.java @@ -32,14 +32,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; public class UserGroupsWsTest { + @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); + WebService.Controller controller; @Before public void setUp() { WsTester tester = new WsTester(new UserGroupsWs( - new SearchAction(mock(DbClient.class)), + new SearchAction(mock(DbClient.class), mock(UserSession.class)), new CreateAction(mock(DbClient.class), mock(UserSession.class), mock(UserGroupUpdater.class)))); controller = tester.controller("api/user_groups"); } diff --git a/server/sonar-web/src/main/js/apps/groups/list-view.js b/server/sonar-web/src/main/js/apps/groups/list-view.js index 20280b7c7c4..cca2de036ae 100644 --- a/server/sonar-web/src/main/js/apps/groups/list-view.js +++ b/server/sonar-web/src/main/js/apps/groups/list-view.js @@ -19,10 +19,12 @@ */ import Marionette from 'backbone.marionette'; import ListItemView from './list-item-view'; +import Template from './templates/groups-list.hbs'; -export default Marionette.CollectionView.extend({ - tagName: 'ul', +export default Marionette.CompositeView.extend({ childView: ListItemView, + childViewContainer: '.js-list', + template: Template, collectionEvents: { 'request': 'showLoading', @@ -35,6 +37,10 @@ export default Marionette.CollectionView.extend({ hideLoading () { this.$el.removeClass('new-loading'); + + const query = this.collection.q || ''; + const shouldHideAnyone = !'anyone'.includes(query.toLowerCase()); + this.$('.js-anyone').toggleClass('hidden', shouldHideAnyone); } }); diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs new file mode 100644 index 00000000000..71820036560 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs @@ -0,0 +1,18 @@ +<div> + <div class="panel panel-vertical js-anyone"> + <div class="display-inline-block text-top width-20"> + <strong class="js-group-name">Anyone</strong> + </div> + + <div class="display-inline-block text-top big-spacer-left width-25"> + + </div> + + <div class="display-inline-block text-top big-spacer-left width-40"> + <span class="js-group-description">{{t 'user_groups.anyone.description'}}</span> + </div> + + </div> + + <ul class="js-list"></ul> +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1153_update_users_external_identity_when_empty.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1153_update_users_external_identity_when_empty.rb new file mode 100644 index 00000000000..fdfe843b69a --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1153_update_users_external_identity_when_empty.rb @@ -0,0 +1,31 @@ +# +# 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. +# + +# +# SonarQube 5.6.1 +# SONAR-7686 +# +class UpdateUsersExternalIdentityWhenEmpty < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v56.UpdateUsersExternalIdentityWhenEmpty') + end + +end diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java index 2c3be5b7f11..2e3b5f8bfcb 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java +++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java @@ -83,6 +83,7 @@ import org.sonar.db.version.v55.FeedRulesLongDateColumns; import org.sonar.db.version.v55.FeedRulesTypes; import org.sonar.db.version.v56.FixLengthOfIssuesMessageOnOracle; import org.sonar.db.version.v56.FixTypeOfRuleTypeOnMysql; +import org.sonar.db.version.v56.UpdateUsersExternalIdentityWhenEmpty; import org.sonar.db.version.v60.AddAnalysisUuidColumnToCeActivity; import org.sonar.db.version.v60.AddAnalysisUuidColumnToEvents; import org.sonar.db.version.v60.AddAnalysisUuidColumnToMeasures; @@ -226,6 +227,7 @@ public class MigrationStepModule extends Module { // 5.6 FixTypeOfRuleTypeOnMysql.class, FixLengthOfIssuesMessageOnOracle.class, + UpdateUsersExternalIdentityWhenEmpty.class, // 6.0 AddUuidColumnsToResourceIndex.class, diff --git a/sonar-db/src/main/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmpty.java b/sonar-db/src/main/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmpty.java new file mode 100644 index 00000000000..68d5a5046d9 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmpty.java @@ -0,0 +1,69 @@ +/* + * 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.db.version.v56; + +import java.sql.SQLException; +import org.sonar.api.utils.System2; +import org.sonar.db.Database; +import org.sonar.db.version.BaseDataChange; +import org.sonar.db.version.MassUpdate; +import org.sonar.db.version.Select; +import org.sonar.db.version.SqlStatement; + +/** + * Update USERS.EXTERNAL_IDENTITY_PROVIDER to 'sonarqube' and USERS.EXTERNAL_IDENTITY to user's login when one of this 2 columns is null + */ +public class UpdateUsersExternalIdentityWhenEmpty extends BaseDataChange { + + private final System2 system2; + + public UpdateUsersExternalIdentityWhenEmpty(Database db, System2 system2) { + super(db); + this.system2 = system2; + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("SELECT u.id, u.login FROM users u WHERE external_identity_provider IS NULL OR external_identity IS NULL"); + massUpdate.update("UPDATE users SET external_identity_provider=?, external_identity=?, updated_at=? WHERE id=?"); + massUpdate.rowPluralName("users"); + massUpdate.execute(new MigrationHandler(system2.now())); + } + + private static class MigrationHandler implements MassUpdate.Handler { + + private final long now; + + public MigrationHandler(long now) { + this.now = now; + } + + @Override + public boolean handle(Select.Row row, SqlStatement update) throws SQLException { + update.setString(1, "sonarqube"); + update.setString(2, row.getString(2)); + update.setLong(3, now); + update.setLong(4, row.getLong(1)); + return true; + } + } + +} diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql index 5e0c15f1de8..fff413c49dd 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -405,6 +405,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1125'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1150'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1151'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1152'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1153'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1200'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1201'); diff --git a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java index b10eba51a6f..579506d38b1 100644 --- a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java +++ b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java @@ -29,6 +29,6 @@ public class MigrationStepModuleTest { public void verify_count_of_added_MigrationStep_types() { ComponentContainer container = new ComponentContainer(); new MigrationStepModule().configure(container); - assertThat(container.size()).isEqualTo(125); + assertThat(container.size()).isEqualTo(126); } } diff --git a/sonar-db/src/test/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest.java b/sonar-db/src/test/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest.java new file mode 100644 index 00000000000..7f0abdcc7db --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest.java @@ -0,0 +1,104 @@ +/* + * 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.db.version.v56; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.version.MigrationStep; + +import static com.google.common.collect.Maps.newHashMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UpdateUsersExternalIdentityWhenEmptyTest { + + @Rule + public DbTester db = DbTester.createForSchema(System2.INSTANCE, UpdateUsersExternalIdentityWhenEmptyTest.class, "schema.sql"); + + static final long PAST = 1_000_000_000_000L; + static final long NOW = 1_500_000_000_000L; + + System2 system = mock(System2.class); + + MigrationStep underTest = new UpdateUsersExternalIdentityWhenEmpty(db.database(), system); + + @Before + public void setUp() throws Exception { + when(system.now()).thenReturn(NOW); + } + + @Test + public void migrate_users() throws Exception { + insertUser("user-without-eternal-identity", null, null, PAST); + insertUser("user-with-only-eternal-identity-provider", "github", null, PAST); + insertUser("user-with-only-eternal-identity", null, "login1", PAST); + insertUser("user-with-both-eternal-identity", "github", "login2", PAST); + + underTest.execute(); + + checkUserIsUpdated("user-without-eternal-identity"); + checkUserIsUpdated("user-with-only-eternal-identity-provider"); + checkUserIsUpdated("user-with-only-eternal-identity"); + + checkUserIsNotUpdated("user-with-both-eternal-identity"); + } + + @Test + public void doest_not_fail_when_no_user() throws Exception { + underTest.execute(); + } + + private void insertUser(String login, @Nullable String externalIdentity, @Nullable String externalIdentityProvider, long updatedAt) { + Map<String, String> params = newHashMap(ImmutableMap.of( + "LOGIN", login, + "CREATED_AT", Long.toString(PAST), + "UPDATED_AT", Long.toString(updatedAt))); + if (externalIdentity != null) { + params.put("EXTERNAL_IDENTITY", externalIdentity); + } + if (externalIdentityProvider != null) { + params.put("EXTERNAL_IDENTITY_PROVIDER", externalIdentityProvider); + } + + db.executeInsert("users", params); + } + + private void checkUserIsUpdated(String login) { + Map<String, Object> row = db.selectFirst("select EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, UPDATED_AT from users where LOGIN='" + login + "'"); + assertThat((String) row.get("EXTERNAL_IDENTITY_PROVIDER")).isEqualTo("sonarqube"); + assertThat((String) row.get("EXTERNAL_IDENTITY")).isEqualTo(login); + assertThat(row.get("UPDATED_AT")).isEqualTo(NOW); + } + + private void checkUserIsNotUpdated(String login) { + Map<String, Object> row = db.selectFirst("select EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, UPDATED_AT from users where LOGIN='" + login + "'"); + assertThat((String) row.get("EXTERNAL_IDENTITY_PROVIDER")).isNotEmpty(); + assertThat((String) row.get("EXTERNAL_IDENTITY")).isNotEmpty(); + assertThat(row.get("UPDATED_AT")).isEqualTo(PAST); + } + +} diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest/schema.sql b/sonar-db/src/test/resources/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest/schema.sql new file mode 100644 index 00000000000..127fc346a9f --- /dev/null +++ b/sonar-db/src/test/resources/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest/schema.sql @@ -0,0 +1,17 @@ +CREATE TABLE "USERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOGIN" VARCHAR(255), + "NAME" VARCHAR(200), + "EMAIL" VARCHAR(100), + "CRYPTED_PASSWORD" VARCHAR(40), + "SALT" VARCHAR(40), + "REMEMBER_TOKEN" VARCHAR(500), + "REMEMBER_TOKEN_EXPIRES_AT" TIMESTAMP, + "ACTIVE" BOOLEAN DEFAULT TRUE, + "SCM_ACCOUNTS" VARCHAR(4000), + "EXTERNAL_IDENTITY" VARCHAR(255), + "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100), + "USER_LOCAL" BOOLEAN, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); |