diff options
Diffstat (limited to 'server/sonar-server/src')
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(); |