From 5f0e3773e9695fd0c9e92ea9180c8a1f5cfaa5c5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 6 May 2020 21:10:00 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../edit/components/integration_form.vue | 42 +++++ .../edit/components/jira_trigger_fields.vue | 99 +++++++++++ .../javascripts/integrations/edit/index.js | 37 +++- .../admin/application_settings_controller.rb | 2 +- app/helpers/x509_helper.rb | 4 + app/models/application_setting.rb | 2 +- app/models/merge_request_diff.rb | 8 +- app/models/project_services/jira_service.rb | 61 ++++++- app/models/snippet_repository.rb | 18 +- app/models/x509_commit_signature.rb | 4 + .../projects/commit/_signature.html.haml | 2 +- .../commit/_signature_badge.html.haml | 6 +- .../x509/_signature_badge_user.html.haml | 2 +- app/views/projects/tags/_tag.html.haml | 3 + app/views/projects/tags/show.html.haml | 2 + app/views/shared/_service_settings.html.haml | 21 +-- ...ra-comment-details-include-branch-name.yml | 5 + ...-fix-mr-diffs-external-diffs-store-bug.yml | 5 + changelogs/unreleased/feat-x509-tag.yml | 5 + .../sh-handle-deployments-no-pods.yml | 5 + .../switch-thread-to-process-memory-cache.yml | 5 + config/environments/test.rb | 4 +- config/initializers/0_thread_cache.rb | 3 - doc/api/vulnerability_exports.md | 30 ++-- doc/development/code_review.md | 15 ++ doc/user/packages/container_registry/index.md | 4 +- .../repository/x509_signed_commits/index.md | 37 +++- lib/feature.rb | 6 +- .../backfill_snippet_repositories.rb | 2 +- lib/gitlab/git/tag.rb | 21 +++ lib/gitlab/json.rb | 34 +++- lib/gitlab/performance_bar.rb | 2 +- ... clear_process_memory_cache_middleware.rb} | 4 +- lib/gitlab/thread_memory_cache.rb | 15 -- lib/gitlab/x509/signature.rb | 4 + lib/gitlab/x509/tag.rb | 41 +++++ locale/gitlab.pot | 101 +++++++---- .../edit/components/active_toggle_spec.js | 1 - .../edit/components/integration_form_spec.js | 81 +++++++++ .../components/jira_trigger_fields_spec.js | 94 ++++++++++ spec/helpers/x509_helper_spec.rb | 18 ++ spec/lib/feature_spec.rb | 8 - spec/lib/gitlab/git/tag_spec.rb | 30 ++++ spec/lib/gitlab/json_spec.rb | 108 +++++++++--- spec/lib/gitlab/performance_bar_spec.rb | 2 +- spec/lib/gitlab/x509/signature_spec.rb | 160 ++++++++++++++++++ spec/lib/gitlab/x509/tag_spec.rb | 42 +++++ .../concerns/cacheable_attributes_spec.rb | 4 +- spec/models/merge_request_diff_spec.rb | 59 ++++++- .../project_services/jira_service_spec.rb | 73 ++++++++ spec/models/snippet_repository_spec.rb | 16 ++ spec/models/x509_commit_signature_spec.rb | 32 +++- spec/services/system_note_service_spec.rb | 3 +- spec/spec_helper.rb | 4 +- spec/support/helpers/stub_object_storage.rb | 2 +- spec/support/helpers/x509_helpers.rb | 137 +++++++++++++++ .../projects/services/_form.haml_spec.rb | 15 -- 57 files changed, 1351 insertions(+), 199 deletions(-) create mode 100644 app/assets/javascripts/integrations/edit/components/integration_form.vue create mode 100644 app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue create mode 100644 changelogs/unreleased/195887-jira-comment-details-include-branch-name.yml create mode 100644 changelogs/unreleased/216216-fix-mr-diffs-external-diffs-store-bug.yml create mode 100644 changelogs/unreleased/feat-x509-tag.yml create mode 100644 changelogs/unreleased/sh-handle-deployments-no-pods.yml create mode 100644 changelogs/unreleased/switch-thread-to-process-memory-cache.yml delete mode 100644 config/initializers/0_thread_cache.rb rename lib/gitlab/testing/{clear_thread_memory_cache_middleware.rb => clear_process_memory_cache_middleware.rb} (65%) delete mode 100644 lib/gitlab/thread_memory_cache.rb create mode 100644 lib/gitlab/x509/tag.rb create mode 100644 spec/frontend/integrations/edit/components/integration_form_spec.js create mode 100644 spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js create mode 100644 spec/lib/gitlab/x509/tag_spec.rb diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue new file mode 100644 index 00000000000..ab6a3f97bfd --- /dev/null +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue new file mode 100644 index 00000000000..70278e401ce --- /dev/null +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -0,0 +1,99 @@ + + + diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 25ae6015f1d..7b476528a33 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -1,26 +1,45 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; -import ActiveToggle from './components/active_toggle.vue'; +import IntegrationForm from './components/integration_form.vue'; export default el => { if (!el) { return null; } - const { showActive: showActiveStr, activated: activatedStr } = el.dataset; - const showActive = parseBoolean(showActiveStr); - const activated = parseBoolean(activatedStr); - - if (!showActive) { - return null; + function parseBooleanInData(data) { + const result = {}; + Object.entries(data).forEach(([key, value]) => { + result[key] = parseBoolean(value); + }); + return result; } + const { type, commentDetail, ...booleanAttributes } = el.dataset; + const { + showActive, + activated, + commitEvents, + mergeRequestEvents, + enableComments, + } = parseBooleanInData(booleanAttributes); + return new Vue({ el, render(createElement) { - return createElement(ActiveToggle, { + return createElement(IntegrationForm, { props: { - initialActivated: activated, + activeToggleProps: { + initialActivated: activated, + }, + showActive, + type, + triggerFieldsProps: { + initialTriggerCommit: commitEvents, + initialTriggerMergeRequest: mergeRequestEvents, + initialEnableComments: enableComments, + initialCommentDetail: commentDetail, + }, }, }); }, diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 942bb4b6b0e..355662bbb38 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -5,7 +5,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController # NOTE: Use @application_setting in this controller when you need to access # application_settings after it has been modified. This is because the - # ApplicationSetting model uses Gitlab::ThreadMemoryCache for caching and the + # ApplicationSetting model uses Gitlab::ProcessMemoryCache for caching and the # cache might be stale immediately after an update. # https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/30233 before_action :set_application_setting, except: :integrations diff --git a/app/helpers/x509_helper.rb b/app/helpers/x509_helper.rb index c330b599d74..009635fb629 100644 --- a/app/helpers/x509_helper.rb +++ b/app/helpers/x509_helper.rb @@ -16,4 +16,8 @@ module X509Helper rescue {} end + + def x509_signature?(sig) + sig.is_a?(X509CommitSignature) || sig.is_a?(Gitlab::X509::Signature) + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index f9481db2268..d13f0a1a000 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -421,7 +421,7 @@ class ApplicationSetting < ApplicationRecord # can cause a significant amount of load on Redis, let's cache it in # memory. def self.cache_backend - Gitlab::ThreadMemoryCache.cache_backend + Gitlab::ProcessMemoryCache.cache_backend end def recaptcha_or_login_protection_enabled diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 7b15d21c095..ea3e00d9cd3 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -141,7 +141,7 @@ class MergeRequestDiff < ApplicationRecord after_create :save_git_content, unless: :importing? after_create_commit :set_as_latest_diff, unless: :importing? - after_save :update_external_diff_store, if: -> { !importing? && saved_change_to_external_diff? } + after_save :update_external_diff_store def self.find_by_diff_refs(diff_refs) find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha) @@ -401,8 +401,10 @@ class MergeRequestDiff < ApplicationRecord end def update_external_diff_store - update_column(:external_diff_store, external_diff.object_store) if - has_attribute?(:external_diff_store) + return unless has_attribute?(:external_diff_store) + return unless saved_change_to_external_diff? || saved_change_to_stored_externally? + + update_column(:external_diff_store, external_diff.object_store) end def saved_change_to_external_diff? diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index f0a5d8e8cdd..53da874ede8 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -177,6 +177,7 @@ class JiraService < IssueTrackerService noteable_id = noteable.respond_to?(:iid) ? noteable.iid : noteable.id noteable_type = noteable_name(noteable) entity_url = build_entity_url(noteable_type, noteable_id) + entity_meta = build_entity_meta(noteable) data = { user: { @@ -185,12 +186,15 @@ class JiraService < IssueTrackerService }, project: { name: project.full_path, - url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper + url: resource_url(project_path(project)) }, entity: { + id: entity_meta[:id], name: noteable_type.humanize.downcase, url: entity_url, - title: noteable.title + title: noteable.title, + description: entity_meta[:description], + branch: entity_meta[:branch] } } @@ -264,14 +268,11 @@ class JiraService < IssueTrackerService end def add_comment(data, issue) - user_name = data[:user][:name] - user_url = data[:user][:url] entity_name = data[:entity][:name] entity_url = data[:entity][:url] entity_title = data[:entity][:title] - project_name = data[:project][:name] - message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'" + message = comment_message(data) link_title = "#{entity_name.capitalize} - #{entity_title}" link_props = build_remote_link_props(url: entity_url, title: link_title) @@ -280,6 +281,37 @@ class JiraService < IssueTrackerService end end + def comment_message(data) + user_link = build_jira_link(data[:user][:name], data[:user][:url]) + + entity = data[:entity] + entity_ref = all_details? ? "#{entity[:name]} #{entity[:id]}" : "a #{entity[:name]}" + entity_link = build_jira_link(entity_ref, entity[:url]) + + project_link = build_jira_link(project.full_name, Gitlab::Routing.url_helpers.project_url(project)) + branch = + if entity[:branch].present? + s_('JiraService| on branch %{branch_link}') % { + branch_link: build_jira_link(entity[:branch], project_tree_url(project, entity[:branch])) + } + end + + entity_message = entity[:description].presence if all_details? + entity_message ||= entity[:title].chomp + + s_('JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}') % { + user_link: user_link, + entity_link: entity_link, + project_link: project_link, + branch: branch, + entity_message: entity_message + } + end + + def build_jira_link(title, url) + "[#{title}|#{url}]" + end + def has_resolution?(issue) issue.respond_to?(:resolution) && issue.resolution.present? end @@ -353,6 +385,23 @@ class JiraService < IssueTrackerService ) end + def build_entity_meta(noteable) + if noteable.is_a?(Commit) + { + id: noteable.short_id, + description: noteable.safe_message, + branch: noteable.ref_names(project.repository).first + } + elsif noteable.is_a?(MergeRequest) + { + id: noteable.to_reference, + branch: noteable.source_branch + } + else + {} + end + end + def noteable_name(noteable) name = noteable.model_name.singular diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb index e60dbb4d141..1e7c069cfaa 100644 --- a/app/models/snippet_repository.rb +++ b/app/models/snippet_repository.rb @@ -7,6 +7,7 @@ class SnippetRepository < ApplicationRecord EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/.freeze CommitError = Class.new(StandardError) + InvalidPathError = Class.new(CommitError) belongs_to :snippet, inverse_of: :snippet_repository @@ -40,8 +41,9 @@ class SnippetRepository < ApplicationRecord rescue Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::PreReceiveError, - Gitlab::Git::CommandError => e - raise CommitError, e.message + Gitlab::Git::CommandError => error + + raise commit_error_exception(error) end def transform_file_entries(files) @@ -85,4 +87,16 @@ class SnippetRepository < ApplicationRecord def build_empty_file_name(index) "#{DEFAULT_EMPTY_FILE_NAME}#{index}.txt" end + + def commit_error_exception(error) + if error.is_a?(Gitlab::Git::Index::IndexError) && invalid_path_error?(error.message) + InvalidPathError.new('Invalid Path') # To avoid returning the message with the path included + else + CommitError.new(error.message) + end + end + + def invalid_path_error?(message) + message.downcase.start_with?('invalid path', 'path cannot include directory traversal') + end end diff --git a/app/models/x509_commit_signature.rb b/app/models/x509_commit_signature.rb index ed7c638cecc..57d809f7cfb 100644 --- a/app/models/x509_commit_signature.rb +++ b/app/models/x509_commit_signature.rb @@ -41,4 +41,8 @@ class X509CommitSignature < ApplicationRecord Gitlab::X509::Commit.new(commit) end + + def user + commit.committer + end end diff --git a/app/views/projects/commit/_signature.html.haml b/app/views/projects/commit/_signature.html.haml index aa7c90bad66..fb31ac44118 100644 --- a/app/views/projects/commit/_signature.html.haml +++ b/app/views/projects/commit/_signature.html.haml @@ -1,3 +1,3 @@ - if signature - - uri = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}" + - uri = "projects/commit/#{"x509/" if x509_signature?(signature)}" = render partial: "#{uri}#{signature.verification_status}_signature_badge", locals: { signature: signature } diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index 8ecaa1329fd..8004a5facd7 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -17,13 +17,13 @@ - content = capture do - if show_user .clearfix - - uri_signature_badge_user = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}signature_badge_user" + - uri_signature_badge_user = "projects/commit/#{"x509/" if x509_signature?(signature)}signature_badge_user" = render partial: "#{uri_signature_badge_user}", locals: { signature: signature } - - if signature.instance_of?(X509CommitSignature) + - if x509_signature?(signature) = render partial: "projects/commit/x509/certificate_details", locals: { signature: signature } - = link_to(_('Learn more about x509 signed commits'), help_page_path('user/project/repository/x509_signed_commits/index.md'), class: 'gpg-popover-help-link') + = link_to(_('Learn more about X.509 signed commits'), help_page_path('user/project/repository/x509_signed_commits/index.md'), class: 'gpg-popover-help-link') - else = _('GPG Key ID:') %span.monospace= signature.gpg_key_primary_keyid diff --git a/app/views/projects/commit/x509/_signature_badge_user.html.haml b/app/views/projects/commit/x509/_signature_badge_user.html.haml index b64ccba2a18..f3d39b21ec2 100644 --- a/app/views/projects/commit/x509/_signature_badge_user.html.haml +++ b/app/views/projects/commit/x509/_signature_badge_user.html.haml @@ -1,5 +1,5 @@ -- user = signature.commit.committer - user_email = signature.x509_certificate.email +- user = signature.user - if user = link_to user_path(user), class: 'gpg-popover-user-link' do diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 75805192a61..da693a15ec2 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -30,6 +30,9 @@ = markdown_field(release, :description) .row-fixed-content.controls.flex-row + - if tag.has_signature? + = render partial: 'projects/commit/signature', object: tag.signature + = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] - if can?(current_user, :admin_tag, @project) diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 8086d47479d..6f53a687fb9 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -39,6 +39,8 @@ = s_("TagsPage|Can't find HEAD commit for this tag") .nav-controls + - if @tag.has_signature? + = render partial: 'projects/commit/signature', object: @tag.signature - if can?(current_user, :admin_tag, @project) = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do = icon("pencil") diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 7262c47f9d3..cb298459dc7 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -8,9 +8,10 @@ = markdown @service.help .service-settings - .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s } } + .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, type: @service.to_param, merge_request_events: @service.merge_requests_events.to_s, +commit_events: @service.commit_events.to_s, enable_comments: @service.comment_on_event_enabled.to_s, comment_detail: @service.comment_detail } } - - if @service.configurable_events.present? + - if @service.configurable_events.present? && !@service.is_a?(JiraService) .form-group.row %label.col-form-label.col-sm-2= _('Trigger') @@ -31,22 +32,6 @@ %p.text-muted = @service.class.event_description(event) - - if @service.configurable_event_actions.present? - .form-group.row - %label.col-form-label.col-sm-2= _('Event Actions') - - .col-sm-10 - - @service.configurable_event_actions.each do |action| - .form-group - .form-check - = form.check_box service_event_action_field_name(action), class: 'form-check-input' - = form.label service_event_action_field_name(action), class: 'form-check-label' do - %strong - = event_action_description(action) - - %p.text-muted - = event_action_description(action) - - @service.global_fields.each do |field| - type = field[:type] diff --git a/changelogs/unreleased/195887-jira-comment-details-include-branch-name.yml b/changelogs/unreleased/195887-jira-comment-details-include-branch-name.yml new file mode 100644 index 00000000000..8a5721cc596 --- /dev/null +++ b/changelogs/unreleased/195887-jira-comment-details-include-branch-name.yml @@ -0,0 +1,5 @@ +--- +title: Update Jira comment to include more information +merge_request: 30258 +author: +type: added diff --git a/changelogs/unreleased/216216-fix-mr-diffs-external-diffs-store-bug.yml b/changelogs/unreleased/216216-fix-mr-diffs-external-diffs-store-bug.yml new file mode 100644 index 00000000000..6051b3f70c6 --- /dev/null +++ b/changelogs/unreleased/216216-fix-mr-diffs-external-diffs-store-bug.yml @@ -0,0 +1,5 @@ +--- +title: Correctly track the store that external MR diffs are placed on +merge_request: 31005 +author: +type: fixed diff --git a/changelogs/unreleased/feat-x509-tag.yml b/changelogs/unreleased/feat-x509-tag.yml new file mode 100644 index 00000000000..b57b87a889f --- /dev/null +++ b/changelogs/unreleased/feat-x509-tag.yml @@ -0,0 +1,5 @@ +--- +title: Display x509 signed tags +merge_request: 27211 +author: Roger Meier +type: added diff --git a/changelogs/unreleased/sh-handle-deployments-no-pods.yml b/changelogs/unreleased/sh-handle-deployments-no-pods.yml new file mode 100644 index 00000000000..72ffb22518a --- /dev/null +++ b/changelogs/unreleased/sh-handle-deployments-no-pods.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error loading environments index +merge_request: 31184 +author: +type: fixed diff --git a/changelogs/unreleased/switch-thread-to-process-memory-cache.yml b/changelogs/unreleased/switch-thread-to-process-memory-cache.yml new file mode 100644 index 00000000000..7cc12849d5f --- /dev/null +++ b/changelogs/unreleased/switch-thread-to-process-memory-cache.yml @@ -0,0 +1,5 @@ +--- +title: Use process-wide cache for application settings and performance bar +merge_request: 31135 +author: +type: performance diff --git a/config/environments/test.rb b/config/environments/test.rb index 71cd5200415..0c9033c45bc 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,12 +1,12 @@ require 'gitlab/testing/request_blocker_middleware' require 'gitlab/testing/request_inspector_middleware' -require 'gitlab/testing/clear_thread_memory_cache_middleware' +require 'gitlab/testing/clear_process_memory_cache_middleware' Rails.application.configure do # Make sure the middleware is inserted first in middleware chain config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestBlockerMiddleware) config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestInspectorMiddleware) - config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::ClearThreadMemoryCacheMiddleware) + config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::ClearProcessMemoryCacheMiddleware) # Settings specified here will take precedence over those in config/application.rb diff --git a/config/initializers/0_thread_cache.rb b/config/initializers/0_thread_cache.rb deleted file mode 100644 index feb8057132e..00000000000 --- a/config/initializers/0_thread_cache.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -Gitlab::ThreadMemoryCache.cache_backend diff --git a/doc/api/vulnerability_exports.md b/doc/api/vulnerability_exports.md index 42dafc1612a..f53a0ca08a3 100644 --- a/doc/api/vulnerability_exports.md +++ b/doc/api/vulnerability_exports.md @@ -151,18 +151,18 @@ The response will be `404 Not Found` if the vulnerability export is not finished Example response: ```csv -Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE -container_scanning,Clair,confirmed,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997 -container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269 -container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001 -container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228 -container_scanning,Clair,confirmed,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052 -container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520 -container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869 -dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a -dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98 -sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47 -sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29 -sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41 -sast,Find Security Bugs,confirmed,ECB mode is insecure 2,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29 -``` +Group Name,Project Name,Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE +Gitlab.org,Defend,container_scanning,Clair,confirmed,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997 +Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269 +Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001 +Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228 +Gitlab.org,Defend,container_scanning,Clair,confirmed,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052 +Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520 +Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869 +Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a +Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98 +Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47 +Gitlab.org,Defend,sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29 +Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41 +Gitlab.org,Defend,sast,Find Security Bugs,confirmed,ECB mode is insecure 2,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29 +Gitlab.org,Defend,``` diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 8ea7e35dfb1..07bf6bf5fbf 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -468,6 +468,21 @@ When a merge request author has been blocked for longer than the `Review-response` SLO, they are free to remind the reviewer through Slack or assign another reviewer. +#### Customer critical merge requests + +A merge request may benefit from being considered a customer critical priority because there is a significant benefit to the business in doing so. + +Properties of customer critical merge requests: + +- The [Senior Director of Development](https://about.gitlab.com/job-families/engineering/engineering-management/#senior-director-engineering) [@clefelhocz1](https://gitlab.com/clefelhocz1) is the DRI for deciding if a merge request will be customer critical. +- The DRI will assign the `customer-critical-merge-request` label to the merge request. +- It is required that the reviewer(s) and maintainer(s) involved with a customer critical merge request are engaged as soon as this decision is made. +- It is required to prioritize work for those involved on a customer critical merge request so that they have the time available necessary to focus on it. +- It is required to adhere to GitLab [values](https://about.gitlab.com/handbook/values.md) and processes when working on customer critical merge requests, taking particular note of family and friends first/work second, definition of done, iteration, and release when it's ready. +- Customer critical merge requests are required to not reduce security, introduce data-loss risk, reduce availability, nor break existing functionality per the process for [prioritizing technical decisions](https://about.gitlab.com/handbook/engineering/#prioritizing-technical-decisions.md). +- On customer critical requests, it is _recommended_ that those involved _consider_ coordinating synchronously (Zoom, Slack) in addition to asynchronously (merge requests comments) if they believe this will reduce elapsed time to merge even though this _may_ sacrifice [efficiency](https://about.gitlab.com/company/culture/all-remote/asynchronous/#evaluating-efficiency.md). +- After a customer critical merge request is merged, a retrospective must be completed with the intention of reducing the frequency of future customer critical merge requests. + ## Examples How code reviews are conducted can surprise new contributors. Here are some examples of code reviews that should help to orient you as to what to expect. diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md index 9f4702a09e5..5ad476c9510 100644 --- a/doc/user/packages/container_registry/index.md +++ b/doc/user/packages/container_registry/index.md @@ -552,10 +552,12 @@ being cleaned up will be minimal. ## Limitations -Moving or renaming existing Container Registry repositories is not supported +- Moving or renaming existing Container Registry repositories is not supported once you have pushed images, because the images are signed, and the signature includes the repository name. To move or rename a repository with a Container Registry, you will have to delete all existing images. +- Prior to GitLab 12.10, any tags that use the same image ID as the `latest` tag +will not be deleted by the expiration policy. ## Troubleshooting the GitLab Container Registry diff --git a/doc/user/project/repository/x509_signed_commits/index.md b/doc/user/project/repository/x509_signed_commits/index.md index ad19e2aae75..7d377999bc1 100644 --- a/doc/user/project/repository/x509_signed_commits/index.md +++ b/doc/user/project/repository/x509_signed_commits/index.md @@ -2,7 +2,7 @@ type: concepts, howto --- -# Signing commits with X.509 +# Signing commits and tags with X.509 [X.509](https://en.wikipedia.org/wiki/X.509) is a standard format for public key certificates issued by a public or private Public Key Infrastructure (PKI). @@ -16,7 +16,7 @@ instead of a web of trust with GPG. GitLab uses its own certificate store and therefore defines the trust chain. -For a commit to be *verified* by GitLab: +For a commit or tag to be *verified* by GitLab: - The signing certificate email must match a verified email address used by the committer in GitLab. - The Certificate Authority has to be trusted by the GitLab instance, see also @@ -27,6 +27,11 @@ For a commit to be *verified* by GitLab: NOTE: **Note:** Certificate revocation lists are checked on a daily basis via background worker. +NOTE: **Note:** Self signed certificates without `authorityKeyIdentifier`, +`subjectKeyIdentifier`, and `crlDistributionPoints` are not supported. We +recommend using certificates from a PKI that are in line with +[RFC 5280](https://tools.ietf.org/html/rfc5280). + ## Obtaining an X.509 key pair If your organization has Public Key Infrastructure (PKI), that PKI will provide @@ -98,3 +103,31 @@ To verify that a commit is signed, you can use the `--show-signature` flag: ```sh git log --show-signature ``` + +## Signing tags + +After you have [associated your X.509 certificate with Git](#associating-your-x509-certificate-with-git) you +can start signing your tags: + +1. Tag like you used to, the only difference is the addition of the `-s` flag: + + ```sh + git tag -s v1.1.1 -m "My signed tag" + ``` + +1. Push to GitLab and check that your tags [are verified](#verifying-tags). + +If you don't want to type the `-s` flag every time you tag, you can tell Git +to sign your tags automatically: + +```sh +git config --global tag.gpgsign true +``` + +## Verifying tags + +To verify that a tag is signed, you can use the `--verify` flag: + +```sh +git tag --verify v1.1.1 +``` diff --git a/lib/feature.rb b/lib/feature.rb index 60a5c03a839..dc7e8da8f35 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -134,11 +134,7 @@ class Feature end def l1_cache_backend - if Gitlab::Utils.to_boolean(ENV['USE_THREAD_MEMORY_CACHE']) - Gitlab::ThreadMemoryCache.cache_backend - else - Gitlab::ProcessMemoryCache.cache_backend - end + Gitlab::ProcessMemoryCache.cache_backend end def l2_cache_backend diff --git a/lib/gitlab/background_migration/backfill_snippet_repositories.rb b/lib/gitlab/background_migration/backfill_snippet_repositories.rb index 192a82c84e6..148c25685ed 100644 --- a/lib/gitlab/background_migration/backfill_snippet_repositories.rb +++ b/lib/gitlab/background_migration/backfill_snippet_repositories.rb @@ -117,7 +117,7 @@ module Gitlab # the migration can succeed, to achieve that, we'll identify in migration retries # that the path is invalid def set_file_path_error(error) - @invalid_path_error = error.message.downcase.start_with?('invalid path', 'path cannot include directory traversal') + @invalid_path_error = error.is_a?(SnippetRepository::InvalidPathError) end end end diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb index 08dbd52e3fb..da86d6baf4a 100644 --- a/lib/gitlab/git/tag.rb +++ b/lib/gitlab/git/tag.rb @@ -66,6 +66,27 @@ module Gitlab @raw_tag.tagger end + def has_signature? + signature_type != :NONE + end + + def signature_type + @raw_tag.signature_type || :NONE + end + + def signature + return unless has_signature? + + case signature_type + when :PGP + nil # not implemented, see https://gitlab.com/gitlab-org/gitlab/issues/19260 + when :X509 + X509::Tag.new(@raw_tag).signature + else + nil + end + end + private def message_from_gitaly_tag diff --git a/lib/gitlab/json.rb b/lib/gitlab/json.rb index 5ebda67e2ae..5b6689dbefe 100644 --- a/lib/gitlab/json.rb +++ b/lib/gitlab/json.rb @@ -2,13 +2,25 @@ module Gitlab module Json + INVALID_LEGACY_TYPES = [String, TrueClass, FalseClass].freeze + class << self - def parse(*args) - adapter.parse(*args) + def parse(string, *args, **named_args) + legacy_mode = legacy_mode_enabled?(named_args.delete(:legacy_mode)) + data = adapter.parse(string, *args, **named_args) + + handle_legacy_mode!(data) if legacy_mode + + data end - def parse!(*args) - adapter.parse!(*args) + def parse!(string, *args, **named_args) + legacy_mode = legacy_mode_enabled?(named_args.delete(:legacy_mode)) + data = adapter.parse!(string, *args, **named_args) + + handle_legacy_mode!(data) if legacy_mode + + data end def dump(*args) @@ -28,6 +40,20 @@ module Gitlab def adapter ::JSON end + + def parser_error + ::JSON::ParserError + end + + def legacy_mode_enabled?(arg_value) + arg_value.nil? ? false : arg_value + end + + def handle_legacy_mode!(data) + return data unless Feature.enabled?(:json_wrapper_legacy_mode, default_enabled: true) + + raise parser_error if INVALID_LEGACY_TYPES.any? { |type| data.is_a?(type) } + end end end end diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb index 4445c876e7a..e26309b5dfd 100644 --- a/lib/gitlab/performance_bar.rb +++ b/lib/gitlab/performance_bar.rb @@ -44,7 +44,7 @@ module Gitlab end def self.l1_cache_backend - Gitlab::ThreadMemoryCache.cache_backend + Gitlab::ProcessMemoryCache.cache_backend end def self.l2_cache_backend diff --git a/lib/gitlab/testing/clear_thread_memory_cache_middleware.rb b/lib/gitlab/testing/clear_process_memory_cache_middleware.rb similarity index 65% rename from lib/gitlab/testing/clear_thread_memory_cache_middleware.rb rename to lib/gitlab/testing/clear_process_memory_cache_middleware.rb index 6f54038ae22..1e69e5e142d 100644 --- a/lib/gitlab/testing/clear_thread_memory_cache_middleware.rb +++ b/lib/gitlab/testing/clear_process_memory_cache_middleware.rb @@ -2,13 +2,13 @@ module Gitlab module Testing - class ClearThreadMemoryCacheMiddleware + class ClearProcessMemoryCacheMiddleware def initialize(app) @app = app end def call(env) - Gitlab::ThreadMemoryCache.cache_backend.clear + Gitlab::ProcessMemoryCache.cache_backend.clear @app.call(env) end diff --git a/lib/gitlab/thread_memory_cache.rb b/lib/gitlab/thread_memory_cache.rb deleted file mode 100644 index 7f363dc7feb..00000000000 --- a/lib/gitlab/thread_memory_cache.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - class ThreadMemoryCache - THREAD_KEY = :thread_memory_cache - - def self.cache_backend - # Note ActiveSupport::Cache::MemoryStore is thread-safe. Since - # each backend is local per thread we probably don't need to worry - # about synchronizing access, but this is a drop-in replacement - # for ActiveSupport::Cache::RedisStore. - Thread.current[THREAD_KEY] ||= ActiveSupport::Cache::MemoryStore.new - end - end -end diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb index ed248e29211..7d4d4d9d13a 100644 --- a/lib/gitlab/x509/signature.rb +++ b/lib/gitlab/x509/signature.rb @@ -22,6 +22,10 @@ module Gitlab X509Certificate.safe_create!(certificate_attributes) unless verified_signature.nil? end + def user + User.find_by_any_email(@email) + end + def verified_signature strong_memoize(:verified_signature) { verified_signature? } end diff --git a/lib/gitlab/x509/tag.rb b/lib/gitlab/x509/tag.rb new file mode 100644 index 00000000000..48582c17764 --- /dev/null +++ b/lib/gitlab/x509/tag.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true +require 'openssl' +require 'digest' + +module Gitlab + module X509 + class Tag + include Gitlab::Utils::StrongMemoize + + def initialize(raw_tag) + @raw_tag = raw_tag + end + + def signature + signature = X509::Signature.new(signature_text, signed_text, @raw_tag.tagger.email, Time.at(@raw_tag.tagger.date.seconds)) + + return if signature.verified_signature.nil? + + signature + end + + private + + def signature_text + @raw_tag.message.slice(@raw_tag.message.index("-----BEGIN SIGNED MESSAGE-----")..-1) + rescue + nil + end + + def signed_text + # signed text is reconstructed as long as there is no specific gitaly function + %{object #{@raw_tag.target_commit.id} +type commit +tag #{@raw_tag.name} +tagger #{@raw_tag.tagger.name} <#{@raw_tag.tagger.email}> #{@raw_tag.tagger.date.seconds} #{@raw_tag.tagger.timezone} + +#{@raw_tag.message.gsub(/-----BEGIN SIGNED MESSAGE-----(.*)-----END SIGNED MESSAGE-----/m, "")}} + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b77364cc74b..53f88901e78 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3410,12 +3410,6 @@ msgstr "" msgid "Business metrics (Custom)" msgstr "" -msgid "Buy EE" -msgstr "" - -msgid "Buy GitLab Enterprise Edition" -msgstr "" - msgid "Buy more Pipeline minutes" msgstr "" @@ -5593,18 +5587,9 @@ msgstr "" msgid "Connection timed out" msgstr "" -msgid "Contact an owner of group %{namespace_name} to upgrade the plan." -msgstr "" - -msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan." -msgstr "" - msgid "Contact sales to upgrade" msgstr "" -msgid "Contact your Administrator to upgrade your license." -msgstr "" - msgid "Container Registry" msgstr "" @@ -8572,9 +8557,6 @@ msgstr "" msgid "Estimated" msgstr "" -msgid "Event Actions" -msgstr "" - msgid "EventFilterBy|Filter by all" msgstr "" @@ -9793,10 +9775,7 @@ msgstr "" msgid "Geo|Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information." msgstr "" -msgid "Geo|All" -msgstr "" - -msgid "Geo|All %{replicable_type}" +msgid "Geo|All %{replicable_name}" msgstr "" msgid "Geo|All projects" @@ -11306,9 +11285,6 @@ msgstr "" msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition." msgstr "" -msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." -msgstr "" - msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" @@ -11411,6 +11387,12 @@ msgstr "" msgid "Insights" msgstr "" +msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)." +msgstr "" + +msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)." +msgstr "" + msgid "Install" msgstr "" @@ -11461,6 +11443,30 @@ msgstr "" msgid "Integrations allow you to integrate GitLab with other applications" msgstr "" +msgid "Integrations|All details" +msgstr "" + +msgid "Integrations|Comment detail:" +msgstr "" + +msgid "Integrations|Comment settings:" +msgstr "" + +msgid "Integrations|Enable comments" +msgstr "" + +msgid "Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs" +msgstr "" + +msgid "Integrations|Includes commit title and branch" +msgstr "" + +msgid "Integrations|Standard" +msgstr "" + +msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created." +msgstr "" + msgid "Interested parties can even contribute by pushing commits if they want to." msgstr "" @@ -11764,6 +11770,12 @@ msgstr "" msgid "Jira project: %{importProject}" msgstr "" +msgid "JiraService| on branch %{branch_link}" +msgstr "" + +msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}" +msgstr "" + msgid "JiraService|Events for %{noteable_model_name} are disabled." msgstr "" @@ -12189,6 +12201,9 @@ msgstr "" msgid "Learn more about Web Terminal" msgstr "" +msgid "Learn more about X.509 signed commits" +msgstr "" + msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}." msgstr "" @@ -12210,9 +12225,6 @@ msgstr "" msgid "Learn more about the dependency list" msgstr "" -msgid "Learn more about x509 signed commits" -msgstr "" - msgid "Learn more in the" msgstr "" @@ -16671,24 +16683,51 @@ msgstr "" msgid "Promoted issue to an epic." msgstr "" +msgid "Promotions|Buy EE" +msgstr "" + +msgid "Promotions|Buy GitLab Enterprise Edition" +msgstr "" + +msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan." +msgstr "" + +msgid "Promotions|Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan." +msgstr "" + +msgid "Promotions|Contact your Administrator to upgrade your license." +msgstr "" + msgid "Promotions|Don't show me this again" msgstr "" msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones." msgstr "" +msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition." +msgstr "" + msgid "Promotions|Learn more" msgstr "" msgid "Promotions|See the other features in the %{subscription_link_start}bronze plan%{subscription_link_end}" msgstr "" +msgid "Promotions|Start GitLab Ultimate trial" +msgstr "" + msgid "Promotions|This feature is locked." msgstr "" msgid "Promotions|Upgrade plan" msgstr "" +msgid "Promotions|Upgrade your plan" +msgstr "" + +msgid "Promotions|Weight" +msgstr "" + msgid "Promotions|Weighting your issue" msgstr "" @@ -19774,9 +19813,6 @@ msgstr "" msgid "Stars" msgstr "" -msgid "Start GitLab Ultimate trial" -msgstr "" - msgid "Start Web Terminal" msgstr "" @@ -22643,9 +22679,6 @@ msgstr "" msgid "Upgrade plan to unlock Canary Deployments feature" msgstr "" -msgid "Upgrade your plan" -msgstr "" - msgid "Upgrade your plan to activate Advanced Global Search." msgstr "" diff --git a/spec/frontend/integrations/edit/components/active_toggle_spec.js b/spec/frontend/integrations/edit/components/active_toggle_spec.js index 8a11c200c15..7f13d707efd 100644 --- a/spec/frontend/integrations/edit/components/active_toggle_spec.js +++ b/spec/frontend/integrations/edit/components/active_toggle_spec.js @@ -9,7 +9,6 @@ describe('ActiveToggle', () => { const defaultProps = { initialActivated: true, - disabled: false, }; const createComponent = props => { diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js new file mode 100644 index 00000000000..d0b36e5eaf2 --- /dev/null +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -0,0 +1,81 @@ +import { shallowMount } from '@vue/test-utils'; +import IntegrationForm from '~/integrations/edit/components/integration_form.vue'; +import ActiveToggle from '~/integrations/edit/components/active_toggle.vue'; +import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; + +describe('IntegrationForm', () => { + let wrapper; + + const defaultProps = { + activeToggleProps: { + initialActivated: true, + }, + showActive: true, + triggerFieldsProps: { + initialTriggerCommit: false, + initialTriggerMergeRequest: false, + initialEnableComments: false, + }, + type: '', + }; + + const createComponent = props => { + wrapper = shallowMount(IntegrationForm, { + propsData: { ...defaultProps, ...props }, + stubs: { + ActiveToggle, + JiraTriggerFields, + }, + }); + }; + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + const findActiveToggle = () => wrapper.find(ActiveToggle); + const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields); + + describe('template', () => { + describe('showActive is true', () => { + it('renders ActiveToggle', () => { + createComponent(); + + expect(findActiveToggle().exists()).toBe(true); + }); + }); + + describe('showActive is false', () => { + it('does not render ActiveToggle', () => { + createComponent({ + showActive: false, + }); + + expect(findActiveToggle().exists()).toBe(false); + }); + }); + + describe('type is "slack"', () => { + it('does not render JiraTriggerFields', () => { + createComponent({ + type: 'slack', + }); + + expect(findJiraTriggerFields().exists()).toBe(false); + }); + }); + + describe('type is "jira"', () => { + it('renders JiraTriggerFields', () => { + createComponent({ + type: 'jira', + }); + + expect(findJiraTriggerFields().exists()).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js new file mode 100644 index 00000000000..16d9df96c16 --- /dev/null +++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js @@ -0,0 +1,94 @@ +import { mount } from '@vue/test-utils'; +import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; +import { GlFormCheckbox } from '@gitlab/ui'; + +describe('JiraTriggerFields', () => { + let wrapper; + + const defaultProps = { + initialTriggerCommit: false, + initialTriggerMergeRequest: false, + initialEnableComments: false, + }; + + const createComponent = props => { + wrapper = mount(JiraTriggerFields, { + propsData: Object.assign({}, defaultProps, props), + }); + }; + + afterEach(() => { + if (wrapper) wrapper.destroy(); + }); + + const findCommentSettings = () => wrapper.find('[data-testid="comment-settings"]'); + const findCommentDetail = () => wrapper.find('[data-testid="comment-detail"]'); + const findCommentSettingsCheckbox = () => findCommentSettings().find(GlFormCheckbox); + + describe('template', () => { + describe('initialTriggerCommit and initialTriggerMergeRequest are false', () => { + it('does not show comment settings', () => { + createComponent(); + + expect(findCommentSettings().isVisible()).toBe(false); + expect(findCommentDetail().isVisible()).toBe(false); + }); + }); + + describe('initialTriggerCommit is true', () => { + beforeEach(() => { + createComponent({ + initialTriggerCommit: true, + }); + }); + + it('shows comment settings', () => { + expect(findCommentSettings().isVisible()).toBe(true); + expect(findCommentDetail().isVisible()).toBe(false); + }); + + // As per https://vuejs.org/v2/guide/forms.html#Checkbox-1, + // browsers don't include unchecked boxes in form submissions. + it('includes comment settings as false even if unchecked', () => { + expect( + findCommentSettings() + .find('input[name="service[comment_on_event_enabled]"]') + .exists(), + ).toBe(true); + }); + + describe('on enable comments', () => { + it('shows comment detail', () => { + findCommentSettingsCheckbox().vm.$emit('input', true); + + return wrapper.vm.$nextTick().then(() => { + expect(findCommentDetail().isVisible()).toBe(true); + }); + }); + }); + }); + + describe('initialTriggerMergeRequest is true', () => { + it('shows comment settings', () => { + createComponent({ + initialTriggerMergeRequest: true, + }); + + expect(findCommentSettings().isVisible()).toBe(true); + expect(findCommentDetail().isVisible()).toBe(false); + }); + }); + + describe('initialTriggerCommit is true, initialEnableComments is true', () => { + it('shows comment settings and comment detail', () => { + createComponent({ + initialTriggerCommit: true, + initialEnableComments: true, + }); + + expect(findCommentSettings().isVisible()).toBe(true); + expect(findCommentDetail().isVisible()).toBe(true); + }); + }); + }); +}); diff --git a/spec/helpers/x509_helper_spec.rb b/spec/helpers/x509_helper_spec.rb index dcdf57ce035..db3f6158195 100644 --- a/spec/helpers/x509_helper_spec.rb +++ b/spec/helpers/x509_helper_spec.rb @@ -57,4 +57,22 @@ describe X509Helper do end end end + + describe '#x509_signature?' do + let(:x509_signature) { create(:x509_commit_signature) } + let(:gpg_signature) { create(:gpg_signature) } + + it 'detects a x509 signed commit' do + signature = Gitlab::X509::Signature.new( + X509Helpers::User1.signed_commit_signature, + X509Helpers::User1.signed_commit_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + + expect(x509_signature?(x509_signature)).to be_truthy + expect(x509_signature?(signature)).to be_truthy + expect(x509_signature?(gpg_signature)).to be_falsey + end + end end diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 2890b8d4f3b..81fa2dc5cad 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -146,14 +146,6 @@ describe Feature do expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy end - context 'with USE_THREAD_MEMORY_CACHE defined' do - before do - stub_env('USE_THREAD_MEMORY_CACHE', '1') - end - - it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) } - end - it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) } it { expect(described_class.l2_cache_backend).to eq(Rails.cache) } diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index 87db3f588ad..6d3b239c38f 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -13,6 +13,13 @@ describe Gitlab::Git::Tag, :seed_helper do it { expect(tag.target).to eq("f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8") } it { expect(tag.dereferenced_target.sha).to eq("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") } it { expect(tag.message).to eq("Release") } + it { expect(tag.has_signature?).to be_falsey } + it { expect(tag.signature_type).to eq(:NONE) } + it { expect(tag.signature).to be_nil } + it { expect(tag.tagger.name).to eq("Dmitriy Zaporozhets") } + it { expect(tag.tagger.email).to eq("dmitriy.zaporozhets@gmail.com") } + it { expect(tag.tagger.date).to eq(Google::Protobuf::Timestamp.new(seconds: 1393491299)) } + it { expect(tag.tagger.timezone).to eq("+0200") } end describe 'last tag' do @@ -22,6 +29,29 @@ describe Gitlab::Git::Tag, :seed_helper do it { expect(tag.target).to eq("2ac1f24e253e08135507d0830508febaaccf02ee") } it { expect(tag.dereferenced_target.sha).to eq("fa1b1e6c004a68b7d8763b86455da9e6b23e36d6") } it { expect(tag.message).to eq("Version 1.2.1") } + it { expect(tag.has_signature?).to be_falsey } + it { expect(tag.signature_type).to eq(:NONE) } + it { expect(tag.signature).to be_nil } + it { expect(tag.tagger.name).to eq("Douwe Maan") } + it { expect(tag.tagger.email).to eq("douwe@selenight.nl") } + it { expect(tag.tagger.date).to eq(Google::Protobuf::Timestamp.new(seconds: 1427789449)) } + it { expect(tag.tagger.timezone).to eq("+0200") } + end + + describe 'signed tag' do + let(:project) { create(:project, :repository) } + let(:tag) { project.repository.find_tag('v1.1.1') } + + it { expect(tag.target).to eq("8f03acbcd11c53d9c9468078f32a2622005a4841") } + it { expect(tag.dereferenced_target.sha).to eq("189a6c924013fc3fe40d6f1ec1dc20214183bc97") } + it { expect(tag.message).to eq("x509 signed tag" + "\n" + X509Helpers::User1.signed_tag_signature.chomp) } + it { expect(tag.has_signature?).to be_truthy } + it { expect(tag.signature_type).to eq(:X509) } + it { expect(tag.signature).not_to be_nil } + it { expect(tag.tagger.name).to eq("Roger Meier") } + it { expect(tag.tagger.email).to eq("r.meier@siemens.com") } + it { expect(tag.tagger.date).to eq(Google::Protobuf::Timestamp.new(seconds: 1574261780)) } + it { expect(tag.tagger.timezone).to eq("+0100") } end it { expect(repository.tags.size).to eq(SeedRepo::Repo::TAGS.size) } diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb index 5186ab041da..e500b433cef 100644 --- a/spec/lib/gitlab/json_spec.rb +++ b/spec/lib/gitlab/json_spec.rb @@ -3,47 +3,103 @@ require "spec_helper" RSpec.describe Gitlab::Json do + before do + stub_feature_flags(json_wrapper_legacy_mode: true) + end + describe ".parse" do - it "parses an object" do - expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + context "legacy_mode is disabled by default" do + it "parses an object" do + expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + end + + it "parses an array" do + expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) + end + + # These tests will change expectations when the gem is upgraded + + it "raises an error on a string" do + expect { subject.parse('"foo"') }.to raise_error(JSON::ParserError) + end + + it "raises an error on a true bool" do + expect { subject.parse("true") }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse("false") }.to raise_error(JSON::ParserError) + end end - it "parses an array" do - expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) - end + context "legacy_mode is enabled" do + it "parses an object" do + expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" }) + end - it "raises an error on a string" do - expect { subject.parse('"foo"') }.to raise_error(JSON::ParserError) - end + it "parses an array" do + expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }]) + end - it "raises an error on a true bool" do - expect { subject.parse("true") }.to raise_error(JSON::ParserError) - end + it "raises an error on a string" do + expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError) + end - it "raises an error on a false bool" do - expect { subject.parse("false") }.to raise_error(JSON::ParserError) + it "raises an error on a true bool" do + expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError) + end end end describe ".parse!" do - it "parses an object" do - expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + context "legacy_mode is disabled by default" do + it "parses an object" do + expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" }) + end + + it "parses an array" do + expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) + end + + # These tests will change expectations when the gem is upgraded + + it "raises an error on a string" do + expect { subject.parse!('"foo"') }.to raise_error(JSON::ParserError) + end + + it "raises an error on a true bool" do + expect { subject.parse!("true") }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse!("false") }.to raise_error(JSON::ParserError) + end end - it "parses an array" do - expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }]) - end + context "legacy_mode is enabled" do + it "parses an object" do + expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" }) + end - it "raises an error on a string" do - expect { subject.parse!('"foo"') }.to raise_error(JSON::ParserError) - end + it "parses an array" do + expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }]) + end - it "raises an error on a true bool" do - expect { subject.parse!("true") }.to raise_error(JSON::ParserError) - end + it "raises an error on a string" do + expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError) + end - it "raises an error on a false bool" do - expect { subject.parse!("false") }.to raise_error(JSON::ParserError) + it "raises an error on a true bool" do + expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError) + end + + it "raises an error on a false bool" do + expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError) + end end end diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb index 816db49d94a..f720b3293e0 100644 --- a/spec/lib/gitlab/performance_bar_spec.rb +++ b/spec/lib/gitlab/performance_bar_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::PerformanceBar do end end - it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) } + it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) } it { expect(described_class.l2_cache_backend).to eq(Rails.cache) } describe '.enabled_for_user?' do diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb index 6c585acd5cd..cff2fd7748b 100644 --- a/spec/lib/gitlab/x509/signature_spec.rb +++ b/spec/lib/gitlab/x509/signature_spec.rb @@ -229,4 +229,164 @@ describe Gitlab::X509::Signature do end end end + + describe '#user' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + + context 'if email is assigned to a user' do + let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) } + + it 'returns user' do + expect(signature.user).to eq(user) + end + end + + it 'if email is not assigned to a user, return nil' do + expect(signature.user).to be_nil + end + end + + context 'tag signature' do + let(:certificate_attributes) do + { + subject_key_identifier: X509Helpers::User1.tag_certificate_subject_key_identifier, + subject: X509Helpers::User1.certificate_subject, + email: X509Helpers::User1.certificate_email, + serial_number: X509Helpers::User1.tag_certificate_serial + } + end + + let(:issuer_attributes) do + { + subject_key_identifier: X509Helpers::User1.tag_issuer_subject_key_identifier, + subject: X509Helpers::User1.tag_certificate_issuer, + crl_url: X509Helpers::User1.tag_certificate_crl + } + end + + context 'verified signature' do + context 'with trusted certificate store' do + before do + store = OpenSSL::X509::Store.new + certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert + store.add_cert(certificate) + allow(OpenSSL::X509::Store).to receive(:new).and_return(store) + end + + it 'returns a verified signature if email does match' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + + expect(signature.x509_certificate).to have_attributes(certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) + expect(signature.verified_signature).to be_truthy + expect(signature.verification_status).to eq(:verified) + end + + it 'returns an unverified signature if email does not match' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + "gitlab@example.com", + X509Helpers::User1.signed_commit_time + ) + + expect(signature.x509_certificate).to have_attributes(certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) + expect(signature.verified_signature).to be_truthy + expect(signature.verification_status).to eq(:unverified) + end + + it 'returns an unverified signature if email does match and time is wrong' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + Time.new(2020, 2, 22) + ) + + expect(signature.x509_certificate).to have_attributes(certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) + expect(signature.verified_signature).to be_falsey + expect(signature.verification_status).to eq(:unverified) + end + + it 'returns an unverified signature if certificate is revoked' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + + expect(signature.verification_status).to eq(:verified) + + signature.x509_certificate.revoked! + + expect(signature.verification_status).to eq(:unverified) + end + end + + context 'without trusted certificate within store' do + before do + store = OpenSSL::X509::Store.new + allow(OpenSSL::X509::Store).to receive(:new) + .and_return( + store + ) + end + + it 'returns an unverified signature' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + + expect(signature.x509_certificate).to have_attributes(certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) + expect(signature.verified_signature).to be_falsey + expect(signature.verification_status).to eq(:unverified) + end + end + end + + context 'invalid signature' do + it 'returns nil' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature.tr('A', 'B'), + X509Helpers::User1.signed_tag_base_data, + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + expect(signature.x509_certificate).to be_nil + expect(signature.verified_signature).to be_falsey + expect(signature.verification_status).to eq(:unverified) + end + end + + context 'invalid message' do + it 'returns nil' do + signature = described_class.new( + X509Helpers::User1.signed_tag_signature, + 'x', + X509Helpers::User1.certificate_email, + X509Helpers::User1.signed_commit_time + ) + expect(signature.x509_certificate).to be_nil + expect(signature.verified_signature).to be_falsey + expect(signature.verification_status).to eq(:unverified) + end + end + end end diff --git a/spec/lib/gitlab/x509/tag_spec.rb b/spec/lib/gitlab/x509/tag_spec.rb new file mode 100644 index 00000000000..4bc9723bd0d --- /dev/null +++ b/spec/lib/gitlab/x509/tag_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::X509::Tag do + subject(:signature) { described_class.new(tag).signature } + + describe '#signature' do + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } + let(:project) { create(:project, :repository) } + + describe 'signed tag' do + let(:tag) { project.repository.find_tag('v1.1.1') } + let(:certificate_attributes) do + { + subject_key_identifier: X509Helpers::User1.tag_certificate_subject_key_identifier, + subject: X509Helpers::User1.certificate_subject, + email: X509Helpers::User1.certificate_email, + serial_number: X509Helpers::User1.tag_certificate_serial + } + end + + let(:issuer_attributes) do + { + subject_key_identifier: X509Helpers::User1.tag_issuer_subject_key_identifier, + subject: X509Helpers::User1.tag_certificate_issuer, + crl_url: X509Helpers::User1.tag_certificate_crl + } + end + + it { expect(signature).not_to be_nil } + it { expect(signature.verification_status).to eq(:unverified) } + it { expect(signature.x509_certificate).to have_attributes(certificate_attributes) } + it { expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) } + end + + context 'unsigned tag' do + let(:tag) { project.repository.find_tag('v1.0.0') } + + it { expect(signature).to be_nil } + end + end +end diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index d8f940a808e..56e0d044247 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -205,11 +205,11 @@ describe CacheableAttributes do end end - it 'uses RequestStore in addition to Thread memory cache', :request_store do + it 'uses RequestStore in addition to process memory cache', :request_store do # Warm up the cache create(:application_setting).cache! - expect(ApplicationSetting.cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) + expect(ApplicationSetting.cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) expect(ApplicationSetting.cache_backend).to receive(:read).with(ApplicationSetting.cache_key).once.and_call_original 2.times { ApplicationSetting.current } diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 016af4f269b..0839dde696a 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe MergeRequestDiff do + using RSpec::Parameterized::TableSyntax + include RepoHelpers let(:diff_with_commits) { create(:merge_request).merge_request_diff } @@ -125,18 +127,71 @@ describe MergeRequestDiff do end end + describe '#update_external_diff_store' do + let_it_be(:merge_request) { create(:merge_request) } + + let(:diff) { merge_request.merge_request_diff } + let(:store) { diff.external_diff.object_store } + + where(:change_stored_externally, :change_external_diff) do + false | false + false | true + true | false + true | true + end + + with_them do + it do + diff.stored_externally = true if change_stored_externally + diff.external_diff = "new-filename" if change_external_diff + + update_store = receive(:update_column).with(:external_diff_store, store) + + if change_stored_externally || change_external_diff + expect(diff).to update_store + else + expect(diff).not_to update_store + end + + diff.save! + end + end + end + describe '#migrate_files_to_external_storage!' do + let(:uploader) { ExternalDiffUploader } + let(:file_store) { uploader::Store::LOCAL } + let(:remote_store) { uploader::Store::REMOTE } let(:diff) { create(:merge_request).merge_request_diff } - it 'converts from in-database to external storage' do + it 'converts from in-database to external file storage' do expect(diff).not_to be_stored_externally stub_external_diffs_setting(enabled: true) - expect(diff).to receive(:save!) + + expect(diff).to receive(:save!).and_call_original diff.migrate_files_to_external_storage! expect(diff).to be_stored_externally + expect(diff.external_diff_store).to eq(file_store) + end + + it 'converts from in-database to external object storage' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + + # Without direct_upload: true, the files would be saved to disk, and a + # background job would be enqueued to move the file to object storage + stub_external_diffs_object_storage(uploader, direct_upload: true) + + expect(diff).to receive(:save!).and_call_original + + diff.migrate_files_to_external_storage! + + expect(diff).to be_stored_externally + expect(diff.external_diff_store).to eq(remote_store) end it 'does nothing with an external diff' do diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index cffd5232a7e..a0d36f0a238 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -582,6 +582,79 @@ describe JiraService do end end + describe '#create_cross_reference_note' do + let_it_be(:user) { build_stubbed(:user) } + let_it_be(:project) { create(:project, :repository) } + let(:jira_service) do + described_class.new( + project: project, + url: url, + username: username, + password: password + ) + end + let(:jira_issue) { ExternalIssue.new('JIRA-123', project) } + + subject { jira_service.create_cross_reference_note(jira_issue, resource, user) } + + shared_examples 'creates a comment on Jira' do + let(:issue_url) { "#{url}/rest/api/2/issue/JIRA-123" } + let(:comment_url) { "#{issue_url}/comment" } + let(:remote_link_url) { "#{issue_url}/remotelink" } + + before do + allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) + stub_request(:get, issue_url).with(basic_auth: [username, password]) + stub_request(:post, comment_url).with(basic_auth: [username, password]) + stub_request(:post, remote_link_url).with(basic_auth: [username, password]) + end + + it 'creates a comment on Jira' do + subject + + expect(WebMock).to have_requested(:post, comment_url).with( + body: /mentioned this issue in/ + ).once + end + end + + context 'when resource is a commit' do + let(:resource) { project.commit('master') } + + context 'when disabled' do + before do + allow_next_instance_of(JiraService) do |instance| + allow(instance).to receive(:commit_events) { false } + end + end + + it { is_expected.to eq('Events for commits are disabled.') } + end + + context 'when enabled' do + it_behaves_like 'creates a comment on Jira' + end + end + + context 'when resource is a merge request' do + let(:resource) { build_stubbed(:merge_request, source_project: project) } + + context 'when disabled' do + before do + allow_next_instance_of(JiraService) do |instance| + allow(instance).to receive(:merge_requests_events) { false } + end + end + + it { is_expected.to eq('Events for merge requests are disabled.') } + end + + context 'when enabled' do + it_behaves_like 'creates a comment on Jira' + end + end + end + describe '#test' do let(:jira_service) do described_class.new( diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb index dc9f9a95d24..fec1b5418e4 100644 --- a/spec/models/snippet_repository_spec.rb +++ b/spec/models/snippet_repository_spec.rb @@ -202,6 +202,22 @@ describe SnippetRepository do it_behaves_like 'snippet repository with file names', 'snippetfile10.txt', 'snippetfile11.txt' end + + shared_examples 'snippet repository with git errors' do |path, error| + let(:new_file) { { file_path: path, content: 'bar' } } + + it 'raises a path specific error' do + expect do + snippet_repository.multi_files_action(user, data, commit_opts) + end.to raise_error(error) + end + end + + context 'with git errors' do + it_behaves_like 'snippet repository with git errors', 'invalid://path/here', described_class::InvalidPathError + it_behaves_like 'snippet repository with git errors', '../../path/traversal/here', described_class::InvalidPathError + it_behaves_like 'snippet repository with git errors', 'README', described_class::CommitError + end end def blob_at(snippet, path) diff --git a/spec/models/x509_commit_signature_spec.rb b/spec/models/x509_commit_signature_spec.rb index a2f72228a86..2efb77c96ad 100644 --- a/spec/models/x509_commit_signature_spec.rb +++ b/spec/models/x509_commit_signature_spec.rb @@ -9,6 +9,15 @@ RSpec.describe X509CommitSignature do let(:x509_certificate) { create(:x509_certificate) } let(:x509_signature) { create(:x509_commit_signature, commit_sha: commit_sha) } + let(:attributes) do + { + commit_sha: commit_sha, + project: project, + x509_certificate_id: x509_certificate.id, + verification_status: "verified" + } + end + it_behaves_like 'having unique enum values' describe 'validation' do @@ -23,15 +32,6 @@ RSpec.describe X509CommitSignature do end describe '.safe_create!' do - let(:attributes) do - { - commit_sha: commit_sha, - project: project, - x509_certificate_id: x509_certificate.id, - verification_status: "verified" - } - end - it 'finds a signature by commit sha if it existed' do x509_signature @@ -50,4 +50,18 @@ RSpec.describe X509CommitSignature do expect(signature.x509_certificate_id).to eq(x509_certificate.id) end end + + describe '#user' do + context 'if email is assigned to a user' do + let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) } + + it 'returns user' do + expect(described_class.safe_create!(attributes).user).to eq(user) + end + end + + it 'if email is not assigned to a user, return nil' do + expect(described_class.safe_create!(attributes).user).to be_nil + end + end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 5b87ec022ae..f3e6cb3cc52 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -462,7 +462,8 @@ describe SystemNoteService do describe "existing reference" do before do allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) - message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.full_path}|http://localhost/#{project.full_path}/-/commit/#{commit.id}]:\n'#{commit.title.chomp}'" + message = double('message') + allow(message).to receive(:include?) { true } allow_next_instance_of(JIRA::Resource::Issue) do |instance| allow(instance).to receive(:comments).and_return([OpenStruct.new(body: message)]) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c69ac915320..dfd248f6f22 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -209,9 +209,7 @@ RSpec.configure do |config| # expect(Gitlab::Git::KeepAround).to receive(:execute).and_call_original allow(Gitlab::Git::KeepAround).to receive(:execute) - [Gitlab::ThreadMemoryCache, Gitlab::ProcessMemoryCache].each do |cache| - cache.cache_backend.clear - end + Gitlab::ProcessMemoryCache.cache_backend.clear Sidekiq::Worker.clear_all diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index d4ac286e959..b473cdaefc1 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -45,7 +45,7 @@ module StubObjectStorage def stub_external_diffs_object_storage(uploader = described_class, **params) stub_object_storage_uploader(config: Gitlab.config.external_diffs.object_store, uploader: uploader, - remote_directory: 'external_diffs', + remote_directory: 'external-diffs', **params) end diff --git a/spec/support/helpers/x509_helpers.rb b/spec/support/helpers/x509_helpers.rb index 9ea997bf5f4..ce0fa268ace 100644 --- a/spec/support/helpers/x509_helpers.rb +++ b/spec/support/helpers/x509_helpers.rb @@ -173,22 +173,155 @@ module X509Helpers Time.at(1561027326) end + def signed_tag_signature + <<~SIGNATURE + -----BEGIN SIGNED MESSAGE----- + MIISfwYJKoZIhvcNAQcCoIIScDCCEmwCAQExDTALBglghkgBZQMEAgEwCwYJKoZI + hvcNAQcBoIIP8zCCB3QwggVcoAMCAQICBBXXLOIwDQYJKoZIhvcNAQELBQAwgbYx + CzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVu + MRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwU + U2llbWVucyBUcnVzdCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBD + QSBNZWRpdW0gU3RyZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjAeFw0xNzAyMDMw + NjU4MzNaFw0yMDAyMDMwNjU4MzNaMFsxETAPBgNVBAUTCFowMDBOV0RIMQ4wDAYD + VQQqDAVSb2dlcjEOMAwGA1UEBAwFTWVpZXIxEDAOBgNVBAoMB1NpZW1lbnMxFDAS + BgNVBAMMC01laWVyIFJvZ2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAuBNea/68ZCnHYQjpm/k3ZBG0wBpEKSwG6lk9CEQlSxsqVLQHAoAKBIlJm1in + YVLcK/Sq1yhYJ/qWcY/M53DhK2rpPuhtrWJUdOUy8EBWO20F4bd4Fw9pO7jt8bme + u33TSrK772vKjuppzB6SeG13Cs08H+BIeD106G27h7ufsO00pvsxoSDL+uc4slnr + pBL+2TAL7nSFnB9QHWmRIK27SPqJE+lESdb0pse11x1wjvqKy2Q7EjL9fpqJdHzX + NLKHXd2r024TOORTa05DFTNR+kQEKKV96XfpYdtSBomXNQ44cisiPBJjFtYvfnFE + wgrHa8fogn/b0C+A+HAoICN12wIDAQABo4IC4jCCAt4wHQYDVR0OBBYEFCF+gkUp + XQ6xGc0kRWXuDFxzA14zMEMGA1UdEQQ8MDqgIwYKKwYBBAGCNxQCA6AVDBNyLm1l + aWVyQHNpZW1lbnMuY29tgRNyLm1laWVyQHNpZW1lbnMuY29tMA4GA1UdDwEB/wQE + AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwgcoGA1UdHwSBwjCB + vzCBvKCBuaCBtoYmaHR0cDovL2NoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5j + cmyGQWxkYXA6Ly9jbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jZXJ0 + aWZpY2F0ZVJldm9jYXRpb25MaXN0hklsZGFwOi8vY2wuc2llbWVucy5jb20vQ049 + WlpaWlpaQTYsbz1UcnVzdGNlbnRlcj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0 + MEUGA1UdIAQ+MDwwOgYNKwYBBAGhaQcCAgMBAzApMCcGCCsGAQUFBwIBFhtodHRw + Oi8vd3d3LnNpZW1lbnMuY29tL3BraS8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW + gBT4FV1HDGx3e3LEAheRaKK292oJRDCCAQQGCCsGAQUFBwEBBIH3MIH0MDIGCCsG + AQUFBzAChiZodHRwOi8vYWguc2llbWVucy5jb20vcGtpP1paWlpaWkE2LmNydDBB + BggrBgEFBQcwAoY1bGRhcDovL2FsLnNpZW1lbnMubmV0L0NOPVpaWlpaWkE2LEw9 + UEtJP2NBQ2VydGlmaWNhdGUwSQYIKwYBBQUHMAKGPWxkYXA6Ly9hbC5zaWVtZW5z + LmNvbS9DTj1aWlpaWlpBNixvPVRydXN0Y2VudGVyP2NBQ2VydGlmaWNhdGUwMAYI + KwYBBQUHMAGGJGh0dHA6Ly9vY3NwLnBraS1zZXJ2aWNlcy5zaWVtZW5zLmNvbTAN + BgkqhkiG9w0BAQsFAAOCAgEAXPVcX6vaEcszJqg5IemF9aFTlwTrX5ITNIpzcqG+ + kD5haOf2mZYLjl+MKtLC1XfmIsGCUZNb8bjP6QHQEI+2d6x/ZOqPq7Kd7PwVu6x6 + xZrkDjUyhUbUntT5+RBy++l3Wf6Cq6Kx+K8ambHBP/bu90/p2U8KfFAG3Kr2gI2q + fZrnNMOxmJfZ3/sXxssgLkhbZ7hRa+MpLfQ6uFsSiat3vlawBBvTyHnoZ/7oRc8y + qi6QzWcd76CPpMElYWibl+hJzKbBZUWvc71AzHR6i1QeZ6wubYz7vr+FF5Y7tnxB + Vz6omPC9XAg0F+Dla6Zlz3Awj5imCzVXa+9SjtnsidmJdLcKzTAKyDewewoxYOOJ + j3cJU7VSjJPl+2fVmDBaQwcNcUcu/TPAKApkegqO7tRF9IPhjhW8QkRnkqMetO3D + OXmAFVIsEI0Hvb2cdb7B6jSpjGUuhaFm9TCKhQtCk2p8JCDTuaENLm1x34rrJKbT + 2vzyYN0CZtSkUdgD4yQxK9VWXGEzexRisWb4AnZjD2NAquLPpXmw8N0UwFD7MSpC + dpaX7FktdvZmMXsnGiAdtLSbBgLVWOD1gmJFDjrhNbI8NOaOaNk4jrfGqNh5lhGU + 4DnBT2U6Cie1anLmFH/oZooAEXR2o3Nu+1mNDJChnJp0ovs08aa3zZvBdcloOvfU + qdowggh3MIIGX6ADAgECAgQtyi/nMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYDVQQG + EwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UE + CgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaQTExHTAbBgNVBAsMFFNpZW1lbnMg + VHJ1c3QgQ2VudGVyMSIwIAYDVQQDDBlTaWVtZW5zIFJvb3QgQ0EgVjMuMCAyMDE2 + MB4XDTE2MDcyMDEzNDYxMFoXDTIyMDcyMDEzNDYxMFowgbYxCzAJBgNVBAYTAkRF + MQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQKDAdT + aWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVz + dCBDZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3Ry + ZW5ndGggQXV0aGVudGljYXRpb24gMjAxNjCCAiIwDQYJKoZIhvcNAQEBBQADggIP + ADCCAgoCggIBAL9UfK+JAZEqVMVvECdYF9IK4KSw34AqyNl3rYP5x03dtmKaNu+2 + 0fQqNESA1NGzw3s6LmrKLh1cR991nB2cvKOXu7AvEGpSuxzIcOROd4NpvRx+Ej1p + JIPeqf+ScmVK7lMSO8QL/QzjHOpGV3is9sG+ZIxOW9U1ESooy4Hal6ZNs4DNItsz + piCKqm6G3et4r2WqCy2RRuSqvnmMza7Y8BZsLy0ZVo5teObQ37E/FxqSrbDI8nxn + B7nVUve5ZjrqoIGSkEOtyo11003dVO1vmWB9A0WQGDqE/q3w178hGhKfxzRaqzyi + SoADUYS2sD/CglGTUxVq6u0pGLLsCFjItcCWqW+T9fPYfJ2CEd5b3hvqdCn+pXjZ + /gdX1XAcdUF5lRnGWifaYpT9n4s4adzX8q6oHSJxTppuAwLRKH6eXALbGQ1I9lGQ + DSOipD/09xkEsPw6HOepmf2U3YxZK1VU2sHqugFJboeLcHMzp6E1n2ctlNG1GKE9 + FDHmdyFzDi0Nnxtf/GgVjnHF68hByEE1MYdJ4nJLuxoT9hyjYdRW9MpeNNxxZnmz + W3zh7QxIqP0ZfIz6XVhzrI9uZiqwwojDiM5tEOUkQ7XyW6grNXe75yt6mTj89LlB + H5fOW2RNmCy/jzBXDjgyskgK7kuCvUYTuRv8ITXbBY5axFA+CpxZqokpAgMBAAGj + ggKmMIICojCCAQUGCCsGAQUFBwEBBIH4MIH1MEEGCCsGAQUFBzAChjVsZGFwOi8v + YWwuc2llbWVucy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/Y0FDZXJ0aWZpY2F0ZTAy + BggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBMS5j + cnQwSgYIKwYBBQUHMAKGPmxkYXA6Ly9hbC5zaWVtZW5zLmNvbS91aWQ9WlpaWlpa + QTEsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRw + Oi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAUcG2g + UOyp0CxnnRkV/v0EczXD4tQwEgYDVR0TAQH/BAgwBgEB/wIBADBABgNVHSAEOTA3 + MDUGCCsGAQQBoWkHMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuc2llbWVucy5j + b20vcGtpLzCBxwYDVR0fBIG/MIG8MIG5oIG2oIGzhj9sZGFwOi8vY2wuc2llbWVu + cy5uZXQvQ049WlpaWlpaQTEsTD1QS0k/YXV0aG9yaXR5UmV2b2NhdGlvbkxpc3SG + Jmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTEuY3JshkhsZGFwOi8v + Y2wuc2llbWVucy5jb20vdWlkPVpaWlpaWkExLG89VHJ1c3RjZW50ZXI/YXV0aG9y + aXR5UmV2b2NhdGlvbkxpc3QwJwYDVR0lBCAwHgYIKwYBBQUHAwIGCCsGAQUFBwME + BggrBgEFBQcDCTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPgVXUcMbHd7csQC + F5Foorb3aglEMA0GCSqGSIb3DQEBCwUAA4ICAQBw+sqMp3SS7DVKcILEmXbdRAg3 + lLO1r457KY+YgCT9uX4VG5EdRKcGfWXK6VHGCi4Dos5eXFV34Mq/p8nu1sqMuoGP + YjHn604eWDprhGy6GrTYdxzcE/GGHkpkuE3Ir/45UcmZlOU41SJ9SNjuIVrSHMOf + ccSY42BCspR/Q1Z/ykmIqQecdT3/Kkx02GzzSN2+HlW6cEO4GBW5RMqsvd2n0h2d + fe2zcqOgkLtx7u2JCR/U77zfyxG3qXtcymoz0wgSHcsKIl+GUjITLkHfS9Op8V7C + Gr/dX437sIg5pVHmEAWadjkIzqdHux+EF94Z6kaHywohc1xG0KvPYPX7iSNjkvhz + 4NY53DHmxl4YEMLffZnaS/dqyhe1GTpcpyN8WiR4KuPfxrkVDOsuzWFtMSvNdlOV + gdI0MXcLMP+EOeANZWX6lGgJ3vWyemo58nzgshKd24MY3w3i6masUkxJH2KvI7UH + /1Db3SC8oOUjInvSRej6M3ZhYWgugm6gbpUgFoDw/o9Cg6Qm71hY0JtcaPC13rzm + N8a2Br0+Fa5e2VhwLmAxyfe1JKzqPwuHT0S5u05SQghL5VdzqfA8FCL/j4XC9yI6 + csZTAQi73xFQYVjZt3+aoSz84lOlTmVo/jgvGMY/JzH9I4mETGgAJRNj34Z/0meh + M+pKWCojNH/dgyJSwDGCAlIwggJOAgEBMIG/MIG2MQswCQYDVQQGEwJERTEPMA0G + A1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2llbWVu + czERMA8GA1UEBRMIWlpaWlpaQTYxHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3QgQ2Vu + dGVyMT8wPQYDVQQDDDZTaWVtZW5zIElzc3VpbmcgQ0EgTWVkaXVtIFN0cmVuZ3Ro + IEF1dGhlbnRpY2F0aW9uIDIwMTYCBBXXLOIwCwYJYIZIAWUDBAIBoGkwHAYJKoZI + hvcNAQkFMQ8XDTE5MTEyMDE0NTYyMFowLwYJKoZIhvcNAQkEMSIEIJDnZUpcVLzC + OdtpkH8gtxwLPIDE0NmAmFC9uM8q2z+OMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0B + BwEwCwYJKoZIhvcNAQEBBIIBAH/Pqv2xp3a0jSPkwU1K3eGA/1lfoNJMUny4d/PS + LVWlkgrmedXdLmuBzAGEaaZOJS0lEpNd01pR/reHs7xxZ+RZ0olTs2ufM0CijQSx + OL9HDl2O3OoD77NWx4tl3Wy1yJCeV3XH/cEI7AkKHCmKY9QMoMYWh16ORBtr+YcS + YK+gONOjpjgcgTJgZ3HSFgQ50xiD4WT1kFBHsuYsLqaOSbTfTN6Ayyg4edjrPQqa + VcVf1OQcIrfWA3yMQrnEZfOYfN/D4EPjTfxBV+VCi/F2bdZmMbJ7jNk1FbewSwWO + SDH1i0K32NyFbnh0BSos7njq7ELqKlYBsoB/sZfaH2vKy5U= + -----END SIGNED MESSAGE----- + SIGNATURE + end + + def signed_tag_base_data + <<~SIGNEDDATA + object 189a6c924013fc3fe40d6f1ec1dc20214183bc97 + type commit + tag v1.1.1 + tagger Roger Meier 1574261780 +0100 + + x509 signed tag + SIGNEDDATA + end + def certificate_crl 'http://ch.siemens.com/pki?ZZZZZZA2.crl' end + def tag_certificate_crl + 'http://ch.siemens.com/pki?ZZZZZZA6.crl' + end + def certificate_serial 1810356222 end + def tag_certificate_serial + 3664232660 + end + def certificate_subject_key_identifier 'EC:00:B5:28:02:5C:D3:A5:A1:AB:C2:A1:34:81:84:AA:BF:9B:CF:F8' end + def tag_certificate_subject_key_identifier + '21:7E:82:45:29:5D:0E:B1:19:CD:24:45:65:EE:0C:5C:73:03:5E:33' + end + def issuer_subject_key_identifier 'BD:BD:2A:43:22:3D:48:4A:57:7E:98:31:17:A9:70:9D:EE:9F:A8:99' end + def tag_issuer_subject_key_identifier + 'F8:15:5D:47:0C:6C:77:7B:72:C4:02:17:91:68:A2:B6:F7:6A:09:44' + end + def certificate_email 'r.meier@siemens.com' end @@ -197,6 +330,10 @@ module X509Helpers 'CN=Siemens Issuing CA EE Auth 2016,OU=Siemens Trust Center,serialNumber=ZZZZZZA2,O=Siemens,L=Muenchen,ST=Bayern,C=DE' end + def tag_certificate_issuer + 'CN=Siemens Issuing CA Medium Strength Authentication 2016,OU=Siemens Trust Center,serialNumber=ZZZZZZA6,O=Siemens,L=Muenchen,ST=Bayern,C=DE' + end + def certificate_subject 'CN=Meier Roger,O=Siemens,SN=Meier,GN=Roger,serialNumber=Z000NWDH' end diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb index 272ac97604a..95f5b427693 100644 --- a/spec/views/projects/services/_form.haml_spec.rb +++ b/spec/views/projects/services/_form.haml_spec.rb @@ -29,20 +29,5 @@ describe 'projects/services/_form' do expect(rendered).to have_content('Event will be triggered when a commit is created/updated') expect(rendered).to have_content('Event will be triggered when a merge request is created/updated/merged') end - - context 'when service is Jira' do - let(:project) { create(:jira_project) } - - before do - assign(:service, project.jira_service) - end - - it 'display merge_request_events and commit_events descriptions' do - render - - expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a commit.') - expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a merge request.') - end - end end end