Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2019-08-30 01:17:37 +0300
committerRobert Speicher <rspeicher@gmail.com>2019-08-30 01:17:37 +0300
commit7698d405506bc10dfd7fb2e6e02b419dd5925725 (patch)
treeb2c188fd3bc588f15d04d8ce6137f9447f7a72c7 /spec
parentfa160c26b14d233eb2e3b861a0742766d1ac734b (diff)
parent090956259c47d839b136f9391c3f74255764da81 (diff)
Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb205
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb90
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb311
-rw-r--r--spec/controllers/projects/services_controller_spec.rb5
-rw-r--r--spec/controllers/sessions_controller_spec.rb108
-rw-r--r--spec/controllers/uploads_controller_spec.rb18
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/markdown/math_spec.rb6
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb17
-rw-r--r--spec/helpers/emails_helper_spec.rb56
-rw-r--r--spec/helpers/labels_helper_spec.rb10
-rw-r--r--spec/helpers/markup_helper_spec.rb6
-rw-r--r--spec/initializers/asset_proxy_setting_spec.rb13
-rw-r--r--spec/initializers/rest-client-hostname_override_spec.rb147
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb95
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb72
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb22
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb44
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb78
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb53
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb18
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb82
-rw-r--r--spec/models/application_setting_spec.rb65
-rw-r--r--spec/models/concerns/issuable_spec.rb26
-rw-r--r--spec/models/label_spec.rb7
-rw-r--r--spec/models/note_spec.rb1
-rw-r--r--spec/models/project_services/discord_service_spec.rb33
-rw-r--r--spec/models/user_spec.rb41
-rw-r--r--spec/policies/issue_policy_spec.rb28
-rw-r--r--spec/policies/merge_request_policy_spec.rb89
-rw-r--r--spec/requests/api/settings_spec.rb28
-rw-r--r--spec/services/application_settings/update_service_spec.rb33
-rw-r--r--spec/services/issues/close_service_spec.rb40
-rw-r--r--spec/services/projects/create_service_spec.rb68
-rw-r--r--spec/services/todo_service_spec.rb121
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/models/concern/issuable_shared_examples.rb8
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb1
46 files changed, 1917 insertions, 218 deletions
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 80f54dd258d..d0370dfaeee 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -737,19 +737,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
@@ -1070,17 +1114,39 @@ describe Projects::MergeRequestsController do
let(:status) { pipeline.detailed_status(double('user')) }
- before do
+ it 'returns a detailed head_pipeline status in json' do
get_pipeline_status
- end
- it 'return a detailed head_pipeline status in json' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
+
+ context 'with project member visibility on a public project' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ it 'returns pipeline data to project members' do
+ project.add_developer(user)
+
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['text']).to eq status.text
+ expect(json_response['label']).to eq status.label
+ expect(json_response['icon']).to eq status.icon
+ expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
+ end
+
+ it 'returns blank OK response to non-project-members' do
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+ end
end
context 'when head_pipeline does not exist' do
@@ -1088,7 +1154,7 @@ describe Projects::MergeRequestsController do
get_pipeline_status
end
- it 'return empty' do
+ it 'returns blank OK response' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 4500c412521..4db77921f24 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -212,40 +212,232 @@ describe Projects::NotesController do
describe 'POST create' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
+ let(:note_text) { 'some note' }
let(:request_params) do
{
- note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
+ note: { note: note_text, noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
namespace_id: project.namespace,
project_id: project,
merge_request_diff_head_sha: 'sha',
target_type: 'merge_request',
target_id: merge_request.id
- }
+ }.merge(extra_request_params)
+ end
+ let(:extra_request_params) { {} }
+
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::ENABLED }
+
+ def create!
+ post :create, params: request_params
end
before do
+ project.update_attribute(:visibility_level, project_visibility)
+ project.project_feature.update(merge_requests_access_level: merge_requests_access_level)
sign_in(user)
- project.add_developer(user)
end
- it "returns status 302 for html" do
- post :create, params: request_params
+ describe 'making the creation request' do
+ before do
+ create!
+ end
+
+ context 'the project is publically available' do
+ context 'for HTML' do
+ it "returns status 302" do
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'for JSON' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200 for json" do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
- expect(response).to have_gitlab_http_status(302)
+ context 'the project is a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+
+ [{}, { format: :json }].each do |extra|
+ context "format is #{extra[:format]}" do
+ let(:extra_request_params) { extra }
+
+ it "returns status 404" do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
end
- it "returns status 200 for json" do
- post :create, params: request_params.merge(format: :json)
+ context 'the user is a developer on a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
+ before do
+ project.add_developer(user)
+ end
+
+ context 'HTML requests' do
+ it "returns status 302 (redirect)" do
+ create!
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'JSON requests' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200" do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'the return_discussion param is set' do
+ let(:extra_request_params) { { format: :json, return_discussion: 'true' } }
+
+ it 'returns discussion JSON when the return_discussion param is set' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to have_key 'discussion'
+ expect(json_response.dig('discussion', 'notes', 0, 'note')).to eq(request_params[:note][:note])
+ end
+ end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+ let(:extra_request_params) { { format: :json } }
+
+ it 'includes changes in commands_changes ' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+ let(:extra_request_params) { { format: :json, target_id: issue.id, target_type: 'issue' } }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
- it 'returns discussion JSON when the return_discussion param is set' do
- post :create, params: request_params.merge(format: :json, return_discussion: 'true')
+ context 'when the internal project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::INTERNAL }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to have_key 'discussion'
- expect(json_response['discussion']['notes'][0]['note']).to eq(request_params[:note][:note])
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can add comments' do
+ expect { create! }.to change { project.notes.count }.by(1)
+ end
+ end
+
+ # Illustration of the attack vector for posting comments to discussions that should
+ # be inaccessible.
+ #
+ # This relies on posting a note to a commit that is not necessarily even in the
+ # merge request, with a value of :in_reply_to_discussion_id that points to a
+ # discussion on a merge_request that should not be accessible.
+ context 'when the request includes a :in_reply_to_discussion_id designed to fool us' do
+ let(:commit) { create(:commit, project: project) }
+
+ let(:existing_comment) do
+ create(:note_on_commit,
+ note: 'first',
+ project: project,
+ commit_id: merge_request.commit_shas.first)
+ end
+
+ let(:discussion) { existing_comment.discussion }
+
+ # see !60465 for details of the structure of this request
+ let(:request_params) do
+ { "utf8" => "✓",
+ "authenticity_token" => "1",
+ "view" => "inline",
+ "line_type" => "",
+ "merge_request_diff_head_sha" => "",
+ "in_reply_to_discussion_id" => discussion.id,
+ "note_project_id" => project.id,
+ "project_id" => project.id,
+ "namespace_id" => project.namespace,
+ "target_type" => "commit",
+ "target_id" => commit.id,
+ "note" => {
+ "noteable_type" => "",
+ "noteable_id" => "",
+ "commit_id" => "",
+ "type" => "",
+ "line_code" => "",
+ "position" => "",
+ "note" => "ThisReplyWillGoToMergeRequest"
+ } }
+ end
+
+ it 'prevents the request from adding notes to the spoofed discussion' do
+ expect { create! }.not_to change { discussion.notes.count }
+ end
+
+ it 'returns an error to the user' do
+ create!
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when the public project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
+
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ create!
+ end
+
+ it 'can add comments' do
+ expect(response).to be_redirect
+ end
+ end
end
context 'when merge_request_diff_head_sha present' do
@@ -262,7 +454,7 @@ describe Projects::NotesController do
end
it "returns status 302 for html" do
- post :create, params: request_params
+ create!
expect(response).to have_gitlab_http_status(302)
end
@@ -285,7 +477,7 @@ describe Projects::NotesController do
end
context 'when creating a commit comment from an MR fork' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
let(:forked_project) do
fork_project(project, nil, repository: true)
@@ -299,45 +491,59 @@ describe Projects::NotesController do
create(:note_on_commit, note: 'a note', project: forked_project, commit_id: merge_request.commit_shas.first)
end
- def post_create(extra_params = {})
- post :create, params: {
+ let(:note_project_id) do
+ forked_project.id
+ end
+
+ let(:request_params) do
+ {
note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace,
project_id: project,
target_type: 'merge_request',
target_id: merge_request.id,
- note_project_id: forked_project.id,
+ note_project_id: note_project_id,
in_reply_to_discussion_id: existing_comment.discussion_id
- }.merge(extra_params)
+ }
+ end
+
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+
+ before do
+ forked_project.update_attribute(:visibility_level, fork_visibility)
end
context 'when the note_project_id is not correct' do
- it 'returns a 404' do
- post_create(note_project_id: Project.maximum(:id).succ)
+ let(:note_project_id) do
+ project.id && Project.maximum(:id).succ
+ end
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has no access to the fork' do
- it 'returns a 404' do
- post_create
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has access to the fork' do
- let(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let!(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
- before do
- forked_project.add_developer(user)
-
- existing_comment
+ it 'is successful' do
+ create!
+ expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
- expect { post_create }.to change { forked_project.notes.count }.by(1)
+ expect { create! }.to change { forked_project.notes.count }.by(1)
end
end
end
@@ -346,11 +552,6 @@ describe Projects::NotesController do
let(:locked_issue) { create(:issue, :locked, project: project) }
let(:issue) {create(:issue, project: project)}
- before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
- project.project_member(user).destroy
- end
-
it 'uses target_id and ignores noteable_id' do
request_params = {
note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
@@ -368,7 +569,6 @@ describe Projects::NotesController do
context 'when the merge request discussion is locked' do
before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
merge_request.update_attribute(:discussion_locked, true)
end
@@ -382,6 +582,10 @@ describe Projects::NotesController do
end
context 'when a user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
it 'returns 302 status for html' do
post :create, params: request_params
@@ -400,10 +604,6 @@ describe Projects::NotesController do
end
context 'when a user is not a team member' do
- before do
- project.project_member(user).destroy
- end
-
it 'returns 404 status' do
post :create, params: request_params
@@ -415,37 +615,6 @@ describe Projects::NotesController do
end
end
end
-
- context 'when creating a note with quick actions' do
- context 'with commands that return changes' do
- let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
-
- it 'includes changes in commands_changes ' do
- post :create, params: request_params.merge(note: { note: note_text }, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
-
- context 'with commands that do not return changes' do
- let(:issue) { create(:issue, project: project) }
- let(:other_project) { create(:project) }
- let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
-
- before do
- other_project.add_developer(user)
- end
-
- it 'does not include changes in commands_changes' do
- post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
- end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 563b61962cf..180d997a8e8 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -11,6 +11,7 @@ describe Projects::ServicesController do
before do
sign_in(user)
project.add_maintainer(user)
+ allow(Gitlab::UrlBlocker).to receive(:validate!).and_return([URI.parse('http://example.com'), nil])
end
describe '#test' do
@@ -56,6 +57,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
@@ -66,6 +69,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 9c4ddce5409..68b7bf61231 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -100,16 +100,8 @@ describe SessionsController do
end
end
- context 'when reCAPTCHA is enabled' do
- let(:user) { create(:user) }
- let(:user_params) { { login: user.username, password: user.password } }
-
- before do
- stub_application_setting(recaptcha_enabled: true)
- request.headers[described_class::CAPTCHA_HEADER] = 1
- end
-
- it 'displays an error when the reCAPTCHA is not solved' do
+ context 'with reCAPTCHA' do
+ def unsuccesful_login(user_params, sesion_params: {})
# Without this, `verify_recaptcha` arbitrarily returns true in test env
Recaptcha.configuration.skip_verify_env.delete('test')
counter = double(:counter)
@@ -119,14 +111,10 @@ describe SessionsController do
.with(:failed_login_captcha_total, anything)
.and_return(counter)
- post(:create, params: { user: user_params })
-
- expect(response).to render_template(:new)
- expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
- expect(subject.current_user).to be_nil
+ post(:create, params: { user: user_params }, session: sesion_params)
end
- it 'successfully logs in a user when reCAPTCHA is solved' do
+ def succesful_login(user_params, sesion_params: {})
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
Recaptcha.configuration.skip_verify_env << 'test'
counter = double(:counter)
@@ -137,9 +125,80 @@ describe SessionsController do
.and_return(counter)
expect(Gitlab::Metrics).to receive(:counter).and_call_original
- post(:create, params: { user: user_params })
+ post(:create, params: { user: user_params }, session: sesion_params)
+ end
- expect(subject.current_user).to eq user
+ context 'when reCAPTCHA is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ request.headers[described_class::CAPTCHA_HEADER] = 1
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ # Without this, `verify_recaptcha` arbitrarily returns true in test env
+
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when reCAPTCHA login protection is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(login_recaptcha_protection_enabled: true)
+ end
+
+ context 'when user tried to login 5 times' do
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when there are more than 5 anonymous session with the same IP' do
+ before do
+ allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :stored_sessions).and_return(6)
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_entries)
+
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
end
end
end
@@ -348,4 +407,17 @@ describe SessionsController do
expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
end
+
+ context 'when login fails' do
+ before do
+ set_devise_mapping(context: @request)
+ @request.env["warden.options"] = { action: 'unauthenticated' }
+ end
+
+ it 'does increment failed login counts for session' do
+ get(:new, params: { user: { login: 'failed' } })
+
+ expect(session[:failed_login_attempts]).to eq(1)
+ end
+ end
end
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/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index ddd87404003..eb59de2e132 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -263,6 +263,7 @@ describe 'Admin updates settings' do
page.within('.as-spam') do
check 'Enable reCAPTCHA'
+ check 'Enable reCAPTCHA for login'
fill_in 'reCAPTCHA Site Key', with: 'key'
fill_in 'reCAPTCHA Private Key', with: 'key'
fill_in 'IPs per user', with: 15
@@ -271,6 +272,7 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
expect(current_settings.recaptcha_enabled).to be true
+ expect(current_settings.login_recaptcha_protection_enabled).to be true
expect(current_settings.unique_ips_limit_per_user).to eq(15)
end
end
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 68d99b4241a..76eef66c517 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -34,7 +34,9 @@ describe 'Math rendering', :js do
visit project_issue_path(project, issue)
- expect(page).to have_selector('.katex-error', text: "\href{javascript:alert('xss');}{xss}")
- expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ page.within '.description > .md' do
+ expect(page).to have_selector('.katex-error')
+ expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ end
end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 1ab7742b36e..0905ab0aef8 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -49,6 +49,23 @@ describe 'User edit profile' do
end
end
+ describe 'when I change my email' do
+ before do
+ user.send_reset_password_instructions
+ end
+
+ it 'clears the reset password token' do
+ expect(user.reset_password_token?).to be true
+
+ fill_in 'user_email', with: 'new-email@example.com'
+ submit_settings
+
+ user.reload
+ expect(user.confirmation_token).not_to be_nil
+ expect(user.reset_password_token?).to be false
+ end
+ end
+
context 'user avatar' do
before do
attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index d25f0c6de4a..a14ae2cde4b 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -6,30 +6,62 @@ describe EmailsHelper do
let(:merge_request) { create(:merge_request) }
let(:merge_request_presenter) { merge_request.present }
- context "and format is text" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user can read merge request' do
+ let(:user) { create(:user) }
+
+ before do
+ merge_request.project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, merge_request.project)
+ end
+
+ context "and format is text" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
- end
- context "and format is HTML" do
- it "returns HTML" do
- expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ context "and format is HTML" do
+ it "returns HTML" do
+ expect(helper.closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ end
+ end
+
+ context "and format is unknown" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
end
- context "and format is unknown" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user cannot read merge request' do
+ it "does not have link to merge request" do
+ expect(helper.closure_reason_text(merge_request)).to be_empty
end
end
end
context 'when given a String' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
- it "returns plain text" do
- expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ context 'when user can read commits' do
+ before do
+ project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, project)
+ end
+
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ end
+ end
+
+ context 'when user cannot read commits' do
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to be_empty
+ end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4f1cab38f34..1d57aaa0da5 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -278,4 +278,14 @@ describe LabelsHelper do
it { is_expected.to eq('Subscribe at group level') }
end
end
+
+ describe '#label_tooltip_title' do
+ let(:html) { '<img src="example.png">This is an image</img>' }
+ let(:label_with_html_content) { create(:label, title: 'test', description: html) }
+
+ it 'removes HTML' do
+ tooltip = label_tooltip_title(label_with_html_content)
+ expect(tooltip).to eq('This is an image')
+ end
+ end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index f6e1720e113..1757ec8fa4d 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -65,6 +65,9 @@ describe MarkupHelper do
describe 'inside a group' do
before do
+ # Ensure the generated reference links aren't redacted
+ group.add_maintainer(user)
+
helper.instance_variable_set(:@group, group)
helper.instance_variable_set(:@project, nil)
end
@@ -78,6 +81,9 @@ describe MarkupHelper do
let(:project_in_group) { create(:project, group: group) }
before do
+ # Ensure the generated reference links aren't redacted
+ project_in_group.add_maintainer(user)
+
helper.instance_variable_set(:@group, group)
helper.instance_variable_set(:@project, project_in_group)
end
diff --git a/spec/initializers/asset_proxy_setting_spec.rb b/spec/initializers/asset_proxy_setting_spec.rb
new file mode 100644
index 00000000000..42e4d4aa594
--- /dev/null
+++ b/spec/initializers/asset_proxy_setting_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'Asset proxy settings initialization' do
+ describe '#asset_proxy' do
+ it 'defaults to disabled' do
+ expect(Banzai::Filter::AssetProxyFilter).to receive(:initialize_settings)
+
+ require_relative '../../config/initializers/asset_proxy_settings'
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_falsey
+ end
+ end
+end
diff --git a/spec/initializers/rest-client-hostname_override_spec.rb b/spec/initializers/rest-client-hostname_override_spec.rb
new file mode 100644
index 00000000000..3707e001d41
--- /dev/null
+++ b/spec/initializers/rest-client-hostname_override_spec.rb
@@ -0,0 +1,147 @@
+require 'spec_helper'
+
+describe 'rest-client dns rebinding protection' do
+ include StubRequests
+
+ context 'when local requests are not allowed' do
+ it 'allows an external request with http' do
+ request_stub = stub_full_request('http://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('http://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request with https' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request that resolves to a local address' do
+ stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request that resolves to a localhost address' do
+ stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to localhost are not allowed")
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('http://172.16.0.0') }
+ .to raise_error(ArgumentError,
+ "URL 'http://172.16.0.0' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('http://127.0.0.1') }
+ .to raise_error(ArgumentError,
+ "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when port different from URL scheme is used' do
+ it 'allows the request' do
+ request_stub = stub_full_request('https://example.com:8080', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com:8080/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('https://172.16.0.0:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://172.16.0.0:8080' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('https://127.0.0.1:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://127.0.0.1:8080' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when DNS rebinding protection is disabled' do
+ before do
+ stub_application_setting(dns_rebinding_protection_enabled: false)
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when http(s) proxy environment variable is set' do
+ before do
+ stub_env('https_proxy' => 'https://my.proxy')
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'allows an external request' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a local address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a localhost address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a local address request' do
+ request_stub = stub_request(:get, 'http://172.16.0.0')
+
+ RestClient.get('http://172.16.0.0')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a localhost address request' do
+ request_stub = stub_request(:get, 'http://127.0.0.1')
+
+ RestClient.get('http://127.0.0.1')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
new file mode 100644
index 00000000000..b7f45421b2a
--- /dev/null
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AssetProxyFilter do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'does not replace if disabled' do
+ stub_asset_proxy_setting(enabled: false)
+
+ context = described_class.transform_context({})
+ src = 'http://example.com/test.png'
+ doc = filter(image(src), context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ context 'during initialization' do
+ after do
+ Gitlab.config.asset_proxy['enabled'] = false
+ end
+
+ it '#initialize_settings' do
+ stub_application_setting(asset_proxy_enabled: true)
+ stub_application_setting(asset_proxy_secret_key: 'shared-secret')
+ stub_application_setting(asset_proxy_url: 'https://assets.example.com')
+ stub_application_setting(asset_proxy_whitelist: %w(gitlab.com *.mydomain.com))
+
+ described_class.initialize_settings
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_truthy
+ expect(Gitlab.config.asset_proxy.secret_key).to eq 'shared-secret'
+ expect(Gitlab.config.asset_proxy.url).to eq 'https://assets.example.com'
+ expect(Gitlab.config.asset_proxy.whitelist).to eq %w(gitlab.com *.mydomain.com)
+ expect(Gitlab.config.asset_proxy.domain_regexp).to eq /^(gitlab\.com|.*?\.mydomain\.com)$/i
+ end
+ end
+
+ context 'when properly configured' do
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: described_class.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ @context = described_class.transform_context({})
+ end
+
+ it 'replaces img src' do
+ src = 'http://example.com/test.png'
+ new_src = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq new_src
+ expect(doc.at_css('img')['data-canonical-src']).to eq src
+ end
+
+ it 'skips internal images' do
+ src = "#{Gitlab.config.gitlab.url}/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skip relative urls' do
+ src = "/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain' do
+ src = "http://gitlab.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain and ignores url in query string' do
+ src = "http://gitlab.com/test.png?url=http://example.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips wildcarded domain' do
+ src = "http://images.mydomain.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 59fea5766ee..4b2500b31f7 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -156,6 +156,18 @@ describe Banzai::Filter::ExternalLinkFilter do
expect(doc_email.to_html).to include('http://xn--example-6p25f.com/</a>')
end
end
+
+ context 'autolinked image' do
+ let(:html) { %q(<a href="https://assets.example.com/6d8b/634c" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"><img src="http://exa%F0%9F%98%84mple.com/test.png" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"></a>) }
+ let(:doc) { filter(html) }
+
+ it_behaves_like 'an external link with rel attribute'
+
+ it 'adds a toolip with punycode' do
+ expect(doc.to_html).to include('class="has-tooltip"')
+ expect(doc.to_html).to include('title="http://xn--example-6p25f.com/test.png"')
+ end
+ end
end
context 'for links that look malicious' do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 7b0cb675551..011e3a1e2da 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -28,4 +28,11 @@ describe Banzai::Filter::ImageLinkFilter do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end
+
+ it 'keep the data-canonical-src' do
+ doc = filter(%q(<img src="http://assets.example.com/6cd/4d7" data-canonical-src="http://example.com/test.png" />))
+
+ expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
+ expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src']
+ 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 213a5459118..35e99d2586e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -10,6 +10,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 3f021adc756..ab0c2c383c5 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -329,6 +329,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
@@ -340,6 +344,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/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 69f9c1ae829..927d226c400 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -26,10 +26,18 @@ describe Banzai::Filter::ProjectReferenceFilter do
expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp))
end
- it 'fails fast for long invalid string' do
- expect do
- Timeout.timeout(5.seconds) { reference_filter("A" * 50000).to_html }
- end.not_to raise_error
+ context 'when invalid reference strings are very long' do
+ shared_examples_for 'fails fast' do |ref_string|
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172824
+ expect do
+ Timeout.timeout(3.seconds) { reference_filter(ref_string).to_html }
+ end.not_to raise_error
+ end
+ end
+
+ it_behaves_like 'fails fast', 'A' * 50000
+ it_behaves_like 'fails fast', '/a' * 50000
end
it 'allows references with text after the > character' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index ecb83b6cb66..789530fbc56 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -7,6 +7,7 @@ describe Banzai::Filter::RelativeLinkFilter do
contexts.reverse_merge!({
commit: commit,
project: project,
+ current_user: user,
group: group,
project_wiki: project_wiki,
ref: ref,
@@ -33,7 +34,8 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<div>#{element}</div>)
end
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:user) { create(:user) }
let(:group) { nil }
let(:project_path) { project.full_path }
let(:ref) { 'markdown' }
@@ -75,6 +77,11 @@ describe Banzai::Filter::RelativeLinkFilter do
include_examples :preserve_unchanged
end
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+ include_examples :preserve_unchanged
+ end
+
it 'does not raise an exception on invalid URIs' do
act = link("://foo")
expect { filter(act) }.not_to raise_error
@@ -282,6 +289,37 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+
+ it 'does not rebuild relative URL for a link' do
+ doc = filter(link(upload_path))
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+
+ doc = filter(nested(link(upload_path)))
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ it 'does not rebuild relative URL for an image' do
+ doc = filter(image(upload_path))
+ expect(doc.at_css('img')['src']).to eq(upload_path)
+
+ doc = filter(nested(image(upload_path)))
+ expect(doc.at_css('img')['src']).to eq(upload_path)
+ end
+
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'does not rewrite the link' do
+ doc = filter(link(upload_path))
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+ end
+ end
+
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
@@ -331,11 +369,41 @@ describe Banzai::Filter::RelativeLinkFilter do
end
context 'to a group upload' do
- let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
+ let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
+ let(:upload_link) { link(upload_path) }
let(:group) { create(:group) }
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+ context 'without group read access' do
+ let(:group) { create(:group, :private) }
+
+ it 'does not rewrite the link' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ it 'does not rewrite the link for subgroup' do
+ group.update!(parent: create(:group))
+
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'does not rewrite the link' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(upload_path)
+ end
+ end
+ end
+
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 483e806624c..cd932f502f3 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -49,4 +49,26 @@ describe Banzai::Filter::VideoLinkFilter do
expect(element['src']).to eq '/path/my_image.jpg'
end
end
+
+ context 'when asset proxy is enabled' do
+ it 'uses the correct src' do
+ stub_asset_proxy_setting(enabled: true)
+
+ proxy_src = 'https://assets.example.com/6d8b63'
+ canonical_src = 'http://example.com/test.mp4'
+ image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}" />)
+ container = filter(image, asset_proxy_enabled: true).children.first
+
+ expect(container['class']).to eq 'video-container'
+
+ video, paragraph = container.children
+
+ expect(video['src']).to eq proxy_src
+ expect(video['data-canonical-src']).to eq canonical_src
+
+ link = paragraph.children.first
+
+ expect(link['href']).to eq proxy_src
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 0a3e0962452..3a9ecd2fb81 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -142,4 +142,48 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include(Gitlab::Routing.url_helpers.milestone_path(milestone))
end
end
+
+ describe 'asset proxy' do
+ let(:project) { create(:project, :public) }
+ let(:image) { '![proxy](http://example.com/test.png)' }
+ let(:proxy) { 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67' }
+ let(:version) { Gitlab::CurrentSettings.current_application_settings.local_markdown_version }
+
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: Banzai::Filter::AssetProxyFilter.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ end
+
+ it 'replaces a lazy loaded img src' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('img').first
+
+ expect(result['data-src']).to eq(proxy)
+ end
+
+ it 'autolinks images to the proxy' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://example.com/test.png')
+ end
+
+ it 'properly adds tooltips to link for IDN images' do
+ image = '![proxy](http://exa😄mple.com/test.png)'
+ proxy = 'https://assets.example.com/6d8b634c412a23c6bfe1b2963f174febf5635ddd/687474703a2f2f6578612546302539462539382538346d706c652e636f6d2f746573742e706e67'
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://exa%F0%9F%98%84mple.com/test.png')
+ expect(result['title']).to eq 'http://xn--example-6p25f.com/test.png'
+ end
+ end
end
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
new file mode 100644
index 00000000000..628aae81ada
--- /dev/null
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
+ let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
+ let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
+
+ subject { new_anonymous_session }
+
+ def new_anonymous_session(session_id = default_session_id)
+ described_class.new('127.0.0.1', session_id: session_id)
+ end
+
+ describe '#store_session_id_per_ip' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ it 'adds expiration time to key' do
+ Timecop.freeze do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.ttl("session:lookup:ip:gitlab:127.0.0.1")).to eq(24.hours.to_i)
+ end
+ end
+ end
+
+ it 'adds id only once' do
+ subject.store_session_id_per_ip
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ context 'when there is already one session' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+ new_anonymous_session(additional_session_id).store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to contain_exactly(default_session_id, additional_session_id)
+ end
+ end
+ end
+ end
+
+ describe '#stored_sessions' do
+ it 'returns all anonymous sessions per ip' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ expect(subject.stored_sessions).to eq(2)
+ end
+ end
+
+ it 'removes obsolete lookup through ip entries' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ subject.cleanup_session_per_ip_entries
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [additional_session_id]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index b25f0f6b887..b3dedfe1f77 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -130,7 +130,7 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
it 'returns error when saving project ID fails' do
- allow(application_setting).to receive(:update) { false }
+ allow(application_setting).to receive(:save) { false }
expect { result }.to raise_error(StandardError, 'Could not save project ID')
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index f49d4e23e39..e5d688aa391 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
describe Gitlab::Kubernetes::KubeClient do
+ include StubRequests
include KubernetesHelpers
let(:api_url) { 'https://kubernetes.example.com/prefix' }
@@ -14,6 +15,17 @@ describe Gitlab::Kubernetes::KubeClient do
stub_kubeclient_discover(api_url)
end
+ def method_call(client, method_name)
+ case method_name
+ when /\A(get_|delete_)/
+ client.public_send(method_name)
+ when /\A(create_|update_)/
+ client.public_send(method_name, {})
+ else
+ raise "Unknown method name #{method_name}"
+ end
+ end
+
shared_examples 'a Kubeclient' do
it 'is a Kubeclient::Client' do
is_expected.to be_an_instance_of Kubeclient::Client
@@ -25,28 +37,30 @@ describe Gitlab::Kubernetes::KubeClient do
end
shared_examples 'redirection not allowed' do |method_name|
- before do
- redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
+ context 'api_url is redirected' do
+ before do
+ redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
- stub_request(:get, %r{\A#{api_url}/})
- .to_return(status: 302, headers: { location: redirect_url })
+ stub_request(:get, %r{\A#{api_url}/})
+ .to_return(status: 302, headers: { location: redirect_url })
- stub_request(:get, redirect_url)
- .to_return(status: 200, body: '{}')
- end
+ stub_request(:get, redirect_url)
+ .to_return(status: 200, body: '{}')
+ end
- it 'does not follow redirects' do
- method_call = -> do
- case method_name
- when /\A(get_|delete_)/
- client.public_send(method_name)
- when /\A(create_|update_)/
- client.public_send(method_name, {})
- else
- raise "Unknown method name #{method_name}"
- end
+ it 'does not follow redirects' do
+ expect { method_call(client, method_name) }.to raise_error(Kubeclient::HttpError)
end
- expect { method_call.call }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+
+ shared_examples 'dns rebinding not allowed' do |method_name|
+ it 'does not allow DNS rebinding' do
+ stub_dns(api_url, ip_address: '8.8.8.8')
+ client
+
+ stub_dns(api_url, ip_address: '192.168.2.120')
+ expect { method_call(client, method_name) }.to raise_error(ArgumentError, /is blocked/)
end
end
@@ -160,6 +174,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the core client' do
expect(client).to delegate_method(method).to(:core_client)
@@ -185,6 +200,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the rbac client' do
expect(client).to delegate_method(method).to(:rbac_client)
@@ -203,6 +219,7 @@ describe Gitlab::Kubernetes::KubeClient do
describe '#get_deployments' do
include_examples 'redirection not allowed', 'get_deployments'
+ include_examples 'dns rebinding not allowed', 'get_deployments'
it 'delegates to the extensions client' do
expect(client).to delegate_method(:get_deployments).to(:extensions_client)
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 22c5f27dc6d..f882dbbdb5c 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -7,7 +7,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
@@ -18,7 +20,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/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
new file mode 100644
index 00000000000..325ac3c6f31
--- /dev/null
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::VisibilityLevelChecker do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:visibility_level_checker) { }
+ let(:override_params) { {} }
+
+ subject { described_class.new(user, project, project_params: override_params) }
+
+ describe '#level_restricted?' do
+ context 'when visibility level is allowed' do
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when visibility level is restricted' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns true and visibility name' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ context 'overridden visibility' do
+ let(:override_params) do
+ {
+ import_data: {
+ data: {
+ override_params: {
+ visibility: override_visibility
+ }
+ }
+ }
+ }
+ end
+
+ context 'when restricted' do
+ let(:override_visibility) { 'public' }
+
+ it 'returns true and visibility name' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context 'when misspelled' do
+ let(:override_visibility) { 'publik' }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when import_data is missing' do
+ let(:override_params) { {} }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index db80b85360f..4f7a6d102b8 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -355,6 +355,71 @@ describe ApplicationSetting do
end
end
end
+
+ context 'asset proxy settings' do
+ before do
+ subject.asset_proxy_enabled = true
+ end
+
+ describe '#asset_proxy_url' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_url) }
+ it { is_expected.to allow_value(http).for(:asset_proxy_url) }
+ it { is_expected.to allow_value(https).for(:asset_proxy_url) }
+ it { is_expected.not_to allow_value(ftp).for(:asset_proxy_url) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_url = ''
+
+ expect(subject).to be_valid
+ end
+ end
+
+ describe '#asset_proxy_secret_key' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_secret_key) }
+ it { is_expected.to allow_value('anything').for(:asset_proxy_secret_key) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_secret_key = ''
+
+ expect(subject).to be_valid
+ end
+
+ it 'is encrypted' do
+ subject.asset_proxy_secret_key = 'shared secret'
+
+ expect(subject.encrypted_asset_proxy_secret_key).to be_present
+ expect(subject.encrypted_asset_proxy_secret_key).not_to eq(subject.asset_proxy_secret_key)
+ end
+ end
+
+ describe '#asset_proxy_whitelist' do
+ context 'when given an Array' do
+ it 'sets the domains and adds current running host' do
+ setting.asset_proxy_whitelist = ['example.com', 'assets.example.com']
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', 'assets.example.com', 'localhost'])
+ end
+ end
+
+ context 'when given a String' do
+ it 'sets multiple domains with spaces' do
+ setting.asset_proxy_whitelist = 'example.com *.example.com'
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.asset_proxy_whitelist = "example.com\n *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.asset_proxy_whitelist = "example.com, *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
+ end
+ end
end
context 'restrict creating duplicates' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 39680c0e51a..65d41edc035 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -46,6 +46,7 @@ describe Issuable do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(1_000_000) }
end
describe 'milestone' do
@@ -795,4 +796,29 @@ describe Issuable do
end
end
end
+
+ describe '#matches_cross_reference_regex?' do
+ context "issue description with long path string" do
+ let(:mentionable) { build(:issue, description: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:mentionable) { build(:note, note: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:project) { create(:project, :public, :repository) }
+ let(:mentionable) { project.commit }
+
+ before do
+ expect(mentionable.raw).to receive(:message).and_return("/a" * 50000)
+ end
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+ end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index c2e2298823e..baf2cfeab0c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -84,6 +84,13 @@ describe Label do
end
end
+ describe '#description' do
+ it 'sanitizes description' do
+ label = described_class.new(description: '<b>foo & bar?</b>')
+ expect(label.description).to eq('foo & bar?')
+ end
+ end
+
describe 'priorization' do
subject(:label) { create(:label) }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bfd0e5f0558..927fbdb93d8 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -22,6 +22,7 @@ describe Note do
end
describe 'validation' do
+ it { is_expected.to validate_length_of(:note).is_at_most(1_000_000) }
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb
index be82f223478..96ac532dcd1 100644
--- a/spec/models/project_services/discord_service_spec.rb
+++ b/spec/models/project_services/discord_service_spec.rb
@@ -8,4 +8,37 @@ describe DiscordService do
let(:client_arguments) { { url: webhook_url } }
let(:content_key) { :content }
end
+
+ describe '#execute' do
+ include StubRequests
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:webhook_url) { "https://example.gitlab.com/" }
+
+ let(:sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(subject).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ context 'DNS rebind to local address' do
+ before do
+ stub_dns(webhook_url, ip_address: '192.168.2.120')
+ end
+
+ it 'does not allow DNS rebinding' do
+ expect { subject.execute(sample_data) }.to raise_error(ArgumentError, /is blocked/)
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f1408194250..b8c323904b8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3065,6 +3065,47 @@ describe User do
end
end
+ describe '#will_save_change_to_login?' do
+ let(:user) { create(:user, username: 'old-username', email: 'old-email@example.org') }
+ let(:new_username) { 'new-name' }
+ let(:new_email) { 'new-email@example.org' }
+
+ subject { user.will_save_change_to_login? }
+
+ context 'when the username is changed' do
+ before do
+ user.username = new_username
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when the email is changed' do
+ before do
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when both email and username are changed' do
+ before do
+ user.username = new_username
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when email and username aren\'t changed' do
+ before do
+ user.name = 'new_name'
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#sync_attribute?' do
let(:user) { described_class.new }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index b149dbcf871..25267d36ab8 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -172,6 +172,34 @@ describe IssuePolicy do
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue)
end
+ context 'when issues are private' do
+ before do
+ project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
+ end
+ let(:issue) { create(:issue, project: project, author: author) }
+ let(:visitor) { create(:user) }
+ let(:admin) { create(:user, :admin) }
+
+ it 'forbids visitors from viewing issues' do
+ expect(permissions(visitor, issue)).to be_disallowed(:read_issue)
+ end
+ it 'forbids visitors from commenting' do
+ expect(permissions(visitor, issue)).to be_disallowed(:create_note)
+ end
+ it 'allows guests to view' do
+ expect(permissions(guest, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows guests to comment' do
+ expect(permissions(guest, issue)).to be_allowed(:create_note)
+ end
+ it 'allows admins to view' do
+ expect(permissions(admin, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows admins to comment' do
+ expect(permissions(admin, issue)).to be_allowed(:create_note)
+ end
+ end
+
context 'with confidential issues' do
let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) }
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 81279225d61..87205f56589 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -6,6 +6,7 @@ describe MergeRequestPolicy do
let(:guest) { create(:user) }
let(:author) { create(:user) }
let(:developer) { create(:user) }
+ let(:non_team_member) { create(:user) }
let(:project) { create(:project, :public) }
def permissions(user, merge_request)
@@ -18,6 +19,78 @@ describe MergeRequestPolicy do
project.add_developer(developer)
end
+ MR_PERMS = %i[create_merge_request_in
+ create_merge_request_from
+ read_merge_request
+ create_note].freeze
+
+ shared_examples_for 'a denied user' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "cannot #{thing}" do
+ expect(perms).to be_disallowed(thing)
+ end
+ end
+ end
+
+ shared_examples_for 'a user with access' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "can #{thing}" do
+ expect(perms).to be_allowed(thing)
+ end
+ end
+ end
+
+ context 'when merge requests have been disabled' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
+ end
+
+ describe 'the author' do
+ subject { author }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a guest' do
+ subject { guest }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'any other user' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+ end
+
+ context 'when merge requests are private' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
+ describe 'a non-team-member' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a user with access'
+ end
+ end
+
context 'when merge request is unlocked' do
let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
@@ -48,6 +121,22 @@ describe MergeRequestPolicy do
it 'prevents guests from reopening merge request' do
expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
end
+
+ context 'when the user is not a project member' do
+ let(:user) { create(:user) }
+
+ it 'cannot create a note' do
+ expect(permissions(user, merge_request_locked)).to be_disallowed(:create_note)
+ end
+ end
+
+ context 'when the user is project member, with at least guest access' do
+ let(:user) { guest }
+
+ it 'can create a note' do
+ expect(permissions(user, merge_request_locked)).to be_allowed(:create_note)
+ end
+ end
end
context 'with external authorization enabled' do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 590107d5161..048d04cdefd 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -224,5 +224,33 @@ describe API::Settings, 'Settings' do
expect(json_response['error']).to eq('plantuml_url is missing')
end
end
+
+ context 'asset_proxy settings' do
+ it 'updates application settings' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_enabled: true,
+ asset_proxy_url: 'http://assets.example.com',
+ asset_proxy_secret_key: 'shared secret',
+ asset_proxy_whitelist: ['example.com', '*.example.com']
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_enabled']).to be(true)
+ expect(json_response['asset_proxy_url']).to eq('http://assets.example.com')
+ expect(json_response['asset_proxy_secret_key']).to be_nil
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'allows a string for asset_proxy_whitelist' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_whitelist: 'example.com, *.example.com'
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
end
end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index ab06c1a1209..51fb43907a6 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -110,6 +110,39 @@ describe ApplicationSettings::UpdateService do
end
end
+ describe 'markdown cache invalidators' do
+ shared_examples 'invalidates markdown cache' do |attribute|
+ let(:params) { attribute }
+
+ it 'increments cache' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).by(1)
+ end
+ end
+
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_enabled: true }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_url: 'http://test.com' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_secret_key: 'another secret' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_whitelist: ['domain.com'] }
+
+ context 'when also setting the local_markdown_version' do
+ let(:params) { { asset_proxy_enabled: true, local_markdown_version: 12 } }
+
+ it 'does not increment' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).to(12)
+ end
+ end
+
+ context 'do not invalidate if value does not change' do
+ let(:params) { { asset_proxy_enabled: true, asset_proxy_secret_key: 'secret', asset_proxy_url: 'http://test.com' } }
+
+ it 'does not increment' do
+ described_class.new(application_settings, admin, params).execute
+
+ expect { described_class.new(application_settings, admin, params).execute }.not_to change(application_settings, :local_markdown_version)
+ end
+ end
+ end
+
describe 'performance bar settings' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 6874a8a0929..642a49d57d5 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -60,35 +60,63 @@ describe Issues::CloseService do
describe '#close_issue' do
context "closed by a merge request" do
- before do
+ it 'mentions closure via a merge request' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
end
- end
- it 'mentions closure via a merge request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference))
end
+
+ context 'when user cannot read merge request' do
+ it 'does not mention merge request' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_merge_request.to_reference)
+ end
+ end
end
context "closed by a commit" do
- before do
+ it 'mentions closure via a commit' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
end
- end
- it 'mentions closure via a commit' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_commit.id))
end
+
+ context 'when user cannot read the commit' do
+ it 'does not mention the commit id' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_commit.id)
+ end
+ end
end
context "valid params" do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d4fa62fa85d..8178b7d2ba2 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -183,27 +183,65 @@ describe Projects::CreateService, '#execute' do
context 'restricted visibility level' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
- opts.merge!(
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
- )
+ shared_examples 'restricted visibility' do
+ it 'does not allow a restricted visibility level for non-admins' do
+ project = create_project(user, opts)
+
+ expect(project).to respond_to(:errors)
+ expect(project.errors.messages).to have_key(:visibility_level)
+ expect(project.errors.messages[:visibility_level].first).to(
+ match('restricted by your GitLab administrator')
+ )
+ end
+
+ it 'allows a restricted visibility level for admins' do
+ admin = create(:admin)
+ project = create_project(admin, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
end
- it 'does not allow a restricted visibility level for non-admins' do
- project = create_project(user, opts)
- expect(project).to respond_to(:errors)
- expect(project.errors.messages).to have_key(:visibility_level)
- expect(project.errors.messages[:visibility_level].first).to(
- match('restricted by your GitLab administrator')
- )
+ context 'when visibility is project based' do
+ before do
+ opts.merge!(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ )
+ end
+
+ include_examples 'restricted visibility'
end
- it 'allows a restricted visibility level for admins' do
- admin = create(:admin)
- project = create_project(admin, opts)
+ context 'when visibility is overridden' do
+ let(:visibility) { 'public' }
- expect(project.errors.any?).to be(false)
- expect(project.saved?).to be(true)
+ before do
+ opts.merge!(
+ import_data: {
+ data: {
+ override_params: {
+ visibility: visibility
+ }
+ }
+ }
+ )
+ end
+
+ include_examples 'restricted visibility'
+
+ context 'when visibility is misspelled' do
+ let(:visibility) { 'publik' }
+
+ it 'does not restrict project creation' do
+ project = create_project(user, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
+ end
end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 9ee23f3eb48..bdf2f59704c 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -436,25 +436,114 @@ describe TodoService do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
end
- context 'on commit' do
- let(:project) { create(:project, :repository) }
-
- it 'creates a todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(note_on_commit, john_doe)
-
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ context 'commits' do
+ let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }
+
+ context 'leaving a note on a commit in a public project' do
+ let(:project) { create(:project, :repository, :public) }
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
end
- it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(addressed_note_on_commit, john_doe)
+ context 'leaving a note on a commit in a public project with private code' do
+ let(:project) { create(:project, :repository, :public, :repository_private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+ end
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ context 'leaving a note on a commit in a private project' do
+ let(:project) { create(:project, :repository, :private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index dec7898d8d2..f364e4fd158 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -105,6 +105,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end
+ def stub_asset_proxy_setting(messages)
+ allow(Gitlab.config.asset_proxy).to receive_messages(to_settings(messages))
+ end
+
def stub_rack_attack_setting(messages)
allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
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 39d13cccb13..4bc22861d58 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -7,6 +7,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
@@ -271,7 +273,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
@@ -284,7 +288,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) { "&lt;img src=\"\" onerror=alert(`bug`)&gt;" }
+
+ 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
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
new file mode 100644
index 00000000000..9604555c57d
--- /dev/null
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -0,0 +1,8 @@
+shared_examples_for 'matches_cross_reference_regex? fails fast' do
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823
+ expect do
+ Timeout.timeout(3.seconds) { mentionable.matches_cross_reference_regex? }
+ end.not_to raise_error
+ end
+end
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 66c064e3fba..5d521d18c70 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'devise/shared/_signin_box' do
assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
allow(view).to receive(:captcha_enabled?).and_return(false)
+ allow(view).to receive(:captcha_on_login_required?).and_return(false)
end
it 'is shown when Crowd is enabled' do