3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.permission;
22 import org.apache.commons.lang.StringUtils;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.sonar.api.resources.Qualifiers;
27 import org.sonar.api.resources.ResourceTypes;
28 import org.sonar.api.utils.System2;
29 import org.sonar.api.web.UserRole;
30 import org.sonar.core.permission.GlobalPermissions;
31 import org.sonar.core.util.SequenceUuidFactory;
32 import org.sonar.core.util.Uuids;
33 import org.sonar.db.DbTester;
34 import org.sonar.db.component.ComponentDto;
35 import org.sonar.db.component.ResourceTypesRule;
36 import org.sonar.db.permission.GlobalPermission;
37 import org.sonar.db.permission.GroupPermissionDto;
38 import org.sonar.db.user.GroupDto;
39 import org.sonar.db.user.UserDto;
40 import org.sonar.server.exceptions.BadRequestException;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.assertj.core.api.Assertions.assertThatThrownBy;
44 import static org.assertj.core.api.Assertions.fail;
45 import static org.sonar.db.permission.GlobalPermission.ADMINISTER;
46 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
47 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
49 public class GroupPermissionChangerTest {
52 public DbTester db = DbTester.create(System2.INSTANCE);
54 private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT);
55 private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes);
56 private final GroupPermissionChanger underTest = new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory());
57 private GroupDto group;
58 private ComponentDto privateProject;
59 private ComponentDto publicProject;
63 group = db.users().insertGroup("a-group");
64 privateProject = db.components().insertPrivateProject();
65 publicProject = db.components().insertPublicProject();
69 public void apply_adds_global_permission_to_group() {
70 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
72 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, GlobalPermissions.QUALITY_GATE_ADMIN, null, groupUuid, permissionService));
74 assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(GlobalPermissions.QUALITY_GATE_ADMIN);
78 public void apply_adds_global_permission_to_group_AnyOne() {
79 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
81 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, GlobalPermissions.QUALITY_GATE_ADMIN, null, groupUuid, permissionService));
83 assertThat(db.users().selectAnyonePermissions(null)).containsOnly(GlobalPermissions.QUALITY_GATE_ADMIN);
87 public void apply_fails_with_BadRequestException_when_adding_any_permission_to_group_AnyOne_on_private_project() {
88 GroupUuidOrAnyone anyOneGroup = GroupUuidOrAnyone.forAnyone();
89 permissionService.getAllProjectPermissions()
91 GroupPermissionChange change = new GroupPermissionChange(PermissionChange.Operation.ADD, perm, privateProject, anyOneGroup, permissionService);
94 fail("a BadRequestException should have been thrown");
95 } catch (BadRequestException e) {
96 assertThat(e).hasMessage("No permission can be granted to Anyone on a private component");
102 public void apply_has_no_effect_when_removing_any_permission_to_group_AnyOne_on_private_project() {
103 permissionService.getAllProjectPermissions()
104 .forEach(this::unsafeInsertProjectPermissionOnAnyone);
106 GroupUuidOrAnyone anyOneGroup = GroupUuidOrAnyone.forAnyone();
107 permissionService.getAllProjectPermissions()
109 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, perm, privateProject, anyOneGroup, permissionService));
111 assertThat(db.users().selectAnyonePermissions(privateProject)).contains(perm);
116 public void apply_adds_permission_USER_to_group_on_private_project() {
117 applyAddsPermissionToGroupOnPrivateProject(UserRole.USER);
121 public void apply_adds_permission_CODEVIEWER_to_group_on_private_project() {
122 applyAddsPermissionToGroupOnPrivateProject(UserRole.CODEVIEWER);
126 public void apply_adds_permission_ADMIN_to_group_on_private_project() {
127 applyAddsPermissionToGroupOnPrivateProject(UserRole.ADMIN);
131 public void apply_adds_permission_ISSUE_ADMIN_to_group_on_private_project() {
132 applyAddsPermissionToGroupOnPrivateProject(UserRole.ISSUE_ADMIN);
136 public void apply_adds_permission_SCAN_EXECUTION_to_group_on_private_project() {
137 applyAddsPermissionToGroupOnPrivateProject(GlobalPermissions.SCAN_EXECUTION);
140 private void applyAddsPermissionToGroupOnPrivateProject(String permission) {
141 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
143 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, permission, privateProject, groupUuid, permissionService));
145 assertThat(db.users().selectGroupPermissions(group, null)).isEmpty();
146 assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission);
150 public void apply_removes_permission_USER_from_group_on_private_project() {
151 applyRemovesPermissionFromGroupOnPrivateProject(UserRole.USER);
155 public void apply_removes_permission_CODEVIEWER_from_group_on_private_project() {
156 applyRemovesPermissionFromGroupOnPrivateProject(UserRole.CODEVIEWER);
160 public void apply_removes_permission_ADMIN_from_on_private_project() {
161 applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ADMIN);
165 public void apply_removes_permission_ISSUE_ADMIN_from_on_private_project() {
166 applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ISSUE_ADMIN);
170 public void apply_removes_permission_SCAN_EXECUTION_from_on_private_project() {
171 applyRemovesPermissionFromGroupOnPrivateProject(GlobalPermissions.SCAN_EXECUTION);
174 private void applyRemovesPermissionFromGroupOnPrivateProject(String permission) {
175 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
176 db.users().insertProjectPermissionOnGroup(group, permission, privateProject);
178 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, permission, privateProject, groupUuid, permissionService));
180 assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission);
184 public void apply_has_no_effect_when_adding_USER_permission_to_group_AnyOne_on_a_public_project() {
185 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
187 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.USER, publicProject, groupUuid, permissionService));
189 assertThat(db.users().selectAnyonePermissions(publicProject)).isEmpty();
193 public void apply_has_no_effect_when_adding_CODEVIEWER_permission_to_group_AnyOne_on_a_public_project() {
194 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
196 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.CODEVIEWER, publicProject, groupUuid, permissionService));
198 assertThat(db.users().selectAnyonePermissions(publicProject)).isEmpty();
202 public void apply_fails_with_BadRequestException_when_adding_permission_ADMIN_to_group_AnyOne_on_a_public_project() {
203 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
205 assertThatThrownBy(() -> apply(new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.ADMIN, publicProject, groupUuid, permissionService)))
206 .isInstanceOf(BadRequestException.class)
207 .hasMessage("It is not possible to add the 'admin' permission to group 'Anyone'.");
211 public void apply_adds_permission_ISSUE_ADMIN_to_group_AnyOne_on_a_public_project() {
212 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
214 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.ISSUE_ADMIN, publicProject, groupUuid, permissionService));
216 assertThat(db.users().selectAnyonePermissions(publicProject)).containsOnly(UserRole.ISSUE_ADMIN);
220 public void apply_adds_permission_SCAN_EXECUTION_to_group_AnyOne_on_a_public_project() {
221 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
223 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, GlobalPermissions.SCAN_EXECUTION, publicProject, groupUuid, permissionService));
225 assertThat(db.users().selectAnyonePermissions(publicProject)).containsOnly(GlobalPermissions.SCAN_EXECUTION);
229 public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_group_AnyOne_on_a_public_project() {
230 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
232 assertThatThrownBy(() -> apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.USER, publicProject, groupUuid, permissionService)))
233 .isInstanceOf(BadRequestException.class)
234 .hasMessage("Permission user can't be removed from a public component");
238 public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_group_AnyOne_on_a_public_project() {
239 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
241 assertThatThrownBy(() -> apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.CODEVIEWER, publicProject, groupUuid, permissionService)))
242 .isInstanceOf(BadRequestException.class)
243 .hasMessage("Permission codeviewer can't be removed from a public component");
247 public void apply_removes_ADMIN_permission_from_group_AnyOne_on_a_public_project() {
248 applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ADMIN);
252 public void apply_removes_ISSUE_ADMIN_permission_from_group_AnyOne_on_a_public_project() {
253 applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ISSUE_ADMIN);
257 public void apply_removes_SCAN_EXECUTION_permission_from_group_AnyOne_on_a_public_project() {
258 applyRemovesPermissionFromGroupAnyOneOnAPublicProject(GlobalPermissions.SCAN_EXECUTION);
261 private void applyRemovesPermissionFromGroupAnyOneOnAPublicProject(String permission) {
262 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
263 db.users().insertProjectPermissionOnAnyone(permission, publicProject);
265 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, permission, publicProject, groupUuid, permissionService));
267 assertThat(db.users().selectAnyonePermissions(publicProject)).isEmpty();
271 public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_a_group_on_a_public_project() {
272 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
274 assertThatThrownBy(() -> apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.USER, publicProject, groupUuid, permissionService)))
275 .isInstanceOf(BadRequestException.class)
276 .hasMessage("Permission user can't be removed from a public component");
280 public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_a_group_on_a_public_project() {
281 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
283 assertThatThrownBy(() -> apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.CODEVIEWER, publicProject, groupUuid, permissionService)))
284 .isInstanceOf(BadRequestException.class)
285 .hasMessage("Permission codeviewer can't be removed from a public component");
289 public void add_permission_to_anyone() {
290 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.forAnyone();
292 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, GlobalPermissions.QUALITY_GATE_ADMIN, null, groupUuid, permissionService));
294 assertThat(db.users().selectGroupPermissions(group, null)).isEmpty();
295 assertThat(db.users().selectAnyonePermissions(null)).containsOnly(GlobalPermissions.QUALITY_GATE_ADMIN);
299 public void do_nothing_when_adding_permission_that_already_exists() {
300 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
301 db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES);
303 apply(new GroupPermissionChange(PermissionChange.Operation.ADD, ADMINISTER_QUALITY_GATES.getKey(), null, groupUuid, permissionService));
305 assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_GATES.getKey());
309 public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_private_project() {
310 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
312 permissionService.getGlobalPermissions().stream()
313 .map(GlobalPermission::getKey)
314 .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermissions.SCAN_EXECUTION.equals(perm))
317 new GroupPermissionChange(PermissionChange.Operation.ADD, perm, privateProject, groupUuid, permissionService);
318 fail("a BadRequestException should have been thrown for permission " + perm);
319 } catch (BadRequestException e) {
320 assertThat(e).hasMessage("Invalid project permission '" + perm +
321 "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]");
327 public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_public_project() {
328 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
330 permissionService.getGlobalPermissions().stream()
331 .map(GlobalPermission::getKey)
332 .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermissions.SCAN_EXECUTION.equals(perm))
335 new GroupPermissionChange(PermissionChange.Operation.ADD, perm, publicProject, groupUuid, permissionService);
336 fail("a BadRequestException should have been thrown for permission " + perm);
337 } catch (BadRequestException e) {
338 assertThat(e).hasMessage("Invalid project permission '" + perm +
339 "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]");
345 public void fail_to_add_project_permission_but_SCAN_and_ADMIN_on_global_group() {
346 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
348 permissionService.getAllProjectPermissions()
350 .filter(perm -> !GlobalPermissions.SCAN_EXECUTION.equals(perm) && !GlobalPermission.ADMINISTER.getKey().equals(perm))
351 .forEach(permission -> {
353 new GroupPermissionChange(PermissionChange.Operation.ADD, permission, null, groupUuid, permissionService);
354 fail("a BadRequestException should have been thrown for permission " + permission);
355 } catch (BadRequestException e) {
356 assertThat(e).hasMessage("Invalid global permission '" + permission + "'. Valid values are [admin, gateadmin, profileadmin, provisioning, scan]");
362 public void remove_permission_from_group() {
363 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
364 db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES);
365 db.users().insertPermissionOnGroup(group, PROVISION_PROJECTS);
367 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, ADMINISTER_QUALITY_GATES.getKey(), null, groupUuid, permissionService));
369 assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(PROVISION_PROJECTS.getKey());
373 public void remove_project_permission_from_group() {
374 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
375 db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES);
376 db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, privateProject);
377 db.users().insertProjectPermissionOnGroup(group, UserRole.CODEVIEWER, privateProject);
379 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, groupUuid, permissionService));
381 assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_GATES.getKey());
382 assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(UserRole.CODEVIEWER);
386 public void do_not_fail_if_removing_a_permission_that_does_not_exist() {
387 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
389 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, groupUuid, permissionService));
391 assertThat(db.users().selectGroupPermissions(group, null)).isEmpty();
392 assertThat(db.users().selectGroupPermissions(group, privateProject)).isEmpty();
396 public void fail_to_remove_admin_permission_if_no_more_admins() {
397 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
398 db.users().insertPermissionOnGroup(group, ADMINISTER);
400 assertThatThrownBy(() -> underTest.apply(db.getSession(), new GroupPermissionChange(PermissionChange.Operation.REMOVE, ADMINISTER.getKey(), null, groupUuid, permissionService)))
401 .isInstanceOf(BadRequestException.class)
402 .hasMessage("Last group with permission 'admin'. Permission cannot be removed.");
406 public void remove_admin_group_if_still_other_admins() {
407 GroupUuidOrAnyone groupUuid = GroupUuidOrAnyone.from(group);
408 db.users().insertPermissionOnGroup(group, ADMINISTER);
409 UserDto admin = db.users().insertUser();
410 db.users().insertPermissionOnUser(admin, ADMINISTER);
412 apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, ADMINISTER.getKey(), null, groupUuid, permissionService));
414 assertThat(db.users().selectGroupPermissions(group, null)).isEmpty();
417 private void apply(GroupPermissionChange change) {
418 underTest.apply(db.getSession(), change);
422 private void unsafeInsertProjectPermissionOnAnyone(String perm) {
423 GroupPermissionDto dto = new GroupPermissionDto()
424 .setUuid(Uuids.createFast())
427 .setComponentUuid(privateProject.uuid())
428 .setComponentName(privateProject.name());
429 db.getDbClient().groupPermissionDao().insert(db.getSession(), dto, privateProject, null);