diff options
32 files changed, 341 insertions, 173 deletions
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index eb469d2d714..4a31f1f3d57 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -41,7 +41,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont def set_pipeline_variables @pipelines = - if can?(current_user, :read_pipeline, @project) + if can?(current_user, :read_pipeline, @merge_request.source_project) @merge_request.all_pipelines else Ci::Pipeline.none diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8f177895b08..ea3d68449bd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -82,7 +82,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def pipelines - @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30) + set_pipeline_variables + @pipelines = @pipelines.page(params[:page]).per(30) Gitlab::PollingInterval.set_header(response, interval: 10_000) diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index 284e119ca06..7159d0243a3 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -4,7 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController before_action :authorize_admin_build! before_action :authorize_manage_trigger!, except: [:index, :create] before_action :authorize_admin_trigger!, only: [:edit, :update] - before_action :trigger, only: [:take_ownership, :edit, :update, :destroy] + before_action :trigger, only: [:edit, :update, :destroy] layout 'project_settings' @@ -24,16 +24,6 @@ class Projects::TriggersController < Projects::ApplicationController redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') end - def take_ownership - if trigger.update(owner: current_user) - flash[:notice] = _('Trigger was re-assigned.') - else - flash[:alert] = _('You could not take ownership of trigger.') - end - - redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') - end - def edit end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index bfabc6d262c..46925f6704d 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -35,6 +35,8 @@ class SlashCommandsService < Service chat_user = find_chat_user(params) if chat_user&.user + return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands) + Gitlab::SlashCommands::Command.new(project, chat_user, params).execute else url = authorize_chat_name_url(params) diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index e85397422e6..1807b741edb 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -37,6 +37,7 @@ class GlobalPolicy < BasePolicy enable :access_git enable :receive_notifications enable :use_quick_actions + enable :use_slash_commands end rule { blocked | internal }.policy do @@ -44,6 +45,7 @@ class GlobalPolicy < BasePolicy prevent :access_api prevent :access_git prevent :receive_notifications + prevent :use_slash_commands end rule { required_terms_not_accepted }.policy do @@ -61,6 +63,7 @@ class GlobalPolicy < BasePolicy rule { access_locked }.policy do prevent :log_in + prevent :use_slash_commands end rule { ~(anonymous & restricted_public_level) }.policy do diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 914ad628a99..2e994f4c3f1 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -16,9 +16,14 @@ class IssueEntity < IssuableEntity expose :discussion_locked expose :assignees, using: API::Entities::UserBasic expose :due_date - expose :moved_to_id expose :project_id + expose :moved_to_id do |issue| + if issue.moved_to_id.present? && can?(request.current_user, :read_issue, issue.moved_to) + issue.moved_to_id + end + end + expose :web_url do |issue| project_issue_path(issue.project, issue) end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 109c964e577..b28f80939ae 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -11,15 +11,18 @@ module MergeRequests # https://gitlab.com/gitlab-org/gitlab-ce/issues/53658 merge_quick_actions_into_params!(merge_request, only: [:target_branch]) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch) - merge_request.assign_attributes(params) + # Assign the projects first so we can use policies for `filter_params` merge_request.author = current_user + merge_request.source_project = find_source_project + merge_request.target_project = find_target_project + + filter_params(merge_request) + merge_request.assign_attributes(params.to_h.compact) + merge_request.compare_commits = [] - merge_request.source_project = find_source_project - merge_request.target_project = find_target_project - merge_request.target_branch = find_target_branch - merge_request.can_be_created = projects_and_branches_valid? - ensure_milestone_available(merge_request) + merge_request.target_branch = find_target_branch + merge_request.can_be_created = projects_and_branches_valid? # compare branches only if branches are valid, otherwise # compare_branches may raise an error @@ -50,12 +53,14 @@ module MergeRequests to: :merge_request def find_source_project + source_project = project_from_params(:source_project) return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project) project end def find_target_project + target_project = project_from_params(:target_project) return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) target_project = project.default_merge_request_target @@ -65,6 +70,17 @@ module MergeRequests project end + def project_from_params(param_name) + project_from_params = params.delete(param_name) + + id_param_name = :"#{param_name}_id" + if project_from_params.nil? && params[id_param_name] + project_from_params = Project.find_by_id(params.delete(id_param_name)) + end + + project_from_params + end + def find_target_branch target_branch || target_project.default_branch end diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 6f6f1e5e0c5..7367a96f8e4 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -30,10 +30,7 @@ Never %td.text-right.trigger-actions - - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?" - revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?" - - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger) - = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership" - if can?(current_user, :admin_trigger, trigger) = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do %i.fa.fa-pencil diff --git a/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml new file mode 100644 index 00000000000..cd31fe0f35c --- /dev/null +++ b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml @@ -0,0 +1,5 @@ +--- +title: Restrict slash commands to users who can log in +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-bvl-filter-mr-params.yml b/changelogs/unreleased/security-bvl-filter-mr-params.yml new file mode 100644 index 00000000000..4433ec73b7c --- /dev/null +++ b/changelogs/unreleased/security-bvl-filter-mr-params.yml @@ -0,0 +1,5 @@ +--- +title: Filter merge request params on the new merge request page +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-dns-ssrf-bypass.yml b/changelogs/unreleased/security-dns-ssrf-bypass.yml new file mode 100644 index 00000000000..e48696ce5bd --- /dev/null +++ b/changelogs/unreleased/security-dns-ssrf-bypass.yml @@ -0,0 +1,5 @@ +--- +title: Fix Server Side Request Forgery mitigation bypass +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-hide_moved_issue_id.yml b/changelogs/unreleased/security-hide_moved_issue_id.yml new file mode 100644 index 00000000000..24353d797c9 --- /dev/null +++ b/changelogs/unreleased/security-hide_moved_issue_id.yml @@ -0,0 +1,5 @@ +--- +title: Do not show moved issue id for users that cannot read issue +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-mr-pipeline-permissions.yml b/changelogs/unreleased/security-mr-pipeline-permissions.yml new file mode 100644 index 00000000000..a317c93228c --- /dev/null +++ b/changelogs/unreleased/security-mr-pipeline-permissions.yml @@ -0,0 +1,5 @@ +--- +title: Use source project as permissions reference for MergeRequestsController#pipelines +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-remove-take-trigger-ownership-feature.yml b/changelogs/unreleased/security-remove-take-trigger-ownership-feature.yml new file mode 100644 index 00000000000..201f66e1f18 --- /dev/null +++ b/changelogs/unreleased/security-remove-take-trigger-ownership-feature.yml @@ -0,0 +1,5 @@ +--- +title: Drop feature to take ownership of trigger token. +merge_request: +author: +type: security diff --git a/config/routes/project.rb b/config/routes/project.rb index 0f7621728d9..6278ce72352 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -176,11 +176,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do resource :variables, only: [:show, :update] - resources :triggers, only: [:index, :create, :edit, :update, :destroy] do - member do - post :take_ownership - end - end + resources :triggers, only: [:index, :create, :edit, :update, :destroy] resource :mirror, only: [:show, :update] do member do diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md index 736312df116..e207ff8e98a 100644 --- a/doc/api/pipeline_triggers.md +++ b/doc/api/pipeline_triggers.md @@ -120,35 +120,6 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form descript } ``` -## Take ownership of a project trigger - -Update an owner of a project trigger. - -``` -POST /projects/:id/triggers/:trigger_id/take_ownership -``` - -| Attribute | Type | required | Description | -|---------------|---------|----------|--------------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `trigger_id` | integer | yes | The trigger id | - -``` -curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership" -``` - -```json -{ - "id": 10, - "description": "my trigger", - "created_at": "2016-01-07T09:53:58.235Z", - "last_used": null, - "token": "6d056f63e50fe6f8c5f8f4aa10edb7", - "updated_at": "2016-01-07T09:53:58.235Z", - "owner": null -} -``` - ## Remove a project trigger Remove a project's build trigger. diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index ad80b5d8818..7fec4c7a867 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -93,17 +93,6 @@ overview of the time the triggers were last used. ![Triggers page overview](img/triggers_page.png) -## Taking ownership of a trigger - -> **Note**: -GitLab 9.0 introduced a trigger ownership to solve permission problems. - -Each created trigger when run will impersonate their associated user including -their access to projects and their project permissions. - -You can take ownership of existing triggers by clicking *Take ownership*. -From now on the trigger will be run as you. - ## Revoking a trigger You can revoke a trigger any time by going at your project's @@ -278,8 +267,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy. Triggers with the legacy label do not have an associated user and only have access to the current project. They are considered deprecated and will be -removed with one of the future versions of GitLab. You are advised to -[take ownership](#taking-ownership-of-a-trigger) of any legacy triggers. +removed with one of the future versions of GitLab. [ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017 [ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346 diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 6c3fa5eb463..9c4871c5e15 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -91,8 +91,7 @@ to steal the tokens of other jobs. Since 9.0 [pipeline triggers][triggers] do support the new permission model. The new triggers do impersonate their associated user including their access -to projects and their project permissions. To migrate trigger to use new permission -model use **Take ownership**. +to projects and their project permissions. ## Before GitLab 8.12 diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 0e829c5699b..eeecc390256 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -112,27 +112,6 @@ module API end end - desc 'Take ownership of trigger' do - success Entities::Trigger - end - params do - requires :trigger_id, type: Integer, desc: 'The trigger ID' - end - post ':id/triggers/:trigger_id/take_ownership' do - authenticate! - authorize! :admin_build, user_project - - trigger = user_project.triggers.find(params.delete(:trigger_id)) - break not_found!('Trigger') unless trigger - - if trigger.update(owner: current_user) - status :ok - present trigger, with: Entities::Trigger, current_user: current_user - else - render_validation_error!(trigger) - end - end - desc 'Delete a trigger' do success Entities::Trigger end diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 9a8df719827..d9070ce5a09 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -20,6 +20,7 @@ module Gitlab # Returns an array with [<uri>, <original-hostname>]. # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/ParameterLists + # rubocop:disable Metrics/PerceivedComplexity def validate!( url, ports: [], @@ -32,6 +33,7 @@ module Gitlab dns_rebind_protection: true) # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/ParameterLists + # rubocop:enable Metrics/PerceivedComplexity return [nil, nil] if url.nil? @@ -56,7 +58,15 @@ module Gitlab addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr end rescue SocketError - return [uri, nil] + # In the test suite we use a lot of mocked urls that are either invalid or + # don't exist. In order to avoid modifying a ton of tests and factories + # we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS + # is not true + return [uri, nil] if Rails.env.test? && ENV['RSPEC_ALLOW_INVALID_URLS'] == 'true' + + # If the addr can't be resolved or the url is invalid (i.e http://1.1.1.1.1) + # we block the url + raise BlockedUrlError, "Host cannot be resolved or invalid" end protected_uri_with_hostname = enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection) @@ -92,9 +102,9 @@ module Gitlab # we'll be making the request to the IP address, instead of using the hostname. def enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection) address = addrs_info.first - ip_address = address&.ip_address + ip_address = address.ip_address - return [uri, nil] unless dns_rebind_protection && ip_address && ip_address != hostname + return [uri, nil] unless dns_rebind_protection && ip_address != hostname uri = uri.dup uri.hostname = ip_address diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e44f7e07e61..67932bda316 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10232,9 +10232,6 @@ msgstr "" msgid "Trigger was created successfully." msgstr "" -msgid "Trigger was re-assigned." -msgstr "" - msgid "Trigger was successfully updated." msgstr "" @@ -11065,9 +11062,6 @@ msgstr "" msgid "You could not create a new trigger." msgstr "" -msgid "You could not take ownership of trigger." -msgstr "" - msgid "You do not have any subscriptions yet" msgstr "" diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index f4a18dcba51..63b04e9d049 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -598,10 +598,100 @@ describe Projects::MergeRequestsController do format: :json end - it 'responds with serialized pipelines' do - expect(json_response['pipelines']).not_to be_empty - expect(json_response['count']['all']).to eq 1 - expect(response).to include_pagination_headers + context 'with "enabled" builds on a public project' do + let(:project) { create(:project, :repository, :public) } + + context 'for a project owner' do + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for an unassociated user' do + let(:user) { create :user } + + it 'responds with no pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + + context 'with private builds on a public project' do + let(:project) { create(:project, :repository, :public, :builds_private) } + + context 'for a project owner' do + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for an unassociated user' do + let(:user) { create :user } + + it 'responds with no pipelines' do + expect(json_response['pipelines']).to be_empty + expect(json_response['count']['all']).to eq(0) + expect(response).to include_pagination_headers + end + end + + context 'from a project fork' do + let(:fork_user) { create :user } + let(:forked_project) { fork_project(project, fork_user, repository: true) } # Forked project carries over :builds_private + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: forked_project) } + + context 'with private builds' do + context 'for the target project member' do + it 'does not respond with serialized pipelines' do + expect(json_response['pipelines']).to be_empty + expect(json_response['count']['all']).to eq(0) + expect(response).to include_pagination_headers + end + end + + context 'for the source project member' do + let(:user) { fork_user } + + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + + context 'with public builds' do + let(:forked_project) do + fork_project(project, fork_user, repository: true).tap do |new_project| + new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED) + end + end + + context 'for the target project member' do + it 'does not respond with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for the source project member' do + let(:user) { fork_user } + + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + end end end diff --git a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb index 9318b5f1ebb..1ebe9e2e409 100644 --- a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb +++ b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'Merge Request > Tries to access private repo of public project' do +describe 'Merge Request > User tries to access private project information through the new mr page' do let(:current_user) { create(:user) } let(:private_project) do create(:project, :public, :repository, @@ -33,5 +35,22 @@ describe 'Merge Request > Tries to access private repo of public project' do it "does not mention the project the user can't see the repo of" do expect(page).not_to have_content('nothing-to-see-here') end + + context 'when the user enters label information from the private project in the querystring' do + let(:inaccessible_label) { create(:label, project: private_project) } + let(:mr_path) do + project_new_merge_request_path( + owned_project, + merge_request: { + label_ids: [inaccessible_label.id], + source_branch: 'feature' + } + ) + end + + it 'does not expose the label name' do + expect(page).not_to have_content(inaccessible_label.name) + end + end end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 919859c145a..41b640bb53a 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -81,29 +81,6 @@ describe 'Triggers', :js do end end - describe 'trigger "Take ownership" workflow' do - before do - create(:ci_trigger, owner: user2, project: @project, description: trigger_title) - visit project_settings_ci_cd_path(@project) - end - - it 'button "Take ownership" has correct alert' do - expected_alert = 'By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?' - expect(page.find('a.btn-trigger-take-ownership')['data-confirm']).to eq expected_alert - end - - it 'take trigger ownership' do - # See if "Take ownership" on trigger works post trigger creation - page.accept_confirm do - first(:link, "Take ownership").send_keys(:return) - end - - expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.' - expect(page.find('.triggers-list')).to have_content trigger_title - expect(page.find('.triggers-list .trigger-owner')).to have_content user.name - end - end - describe 'trigger "Revoke" workflow' do before do create(:ci_trigger, owner: user2, project: @project, description: trigger_title) diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 253366e0789..0d88a1c11a6 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -68,6 +68,16 @@ describe Gitlab::UrlBlocker do expect(uri).to eq(Addressable::URI.parse('https://example.org')) expect(hostname).to eq(nil) end + + context 'when it cannot be resolved' do + let(:import_url) { 'http://foobar.x' } + + it 'raises error' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError) + end + end end context 'when the URL hostname is an IP address' do @@ -79,6 +89,16 @@ describe Gitlab::UrlBlocker do expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) expect(hostname).to be(nil) end + + context 'when it is invalid' do + let(:import_url) { 'http://1.1.1.1.1' } + + it 'raises an error' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError) + end + end end end end @@ -180,8 +200,6 @@ describe Gitlab::UrlBlocker do end it 'returns true for a non-alphanumeric hostname' do - stub_resolv - aggregate_failures do expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a') @@ -300,10 +318,6 @@ describe Gitlab::UrlBlocker do end context 'when enforce_user is' do - before do - stub_resolv - end - context 'false (default)' do it 'does not block urls with a non-alphanumeric username' do expect(described_class).not_to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a') @@ -351,6 +365,18 @@ describe Gitlab::UrlBlocker do expect(described_class.blocked_url?('https://git‌lab.com/foo/foo.bar', ascii_only: true)).to be true end end + + it 'blocks urls with invalid ip address' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect(described_class).to be_blocked_url('http://8.8.8.8.8') + end + + it 'blocks urls whose hostname cannot be resolved' do + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + + expect(described_class).to be_blocked_url('http://foobar.x') + end end describe '#validate_hostname!' do @@ -382,10 +408,4 @@ describe Gitlab::UrlBlocker do end end end - - # Resolv does not support resolving UTF-8 domain names - # See https://bugs.ruby-lang.org/issues/4270 - def stub_resolv - allow(Resolv).to receive(:getaddresses).and_return([]) - end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 12be3927e18..df6cc526eb0 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -226,4 +226,32 @@ describe GlobalPolicy do it { is_expected.not_to be_allowed(:read_instance_statistics) } end end + + describe 'slash commands' do + context 'regular user' do + it { is_expected.to be_allowed(:use_slash_commands) } + end + + context 'when internal' do + let(:current_user) { User.ghost } + + it { is_expected.not_to be_allowed(:use_slash_commands) } + end + + context 'when blocked' do + before do + current_user.block + end + + it { is_expected.not_to be_allowed(:use_slash_commands) } + end + + context 'when access locked' do + before do + current_user.lock_access! + end + + it { is_expected.not_to be_allowed(:use_slash_commands) } + end + end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index f0f01e97f1d..8ea3d16a41f 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -270,34 +270,6 @@ describe API::Triggers do end end - describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do - context 'authenticated user with valid permissions' do - it 'updates owner' do - post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to include('owner') - expect(trigger.reload.owner).to eq(user) - end - end - - context 'authenticated user with invalid permissions' do - it 'does not update owner' do - post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'unauthenticated user' do - it 'does not update owner' do - post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership") - - expect(response).to have_gitlab_http_status(401) - end - end - end - describe 'DELETE /projects/:id/triggers/:trigger_id' do context 'authenticated user with valid permissions' do it 'deletes trigger' do diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb index caa3e41402b..0e05b3c84f4 100644 --- a/spec/serializers/issue_entity_spec.rb +++ b/spec/serializers/issue_entity_spec.rb @@ -17,4 +17,37 @@ describe IssueEntity do it 'has time estimation attributes' do expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent) end + + context 'when issue got moved' do + let(:public_project) { create(:project, :public) } + let(:member) { create(:user) } + let(:non_member) { create(:user) } + let(:issue) { create(:issue, project: public_project) } + + before do + project.add_developer(member) + public_project.add_developer(member) + Issues::MoveService.new(public_project, member).execute(issue, project) + end + + context 'when user cannot read target project' do + it 'does not return moved_to_id' do + request = double('request', current_user: non_member) + + response = described_class.new(issue, request: request).as_json + + expect(response[:moved_to_id]).to be_nil + end + end + + context 'when user can read target project' do + it 'returns moved moved_to_id' do + request = double('request', current_user: member) + + response = described_class.new(issue, request: request).as_json + + expect(response[:moved_to_id]).to eq(issue.moved_to_id) + end + end + end end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 5c3b209086c..f18239f6d39 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' describe MergeRequests::BuildService do @@ -225,6 +224,11 @@ describe MergeRequests::BuildService do let(:label_ids) { [label2.id] } let(:milestone_id) { milestone2.id } + before do + # Guests are not able to assign labels or milestones to an issue + project.add_developer(user) + end + it 'assigns milestone_id and label_ids instead of issue labels and milestone' do expect(merge_request.milestone).to eq(milestone2) expect(merge_request.labels).to match_array([label2]) @@ -479,4 +483,35 @@ describe MergeRequests::BuildService do end end end + + context 'when assigning labels' do + let(:label_ids) { [create(:label, project: project).id] } + + context 'for members with less than developer access' do + it 'is not allowed' do + expect(merge_request.label_ids).to be_empty + end + end + + context 'for users allowed to assign labels' do + before do + project.add_developer(user) + end + + context 'for labels in the project' do + it 'is allowed for developers' do + expect(merge_request.label_ids).to contain_exactly(*label_ids) + end + end + + context 'for unrelated labels' do + let(:project_label) { create(:label, project: project) } + let(:label_ids) { [create(:label).id, project_label.id] } + + it 'only assigns related labels' do + expect(merge_request.label_ids).to contain_exactly(project_label.id) + end + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ec17bee640d..72cbc9ebc5a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,7 @@ SimpleCovEnv.start! ENV["RAILS_ENV"] = 'test' ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' +ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true' require File.expand_path('../config/environment', __dir__) require 'rspec/rails' diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb index dc97a39f051..ef40287fd6e 100644 --- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb @@ -91,6 +91,19 @@ RSpec.shared_examples 'chat slash commands service' do subject.trigger(params) end + + context 'when user is blocked' do + before do + chat_name.user.block + end + + it 'blocks command execution' do + expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute) + + result = subject.trigger(params) + expect(result).to include(text: /^Whoops! This action is not allowed/) + end + end end end end diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index a9e03f3d4e5..5ee0a10f38d 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -85,8 +85,7 @@ describe FileMover do context 'when tmp uploader is not local storage' do before do - allow(PersonalFileUploader).to receive(:object_store_enabled?) { true } - tmp_uploader.object_store = ObjectStorage::Store::REMOTE + stub_uploads_object_storage(uploader: PersonalFileUploader) allow_any_instance_of(PersonalFileUploader).to receive(:file_storage?) { false } end |