diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-30 12:09:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-30 12:09:35 +0300 |
commit | bf774d67fc8a84f76f20494c318d7cfacb0c69ac (patch) | |
tree | b991cad6b68560d19f8ce3075577aab2eb51fae6 | |
parent | 50f0475ee134da9ea12e758a9f3250f2ab19cd65 (diff) |
Add latest changes from gitlab-org/gitlab@master
49 files changed, 446 insertions, 350 deletions
diff --git a/app/assets/javascripts/members/components/members_tabs.vue b/app/assets/javascripts/members/components/members_tabs.vue index ee4743010cf..98995730df4 100644 --- a/app/assets/javascripts/members/components/members_tabs.vue +++ b/app/assets/javascripts/members/components/members_tabs.vue @@ -77,6 +77,10 @@ export default { urlParams.push(state.filteredSearchBar.searchParam); } + if (state?.filteredSearchBar?.tokens) { + urlParams.push(...state.filteredSearchBar.tokens); + } + return urlParams; }, getTabCount({ namespace }) { diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js index c66a19c4765..943742e184b 100644 --- a/app/assets/javascripts/members/constants.js +++ b/app/assets/javascripts/members/constants.js @@ -130,9 +130,15 @@ export const FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS = { ], }; +export const FILTERED_SEARCH_TOKEN_GROUPS_WITH_INHERITED_PERMISSIONS = { + ...FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS, + type: 'groups_with_inherited_permissions', +}; + export const AVAILABLE_FILTERED_SEARCH_TOKENS = [ FILTERED_SEARCH_TOKEN_TWO_FACTOR, FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS, + FILTERED_SEARCH_TOKEN_GROUPS_WITH_INHERITED_PERMISSIONS, ]; export const AVATAR_SIZE = 48; diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js index 79ac31f1659..5bfce9bc7bf 100644 --- a/app/assets/javascripts/pages/groups/group_members/index.js +++ b/app/assets/javascripts/pages/groups/group_members/index.js @@ -44,7 +44,7 @@ initMembersApp(document.querySelector('.js-group-members-list-app'), { ? { filteredSearchBar: { show: true, - tokens: ['with_inherited_permissions'], + tokens: ['groups_with_inherited_permissions'], searchParam: 'search_groups', placeholder: s__('Members|Filter groups'), recentSearchesStorageKey: 'group_links_members', diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js index 81d2168e2ce..3e6d2e675ed 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/index.js +++ b/app/assets/javascripts/repository/components/blob_viewers/index.js @@ -9,6 +9,7 @@ const viewers = { lfs: () => import('./lfs_viewer.vue'), audio: () => import('./audio_viewer.vue'), svg: () => import('./image_viewer.vue'), + sketch: () => import('./sketch_viewer.vue'), }; export const loadViewer = (type, isUsingLfs) => { diff --git a/app/assets/javascripts/repository/components/blob_viewers/sketch_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/sketch_viewer.vue new file mode 100644 index 00000000000..32e24a9cc07 --- /dev/null +++ b/app/assets/javascripts/repository/components/blob_viewers/sketch_viewer.vue @@ -0,0 +1,31 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import SketchLoader from '~/blob/sketch'; + +export default { + components: { + GlLoadingIcon, + }, + props: { + blob: { + type: Object, + required: true, + }, + }, + data() { + return { + url: this.blob.rawPath, + }; + }, + mounted() { + // eslint-disable-next-line no-new + new SketchLoader(this.$refs.viewer); + }, +}; +</script> + +<template> + <div ref="viewer" class="file-content" :data-endpoint="url" data-testid="sketch"> + <gl-loading-icon class="my-4 js-loading-icon" size="md" /> + </div> +</template> diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 0b9024dc3db..fb11bece79c 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -143,8 +143,8 @@ module MembershipActions raise NotImplementedError end - def requested_relations - case params[:with_inherited_permissions].presence + def requested_relations(inherited_permissions = :with_inherited_permissions) + case params[inherited_permissions].presence when 'exclude' [:direct] when 'only' diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index d325bb402e7..282adcf878b 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -27,7 +27,7 @@ class Groups::GroupMembersController < Groups::ApplicationController push_frontend_feature_flag(:group_member_inherited_group, @group) @sort = params[:sort].presence || sort_value_name - @include_relations ||= requested_relations + @include_relations ||= requested_relations(:groups_with_inherited_permissions) if can?(current_user, :admin_group_member, @group) @invited_members = invited_members diff --git a/app/models/concerns/integrations/base_data_fields.rb b/app/models/concerns/integrations/base_data_fields.rb index 3cedb90756f..b6e1e428295 100644 --- a/app/models/concerns/integrations/base_data_fields.rb +++ b/app/models/concerns/integrations/base_data_fields.rb @@ -4,10 +4,15 @@ module Integrations module BaseDataFields extend ActiveSupport::Concern + LEGACY_FOREIGN_KEY_NAME = %w( + Integrations::IssueTrackerData + Integrations::JiraTrackerData + ).freeze + included do # TODO: Once we rename the tables we can't rely on `table_name` anymore. # https://gitlab.com/gitlab-org/gitlab/-/issues/331953 - belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: :service_id + belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: foreign_key_name delegate :activated?, to: :integration, allow_nil: true @@ -23,6 +28,22 @@ module Integrations algorithm: 'aes-256-gcm' } end + + private + + # Older data field models use the `service_id` foreign key for the + # integration association. + def foreign_key_name + return :service_id if self.name.in?(LEGACY_FOREIGN_KEY_NAME) + + :integration_id + end + end + + def to_database_hash + as_json( + only: self.class.column_names + ).except('id', 'service_id', 'integration_id', 'created_at', 'updated_at') end end end diff --git a/app/models/integration.rb b/app/models/integration.rb index b5064cfae2d..a16e8dc960a 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -465,13 +465,14 @@ class Integration < ApplicationRecord super.except('properties') end - # return a hash of columns => values suitable for passing to insert_all - def to_integration_hash + # Returns a hash of attributes (columns => values) used for inserting into the database. + def to_database_hash column = self.class.attribute_aliases.fetch('type', 'type') - as_json(except: %w[id instance project_id group_id]) - .merge(column => type) - .merge(reencrypt_properties) + as_json( + except: %w[id instance project_id group_id created_at updated_at] + ).merge(column => type) + .merge(reencrypt_properties) end def reencrypt_properties @@ -484,10 +485,6 @@ class Integration < ApplicationRecord { 'encrypted_properties' => ep, 'encrypted_properties_iv' => iv } end - def to_data_fields_hash - data_fields.as_json(only: data_fields.class.column_names).except('id', 'service_id', 'integration_id') - end - def event_channel_names [] end diff --git a/app/models/integrations/zentao_tracker_data.rb b/app/models/integrations/zentao_tracker_data.rb index 468e4e5d7d7..e9d63abd66b 100644 --- a/app/models/integrations/zentao_tracker_data.rb +++ b/app/models/integrations/zentao_tracker_data.rb @@ -2,18 +2,7 @@ module Integrations class ZentaoTrackerData < ApplicationRecord - belongs_to :integration, inverse_of: :zentao_tracker_data, foreign_key: :integration_id - delegate :activated?, to: :integration - validates :integration, presence: true - - scope :encryption_options, -> do - { - key: Settings.attr_encrypted_db_key_base_32, - encode: true, - mode: :per_attribute_iv, - algorithm: 'aes-256-gcm' - } - end + include BaseDataFields attr_encrypted :url, encryption_options attr_encrypted :api_url, encryption_options diff --git a/app/models/repository.rb b/app/models/repository.rb index c6bfd69f4e6..0135020e586 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -176,8 +176,8 @@ class Repository end # Returns a list of commits that are not present in any reference - def new_commits(newrev, allow_quarantine: false) - commits = raw.new_commits(newrev, allow_quarantine: allow_quarantine) + def new_commits(newrev) + commits = raw.new_commits(newrev) ::Commit.decorate(commits, container) end diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb index 3a214122ed3..8fbb7f4f347 100644 --- a/app/services/bulk_create_integration_service.rb +++ b/app/services/bulk_create_integration_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class BulkCreateIntegrationService + include Integrations::BulkOperationHashes + def initialize(integration, batch, association) @integration = integration @batch = batch @@ -8,13 +10,13 @@ class BulkCreateIntegrationService end def execute - service_list = ServiceList.new(batch, integration_hash, association).to_array + service_list = ServiceList.new(batch, integration_hash(:create), association).to_array Integration.transaction do results = bulk_insert(*service_list) if integration.data_fields_present? - data_list = DataList.new(results, data_fields_hash, integration.data_fields.class).to_array + data_list = DataList.new(results, data_fields_hash(:create), integration.data_fields.class).to_array bulk_insert(*data_list) end @@ -30,12 +32,4 @@ class BulkCreateIntegrationService klass.insert_all(items_to_insert, returning: [:id]) end - - def integration_hash - integration.to_integration_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id } - end - - def data_fields_hash - integration.to_data_fields_hash - end end diff --git a/app/services/bulk_update_integration_service.rb b/app/services/bulk_update_integration_service.rb index 29c4d0cc220..57ceec57962 100644 --- a/app/services/bulk_update_integration_service.rb +++ b/app/services/bulk_update_integration_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class BulkUpdateIntegrationService + include Integrations::BulkOperationHashes + def initialize(integration, batch) @integration = integration @batch = batch @@ -9,10 +11,13 @@ class BulkUpdateIntegrationService # rubocop: disable CodeReuse/ActiveRecord def execute Integration.transaction do - Integration.where(id: batch_ids).update_all(integration_hash) + Integration.where(id: batch_ids).update_all(integration_hash(:update)) if integration.data_fields_present? - integration.data_fields.class.where(data_fields_foreign_key => batch_ids).update_all(data_fields_hash) + integration.data_fields.class.where(data_fields_foreign_key => batch_ids) + .update_all( + data_fields_hash(:update) + ) end end end @@ -27,14 +32,6 @@ class BulkUpdateIntegrationService integration.data_fields.class.reflections['integration'].foreign_key end - def integration_hash - integration.to_integration_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id } - end - - def data_fields_hash - integration.to_data_fields_hash - end - def batch_ids @batch_ids ||= if batch.is_a?(ActiveRecord::Relation) diff --git a/app/services/concerns/integrations/bulk_operation_hashes.rb b/app/services/concerns/integrations/bulk_operation_hashes.rb new file mode 100644 index 00000000000..3f13c764ebe --- /dev/null +++ b/app/services/concerns/integrations/bulk_operation_hashes.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Returns hashes of attributes suitable for passing to `.insert_all` or `update_all` +module Integrations + module BulkOperationHashes + private + + def integration_hash(operation) + integration + .to_database_hash + .merge('inherit_from_id' => integration.inherit_from_id || integration.id) + .merge(update_timestamps(operation)) + end + + def data_fields_hash(operation) + integration + .data_fields + .to_database_hash + .merge(update_timestamps(operation)) + end + + def update_timestamps(operation) + time_now = Time.current + + { + 'created_at' => (time_now if operation == :create), + 'updated_at' => time_now + }.compact + end + end +end diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index b66435d013b..d01e96a1a2d 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -112,7 +112,7 @@ module Projects integration = project.find_or_initialize_integration(::Integrations::Prometheus.to_param) integration.assign_attributes(attrs) - attrs = integration.to_integration_hash.except('created_at', 'updated_at') + attrs = integration.to_database_hash { prometheus_integration_attributes: attrs } end diff --git a/config/feature_flags/development/filter_quarantined_commits.yml b/config/feature_flags/development/filter_quarantined_commits.yml deleted file mode 100644 index 2c18e83d947..00000000000 --- a/config/feature_flags/development/filter_quarantined_commits.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: filter_quarantined_commits -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86440 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/361174 -milestone: '15.0' -type: development -group: group::gitaly -default_enabled: true diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md index a14de8dadd3..edcc0707e73 100644 --- a/doc/api/broadcast_messages.md +++ b/doc/api/broadcast_messages.md @@ -100,8 +100,8 @@ Parameters: | Attribute | Type | Required | Description | |:-----------------------|:------------------|:---------|:------------------------------------------------------| | `message` | string | yes | Message to display. | -| `starts_at` | datetime | no | Starting time (defaults to current time). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | -| `ends_at` | datetime | no | Ending time (defaults to one hour from current time). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | +| `starts_at` | datetime | no | Starting time (defaults to current time in UTC). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | +| `ends_at` | datetime | no | Ending time (defaults to one hour from current time in UTC). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | | `color` | string | no | Background color hex code. | | `font` | string | no | Foreground color hex code. | | `target_access_levels` | array of integers | no | Target access levels (roles) of the broadcast message.| @@ -158,8 +158,8 @@ Parameters: |:-----------------------|:------------------|:---------|:------------------------------------------------------| | `id` | integer | yes | ID of broadcast message to update. | | `message` | string | no | Message to display. | -| `starts_at` | datetime | no | Starting time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | -| `ends_at` | datetime | no | Ending time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | +| `starts_at` | datetime | no | Starting time (UTC). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | +| `ends_at` | datetime | no | Ending time (UTC). Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) | | `color` | string | no | Background color hex code. | | `font` | string | no | Foreground color hex code. | | `target_access_levels` | array of integers | no | Target access levels (roles) of the broadcast message.| diff --git a/doc/update/index.md b/doc/update/index.md index bf0b7b81d2a..e614a3eec4d 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -752,6 +752,15 @@ Other issues: - See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144). +- GitLab 13.11 includes a faulty background migration ([`RescheduleArtifactExpiryBackfillAgain`](https://gitlab.com/gitlab-org/gitlab/-/blob/ccc70031b843ff8cff1185988c2e472a521c2701/db/post_migrate/20210413132500_reschedule_artifact_expiry_backfill_again.rb)) + that incorrectly sets the `expire_at` column in the `ci_job_artifacts` database table. + Incorrect `expire_at` values can potentially cause data loss. + + To prevent this risk of data loss, you must remove the content of the `RescheduleArtifactExpiryBackfillAgain` + migration, which makes it a no-op migration. You can repeat the changes from the + [commit that makes the migration no-op in 14.9 and later](https://gitlab.com/gitlab-org/gitlab/-/blob/42c3dfc5a1c8181767bbb5c76e7c5fa6fefbbc2b/db/post_migrate/20210413132500_reschedule_artifact_expiry_backfill_again.rb). + For more information, please see [how to disable a data migration](../development/database/deleting_migrations.md#how-to-disable-a-data-migration). + ### 13.10.0 See [Maintenance mode issue in GitLab 13.9 to 14.4](#maintenance-mode-issue-in-gitlab-139-to-144). diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md index 9d6dcf30908..22544e909fc 100644 --- a/doc/user/admin_area/broadcast_messages.md +++ b/doc/user/admin_area/broadcast_messages.md @@ -70,7 +70,7 @@ To add a broadcast message: 1. Select the **Dismissable** checkbox to enable users to dismiss the broadcast message. 1. Optional. Select **Target roles** to only show the broadcast message to users with the selected roles. The message displays on group, subgroup, and project pages, and does not display in Git remote responses. 1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `mygroup/myproject*`. -1. Select a date for the message to start and end. +1. Select a date and time (UTC) for the message to start and end. 1. Select **Add broadcast message**. When a broadcast message expires, it no longer displays in the user interface but is still listed in the diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index b44941c22d8..463bb18b941 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -12,35 +12,69 @@ GitLab can check your application for security vulnerabilities including: - Data leaks. - Denial of Service (DoS) attacks. +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For an overview of GitLab application security, see [Shifting Security Left](https://www.youtube.com/watch?v=XnYstHObqlA&t). + Statistics and details on vulnerabilities are included in the merge request. Providing actionable information _before_ changes are merged enables you to be proactive. -GitLab also provides high-level statistics of vulnerabilities across projects and groups: +To help with the task of managing and addressing vulnerabilities, GitLab provides a security +dashboard you can access from your project or group. For more details, see +[Security Dashboard](security_dashboard/index.md). -- The [Security Dashboard](security_dashboard/index.md) provides a - high-level view of vulnerabilities detected in your projects, pipeline, and groups. +## Application coverage -<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> -For an overview of GitLab application security, see [Shifting Security Left](https://www.youtube.com/watch?v=XnYstHObqlA&t). +GitLab analyzes various details of your application, either as part of your CI/CD pipeline or on a +schedule. Coverage includes: + +- Source code. +- Dependencies in your projects or container images. +- Vulnerabilities in a running web application. +- Infrastructure as code configuration. + +### Source code analysis + +Source code analysis occurs on every code commit. Details of vulnerabilities detected are provided +in the merge request. + +A source code analysis can: + +- Analyze source code for vulnerabilities - [Static Application Security Testing (SAST)](sast/index.md). +- Analyze the Git repository's history for secrets - [Secret Detection](secret_detection/index.md). + +### Analysis of the running web application -## Security scanning tools - -GitLab uses the following tools to scan and report known vulnerabilities found in your project. - -| Secure scanning tool | Description | -| :------------------------------------------------------------- | :------------------------------------------------------------------ | -| [Container Scanning](container_scanning/index.md) | Scan Docker containers for known vulnerabilities. | -| [Dependency List](dependency_list/index.md) | View your project's dependencies and their known vulnerabilities. | -| [Dependency Scanning](dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. | -| [Dynamic Application Security Testing (DAST)](dast/index.md) | Analyze running web applications for known vulnerabilities. | -| [DAST API](dast_api/index.md) | Analyze running web APIs for known vulnerabilities. | -| [API fuzzing](api_fuzzing/index.md) | Find unknown bugs and vulnerabilities in web APIs with fuzzing. | -| [Secret Detection](secret_detection/index.md) | Analyze Git history for leaked secrets. | -| [Security Dashboard](security_dashboard/index.md) | View vulnerabilities in all your projects and groups. | -| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. | -| [Infrastructure as Code (IaC) Scanning](iac_scanning/index.md) | Analyze your IaC configuration files for known vulnerabilities. | -| [Coverage fuzzing](coverage_fuzzing/index.md) | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. | -| [Cluster Image Scanning](cluster_image_scanning/index.md) | Scan Kubernetes clusters for known vulnerabilities. | +Analysis of the web application occurs on every code commit. As part of the CI/CD pipeline, your +application is built, deployed to a test environment, and subjected to the following tests: + +- Test for known application vectors - [Dynamic Application Security Testing (DAST)](dast/index.md). +- Analysis of APIs for known attack vectors - [DAST API](dast_api/index.md). +- Analysis of web APIs for unknown bugs and vulnerabilities - [API fuzzing](api_fuzzing/index.md). + +### Dependency analysis + +Dependency analysis occurs on every code commit. Your application's dependencies are collated and +checked against a database of known vulnerabilities. + +Dependency analysis can run: + +- At build time - [Dependency Scanning](dependency_scanning/index.md). +- For projects that use container images, also after the final container + image is built - [Container Scanning](container_scanning/index.md). + +For more details, see +[Dependency Scanning compared to Container Scanning](dependency_scanning/index.md#dependency-scanning-compared-to-container-scanning). + +Additionally, dependencies in operational container images can be analyzed for vulnerabilities +on a regular schedule or cadence. For more details, see [Cluster Image Scanning](cluster_image_scanning/index.md). + +### Infrastructure analysis + +Your application's infrastructure is a source of potential vulnerabilities. To help defend +against this, infrastructure analysis occurs on every merge request. Checks are run against: + +- Infrastructure as Code (IaC) configuration files that define your application's deployment + environment - [Infrastructure as Code (IaC) Scanning](iac_scanning/index.md). ## Vulnerability scanner maintenance @@ -102,9 +136,6 @@ variables: DAST_WEBSITE: https://staging.example.com ``` -For more details about each of the security scanning tools, see their respective -[documentation sections](#security-scanning-tools). - ### Override the default registry base address By default, GitLab security scanners use `registry.gitlab.com/security-products` as the @@ -126,7 +157,7 @@ rules: ### Secure jobs in your pipeline -If you add the security scanning jobs as described in [Security scanning with Auto DevOps](#security-scanning-with-auto-devops) or [Security scanning without Auto DevOps](#security-scanning-without-auto-devops) to your `.gitlab-ci.yml` each added [security scanning tool](#security-scanning-tools) behave as described below. +If you add the security scanning jobs as described in [Security scanning with Auto DevOps](#security-scanning-with-auto-devops) or [Security scanning without Auto DevOps](#security-scanning-without-auto-devops) to your `.gitlab-ci.yml` each added [security scanning tool](#application-coverage) behave as described below. For each compatible analyzer, a job is created in the `test`, `dast` or `fuzz` stage of your pipeline and runs on the next new branch pipeline. Features such as the [Security Dashboard](security_dashboard/index.md), [Vulnerability Report](vulnerability_report/index.md), and [Dependency List](dependency_list/index.md) diff --git a/doc/user/application_security/policies/scan-result-policies.md b/doc/user/application_security/policies/scan-result-policies.md index 232a5c9f91c..3da884aca6a 100644 --- a/doc/user/application_security/policies/scan-result-policies.md +++ b/doc/user/application_security/policies/scan-result-policies.md @@ -91,7 +91,7 @@ the defined policy. Requirements and limitations: -- You must add the respective [security scanning tools](../index.md#security-scanning-tools). +- You must add the respective [security scanning tools](../index.md#application-coverage). Otherwise, scan result policies won't have any effect. - The maximum number of policies is five. - Each policy can have a maximum of five rules. diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md index 577606885ca..3cb4bd4a02d 100644 --- a/doc/user/application_security/security_dashboard/index.md +++ b/doc/user/application_security/security_dashboard/index.md @@ -7,13 +7,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w # GitLab Security Dashboards and Security Center **(ULTIMATE)** -You can use Security Dashboards to view details about vulnerabilities -detected by [security scanners](../index.md#security-scanning-tools). -These details are shown in pipelines, projects, and groups. +You can use Security Dashboards to view trends about vulnerabilities +detected by [security scanners](../index.md#application-coverage). +These trends are shown in projects, groups, and the Security Center. To use the Security Dashboards, you must: -- Configure at least one [security scanner](../index.md#security-scanning-tools) in a project. +- Configure at least one [security scanner](../index.md#application-coverage) in a project. - Configure jobs to use the [`reports` syntax](../../../ci/yaml/index.md#artifactsreports). - Use [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or later. If you use the shared runners on GitLab.com, you are using the correct version. diff --git a/doc/user/application_security/vulnerability_report/index.md b/doc/user/application_security/vulnerability_report/index.md index 54663235d91..a20281dde46 100644 --- a/doc/user/application_security/vulnerability_report/index.md +++ b/doc/user/application_security/vulnerability_report/index.md @@ -105,7 +105,7 @@ When using the tool filter, you can choose: - Individual GitLab-provided tools. - Any integrated third-party tool. -For details of each of the available tools, see [Security scanning tools](../index.md#security-scanning-tools). +For details of each of the available tools, see [Security scanning tools](../index.md#application-coverage). ### Project filter diff --git a/lib/gitlab/checks/changes_access.rb b/lib/gitlab/checks/changes_access.rb index 2e469aabeb2..99752dc6a01 100644 --- a/lib/gitlab/checks/changes_access.rb +++ b/lib/gitlab/checks/changes_access.rb @@ -36,35 +36,17 @@ module Gitlab # any of the new revisions. def commits strong_memoize(:commits) do - allow_quarantine = true - newrevs = @changes.map do |change| - oldrev = change[:oldrev] newrev = change[:newrev] next if blank_rev?(newrev) - # In case any of the old revisions is blank, then we cannot reliably - # detect which commits are new for a given change when enumerating - # objects via the object quarantine directory given that the client - # may have pushed too many commits, and we don't know when to - # terminate the walk. We thus fall back to using `git rev-list --not - # --all`, which is a lot less efficient but at least can only ever - # returns commits which really are new. - allow_quarantine = false if allow_quarantine && blank_rev?(oldrev) - newrev end.compact next [] if newrevs.empty? - # When filtering quarantined commits we can enable usage of the object - # quarantine no matter whether we have an `oldrev` or not. - if Feature.enabled?(:filter_quarantined_commits) - allow_quarantine = true - end - - project.repository.new_commits(newrevs, allow_quarantine: allow_quarantine) + project.repository.new_commits(newrevs) end end diff --git a/lib/gitlab/checks/single_change_access.rb b/lib/gitlab/checks/single_change_access.rb index 8e12801daee..2fd48dfbfe2 100644 --- a/lib/gitlab/checks/single_change_access.rb +++ b/lib/gitlab/checks/single_change_access.rb @@ -35,8 +35,7 @@ module Gitlab end def commits - @commits ||= project.repository.new_commits(newrev, - allow_quarantine: Feature.enabled?(:filter_quarantined_commits)) + @commits ||= project.repository.new_commits(newrev) end protected diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index ab365069adf..df744bd60b4 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -366,9 +366,9 @@ module Gitlab end end - def new_commits(newrevs, allow_quarantine: false) + def new_commits(newrevs) wrapped_gitaly_errors do - gitaly_commit_client.list_new_commits(Array.wrap(newrevs), allow_quarantine: allow_quarantine) + gitaly_commit_client.list_new_commits(Array.wrap(newrevs)) end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 5e1f92ae835..9fb34f74c82 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -258,9 +258,9 @@ module Gitlab end # List all commits which are new in the repository. If commits have been pushed into the repo - def list_new_commits(revisions, allow_quarantine: false) + def list_new_commits(revisions) git_env = Gitlab::Git::HookEnv.all(@gitaly_repo.gl_repository) - if allow_quarantine && git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].present? + if git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].present? # If we have a quarantine environment, then we can optimize the check # by doing a ListAllCommitsRequest. Instead of walking through # references, we just walk through all quarantined objects, which is @@ -278,32 +278,29 @@ module Gitlab response = GitalyClient.call(@repository.storage, :commit_service, :list_all_commits, request, timeout: GitalyClient.medium_timeout) quarantined_commits = consume_commits_response(response) - - if Feature.enabled?(:filter_quarantined_commits) - quarantined_commit_ids = quarantined_commits.map(&:id) - - # While in general the quarantine directory would only contain objects - # which are actually new, this is not guaranteed by Git. In fact, - # git-push(1) may sometimes push objects which already exist in the - # target repository. We do not want to return those from this method - # though given that they're not actually new. - # - # To fix this edge-case we thus have to filter commits down to those - # which don't yet exist. To do so, we must check for object existence - # in the main repository, but the object directory of our repository - # points into the object quarantine. This can be fixed by unsetting - # it, which will cause us to use the normal repository as indicated by - # its relative path again. - main_repo = @gitaly_repo.dup - main_repo.git_object_directory = "" - - # Check object existence of all quarantined commits' IDs. - quarantined_commit_existence = object_existence_map(quarantined_commit_ids, gitaly_repo: main_repo) - - # And then we reject all quarantined commits which exist in the main - # repository already. - quarantined_commits.reject! { |c| quarantined_commit_existence[c.id] } - end + quarantined_commit_ids = quarantined_commits.map(&:id) + + # While in general the quarantine directory would only contain objects + # which are actually new, this is not guaranteed by Git. In fact, + # git-push(1) may sometimes push objects which already exist in the + # target repository. We do not want to return those from this method + # though given that they're not actually new. + # + # To fix this edge-case we thus have to filter commits down to those + # which don't yet exist. To do so, we must check for object existence + # in the main repository, but the object directory of our repository + # points into the object quarantine. This can be fixed by unsetting + # it, which will cause us to use the normal repository as indicated by + # its relative path again. + main_repo = @gitaly_repo.dup + main_repo.git_object_directory = "" + + # Check object existence of all quarantined commits' IDs. + quarantined_commit_existence = object_existence_map(quarantined_commit_ids, gitaly_repo: main_repo) + + # And then we reject all quarantined commits which exist in the main + # repository already. + quarantined_commits.reject! { |c| quarantined_commit_existence[c.id] } quarantined_commits else diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8fbdd673780..460e54d17b5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26268,7 +26268,7 @@ msgstr "" msgid "OnDemandScans|%{learnMoreLinkStart}Learn more about on-demand scans%{learnMoreLinkEnd}." msgstr "" -msgid "OnDemandScans|%{scannerType} profile library" +msgid "OnDemandScans|%{profileType} profile library" msgstr "" msgid "OnDemandScans|Add a schedule to run this scan at a specified date and time or on a recurring basis. Scheduled scans are automatically saved to scan library." @@ -26322,7 +26322,7 @@ msgstr "" msgid "OnDemandScans|Dynamic Application Security Testing (DAST)" msgstr "" -msgid "OnDemandScans|Edit %{scannerType} profile" +msgid "OnDemandScans|Edit %{profileType} profile" msgstr "" msgid "OnDemandScans|Edit on-demand DAST scan" @@ -26349,7 +26349,7 @@ msgstr "" msgid "OnDemandScans|My daily scan" msgstr "" -msgid "OnDemandScans|New %{scannerType} profile" +msgid "OnDemandScans|New %{profileType} profile" msgstr "" msgid "OnDemandScans|New on-demand DAST scan" @@ -26364,7 +26364,7 @@ msgstr "" msgid "OnDemandScans|New scan" msgstr "" -msgid "OnDemandScans|No %{scannerType} profiles found for DAST" +msgid "OnDemandScans|No %{profileType} profiles found for DAST" msgstr "" msgid "OnDemandScans|No profile yet. In order to create a new scan, you need to have at least one completed scanner profile." diff --git a/spec/frontend/members/components/members_tabs_spec.js b/spec/frontend/members/components/members_tabs_spec.js index 1d882e5ef09..1354b938d77 100644 --- a/spec/frontend/members/components/members_tabs_spec.js +++ b/spec/frontend/members/components/members_tabs_spec.js @@ -9,6 +9,7 @@ import { MEMBER_TYPES, TAB_QUERY_PARAM_VALUES, ACTIVE_TAB_QUERY_PARAM_NAME, + FILTERED_SEARCH_TOKEN_GROUPS_WITH_INHERITED_PERMISSIONS, } from '~/members/constants'; import { pagination } from '../mock_data'; @@ -42,6 +43,7 @@ describe('MembersTabs', () => { }, filteredSearchBar: { searchParam: 'search_groups', + tokens: [FILTERED_SEARCH_TOKEN_GROUPS_WITH_INHERITED_PERMISSIONS.type], }, }, }, @@ -163,6 +165,18 @@ describe('MembersTabs', () => { expect(findTabByText('Groups')).not.toBeUndefined(); }); }); + + describe('when url param matches `filteredSearchBar.tokens`', () => { + beforeEach(() => { + setWindowLocation('?groups_with_inherited_permissions=exclude'); + }); + + it('shows tab that corresponds to filtered search token', async () => { + await createComponent({ totalItems: 0 }); + + expect(findTabByText('Groups')).not.toBeUndefined(); + }); + }); }); describe('when `canManageMembers` is `false`', () => { diff --git a/spec/frontend/repository/components/blob_viewers/sketch_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/sketch_viewer_spec.js new file mode 100644 index 00000000000..b5c8c02c4a0 --- /dev/null +++ b/spec/frontend/repository/components/blob_viewers/sketch_viewer_spec.js @@ -0,0 +1,32 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import SketchViewer from '~/repository/components/blob_viewers/sketch_viewer.vue'; +import SketchLoader from '~/blob/sketch'; + +jest.mock('~/blob/sketch'); + +describe('Sketch Viewer', () => { + let wrapper; + + const DEFAULT_BLOB_DATA = { + rawPath: 'some/file.sketch', + }; + + const createComponent = () => { + wrapper = shallowMountExtended(SketchViewer, { + propsData: { blob: DEFAULT_BLOB_DATA }, + }); + }; + + const findSketchWrapper = () => wrapper.findByTestId('sketch'); + + beforeEach(() => createComponent()); + + it('inits the sketch loader', () => { + expect(SketchLoader).toHaveBeenCalledWith(wrapper.vm.$refs.viewer); + }); + + it('renders the sketch viewer', () => { + expect(findSketchWrapper().exists()).toBe(true); + expect(findSketchWrapper().attributes('data-endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath); + }); +}); diff --git a/spec/lib/gitlab/checks/changes_access_spec.rb b/spec/lib/gitlab/checks/changes_access_spec.rb index 41ec11c1055..60118823b5a 100644 --- a/spec/lib/gitlab/checks/changes_access_spec.rb +++ b/spec/lib/gitlab/checks/changes_access_spec.rb @@ -49,56 +49,26 @@ RSpec.describe Gitlab::Checks::ChangesAccess do context 'when changes contain empty revisions' do let(:expected_commit) { instance_double(Commit) } - let(:expected_allow_quarantine) { allow_quarantine } shared_examples 'returns only commits with non empty revisions' do - before do - stub_feature_flags(filter_quarantined_commits: filter_quarantined_commits) - end - specify do expect(project.repository) .to receive(:new_commits) - .with([newrev], allow_quarantine: expected_allow_quarantine) { [expected_commit] } + .with([newrev]) { [expected_commit] } expect(subject.commits).to match_array([expected_commit]) end end - it_behaves_like 'returns only commits with non empty revisions' do + context 'with oldrev' do let(:changes) { [{ oldrev: oldrev, newrev: newrev }, { newrev: '' }, { newrev: Gitlab::Git::BLANK_SHA }] } - let(:allow_quarantine) { true } - let(:filter_quarantined_commits) { true } + + it_behaves_like 'returns only commits with non empty revisions' end context 'without oldrev' do let(:changes) { [{ newrev: newrev }, { newrev: '' }, { newrev: Gitlab::Git::BLANK_SHA }] } - context 'with disallowed quarantine' do - # The quarantine directory should not be used because we're lacking - # oldrev, and we're not filtering commits. - let(:allow_quarantine) { false } - let(:filter_quarantined_commits) { false } - - it_behaves_like 'returns only commits with non empty revisions' - end - - context 'with allowed quarantine and :filter_quarantined_commits disabled' do - # When we allow usage of the quarantine but have no oldrev and we're - # not filtering commits then results returned by the quarantine aren't - # accurate. We thus mustn't try using it. - let(:allow_quarantine) { true } - let(:filter_quarantined_commits) { false } - let(:expected_allow_quarantine) { false } - - it_behaves_like 'returns only commits with non empty revisions' - end - - context 'with allowed quarantine and :filter_quarantined_commits enabled' do - let(:allow_quarantine) { true } - let(:filter_quarantined_commits) { true } - - it_behaves_like 'returns only commits with non empty revisions' - end + it_behaves_like 'returns only commits with non empty revisions' end end end diff --git a/spec/lib/gitlab/checks/single_change_access_spec.rb b/spec/lib/gitlab/checks/single_change_access_spec.rb index 1b34e58797e..8d9f96dd2b4 100644 --- a/spec/lib/gitlab/checks/single_change_access_spec.rb +++ b/spec/lib/gitlab/checks/single_change_access_spec.rb @@ -96,26 +96,14 @@ RSpec.describe Gitlab::Checks::SingleChangeAccess do let(:provided_commits) { nil } before do - stub_feature_flags(filter_quarantined_commits: filter_quarantined_commits) - expect(project.repository) .to receive(:new_commits) - .with(newrev, allow_quarantine: filter_quarantined_commits) + .with(newrev) .once .and_return(expected_commits) end - context 'with :filter_quarantined_commits disabled' do - let(:filter_quarantined_commits) { false } - - it_behaves_like '#commits' - end - - context 'with :filter_quarantined_commits enabled' do - let(:filter_quarantined_commits) { true } - - it_behaves_like '#commits' - end + it_behaves_like '#commits' end end end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 92860c9232f..3a34d39c722 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -340,17 +340,12 @@ RSpec.describe Gitlab::GitalyClient::CommitService do let(:revisions) { [revision] } let(:gitaly_commits) { create_list(:gitaly_commit, 3) } let(:expected_commits) { gitaly_commits.map { |c| Gitlab::Git::Commit.new(repository, c) }} - let(:filter_quarantined_commits) { false } subject do - client.list_new_commits(revisions, allow_quarantine: allow_quarantine) + client.list_new_commits(revisions) end shared_examples 'a #list_all_commits message' do - before do - stub_feature_flags(filter_quarantined_commits: filter_quarantined_commits) - end - it 'sends a list_all_commits message' do expected_repository = repository.gitaly_repository.dup expected_repository.git_alternate_object_directories = Google::Protobuf::RepeatedField.new(:string) @@ -360,29 +355,25 @@ RSpec.describe Gitlab::GitalyClient::CommitService do .with(gitaly_request_with_params(repository: expected_repository), kind_of(Hash)) .and_return([Gitaly::ListAllCommitsResponse.new(commits: gitaly_commits)]) - if filter_quarantined_commits - # The object directory of the repository must not be set so that we - # don't use the quarantine directory. - objects_exist_repo = repository.gitaly_repository.dup - objects_exist_repo.git_object_directory = "" - - # The first request contains the repository, the second request the - # commit IDs we want to check for existence. - objects_exist_request = [ - gitaly_request_with_params(repository: objects_exist_repo), - gitaly_request_with_params(revisions: gitaly_commits.map(&:id)) - ] - - objects_exist_response = Gitaly::CheckObjectsExistResponse.new(revisions: revision_existence.map do - |rev, exists| Gitaly::CheckObjectsExistResponse::RevisionExistence.new(name: rev, exists: exists) - end) - - expect(service).to receive(:check_objects_exist) - .with(objects_exist_request, kind_of(Hash)) - .and_return([objects_exist_response]) - else - expect(service).not_to receive(:check_objects_exist) - end + # The object directory of the repository must not be set so that we + # don't use the quarantine directory. + objects_exist_repo = repository.gitaly_repository.dup + objects_exist_repo.git_object_directory = "" + + # The first request contains the repository, the second request the + # commit IDs we want to check for existence. + objects_exist_request = [ + gitaly_request_with_params(repository: objects_exist_repo), + gitaly_request_with_params(revisions: gitaly_commits.map(&:id)) + ] + + objects_exist_response = Gitaly::CheckObjectsExistResponse.new(revisions: revision_existence.map do + |rev, exists| Gitaly::CheckObjectsExistResponse::RevisionExistence.new(name: rev, exists: exists) + end) + + expect(service).to receive(:check_objects_exist) + .with(objects_exist_request, kind_of(Hash)) + .and_return([objects_exist_response]) end expect(subject).to eq(expected_commits) @@ -418,49 +409,31 @@ RSpec.describe Gitlab::GitalyClient::CommitService do } end - context 'with allowed quarantine' do - let(:allow_quarantine) { true } - - context 'without commit filtering' do - it_behaves_like 'a #list_all_commits message' - end - - context 'with commit filtering' do - let(:filter_quarantined_commits) { true } - - context 'reject commits which exist in target repository' do - let(:revision_existence) { gitaly_commits.to_h { |c| [c.id, true] } } - let(:expected_commits) { [] } - - it_behaves_like 'a #list_all_commits message' - end - - context 'keep commits which do not exist in target repository' do - let(:revision_existence) { gitaly_commits.to_h { |c| [c.id, false] } } + context 'reject commits which exist in target repository' do + let(:revision_existence) { gitaly_commits.to_h { |c| [c.id, true] } } + let(:expected_commits) { [] } - it_behaves_like 'a #list_all_commits message' - end + it_behaves_like 'a #list_all_commits message' + end - context 'mixed existing and nonexisting commits' do - let(:revision_existence) do - { - gitaly_commits[0].id => true, - gitaly_commits[1].id => false, - gitaly_commits[2].id => true - } - end + context 'keep commits which do not exist in target repository' do + let(:revision_existence) { gitaly_commits.to_h { |c| [c.id, false] } } - let(:expected_commits) { [Gitlab::Git::Commit.new(repository, gitaly_commits[1])] } + it_behaves_like 'a #list_all_commits message' + end - it_behaves_like 'a #list_all_commits message' - end + context 'mixed existing and nonexisting commits' do + let(:revision_existence) do + { + gitaly_commits[0].id => true, + gitaly_commits[1].id => false, + gitaly_commits[2].id => true + } end - end - context 'with disallowed quarantine' do - let(:allow_quarantine) { false } + let(:expected_commits) { [Gitlab::Git::Commit.new(repository, gitaly_commits[1])] } - it_behaves_like 'a #list_commits message' + it_behaves_like 'a #list_all_commits message' end end @@ -472,17 +445,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do } end - context 'with allowed quarantine' do - let(:allow_quarantine) { true } - - it_behaves_like 'a #list_commits message' - end - - context 'with disallowed quarantine' do - let(:allow_quarantine) { false } - - it_behaves_like 'a #list_commits message' - end + it_behaves_like 'a #list_commits message' end end diff --git a/spec/models/data_list_spec.rb b/spec/models/data_list_spec.rb index d2f15386808..67db2730a78 100644 --- a/spec/models/data_list_spec.rb +++ b/spec/models/data_list_spec.rb @@ -14,7 +14,7 @@ RSpec.describe DataList do end def data_list(integration) - DataList.new([integration], integration.to_data_fields_hash, integration.data_fields.class).to_array + DataList.new([integration], integration.to_database_hash, integration.data_fields.class).to_array end it 'returns current data' do diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index 0567a8bd386..0d38e10c5e6 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -250,7 +250,7 @@ RSpec.describe Integration do context 'with all existing instances' do def integration_hash(type) - Integration.new(instance: true, type: type).to_integration_hash + Integration.new(instance: true, type: type).to_database_hash end before do @@ -977,19 +977,25 @@ RSpec.describe Integration do end end - describe '#to_integration_hash' do + describe '#to_database_hash' do let(:properties) { { foo: 1, bar: true } } let(:db_props) { properties.stringify_keys } let(:record) { create(:integration, :instance, properties: properties) } it 'does not include the properties key' do - hash = record.to_integration_hash + hash = record.to_database_hash expect(hash).not_to have_key('properties') end + it 'does not include certain attributes' do + hash = record.to_database_hash + + expect(hash.keys).not_to include('id', 'instance', 'project_id', 'group_id', 'created_at', 'updated_at') + end + it 'saves correctly using insert_all' do - hash = record.to_integration_hash + hash = record.to_database_hash hash[:project_id] = project.id expect do @@ -999,8 +1005,8 @@ RSpec.describe Integration do expect(described_class.last).to have_attributes(properties: db_props) end - it 'is part of the to_integration_hash' do - hash = record.to_integration_hash + it 'decrypts encrypted properties correctly' do + hash = record.to_database_hash expect(hash).to include('encrypted_properties' => be_present, 'encrypted_properties_iv' => be_present) expect(hash['encrypted_properties']).not_to eq(record.encrypted_properties) @@ -1016,14 +1022,14 @@ RSpec.describe Integration do context 'when the properties are empty' do let(:properties) { {} } - it 'is part of the to_integration_hash' do - hash = record.to_integration_hash + it 'is part of the to_database_hash' do + hash = record.to_database_hash expect(hash).to include('encrypted_properties' => be_nil, 'encrypted_properties_iv' => be_nil) end it 'saves correctly using insert_all' do - hash = record.to_integration_hash + hash = record.to_database_hash hash[:project_id] = project expect do diff --git a/spec/models/integrations/issue_tracker_data_spec.rb b/spec/models/integrations/issue_tracker_data_spec.rb index 597df237c67..233ed7b8475 100644 --- a/spec/models/integrations/issue_tracker_data_spec.rb +++ b/spec/models/integrations/issue_tracker_data_spec.rb @@ -3,7 +3,11 @@ require 'spec_helper' RSpec.describe Integrations::IssueTrackerData do - describe 'associations' do - it { is_expected.to belong_to :integration } + it_behaves_like Integrations::BaseDataFields + + describe 'encrypted attributes' do + subject { described_class.encrypted_attributes.keys } + + it { is_expected.to contain_exactly(:issues_url, :new_issue_url, :project_url) } end end diff --git a/spec/models/integrations/jira_tracker_data_spec.rb b/spec/models/integrations/jira_tracker_data_spec.rb index 5430dd2eb52..d9f91527fbb 100644 --- a/spec/models/integrations/jira_tracker_data_spec.rb +++ b/spec/models/integrations/jira_tracker_data_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' RSpec.describe Integrations::JiraTrackerData do - describe 'associations' do - it { is_expected.to belong_to(:integration) } - end + it_behaves_like Integrations::BaseDataFields describe 'deployment_type' do - it { is_expected.to define_enum_for(:deployment_type).with_values([:unknown, :server, :cloud]).with_prefix(:deployment) } + specify do + is_expected.to define_enum_for(:deployment_type).with_values([:unknown, :server, :cloud]).with_prefix(:deployment) + end end describe 'encrypted attributes' do diff --git a/spec/models/integrations/zentao_tracker_data_spec.rb b/spec/models/integrations/zentao_tracker_data_spec.rb index b078c57830b..dca5c4d79ae 100644 --- a/spec/models/integrations/zentao_tracker_data_spec.rb +++ b/spec/models/integrations/zentao_tracker_data_spec.rb @@ -3,16 +3,14 @@ require 'spec_helper' RSpec.describe Integrations::ZentaoTrackerData do + it_behaves_like Integrations::BaseDataFields + describe 'factory available' do let(:zentao_tracker_data) { create(:zentao_tracker_data) } it { expect(zentao_tracker_data.valid?).to eq true } end - describe 'associations' do - it { is_expected.to belong_to(:integration) } - end - describe 'encrypted attributes' do subject { described_class.encrypted_attributes.keys } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 215f83adf5d..e1d903a40cf 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -418,46 +418,31 @@ RSpec.describe Repository do end describe '#new_commits' do - shared_examples '#new_commits' do - let_it_be(:project) { create(:project, :repository) } - - let(:repository) { project.repository } - - subject { repository.new_commits(rev, allow_quarantine: allow_quarantine) } - - context 'when there are no new commits' do - let(:rev) { repository.commit.id } + let_it_be(:project) { create(:project, :repository) } - it 'returns an empty array' do - expect(subject).to eq([]) - end - end + let(:repository) { project.repository } - context 'when new commits are found' do - let(:branch) { 'orphaned-branch' } - let!(:rev) { repository.commit(branch).id } - let(:allow_quarantine) { false } + subject { repository.new_commits(rev) } - it 'returns the commits' do - repository.delete_branch(branch) + context 'when there are no new commits' do + let(:rev) { repository.commit.id } - expect(subject).not_to be_empty - expect(subject).to all( be_a(::Commit) ) - expect(subject.size).to eq(1) - end + it 'returns an empty array' do + expect(subject).to eq([]) end end - context 'with quarantine' do - let(:allow_quarantine) { true } + context 'when new commits are found' do + let(:branch) { 'orphaned-branch' } + let!(:rev) { repository.commit(branch).id } - it_behaves_like '#new_commits' - end - - context 'without quarantine' do - let(:allow_quarantine) { false } + it 'returns the commits' do + repository.delete_branch(branch) - it_behaves_like '#new_commits' + expect(subject).not_to be_empty + expect(subject).to all( be_a(::Commit) ) + expect(subject.size).to eq(1) + end end end diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb index 68c5af33fd8..ddc95ff92d5 100644 --- a/spec/services/bulk_create_integration_service_spec.rb +++ b/spec/services/bulk_create_integration_service_spec.rb @@ -21,7 +21,7 @@ RSpec.describe BulkCreateIntegrationService do ] end - shared_examples 'creates integration from batch ids' do + shared_examples 'creates integration successfully' do def attributes(record) record.reload.attributes.except(*excluded_attributes) end @@ -41,15 +41,31 @@ RSpec.describe BulkCreateIntegrationService do expect(attributes(created_integration.data_fields)) .to eq attributes(integration.data_fields) end + + it 'sets created_at and updated_at timestamps', :freeze_time do + described_class.new(integration, batch, association).execute + + expect(created_integration.data_fields.reload).to have_attributes( + created_at: eq(Time.current), + updated_at: eq(Time.current) + ) + end end - end - shared_examples 'updates inherit_from_id' do it 'updates inherit_from_id attributes' do described_class.new(integration, batch, association).execute expect(created_integration.reload.inherit_from_id).to eq(inherit_from_id) end + + it 'sets created_at and updated_at timestamps', :freeze_time do + described_class.new(integration, batch, association).execute + + expect(created_integration.reload).to have_attributes( + created_at: eq(Time.current), + updated_at: eq(Time.current) + ) + end end context 'passing an instance-level integration' do @@ -62,8 +78,7 @@ RSpec.describe BulkCreateIntegrationService do let(:batch) { Project.where(id: project.id) } let(:association) { 'project' } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' end context 'with a group association' do @@ -72,8 +87,7 @@ RSpec.describe BulkCreateIntegrationService do let(:batch) { Group.where(id: group.id) } let(:association) { 'group' } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' end end @@ -88,15 +102,13 @@ RSpec.describe BulkCreateIntegrationService do let(:association) { 'project' } let(:inherit_from_id) { integration.id } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' context 'with different foreign key of data_fields' do let(:integration) { create(:zentao_integration, :group, group: group) } let(:created_integration) { project.zentao_integration } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' end end @@ -108,14 +120,12 @@ RSpec.describe BulkCreateIntegrationService do let(:association) { 'group' } let(:inherit_from_id) { instance_integration.id } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' context 'with different foreign key of data_fields' do let(:integration) { create(:zentao_integration, :group, group: group, inherit_from_id: instance_integration.id) } - it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates inherit_from_id' + it_behaves_like 'creates integration successfully' end end end diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb index dcc8d2df36d..23e2a0ef0ff 100644 --- a/spec/services/bulk_update_integration_service_spec.rb +++ b/spec/services/bulk_update_integration_service_spec.rb @@ -55,6 +55,20 @@ RSpec.describe BulkUpdateIntegrationService do .not_to eq(subgroup_integration.attributes.except(*excluded_attributes)) end + it 'does not change the created_at timestamp' do + subgroup_integration.update_column(:created_at, Time.utc('2022-01-01')) + + expect do + described_class.new(subgroup_integration, batch).execute + end.not_to change { integration.reload.created_at } + end + + it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do + expect do + described_class.new(subgroup_integration, batch).execute + end.to change { integration.reload.updated_at }.to(Time.current) + end + context 'with integration with data fields' do let(:excluded_attributes) do %w[id service_id created_at updated_at encrypted_properties encrypted_properties_iv] @@ -69,6 +83,20 @@ RSpec.describe BulkUpdateIntegrationService do expect(integration.data_fields.attributes.except(*excluded_attributes)) .not_to eq(excluded_integration.data_fields.attributes.except(*excluded_attributes)) end + + it 'does not change the created_at timestamp' do + subgroup_integration.data_fields.update_column(:created_at, Time.utc('2022-01-02')) + + expect do + described_class.new(subgroup_integration, batch).execute + end.not_to change { integration.data_fields.reload.created_at } + end + + it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do + expect do + described_class.new(subgroup_integration, batch).execute + end.to change { integration.data_fields.reload.updated_at }.to(Time.current) + end end end diff --git a/spec/support/shared_examples/models/integrations/base_data_fields_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_data_fields_shared_examples.rb new file mode 100644 index 00000000000..e15299bbdab --- /dev/null +++ b/spec/support/shared_examples/models/integrations/base_data_fields_shared_examples.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples Integrations::BaseDataFields do + describe 'associations' do + it { is_expected.to belong_to :integration } + end + + describe '#to_database_hash' do + it 'does not include certain attributes' do + hash = described_class.new.to_database_hash + + expect(hash.keys).not_to include('id', 'service_id', 'integration_id', 'created_at', 'updated_at') + end + end +end diff --git a/workhorse/go.mod b/workhorse/go.mod index 43107a5dc77..2d723e504ac 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -10,7 +10,7 @@ require ( github.com/aws/aws-sdk-go v1.38.35 github.com/disintegration/imaging v1.6.2 github.com/getsentry/raven-go v0.2.0 - github.com/golang-jwt/jwt/v4 v4.0.0 + github.com/golang-jwt/jwt/v4 v4.4.1 github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 github.com/golang/protobuf v1.5.2 github.com/gomodule/redigo v2.0.0+incompatible diff --git a/workhorse/go.sum b/workhorse/go.sum index fbc735e77c1..299c53f0131 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -356,8 +356,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw= diff --git a/workhorse/internal/secret/jwt.go b/workhorse/internal/secret/jwt.go index 804f3a9aba9..ce0de6ca38d 100644 --- a/workhorse/internal/secret/jwt.go +++ b/workhorse/internal/secret/jwt.go @@ -7,7 +7,7 @@ import ( ) var ( - DefaultClaims = jwt.StandardClaims{Issuer: "gitlab-workhorse"} + DefaultClaims = jwt.RegisteredClaims{Issuer: "gitlab-workhorse"} ) func JWTTokenString(claims jwt.Claims) (string, error) { diff --git a/workhorse/internal/testhelper/testhelper.go b/workhorse/internal/testhelper/testhelper.go index 6bbdfddcd60..1a4bcbb473f 100644 --- a/workhorse/internal/testhelper/testhelper.go +++ b/workhorse/internal/testhelper/testhelper.go @@ -152,7 +152,7 @@ func ParseJWT(token *jwt.Token) (interface{}, error) { // UploadClaims represents the JWT claim for upload parameters type UploadClaims struct { Upload map[string]string `json:"upload"` - jwt.StandardClaims + jwt.RegisteredClaims } func Retry(t testing.TB, timeout time.Duration, fn func() error) { diff --git a/workhorse/internal/upload/destination/destination.go b/workhorse/internal/upload/destination/destination.go index 82dde11ea3e..79fce99ee8e 100644 --- a/workhorse/internal/upload/destination/destination.go +++ b/workhorse/internal/upload/destination/destination.go @@ -54,7 +54,7 @@ type FileHandler struct { type uploadClaims struct { Upload map[string]string `json:"upload"` - jwt.StandardClaims + jwt.RegisteredClaims } // SHA256 hash of the handled file @@ -97,7 +97,7 @@ func (fh *FileHandler) GitLabFinalizeFields(prefix string) (map[string]string, e signedData[hashName] = hash } - claims := uploadClaims{Upload: signedData, StandardClaims: secret.DefaultClaims} + claims := uploadClaims{Upload: signedData, RegisteredClaims: secret.DefaultClaims} jwtData, err := secret.JWTTokenString(claims) if err != nil { return nil, err diff --git a/workhorse/internal/upload/saved_file_tracker.go b/workhorse/internal/upload/saved_file_tracker.go index b70a303a4a4..77758520d94 100644 --- a/workhorse/internal/upload/saved_file_tracker.go +++ b/workhorse/internal/upload/saved_file_tracker.go @@ -44,7 +44,7 @@ func (s *SavedFileTracker) Finalize(_ context.Context) error { return nil } - claims := MultipartClaims{RewrittenFields: s.rewrittenFields, StandardClaims: secret.DefaultClaims} + claims := MultipartClaims{RewrittenFields: s.rewrittenFields, RegisteredClaims: secret.DefaultClaims} tokenString, err := secret.JWTTokenString(claims) if err != nil { return fmt.Errorf("savedFileTracker.Finalize: %v", err) diff --git a/workhorse/internal/upload/uploads.go b/workhorse/internal/upload/uploads.go index 601f109b52d..abce855682a 100644 --- a/workhorse/internal/upload/uploads.go +++ b/workhorse/internal/upload/uploads.go @@ -25,7 +25,7 @@ type PreAuthorizer interface { type MultipartClaims struct { RewrittenFields map[string]string `json:"rewritten_fields"` - jwt.StandardClaims + jwt.RegisteredClaims } // MultipartFormProcessor abstracts away implementation differences |