diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 1ee886b4a7682ba584e202de5425dd460253d37c..2923241485af7422c0bce3eec191ece9433a5040 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -503,6 +503,7 @@ def visible_attributes :bulk_import_enabled, :bulk_import_max_download_file_size, :silent_admin_exports_enabled, + :allow_contribution_mapping_to_admins, :allow_runner_registration_token, :user_defaults_to_private_profile, :deactivation_email_additional_text, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d19ea0f41ea70a3fc7c5053e12eaf6c621d2a3bb..c9e90ffbdce87b8b67421099ce025d1115c2b922 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -648,7 +648,8 @@ def self.kroki_formats_attributes throttle_unauthenticated_git_http_period_in_seconds: [:integer, { default: 3600 }] jsonb_accessor :importers, - silent_admin_exports_enabled: [:boolean, { default: false }] + silent_admin_exports_enabled: [:boolean, { default: false }], + allow_contribution_mapping_to_admins: [:boolean, { default: false }] validates :rate_limits, json_schema: { filename: "application_setting_rate_limits" } diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 342e76f06d44d74ff670bdf6f3e90d54667bd382..aa58009b3d781423a4e6464bd8c4ce3a62728a28 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -264,6 +264,7 @@ def defaults # rubocop:disable Metrics/AbcSize bulk_import_enabled: false, bulk_import_max_download_file_size: 5120, silent_admin_exports_enabled: false, + allow_contribution_mapping_to_admins: false, allow_runner_registration_token: true, user_defaults_to_private_profile: false, projects_api_rate_limit_unauthenticated: 400, diff --git a/app/services/import/source_users/reassign_service.rb b/app/services/import/source_users/reassign_service.rb index e872bd124ec31c5c5ab917841786a26964190b18..9a6020119c5d2a193dd139ec1788a0fc72d7f5b6 100644 --- a/app/services/import/source_users/reassign_service.rb +++ b/app/services/import/source_users/reassign_service.rb @@ -35,14 +35,34 @@ def reassign_user def error_invalid_assignee ServiceResponse.error( - message: s_('Import|Only active regular, auditor, or administrator users can be assigned'), + message: invalid_assignee_message, reason: :invalid_assignee, payload: import_source_user ) end + def invalid_assignee_message + if allow_mapping_to_admins? + s_('UserMapping|You can assign users with regular, auditor, or administrator access only.') + else + s_('UserMapping|You can assign only active users with regular or auditor access. ' \ + 'To assign users with administrator access, ask your GitLab administrator to ' \ + 'enable the "Allow contribution mapping to admins" setting.') + end + end + def valid_assignee?(user) - user.present? && user.human? && user.active? + user.present? && + user.human? && + user.active? && + # rubocop:disable Cop/UserAdmin -- This should not be affected by admin mode. + # We just want to know whether the user CAN have admin privileges or not. + (allow_mapping_to_admins? ? true : !user.admin?) + # rubocop:enable Cop/UserAdmin + end + + def allow_mapping_to_admins? + ::Gitlab::CurrentSettings.allow_contribution_mapping_to_admins? end end end diff --git a/app/validators/json_schemas/application_setting_importers.json b/app/validators/json_schemas/application_setting_importers.json index c69507456b8c36b0934b90673906f5d23c979fc3..f33229fc2969ecfdab3001b69fa61ce5b82a9d37 100644 --- a/app/validators/json_schemas/application_setting_importers.json +++ b/app/validators/json_schemas/application_setting_importers.json @@ -5,6 +5,9 @@ "properties": { "silent_admin_exports_enabled": { "type": "boolean" + }, + "allow_contribution_mapping_to_admins": { + "type": "boolean" } }, "additionalProperties": false diff --git a/app/views/admin/application_settings/_import_and_export.html.haml b/app/views/admin/application_settings/_import_and_export.html.haml index b3a8b6cc5f6f85d8e74656b53cd7f1177593ad81..31b7cccce20c3f8c2ff06a8d7006e638a8d12131 100644 --- a/app/views/admin/application_settings/_import_and_export.html.haml +++ b/app/views/admin/application_settings/_import_and_export.html.haml @@ -22,6 +22,12 @@ - silent_admin_exports_label_text = safe_format(s_('AdminSettings|Silent exports by admins %{silent_admin_exports_link_start}%{icon}%{silent_admin_exports_link_end}'), tag_pair_silent_admin_exports, icon: sprite_icon('question-o')) = f.label :silent_admin_exports_enabled, silent_admin_exports_label_text, class: 'gl-font-bold' = f.gitlab_ui_checkbox_component :silent_admin_exports_enabled, s_('AdminSettings|Enabled') + - if Feature.enabled?(:importer_user_mapping, current_user) + .form-group + - tag_pair_allow_contribution_mapping_to_admins = tag_pair(link_to('', help_page_path('administration/settings/import_and_export_settings.md', anchor: 'allow-contribution-mapping-to-administrators'), target: '_blank', rel: 'noopener noreferrer'), :allow_contribution_mapping_to_admins_link_start, :allow_contribution_mapping_to_admins_link_end) + - allow_contribution_mapping_to_admins_label_text = safe_format(s_('AdminSettings|Allow contribution mapping to administrators %{allow_contribution_mapping_to_admins_link_start}%{icon}%{allow_contribution_mapping_to_admins_link_end}'), tag_pair_allow_contribution_mapping_to_admins, icon: sprite_icon('question-o')) + = f.label :allow_contribution_mapping_to_admins, allow_contribution_mapping_to_admins_label_text, class: 'gl-font-bold' + = f.gitlab_ui_checkbox_component :allow_contribution_mapping_to_admins, s_('AdminSettings|Enabled') .form-group = f.label :max_export_size, _('Maximum export size (MiB)'), class: 'label-light' = f.number_field :max_export_size, class: 'form-control gl-form-input', title: _('Maximum size of export files.'), data: { toggle: 'tooltip', container: 'body' } diff --git a/doc/administration/settings/import_and_export_settings.md b/doc/administration/settings/import_and_export_settings.md index c50cd43d6bc88a40050738c6635a1d141141c4db..43a1d4067a860373dcdcd1da92b6397fbb6bf831 100644 --- a/doc/administration/settings/import_and_export_settings.md +++ b/doc/administration/settings/import_and_export_settings.md @@ -83,6 +83,19 @@ To enable silent admin project and group file exports: 1. Scroll to **Silent exports by admins**. 1. Select the **Enabled** checkbox. +## Allow contribution mapping to administrators + +> - Introduced in GitLab 17.5 [with flag](../../administration/feature_flags.md) named `importer_user_mapping`. Disabled by default. + +Allow mapping of imported user contributions to administrators. + +To allow mapping of imported user contributions to administrators: + +1. On the left sidebar, at the bottom, select **Admin**. +1. Select **Settings > General**, then expand **Import and export settings**. +1. Scroll to **Allow contribution mapping to administrators**. +1. Select the **Enabled** checkbox. + ## Max export size > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86124) in GitLab 15.0. diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index ae3ce992039cb56e130a392afcd3fa07ae70efab..3b993f9c786434ebe3da03dc2abb0889c1fc6d71 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -241,6 +241,9 @@ starting. Reassigning contributions and membership to an incorrect user poses a security threat, because the user becomes a member of your group. They can, therefore, view information they should not be able to see. +Reassigning contributions to users with administrator access is disabled by default, but you can +[enable](../../../administration/settings/import_and_export_settings.md#allow-contribution-mapping-to-administrators) it. + #### Request reassignment in UI Prerequisites: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a3414d09e633def40fdcade18ea9ac95dc7aa764..350ef0c80a439bb1831e66a4c720889dd92fcc4f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4004,6 +4004,9 @@ msgstr "" msgid "AdminSettings|All new projects can use instance runners by default." msgstr "" +msgid "AdminSettings|Allow contribution mapping to administrators %{allow_contribution_mapping_to_admins_link_start}%{icon}%{allow_contribution_mapping_to_admins_link_end}" +msgstr "" + msgid "AdminSettings|Allow migrating GitLab groups and projects by direct transfer" msgstr "" @@ -28264,9 +28267,6 @@ msgstr "" msgid "Import|No import details" msgstr "" -msgid "Import|Only active regular, auditor, or administrator users can be assigned" -msgstr "" - msgid "Import|Partially completed" msgstr "" @@ -59627,6 +59627,12 @@ msgstr "" msgid "UserMapping|Use a CSV file to reassign contributions from placeholder users to existing group members. This can be done in a few steps. %{linkStart}Learn more about matching users by CSV%{linkEnd}." msgstr "" +msgid "UserMapping|You can assign only active users with regular or auditor access. To assign users with administrator access, ask your GitLab administrator to enable the \"Allow contribution mapping to admins\" setting." +msgstr "" + +msgid "UserMapping|You can assign users with regular, auditor, or administrator access only." +msgstr "" + msgid "UserMapping|You have approved the reassignment of contributions from %{strong_open}%{source_user_name} (@%{source_username})%{strong_close} on %{strong_open}%{source_hostname}%{strong_close} to yourself on the group %{strong_open}%{destination_group}%{strong_close}. Reassignment processing is in progress." msgstr "" diff --git a/spec/services/import/source_users/reassign_service_spec.rb b/spec/services/import/source_users/reassign_service_spec.rb index cf00ed011ff0da40e8b518a9126ce289726c2225..8eee628d3da2f0700ad614c1b59cb25b87b816b7 100644 --- a/spec/services/import/source_users/reassign_service_spec.rb +++ b/spec/services/import/source_users/reassign_service_spec.rb @@ -56,21 +56,59 @@ let(:assignee_user) { nil } it_behaves_like 'an error response', 'invalid assignee', - error: 'Only active regular, auditor, or administrator users can be assigned' + error: s_('UserMapping|You can assign only active users with regular or auditor access. ' \ + 'To assign users with administrator access, ask your GitLab administrator to ' \ + 'enable the "Allow contribution mapping to admins" setting.') end context 'when assignee user is not a human' do let(:assignee_user) { create(:user, :bot) } it_behaves_like 'an error response', 'invalid assignee', - error: 'Only active regular, auditor, or administrator users can be assigned' + error: s_('UserMapping|You can assign only active users with regular or auditor access. ' \ + 'To assign users with administrator access, ask your GitLab administrator to ' \ + 'enable the "Allow contribution mapping to admins" setting.') end context 'when assignee user is not active' do let(:assignee_user) { create(:user, :deactivated) } it_behaves_like 'an error response', 'invalid assignee', - error: 'Only active regular, auditor, or administrator users can be assigned' + error: s_('UserMapping|You can assign only active users with regular or auditor access. ' \ + 'To assign users with administrator access, ask your GitLab administrator to ' \ + 'enable the "Allow contribution mapping to admins" setting.') + end + + context 'when assignee user is an admin' do + let(:assignee_user) { create(:user, :admin) } + + it_behaves_like 'an error response', 'invalid assignee', + error: s_('UserMapping|You can assign only active users with regular or auditor access. ' \ + 'To assign users with administrator access, ask your GitLab administrator to ' \ + 'enable the "Allow contribution mapping to admins" setting.') + end + + context 'when allow_contribution_mapping_to_admins setting is enabled' do + before do + stub_application_setting(allow_contribution_mapping_to_admins: true) + end + + context 'and the assignee user is invalid' do + let(:assignee_user) { create(:user, :deactivated) } + + it_behaves_like 'an error response', 'invalid assignee', + error: s_('UserMapping|You can assign users with regular, auditor, or administrator access only.') + end + + context 'and the assignee user is an admin' do + let(:assignee_user) { create(:user, :admin) } + + it 'assigns the user' do + expect(Notify).to receive_message_chain(:import_source_user_reassign, :deliver_now) + + expect(service.execute).to be_success + end + end end context 'when an error occurs' do