diff options
21 files changed, 485 insertions, 12 deletions
diff --git a/build.gradle b/build.gradle index 6313a872b1e..726da0b4fe6 100644 --- a/build.gradle +++ b/build.gradle @@ -340,7 +340,7 @@ subprojects { dependency 'org.postgresql:postgresql:42.5.0' dependency 'org.reflections:reflections:0.10.2' dependency 'org.simpleframework:simple:5.1.6' - dependency 'org.sonarsource.orchestrator:sonar-orchestrator:3.39.0.167' + dependency 'org.sonarsource.orchestrator:sonar-orchestrator:3.40.0.183' dependency 'org.sonarsource.update-center:sonar-update-center-common:1.29.0.1000' dependency('org.springframework:spring-context:5.3.23') { exclude 'commons-logging:commons-logging' diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionDao.java index c23b174baee..3de87b133e1 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionDao.java @@ -73,6 +73,14 @@ public class GroupPermissionDao implements Dao { return executeLargeInputs(groupUuids, groups -> mapper(dbSession).selectByGroupUuids(groups, projectUuid)); } + public List<String> selectProjectKeysWithAnyonePermissions(DbSession dbSession, int max) { + return mapper(dbSession).selectProjectKeysWithAnyonePermissions(max); + } + + public int countProjectsWithAnyonePermissions(DbSession dbSession) { + return mapper(dbSession).countProjectsWithAnyonePermissions(); + } + /** * Select global and project permissions of a given group (Anyone group is NOT supported) * Each row returns a {@link GroupPermissionDto} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionMapper.java index a97740da6af..bcb9705b655 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/GroupPermissionMapper.java @@ -37,6 +37,10 @@ public interface GroupPermissionMapper { void groupsCountByProjectUuidAndPermission(Map<String, Object> parameters, ResultHandler<CountPerProjectPermission> resultHandler); + List<String> selectProjectKeysWithAnyonePermissions(int max); + + int countProjectsWithAnyonePermissions(); + void insert(GroupPermissionDto dto); int delete(@Param("permission") String permission, @Nullable @Param("groupUuid") String groupUuid, @Nullable @Param("rootComponentUuid") String rootComponentUuid); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/GroupPermissionMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/GroupPermissionMapper.xml index 1ce169e2918..d062673b736 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/GroupPermissionMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/GroupPermissionMapper.xml @@ -39,6 +39,37 @@ groups.componentUuid </select> + <select id="selectProjectKeysWithAnyonePermissions" parameterType="int" resultType="string"> + select distinct(p.kee) as kee from group_roles gr + left join projects p on gr.component_uuid = p.uuid + where gr.group_uuid is null and gr.component_uuid is not null + ORDER BY kee ASC + limit #{max} + </select> + + <!-- Oracle --> + <select id="selectProjectKeysWithAnyonePermissions" parameterType="int" resultType="string" databaseId="oracle"> + select * from (select distinct p.kee as kee from group_roles gr + left join projects p on gr.component_uuid = p.uuid + where gr.group_uuid is null and gr.component_uuid is not null + ORDER BY kee ASC + ) + where rownum <= #{max} + </select> + + <!-- SQL Server --> + <select id="selectProjectKeysWithAnyonePermissions" parameterType="int" resultType="string" databaseId="mssql"> + select distinct top(#{max}) p.kee as kee from group_roles gr + left join projects p on gr.component_uuid = p.uuid + where gr.group_uuid is null and gr.component_uuid is not null + ORDER BY kee ASC + </select> + + <select id="countProjectsWithAnyonePermissions" resultType="int"> + select count(distinct(gr.component_uuid)) + from group_roles gr where gr.group_uuid is null and gr.component_uuid is not null + </select> + <select id="selectGroupNamesByQuery" parameterType="map" resultType="string"> select sub.name, lower(sub.name), sub.groupUuid <include refid="groupsByQuery"/> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/permission/GroupPermissionDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/permission/GroupPermissionDaoTest.java index 33fc0b5dd3e..ea7679c9e78 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/permission/GroupPermissionDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/permission/GroupPermissionDaoTest.java @@ -438,6 +438,65 @@ public class GroupPermissionDaoTest { } @Test + public void selectProjectKeysWithAnyonePermissions_on_public_project_none_found() { + ComponentDto project1 = db.components().insertPublicProject(); + ComponentDto project2 = db.components().insertPublicProject(); + GroupDto group = db.users().insertGroup(); + db.users().insertProjectPermissionOnGroup(group, "perm1", project1); + db.users().insertProjectPermissionOnGroup(group, "perm1", project2); + assertThat(underTest.selectProjectKeysWithAnyonePermissions(dbSession, 3)).isEmpty(); + } + + @Test + public void selectProjectKeysWithAnyonePermissions_on_public_project_ordered_by_kee() { + ComponentDto project1 = db.components().insertPublicProject(); + ComponentDto project2 = db.components().insertPublicProject(); + ComponentDto project3 = db.components().insertPublicProject(); + db.users().insertProjectPermissionOnAnyone("perm1", project1); + db.users().insertProjectPermissionOnAnyone("perm1", project2); + db.users().insertProjectPermissionOnAnyone("perm1", project3); + assertThat(underTest.selectProjectKeysWithAnyonePermissions(dbSession, 3)) + .containsExactly(project1.getKey(), project2.getKey(), project3.getKey()); + } + + @Test + public void selectProjectKeysWithAnyonePermissions_on_public_project_ordered_by_kee_max_5() { + IntStream.rangeClosed(1, 9).forEach(i -> { + ComponentDto project = db.components().insertPublicProject(p -> p.setKey("key-" + i)); + db.users().insertProjectPermissionOnAnyone("perm-" + i, project); + }); + + assertThat(underTest.selectProjectKeysWithAnyonePermissions(dbSession, 5)) + .containsExactly("key-1", "key-2", "key-3", "key-4", "key-5"); + } + + @Test + public void selectProjectKeysWithAnyonePermissions_on_public_projects_omit_blanket_anyone_group_permissions() { + // Although saved in the same table (group_roles), this should not be included in the result as not assigned to single project. + db.users().insertPermissionOnAnyone("perm-anyone"); + + IntStream.rangeClosed(1, 9).forEach(i -> { + ComponentDto project = db.components().insertPublicProject(p -> p.setKey("key-" + i)); + db.users().insertProjectPermissionOnAnyone("perm-" + i, project); + }); + + assertThat(underTest.selectProjectKeysWithAnyonePermissions(dbSession, 5)) + .containsExactly("key-1", "key-2", "key-3", "key-4", "key-5"); + } + + @Test + public void countProjectsWithAnyonePermissions() { + GroupDto group = db.users().insertGroup(); + IntStream.rangeClosed(1, 5).forEach(i -> { + ComponentDto project = db.components().insertPublicProject(p -> p.setKey("key-" + i)); + db.users().insertProjectPermissionOnAnyone("perm-" + i, project); + db.users().insertProjectPermissionOnGroup(group, "perm-", project); + }); + + assertThat(underTest.countProjectsWithAnyonePermissions(dbSession)).isEqualTo(5); + } + + @Test public void selectProjectPermissionsOfGroup_on_private_project() { GroupDto group1 = db.users().insertGroup("group1"); ComponentDto project1 = db.components().insertPrivateProject(); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java index 762a1c14213..c374e62fa84 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java @@ -169,7 +169,7 @@ public class PopulateInitialSchema extends DataChange { upsert .setString(1, uuidFactory.create()) .setString(2, USERS_GROUP) - .setString(3, "Any new users created will automatically join this group") + .setString(3, "Every authenticated user automatically belongs to this group") .setDate(4, now) .setDate(5, now) .addBatch(); @@ -235,7 +235,7 @@ public class PopulateInitialSchema extends DataChange { for (String anyoneRole : Arrays.asList("scan", "provisioning")) { upsert .setString(1, uuidFactory.create()) - .setString(2, null) + .setString(2, groups.getUserGroupUuid()) .setString(3, anyoneRole) .addBatch(); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/DbVersion98.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/DbVersion98.java index f792305f9c7..ab5da5a55db 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/DbVersion98.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/DbVersion98.java @@ -30,6 +30,7 @@ public class DbVersion98 implements DbVersion { .add(6701, "Drop live measure variation column", DropLiveMeasureVariationColumn.class) .add(6702, "Move project measure variations to values", MoveProjectMeasureVariationToValue.class) .add(6703, "Drop project measure variation column", DropProjectMeasureVariationColumn.class) + .add(6704, "Update sonar-users group description", UpsertSonarUsersDescription.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescription.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescription.java new file mode 100644 index 00000000000..52b72475631 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescription.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db.migration.version.v98; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.Upsert; + +public class UpsertSonarUsersDescription extends DataChange { + + public UpsertSonarUsersDescription(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + Upsert upsert = context.prepareUpsert("update groups set description = ? where name = 'sonar-users'"); + upsert.setString(1, "Every authenticated user automatically belongs to this group"); + upsert.execute(); + upsert.commit(); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchemaTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchemaTest.java index ad8bc02f549..909159f0a09 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchemaTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchemaTest.java @@ -67,7 +67,7 @@ public class PopulateInitialSchemaTest { underTest.execute(); verifyAdminUser(); - verifyGroup("sonar-users", "Any new users created will automatically join this group"); + verifyGroup("sonar-users", "Every authenticated user automatically belongs to this group"); verifyGroup("sonar-administrators", "System administrators"); String qualityGateUuid = verifyQualityGate(); verifyInternalProperties(); @@ -204,7 +204,7 @@ public class PopulateInitialSchemaTest { } private void verifyRolesOfUsersGroup() { - assertThat(selectRoles("sonar-users")).isEmpty(); + assertThat(selectRoles("sonar-users")).containsOnly("provisioning", "scan"); } private void verifyRolesOfAnyone() { @@ -212,7 +212,7 @@ public class PopulateInitialSchemaTest { "from group_roles gr where gr.group_uuid is null"); Stream<String> roles = rows.stream() .map(row -> (String) row.get("role")); - assertThat(roles).containsOnly("provisioning", "scan"); + assertThat(roles).isEmpty(); } private Stream<String> selectRoles(String groupName) { diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest.java new file mode 100644 index 00000000000..b894de21c6b --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db.migration.version.v98; + +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UpsertSonarUsersDescriptionTest { + + private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + public static final String OLD_DESCRIPTION = "Any new users created will automatically join this group"; + public static final String NEW_DESCRIPTION = "Every authenticated user automatically belongs to this group"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(UpsertSonarUsersDescriptionTest.class, "schema.sql"); + + private final DataChange underTest = new UpsertSonarUsersDescription(db.database()); + + @Test + public void migration_populates_sonar_users_group_description() throws SQLException { + String uuid = insertSonarUsersGroupWithOldDescription(); + underTest.execute(); + assertSonarUsersGroupDescriptionIsUpsertedCorrectly(uuid); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + String userUuid1 = insertSonarUsersGroupWithOldDescription(); + underTest.execute(); + // re-entrant + underTest.execute(); + assertSonarUsersGroupDescriptionIsUpsertedCorrectly(userUuid1); + } + + private void assertSonarUsersGroupDescriptionIsUpsertedCorrectly(String userUuid) { + String selectSql = String.format("select description from groups where uuid='%s'", userUuid); + assertThat(db.select(selectSql).stream().map(row -> row.get("DESCRIPTION")).collect(Collectors.toList())) + .containsExactlyInAnyOrder(UpsertSonarUsersDescriptionTest.NEW_DESCRIPTION); + } + + private String insertSonarUsersGroupWithOldDescription() { + Map<String, Object> map = new HashMap<>(); + String uuid = uuidFactory.create(); + map.put("UUID", uuid); + map.put("NAME", "sonar-users"); + map.put("DESCRIPTION", OLD_DESCRIPTION); + map.put("CREATED_AT", new Date()); + db.executeInsert("groups", map); + return uuid; + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest/schema.sql new file mode 100644 index 00000000000..6a9845ad643 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v98/UpsertSonarUsersDescriptionTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "GROUPS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "NAME" CHARACTER VARYING(500) NOT NULL, + "DESCRIPTION" CHARACTER VARYING(200), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); +ALTER TABLE "GROUPS" ADD CONSTRAINT "PK_GROUPS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_GROUPS_NAME" ON "GROUPS"("NAME" NULLS FIRST); diff --git a/server/sonar-web/src/main/js/apps/groups/components/List.tsx b/server/sonar-web/src/main/js/apps/groups/components/List.tsx index a07ca958903..e3808393622 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/List.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/List.tsx @@ -50,6 +50,7 @@ export default function List(props: Props) { <tr className="js-anyone" key="anyone"> <td className="width-20"> <strong className="js-group-name">{translate('groups.anyone')}</strong> + <span className="spacer-left badge badge-error">{translate('deprecated')}</span> </td> <td className="width-10" colSpan={2} /> <td className="width-40" colSpan={2}> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap index 3598b1a868d..2db446a6c97 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap @@ -38,6 +38,11 @@ exports[`should render 1`] = ` > groups.anyone </strong> + <span + className="spacer-left badge badge-error" + > + deprecated + </span> </td> <td className="width-10" diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx index 0203577dab6..71bf88da055 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx @@ -19,6 +19,7 @@ */ import { without } from 'lodash'; import * as React from 'react'; +import { translate } from '../../../../helpers/l10n'; import GroupIcon from '../../../../components/icons/GroupIcon'; import { PermissionDefinitions, PermissionGroup } from '../../../../types/types'; import { isPermissionDefinitionGroup } from '../../utils'; @@ -35,6 +36,8 @@ interface State { loading: string[]; } +const ANYONE = 'Anyone'; + export default class GroupHolder extends React.PureComponent<Props, State> { mounted = false; state: State = { loading: [] }; @@ -74,9 +77,14 @@ export default class GroupHolder extends React.PureComponent<Props, State> { <div className="max-width-100"> <div className="max-width-100 text-ellipsis"> <strong>{group.name}</strong> + {group.name === ANYONE && ( + <span className="spacer-left badge badge-error">{translate('deprecated')}</span> + )} </div> <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}> - {group.description} + {group.name === ANYONE + ? translate('user_groups.anyone.description') + : group.description} </div> </div> </div> diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartup.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartup.java new file mode 100644 index 00000000000..52f6a8b491f --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartup.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db; + +import java.util.List; +import java.util.Optional; +import org.sonar.api.Startable; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +/** + * Checks if there are any projects which have 'Anyone' group permissions at startup, after executing db migrations. If + * any are found, it is logged as a warning, and some example projects (up to 3) are listed. This requires to be defined + * in platform level 4 ({@link org.sonar.server.platform.platformlevel.PlatformLevel4}). + */ +public class CheckAnyonePermissionsAtStartup implements Startable { + + private static final Logger LOG = Loggers.get(CheckAnyonePermissionsAtStartup.class); + private static final String FORCE_AUTHENTICATION_PROPERTY_NAME = "sonar.forceAuthentication"; + private final DbClient dbClient; + private final Configuration config; + + public CheckAnyonePermissionsAtStartup(DbClient dbClient, Configuration config) { + this.dbClient = dbClient; + this.config = config; + } + + @Override + public void start() { + Optional<Boolean> property = config.getBoolean(FORCE_AUTHENTICATION_PROPERTY_NAME); + if (property.isEmpty() || Boolean.TRUE.equals(property.get())) { + return; + } + + logWarningIfProjectsWithAnyonePermissionsExist(); + } + + private void logWarningIfProjectsWithAnyonePermissionsExist() { + try (DbSession dbSession = dbClient.openSession(false)) { + int total = dbClient.groupPermissionDao().countProjectsWithAnyonePermissions(dbSession); + if (total > 0) { + List<String> list = dbClient.groupPermissionDao().selectProjectKeysWithAnyonePermissions(dbSession, 3); + LOG.warn("A total of {} public project(s) are found to have enabled 'Anyone' group permissions, including: {}. " + + "Make sure your project permissions are set as intended.", + total, String.join(", ", list)); + } + } + } + + @Override + public void stop() { + // do nothing + } + +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartupTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartupTest.java new file mode 100644 index 00000000000..edd4fe9e716 --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/CheckAnyonePermissionsAtStartupTest.java @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.platform.db; + +import java.util.stream.IntStream; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.user.GroupDto; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CheckAnyonePermissionsAtStartupTest { + + @ClassRule + public static LogTester logTester = new LogTester().setLevel(LoggerLevel.WARN); + @Rule + public final DbTester dbTester = DbTester.create(System2.INSTANCE); + private final DbClient dbClient = dbTester.getDbClient(); + private final MapSettings settings = new MapSettings(); + private final CheckAnyonePermissionsAtStartup underTest = new CheckAnyonePermissionsAtStartup(dbClient, settings.asConfig()); + + @After + public void tearDown() { + logTester.clear(); + underTest.stop(); + } + + @Test + public void test_logs_present_when_exactly_3_projects_contain_anyone_permissions_and_force_authentication_false() { + int expectedProjectCount = 3; + setForceAuthentication(false); + execute(expectedProjectCount); + assertAnyonePermissionWarningInLogs(expectedProjectCount, "key-1", "key-2", "key-3"); + } + + @Test + public void test_logs_present_when_less_than_3_projects_contain_anyone_permissions_and_force_authentication_false() { + int expectedProjectCount = 1; + setForceAuthentication(false); + execute(expectedProjectCount); + assertAnyonePermissionWarningInLogs(expectedProjectCount, "key-1"); + } + + @Test + public void test_logs_present_when_more_than_3_projects_contain_anyone_permissions_and_force_authentication_false() { + int expectedProjectCount = 8; + setForceAuthentication(false); + execute(expectedProjectCount); + assertAnyonePermissionWarningInLogs(expectedProjectCount, "key-1", "key-2", "key-3"); + } + + @Test + public void test_logs_not_present_when_no_projects_contain_anyone_permissions_and_force_authentication_false() { + setForceAuthentication(false); + generatePublicProjectsWithGroupPermissions(); + assertAnyonePermissionWarningNotInLogs(); + } + + @Test + public void test_logs_present_when_1_projects_contain_anyone_permissions_and_full_anyone_group_permission_and_force_authentication_false() { + // Although saved in the same table (group_roles), this should not be included in the logs as not assigned to single project. + dbTester.users().insertPermissionOnAnyone("perm-anyone"); + + int expectedProjectCount = 1; + setForceAuthentication(false); + execute(expectedProjectCount); + assertAnyonePermissionWarningInLogs(expectedProjectCount, "key-1"); + } + + @Test + public void test_logs_not_present_when_some_projects_contain_anyone_permissions_and_force_authentication_true() { + setForceAuthentication(true); + execute(3); + assertAnyonePermissionWarningNotInLogs(); + } + + @Test + public void test_logs_not_present_when_no_projects_contain_anyone_permissions_and_force_authentication_true() { + setForceAuthentication(true); + generatePublicProjectsWithGroupPermissions(); + assertAnyonePermissionWarningNotInLogs(); + } + + @Test + public void test_logs_not_present_when_projects_contain_anyone_permissions_and_force_authentication_default() { + settings.clear(); + execute(3); + assertAnyonePermissionWarningNotInLogs(); + } + + private void generatePublicProjectsWithGroupPermissions() { + GroupDto group = dbTester.users().insertGroup(); + IntStream.rangeClosed(1, 3).forEach(i -> { + ComponentDto project = dbTester.components().insertPublicProject(p -> p.setKey("key-" + i)); + dbTester.users().insertProjectPermissionOnGroup(group, "perm-" + i, project); + }); + underTest.start(); + } + + private void execute(int projectCount) { + IntStream.rangeClosed(1, projectCount).forEach(i -> { + ComponentDto project = dbTester.components().insertPublicProject(p -> p.setKey("key-" + i)); + dbTester.users().insertProjectPermissionOnAnyone("perm-" + i, project); + }); + underTest.start(); + } + + private void setForceAuthentication(Boolean isForceAuthentication) { + settings.setProperty("sonar.forceAuthentication", isForceAuthentication.toString()); + } + + private void assertAnyonePermissionWarningNotInLogs() { + boolean noneMatch = logTester.logs().stream() + .noneMatch(s -> s.matches(".*A total of [0-9]+ public project\\(s\\) are found to have enabled 'Anyone' group permissions, including: %s. " + + "Make sure your project permissions are set as intended.*")); + assertThat(noneMatch).isTrue(); + } + + private void assertAnyonePermissionWarningInLogs(int expectedProjectCountString, String... expectedListedProjects) { + String expected = String.format("A total of %d public project(s) are found to have enabled 'Anyone' group permissions, including: %s. " + + "Make sure your project permissions are set as intended.", + expectedProjectCountString, String.join(", ", expectedListedProjects)); + assertThat(logTester.logs(LoggerLevel.WARN)).contains(expected); + } + +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json index 9a67c7fa65f..5e69f5b688f 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json @@ -18,7 +18,7 @@ { "id": "AU-Tpxb--iU5OvuD2FLz", "name": "sonar-users", - "description": "Any new users created will automatically join this group", + "description": "Every authenticated user automatically belongs to this group", "permissions": [] } ] diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/template/template_groups-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/template/template_groups-example.json index 7a8ead45905..ee39ebfac02 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/template/template_groups-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/template/template_groups-example.json @@ -21,7 +21,7 @@ }, { "name": "sonar-users", - "description": "Any new users created will automatically join this group", + "description": "Every authenticated user automatically belongs to this group", "permissions": [ "issueadmin" ] diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java index 3b74ec90074..44508533191 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java @@ -85,7 +85,7 @@ public class TemplateGroupsActionTest extends BasePermissionWsTest<TemplateGroup @Test public void template_groups_of_json_example() { GroupDto adminGroup = insertGroup("sonar-administrators", "System administrators"); - GroupDto userGroup = insertGroup("sonar-users", "Any new users created will automatically join this group"); + GroupDto userGroup = insertGroup("sonar-users", "Every authenticated user automatically belongs to this group"); PermissionTemplateDto template = addTemplate(); addGroupToTemplate(newPermissionTemplateGroup(ISSUE_ADMIN, template.getUuid(), adminGroup.getUuid()), template.getName()); diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 3f3c27434fe..a57f28666b7 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -155,6 +155,7 @@ import org.sonar.server.platform.ClusterVerification; import org.sonar.server.platform.PersistentSettings; import org.sonar.server.platform.SystemInfoWriterModule; import org.sonar.server.platform.WebCoreExtensionsInstaller; +import org.sonar.server.platform.db.CheckAnyonePermissionsAtStartup; import org.sonar.server.platform.web.SonarLintConnectionFilter; import org.sonar.server.platform.web.WebServiceFilter; import org.sonar.server.platform.web.WebServiceReroutingFilter; @@ -412,6 +413,7 @@ public class PlatformLevel4 extends PlatformLevel { PermissionUpdater.class, UserPermissionChanger.class, GroupPermissionChanger.class, + CheckAnyonePermissionsAtStartup.class, // components new BranchWsModule(), @@ -631,7 +633,6 @@ public class PlatformLevel4 extends PlatformLevel { MainCollector.class, PluginsRiskConsentFilter.class - ); // system info diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 411dd234743..a0405c19920 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4288,7 +4288,7 @@ users.change_admin_password.form.continue_to_app=Continue to SonarQube #------------------------------------------------------------------------------ user_groups.page=Groups user_groups.page.description=Create and administer groups of users. -user_groups.anyone.description=Anybody (authenticated or not) who browses the application belongs to this group +user_groups.anyone.description=Anybody (authenticated or not) who browses the application belongs to this group. If Force user authentication is disabled, any permissions assigned to this group will apply to non-authenticated users. groups.delete_group=Delete Group groups.delete_group.confirmation=Are you sure you want to delete "{0}"? groups.create_group=Create Group |