diff options
75 files changed, 564 insertions, 162 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index fefadab16e4..1134a76a6b8 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -234,7 +234,15 @@ danger-review: - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger" - run_timed_command "retry yarn install --frozen-lockfile" script: - - run_timed_command "bundle exec danger --fail-on-errors=true --verbose" + - > + if [ -z "$DANGER_GITLAB_API_TOKEN" ]; then + # Force danger to skip CI source GitLab and fallback to "local only git repo". + unset GITLAB_CI + # We need to base SHA to help danger determine the base commit for this shallow clone. + run_timed_command "bundle exec danger dry_run --fail-on-errors=true --verbose --base='$CI_MERGE_REQUEST_DIFF_BASE_SHA'" + else + run_timed_command "bundle exec danger --fail-on-errors=true --verbose" + fi update-danger-review-cache: extends: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 39522f7b60c..df2edd04809 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1110,7 +1110,7 @@ .review:rules:danger: rules: - - if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID' + - if: '$CI_MERGE_REQUEST_IID' ############### # Setup rules # diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 218a002aa45..f01dedb40dd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -927,13 +927,6 @@ Style/RedundantRegexpEscape: Style/RedundantSelf: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/RedundantSelfAssignment: - Exclude: - - 'app/models/concerns/issuable.rb' - - 'spec/db/schema_spec.rb' - # Offense count: 213 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowInnerSlashes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fdc3cbaaf..b147b3c3a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 13.10.1 (2021-03-31) + +### Security (6 changes) + +- Leave pool repository on fork unlinking. +- Fixed XSS in merge requests sidebar. +- Fix arbitrary read/write in AsciiDoctor and Kroki gems. +- Prevent infinite loop when checking if collaboration is allowed. +- Disable arbitrary URI and file reads in JSON validator. +- Require POST request to trigger system hooks. + +### Removed (1 change) + +- Make HipChat project service do nothing. !57434 + +### Other (3 changes) + +- Remove direct mimemagic dependency. !57387 +- Refactor MimeMagic calls to new MimeType class. !57421 +- Switch to using a fake mimemagic gem. !57443 + + ## 13.10.0 (2021-03-22) ### Security (3 changes) @@ -529,6 +551,28 @@ entry. - Convert mattermost alert to pajamas. !56556 +## 13.9.5 (2021-03-31) + +### Security (6 changes) + +- Leave pool repository on fork unlinking. +- Fixed XSS in merge requests sidebar. +- Fix arbitrary read/write in AsciiDoctor and Kroki gems. +- Prevent infinite loop when checking if collaboration is allowed. +- Disable arbitrary URI and file reads in JSON validator. +- Require POST request to trigger system hooks. + +### Removed (1 change) + +- Make HipChat project service do nothing. !57434 + +### Other (3 changes) + +- Remove direct mimemagic dependency. !57387 +- Refactor MimeMagic calls to new MimeType class. !57421 +- Switch to using a fake mimemagic gem. !57443 + + ## 13.9.4 (2021-03-17) ### Security (1 change) @@ -1144,6 +1188,27 @@ entry. - Apply new GitLab UI for buttons in pipeline schedules. +## 13.8.7 (2021-03-31) + +### Security (5 changes) + +- Fixed XSS in merge requests sidebar. +- Leave pool repository on fork unlinking. +- Fix arbitrary read/write in AsciiDoctor and Kroki gems. +- Prevent infinite loop when checking if collaboration is allowed. +- Require POST request to trigger system hooks. + +### Removed (1 change) + +- Make HipChat project service do nothing. !57434 + +### Other (3 changes) + +- Remove direct mimemagic dependency. !57387 +- Refactor MimeMagic calls to new MimeType class. !57421 +- Switch to using a fake mimemagic gem. !57443 + + ## 13.8.6 (2021-03-17) ### Security (1 change) @@ -274,7 +274,7 @@ gem 'licensee', '~> 9.14.1' gem 'charlock_holmes', '~> 0.7.7' # Detect mime content type from content -gem 'ruby-magic-static', '~> 0.3.4' +gem 'ruby-magic-static', '~> 0.3.5' # Fake version of the gem to trick bundler gem 'mimemagic', '0.3.7', path: 'vendor/shims/mimemagic', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 75dced890e5..1a14028230f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1113,7 +1113,8 @@ GEM i18n ruby-fogbugz (0.2.1) crack (~> 0.4) - ruby-magic-static (0.3.4) + ruby-magic-static (0.3.5) + mini_portile2 (~> 2.5.0) ruby-prof (1.3.1) ruby-progressbar (1.11.0) ruby-saml (1.7.2) @@ -1559,7 +1560,7 @@ DEPENDENCIES rspec_junit_formatter rspec_profiling (~> 0.0.6) ruby-fogbugz (~> 0.2.1) - ruby-magic-static (~> 0.3.4) + ruby-magic-static (~> 0.3.5) ruby-prof (~> 1.3.0) ruby-progressbar (~> 1.10) ruby_parser (~> 3.15) diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index d2b83f980d7..20407334b3f 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -119,8 +119,8 @@ export default { <gl-button @click="onCancel">{{ s__('Cancel') }}</gl-button> <gl-button :disabled="!canSubmit" - category="primary" - variant="warning" + category="secondary" + variant="danger" @click="onSecondaryAction" > {{ secondaryAction }} diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9b08ca3718e..e64380f5d91 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -55,6 +55,7 @@ class CommitStatus < ApplicationRecord scope :for_ref, -> (ref) { where(ref: ref) } scope :by_name, -> (name) { where(name: name) } scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) } + scope :eager_load_pipeline, -> { eager_load(:pipeline, project: { namespace: :route }) } scope :for_project_paths, -> (paths) do where(project: Project.where_full_path_in(Array(paths))) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e1be0665452..09859fda877 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -324,7 +324,7 @@ module Issuable # This prevents errors when ignored columns are present in the database. issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*" - extra_select_columns = extra_select_columns.unshift("(#{highest_priority}) AS highest_priority") + extra_select_columns.unshift("(#{highest_priority}) AS highest_priority") select(issuable_columns) .select(extra_select_columns) diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index c30f6dc81ee..b63c2b84882 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -7,7 +7,7 @@ class GroupMember < Member SOURCE_TYPE = 'Namespace' belongs_to :group, foreign_key: 'source_id' - + alias_attribute :namespace_id, :source_id delegate :update_two_factor_requirement, to: :user # Make sure group member points only to group as it source diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 833b27756ab..9a86b3a3fd9 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -5,6 +5,8 @@ class ProjectMember < Member belongs_to :project, foreign_key: 'source_id' + delegate :namespace_id, to: :project + # Make sure project member points only to project as it source default_value_for :source_type, SOURCE_TYPE validates :source_type, format: { with: /\AProject\z/ } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4a3162c5aa6..7a63fba2835 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1350,8 +1350,8 @@ class MergeRequest < ApplicationRecord has_no_commits? || branch_missing? || cannot_be_merged? end - def can_be_merged_by?(user) - access = ::Gitlab::UserAccess.new(user, container: project) + def can_be_merged_by?(user, skip_collaboration_check: false) + access = ::Gitlab::UserAccess.new(user, container: project, skip_collaboration_check: skip_collaboration_check) access.can_update_branch?(target_branch) end diff --git a/app/models/project.rb b/app/models/project.rb index 30741e117bf..eb0382414de 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2711,7 +2711,7 @@ class Project < ApplicationRecord # Issue for N+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/49322 Gitlab::GitalyClient.allow_n_plus_1_calls do merge_requests_allowing_collaboration(branch_name).any? do |merge_request| - merge_request.can_be_merged_by?(user) + merge_request.can_be_merged_by?(user, skip_collaboration_check: true) end end end diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index 71cbe28de25..51deb6cbd1d 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -93,10 +93,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated filename_path(repository.license_blob&.name) end - def ci_configuration_path - filename_path(repository.gitlab_ci_yml&.name) - end - def contribution_guide_path if project && contribution_guide = repository.contribution_guide project_blob_path( @@ -131,10 +127,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ide_edit_path(project, default_branch_or_master, 'CONTRIBUTING.md') end - def add_ci_yml_path - add_special_file_path(file_name: ci_config_path_or_default) - end - def add_readme_path add_special_file_path(file_name: 'README.md') end @@ -384,11 +376,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated if cicd_missing? AnchorData.new(false, statistic_icon + _('Set up CI/CD'), - add_ci_yml_path) + project_ci_pipeline_editor_path(project)) elsif repository.gitlab_ci_yml.present? AnchorData.new(false, statistic_icon('doc-text') + _('CI/CD configuration'), - ci_configuration_path, + project_ci_pipeline_editor_path(project), 'btn-default') end end diff --git a/app/services/members/invite_service.rb b/app/services/members/invite_service.rb index db29f41d5f6..b865babdf3c 100644 --- a/app/services/members/invite_service.rb +++ b/app/services/members/invite_service.rb @@ -10,13 +10,14 @@ module Members @errors = {} @emails = params[:email]&.split(',')&.uniq&.flatten + @source = params[:source] end - def execute(source) - @source = source + def execute validate_emails! emails.each(&method(:process_email)) + enqueue_onboarding_progress_action result rescue BlankEmailsError, TooManyEmailsError => e error(e.message) @@ -24,7 +25,7 @@ module Members private - attr_reader :source, :errors, :emails + attr_reader :source, :errors, :emails, :member_created_namespace_id def validate_emails! raise BlankEmailsError, s_('AddMember|Email cannot be blank') if emails.blank? @@ -88,6 +89,7 @@ module Members errors[email] = new_member.errors.full_messages.to_sentence else after_execute(member: new_member) + @member_created_namespace_id ||= new_member.namespace_id end end @@ -98,6 +100,12 @@ module Members success end end + + def enqueue_onboarding_progress_action + return unless member_created_namespace_id + + Namespaces::OnboardingUserAddedWorker.perform_async(member_created_namespace_id) + end end end diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index 6ba3356d612..91632e50ba8 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -32,6 +32,8 @@ module Projects if fork_network = @project.root_of_fork_network fork_network.update(root_project: nil, deleted_root_project_name: @project.full_name) end + + @project.leave_pool_repository end # rubocop: disable Cop/InBatches diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml index 402f7ddb38d..fc4b3260ab3 100644 --- a/app/views/projects/jobs/_table.html.haml +++ b/app/views/projects/jobs/_table.html.haml @@ -12,7 +12,7 @@ = s_('Jobs|Use jobs to automate your tasks') %p = s_('Jobs|Jobs are the building blocks of a GitLab CI/CD pipeline. Each job has a specific task, like testing code. To set up jobs in a CI/CD pipeline, add a CI/CD configuration file to your project.') - = link_to s_('Jobs|Create CI/CD configuration file'), @project.present(current_user: current_user).add_ci_yml_path, class: 'btn gl-button btn-info js-empty-state-button' + = link_to s_('Jobs|Create CI/CD configuration file'), project_ci_pipeline_editor_path(project), class: 'btn gl-button btn-info js-empty-state-button' - else .nothing-here-block= s_('Jobs|No jobs to show') - else diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml index 77a304d9565..f8942dddfb4 100644 --- a/app/views/shared/form_elements/_description.html.haml +++ b/app/views/shared/form_elements/_description.html.haml @@ -1,7 +1,7 @@ - project = local_assigns.fetch(:project) - model = local_assigns.fetch(:model) - form = local_assigns.fetch(:form) -- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a comment or drag your files here…') +- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a description or drag your files here…') - supports_quick_actions = true - preview_url = preview_markdown_path(project, target_type: model.class.name) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 6a90698f1de..72ae7b22123 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -138,7 +138,7 @@ = clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport') .gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed %span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-white-space-nowrap - = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' title='#{source_branch}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: source_branch } + = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' data-testid='ref-name' title='#{html_escape(source_branch)}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: html_escape(source_branch) } = clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport') - if show_forwarding_email diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb index 77b0edfd7de..48bb1160ae8 100644 --- a/app/workers/expire_job_cache_worker.rb +++ b/app/workers/expire_job_cache_worker.rb @@ -10,7 +10,7 @@ class ExpireJobCacheWorker # rubocop: disable CodeReuse/ActiveRecord def perform(job_id) - job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id) + job = CommitStatus.eager_load_pipeline.find_by(id: job_id) return unless job pipeline = job.pipeline diff --git a/changelogs/unreleased/322449-write-description.yml b/changelogs/unreleased/322449-write-description.yml new file mode 100644 index 00000000000..5c709a2a119 --- /dev/null +++ b/changelogs/unreleased/322449-write-description.yml @@ -0,0 +1,5 @@ +--- +title: Update New Issue form description copy from 'wite a comment' to 'wite a description' +merge_request: 58068 +author: +type: changed diff --git a/changelogs/unreleased/323078-combine-concepts-in-inviting-member-service-classes-3.yml b/changelogs/unreleased/323078-combine-concepts-in-inviting-member-service-classes-3.yml new file mode 100644 index 00000000000..246477a1e6e --- /dev/null +++ b/changelogs/unreleased/323078-combine-concepts-in-inviting-member-service-classes-3.yml @@ -0,0 +1,5 @@ +--- +title: Add enqueueing of Onboarding Progress to the Invite Service +merge_request: 57372 +author: +type: other diff --git a/changelogs/unreleased/326038-delete-user-modal.yml b/changelogs/unreleased/326038-delete-user-modal.yml new file mode 100644 index 00000000000..e1c3a31d3aa --- /dev/null +++ b/changelogs/unreleased/326038-delete-user-modal.yml @@ -0,0 +1,5 @@ +--- +title: Deprecate btn-warning on admin area delete user modal +merge_request: 57761 +author: +type: changed diff --git a/changelogs/unreleased/add-cloud-column-to-licenses.yml b/changelogs/unreleased/add-cloud-column-to-licenses.yml new file mode 100644 index 00000000000..21cd9c8addf --- /dev/null +++ b/changelogs/unreleased/add-cloud-column-to-licenses.yml @@ -0,0 +1,5 @@ +--- +title: 'Migration: Add cloud column to licenses' +merge_request: 57781 +author: +type: added diff --git a/changelogs/unreleased/dz-redirect-deprecated-pipeline-routes.yml b/changelogs/unreleased/dz-redirect-deprecated-pipeline-routes.yml new file mode 100644 index 00000000000..3ed396213bd --- /dev/null +++ b/changelogs/unreleased/dz-redirect-deprecated-pipeline-routes.yml @@ -0,0 +1,5 @@ +--- +title: Redirect deprecated pipeline routes +merge_request: 53990 +author: +type: removed diff --git a/changelogs/unreleased/mimemagic_shim.yml b/changelogs/unreleased/mimemagic_shim.yml deleted file mode 100644 index 0376122f0ce..00000000000 --- a/changelogs/unreleased/mimemagic_shim.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Switch to using a fake mimemagic gem -merge_request: 57443 -author: -type: other diff --git a/changelogs/unreleased/pl-rubocop-todo-redundant-self-assignment.yml b/changelogs/unreleased/pl-rubocop-todo-redundant-self-assignment.yml new file mode 100644 index 00000000000..ad20006b98f --- /dev/null +++ b/changelogs/unreleased/pl-rubocop-todo-redundant-self-assignment.yml @@ -0,0 +1,5 @@ +--- +title: Fixes rubocop offenses Style/RedundantSelfAssignment +merge_request: 57920 +author: Shubham Kumar (@imskr) +type: fixed diff --git a/changelogs/unreleased/query_count_expire_job_cache_worker.yml b/changelogs/unreleased/query_count_expire_job_cache_worker.yml new file mode 100644 index 00000000000..8279aa4174a --- /dev/null +++ b/changelogs/unreleased/query_count_expire_job_cache_worker.yml @@ -0,0 +1,5 @@ +--- +title: Reduce query count for popular worker ExpireJobCacheWorker +merge_request: 57773 +author: +type: performance diff --git a/changelogs/unreleased/redirect-cicd-quick-buttons-to-pipeline-editor.yml b/changelogs/unreleased/redirect-cicd-quick-buttons-to-pipeline-editor.yml new file mode 100644 index 00000000000..c04ef8baf23 --- /dev/null +++ b/changelogs/unreleased/redirect-cicd-quick-buttons-to-pipeline-editor.yml @@ -0,0 +1,5 @@ +--- +title: Redirect to the pipeline editor when clicking on CI/CD quick links +merge_request: 57085 +author: +type: changed diff --git a/changelogs/unreleased/remove-direct-mimemagic-dependency-minimal.yml b/changelogs/unreleased/remove-direct-mimemagic-dependency-minimal.yml deleted file mode 100644 index 727887f7e7b..00000000000 --- a/changelogs/unreleased/remove-direct-mimemagic-dependency-minimal.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactor MimeMagic calls to new MimeType class -merge_request: 57421 -author: -type: other diff --git a/changelogs/unreleased/remove-direct-mimemagic-dependency.yml b/changelogs/unreleased/remove-direct-mimemagic-dependency.yml deleted file mode 100644 index 5194dfdf751..00000000000 --- a/changelogs/unreleased/remove-direct-mimemagic-dependency.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove direct mimemagic dependency -merge_request: 57387 -author: -type: other diff --git a/changelogs/unreleased/remove_hipchat_gem.yml b/changelogs/unreleased/remove_hipchat_gem.yml deleted file mode 100644 index 21a5db3bb5a..00000000000 --- a/changelogs/unreleased/remove_hipchat_gem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make HipChat project service do nothing -merge_request: 57434 -author: -type: removed diff --git a/changelogs/unreleased/sh-update-ruby-magic-static-0-3-5.yml b/changelogs/unreleased/sh-update-ruby-magic-static-0-3-5.yml new file mode 100644 index 00000000000..1dc44618898 --- /dev/null +++ b/changelogs/unreleased/sh-update-ruby-magic-static-0-3-5.yml @@ -0,0 +1,5 @@ +--- +title: Update ruby-magic-static to v0.3.5 +merge_request: 57984 +author: +type: changed diff --git a/config/initializers/asciidoctor_patch.rb b/config/initializers/asciidoctor_patch.rb new file mode 100644 index 00000000000..b7da50db77c --- /dev/null +++ b/config/initializers/asciidoctor_patch.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Ensure that locked attributes can not be changed using a counter. +# TODO: this can be removed once `asciidoctor` gem is > 2.0.12 +# and https://github.com/asciidoctor/asciidoctor/issues/3939 is merged +module Asciidoctor + module DocumentPatch + def counter(name, seed = nil) + return @parent_document.counter(name, seed) if @parent_document # rubocop: disable Gitlab/ModuleWithInstanceVariables + + unless attribute_locked? name + super + end + end + end +end + +class Asciidoctor::Document + prepend Asciidoctor::DocumentPatch +end diff --git a/config/routes/project.rb b/config/routes/project.rb index bfe2a0a78f1..363d23590bf 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -553,7 +553,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do # Deprecated unscoped routing. scope as: 'deprecated' do # Issue https://gitlab.com/gitlab-org/gitlab/issues/118849 - draw :pipelines draw :repository # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/29572 @@ -576,7 +575,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do :environments, :protected_environments, :error_tracking, :alert_management, :tracing, :serverless, :clusters, :audit_events, :wikis, :merge_requests, - :vulnerability_feedback, :security, :dependencies, :issues) + :vulnerability_feedback, :security, :dependencies, :issues, + :pipelines, :pipeline_schedules) end # rubocop: disable Cop/PutProjectRoutesUnderScope diff --git a/db/migrate/20210330015805_add_cloud_to_licenses.rb b/db/migrate/20210330015805_add_cloud_to_licenses.rb new file mode 100644 index 00000000000..d0c7112d0b0 --- /dev/null +++ b/db/migrate/20210330015805_add_cloud_to_licenses.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddCloudToLicenses < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :licenses, :cloud, :boolean, default: false + end +end diff --git a/db/schema_migrations/20210330015805 b/db/schema_migrations/20210330015805 new file mode 100644 index 00000000000..14102a0a2d1 --- /dev/null +++ b/db/schema_migrations/20210330015805 @@ -0,0 +1 @@ +a435a211d7e8b9a972323769299fc6e537fdeaa127f8db6ab53031901a51ec36
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3ff4d895aa6..01bbd00bd44 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14182,7 +14182,8 @@ CREATE TABLE licenses ( id integer NOT NULL, data text NOT NULL, created_at timestamp without time zone, - updated_at timestamp without time zone + updated_at timestamp without time zone, + cloud boolean DEFAULT false ); CREATE SEQUENCE licenses_id_seq diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md index 1da52e461af..9565b7594d6 100644 --- a/doc/administration/troubleshooting/postgresql.md +++ b/doc/administration/troubleshooting/postgresql.md @@ -35,7 +35,7 @@ This section is for links to information elsewhere in the GitLab documentation. - Storing data in another location. - Destructively reseeding the GitLab database. - Guidance around updating packaged PostgreSQL, including how to stop it - happening automatically. + from happening automatically. - [Information about external PostgreSQL](../postgresql/external.md). diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index 855436864cc..3348157129d 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -88,7 +88,7 @@ Example response: ## Test system hook ```plaintext -GET /hooks/:id +POST /hooks/:id ``` | Attribute | Type | Required | Description | @@ -98,7 +98,7 @@ GET /hooks/:id Example request: ```shell -curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/hooks/2" +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/hooks/1" ``` Example response: diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md index 413c0a31eec..f6390d1c8ef 100644 --- a/doc/development/dangerbot.md +++ b/doc/development/dangerbot.md @@ -150,16 +150,13 @@ at GitLab so far: ## Limitations -- Danger output is not added to a merge request comment if working on - a fork. This happens because the secret variable from the canonical - project is not shared to forks. - To work around this, you can add an [environment - variable](../ci/variables/README.md) called - `DANGER_GITLAB_API_TOKEN` with a personal API token to your - fork. That way the danger comments are made from CI using that - API token instead. - Making the variable - [masked](../ci/variables/README.md#mask-a-custom-variable) makes sure - it doesn't show up in the job logs. The variable cannot be - [protected](../ci/variables/README.md#protect-a-custom-variable), - as it needs to be present for all feature branches. +Danger is run but its output is not added to a merge request comment if working +on a fork. This happens because the secret variable from the canonical project +is not shared to forks. To work around this, you can add an [environment +variable](../ci/variables/README.md) called `DANGER_GITLAB_API_TOKEN` with a +personal API token to your fork. That way the danger comments are made from CI +using that API token instead. Making the variable +[masked](../ci/variables/README.md#mask-a-custom-variable) makes sure it +doesn't show up in the job logs. The variable cannot be +[protected](../ci/variables/README.md#protect-a-custom-variable), as it needs +to be present for all feature branches. diff --git a/doc/development/usage_ping/index.md b/doc/development/usage_ping/index.md index 91d5898d97f..c41510fa98a 100644 --- a/doc/development/usage_ping/index.md +++ b/doc/development/usage_ping/index.md @@ -896,18 +896,56 @@ On GitLab.com, we have DangerBot setup to monitor Product Intelligence related f On GitLab.com, the Product Intelligence team regularly monitors Usage Ping. They may alert you that your metrics need further optimization to run quicker and with greater success. You may also use the [Usage Ping QA dashboard](https://app.periscopedata.com/app/gitlab/632033/Usage-Ping-QA) to check how well your metric performs. The dashboard allows filtering by GitLab version, by "Self-managed" & "SaaS" and shows you how many failures have occurred for each metric. Whenever you notice a high failure rate, you may re-optimize your metric. -### Optional: Test Prometheus based Usage Ping +### Usage Ping local setup -If the data submitted includes metrics [queried from Prometheus](#prometheus-queries) that you would like to inspect and verify, -then you need to ensure that a Prometheus server is running locally, and that furthermore the respective GitLab components -are exporting metrics to it. If you do not need to test data coming from Prometheus, no further action +To set up Usage Ping locally, you must: + +1. [Set up local repositories]#(set-up-local-repositories) +1. [Test local setup](#test-local-setup) +1. (Optional) [Test Prometheus-based usage ping](#test-prometheus-based-usage-ping) + +#### Set up local repositories + +1. Clone and start [GitLab](https://gitlab.com/gitlab-org/gitlab-development-kit). +1. Clone and start [Versions Application](https://gitlab.com/gitlab-services/version-gitlab-com). + Make sure to run `docker-compose up` to start a PostgreSQL and Redis instance. +1. Point GitLab to the Versions Application endpoint instead of the default endpoint: + 1. Open [submit_usage_ping_service.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/submit_usage_ping_service.rb#L4) in your local and modified `PRODUCTION_URL`. + 1. Set it to the local Versions Application URL `http://localhost:3000/usage_data`. + +#### Test local setup + +1. Using the `gitlab` Rails console, manually trigger a usage ping: + + ```ruby + SubmitUsagePingService.new.execute + ``` + +1. Use the `versions` Rails console to check the usage ping was successfully received, + parsed, and stored in the Versions database: + + ```ruby + UsageData.last + ``` + +### Test Prometheus-based usage ping + +If the data submitted includes metrics [queried from Prometheus](#prometheus-queries) +you want to inspect and verify, you must: + +- Ensure that a Prometheus server is running locally. +- Ensure the respective GitLab components are exporting metrics to the Prometheus server. + +If you do not need to test data coming from Prometheus, no further action is necessary. Usage Ping should degrade gracefully in the absence of a running Prometheus server. -There are three kinds of components that may export data to Prometheus, and which are included in Usage Ping: +Three kinds of components may export data to Prometheus, and are included in Usage Ping: -- [`node_exporter`](https://github.com/prometheus/node_exporter) - Exports node metrics from the host machine -- [`gitlab-exporter`](https://gitlab.com/gitlab-org/gitlab-exporter) - Exports process metrics from various GitLab components -- various GitLab services such as Sidekiq and the Rails server that export their own metrics +- [`node_exporter`](https://github.com/prometheus/node_exporter): Exports node metrics + from the host machine. +- [`gitlab-exporter`](https://gitlab.com/gitlab-org/gitlab-exporter): Exports process metrics + from various GitLab components. +- Other various GitLab services, such as Sidekiq and the Rails server, which export their own metrics. #### Test with an Omnibus container diff --git a/doc/user/project/integrations/jira_integrations.md b/doc/user/project/integrations/jira_integrations.md index 7cc3be88f09..1f895a9e2fa 100644 --- a/doc/user/project/integrations/jira_integrations.md +++ b/doc/user/project/integrations/jira_integrations.md @@ -6,30 +6,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Jira integrations **(FREE)** -GitLab can be integrated with [Jira](https://www.atlassian.com/software/jira). +If your organization uses [Jira](https://www.atlassian.com/software/jira) issues, +you can [migrate](../../../user/project/import/jira.md) your issues from Jira and work +exclusively in GitLab. -[Issues](../issues/index.md) are a tool for discussing ideas, and planning and tracking work. -However, your organization may already use Jira for these purposes, with extensive, established data -and business processes they rely on. +However, if you'd like to continue to use Jira, you can integrate it with GitLab. -Although you can [migrate](../../../user/project/import/jira.md) your Jira issues and work -exclusively in GitLab, you can also continue to use Jira by using the GitLab Jira integrations. +There are two ways to use GitLab with Jira: -## Integration types - -There are two different Jira integrations that allow different types of cross-referencing between -GitLab activity and Jira issues, with additional features: - -- [Jira integration](jira.md), built in to GitLab. In a given GitLab project, it can be configured - to connect to any Jira instance, either hosted by you or hosted in - [Atlassian cloud](https://www.atlassian.com/cloud). -- [Jira development panel integration](../../../integration/jira/index.md). Connects all - GitLab projects under a specified group or personal namespace. - -Jira development panel integration configuration depends on whether: - -- You're using GitLab.com or a self-managed GitLab instance. -- You're using Jira on [Atlassian cloud](https://www.atlassian.com/cloud) or on your own server. +- [Jira integration](jira.md). Connect a GitLab project + to a Jira instance. The Jira instance can be hosted by you or in [Atlassian cloud](https://www.atlassian.com/cloud). +- [Jira Development panel integration](../../../integration/jira_development_panel.md). + Connect all GitLab projects under a group or personal namespace. The integration you choose depends on the capabilities you require. You can also install both at the same time. diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index 52c32b4d1cf..0d562cc18f8 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -25,11 +25,11 @@ module API optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' end post ":id/invitations" do - source = find_source(source_type, params[:id]) + params[:source] = find_source(source_type, params[:id]) - authorize_admin_source!(source_type, source) + authorize_admin_source!(source_type, params[:source]) - ::Members::InviteService.new(current_user, params).execute(source) + ::Members::InviteService.new(current_user, params).execute end desc 'Get a list of group or project invitations viewable by the authenticated user' do diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 42e16d47a0b..fe23a111b7f 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -47,7 +47,7 @@ module API params do requires :id, type: Integer, desc: 'The ID of the system hook' end - get ":id" do + post ":id" do hook = SystemHook.find(params[:id]) data = { event_name: "project_create", diff --git a/lib/gitlab/ci/pipeline/chain/validate/external.rb b/lib/gitlab/ci/pipeline/chain/validate/external.rb index 70a5b3adcea..0212a8fcc6f 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/external.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/external.rb @@ -69,8 +69,12 @@ module Gitlab end def validate_service_request + headers = {} + headers['X-Gitlab-Token'] = validation_service_token if validation_service_token + Gitlab::HTTP.post( validation_service_url, timeout: validation_service_timeout, + headers: headers, body: validation_service_payload.to_json ) end @@ -86,6 +90,10 @@ module Gitlab ENV['EXTERNAL_VALIDATION_SERVICE_URL'] end + def validation_service_token + ENV['EXTERNAL_VALIDATION_SERVICE_TOKEN'] + end + def validation_service_payload { project: { diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb index 36e9a6ccef6..3ec5f2339b5 100644 --- a/lib/gitlab/markdown_cache.rb +++ b/lib/gitlab/markdown_cache.rb @@ -3,7 +3,7 @@ module Gitlab module MarkdownCache # Increment this number every time the renderer changes its output - CACHE_COMMONMARK_VERSION = 26 + CACHE_COMMONMARK_VERSION = 27 CACHE_COMMONMARK_VERSION_START = 10 BaseError = Class.new(StandardError) diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 0af7ad6ec17..a4a1cccf9d5 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -11,10 +11,11 @@ module Gitlab attr_reader :user, :push_ability attr_accessor :container - def initialize(user, container: nil, push_ability: :push_code) + def initialize(user, container: nil, push_ability: :push_code, skip_collaboration_check: false) @user = user @container = container @push_ability = push_ability + @skip_collaboration_check = skip_collaboration_check end def can_do_action?(action) @@ -87,6 +88,8 @@ module Gitlab private + attr_reader :skip_collaboration_check + def can_push? user.can?(push_ability, container) end @@ -98,6 +101,8 @@ module Gitlab end def branch_allows_collaboration_for?(ref) + return false if skip_collaboration_check + # Checking for an internal project or group to prevent an infinite loop: # https://gitlab.com/gitlab-org/gitlab/issues/36805 (!project.internal? && project.branch_allows_collaboration?(user, ref)) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fce27db1a97..a197e17b164 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34656,6 +34656,9 @@ msgstr "" msgid "Write a comment…" msgstr "" +msgid "Write a description or drag your files here…" +msgstr "" + msgid "Write milestone description..." msgstr "" diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index a0b394562de..23502b2ee59 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -166,6 +166,26 @@ module QA end end + # Method for selecting radios + def choose_element(name, click_by_js = false) + if find_element(name, visible: false).checked? + QA::Runtime::Logger.debug("#{name} is already selected") + + return + end + + retry_until(sleep_interval: 1) do + radio = find_element(name, visible: false) + # Some radio buttons are hidden by their labels and cannot be clicked directly + click_by_js ? page.execute_script("arguments[0].click();", radio) : radio.click + selected = find_element(name, visible: false).checked? + + QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected") + + selected + end + end + # Use this to simulate moving the pointer to an element's coordinate # and sending a click event. # This is a helpful workaround when there is a transparent element overlapping diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index fe5d629effe..0b4a12dbb2e 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -20,11 +20,11 @@ module QA end def click_save_changes - click_element :save_merge_request_changes_button + click_element(:save_merge_request_changes_button) end def enable_ff_only - click_element :merge_ff_radio_button + click_element(:merge_ff_radio_button) click_save_changes end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index b27a8192a64..1dbbc052e11 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -81,7 +81,7 @@ module QA end def fill_element(name, content) - masked_content = name.to_s.include?('password') ? '*****' : content + masked_content = name.to_s.match?(/token|key|password/) ? '*****' : content log(%Q(filling :#{name} with "#{masked_content}")) diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml index 01a94fcee21..7daf3f80efc 100644 --- a/scripts/review_apps/base-config.yaml +++ b/scripts/review_apps/base-config.yaml @@ -136,8 +136,10 @@ postgresql: metrics: enabled: false resources: + requests: cpu: 600m memory: 1000M + limits: cpu: 1300m memory: 1500M prometheus: diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 0d8407dd7fa..0c44dc2a87a 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -114,7 +114,7 @@ RSpec.describe 'Database schema' do # postgres and mysql both automatically create an index on the primary # key. Also, the rails connection.indexes() method does not return # automatically generated indexes (like the primary key index). - first_indexed_column = first_indexed_column.push(primary_key_column) + first_indexed_column.push(primary_key_column) expect(first_indexed_column.uniq).to include(*foreign_keys_columns) end diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb index f0905d28c70..f3f3e33189b 100644 --- a/spec/factories/pool_repositories.rb +++ b/spec/factories/pool_repositories.rb @@ -6,7 +6,7 @@ FactoryBot.define do state { :none } before(:create) do |pool| - pool.source_project = create(:project, :repository) + pool.source_project ||= create(:project, :repository) pool.source_project.update!(pool_repository: pool) end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 6e380bdbc0a..e2e204f03db 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -45,7 +45,7 @@ RSpec.describe "User creates issue" do .and have_no_content("Milestone") expect(page.find('#issue_title')['placeholder']).to eq 'Title' - expect(page.find('#issue_description')['placeholder']).to eq 'Write a comment or drag your files here…' + expect(page.find('#issue_description')['placeholder']).to eq 'Write a description or drag your files here…' end issue_title = "500 error on profile" diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 9bda48a3ec5..f1b44010f63 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -111,4 +111,21 @@ RSpec.describe 'User views an open merge request' do end end end + + context 'XSS source branch' do + let(:project) { create(:project, :public, :repository) } + let(:source_branch) { "'><iframe/srcdoc=''></iframe>" } + + before do + project.repository.create_branch(source_branch, "master") + + mr = create(:merge_request, source_project: project, target_project: project, source_branch: source_branch) + + visit(merge_request_path(mr)) + end + + it 'encodes branch name' do + expect(find("[data-testid='ref-name']")[:title]).to eq(source_branch) + end + end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 7811394b541..35ca11cb02b 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do it 'shows the empty state page' do expect(page).to have_content('Use jobs to automate your tasks') - expect(page).to have_link('Create CI/CD configuration file', href: project.present(current_user: user).add_ci_yml_path) + expect(page).to have_link('Create CI/CD configuration file', href: project_ci_pipeline_editor_path(project)) end end diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index 9b51e867156..dc551158895 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -226,11 +226,11 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.gitlab_ci_yml).to be_nil page.within('.project-buttons') do - expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path) + expect(page).to have_link('Set up CI/CD', href: project_ci_pipeline_editor_path(project)) end end - it 'no "Set up CI/CD" button if the project already has a .gitlab-ci.yml' do + it '"Set up CI/CD" button is renamed if the project already has a .gitlab-ci.yml' do Files::CreateService.new( project, project.creator, @@ -247,6 +247,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do page.within('.project-buttons') do expect(page).not_to have_link('Set up CI/CD') + expect(page).to have_link('CI/CD configuration') end end end diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap index ddeaa2a79db..9f02e5b9432 100644 --- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap +++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap @@ -50,11 +50,11 @@ exports[`User Operation confirmation modal renders modal with form included 1`] <gl-button-stub buttontextclasses="" - category="primary" + category="secondary" disabled="true" icon="" size="medium" - variant="warning" + variant="danger" > secondaryAction diff --git a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js index c7293b00adf..318b6d16008 100644 --- a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js +++ b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js @@ -11,15 +11,15 @@ describe('User Operation confirmation modal', () => { let wrapper; let formSubmitSpy; - const findButton = (variant) => + const findButton = (variant, category) => wrapper .findAll(GlButton) - .filter((w) => w.attributes('variant') === variant) + .filter((w) => w.attributes('variant') === variant && w.attributes('category') === category) .at(0); const findForm = () => wrapper.find('form'); const findUsernameInput = () => wrapper.find(GlFormInput); - const findPrimaryButton = () => findButton('danger'); - const findSecondaryButton = () => findButton('warning'); + const findPrimaryButton = () => findButton('danger', 'primary'); + const findSecondaryButton = () => findButton('danger', 'secondary'); const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token'); const getUsername = () => findUsernameInput().attributes('value'); const getMethodParam = () => new FormData(findForm().element).get('_method'); diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 08510d4652b..3eb015a5a22 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -92,6 +92,15 @@ module Gitlab expect(render(data[:input], context)).to include(data[:output]) end end + + it 'does not allow locked attributes to be overridden' do + input = <<~ADOC + {counter:max-include-depth:1234} + <|-- {max-include-depth} + ADOC + + expect(render(input, {})).not_to include('1234') + end end context "images" do @@ -543,6 +552,40 @@ module Gitlab expect(render(input, context)).to include(output.strip) end + + it 'does not allow kroki-plantuml-include to be overridden' do + input = <<~ADOC + [plantuml, test="{counter:kroki-plantuml-include:/etc/passwd}", format="png"] + .... + class BlockProcessor + + BlockProcessor <|-- {counter:kroki-plantuml-include} + .... + ADOC + + output = <<~HTML + <div> + <div> + <a class=\"no-attachment-icon\" href=\"https://kroki.io/plantuml/png/eNpLzkksLlZwyslPzg4oyk9OLS7OL-LiQuUr2NTo6ipUJ-eX5pWkFlllF-VnZ-oW5CTmlZTm5uhm5iXnlKak1gIABQEb8A==\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Diagram\" class=\"lazy\" data-src=\"https://kroki.io/plantuml/png/eNpLzkksLlZwyslPzg4oyk9OLS7OL-LiQuUr2NTo6ipUJ-eX5pWkFlllF-VnZ-oW5CTmlZTm5uhm5iXnlKak1gIABQEb8A==\"></a> + </div> + </div> + HTML + + expect(render(input, {})).to include(output.strip) + end + + it 'does not allow kroki-server-url to be overridden' do + input = <<~ADOC + [plantuml, test="{counter:kroki-server-url:evilsite}", format="png"] + .... + class BlockProcessor + + BlockProcessor + .... + ADOC + + expect(render(input, {})).not_to include('evilsite') + end end context 'with Kroki and BlockDiag (additional format) enabled' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index cb0671f02c6..1fd40b454ff 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -63,6 +63,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do expect(::Gitlab::HTTP).to receive(:post) do |_url, params| expect(params[:body]).to match_schema('/external_validation') expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT) + expect(params[:headers]).to eq({}) end perform! @@ -119,6 +120,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end end + context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do + before do + stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', '123') + end + + it 'passes token in X-Gitlab-Token header' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + expect(params[:headers]).to eq({ 'X-Gitlab-Token' => '123' }) + end + + perform! + end + end + context 'when validation returns 200 OK' do before do stub_request(:post, validation_service_url).to_return(status: 200, body: "{}") diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index 97fff030906..01890305df4 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -216,6 +216,15 @@ RSpec.describe Gitlab::UserAccess do expect(access.can_merge_to_branch?(@branch.name)).to be_falsey end end + + context 'when skip_collaboration_check is true' do + let(:access) { described_class.new(user, container: project, skip_collaboration_check: true) } + + it 'does not call Project#branch_allows_collaboration?' do + expect(project).not_to receive(:branch_allows_collaboration?) + expect(access.can_push_to_branch?('master')).to be_falsey + end + end end describe '#can_create_tag?' do diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 3d3ed6fc54a..908bb9f91a3 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -66,6 +66,12 @@ RSpec.describe GroupMember do it_behaves_like 'members notifications', :group + describe '#namespace_id' do + subject { build(:group_member, source_id: 1).namespace_id } + + it { is_expected.to eq 1 } + end + describe '#real_source_type' do subject { create(:group_member).real_source_type } diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 388d04c8012..ce3e86f964d 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -13,6 +13,10 @@ RSpec.describe ProjectMember do it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) } end + describe 'delegations' do + it { is_expected.to delegate_method(:namespace_id).to(:project) } + end + describe '.access_level_roles' do it 'returns Gitlab::Access.options' do expect(described_class.access_level_roles).to eq(Gitlab::Access.options) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6c95fb6c52e..b99ae8f0642 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5319,6 +5319,64 @@ RSpec.describe Project, factory_default: :keep do end end + describe '#branch_allows_collaboration?' do + context 'when there are open merge requests that have their source/target branches point to each other' do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:guest) { create(:user) } + + before_all do + create( + :merge_request, + target_project: project, + target_branch: 'master', + source_project: project, + source_branch: 'merge-test', + allow_collaboration: true + ) + + create( + :merge_request, + target_project: project, + target_branch: 'merge-test', + source_project: project, + source_branch: 'master', + allow_collaboration: true + ) + + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + shared_examples_for 'successful check' do + it 'does not go into an infinite loop' do + expect { project.branch_allows_collaboration?(user, 'master') } + .not_to raise_error + end + end + + context 'when user is a developer' do + let(:user) { developer } + + it_behaves_like 'successful check' + end + + context 'when user is a reporter' do + let(:user) { reporter } + + it_behaves_like 'successful check' + end + + context 'when user is a guest' do + let(:user) { guest } + + it_behaves_like 'successful check' + end + end + end + context 'with cross project merge requests' do let(:user) { create(:user) } let(:target_project) { create(:project, :repository) } diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 01b46053d52..3cea1af686e 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -103,15 +103,15 @@ RSpec.describe API::SystemHooks do end end - describe "GET /hooks/:id" do - it "returns hook by id" do - get api("/hooks/#{hook.id}", admin) - expect(response).to have_gitlab_http_status(:ok) + describe 'POST /hooks/:id' do + it "returns and trigger hook by id" do + post api("/hooks/#{hook.id}", admin) + expect(response).to have_gitlab_http_status(:created) expect(json_response['event_name']).to eq('project_create') end it "returns 404 on failure" do - get api("/hooks/404", admin) + post api("/hooks/404", admin) expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 80065eb97dd..056f4d30ea5 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -684,6 +684,26 @@ RSpec.describe 'project routing' do end end + describe Projects::PipelinesController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/-/pipelines')).to route_to('projects/pipelines#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/-/pipelines/12')).to route_to('projects/pipelines#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '12') + end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipelines', '/gitlab/gitlabhq/-/pipelines' + end + + describe Projects::PipelineSchedulesController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/-/pipeline_schedules')).to route_to('projects/pipeline_schedules#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipeline_schedules', '/gitlab/gitlabhq/-/pipeline_schedules' + end + describe Projects::Settings::OperationsController, 'routing' do it 'to #reset_alerting_token' do expect(post('/gitlab/gitlabhq/-/settings/operations/reset_alerting_token')).to route_to('projects/settings/operations#reset_alerting_token', namespace_id: 'gitlab', project_id: 'gitlabhq') diff --git a/spec/services/members/invite_service_spec.rb b/spec/services/members/invite_service_spec.rb index cced93896a5..82793d2e73c 100644 --- a/spec/services/members/invite_service_spec.rb +++ b/spec/services/members/invite_service_spec.rb @@ -2,29 +2,43 @@ require 'spec_helper' -RSpec.describe Members::InviteService, :aggregate_failures do +RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline do let_it_be(:project) { create(:project) } let_it_be(:user) { project.owner } let_it_be(:project_user) { create(:user) } + let_it_be(:namespace) { project.namespace } let(:params) { {} } - let(:base_params) { { access_level: Gitlab::Access::GUEST } } + let(:base_params) { { access_level: Gitlab::Access::GUEST, source: project } } - subject(:result) { described_class.new(user, base_params.merge(params)).execute(project) } + subject(:result) { described_class.new(user, base_params.merge(params) ).execute } - context 'when email is previously unused by current members' do + context 'when there is a valid member invited' do let(:params) { { email: 'email@example.org' } } it 'successfully creates a member' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:success) end + + it_behaves_like 'records an onboarding progress action', :user_added + end + + context 'when email is not a valid email' do + let(:params) { { email: '_bogus_' } } + + it 'returns an error' do + expect_not_to_create_members + expect(result[:message]['_bogus_']).to eq("Invite email is invalid") + end + + it_behaves_like 'does not record an onboarding progress action' end context 'when emails are passed as an array' do let(:params) { { email: %w[email@example.org email2@example.org] } } it 'successfully creates members' do - expect { result }.to change(ProjectMember, :count).by(2) + expect_to_create_members(count: 2) expect(result[:status]).to eq(:success) end end @@ -33,33 +47,23 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: '' } } it 'returns an error' do - expect(result[:status]).to eq(:error) + expect_not_to_create_members expect(result[:message]).to eq('Email cannot be blank') end end context 'when email param is not included' do it 'returns an error' do - expect(result[:status]).to eq(:error) + expect_not_to_create_members expect(result[:message]).to eq('Email cannot be blank') end end - context 'when email is not a valid email' do - let(:params) { { email: '_bogus_' } } - - it 'returns an error' do - expect { result }.not_to change(ProjectMember, :count) - expect(result[:status]).to eq(:error) - expect(result[:message]['_bogus_']).to eq("Invite email is invalid") - end - end - context 'when duplicate email addresses are passed' do let(:params) { { email: 'email@example.org,email@example.org' } } it 'only creates one member per unique address' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:success) end end @@ -71,8 +75,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: emails } } it 'limits the number of emails to 100' do - expect { result }.not_to change(ProjectMember, :count) - expect(result[:status]).to eq(:error) + expect_not_to_create_members expect(result[:message]).to eq('Too many users specified (limit is 100)') end end @@ -81,8 +84,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: 'email@example.org,email2@example.org', limit: 1 } } it 'limits the number of emails to the limit supplied' do - expect { result }.not_to change(ProjectMember, :count) - expect(result[:status]).to eq(:error) + expect_not_to_create_members expect(result[:message]).to eq('Too many users specified (limit is 1)') end end @@ -91,7 +93,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: emails, limit: -1 } } it 'does not limit number of emails' do - expect { result }.to change(ProjectMember, :count).by(101) + expect_to_create_members(count: 101) expect(result[:status]).to eq(:success) end end @@ -101,7 +103,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: project_user.email } } it 'adds an existing user to members' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:success) expect(project.users).to include project_user end @@ -111,8 +113,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: project_user.email, access_level: -1 } } it 'returns an error' do - expect { result }.not_to change(ProjectMember, :count) - expect(result[:status]).to eq(:error) + expect_not_to_create_members expect(result[:message][project_user.email]).to eq("Access level is not included in the list") end end @@ -122,7 +123,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: "#{invited_member.invite_email},#{project_user.email}" } } it 'adds new email and returns an error for the already invited email' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:error) expect(result[:message][invited_member.invite_email]).to eq("Member already invited to #{project.name}") expect(project.users).to include project_user @@ -134,7 +135,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: "#{requested_member.user.email},#{project_user.email}" } } it 'adds new email and returns an error for the already invited email' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:error) expect(result[:message][requested_member.user.email]) .to eq("Member cannot be invited because they already requested to join #{project.name}") @@ -147,10 +148,19 @@ RSpec.describe Members::InviteService, :aggregate_failures do let(:params) { { email: "#{existing_member.user.email},#{project_user.email}" } } it 'adds new email and returns an error for the already invited email' do - expect { result }.to change(ProjectMember, :count).by(1) + expect_to_create_members(count: 1) expect(result[:status]).to eq(:error) expect(result[:message][existing_member.user.email]).to eq("Already a member of #{project.name}") expect(project.users).to include project_user end end + + def expect_to_create_members(count:) + expect { result }.to change(ProjectMember, :count).by(count) + end + + def expect_not_to_create_members + expect { result }.not_to change(ProjectMember, :count) + expect(result[:status]).to eq(:error) + end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index df02f8ea15d..276656656ec 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -403,7 +403,7 @@ RSpec.describe Projects::ForkService do end context 'when forking with object pools' do - let(:fork_from_project) { create(:project, :public) } + let(:fork_from_project) { create(:project, :repository, :public) } let(:forker) { create(:user) } context 'when no pool exists' do diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 2a8965e62ce..90def365fca 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -207,6 +207,17 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin end end + context 'a project with pool repository' do + let(:project) { create(:project, :public, :repository) } + let!(:pool_repository) { create(:pool_repository, :ready, source_project: project) } + + subject { described_class.new(project, user) } + + it 'when unlinked leaves pool repository' do + expect { subject.execute }.to change { project.reload.has_pool_repository? }.from(true).to(false) + end + end + context 'when given project is not part of a fork network' do let!(:project_without_forks) { create(:project, :public) } diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb index 95c54a762a4..8efead31a42 100644 --- a/spec/workers/expire_job_cache_worker_spec.rb +++ b/spec/workers/expire_job_cache_worker_spec.rb @@ -8,7 +8,8 @@ RSpec.describe ExpireJobCacheWorker do describe '#perform' do context 'with a job in the pipeline' do - let(:job) { create(:ci_build, pipeline: pipeline) } + let_it_be(:job) { create(:ci_build, pipeline: pipeline) } + let(:job_args) { job.id } include_examples 'an idempotent worker' do @@ -31,6 +32,24 @@ RSpec.describe ExpireJobCacheWorker do subject end end + + it 'does not perform extra queries', :aggregate_failures do + worker = described_class.new + recorder = ActiveRecord::QueryRecorder.new { worker.perform(job.id) } + + occurences = recorder.data.values.flat_map {|v| v[:occurrences]} + project_queries = occurences.select {|s| s.include?('FROM "projects"')} + namespace_queries = occurences.select {|s| s.include?('FROM "namespaces"')} + route_queries = occurences.select {|s| s.include?('FROM "routes"')} + + # This worker is run 1 million times an hour, so we need to save as much + # queries as possible. + expect(recorder.count).to be <= 1 + + expect(project_queries.size).to eq(0) + expect(namespace_queries.size).to eq(0) + expect(route_queries.size).to eq(0) + end end context 'when there is no job in the pipeline' do diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 259148fa18f..259148fa18f 100644..100755 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore index a1c2a238a96..a1c2a238a96 100644..100755 --- a/vendor/gitignore/Java.gitignore +++ b/vendor/gitignore/Java.gitignore |