diff options
26 files changed, 390 insertions, 74 deletions
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index df5119ec64e..d139a75408e 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -8.7.0 +8.7.1 diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index f5d35379e10..60a68cec3c3 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -127,4 +127,8 @@ module UploadsActions def model strong_memoize(:model) { find_model } end + + def workhorse_authorize_request? + action_name == 'authorize' + end end diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb index f8e32451b02..af2b2cbd1fd 100644 --- a/app/controllers/groups/runners_controller.rb +++ b/app/controllers/groups/runners_controller.rb @@ -3,7 +3,7 @@ class Groups::RunnersController < Groups::ApplicationController # Proper policies should be implemented per # https://gitlab.com/gitlab-org/gitlab-ce/issues/45894 - before_action :authorize_admin_pipeline! + before_action :authorize_admin_group! before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show] @@ -50,10 +50,6 @@ class Groups::RunnersController < Groups::ApplicationController @runner ||= @group.runners.find(params[:id]) end - def authorize_admin_pipeline! - return render_404 unless can?(current_user, :admin_pipeline, group) - end - def runner_params params.require(:runner).permit(Ci::Runner::FORM_EDITABLE) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index f4d381244d9..c9a1f28f87e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -12,6 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo skip_before_action :merge_request, only: [:index, :bulk_update] before_action :whitelist_query_limiting, only: [:assign_related_issues, :update] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authorize_test_reports!, only: [:test_reports] before_action :set_issuables_index, only: [:index] before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] @@ -336,4 +337,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo render json: { status_reason: 'Unknown error' }, status: :internal_server_error end end + + def authorize_test_reports! + # MergeRequest#actual_head_pipeline is the pipeline accessed in MergeRequest#compare_reports. + return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline) + end end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 94bd18f70d4..2adfeab182e 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -2,6 +2,7 @@ class UploadsController < ApplicationController include UploadsActions + include WorkhorseRequest UnknownUploadModelError = Class.new(StandardError) @@ -21,7 +22,8 @@ class UploadsController < ApplicationController before_action :upload_mount_satisfied? before_action :find_model before_action :authorize_access!, only: [:show] - before_action :authorize_create_access!, only: [:create] + before_action :authorize_create_access!, only: [:create, :authorize] + before_action :verify_workhorse_api!, only: [:authorize] def uploader_class PersonalFileUploader @@ -72,7 +74,7 @@ class UploadsController < ApplicationController end def render_unauthorized - if current_user + if current_user || workhorse_authorize_request? render_404 else authenticate_user! diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb index 1ac69601d18..3efdd0aa1d9 100644 --- a/app/uploaders/personal_file_uploader.rb +++ b/app/uploaders/personal_file_uploader.rb @@ -6,6 +6,10 @@ class PersonalFileUploader < FileUploader options.storage_path end + def self.workhorse_local_upload_path + File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH) + end + def self.base_dir(model, _store = nil) # base_dir is the path seen by the user when rendering Markdown, so # it should be the same for both local and object storage. It is diff --git a/changelogs/unreleased/security-ci-metrics-permissions.yml b/changelogs/unreleased/security-ci-metrics-permissions.yml new file mode 100644 index 00000000000..51c6493442a --- /dev/null +++ b/changelogs/unreleased/security-ci-metrics-permissions.yml @@ -0,0 +1,6 @@ +--- +title: Restrict MergeRequests#test_reports to authenticated users with read-access + on Builds +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fix-markdown-xss.yml b/changelogs/unreleased/security-fix-markdown-xss.yml new file mode 100644 index 00000000000..7ef19f13fd5 --- /dev/null +++ b/changelogs/unreleased/security-fix-markdown-xss.yml @@ -0,0 +1,5 @@ +--- +title: Make sure HTML text is always escaped when replacing label/milestone references. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-group-runners-permissions.yml b/changelogs/unreleased/security-group-runners-permissions.yml new file mode 100644 index 00000000000..6c74be30b6d --- /dev/null +++ b/changelogs/unreleased/security-group-runners-permissions.yml @@ -0,0 +1,5 @@ +--- +title: Use admin_group authorization in Groups::RunnersController +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-personal-snippets.yml b/changelogs/unreleased/security-personal-snippets.yml new file mode 100644 index 00000000000..95f61993b98 --- /dev/null +++ b/changelogs/unreleased/security-personal-snippets.yml @@ -0,0 +1,5 @@ +--- +title: Remove EXIF from users/personal snippet uploads. +merge_request: +author: +type: security diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index 920f8454ce2..096ef146e07 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -30,6 +30,10 @@ scope path: :uploads do to: 'uploads#create', constraints: { model: /personal_snippet|user/, id: /\d+/ }, as: 'upload' + + post ':model/authorize', + to: 'uploads#authorize', + constraints: { model: /personal_snippet|user/ } end # Redirect old note attachments path to new uploads path. diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md index ae5ccfb9e37..7574660d848 100644 --- a/doc/administration/raketasks/uploads/sanitize.md +++ b/doc/administration/raketasks/uploads/sanitize.md @@ -37,6 +37,8 @@ Parameter | Type | Description `stop_id` | integer | Only uploads with equal or smaller ID will be processed `dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true `sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds +`uploader` | string | Run sanitization only for uploads of the given uploader (`FileUploader`, `PersonalFileUploader`, `NamespaceFileUploader`) +`since` | date | Run sanitization only for uploads newer than given date (e.g. `2019-05-01`) If you have too many uploads, you can speed up sanitization by setting `sleep_time` to a lower value or by running multiple rake tasks in parallel, diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 0224dd8fcd1..64b0a68b7dc 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -7,6 +7,14 @@ module Banzai class AbstractReferenceFilter < ReferenceFilter include CrossProjectReference + # REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found + # reference (which we replace with placeholder during re-scaping). The + # random number helps ensure it's pretty close to unique. Since it's a + # transitory value (it never gets saved) we can initialize once, and it + # doesn't matter if it changes on a restart. + REFERENCE_PLACEHOLDER = "_reference_#{SecureRandom.hex(16)}_" + REFERENCE_PLACEHOLDER_PATTERN = %r{#{REFERENCE_PLACEHOLDER}(\d+)}.freeze + def self.object_class # Implement in child class # Example: MergeRequest @@ -371,6 +379,14 @@ module Banzai def escape_html_entities(text) CGI.escapeHTML(text.to_s) end + + def escape_with_placeholders(text, placeholder_data) + escaped = escape_html_entities(text) + + escaped.gsub(REFERENCE_PLACEHOLDER_PATTERN) do |match| + placeholder_data[$1.to_i] + end + end end end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 4892668fc22..a0789b7ca06 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -14,24 +14,24 @@ module Banzai find_labels(parent_object).find(id) end - def self.references_in(text, pattern = Label.reference_pattern) - unescape_html_entities(text).gsub(pattern) do |match| - yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~ - end - end - def references_in(text, pattern = Label.reference_pattern) - unescape_html_entities(text).gsub(pattern) do |match| + labels = {} + unescaped_html = unescape_html_entities(text).gsub(pattern) do |match| namespace, project = $~[:namespace], $~[:project] project_path = full_project_path(namespace, project) label = find_label(project_path, $~[:label_id], $~[:label_name]) if label - yield match, label.id, project, namespace, $~ + labels[label.id] = yield match, label.id, project, namespace, $~ + "#{REFERENCE_PLACEHOLDER}#{label.id}" else - escape_html_entities(match) + match end end + + return text if labels.empty? + + escape_with_placeholders(unescaped_html, labels) end def find_label(parent_ref, label_id, label_name) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 08969753d75..4c47ee4dba1 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -51,15 +51,21 @@ module Banzai # default implementation. return super(text, pattern) if pattern != Milestone.reference_pattern - unescape_html_entities(text).gsub(pattern) do |match| + milestones = {} + unescaped_html = unescape_html_entities(text).gsub(pattern) do |match| milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.id, $~[:project], $~[:namespace], $~ + milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~ + "#{REFERENCE_PLACEHOLDER}#{milestone.id}" else - escape_html_entities(match) + match end end + + return text if milestones.empty? + + escape_with_placeholders(unescaped_html, milestones) end def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name) diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb index 0354c710a3f..03a2f62cbd9 100644 --- a/lib/gitlab/markdown_cache.rb +++ b/lib/gitlab/markdown_cache.rb @@ -3,8 +3,8 @@ module Gitlab module MarkdownCache # Increment this number every time the renderer changes its output + CACHE_COMMONMARK_VERSION = 17 CACHE_COMMONMARK_VERSION_START = 10 - CACHE_COMMONMARK_VERSION = 16 BaseError = Class.new(StandardError) UnsupportedClassError = Class.new(BaseError) diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb index bb4e4ce7bbc..2f3d14ecebd 100644 --- a/lib/gitlab/sanitizers/exif.rb +++ b/lib/gitlab/sanitizers/exif.rb @@ -53,15 +53,18 @@ module Gitlab end # rubocop: disable CodeReuse/ActiveRecord - def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil) + def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil, uploader: nil, since: nil) relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?', '%.jpg', '%.jpeg', '%.tiff') + relation = relation.where(uploader: uploader) if uploader + relation = relation.where('created_at > ?', since) if since logger.info "running in dry run mode, no images will be rewritten" if dry_run find_params = { start: start_id.present? ? start_id.to_i : nil, - finish: stop_id.present? ? stop_id.to_i : Upload.last&.id + finish: stop_id.present? ? stop_id.to_i : Upload.last&.id, + batch_size: 1000 } relation.find_each(find_params) do |upload| diff --git a/lib/tasks/gitlab/uploads/sanitize.rake b/lib/tasks/gitlab/uploads/sanitize.rake index 12cf5302555..4f23a0a5d82 100644 --- a/lib/tasks/gitlab/uploads/sanitize.rake +++ b/lib/tasks/gitlab/uploads/sanitize.rake @@ -2,7 +2,7 @@ namespace :gitlab do namespace :uploads do namespace :sanitize do desc 'GitLab | Uploads | Remove EXIF from images.' - task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time] => :environment do |task, args| + task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time, :uploader, :since] => :environment do |task, args| args.with_defaults(dry_run: 'true') args.with_defaults(sleep_time: 0.3) @@ -11,7 +11,9 @@ namespace :gitlab do sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger) sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id, dry_run: args.dry_run != 'false', - sleep_time: args.sleep_time.to_f) + sleep_time: args.sleep_time.to_f, + uploader: args.uploader, + since: args.since) end end end diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb index 91f9e2c7832..14b0cf959b3 100644 --- a/spec/controllers/groups/runners_controller_spec.rb +++ b/spec/controllers/groups/runners_controller_spec.rb @@ -3,73 +3,202 @@ require 'spec_helper' describe Groups::RunnersController do - let(:user) { create(:user) } - let(:group) { create(:group) } + let(:user) { create(:user) } + let(:group) { create(:group) } let(:runner) { create(:ci_runner, :group, groups: [group]) } - - let(:params) do - { - group_id: group, - id: runner - } - end + let(:params) { { group_id: group, id: runner } } before do sign_in(user) - group.add_maintainer(user) + end + + describe '#show' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'renders show with 200 status code' do + get :show, params: { group_id: group, id: runner } + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:show) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + get :show, params: { group_id: group, id: runner } + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe '#edit' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'renders show with 200 status code' do + get :edit, params: { group_id: group, id: runner } + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:edit) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + get :edit, params: { group_id: group, id: runner } + + expect(response).to have_gitlab_http_status(404) + end + end end describe '#update' do - it 'updates the runner and ticks the queue' do - new_desc = runner.description.swapcase + context 'when user is an owner' do + before do + group.add_owner(user) + end - expect do - post :update, params: params.merge(runner: { description: new_desc } ) - end.to change { runner.ensure_runner_queue_value } + it 'updates the runner, ticks the queue, and redirects' do + new_desc = runner.description.swapcase - runner.reload + expect do + post :update, params: params.merge(runner: { description: new_desc } ) + end.to change { runner.ensure_runner_queue_value } - expect(response).to have_gitlab_http_status(302) - expect(runner.description).to eq(new_desc) + expect(response).to have_gitlab_http_status(302) + expect(runner.reload.description).to eq(new_desc) + end + end + + context 'when user is not an owner' do + before do + group.add_maintainer(user) + end + + it 'rejects the update and responds 404' do + old_desc = runner.description + + expect do + post :update, params: params.merge(runner: { description: old_desc.swapcase } ) + end.not_to change { runner.ensure_runner_queue_value } + + expect(response).to have_gitlab_http_status(404) + expect(runner.reload.description).to eq(old_desc) + end end end describe '#destroy' do - it 'destroys the runner' do - delete :destroy, params: params + context 'when user is an owner' do + before do + group.add_owner(user) + end + + it 'destroys the runner and redirects' do + delete :destroy, params: params + + expect(response).to have_gitlab_http_status(302) + expect(Ci::Runner.find_by(id: runner.id)).to be_nil + end + end + + context 'when user is not an owner' do + before do + group.add_maintainer(user) + end + + it 'responds 404 and does not destroy the runner' do + delete :destroy, params: params - expect(response).to have_gitlab_http_status(302) - expect(Ci::Runner.find_by(id: runner.id)).to be_nil + expect(response).to have_gitlab_http_status(404) + expect(Ci::Runner.find_by(id: runner.id)).to be_present + end end end describe '#resume' do - it 'marks the runner as active and ticks the queue' do - runner.update(active: false) + context 'when user is an owner' do + before do + group.add_owner(user) + end - expect do - post :resume, params: params - end.to change { runner.ensure_runner_queue_value } + it 'marks the runner as active, ticks the queue, and redirects' do + runner.update(active: false) - runner.reload + expect do + post :resume, params: params + end.to change { runner.ensure_runner_queue_value } - expect(response).to have_gitlab_http_status(302) - expect(runner.active).to eq(true) + expect(response).to have_gitlab_http_status(302) + expect(runner.reload.active).to eq(true) + end + end + + context 'when user is not an owner' do + before do + group.add_maintainer(user) + end + + it 'responds 404 and does not activate the runner' do + runner.update(active: false) + + expect do + post :resume, params: params + end.not_to change { runner.ensure_runner_queue_value } + + expect(response).to have_gitlab_http_status(404) + expect(runner.reload.active).to eq(false) + end end end describe '#pause' do - it 'marks the runner as inactive and ticks the queue' do - runner.update(active: true) + context 'when user is an owner' do + before do + group.add_owner(user) + end + + it 'marks the runner as inactive, ticks the queue, and redirects' do + runner.update(active: true) + + expect do + post :pause, params: params + end.to change { runner.ensure_runner_queue_value } + + expect(response).to have_gitlab_http_status(302) + expect(runner.reload.active).to eq(false) + end + end + + context 'when user is not an owner' do + before do + group.add_maintainer(user) + end - expect do - post :pause, params: params - end.to change { runner.ensure_runner_queue_value } + it 'responds 404 and does not update the runner or queue' do + runner.update(active: true) - runner.reload + expect do + post :pause, params: params + end.not_to change { runner.ensure_runner_queue_value } - expect(response).to have_gitlab_http_status(302) - expect(runner.active).to eq(false) + expect(response).to have_gitlab_http_status(404) + expect(runner.reload.active).to eq(true) + end end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index bdd7322290f..3555d12b41c 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -719,19 +719,63 @@ describe Projects::MergeRequestsController do end describe 'GET test_reports' do + let(:merge_request) do + create(:merge_request, + :with_diffs, + :with_merge_request_pipeline, + target_project: project, + source_project: project + ) + end + subject do - get :test_reports, - params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid - }, - format: :json + get :test_reports, params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, + format: :json end before do allow_any_instance_of(MergeRequest) - .to receive(:compare_test_reports).and_return(comparison_status) + .to receive(:compare_test_reports) + .and_return(comparison_status) + + allow_any_instance_of(MergeRequest) + .to receive(:actual_head_pipeline) + .and_return(merge_request.all_pipelines.take) + end + + describe 'permissions on a public project with private CI/CD' do + let(:project) { create :project, :repository, :public, :builds_private } + let(:comparison_status) { { status: :parsed, data: { summary: 1 } } } + + context 'while signed out' do + before do + sign_out(user) + end + + it 'responds with a 404' do + subject + + expect(response).to have_gitlab_http_status(404) + expect(response.body).to be_blank + end + end + + context 'while signed in as an unrelated user' do + before do + sign_in(create(:user)) + end + + it 'responds with a 404' do + subject + + expect(response).to have_gitlab_http_status(404) + expect(response.body).to be_blank + end + end end context 'when comparison is being processed' do diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 0876502a899..5f4a6bf8ee7 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -21,8 +21,20 @@ shared_examples 'content publicly cached' do end describe UploadsController do + include WorkhorseHelpers + let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } + describe 'POST #authorize' do + it_behaves_like 'handle uploads authorize' do + let(:uploader_class) { PersonalFileUploader } + let(:model) { create(:personal_snippet, :public) } + let(:params) do + { model: 'personal_snippet', id: model.id } + end + end + end + describe 'POST create' do let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } @@ -636,4 +648,10 @@ describe UploadsController do end end end + + def post_authorize(verified: true) + request.headers.merge!(workhorse_internal_api_request_header) if verified + + post :authorize, params: { model: 'personal_snippet', id: model.id }, format: :json + end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 108d7b43a26..799539b0f46 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -8,6 +8,11 @@ describe Banzai::Filter::LabelReferenceFilter do let(:label) { create(:label, project: project) } let(:reference) { label.to_reference } + it_behaves_like 'HTML text with references' do + let(:resource) { label } + let(:resource_text) { resource.title } + end + it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index f0a5dc8d0d7..e3d50dcf86b 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -327,6 +327,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do it_behaves_like 'cross-project / same-namespace complete reference' it_behaves_like 'cross project shorthand reference' it_behaves_like 'references with HTML entities' + it_behaves_like 'HTML text with references' do + let(:resource) { milestone } + let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" } + end end shared_context 'group milestones' do @@ -338,6 +342,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do it_behaves_like 'String-based multi-word references in quotes' it_behaves_like 'referencing a milestone in a link href' it_behaves_like 'references with HTML entities' + it_behaves_like 'HTML text with references' do + let(:resource) { milestone } + let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" } + end it 'does not support references by IID' do doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}") diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb index bd5f330c7a1..0a4bc1bbdbb 100644 --- a/spec/lib/gitlab/sanitizers/exif_spec.rb +++ b/spec/lib/gitlab/sanitizers/exif_spec.rb @@ -5,7 +5,9 @@ describe Gitlab::Sanitizers::Exif do describe '#batch_clean' do context 'with image uploads' do - let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) } + set(:upload1) { create(:upload, :with_file, :issuable_upload) } + set(:upload2) { create(:upload, :with_file, :personal_snippet_upload) } + set(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) } it 'processes all uploads if range ID is not set' do expect(sanitizer).to receive(:clean).exactly(3).times @@ -16,7 +18,19 @@ describe Gitlab::Sanitizers::Exif do it 'processes only uploads in the selected range' do expect(sanitizer).to receive(:clean).once - sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id) + sanitizer.batch_clean(start_id: upload1.id, stop_id: upload1.id) + end + + it 'processes only uploads for the selected uploader' do + expect(sanitizer).to receive(:clean).once + + sanitizer.batch_clean(uploader: 'PersonalFileUploader') + end + + it 'processes only uploads created since specified date' do + expect(sanitizer).to receive(:clean).exactly(2).times + + sanitizer.batch_clean(since: 2.days.ago) end it 'pauses if sleep_time is set' do diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 9036838e50a..d6bc1493254 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -5,6 +5,8 @@ shared_examples 'handle uploads' do let(:secret) { FileUploader.generate_secret } let(:uploader_class) { FileUploader } + it_behaves_like 'handle uploads authorize' + describe "POST #create" do context 'when a user is not authorized to upload a file' do it 'returns 404 status' do @@ -269,7 +271,9 @@ shared_examples 'handle uploads' do end end end +end +shared_examples 'handle uploads authorize' do describe "POST #authorize" do context 'when a user is not authorized to upload a file' do it 'returns 404 status' do @@ -282,7 +286,12 @@ shared_examples 'handle uploads' do context 'when a user can upload a file' do before do sign_in(user) - model.add_developer(user) + + if model.is_a?(PersonalSnippet) + model.update!(author: user) + else + model.add_developer(user) + end end context 'and the request bypassed workhorse' do diff --git a/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb new file mode 100644 index 00000000000..b1ecd4fd007 --- /dev/null +++ b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'HTML text with references' do + let(:markdown_prepend) { "<img src=\"\" onerror=alert(`bug`)>" } + + it 'preserves escaped HTML text and adds valid references' do + reference = resource.to_reference(format: :name) + + doc = reference_filter("#{markdown_prepend}#{reference}") + + expect(doc.to_html).to start_with(markdown_prepend) + expect(doc.text).to eq %(<img src="" onerror=alert(`bug`)>#{resource_text}) + end + + it 'preserves escaped HTML text if there are no valid references' do + reference = "#{resource.class.reference_prefix}invalid" + text = "#{markdown_prepend}#{reference}" + + doc = reference_filter(text) + + expect(doc.to_html).to eq text + end +end |