diff --git a/ee/spec/lib/gitlab/import_export/members_mapper_spec.rb b/ee/spec/lib/gitlab/import_export/members_mapper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..281410fe00267b9468bcabb273270ebffed8b4f3 --- /dev/null +++ b/ee/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::ImportExport::MembersMapper, feature_category: :importers do + let_it_be(:current_user) { create(:admin) } + let_it_be(:mapping_user) { create(:user) } + + let(:exported_user_id) { 99 } + let(:exported_members) do + [{ + "id" => 2, + "access_level" => 40, + "source_id" => 14, + "source_type" => source_type, + "notification_level" => 3, + "created_at" => "2016-03-11T10:21:44.822Z", + "updated_at" => "2016-03-11T10:21:44.822Z", + "created_by_id" => 1, + "invite_email" => nil, + "invite_token" => nil, + "invite_accepted_at" => nil, + "user" => + { + "id" => exported_user_id, + "public_email" => mapping_user.email, + "username" => 'test' + }, + "user_id" => 19 + }] + end + + let(:members_mapper) do + described_class.new(exported_members: exported_members, user: current_user, importable: importable) + end + + describe 'membership_lock enabled' do + let_it_be(:group) { create(:group, membership_lock: true) } + let_it_be(:project) { create(:project, namespace: group) } + + context 'when importable is Project' do + let(:importable) { project } + let(:source_type) { 'Project' } + + it 'maps to the existing user' do + expect(members_mapper.map[exported_user_id]).to eq(mapping_user.id) + end + + it 'defaults to importer member if it does not exist' do + expect(members_mapper.map[-1]).to eq(current_user.id) + end + + it 'does not create any member' do + members_mapper.map + + expect(importable.reload.members.count).to eq(0) + end + end + + context 'when importable is Group' do + let(:importable) { group } + let(:source_type) { 'Namespace' } + + context 'when membership_lock is enabled' do + before do + importable.membership_lock = true + end + + it 'maps to the existing user' do + expect(members_mapper.map[exported_user_id]).to eq(mapping_user.id) + end + + it 'defaults to importer member if it does not exist' do + expect(members_mapper.map[-1]).to eq(current_user.id) + end + + it 'creates a membership for the importer user and the mapped user' do + members_mapper.map + + expect(importable.reload.members.count).to eq(2) + end + end + end + end +end diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb index 97646ef1112f7cbc81f2811147b63652e77e9734..c985202077f10863eae07d8e8379a07515df9e05 100644 --- a/lib/gitlab/import_export/members_mapper.rb +++ b/lib/gitlab/import_export/members_mapper.rb @@ -2,11 +2,29 @@ module Gitlab module ImportExport + # Maps exported project/group members to existing users during import. + # + # This class is responsible for: + # - Mapping users from exported data to existing users + # - Creating memberships for all successfully mapped users + # - Creating a membership for the importing user when `default_member` is true + # - In case membership_lock is enabled for the project's group, no member is created + # but user mapping is still applied + # + # User mapping behavior: + # - Only applied when the importing user is an admin + # - Users are mapped by email address (source instance public email must match user primary email) + # - Disabled for Custom Project template imports (even for admin users) + # + # @param exported_members [Array] Array of exported member data + # @param user [User] The user performing the import + # @param importable [Project, Group] The project or group being imported into + # @param default_member [Boolean] Whether to create membership for the importing user class MembersMapper def initialize(exported_members:, user:, importable:, default_member: true) - @exported_members = user.admin? ? exported_members : [] @user = user @importable = importable + @exported_members = determine_exported_members(exported_members) # This needs to run first, as second call would be from #map # which means Project/Group members already exist. @@ -37,6 +55,17 @@ def include?(old_user_id) private + def determine_exported_members(exported_members) + return [] unless @user.admin? + return [] if custom_project_template_import? + + exported_members + end + + def custom_project_template_import? + @importable.try(:import_type) == 'gitlab_custom_project_template' + end + def missing_keys_tracking_hash Hash.new do |_, key| default_user_id @@ -44,6 +73,8 @@ def missing_keys_tracking_hash end def ensure_default_member! + return if @importable.is_a?(::Project) && @importable.membership_locked? + return if user_already_member? @importable.members.destroy_all # rubocop: disable Cop/DestroyAll @@ -100,6 +131,7 @@ def get_email(member_data) end def add_team_member(member, existing_user_id = nil) + return true if @importable.is_a?(::Project) && @importable.membership_locked? return true if existing_user_id && @importable.members.exists?(user_id: existing_user_id) member_hash = member_hash(member) diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 57c6d9d5e52ac887b2f7d801b2f619c2ec6db807..3388e5ddb74bfc41fd8a6c5d4ef32cfae224fadd 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ImportExport::MembersMapper do +RSpec.describe Gitlab::ImportExport::MembersMapper, feature_category: :importers do describe 'map members' do shared_examples 'imports exported members' do let(:user) { create(:admin) } @@ -318,6 +318,20 @@ expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}") end end + + context 'when import type is gitlab_custom_project_template' do + before do + importable.import_type = 'gitlab_custom_project_template' + end + + it 'does not map a member' do + expect(members_mapper.map[exported_user_id]).to eq(user.id) + end + + it 'defaults to importer member if it does not exist' do + expect(members_mapper.map[-1]).to eq(user.id) + end + end end end