From 4c480be39b0862f41043e94a6dc5b943b6f8e8f0 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 12 Aug 2016 12:04:33 +0200 Subject: [PATCH 1/2] Prevent claiming associated model IDs via import and added relevant specs --- lib/gitlab/import_export/attribute_cleaner.rb | 13 ++ .../import_export/project_tree_restorer.rb | 5 +- lib/gitlab/import_export/relation_factory.rb | 12 +- .../import_export/attribute_cleaner_spec.rb | 34 +++++ .../import_export/relation_factory_spec.rb | 125 ++++++++++++++++++ 5 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 lib/gitlab/import_export/attribute_cleaner.rb create mode 100644 spec/lib/gitlab/import_export/attribute_cleaner_spec.rb create mode 100644 spec/lib/gitlab/import_export/relation_factory_spec.rb diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb new file mode 100644 index 00000000000..c12b898a665 --- /dev/null +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -0,0 +1,13 @@ +module Gitlab + module ImportExport + class AttributeCleaner + IGNORED_REFERENCES = Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES + Gitlab::ImportExport::RelationFactory::USER_REFERENCES + + def self.clean!(relation_hash:) + relation_hash.select! do |key, _value| + IGNORED_REFERENCES.include?(key) || !key.end_with?('_id') + end + end + end + end +end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index c7b3551b84c..35adcd8f0da 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -104,9 +104,10 @@ module Gitlab def create_relation(relation, relation_hash_list) relation_array = [relation_hash_list].flatten.map do |relation_hash| Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym, - relation_hash: relation_hash.merge('project_id' => restored_project.id), + relation_hash: relation_hash, members_mapper: members_mapper, - user: @user) + user: @user, + project_id: restored_project.id) end relation_hash_list.is_a?(Array) ? relation_array : relation_array.first diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 354ccd64696..9300f789e1b 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -13,6 +13,8 @@ module Gitlab USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze + PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze + BUILD_MODELS = %w[Ci::Build commit_status].freeze IMPORTED_OBJECT_MAX_RETRIES = 5.freeze @@ -25,9 +27,9 @@ module Gitlab new(*args).create end - def initialize(relation_sym:, relation_hash:, members_mapper:, user:) + def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:) @relation_name = OVERRIDES[relation_sym] || relation_sym - @relation_hash = relation_hash.except('id', 'noteable_id') + @relation_hash = relation_hash.except('id', 'noteable_id').merge('project_id' => project_id) @members_mapper = members_mapper @user = user @imported_object_retries = 0 @@ -153,7 +155,11 @@ module Gitlab end def parsed_relation_hash - @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + @parsed_relation_hash ||= begin + Gitlab::ImportExport::AttributeCleaner.clean!(relation_hash: @relation_hash) + + @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + end end def set_st_diffs diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb new file mode 100644 index 00000000000..b8e7932eb4a --- /dev/null +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AttributeCleaner, lib: true do + let(:unsafe_hash) do + { + 'service_id' => 99, + 'moved_to_id' => 99, + 'namespace_id' => 99, + 'ci_id' => 99, + 'random_project_id' => 99, + 'random_id' => 99, + 'milestone_id' => 99, + 'project_id' => 99, + 'user_id' => 99, + 'random_id_in_the_middle' => 99, + 'notid' => 99 + } + end + + let(:post_safe_hash) do + { + 'project_id' => 99, + 'user_id' => 99, + 'random_id_in_the_middle' => 99, + 'notid' => 99 + } + end + + it 'removes unwanted attributes from the hash' do + described_class.clean!(relation_hash: unsafe_hash) + + expect(unsafe_hash).to eq(post_safe_hash) + end +end diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb new file mode 100644 index 00000000000..3aa492a8ab1 --- /dev/null +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::RelationFactory, lib: true do + let(:project) { create(:empty_project) } + let(:members_mapper) { double('members_mapper').as_null_object } + let(:user) { create(:user) } + let(:created_object) do + described_class.create(relation_sym: relation_sym, + relation_hash: relation_hash, + members_mapper: members_mapper, + user: user, + project_id: project.id) + end + + context 'hook object' do + let(:relation_sym) { :hooks } + let(:id) { 999 } + let(:service_id) { 99 } + let(:original_project_id) { 8 } + let(:token) { 'secret' } + + let(:relation_hash) do + { + 'id' => id, + 'url' => 'https://example.json', + 'project_id' => original_project_id, + 'created_at' => '2016-08-12T09:41:03.462Z', + 'updated_at' => '2016-08-12T09:41:03.462Z', + 'service_id' => service_id, + 'push_events' => true, + 'issues_events' => false, + 'merge_requests_events' => true, + 'tag_push_events' => false, + 'note_events' => true, + 'enable_ssl_verification' => true, + 'build_events' => false, + 'wiki_page_events' => true, + 'token' => token + } + end + + it 'does not have the original ID' do + expect(created_object.id).not_to eq(id) + end + + it 'does not have the original service_id' do + expect(created_object.service_id).not_to eq(service_id) + end + + it 'does not have the original project_id' do + expect(created_object.project_id).not_to eq(original_project_id) + end + + it 'has the new project_id' do + expect(created_object.project_id).to eq(project.id) + end + + it 'has a token' do + expect(created_object.token).to eq(token) + end + + context 'original service exists' do + let(:service_id) { Service.create(project: project).id } + + it 'does not have the original service_id' do + expect(created_object.service_id).not_to eq(service_id) + end + end + end + + # Mocks an ActiveRecordish object with the dodgy columns + class FooModel + include ActiveModel::Model + + def initialize(params) + params.each { |key, value| send("#{key}=", value) } + end + + def values + instance_variables.map { |ivar| instance_variable_get(ivar) } + end + end + + # `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already + # re-assigned by described_class. + context 'Potentially hazardous foreign keys' do + let(:relation_sym) { :hazardous_foo_model } + let(:relation_hash) do + { + 'service_id' => 99, + 'moved_to_id' => 99, + 'namespace_id' => 99, + 'ci_id' => 99, + 'random_project_id' => 99, + 'random_id' => 99, + 'milestone_id' => 99, + 'project_id' => 99, + 'user_id' => 99, + } + end + + class HazardousFooModel < FooModel + attr_accessor :service_id, :moved_to_id, :namespace_id, :ci_id, :random_project_id, :random_id, :milestone_id, :project_id + end + + it 'does not preserve any foreign key IDs' do + expect(created_object.values).not_to include(99) + end + end + + context 'Project references' do + let(:relation_sym) { :project_foo_model } + let(:relation_hash) do + Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES.map { |ref| { ref => 99 } }.inject(:merge) + end + + class ProjectFooModel < FooModel + attr_accessor(*Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES) + end + + it 'does not preserve any project foreign key IDs' do + expect(created_object.values).not_to include(99) + end + end +end From 9e0b7c630f2fc64805062d0c7e02fd6092631071 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 27 Sep 2016 16:12:08 +0200 Subject: [PATCH 2/2] updated attribute cleaner to use allowed keyword and reject attributes --- lib/gitlab/import_export/attribute_cleaner.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb index c12b898a665..b9e4042220a 100644 --- a/lib/gitlab/import_export/attribute_cleaner.rb +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -1,11 +1,11 @@ module Gitlab module ImportExport class AttributeCleaner - IGNORED_REFERENCES = Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES + Gitlab::ImportExport::RelationFactory::USER_REFERENCES + ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES def self.clean!(relation_hash:) - relation_hash.select! do |key, _value| - IGNORED_REFERENCES.include?(key) || !key.end_with?('_id') + relation_hash.reject! do |key, _value| + key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key) end end end