aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-server/src')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsAction.java77
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/ws/PreventUserDeletionAction.java87
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java64
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateLoginAction.java15
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java3
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/issue/ws/changelog-example.json1
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/organization/ws/prevent_user_deletion-example.json12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java20
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsActionTest.java141
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/ws/PreventUserDeletionActionTest.java208
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java22
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java56
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateLoginActionTest.java18
33 files changed, 435 insertions, 374 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
index d1e7a9d7fa0..85828432ad1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
@@ -92,11 +92,6 @@ public class SafeModeUserSession extends AbstractUserSession {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return Optional.empty();
- }
-
- @Override
public boolean isLoggedIn() {
return false;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java
index 11c91719c79..bbd294d475e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java
@@ -127,7 +127,8 @@ public class ChangelogAction implements IssuesWsAction {
UserDto user = userUUuid == null ? null : results.users.get(userUUuid);
if (user != null) {
changelogBuilder.setUser(user.getLogin());
- changelogBuilder.setUserName(user.getName());
+ changelogBuilder.setIsUserActive(user.isActive());
+ ofNullable(user.getName()).ifPresent(changelogBuilder::setUserName);
ofNullable(emptyToNull(user.getEmail())).ifPresent(email -> changelogBuilder.setAvatar(avatarFactory.create(user)));
}
change.diffs().entrySet().stream()
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java
index 5bc4e3b6675..de4f15b2ee7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java
@@ -37,7 +37,7 @@ public interface OrganizationUpdater {
String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s";
/**
- * Create a new non guarded organization with the specified properties and of which the specified user will assign
+ * Create a new organization with the specified properties and of which the specified user will assign
* Administer Organization permission.
* <p>
* This method does several operations at once:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java
index 6e3dfb7d8dd..54a1c2e60d7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java
@@ -111,9 +111,9 @@ public class AddMemberAction implements OrganizationsWsAction {
AddMemberWsResponse.Builder response = AddMemberWsResponse.newBuilder();
User.Builder wsUser = User.newBuilder()
.setLogin(user.getLogin())
- .setName(user.getName())
.setGroupCount(groups);
ofNullable(emptyToNull(user.getEmail())).ifPresent(text -> wsUser.setAvatar(avatarResolver.create(user)));
+ ofNullable(user.getName()).ifPresent(wsUser::setName);
response.setUser(wsUser);
return response.build();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsAction.java
deleted file mode 100644
index d0fb9681f6b..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsAction.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.organization.ws;
-
-import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.organization.OrganizationQuery;
-import org.sonar.server.user.AbstractUserSession;
-import org.sonar.server.user.SystemPasscode;
-import org.sonar.server.user.UserSession;
-
-public class DeleteEmptyPersonalOrgsAction implements OrganizationsWsAction {
-
- private static final Logger LOGGER = Loggers.get(DeleteEmptyPersonalOrgsAction.class);
-
- private static final String ACTION = "delete_empty_personal_orgs";
-
- private final SystemPasscode passcode;
- private final UserSession userSession;
- private final OrganizationDeleter organizationDeleter;
-
- public DeleteEmptyPersonalOrgsAction(SystemPasscode passcode, UserSession userSession, OrganizationDeleter organizationDeleter) {
- this.passcode = passcode;
- this.userSession = userSession;
- this.organizationDeleter = organizationDeleter;
- }
-
- @Override
- public void define(WebService.NewController context) {
- context.createAction(ACTION)
- .setDescription("Internal use. Requires system administration permission. Delete empty personal organizations.")
- .setInternal(true)
- .setPost(true)
- .setHandler(this);
- }
-
- @Override
- public void handle(Request request, Response response) throws Exception {
- if (!passcode.isValid(request) && !userSession.isSystemAdministrator()) {
- throw AbstractUserSession.insufficientPrivilegesException();
- }
-
- LOGGER.info("deleting empty personal organizations");
-
- OrganizationQuery query = OrganizationQuery.newOrganizationQueryBuilder()
- .setOnlyPersonal()
- .setWithoutProjects()
- .build();
-
- organizationDeleter.deleteByQuery(query);
-
- LOGGER.info("Deleted empty personal organizations");
-
- response.noContent();
- }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java
index a19b95ebff3..a3df5bf41d6 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java
@@ -52,9 +52,9 @@ public class OrganizationsWsModule extends Module {
CreateAction.class,
OrganizationDeleter.class,
DeleteAction.class,
- DeleteEmptyPersonalOrgsAction.class,
RemoveMemberAction.class,
- UpdateAction.class);
+ UpdateAction.class,
+ PreventUserDeletionAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/PreventUserDeletionAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/PreventUserDeletionAction.java
new file mode 100644
index 00000000000..7ff1feb859d
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/PreventUserDeletionAction.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.organization.ws;
+
+import java.util.List;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationHelper;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Organizations;
+
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class PreventUserDeletionAction implements OrganizationsWsAction {
+
+ private static final String ACTION = "prevent_user_deletion";
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+
+ public PreventUserDeletionAction(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ context.createAction(ACTION)
+ .setPost(false)
+ .setDescription("List organizations that prevent the deletion of the authenticated user.")
+ .setResponseExample(getClass().getResource("prevent_user_deletion-example.json"))
+ .setInternal(true)
+ .setSince("7.9")
+ .setHandler(this);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ userSession.checkLoggedIn();
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ int userId = userSession.getUserId();
+ List<OrganizationDto> organizationsThatPreventUserDeletion = new OrganizationHelper(dbClient).selectOrganizationsWithLastAdmin(dbSession, userId);
+
+ Organizations.PreventUserDeletionWsResponse wsResponse = buildResponse(organizationsThatPreventUserDeletion);
+ writeProtobuf(wsResponse, request, response);
+ }
+ }
+
+ private Organizations.PreventUserDeletionWsResponse buildResponse(List<OrganizationDto> organizations) {
+ Organizations.PreventUserDeletionWsResponse.Builder response = Organizations.PreventUserDeletionWsResponse.newBuilder();
+ Organizations.PreventUserDeletionWsResponse.Organization.Builder wsOrganization = Organizations.PreventUserDeletionWsResponse.Organization.newBuilder();
+ organizations.forEach(o -> {
+ wsOrganization.clear();
+ response.addOrganizations(toOrganization(wsOrganization, o));
+ });
+ return response.build();
+ }
+
+ private static Organizations.PreventUserDeletionWsResponse.Organization.Builder toOrganization(
+ Organizations.PreventUserDeletionWsResponse.Organization.Builder builder, OrganizationDto organization) {
+ return builder
+ .setName(organization.getName())
+ .setKey(organization.getKey());
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java
index eee4c7a34ea..cc588949555 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java
@@ -137,9 +137,9 @@ public class SearchMembersAction implements OrganizationsWsAction {
String login = userDto.getLogin();
wsUser
.clear()
- .setLogin(login)
- .setName(userDto.getName());
+ .setLogin(login);
ofNullable(emptyToNull(userDto.getEmail())).ifPresent(text -> wsUser.setAvatar(avatarResolver.create(userDto)));
+ ofNullable(userDto.getName()).ifPresent(wsUser::setName);
ofNullable(groupCountByLogin).ifPresent(count -> wsUser.setGroupCount(groupCountByLogin.count(login)));
return wsUser;
})
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java
index 355a41caa7e..a60f5a40058 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java
@@ -156,8 +156,8 @@ public class SearchUsersAction implements QProfileWsAction {
private SearchUsersResponse.User toUser(UserDto user, boolean isSelected) {
SearchUsersResponse.User.Builder builder = SearchUsersResponse.User.newBuilder()
.setLogin(user.getLogin())
- .setName(user.getName())
.setSelected(isSelected);
+ ofNullable(user.getName()).ifPresent(builder::setName);
ofNullable(emptyToNull(user.getEmail())).ifPresent(e -> builder.setAvatar(avatarResolver.create(user)));
return builder
.build();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java b/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java
index d9bc5ee1a26..4f695b25f8d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java
@@ -113,11 +113,6 @@ public final class DoPrivileged {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return Optional.empty();
- }
-
- @Override
protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) {
return true;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java
index 2564c4c8e25..1485eca1891 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java
@@ -133,11 +133,6 @@ public class ServerUserSession extends AbstractUserSession {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return ofNullable(userDto).map(UserDto::getOrganizationUuid);
- }
-
- @Override
protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) {
if (permissionsByOrganizationUuid == null) {
permissionsByOrganizationUuid = new HashMap<>();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
index 6a04149538b..3e5712bc5fa 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
@@ -96,11 +96,6 @@ public class ThreadLocalUserSession implements UserSession {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return get().getPersonalOrganizationUuid();
- }
-
- @Override
public boolean isLoggedIn() {
return get().isLoggedIn();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
index 85a09760052..3399e36cdcc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
@@ -149,11 +149,6 @@ public interface UserSession {
Optional<ExternalIdentity> getExternalIdentity();
/**
- * The UUID of the personal organization of the authenticated user.
- */
- Optional<String> getPersonalOrganizationUuid();
-
- /**
* Whether the user is logged-in or anonymous.
*/
boolean isLoggedIn();
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 bf54d87f49e..8a6da917e1c 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
@@ -397,7 +397,7 @@ public class UserUpdater {
if (existingUser != null && matchingUser.getId().equals(existingUser.getId())) {
continue;
}
- matchingUsersWithoutExistingUser.add(matchingUser.getName() + " (" + matchingUser.getLogin() + ")");
+ matchingUsersWithoutExistingUser.add(getNameOrLogin(matchingUser) + " (" + matchingUser.getLogin() + ")");
}
if (!matchingUsersWithoutExistingUser.isEmpty()) {
messages.add(format("The scm account '%s' is already used by user(s) : '%s'", scmAccount, Joiner.on(", ").join(matchingUsersWithoutExistingUser)));
@@ -408,6 +408,11 @@ public class UserUpdater {
return isValid;
}
+ private static String getNameOrLogin(UserDto user) {
+ String name = user.getName();
+ return name != null ? name : user.getLogin();
+ }
+
private static List<String> sanitizeScmAccounts(@Nullable List<String> scmAccounts) {
if (scmAccounts != null) {
return new HashSet<>(scmAccounts).stream()
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
index d88102d14b4..dfc25e18ef8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
@@ -150,8 +150,7 @@ public class CreateAction implements UsersWsAction {
}));
}
checkArgument(!existingUser.isActive(), "An active user with login '%s' already exists", login);
- return buildResponse(userUpdater.reactivateAndCommit(dbSession, existingUser, newUser.build(), u -> {
- }));
+ return buildResponse(userUpdater.reactivateAndCommit(dbSession, existingUser, newUser.build(), u -> {}));
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
index c53c29885bd..b14463ecd7c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
@@ -19,23 +19,26 @@
*/
package org.sonar.server.user.ws;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
+import org.sonar.api.config.Configuration;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
+import org.sonar.db.organization.OrganizationHelper;
import org.sonar.db.property.PropertyQuery;
import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession;
import org.sonar.server.user.index.UserIndexer;
@@ -43,11 +46,14 @@ import org.sonar.server.user.index.UserIndexer;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE;
+import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED;
import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkRequest;
public class DeactivateAction implements UsersWsAction {
+ private static final Logger LOGGER = Loggers.get(DeactivateAction.class);
+
private static final String PARAM_LOGIN = "login";
private final DbClient dbClient;
@@ -55,14 +61,16 @@ public class DeactivateAction implements UsersWsAction {
private final UserSession userSession;
private final UserJsonWriter userWriter;
private final DefaultOrganizationProvider defaultOrganizationProvider;
+ private final boolean isSonarCloud;
public DeactivateAction(DbClient dbClient, UserIndexer userIndexer, UserSession userSession, UserJsonWriter userWriter,
- DefaultOrganizationProvider defaultOrganizationProvider) {
+ DefaultOrganizationProvider defaultOrganizationProvider, Configuration configuration) {
this.dbClient = dbClient;
this.userIndexer = userIndexer;
this.userSession = userSession;
this.userWriter = userWriter;
this.defaultOrganizationProvider = defaultOrganizationProvider;
+ this.isSonarCloud = configuration.getBoolean(SONARCLOUD_ENABLED.getKey()).orElse(false);
}
@Override
@@ -82,10 +90,18 @@ public class DeactivateAction implements UsersWsAction {
@Override
public void handle(Request request, Response response) throws Exception {
- userSession.checkLoggedIn().checkIsSystemAdministrator();
+ String login;
- String login = request.mandatoryParam(PARAM_LOGIN);
- checkRequest(!login.equals(userSession.getLogin()), "Self-deactivation is not possible");
+ if (isSonarCloud) {
+ login = request.mandatoryParam(PARAM_LOGIN);
+ if (!login.equals(userSession.getLogin()) && !userSession.checkLoggedIn().isSystemAdministrator()) {
+ throw new ForbiddenException("Insufficient privileges");
+ }
+ } else {
+ userSession.checkLoggedIn().checkIsSystemAdministrator();
+ login = request.mandatoryParam(PARAM_LOGIN);
+ checkRequest(!login.equals(userSession.getLogin()), "Self-deactivation is not possible");
+ }
try (DbSession dbSession = dbClient.openSession(false)) {
UserDto user = dbClient.userDao().selectByLogin(dbSession, login);
@@ -103,13 +119,23 @@ public class DeactivateAction implements UsersWsAction {
dbClient.qProfileEditUsersDao().deleteByUser(dbSession, user);
dbClient.organizationMemberDao().deleteByUserId(dbSession, userId);
dbClient.userPropertiesDao().deleteByUser(dbSession, user);
- dbClient.userDao().deactivateUser(dbSession, user);
+ deactivateUser(dbSession, user);
userIndexer.commitAndIndex(dbSession, user);
+
+ LOGGER.info("Deactivate user: {}; by admin: {}", login, userSession.isSystemAdministrator());
}
writeResponse(response, login);
}
+ private void deactivateUser(DbSession dbSession, UserDto user) {
+ if (isSonarCloud) {
+ dbClient.userDao().deactivateSonarCloudUser(dbSession, user);
+ } else {
+ dbClient.userDao().deactivateUser(dbSession, user);
+ }
+ }
+
private void writeResponse(Response response, String login) {
try (DbSession dbSession = dbClient.openSession(false)) {
UserDto user = dbClient.userDao().selectByLogin(dbSession, login);
@@ -129,38 +155,18 @@ public class DeactivateAction implements UsersWsAction {
}
private void ensureNotLastAdministrator(DbSession dbSession, UserDto user) {
- List<String> problematicOrgs = selectOrganizationsWithNoMoreAdministrators(dbSession, user);
+ List<OrganizationDto> problematicOrgs = new OrganizationHelper(dbClient).selectOrganizationsWithLastAdmin(dbSession, user.getId());
if (problematicOrgs.isEmpty()) {
return;
}
- checkRequest(problematicOrgs.size() != 1 || !defaultOrganizationProvider.get().getUuid().equals(problematicOrgs.get(0)),
+ checkRequest(problematicOrgs.size() != 1 || !defaultOrganizationProvider.get().getUuid().equals(problematicOrgs.get(0).getUuid()),
"User is last administrator, and cannot be deactivated");
String keys = problematicOrgs
.stream()
- .map(orgUuid -> selectOrganizationByUuid(dbSession, orgUuid, user))
.map(OrganizationDto::getKey)
.sorted()
.collect(Collectors.joining(", "));
throw BadRequestException.create(format("User '%s' is last administrator of organizations [%s], and cannot be deactivated", user.getLogin(), keys));
}
- private List<String> selectOrganizationsWithNoMoreAdministrators(DbSession dbSession, UserDto user) {
- Set<String> organizationUuids = dbClient.authorizationDao().selectOrganizationUuidsOfUserWithGlobalPermission(
- dbSession, user.getId(), OrganizationPermission.ADMINISTER.getKey());
- List<String> problematicOrganizations = new ArrayList<>();
- for (String organizationUuid : organizationUuids) {
- int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingUser(dbSession,
- organizationUuid, OrganizationPermission.ADMINISTER.getKey(), user.getId());
- if (remaining == 0) {
- problematicOrganizations.add(organizationUuid);
- }
- }
- return problematicOrganizations;
- }
-
- private OrganizationDto selectOrganizationByUuid(DbSession dbSession, String orgUuid, UserDto user) {
- return dbClient.organizationDao()
- .selectByUuid(dbSession, orgUuid)
- .orElseThrow(() -> new IllegalStateException("Organization with UUID " + orgUuid + " does not exist in DB but is referenced in permissions of user " + user.getLogin()));
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateLoginAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateLoginAction.java
index e67093cb407..21410bf8a2f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateLoginAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateLoginAction.java
@@ -81,11 +81,7 @@ public class UpdateLoginAction implements UsersWsAction {
String newLogin = request.mandatoryParam(PARAM_NEW_LOGIN);
try (DbSession dbSession = dbClient.openSession(false)) {
UserDto user = getUser(dbSession, login);
- userUpdater.updateAndCommit(
- dbSession,
- user,
- new UpdateUser().setLogin(newLogin),
- u -> updatePersonalOrganization(dbSession, u));
+ userUpdater.updateAndCommit(dbSession, user, new UpdateUser().setLogin(newLogin), u -> {});
response.noContent();
}
}
@@ -98,13 +94,4 @@ public class UpdateLoginAction implements UsersWsAction {
return user;
}
- private void updatePersonalOrganization(DbSession dbSession, UserDto user) {
- String personalOrganizationUuid = user.getOrganizationUuid();
- if (personalOrganizationUuid == null) {
- return;
- }
- dbClient.organizationDao().selectByUuid(dbSession, personalOrganizationUuid)
- .ifPresent(organization -> organizationUpdater.updateOrganizationKey(dbSession, organization, user.getLogin()));
- }
-
}
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 8ac76d0c030..a9925a1bf63 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
@@ -28,6 +28,7 @@ import org.sonar.api.utils.text.JsonWriter;
import org.sonar.db.user.UserDto;
import org.sonar.server.user.UserSession;
+import static java.util.Optional.ofNullable;
import static org.sonar.server.ws.JsonWriterUtils.isFieldNeeded;
import static org.sonar.server.ws.JsonWriterUtils.writeIfNeeded;
@@ -61,7 +62,7 @@ public class UserJsonWriter {
public void write(JsonWriter json, UserDto user, Collection<String> groups, @Nullable Collection<String> fields) {
json.beginObject();
json.prop(FIELD_LOGIN, user.getLogin());
- writeIfNeeded(json, user.getName(), FIELD_NAME, fields);
+ ofNullable(user.getName()).ifPresent(name -> writeIfNeeded(json, name, FIELD_NAME, fields));
if (userSession.isLoggedIn()) {
writeIfNeeded(json, user.getEmail(), FIELD_EMAIL, fields);
writeIfNeeded(json, user.isActive(), FIELD_ACTIVE, fields);
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/changelog-example.json b/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/changelog-example.json
index 3b70838b670..45a0a287e07 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/changelog-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/issue/ws/changelog-example.json
@@ -3,6 +3,7 @@
{
"user": "john.smith",
"userName": "John Smith",
+ "isUserActive": true,
"avatar": "b0d8c6e5ea589e6fc3d3e08afb1873bb",
"creationDate": "2014-03-04T23:03:44+0100",
"diffs": [
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/prevent_user_deletion-example.json b/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/prevent_user_deletion-example.json
new file mode 100644
index 00000000000..149a283b943
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/prevent_user_deletion-example.json
@@ -0,0 +1,12 @@
+{
+ "organizations": [
+ {
+ "key": "foo-company",
+ "name": "Foo Company"
+ },
+ {
+ "key": "bar-company",
+ "name": "Bar Company"
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java
index 22ab82cb47a..918feb3e52f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java
@@ -90,6 +90,7 @@ public class ChangelogActionTest {
assertThat(result.getChangelogList()).hasSize(1);
assertThat(result.getChangelogList().get(0).getUser()).isNotNull().isEqualTo(user.getLogin());
assertThat(result.getChangelogList().get(0).getUserName()).isNotNull().isEqualTo(user.getName());
+ assertThat(result.getChangelogList().get(0).getIsUserActive()).isTrue();
assertThat(result.getChangelogList().get(0).getAvatar()).isNotNull().isEqualTo("93942e96f5acd83e2e047ad8fe03114d");
assertThat(result.getChangelogList().get(0).getCreationDate()).isNotEmpty();
assertThat(result.getChangelogList().get(0).getDiffsList()).extracting(Diff::getKey, Diff::getOldValue, Diff::getNewValue).containsOnly(tuple("severity", "MAJOR", "BLOCKER"));
@@ -197,6 +198,25 @@ public class ChangelogActionTest {
}
@Test
+ public void return_changelog_on_deactivated_user() {
+ UserDto user = db.users().insertDisabledUser();
+ IssueDto issueDto = db.issues().insertIssue(newIssue());
+ userSession.logIn("john")
+ .addMembership(db.getDefaultOrganization())
+ .addProjectPermission(USER, project, file);
+ db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date()));
+
+ ChangelogWsResponse result = call(issueDto.getKey());
+
+ assertThat(result.getChangelogList()).hasSize(1);
+ assertThat(result.getChangelogList().get(0).getUser()).isEqualTo(user.getLogin());
+ assertThat(result.getChangelogList().get(0).getIsUserActive()).isFalse();
+ assertThat(result.getChangelogList().get(0).getUserName()).isEqualTo(user.getName());
+ assertThat(result.getChangelogList().get(0).hasAvatar()).isFalse();
+ assertThat(result.getChangelogList().get(0).getDiffsList()).isNotEmpty();
+ }
+
+ @Test
public void return_multiple_diffs() {
UserDto user = insertUser();
IssueDto issueDto = db.issues().insertIssue(newIssue());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java
index 2bcfb03b973..9eb1ed9f23b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java
@@ -73,9 +73,6 @@ import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.
public class OrganizationUpdaterImplTest {
private static final long A_DATE = 12893434L;
- private static final String A_LOGIN = "a-login";
- private static final String SLUG_OF_A_LOGIN = "slug-of-a-login";
- private static final String A_NAME = "a name";
private OrganizationUpdater.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder()
.setName("a-name")
@@ -116,7 +113,7 @@ public class OrganizationUpdaterImplTest {
builtInQProfileRepositoryRule, defaultGroupCreator, permissionService);
@Test
- public void create_creates_unguarded_organization_with_properties_from_NewOrganization_arg() throws OrganizationUpdater.KeyConflictException {
+ public void create_creates_organization_with_properties_from_NewOrganization_arg() throws OrganizationUpdater.KeyConflictException {
builtInQProfileRepositoryRule.initialize();
UserDto user = db.users().insertUser();
db.qualityGates().insertBuiltInQualityGate();
@@ -130,7 +127,6 @@ public class OrganizationUpdaterImplTest {
assertThat(organization.getDescription()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getDescription());
assertThat(organization.getUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getUrl());
assertThat(organization.getAvatarUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getAvatar());
- assertThat(organization.isGuarded()).isFalse();
assertThat(organization.getSubscription()).isEqualTo(Subscription.FREE);
assertThat(organization.getCreatedAt()).isEqualTo(A_DATE);
assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE);
@@ -175,7 +171,6 @@ public class OrganizationUpdaterImplTest {
assertThat(organization.getDescription()).isNull();
assertThat(organization.getUrl()).isNull();
assertThat(organization.getAvatarUrl()).isNull();
- assertThat(organization.isGuarded()).isFalse();
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsActionTest.java
deleted file mode 100644
index ccf960804c3..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteEmptyPersonalOrgsActionTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.organization.ws;
-
-import java.util.Arrays;
-import java.util.List;
-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.System2;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ResourceTypesRule;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.component.ComponentCleanerService;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.ProjectIndexersImpl;
-import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.organization.BillingValidationsProxyImpl;
-import org.sonar.server.project.ProjectLifeCycleListener;
-import org.sonar.server.project.ProjectLifeCycleListenersImpl;
-import org.sonar.server.qualityprofile.QProfileFactoryImpl;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.SystemPasscode;
-import org.sonar.server.user.index.UserIndexer;
-import org.sonar.server.ws.WsActionTester;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
-
-public class DeleteEmptyPersonalOrgsActionTest {
-
- @Rule
- public final UserSessionRule userSession = UserSessionRule.standalone();
-
- @Rule
- public final DbTester db = DbTester.create(new System2());
- private final DbClient dbClient = db.getDbClient();
-
- @Rule
- public final EsTester es = EsTester.create();
- private final EsClient esClient = es.client();
-
- @Rule
- public final ExpectedException expectedException = ExpectedException.none();
-
- private SystemPasscode passcode = mock(SystemPasscode.class);
- private final OrganizationDeleter organizationDeleter = new OrganizationDeleter(dbClient,
- new ComponentCleanerService(dbClient, new ResourceTypesRule(), new ProjectIndexersImpl()),
- new UserIndexer(dbClient, esClient),
- new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), new System2(), new ActiveRuleIndexer(dbClient, esClient)),
- new ProjectLifeCycleListenersImpl(new ProjectLifeCycleListener[0]),
- new BillingValidationsProxyImpl());
-
- private final DeleteEmptyPersonalOrgsAction underTest = new DeleteEmptyPersonalOrgsAction(passcode, userSession, organizationDeleter);
- private final WsActionTester ws = new WsActionTester(underTest);
-
- @Test
- public void request_fails_if_user_is_not_root() {
- userSession.logIn();
-
- expectedException.expect(ForbiddenException.class);
- expectedException.expectMessage("Insufficient privileges");
-
- ws.newRequest().execute();
- }
-
- @Test
- public void delete_empty_personal_orgs() {
- UserDto admin = db.users().insertUser();
- db.users().insertPermissionOnUser(admin, ADMINISTER);
- userSession.logIn().setSystemAdministrator();
-
- doRun();
- }
-
- @Test
- public void authenticate_with_system_passcode() {
- when(passcode.isValid(any())).thenReturn(true);
-
- doRun();
- }
-
- private void doRun() {
- OrganizationDto emptyPersonal = db.organizations().insert(o -> o.setGuarded(true));
- db.users().insertUser(u -> u.setOrganizationUuid(emptyPersonal.getUuid()));
-
- OrganizationDto nonEmptyPersonal = db.organizations().insert(o -> o.setGuarded(true));
- db.users().insertUser(u -> u.setOrganizationUuid(nonEmptyPersonal.getUuid()));
- db.components().insertPublicProject(nonEmptyPersonal);
-
- OrganizationDto emptyRegular = db.organizations().insert();
-
- OrganizationDto nonEmptyRegular = db.organizations().insert();
- db.components().insertPublicProject(nonEmptyRegular);
-
- ws.newRequest().execute();
-
- List<String> notDeleted = Arrays.asList(
- db.getDefaultOrganization().getUuid(),
- nonEmptyPersonal.getUuid(),
- emptyRegular.getUuid(),
- nonEmptyRegular.getUuid());
-
- assertThat(dbClient.organizationDao().selectAllUuids(db.getSession()))
- .containsExactlyInAnyOrderElementsOf(notDeleted);
- }
-
- @Test
- public void definition() {
- WebService.Action action = ws.getDef();
- assertThat(action.isPost()).isTrue();
- assertThat(action.isInternal()).isTrue();
- assertThat(action.handler()).isNotNull();
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/PreventUserDeletionActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/PreventUserDeletionActionTest.java
new file mode 100644
index 00000000000..060e845fcd6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/PreventUserDeletionActionTest.java
@@ -0,0 +1,208 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.organization.ws;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Organizations;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
+import static org.sonar.test.JsonAssert.assertJson;
+
+@RunWith(DataProviderRunner.class)
+public class PreventUserDeletionActionTest {
+
+ private static final Random RANDOM = new Random();
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ @Rule
+ public DbTester db = DbTester.create(mock(System2.class)).setDisableDefaultOrganization(true);
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final PreventUserDeletionAction underTest = new PreventUserDeletionAction(db.getDbClient(), userSession);
+ private final WsActionTester ws = new WsActionTester(underTest);
+
+ @Test
+ public void fail_if_user_is_not_logged_in() {
+ UserDto user1 = db.users().insertUser();
+
+ OrganizationDto org1 = db.organizations().insert();
+ GroupDto group1 = db.users().insertGroup(org1);
+ db.users().insertMember(group1, user1);
+
+ expectedException.expect(UnauthorizedException.class);
+
+ call();
+ }
+
+ @Test
+ public void returns_empty_list_when_user_is_not_admin_of_any_orgs() {
+ UserDto user1 = db.users().insertUser();
+
+ OrganizationDto org1 = db.organizations().insert();
+ GroupDto group1 = db.users().insertGroup(org1);
+ db.users().insertMember(group1, user1);
+
+ userSession.logIn(user1);
+ assertThat(call().getOrganizationsList()).isEmpty();
+ }
+
+ @Test
+ public void returns_orgs_where_user_is_last_admin() {
+ UserDto user1 = db.users().insertUser();
+ UserDto user2 = db.users().insertUser();
+
+ OrganizationDto org1 = db.organizations().insert();
+ OrganizationDto org2 = db.organizations().insert();
+
+ setAsDirectOrIndirectAdmin(user1, org1);
+ setAsDirectOrIndirectAdmin(user2, org1);
+ setAsDirectOrIndirectAdmin(user1, org2);
+
+ userSession.logIn(user1);
+ assertThat(call().getOrganizationsList())
+ .extracting(Organizations.Organization::getKey)
+ .containsExactly(org2.getKey());
+ }
+
+ @Test
+ @UseDataProvider("adminUserCombinationsAndExpectedOrgKeys")
+ public void returns_correct_orgs_for_interesting_combinations_of_last_admin_or_not(
+ boolean user2IsAdminOfOrg1, boolean user1IsAdminOfOrg2, boolean user2IsAdminOfOrg2, List<String> expectedOrgKeys) {
+ UserDto user1 = db.users().insertUser();
+ UserDto user2 = db.users().insertUser();
+
+ OrganizationDto org1 = db.organizations().insert(o -> o.setKey("org1"));
+ OrganizationDto org2 = db.organizations().insert(o -> o.setKey("org2"));
+
+ setAsDirectOrIndirectAdmin(user1, org1);
+ if (user2IsAdminOfOrg1) {
+ setAsDirectOrIndirectAdmin(user2, org1);
+ }
+ if (user1IsAdminOfOrg2) {
+ setAsDirectOrIndirectAdmin(user1, org2);
+ }
+ if (user2IsAdminOfOrg2) {
+ setAsDirectOrIndirectAdmin(user2, org2);
+ }
+
+ userSession.logIn(user1);
+ assertThat(call().getOrganizationsList())
+ .extracting(Organizations.Organization::getKey)
+ .containsExactlyInAnyOrderElementsOf(expectedOrgKeys);
+ }
+
+ @DataProvider
+ public static Object[][] adminUserCombinationsAndExpectedOrgKeys() {
+ return new Object[][] {
+ // note: user1 is always admin of org1
+ // param 1: user2 is admin of org1
+ // param 2: user1 is admin of org2
+ // param 3: user2 is admin of org2
+ // param 4: list of orgs preventing user1 to delete
+ {true, true, true, emptyList()},
+ {true, true, false, singletonList("org2")},
+ {true, false, true, emptyList()},
+ {true, false, false, emptyList()},
+ {false, true, true, singletonList("org1")},
+ {false, true, false, asList("org1", "org2")},
+ {false, false, true, singletonList("org1")},
+ {false, false, false, singletonList("org1")},
+ };
+ }
+
+ @Test
+ public void json_example() {
+ UserDto user1 = db.users().insertUser();
+
+ OrganizationDto org1 = db.organizations().insert(o -> {
+ o.setKey("foo-company");
+ o.setName("Foo Company");
+ });
+ OrganizationDto org2 = db.organizations().insert(o -> {
+ o.setKey("bar-company");
+ o.setName("Bar Company");
+ });
+
+ setAsDirectOrIndirectAdmin(user1, org1);
+ setAsDirectOrIndirectAdmin(user1, org2);
+
+ userSession.logIn(user1);
+
+ String result = ws.newRequest()
+ .execute()
+ .getInput();
+
+ assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action action = ws.getDef();
+
+ assertThat(action.key()).isEqualTo("prevent_user_deletion");
+ assertThat(action.params()).isEmpty();
+ assertThat(action.description()).isNotEmpty();
+ assertThat(action.responseExampleAsString()).isNotEmpty();
+ assertThat(action.since()).isEqualTo("7.9");
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.isPost()).isFalse();
+ }
+
+ private void setAsDirectOrIndirectAdmin(UserDto user, OrganizationDto organization) {
+ boolean useDirectAdmin = RANDOM.nextBoolean();
+ if (useDirectAdmin) {
+ db.users().insertPermissionOnUser(organization, user, ADMINISTER);
+ } else {
+ GroupDto group = db.users().insertGroup(organization);
+ db.users().insertPermissionOnGroup(group, ADMINISTER);
+ db.users().insertMember(group, user);
+ }
+ }
+
+ private Organizations.SearchWsResponse call() {
+ return ws.newRequest().executeProtobuf(Organizations.SearchWsResponse.class);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java
index 7f141946b7a..9c229d7176a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java
@@ -100,22 +100,6 @@ public class SearchActionTest {
}
@Test
- public void root_can_do_everything() {
- OrganizationDto organization = db.organizations().insert();
- OrganizationDto guardedOrganization = db.organizations().insert(dto -> dto.setGuarded(true));
- UserDto user = db.users().insertUser();
- userSession.logIn(user).setRoot();
-
- SearchWsResponse result = call(ws.newRequest());
-
- assertThat(result.getOrganizationsList())
- .extracting(Organization::getKey, o -> o.getActions().getAdmin(), o -> o.getActions().getDelete(), o -> o.getActions().getProvision())
- .containsExactlyInAnyOrder(
- tuple(organization.getKey(), true, true, true),
- tuple(guardedOrganization.getKey(), true, true, true));
- }
-
- @Test
public void provision_action_available_for_each_organization() {
OrganizationDto userProvisionOrganization = db.organizations().insert();
OrganizationDto groupProvisionOrganization = db.organizations().insert();
@@ -402,8 +386,7 @@ public class SearchActionTest {
.setDescription("The Bar company produces quality software too.")
.setUrl("https://www.bar.com")
.setAvatarUrl("https://www.bar.com/logo.png")
- .setSubscription(PAID)
- .setGuarded(false));
+ .setSubscription(PAID));
OrganizationDto fooOrganization = db.organizations().insert(organization -> organization
.setUuid(Uuids.UUID_EXAMPLE_01)
.setKey("foo-company")
@@ -411,8 +394,7 @@ public class SearchActionTest {
.setSubscription(FREE)
.setDescription(null)
.setUrl(null)
- .setAvatarUrl(null)
- .setGuarded(true));
+ .setAvatarUrl(null));
UserDto user = db.users().insertUser();
db.organizations().addMember(barOrganization, user);
db.organizations().addMember(fooOrganization, user);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java b/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java
index 0e4b7bbf1b4..3c5d4068c61 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java
@@ -76,11 +76,6 @@ public class AnonymousMockUserSession extends AbstractMockUserSession<AnonymousM
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return Optional.empty();
- }
-
- @Override
public boolean hasMembershipImpl(OrganizationDto organizationDto) {
return false;
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java b/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java
index daff1a67c78..e59b0eda6f5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java
@@ -144,8 +144,4 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
return Optional.ofNullable(externalIdentity);
}
- @Override
- public Optional<String> getPersonalOrganizationUuid() {
- return Optional.empty();
- }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java
index 13f05371271..5e7cc4147d5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java
@@ -290,11 +290,6 @@ public class UserSessionRule implements TestRule, UserSession {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- return currentUserSession.getPersonalOrganizationUuid();
- }
-
- @Override
public boolean isLoggedIn() {
return currentUserSession.isLoggedIn();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
index 7d2e3f9905a..605560481b6 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
@@ -81,7 +81,7 @@ public class OrganizationActionTest {
initWithPages(
Page.builder("my-plugin/org-page").setName("Organization page").setScope(ORGANIZATION).build(),
Page.builder("my-plugin/org-admin-page").setName("Organization admin page").setScope(ORGANIZATION).setAdmin(true).build());
- OrganizationDto organization = db.organizations().insert(dto -> dto.setGuarded(true));
+ OrganizationDto organization = db.organizations().insert();
userSession.logIn()
.addPermission(PROVISION_PROJECTS, organization);
@@ -214,7 +214,7 @@ public class OrganizationActionTest {
initWithPages(
Page.builder("my-plugin/org-page").setName("Organization page").setScope(ORGANIZATION).build(),
Page.builder("my-plugin/org-admin-page").setName("Organization admin page").setScope(ORGANIZATION).setAdmin(true).build());
- OrganizationDto organization = db.organizations().insert(dto -> dto.setGuarded(true));
+ OrganizationDto organization = db.organizations().insert();
userSession.logIn()
.addPermission(ADMINISTER, organization)
.addPermission(PROVISION_PROJECTS, organization);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
index 4af4d3c807b..af654c54493 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
@@ -24,14 +24,11 @@ import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java b/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java
index 3957e22959f..2dfa06ab569 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java
@@ -98,11 +98,6 @@ public class TestUserSessionFactory implements UserSessionFactory {
}
@Override
- public Optional<String> getPersonalOrganizationUuid() {
- throw notImplemented();
- }
-
- @Override
public boolean isLoggedIn() {
return user != null;
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
index 2f7bebc48fb..afb7691885b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
@@ -63,6 +64,7 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.db.permission.OrganizationPermission.SCAN;
import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;
+import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED;
import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ACTIVE;
import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID;
import static org.sonar.test.JsonAssert.assertJson;
@@ -87,9 +89,9 @@ public class DeactivateActionTest {
private DbClient dbClient = db.getDbClient();
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private DbSession dbSession = db.getSession();
-
- private WsActionTester ws = new WsActionTester(new DeactivateAction(
- dbClient, userIndexer, userSession, new UserJsonWriter(userSession), defaultOrganizationProvider));
+ private MapSettings settings = new MapSettings();
+ private WsActionTester ws = new WsActionTester(new DeactivateAction(dbClient, userIndexer, userSession,
+ new UserJsonWriter(userSession), defaultOrganizationProvider, settings.asConfig()));
@Test
public void deactivate_user_and_delete_his_related_data() {
@@ -102,7 +104,7 @@ public class DeactivateActionTest {
deactivate(user.getLogin());
- verifyThatUserIsDeactivated(user.getLogin());
+ verifyThatUserIsDeactivated(user.getLogin(), false);
assertThat(es.client().prepareSearch(UserIndexDefinition.TYPE_USER)
.setQuery(boolQuery()
.must(termQuery(FIELD_UUID, user.getUuid()))
@@ -242,7 +244,32 @@ public class DeactivateActionTest {
}
@Test
- public void cannot_deactivate_self() {
+ public void user_can_deactivate_itself_on_sonarcloud() {
+ WsActionTester customWs = newSonarCloudWs();
+
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user.getLogin());
+
+ deactivate(customWs, user.getLogin());
+
+ verifyThatUserIsDeactivated(user.getLogin(), true);
+ }
+
+ @Test
+ public void user_cannot_deactivate_another_user_on_sonarcloud() {
+ WsActionTester customWs = newSonarCloudWs();
+
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user.getLogin());
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privilege");
+
+ deactivate(customWs, "other user");
+ }
+
+ @Test
+ public void user_cannot_deactivate_itself_on_sonarqube() {
UserDto user = db.users().insertUser();
userSession.logIn(user.getLogin()).setSystemAdministrator();
@@ -263,7 +290,7 @@ public class DeactivateActionTest {
}
@Test
- public void deactivation_requires_administrator_permission() {
+ public void deactivation_requires_administrator_permission_on_sonarqube() {
userSession.logIn();
expectedException.expect(ForbiddenException.class);
@@ -346,7 +373,7 @@ public class DeactivateActionTest {
deactivate(admin.getLogin());
- verifyThatUserIsDeactivated(admin.getLogin());
+ verifyThatUserIsDeactivated(admin.getLogin(), false);
verifyThatUserExists(anotherAdmin.getLogin());
}
@@ -377,6 +404,10 @@ public class DeactivateActionTest {
}
private TestResponse deactivate(@Nullable String login) {
+ return deactivate(ws, login);
+ }
+
+ private TestResponse deactivate(WsActionTester ws, @Nullable String login) {
TestRequest request = ws.newRequest()
.setMethod("POST");
Optional.ofNullable(login).ifPresent(t -> request.setParam("login", login));
@@ -387,11 +418,20 @@ public class DeactivateActionTest {
assertThat(db.users().selectUserByLogin(login)).isPresent();
}
- private void verifyThatUserIsDeactivated(String login) {
+ private void verifyThatUserIsDeactivated(String login, boolean isSonarCloud) {
Optional<UserDto> user = db.users().selectUserByLogin(login);
assertThat(user).isPresent();
assertThat(user.get().isActive()).isFalse();
assertThat(user.get().getEmail()).isNull();
assertThat(user.get().getScmAccountsAsList()).isEmpty();
+ if (isSonarCloud) {
+ assertThat(user.get().getName()).isNull();
+ }
+ }
+
+ private WsActionTester newSonarCloudWs() {
+ settings.setProperty(SONARCLOUD_ENABLED.getKey(), true);
+ return new WsActionTester(new DeactivateAction(dbClient, userIndexer, userSession,
+ new UserJsonWriter(userSession), defaultOrganizationProvider, settings.asConfig()));
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateLoginActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateLoginActionTest.java
index 12445f7a341..8595dd72aa3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateLoginActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateLoginActionTest.java
@@ -120,24 +120,6 @@ public class UpdateLoginActionTest {
}
@Test
- public void update_personal_organization_when_updating_login() {
- userSession.logIn().setSystemAdministrator();
- String oldLogin = "old_login";
- OrganizationDto personalOrganization = db.organizations().insert(o -> o.setKey(oldLogin).setGuarded(true));
- UserDto user = db.users().insertUser(u -> u.setLogin(oldLogin).setOrganizationUuid(personalOrganization.getUuid()));
-
- ws.newRequest()
- .setParam("login", oldLogin)
- .setParam("newLogin", "new_login")
- .execute();
-
- UserDto userReloaded = db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid());
- assertThat(userReloaded.getLogin()).isEqualTo("new_login");
- OrganizationDto organizationReloaded = db.getDbClient().organizationDao().selectByUuid(db.getSession(), personalOrganization.getUuid()).get();
- assertThat(organizationReloaded.getKey()).isEqualTo("new_login");
- }
-
- @Test
public void fail_with_IAE_when_new_login_is_already_used() {
userSession.logIn().setSystemAdministrator();
UserDto user = db.users().insertUser();