summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue17
-rw-r--r--app/controllers/projects/commit_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/helpers/commits_helper.rb8
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/ci/job_artifact.rb10
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/merge_request.rb35
-rw-r--r--app/serializers/merge_request_widget_entity.rb4
-rw-r--r--app/views/groups/settings/packages_and_registries/index.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml7
-rw-r--r--changelogs/unreleased/gy-artifact-download-multiple-types.yml5
-rw-r--r--changelogs/unreleased/paginate-commit.yml5
-rw-r--r--changelogs/unreleased/sh-handle-fog-host-params.yml5
-rw-r--r--changelogs/unreleased/vs-fix-margin-for-rebase-based-workflows.yml5
-rw-r--r--config/feature_flags/development/paginate_commit_view.yml8
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb9
-rw-r--r--lib/object_storage/config.rb36
-rw-r--r--lib/object_storage/direct_upload.rb4
-rw-r--r--spec/factories/ci/builds.rb18
-rw-r--r--spec/factories/ci/job_artifacts.rb20
-rw-r--r--spec/factories/ci/pipelines.rb24
-rw-r--r--spec/factories/merge_requests.rb24
-rw-r--r--spec/features/commit_spec.rb78
-rw-r--r--spec/features/groups/settings/packages_and_registries_spec.rb18
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report.json983
-rw-r--r--spec/fixtures/security_reports/master/gl-secret-detection-report.json33
-rw-r--r--spec/helpers/commits_helper_spec.rb73
-rw-r--r--spec/lib/object_storage/config_spec.rb63
-rw-r--r--spec/models/ci/build_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb74
32 files changed, 1573 insertions, 22 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 5127ab3d400..6d66a45f382 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -181,17 +181,12 @@ export default {
>
{{ __('Rebase') }}
</gl-button>
- <span
- v-if="!rebasingError"
- class="gl-font-weight-bold gl-ml-0!"
- data-testid="rebase-message"
- >{{
- __(
- 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
- )
- }}</span
- >
- <span v-else class="gl-font-weight-bold danger gl-ml-0!" data-testid="rebase-message">{{
+ <span v-if="!rebasingError" class="gl-font-weight-bold" data-testid="rebase-message">{{
+ __(
+ 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
+ )
+ }}</span>
+ <span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
rebasingError
}}</span>
</div>
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index b694efbc1eb..ffdd9fca95b 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -23,6 +23,7 @@ class Projects::CommitController < Projects::ApplicationController
end
BRANCH_SEARCH_LIMIT = 1000
+ COMMIT_DIFFS_PER_PAGE = 75
feature_category :source_code_management
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 973e43831f1..efbb958cbae 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -168,6 +168,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
}
end
+ def sast_reports
+ reports_response(merge_request.compare_sast_reports(current_user), head_pipeline)
+ end
+
+ def secret_detection_reports
+ reports_response(merge_request.compare_secret_detection_reports(current_user), head_pipeline)
+ end
+
def context_commits
return render_404 unless project.context_commits_enabled?
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 2d4ace5a5bf..f5c75d62097 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -126,6 +126,14 @@ module CommitsHelper
%w(btn gpg-status-box) + Array(additional_classes)
end
+ def conditionally_paginate_diff_files(diffs, paginate:, per: Projects::CommitController::COMMIT_DIFFS_PER_PAGE)
+ if paginate && Feature.enabled?(:paginate_commit_view, @project, type: :development)
+ Kaminari.paginate_array(diffs.diff_files.to_a).page(params[:page]).per(per)
+ else
+ diffs.diff_files
+ end
+ end
+
protected
# Private: Returns a link to a person. If the person has a matching user and
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a9bd890aa4b..db151126caf 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -786,7 +786,9 @@ module Ci
end
def artifacts_file_for_type(type)
- job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
+ file_types = Ci::JobArtifact.associated_file_types_for(type)
+ file_types_ids = file_types&.map { |file_type| Ci::JobArtifact.file_types[file_type] }
+ job_artifacts.find_by(file_type: file_types_ids)&.file
end
def coverage_regex
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index f13be3b3c86..f927111758a 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -19,6 +19,8 @@ module Ci
NON_ERASABLE_FILE_TYPES = %w[trace].freeze
TERRAFORM_REPORT_FILE_TYPES = %w[terraform].freeze
UNSUPPORTED_FILE_TYPES = %i[license_management].freeze
+ SAST_REPORT_TYPES = %w[sast].freeze
+ SECRET_DETECTION_REPORT_TYPES = %w[secret_detection].freeze
DEFAULT_FILE_NAMES = {
archive: nil,
metadata: nil,
@@ -150,6 +152,14 @@ module Ci
with_file_types(REPORT_TYPES.keys.map(&:to_s))
end
+ scope :sast_reports, -> do
+ with_file_types(SAST_REPORT_TYPES)
+ end
+
+ scope :secret_detection_reports, -> do
+ with_file_types(SECRET_DETECTION_REPORT_TYPES)
+ end
+
scope :test_reports, -> do
with_file_types(TEST_REPORT_FILE_TYPES)
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 54d7e92f782..58aaadd5d49 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1219,6 +1219,16 @@ module Ci
false
end
+ def security_reports(report_types: [])
+ reports_scope = report_types.empty? ? ::Ci::JobArtifact.security_reports : ::Ci::JobArtifact.security_reports(file_types: report_types)
+
+ ::Gitlab::Ci::Reports::Security::Reports.new(self).tap do |security_reports|
+ latest_report_builds(reports_scope).each do |build|
+ build.collect_security_reports!(security_reports)
+ end
+ end
+ end
+
private
def add_message(severity, content)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index e41781dce4f..5fad876d3fb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1554,6 +1554,26 @@ class MergeRequest < ApplicationRecord
end || { status: :parsing }
end
+ def has_sast_reports?
+ !!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.sast_reports)
+ end
+
+ def has_secret_detection_reports?
+ !!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.secret_detection_reports)
+ end
+
+ def compare_sast_reports(current_user)
+ return missing_report_error("SAST") unless has_sast_reports?
+
+ compare_reports(::Ci::CompareSecurityReportsService, current_user, 'sast')
+ end
+
+ def compare_secret_detection_reports(current_user)
+ return missing_report_error("secret detection") unless has_secret_detection_reports?
+
+ compare_reports(::Ci::CompareSecurityReportsService, current_user, 'secret_detection')
+ end
+
def calculate_reactive_cache(identifier, current_user_id = nil, report_type = nil, *args)
service_class = identifier.constantize
@@ -1799,8 +1819,19 @@ class MergeRequest < ApplicationRecord
merge_request_reviewers.find_by(user_id: user.id)
end
+ def enabled_reports
+ {
+ sast: report_type_enabled?(:sast),
+ secret_detection: report_type_enabled?(:secret_detection)
+ }
+ end
+
private
+ def missing_report_error(report_type)
+ { status: :error, status_reason: "This merge request does not have #{report_type} reports" }
+ end
+
def with_rebase_lock
if Feature.enabled?(:merge_request_rebase_nowait_lock, default_enabled: true)
with_retried_nowait_lock { yield }
@@ -1842,6 +1873,10 @@ class MergeRequest < ApplicationRecord
key = Gitlab::Routing.url_helpers.cached_widget_project_json_merge_request_path(project, self, format: :json)
Gitlab::EtagCaching::Store.new.touch(key)
end
+
+ def report_type_enabled?(report_type)
+ !!actual_head_pipeline&.batch_lookup_report_artifact_for_file_type(report_type)
+ end
end
MergeRequest.prepend_if_ee('::EE::MergeRequest')
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index ca4e16bc5ff..560dd2ea08b 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -133,6 +133,10 @@ class MergeRequestWidgetEntity < Grape::Entity
help_page_path('user/application_security/index.md', anchor: 'viewing-security-scan-information-in-merge-requests')
end
+ expose :enabled_reports do |merge_request|
+ merge_request.enabled_reports
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/views/groups/settings/packages_and_registries/index.html.haml b/app/views/groups/settings/packages_and_registries/index.html.haml
index b6bd16d51a6..1a12ad4902b 100644
--- a/app/views/groups/settings/packages_and_registries/index.html.haml
+++ b/app/views/groups/settings/packages_and_registries/index.html.haml
@@ -2,4 +2,4 @@
- page_title _('Packages & Registries')
- @content_class = 'limit-container-width' unless fluid_layout
-%section#js-packages-and-registries-settings{ data: { default_expanded: expanded_by_default?.to_s, group_path: @group.path } }
+%section#js-packages-and-registries-settings{ data: { default_expanded: expanded_by_default?.to_s, group_path: @group.full_path } }
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index afe97a06400..5652b503a6d 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -12,7 +12,7 @@
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
= render "ci_menu"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-commit"
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-commit", paginate_diffs: true
.limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 8364311796f..2f533b5848d 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,9 +1,10 @@
- environment = local_assigns.fetch(:environment, nil)
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
-- diff_files = diffs.diff_files
- diff_page_context = local_assigns.fetch(:diff_page_context, nil)
- load_diff_files_async = Feature.enabled?(:async_commit_diff_files, @project) && diff_page_context == "is-commit"
+- paginate_diffs = local_assigns.fetch(:paginate_diffs, false) && !load_diff_files_async && Feature.enabled?(:paginate_commit_view, @project, type: :development)
+- diff_files = conditionally_paginate_diff_files(diffs, paginate: paginate_diffs)
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.files-changed-inner
@@ -27,7 +28,6 @@
- if render_overflow_warning?(diffs)
= render 'projects/diffs/warning', diff_files: diffs
-
.files{ data: { can_create_note: can_create_note } }
- if load_diff_files_async
- url = url_for(safe_params.merge(action: 'diff_files'))
@@ -36,3 +36,6 @@
%span.spinner.spinner-md
- else
= render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context }
+
+ - if paginate_diffs
+ = paginate(diff_files, theme: "gitlab")
diff --git a/changelogs/unreleased/gy-artifact-download-multiple-types.yml b/changelogs/unreleased/gy-artifact-download-multiple-types.yml
new file mode 100644
index 00000000000..15f9fa78372
--- /dev/null
+++ b/changelogs/unreleased/gy-artifact-download-multiple-types.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust job report artifacts downloads to handle multiple types
+merge_request: 53141
+author:
+type: fixed
diff --git a/changelogs/unreleased/paginate-commit.yml b/changelogs/unreleased/paginate-commit.yml
new file mode 100644
index 00000000000..c75435cdf3b
--- /dev/null
+++ b/changelogs/unreleased/paginate-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Paginate single commit view
+merge_request: 52819
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-handle-fog-host-params.yml b/changelogs/unreleased/sh-handle-fog-host-params.yml
new file mode 100644
index 00000000000..4cfcac84b6d
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-fog-host-params.yml
@@ -0,0 +1,5 @@
+---
+title: Support fog-aws host options for Workhorse S3 client
+merge_request: 53326
+author:
+type: fixed
diff --git a/changelogs/unreleased/vs-fix-margin-for-rebase-based-workflows.yml b/changelogs/unreleased/vs-fix-margin-for-rebase-based-workflows.yml
new file mode 100644
index 00000000000..1815933af09
--- /dev/null
+++ b/changelogs/unreleased/vs-fix-margin-for-rebase-based-workflows.yml
@@ -0,0 +1,5 @@
+---
+title: Fix left margin of Merge button in FF merge mode
+merge_request: 53756
+author:
+type: fixed
diff --git a/config/feature_flags/development/paginate_commit_view.yml b/config/feature_flags/development/paginate_commit_view.yml
new file mode 100644
index 00000000000..ee89788a219
--- /dev/null
+++ b/config/feature_flags/development/paginate_commit_view.yml
@@ -0,0 +1,8 @@
+---
+name: paginate_commit_view
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52819
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300540
+milestone: '13.9'
+type: development
+group: group::source code
+default_enabled: false
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 7701916a855..8af7b68d78e 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -19,6 +19,9 @@ module Gitlab
ensure_repository_does_not_exist!
repository.create_from_bundle(path_to_bundle)
+ update_importable_repository_info
+
+ true
rescue => e
shared.error(e)
false
@@ -32,6 +35,10 @@ module Gitlab
attr_accessor :path_to_bundle, :shared
+ def update_importable_repository_info
+ # No-op. Overridden in EE
+ end
+
def ensure_repository_does_not_exist!
if repository.exists?
shared.logger.info(
@@ -44,3 +51,5 @@ module Gitlab
end
end
end
+
+Gitlab::ImportExport::RepoRestorer.prepend_if_ee('EE::Gitlab::ImportExport::RepoRestorer')
diff --git a/lib/object_storage/config.rb b/lib/object_storage/config.rb
index f933d4e4866..0e6408b4917 100644
--- a/lib/object_storage/config.rb
+++ b/lib/object_storage/config.rb
@@ -2,6 +2,8 @@
module ObjectStorage
class Config
+ include Gitlab::Utils::StrongMemoize
+
AWS_PROVIDER = 'AWS'
AZURE_PROVIDER = 'AzureRM'
GOOGLE_PROVIDER = 'Google'
@@ -66,6 +68,36 @@ module ObjectStorage
def provider
credentials[:provider].to_s
end
+
+ # This method converts fog-aws parameters to an endpoint for the
+ # Workhorse S3 client.
+ def s3_endpoint
+ strong_memoize(:s3_endpoint) do
+ # We could omit this line and let the following code handle this, but
+ # this will ensure that working configurations that use `endpoint`
+ # will continue to work.
+ next credentials[:endpoint] if credentials[:endpoint].present?
+
+ generate_s3_endpoint_from_credentials
+ end
+ end
+
+ def generate_s3_endpoint_from_credentials
+ # fog-aws has special handling of the host, region, scheme, etc:
+ # https://github.com/fog/fog-aws/blob/c7a11ba377a76d147861d0e921eb1e245bc11b6c/lib/fog/aws/storage.rb#L440-L449
+ # Rather than reimplement this, we derive it from a sample GET URL.
+ url = fog_connection.get_object_url(bucket, "tmp", nil)
+ uri = ::Addressable::URI.parse(url)
+
+ return unless uri&.scheme && uri&.host
+
+ endpoint = "#{uri.scheme}://#{uri.host}"
+ endpoint += ":#{uri.port}" if uri.port
+ endpoint
+ rescue ::URI::InvalidComponentError, ::Addressable::URI::InvalidURIError => e
+ Gitlab::ErrorTracking.track_exception(e)
+ nil
+ end
# End AWS-specific options
# Begin Azure-specific options
@@ -91,6 +123,10 @@ module ObjectStorage
end
end
+ def fog_connection
+ @connection ||= ::Fog::Storage.new(credentials)
+ end
+
private
# This returns a Hash of HTTP encryption headers to send along to S3.
diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb
index 7f1c30e574d..9fb4b571e06 100644
--- a/lib/object_storage/direct_upload.rb
+++ b/lib/object_storage/direct_upload.rb
@@ -80,7 +80,7 @@ module ObjectStorage
S3Config: {
Bucket: bucket_name,
Region: credentials[:region],
- Endpoint: credentials[:endpoint],
+ Endpoint: config.s3_endpoint,
PathStyle: config.use_path_style?,
UseIamProfile: config.use_iam_profile?,
ServerSideEncryption: config.server_side_encryption,
@@ -229,7 +229,7 @@ module ObjectStorage
end
def connection
- @connection ||= ::Fog::Storage.new(credentials)
+ config.fog_connection
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0f4216708d2..c4f9a4ce82b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -290,6 +290,24 @@ FactoryBot.define do
end
end
+ trait :codequality_report do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :codequality, job: build)
+ end
+ end
+
+ trait :sast_report do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :sast, job: build)
+ end
+ end
+
+ trait :secret_detection_report do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :secret_detection, job: build)
+ end
+ end
+
trait :test_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :junit, job: build)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index ad98e9d1f24..bfd8506566b 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -269,6 +269,26 @@ FactoryBot.define do
end
end
+ trait :sast do
+ file_type { :sast }
+ file_format { :raw }
+
+ after(:build) do |artifact, _|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report.json'), 'application/json')
+ end
+ end
+
+ trait :secret_detection do
+ file_type { :secret_detection }
+ file_format { :raw }
+
+ after(:build) do |artifact, _|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/security_reports/master/gl-secret-detection-report.json'), 'application/json')
+ end
+ end
+
trait :lsif do
file_type { :lsif }
file_format { :zip }
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 530bb0cd25b..e0d7ad3c133 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -93,6 +93,30 @@ FactoryBot.define do
end
end
+ trait :with_codequality_report do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :codequality_report, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
+ trait :with_sast_report do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :sast_report, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
+ trait :with_secret_detection_report do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :secret_detection_report, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :with_test_reports do
status { :success }
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index ecdb1c95866..40068d0da0d 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -221,6 +221,30 @@ FactoryBot.define do
end
end
+ trait :with_sast_reports do
+ after(:build) do |merge_request|
+ merge_request.head_pipeline = build(
+ :ci_pipeline,
+ :success,
+ :with_sast_report,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+ end
+
+ trait :with_secret_detection_reports do
+ after(:build) do |merge_request|
+ merge_request.head_pipeline = build(
+ :ci_pipeline,
+ :success,
+ :with_secret_detection_report,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+ end
+
trait :with_exposed_artifacts do
after(:build) do |merge_request|
merge_request.head_pipeline = build(
diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb
new file mode 100644
index 00000000000..02754cc803e
--- /dev/null
+++ b/spec/features/commit_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Commit' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ describe "single commit view" do
+ let(:commit) do
+ project.repository.commits(nil, limit: 100).find do |commit|
+ commit.diffs.size > 1
+ end
+ end
+
+ let(:files) { commit.diffs.diff_files.to_a }
+
+ before do
+ stub_feature_flags(async_commit_diff_files: false)
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe "commit details" do
+ before do
+ visit project_commit_path(project, commit)
+ end
+
+ it "shows the short commit message" do
+ expect(page).to have_content(commit.title)
+ end
+
+ it "reports the correct number of total changes" do
+ expect(page).to have_content("Changes #{commit.diffs.size}")
+ end
+ end
+
+ context "pagination enabled" do
+ before do
+ stub_feature_flags(paginate_commit_view: true)
+ stub_const("Projects::CommitController::COMMIT_DIFFS_PER_PAGE", 1)
+
+ visit project_commit_path(project, commit)
+ end
+
+ it "shows an adjusted count for changed files on this page" do
+ expect(page).to have_content("Showing 1 changed file")
+ end
+
+ it "shows only the first diff on the first page" do
+ expect(page).to have_selector(".files ##{files[0].file_hash}")
+ expect(page).not_to have_selector(".files ##{files[1].file_hash}")
+ end
+
+ it "can navigate to the second page" do
+ within(".files .gl-pagination") do
+ click_on("2")
+ end
+
+ expect(page).not_to have_selector(".files ##{files[0].file_hash}")
+ expect(page).to have_selector(".files ##{files[1].file_hash}")
+ end
+ end
+
+ context "pagination disabled" do
+ before do
+ stub_feature_flags(paginate_commit_view: false)
+
+ visit project_commit_path(project, commit)
+ end
+
+ it "shows both diffs on the page" do
+ expect(page).to have_selector(".files ##{files[0].file_hash}")
+ expect(page).to have_selector(".files ##{files[1].file_hash}")
+ end
+ end
+ end
+end
diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb
index d53b7603058..e4078fae956 100644
--- a/spec/features/groups/settings/packages_and_registries_spec.rb
+++ b/spec/features/groups/settings/packages_and_registries_spec.rb
@@ -7,9 +7,11 @@ RSpec.describe 'Group Packages & Registries settings' do
let(:user) { create(:user) }
let(:group) { create(:group) }
+ let(:sub_group) { create(:group, parent: group) }
before do
group.add_owner(user)
+ sub_group.add_owner(user)
sign_in(user)
end
@@ -85,6 +87,18 @@ RSpec.describe 'Group Packages & Registries settings' do
expect(page).to have_content('is an invalid regexp')
end
+
+ context 'in a sub group' do
+ it 'works correctly', :js do
+ visit_sub_group_settings_page
+
+ expect(page).to have_content('Allow duplicates')
+
+ find('.gl-toggle').click
+
+ expect(page).to have_content('Do not allow duplicates')
+ end
+ end
end
def find_settings_menu
@@ -94,4 +108,8 @@ RSpec.describe 'Group Packages & Registries settings' do
def visit_settings_page
visit group_settings_packages_and_registries_path(group)
end
+
+ def visit_sub_group_settings_page
+ visit group_settings_packages_and_registries_path(sub_group)
+ end
end
diff --git a/spec/fixtures/security_reports/master/gl-sast-report.json b/spec/fixtures/security_reports/master/gl-sast-report.json
new file mode 100644
index 00000000000..98bb15e349f
--- /dev/null
+++ b/spec/fixtures/security_reports/master/gl-sast-report.json
@@ -0,0 +1,983 @@
+{
+ "version": "1.2",
+ "vulnerabilities": [
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - fopen",
+ "value": "fopen"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - strcpy",
+ "value": "strcpy"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+ ],
+ "remediations": [],
+ "scan": {
+ "scanner": {
+ "id": "gosec",
+ "name": "Gosec",
+ "url": "https://github.com/securego/gosec",
+ "vendor": {
+ "name": "GitLab"
+ },
+ "version": "2.3.0"
+ },
+ "type": "sast",
+ "status": "success",
+ "start_time": "placeholder-value",
+ "end_time": "placeholder-value"
+ }
+}
diff --git a/spec/fixtures/security_reports/master/gl-secret-detection-report.json b/spec/fixtures/security_reports/master/gl-secret-detection-report.json
new file mode 100644
index 00000000000..f0250ec9145
--- /dev/null
+++ b/spec/fixtures/security_reports/master/gl-secret-detection-report.json
@@ -0,0 +1,33 @@
+{
+ "version": "3.0",
+ "vulnerabilities": [
+ {
+ "id": "27d2322d519c94f803ffed1cf6d14e455df97e5a0668e229eb853fdb0d277d2c",
+ "category": "secret_detection",
+ "name": "AWS API key",
+ "message": "AWS API key",
+ "description": "Historic AWS secret has been found in commit 0830d9e4c0b43c0533cde798841b499e9df0653a.",
+ "cve": "aws-key.py:e275768c071cf6a6ea70a70b40f27c98debfe26bfe623c1539ec21c4478c6fca:AWS",
+ "severity": "Critical",
+ "confidence": "Unknown",
+ "scanner": {
+ "id": "gitleaks",
+ "name": "Gitleaks"
+ },
+ "location": {
+ "file": "aws-key.py",
+ "dependency": {
+ "package": {}
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gitleaks_rule_id",
+ "name": "Gitleaks rule ID AWS",
+ "value": "AWS"
+ }
+ ]
+ }
+ ],
+ "remediations": []
+}
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index 45485a12574..2f5f4c4596b 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -176,4 +176,77 @@ RSpec.describe CommitsHelper do
expect(helper.commit_path(project, commit)).to eq(project_commit_path(project, commit))
end
end
+
+ describe "#conditionally_paginate_diff_files" do
+ let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: diff_files) }
+ let(:diff_files) { Gitlab::Git::DiffCollection.new(files) }
+ let(:page) { nil }
+
+ let(:files) do
+ Array.new(85).map do
+ { too_large: false, diff: "" }
+ end
+ end
+
+ let(:params) do
+ {
+ page: page
+ }
+ end
+
+ subject { helper.conditionally_paginate_diff_files(diffs_collection, paginate: paginate) }
+
+ before do
+ allow(helper).to receive(:params).and_return(params)
+ end
+
+ context "pagination is enabled" do
+ let(:paginate) { true }
+
+ it "has been paginated" do
+ expect(subject).to be_an(Array)
+ end
+
+ it "can change the number of items per page" do
+ commits = helper.conditionally_paginate_diff_files(diffs_collection, paginate: paginate, per: 10)
+
+ expect(commits).to be_an(Array)
+ expect(commits.size).to eq(10)
+ end
+
+ context "page 1" do
+ let(:page) { 1 }
+
+ it "has 20 diffs" do
+ expect(subject.size).to eq(75)
+ end
+ end
+
+ context "page 2" do
+ let(:page) { 2 }
+
+ it "has the remaining 10 diffs" do
+ expect(subject.size).to eq(10)
+ end
+ end
+ end
+
+ context "pagination is disabled" do
+ let(:paginate) { false }
+
+ it "returns a standard DiffCollection" do
+ expect(subject).to be_a(Gitlab::Git::DiffCollection)
+ end
+ end
+
+ context "feature flag is disabled" do
+ let(:paginate) { true }
+
+ it "returns a standard DiffCollection" do
+ stub_feature_flags(paginate_commit_view: false)
+
+ expect(subject).to be_a(Gitlab::Git::DiffCollection)
+ end
+ end
+ end
end
diff --git a/spec/lib/object_storage/config_spec.rb b/spec/lib/object_storage/config_spec.rb
index 0ead2a1d269..1361d80fe75 100644
--- a/spec/lib/object_storage/config_spec.rb
+++ b/spec/lib/object_storage/config_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
require 'rspec-parameterized'
-require 'fog/core'
RSpec.describe ObjectStorage::Config do
using RSpec::Parameterized::TableSyntax
@@ -34,7 +33,9 @@ RSpec.describe ObjectStorage::Config do
}
end
- subject { described_class.new(raw_config.as_json) }
+ subject do
+ described_class.new(raw_config.as_json)
+ end
describe '#load_provider' do
before do
@@ -45,6 +46,10 @@ RSpec.describe ObjectStorage::Config do
it 'registers AWS as a provider' do
expect(Fog.providers.keys).to include(:aws)
end
+
+ describe '#fog_connection' do
+ it { expect(subject.fog_connection).to be_a_kind_of(Fog::AWS::Storage::Real) }
+ end
end
context 'with Google' do
@@ -59,6 +64,10 @@ RSpec.describe ObjectStorage::Config do
it 'registers Google as a provider' do
expect(Fog.providers.keys).to include(:google)
end
+
+ describe '#fog_connection' do
+ it { expect(subject.fog_connection).to be_a_kind_of(Fog::Storage::GoogleXML::Real) }
+ end
end
context 'with Azure' do
@@ -73,6 +82,10 @@ RSpec.describe ObjectStorage::Config do
it 'registers AzureRM as a provider' do
expect(Fog.providers.keys).to include(:azurerm)
end
+
+ describe '#fog_connection' do
+ it { expect(subject.fog_connection).to be_a_kind_of(Fog::Storage::AzureRM::Real) }
+ end
end
end
@@ -170,6 +183,50 @@ RSpec.describe ObjectStorage::Config do
it { expect(subject.provider).to eq('AWS') }
it { expect(subject.aws?).to be true }
it { expect(subject.google?).to be false }
+
+ it 'returns the default S3 endpoint' do
+ subject.load_provider
+
+ expect(subject.s3_endpoint).to eq("https://test-bucket.s3.amazonaws.com")
+ end
+
+ describe 'with a custom endpoint' do
+ let(:endpoint) { 'https://my.example.com' }
+
+ before do
+ credentials[:endpoint] = endpoint
+ end
+
+ it 'returns the custom endpoint' do
+ subject.load_provider
+
+ expect(subject.s3_endpoint).to eq(endpoint)
+ end
+ end
+
+ context 'with custom S3 host and port' do
+ where(:host, :port, :scheme, :expected) do
+ 's3.example.com' | 8080 | nil | 'https://test-bucket.s3.example.com:8080'
+ 's3.example.com' | 443 | nil | 'https://test-bucket.s3.example.com'
+ 's3.example.com' | 443 | "https" | 'https://test-bucket.s3.example.com'
+ 's3.example.com' | nil | nil | 'https://test-bucket.s3.example.com'
+ 's3.example.com' | 80 | "http" | 'http://test-bucket.s3.example.com'
+ 's3.example.com' | "bogus" | nil | nil
+ end
+
+ with_them do
+ before do
+ credentials[:host] = host
+ credentials[:port] = port
+ credentials[:scheme] = scheme
+ subject.load_provider
+ end
+
+ it 'returns expected host' do
+ expect(subject.s3_endpoint).to eq(expected)
+ end
+ end
+ end
end
context 'with Google credentials' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 63a626d4c8e..4ad7ce70a44 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1860,7 +1860,7 @@ RSpec.describe Ci::Build do
subject { build.artifacts_file_for_type(file_type) }
it 'queries artifacts for type' do
- expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: Ci::JobArtifact.file_types[file_type])
+ expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: [Ci::JobArtifact.file_types[file_type]])
subject
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 9ead3d531d5..ebe2cd2ac03 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2054,6 +2054,50 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '#has_sast_reports?' do
+ subject { merge_request.has_sast_reports? }
+
+ let(:project) { create(:project, :repository) }
+
+ before do
+ stub_licensed_features(sast: true)
+ end
+
+ context 'when head pipeline has sast reports' do
+ let(:merge_request) { create(:merge_request, :with_sast_reports, source_project: project) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when head pipeline does not have sast reports' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#has_secret_detection_reports?' do
+ subject { merge_request.has_secret_detection_reports? }
+
+ let(:project) { create(:project, :repository) }
+
+ before do
+ stub_licensed_features(secret_detection: true)
+ end
+
+ context 'when head pipeline has secret detection reports' do
+ let(:merge_request) { create(:merge_request, :with_secret_detection_reports, source_project: project) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when head pipeline does not have secrets detection reports' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#calculate_reactive_cache' do
let(:merge_request) { create(:merge_request) }
@@ -4587,4 +4631,34 @@ RSpec.describe MergeRequest, factory_default: :keep do
.from(nil).to(ref)
end
end
+
+ describe '#enabled_reports' do
+ let(:project) { create(:project, :repository) }
+
+ where(:report_type, :with_reports, :feature) do
+ :sast | :with_sast_reports | :sast
+ :secret_detection | :with_secret_detection_reports | :secret_detection
+ end
+
+ with_them do
+ subject { merge_request.enabled_reports[report_type] }
+
+ before do
+ stub_feature_flags(drop_license_management_artifact: false)
+ stub_licensed_features({ feature => true })
+ end
+
+ context "when head pipeline has reports" do
+ let(:merge_request) { create(:merge_request, with_reports, source_project: project) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "when head pipeline does not have reports" do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
end