diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 2bb47c77ba5..4f8d8401e92 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -922,6 +922,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/free_user_limit.md @phillipwells /doc/user/group/ @lciutacu /doc/user/group/clusters/ @phillipwells +/doc/user/group/compliance_frameworks.md @eread /doc/user/group/contribution_analytics/ @lciutacu /doc/user/group/custom_project_templates.md @eread /doc/user/group/devops_adoption/ @lciutacu @@ -931,6 +932,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/group/issues_analytics/ @msedlakjakubowski /doc/user/group/iterations/ @msedlakjakubowski /doc/user/group/planning_hierarchy/ @msedlakjakubowski +/doc/user/group/reporting/ @phillipwells /doc/user/group/repositories_analytics/ @marcel.amirault /doc/user/group/roadmap/ @msedlakjakubowski /doc/user/group/saml_sso/ @jglassman1 @@ -1017,6 +1019,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/project/requirements/ @msedlakjakubowski /doc/user/project/service_desk.md @msedlakjakubowski /doc/user/project/settings/import_export.md @eread +/doc/user/project/settings/import_export_troubleshooting.md @eread /doc/user/project/settings/index.md @lciutacu /doc/user/project/settings/project_access_tokens.md @jglassman1 /doc/user/project/time_tracking.md @msedlakjakubowski @@ -1026,7 +1029,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/public_access.md @lciutacu /doc/user/reserved_names.md @lciutacu /doc/user/search/ @ashrafkhamis -/doc/user/search/global_search/ @ashrafkhamis /doc/user/shortcuts.md @ashrafkhamis /doc/user/snippets.md @ashrafkhamis /doc/user/ssh.md @jglassman1 diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index f0bf79f009d..a434518537a 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -573,6 +573,16 @@ ee:registry-object-storage-tls: GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE GITLAB_QA_OPTS: --omnibus-config registry_object_storage +ee:importers: + extends: .qa + variables: + QA_SCENARIO: Test::Integration::Import + QA_ALLOW_LOCAL_REQUESTS: "true" + rules: + - !reference [.rules:test:qa, rules] + - if: $QA_SUITES =~ /Test::Integration::Import/ + - !reference [.rules:test:manual, rules] + # ========================================== # Post test stage # ========================================== diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue index f5e1525090e..ff23767f364 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue @@ -3,7 +3,11 @@ import { GlNav, GlNavItem } from '@gitlab/ui'; import { mapActions, mapState } from 'vuex'; import { formatNumber } from '~/locale'; import Tracking from '~/tracking'; -import { NAV_LINK_DEFAULT_CLASSES, NUMBER_FORMATING_OPTIONS } from '../constants'; +import { + NAV_LINK_DEFAULT_CLASSES, + NUMBER_FORMATING_OPTIONS, + NAV_LINK_COUNT_DEFAULT_CLASSES, +} from '../constants'; export default { name: 'ScopeNavigation', @@ -20,9 +24,6 @@ export default { }, methods: { ...mapActions(['fetchSidebarCount']), - activeClasses(currentScope) { - return currentScope === this.urlQuery.scope ? 'gl-font-weight-bold' : ''; - }, showFormatedCount(count) { if (!count) { return '0'; @@ -33,14 +34,21 @@ export default { handleClick(scope) { this.track('click_menu_item', { label: `vertical_navigation_${scope}` }); }, - linkClasses(scope) { + linkClasses(isHighlighted) { + return [...this.$options.NAV_LINK_DEFAULT_CLASSES, { 'gl-font-weight-bold': isHighlighted }]; + }, + countClasses(isHighlighted) { return [ - { 'gl-font-weight-bold': scope === this.urlQuery.scope }, - ...this.$options.NAV_LINK_DEFAULT_CLASSES, + ...this.$options.NAV_LINK_COUNT_DEFAULT_CLASSES, + isHighlighted ? 'gl-text-gray-900' : 'gl-text-gray-500', ]; }, + isActive(scope, index) { + return this.urlQuery.scope ? this.urlQuery.scope === scope : index === 0; + }, }, NAV_LINK_DEFAULT_CLASSES, + NAV_LINK_COUNT_DEFAULT_CLASSES, }; @@ -50,13 +58,13 @@ export default { {{ item.label }} + > {{ showFormatedCount(item.count) }} diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js index 3621138afe4..a9c031f91a4 100644 --- a/app/assets/javascripts/search/sidebar/constants/index.js +++ b/app/assets/javascripts/search/sidebar/constants/index.js @@ -9,3 +9,5 @@ export const NAV_LINK_DEFAULT_CLASSES = [ 'gl-justify-content-space-between', 'gl-text-gray-900', ]; + +export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal']; diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 65ea90d0b5d..ede6007e0e2 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -94,12 +94,13 @@ module Emails end end - def access_token_revoked_email(user, token_name) + def access_token_revoked_email(user, token_name, source = nil) return unless user&.active? @user = user @token_name = token_name @target_url = profile_personal_access_tokens_url + @source = source Gitlab::I18n.with_locale(@user.preferred_language) do mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("A personal access token has been revoked"))) diff --git a/app/models/analytics/cycle_analytics/aggregation.rb b/app/models/analytics/cycle_analytics/aggregation.rb index 2e58d64ae95..880b3a7e310 100644 --- a/app/models/analytics/cycle_analytics/aggregation.rb +++ b/app/models/analytics/cycle_analytics/aggregation.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Analytics::CycleAnalytics::Aggregation < ApplicationRecord - include IgnorableColumns include FromUnion belongs_to :group, optional: false @@ -11,14 +10,6 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord scope :priority_order, -> (column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) } scope :enabled, -> { where('enabled IS TRUE') } - # These columns were added with wrong naming convention, the columns were never used. - ignore_column :last_full_run_processed_records, remove_with: '15.1', remove_after: '2022-05-22' - ignore_column :last_full_run_runtimes_in_seconds, remove_with: '15.1', remove_after: '2022-05-22' - ignore_column :last_full_run_issues_updated_at, remove_with: '15.1', remove_after: '2022-05-22' - ignore_column :last_full_run_mrs_updated_at, remove_with: '15.1', remove_after: '2022-05-22' - ignore_column :last_full_run_issues_id, remove_with: '15.1', remove_after: '2022-05-22' - ignore_column :last_full_run_merge_requests_id, remove_with: '15.1', remove_after: '2022-05-22' - def cursor_for(mode, model) { updated_at: self["last_#{mode}_#{model.table_name}_updated_at"], diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 660d9891e46..a2997d3226d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -98,10 +98,10 @@ class NotificationService end # Notify the user when one of their personal access tokens is revoked - def access_token_revoked(user, token_name) + def access_token_revoked(user, token_name, source = nil) return unless user.can?(:receive_notifications) - mailer.access_token_revoked_email(user, token_name).deliver_later + mailer.access_token_revoked_email(user, token_name, source).deliver_later end # Notify the user when at least one of their ssh key has expired today diff --git a/app/services/personal_access_tokens/revoke_service.rb b/app/services/personal_access_tokens/revoke_service.rb index 5371b6c91ef..bb5edc27340 100644 --- a/app/services/personal_access_tokens/revoke_service.rb +++ b/app/services/personal_access_tokens/revoke_service.rb @@ -4,10 +4,13 @@ module PersonalAccessTokens class RevokeService < BaseService attr_reader :token, :current_user, :group - def initialize(current_user = nil, token: nil, group: nil) + VALID_SOURCES = %w[secret_detection].freeze + + def initialize(current_user = nil, token: nil, group: nil, source: nil) @current_user = current_user @token = token @group = group + @source = source end def execute @@ -15,7 +18,7 @@ module PersonalAccessTokens if token.revoke! log_event - notification_service.access_token_revoked(token.user, token.name) + notification_service.access_token_revoked(token.user, token.name, @source) ServiceResponse.success(message: success_message) else ServiceResponse.error(message: error_message) @@ -33,11 +36,24 @@ module PersonalAccessTokens end def revocation_permitted? - Ability.allowed?(current_user, :revoke_token, token) + if current_user + Ability.allowed?(current_user, :revoke_token, token) + else + source && VALID_SOURCES.include?(source) + end + end + + def source + current_user&.username || @source end def log_event - Gitlab::AppLogger.info("PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '#{token.id}'") + Gitlab::AppLogger.info( + class: self.class.name, + message: "PAT Revoked", + revoked_by: source, + revoked_for: token.user.username, + token_id: token.id) end end end diff --git a/app/validators/json_schemas/ci_secure_file_metadata.json b/app/validators/json_schemas/ci_secure_file_metadata.json index 46a7ff60b8f..66e778d6026 100644 --- a/app/validators/json_schemas/ci_secure_file_metadata.json +++ b/app/validators/json_schemas/ci_secure_file_metadata.json @@ -4,10 +4,10 @@ "properties": { "id": { "type": "string" }, "team_name": { "type": "string" }, - "team_id": { "type": "string" }, + "team_id": { "type": "array" }, "app_name": { "type": "string" }, "app_id": { "type": "string" }, - "app_id_prefix": { "type": "string" }, + "app_id_prefix": { "type": "array" }, "xcode_managed": { "type": "boolean" }, "entitlements": { "type": "object" }, "devices": { "type": "array" }, diff --git a/app/views/notify/access_token_revoked_email.html.haml b/app/views/notify/access_token_revoked_email.html.haml index 4d9b9e14d14..ecd2b3e84b2 100644 --- a/app/views/notify/access_token_revoked_email.html.haml +++ b/app/views/notify/access_token_revoked_email.html.haml @@ -2,6 +2,8 @@ = _('Hi %{username}!') % { username: sanitize_name(@user.name) } %p = html_escape(_('A personal access token, named %{code_start}%{token_name}%{code_end}, has been revoked.')) % { code_start: ''.html_safe, token_name: @token_name, code_end: ''.html_safe } +- if @source == 'secret_detection' + = _('We found your token in a public project and have automatically revoked it to protect your account.') %p - pat_link_start = ''.html_safe % { url: @target_url } = html_escape(_('You can check your tokens or create a new one in your %{pat_link_start}personal access tokens settings%{pat_link_end}.')) % { pat_link_start: pat_link_start, pat_link_end: ''.html_safe } diff --git a/app/views/notify/access_token_revoked_email.text.erb b/app/views/notify/access_token_revoked_email.text.erb index 17dd628d76c..a0623f96488 100644 --- a/app/views/notify/access_token_revoked_email.text.erb +++ b/app/views/notify/access_token_revoked_email.text.erb @@ -1,5 +1,9 @@ <%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %> <%= _('A personal access token, named %{token_name}, has been revoked.') % { token_name: @token_name } %> +<% if @source == 'secret_detection' %> + +<%= _('We found your token in a public project and have automatically revoked it to protect your account.') %> +<% end %> <%= _('You can check your tokens or create a new one in your personal access tokens settings %{pat_link}.') % { pat_link: @target_url } %> diff --git a/config/feature_flags/development/gitlab_pat_auto_revocation.yml b/config/feature_flags/development/gitlab_pat_auto_revocation.yml new file mode 100644 index 00000000000..3bbbadac23f --- /dev/null +++ b/config/feature_flags/development/gitlab_pat_auto_revocation.yml @@ -0,0 +1,8 @@ +--- +name: gitlab_pat_auto_revocation +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103713 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382610 +milestone: '15.6' +type: development +group: group::static analysis +default_enabled: false diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml index 92791486491..1205795233f 100644 --- a/doc/.vale/gitlab/Substitutions.yml +++ b/doc/.vale/gitlab/Substitutions.yml @@ -60,3 +60,4 @@ swap: reporter access: the Reporter role reporter permission: the Reporter role reporter permissions: the Reporter role + at least the Owner role: the Owner role diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md index a3c72227f3e..e8fbc15a376 100644 --- a/doc/architecture/blueprints/ci_pipeline_components/index.md +++ b/doc/architecture/blueprints/ci_pipeline_components/index.md @@ -110,7 +110,8 @@ identifying abstract concepts and are subject to changes as we refine the design ## Definition of pipeline component -A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit. Components are used to compose a part or entire pipeline configuration. +A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit. +Components are used to compose a part or entire pipeline configuration. It can optionally take input parameters and set output data to be adaptable and reusable in different pipeline contexts, while encapsulating and isolating implementation details. @@ -133,27 +134,145 @@ For best experience with any systems made of components it's fundamental that co The version identifies the exact interface and behavior of the component. - **Resolvable**: when a component depends on another component, this dependency must be explicit and trackable. -## Proposal +## Structure of a component -Prerequisites to create a component: +A pipeline component is identified by the path to a repository or directory that defines it +and a specific version: `@`. -- Create a project. Description and avatar are highly recommended to improve discoverability. -- Add a `README.md` in the top level directory that documents the component. - What it does, how to use it, how to contribute, etc. - This file is mandatory. -- Add a `.gitlab-ci.yml` in the top level directory to test that the components works as expected. - This file is highly recommended. +For example: `gitlab-org/dast@1.0`. -Characteristics of a component: +### The component path -- It must have a **name** to be referenced to and **description** for extra details. -- It must specify its **type** which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step). -- It must define its **content** based on the type. -- It must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables. -- It can optionally define **output data** that it returns. -- Its YAML specification should be **validated statically** (for example: using JSON schema validators). -- It should be possible to use specific **versions** of a component by referencing official releases and SHA. -- It should be possible to use components defined locally in the same repository. +A component path must contain at least the metadata YAML and optionally a related `README.md` documentation file. + +The component path can be: + +- A path to a project: `gitlab-org/dast`. In this case the 2 files are defined in the root directory of the repository. +- A path to a project subdirectory: `gitlab-org/dast/api-scan`. In this case the 2 files are defined in the `api-scan` directory. +- A path to a local directory: `/path/to/component`. This path must contain the metadata YAML that defines the component. + The path must start with `/` to indicate a full path in the repository. + +The metadata YAML file follows the filename convention `gitlab-.yml` where component type is one of: + +| Component type | Context | +| -------------- | ------- | +| `template` | For components used under `include:` keyword | +| `step` | For components used under `steps:` keyword | +| `workflow` | For components used under `trigger:` keyword | + +Based on the context where the component is used we fetch the correct YAML file. +For example, if we are including a component `gitlab-org/dast@1.0` we expect a YAML file named `gitlab-template.yml` in the +top level directory of `gitlab-org/dast` repository. + +A `gitlab-.yml` file: + +- Must have a **name** to be referenced to and **description** for extra details. +- Must specify its **type** in the filename, which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step). +- Must define its **content** based on the type. +- Must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables. +- Can optionally define **output data** that it returns. +- Should be **validated statically** (for example: using JSON schema validators). + +Components that are released in the catalog must have a `README.md` file in the same directory as the +metadata YAML file. The `README.md` represents the documentation for the specific component, hence it's recommended +even when not releasing versions in the catalog. + +### The component version + +The version of the component can be (in order of highest priority first): + +1. A commit SHA - For example: `gitlab-org/dast@e3262fdd0914fa823210cdb79a8c421e2cef79d8` +1. A released tag - For example: `gitlab-org/dast@1.0` +1. A special moving target version that points to the most recent released tag - For example: `gitlab-org/dast@~latest` +1. An unreleased tag - For example: `gitlab-org/dast@rc-1.0` +1. A branch name - For example: `gitlab-org/dast@master` + +If a tag and branch exist with the same name, the tag takes precedence over the branch. +Similarly, if a tag is named `e3262fdd0914fa823210cdb79a8c421e2cef79d8`, a commit SHA (if exists) +takes precedence over the tag. + +As we want to be able to reference any revisions (even those not released), a component must be defined in a Git repository. + +NOTE: +When referencing a component by local path (for example `./path/to/component`), its version is implicit and matches +the commit SHA of the current pipeline context. + +## Components project + +A components project is a GitLab project/repository that exclusively hosts one or more pipeline components. + +For components projects it's highly recommended to set an appropriate avatar and project description +to improve discoverability in the catalog. + +### Structure of a components project + +A project can host one or more components depending on whether the author wants to define a single component +per project or include multiple cohesive components under the same project. + +Let's imagine we are developing a component that runs RSpec tests for a Rails app. We create a component project +called `myorg/rails-rspec`. + +The following directory structure would support 1 component per project: + +```plaintext +. +├── gitlab-.yml +├── README.md +└── .gitlab-ci.yml +``` + +The `.gitlab-ci.yml` is recommended for the project to ensure changes are verified accordingly. + +The component is now identified by the path `myorg/rails-rspec`. In other words, this means that +the `gitlab-.yml` and `README.md` are located in the root directory of the repository. + +The following directory structure would support multiple components per project: + +```plaintext +. +├── .gitlab-ci.yml +├── unit/ +│ ├── gitlab-workflow.yml +│ └── README.md +├── integration/ +│ ├── gitlab-workflow.yml +│ └── README.md +└── feature/ + ├── gitlab-workflow.yml + └── README.md +``` + +In this example we are defining multiple test profiles that are executed with RSpec. +The user could choose to use one or more of these. + +Each of these components are identified by their path `myorg/rails-rspec/unit`, `myorg/rails-rspec/integration` +and `myorg/rails-rspec/feature`. + +This directory structure could also support both strategies: + +```plaintext +. +├── gitlab-template.yml # myorg/rails-rspec +├── README.md +├── .gitlab-ci.yml +├── unit/ +│ ├── gitlab-workflow.yml # myorg/rails-rspec/unit +│ └── README.md +├── integration/ +│ ├── gitlab-workflow.yml # myorg/rails-rspec/integration +│ └── README.md +└── feature/ + ├── gitlab-workflow.yml # myorg/rails-rspec/feature + └── README.md +``` + +With the above structure we could have a top-level component that can be used as the +default component. For example, `myorg/rails-rspec` could run all the test profiles together. +However, more specific test profiles could be used separately (for example `myorg/rails-rspec/integration`). + +NOTE: +Any nesting more than 1 level is initially not permitted. +This limitation encourages cohesion at project level and keeps complexity low. ## Limits @@ -188,3 +307,34 @@ Some limits we could consider adding: - Allow self-managed administrators to populate their self-managed catalog by importing/updating components from GitLab.com or from repository exports. - Iterate on feedback. + +## Who + +Proposal: + + + +| Role | Who +|------------------------------|-------------------------| +| Author | Fabio Pitino | +| Engineering Leader | ? | +| Product Manager | Dov Hershkovitch | +| Architecture Evolution Coach | Kamil Trzciński, Grzegorz Bizon | + +DRIs: + +| Role | Who +|------------------------------|------------------------| +| Leadership | ? | +| Product | Dov Hershkovitch | +| Engineering | Fabio Pitino | +| UX | Kevin Comoli | + +Domain experts: + +| Area | Who +|------------------------------|------------------------| +| Verify / Pipeline authoring | Avielle Wolfe | +| Verify / Pipeline authoring | Furkan Ayhan | + + diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md index 5c120da32a0..356ebcc9125 100644 --- a/doc/ci/environments/protected_environments.md +++ b/doc/ci/environments/protected_environments.md @@ -200,7 +200,7 @@ To maximize the effectiveness of group-level protected environments, [group-level memberships](../../user/group/index.md) must be correctly configured: -- Operators should be given at least the Owner role +- Operators should be given the Owner role for the top-level group. They can maintain CI/CD configurations for the higher environments (such as production) in the group-level settings page, which includes group-level protected environments, diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md index 6f1bf0df044..49a03e7285d 100644 --- a/doc/subscriptions/gitlab_com/index.md +++ b/doc/subscriptions/gitlab_com/index.md @@ -335,7 +335,7 @@ locked. Projects can only be unlocked by purchasing more storage subscription un Prerequisite: -- You must have at least the Owner role. +- You must have the Owner role. You can purchase a storage subscription for your personal or group namespace. diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 73e87aa190f..d16229b525f 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -129,7 +129,7 @@ for the subgroups and projects where you don't want to use it. Prerequisites: -- You must have at least the Owner role for the group. +- You must have the Owner role for the group. To enable Auto DevOps for a group: diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md index 8dbe459d4af..f1186e4ff53 100644 --- a/doc/user/application_security/secret_detection/post_processing.md +++ b/doc/user/application_security/secret_detection/post_processing.md @@ -6,7 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Secret Detection post-processing and revocation **(FREE SAAS)** -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6. +> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6. +> - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only. + +FLAG: +By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com, +please reach out to GitLab support. GitLab supports running post-processing hooks after detecting a secret. These hooks can perform actions, like notifying the cloud service that issued the secret. @@ -16,7 +21,7 @@ The cloud provider can then confirm the credentials and take remediation actions - Reissuing a secret. - Notifying the creator of the secret. -GitLab SaaS supports post-processing for Amazon Web Services (AWS). +GitLab SaaS supports post-processing for [GitLab personal access tokens](../../profile/personal_access_tokens.md) and Amazon Web Services (AWS). Post-processing workflows vary by supported cloud providers. Post-processing is limited to a project's default branch. The epic diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index a63e6c6dd7f..163354fc37d 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -78,7 +78,7 @@ If you don't want to wait, you can remove a group immediately. Prerequisites: -- You must have at least the Owner role for a group. +- You must have the Owner role for a group. - You have [marked the group for deletion](#remove-a-group). To immediately remove a group marked for deletion: diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index c0b13c76322..6b34908c20c 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -222,21 +222,26 @@ References to pull requests and issues are preserved. Each imported repository m [visibility level is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects), in which case it defaults to the default project visibility. -### Branch protection rules +### Branch protection rules and project settings -Supported GitHub branch protection rules are mapped to GitLab branch protection rules or project-wide GitLab settings when they are imported: +When they are imported, supported GitHub branch protection rules are mapped to either: -- GitHub rule **Require conversation resolution before merging** for the project's default branch is mapped to the [**All threads must be resolved** GitLab setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) in GitLab 15.5. -- GitHub rule **Require a pull request before merging** is mapped to the **No one** option in the **Allowed to push** list of the branch protection rule. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) in GitLab 15.5. -- GitHub rule **Require a pull request before merging - Require review from Code Owners** is mapped to the - [**Code owner approval** branch protection rule](../protected_branches.md#require-code-owner-approval-on-a-protected-branch). Requires GitLab Premium or higher. - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) in GitLab 15.6. -- GitHub rule **Require signed commits** for the project's default branch is mapped to the **Reject unsigned commits** GitLab push rule. Requires GitLab Premium or higher. - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) in GitLab 15.5. -- GitHub rule **Allow force pushes - Everyone** is mapped to the [**Allowed to force push** branch protection rule](../protected_branches.md#allow-force-push-on-a-protected-branch). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) in GitLab 15.6. -- GitHub rule **Allow force pushes - Specify who can force push** is proposed in issue [370945](https://gitlab.com/gitlab-org/gitlab/-/issues/370945). -- Support for GitHub rule **Require status checks to pass before merging** was proposed in issue [370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule cannot be translated during project import into GitLab due to technical difficulties. -You can still create [status checks](../merge_requests/status_checks.md) in GitLab yourself. +- GitLab branch protection rules. +- Project-wide GitLab settings. + +| GitHub rule | GitLab rule | Introduced in | +|:------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------| +| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) | +| **Require a pull request before merging** | **No one** option in the **Allowed to push** list of [branch protection settings](../protected_branches.md#configure-a-protected-branch) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) | +| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#prevent-unintended-consequences) **(PREMIUM)** | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) | +| **Allow force pushes - Everyone** | **Allowed to force push** [branch protection setting](../protected_branches.md#allow-force-push-on-a-protected-branch) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) | +| **Require a pull request before merging - Require review from Code Owners** | **Require approval from code owners** [branch protection setting](../protected_branches.md#require-code-owner-approval-on-a-protected-branch) **(PREMIUM)** | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) | + +Mapping GitHub rule **Require status checks to pass before merging** to +[external status checks](../merge_requests/status_checks.md) was considered in issue +[370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule is not imported during project import +into GitLab due to technical difficulties. You can still create [external status checks](../merge_requests/status_checks.md) +manually. ## Alternative way to import notes and diff notes diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index ef6957ac2d8..399e9f32ae1 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -62,6 +62,41 @@ You can configure a webhook for a group or a project. 1. Optional. Clear the **Enable SSL verification** checkbox to disable [SSL verification](index.md#manage-ssl-verification). 1. Select **Add webhook**. +## Mask sensitive portions of webhook URLs + +> Introduced in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. On GitLab.com, this feature is not available. + +You can define and mask sensitive portions of webhook URLs and replace them +with configured values any number of times when webhooks are executed. +Sensitive portions do not get logged and are encrypted at rest in the database. + +To mask sensitive portions of the webhook URL: + +1. In your project or group, on the left sidebar, select **Settings > Webhooks**. +1. In **URL**, enter the full webhook URL. +1. Select **Mask portions of URL**. +1. In **Sensitive portion of URL**, enter the portion you want to mask. +1. In **How it looks in the UI**, enter the masking value. + +To interpolate sensitive portions for each webhook, use `url_variables`. +For example, if a webhook has the following URL: + +```plaintext +https://{subdomain}.example.com/{path}?key={value} +``` + +You must define the following variables: + +- `subdomain` +- `path` +- `value` + +Variable names can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`). +You can define URL variables directly using the REST API. + ## Configure your webhook receiver endpoint Webhook receiver endpoints should be fast and stable. diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md index d330ccdefb6..74c3b3e24b6 100644 --- a/doc/user/project/merge_requests/status_checks.md +++ b/doc/user/project/merge_requests/status_checks.md @@ -6,7 +6,7 @@ type: reference, concepts disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/status_checks.html' --- -# External Status Checks **(ULTIMATE)** +# External status checks **(ULTIMATE)** > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1. diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md index 6801899160d..f708e62e634 100644 --- a/doc/user/project/repository/branches/default.md +++ b/doc/user/project/repository/branches/default.md @@ -78,7 +78,7 @@ overrides it. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221014) in GitLab 13.6. -Users with at least the Owner role of groups and subgroups can configure the default branch name for a group: +Users with the Owner role of groups and subgroups can configure the default branch name for a group: 1. Go to the group **Settings > Repository**. 1. Expand **Default branch**. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index a872a339433..1d0bc674601 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -270,7 +270,7 @@ You can mark a project to be deleted. Prerequisite: -- You must have at least the Owner role for a project. +- You must have the Owner role for a project. To delete a project: @@ -308,7 +308,7 @@ If you don't want to wait, you can delete a project immediately. Prerequisites: -- You must have at least the Owner role for a project. +- You must have the Owner role for a project. - You have [marked the project for deletion](#delete-a-project). To immediately delete a project marked for deletion: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c196a5744d4..fdab41e0ecd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -45552,6 +45552,9 @@ msgstr "" msgid "We don't have enough data to show this stage." msgstr "" +msgid "We found your token in a public project and have automatically revoked it to protect your account." +msgstr "" + msgid "We have found the following errors:" msgstr "" diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 981b60d1920..8143595a18b 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -8,12 +8,12 @@ module QA def perform_before_hooks if QA::Runtime::Env.admin_personal_access_token.present? QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username, - QA::Runtime::Env.admin_personal_access_token) + QA::Runtime::Env.admin_personal_access_token) end if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present? QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username, - QA::Runtime::Env.personal_access_token) + QA::Runtime::Env.personal_access_token) end # The login page could take some time to load the first time it is visited. @@ -22,6 +22,9 @@ module QA QA::Support::Retrier.retry_on_exception do QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) end + return unless QA::Runtime::Env.allow_local_requests? + + Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true) end end end diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb index 16aa60262d8..0436b726911 100644 --- a/qa/qa/page/profile/two_factor_auth.rb +++ b/qa/qa/page/profile/two_factor_auth.rb @@ -25,7 +25,7 @@ module QA def click_configure_it_later_button # TO DO: Investigate why button does not appear sometimes: # https://gitlab.com/gitlab-org/gitlab/-/issues/382698 - return unless has_element?(:configure_it_later_button) + return unless has_element?(:configure_it_later_button, wait: 20) click_element :configure_it_later_button wait_until(max_duration: 10, message: "Waiting for create a group page") do diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb index 31db8ae4cc6..2daf9684bbc 100644 --- a/qa/qa/resource/bulk_import_group.rb +++ b/qa/qa/resource/bulk_import_group.rb @@ -11,7 +11,7 @@ module QA api_client.personal_access_token end - attribute :gitlab_address do + attribute :source_gitlab_address do QA::Runtime::Scenario.gitlab_address end @@ -28,7 +28,7 @@ module QA Page::Group::New.perform do |group| group.switch_to_import_tab - group.connect_gitlab_instance(gitlab_address, import_access_token) + group.connect_gitlab_instance(source_gitlab_address, import_access_token) end Page::Group::BulkImport.perform do |import_page| @@ -50,7 +50,7 @@ module QA def api_post_body { configuration: { - url: gitlab_address, + url: source_gitlab_address, access_token: import_access_token }, entities: [ @@ -93,7 +93,7 @@ module QA # override transformation only for /bulk_imports endpoint which doesn't have web_url in response and # ignore others so import_id is not overwritten incorrectly - api_resource[:web_url] = "#{gitlab_address}/#{full_path}" + api_resource[:web_url] = "#{source_gitlab_address}/#{full_path}" api_resource[:import_id] = api_resource[:id] api_resource end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 7cb7625118e..33ef37fcc2e 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -493,6 +493,10 @@ module QA enabled?(ENV['QA_USE_PUBLIC_IP_API'], default: false) end + def allow_local_requests? + enabled?(ENV['QA_ALLOW_LOCAL_REQUESTS'], default: false) + end + def chrome_default_download_path ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] end diff --git a/qa/qa/scenario/test/integration/import.rb b/qa/qa/scenario/test/integration/import.rb new file mode 100644 index 00000000000..4b0966998cd --- /dev/null +++ b/qa/qa/scenario/test/integration/import.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class Import < Test::Instance::All + tags :import + end + end + end + end +end diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb index e17e12cdaf3..e87bc5422d7 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb @@ -1,70 +1,14 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', :reliable, :requires_admin, product_group: :import do - describe 'Gitlab migration' do - let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } - let(:admin_api_client) { Runtime::API::Client.as_admin } - let(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end - end - - let(:api_client) { Runtime::API::Client.new(user: user) } - - let(:sandbox) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client - end - end - - let(:destination_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = sandbox - group.path = "destination-group-for-import-#{SecureRandom.hex(4)}" - end - end - - let(:source_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = sandbox - group.path = "source-group-for-import-#{SecureRandom.hex(4)}" - group.avatar = File.new('qa/fixtures/designs/tanuki.jpg', 'r') - end - end - - let(:imported_group) do - Resource::BulkImportGroup.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = destination_group - group.source_group = source_group - end - end - - let(:import_failures) do - imported_group.import_details.sum([]) { |details| details[:failures] } - end - - before do - sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - end - - after do |example| - # Checking for failures in the test currently makes test very flaky due to catching unrelated failures - # Log failures for easier debugging - Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty? - ensure - user.remove_via_api! - end + RSpec.describe "Manage", :reliable, product_group: :import do + include_context "with gitlab group migration" + describe "Gitlab migration" do context 'with subgroups and labels' do let(:subgroup) do Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client + group.api_client = source_admin_api_client group.sandbox = source_group group.path = "subgroup-for-import-#{SecureRandom.hex(4)}" end @@ -80,12 +24,12 @@ module QA before do Resource::GroupLabel.fabricate_via_api! do |label| - label.api_client = api_client + label.api_client = source_admin_api_client label.group = source_group label.title = "source-group-#{SecureRandom.hex(4)}" end Resource::GroupLabel.fabricate_via_api! do |label| - label.api_client = api_client + label.api_client = source_admin_api_client label.group = subgroup label.title = "subgroup-#{SecureRandom.hex(4)}" end @@ -112,7 +56,7 @@ module QA context 'with milestones and badges' do let(:source_milestone) do Resource::GroupMilestone.fabricate_via_api! do |milestone| - milestone.api_client = api_client + milestone.api_client = source_admin_api_client milestone.group = source_group end end @@ -121,7 +65,7 @@ module QA source_milestone Resource::GroupBadge.fabricate_via_api! do |badge| - badge.api_client = api_client + badge.api_client = source_admin_api_client badge.group = source_group badge.link_url = "http://example.com/badge" badge.image_url = "http://shields.io/badge" diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb deleted file mode 100644 index c690202f091..00000000000 --- a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -module QA - describe 'Manage', :requires_admin, :reliable, product_group: :import do - describe 'Gitlab migration' do - let!(:admin_api_client) { Runtime::API::Client.as_admin } - let!(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end - end - - let!(:api_client) { Runtime::API::Client.new(user: user) } - let!(:personal_access_token) { api_client.personal_access_token } - - let(:sandbox) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client - end - end - - let(:source_group) do - Resource::Sandbox.fabricate! do |group| - group.api_client = api_client - group.path = "source-group-for-import-#{SecureRandom.hex(4)}" - end - end - - let(:imported_group) do - Resource::BulkImportGroup.init do |group| - group.api_client = api_client - group.sandbox = sandbox - group.source_group = source_group - end - end - - before do - sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - - Flow::Login.sign_in(as: user) - - source_group - - Page::Main::Menu.perform(&:go_to_create_group) - Page::Group::New.perform do |group| - group.switch_to_import_tab - group.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token) - end - end - - after do - user.remove_via_api! - end - - it( - 'imports group from UI', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862', - issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252', - issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678', - issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351', - except: { job: 'instance-image-slow-network' } - ) do - Page::Group::BulkImport.perform do |import_page| - import_page.import_group(imported_group.path, imported_group.sandbox.path) - - expect(import_page).to have_imported_group(imported_group.path, wait: 300) - - imported_group.reload!.visit! - Page::Group::Show.perform do |group| - expect(group).to have_content(imported_group.path) - end - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb new file mode 100644 index 00000000000..4bcd2c44617 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module QA + describe 'Manage', :reliable, product_group: :import do + describe 'Gitlab migration' do + include_context "with gitlab group migration" + + let!(:imported_group) do + Resource::BulkImportGroup.init do |group| + group.api_client = api_client + group.sandbox = target_sandbox + group.source_group = source_group + end + end + + before do + Flow::Login.sign_in(as: user) + + Page::Main::Menu.perform(&:go_to_create_group) + Page::Group::New.perform do |group| + group.switch_to_import_tab + group.connect_gitlab_instance(source_gitlab_address, source_admin_api_client.personal_access_token) + end + end + + it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862' do + Page::Group::BulkImport.perform do |import_page| + import_page.import_group(source_group.path, target_sandbox.path) + + expect(import_page).to have_imported_group(imported_group.path, wait: 300) + + imported_group.reload!.visit! + Page::Group::Show.perform do |group| + expect(group).to have_content(imported_group.path) + end + end + end + end + end +end diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb new file mode 100644 index 00000000000..9ec84e12ac0 --- /dev/null +++ b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module QA + RSpec.shared_context( + 'with gitlab group migration', + :import, + :orchestrated, + requires_admin: 'creates a user via API' + ) do + let!(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } + + # source instance objects + # + let!(:source_gitlab_address) { ENV["QA_IMPORT_SOURCE_URL"] || raise("QA_IMPORT_SOURCE_URL is required!") } + let!(:source_admin_api_client) do + Runtime::API::Client.new( + source_gitlab_address, + personal_access_token: Runtime::Env.admin_personal_access_token || raise("Admin access token missing!"), + is_new_session: false + ) + end + let!(:source_admin_user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = source_admin_api_client } } + let!(:source_group) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = source_admin_api_client + group.path = "source-group-for-import-#{SecureRandom.hex(4)}" + group.avatar = File.new("qa/fixtures/designs/tanuki.jpg", "r") + end + end + + # target instance objects + # + let!(:admin_api_client) { Runtime::API::Client.as_admin } + let!(:user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = admin_api_client } } + let!(:api_client) { Runtime::API::Client.new(user: user) } + let!(:target_sandbox) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = admin_api_client + end + end + + let(:imported_group) do + Resource::BulkImportGroup.fabricate_via_api! do |group| + group.api_client = api_client + group.sandbox = target_sandbox + group.source_group = source_group + group.source_gitlab_address = source_gitlab_address + group.import_access_token = source_admin_api_client.personal_access_token + end + end + + let(:import_failures) do + imported_group.import_details.sum([]) { |details| details[:failures] } + end + + before do + target_sandbox.add_member(user, Resource::Members::AccessLevel::OWNER) + source_admin_user.set_public_email + end + + after do |example| + # Checking for failures in the test currently makes test very flaky due to catching unrelated failures + # Log failures for easier debugging + Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty? + rescue QA::Resource::Base::NoValueError + # rescue when import did not happen at all + end + end +end diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js index 6262a52e01a..d08fb9911ff 100644 --- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js +++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js @@ -37,6 +37,7 @@ describe('ScopeNavigation', () => { const findGlNav = () => wrapper.findComponent(GlNav); const findGlNavItems = () => wrapper.findAllComponents(GlNavItem); const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active')); + const findGlNavItemActiveLabel = () => findGlNavItemActive().at(0).findAll('span').at(0).text(); const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1); describe('scope navigation', () => { @@ -64,17 +65,35 @@ describe('ScopeNavigation', () => { }); }); - describe('scope navigation sets proper state', () => { + describe('scope navigation sets proper state with url scope set', () => { beforeEach(() => { createComponent(); }); - it('sets proper class to active item', () => { + it('correct item is active', () => { expect(findGlNavItemActive()).toHaveLength(1); + expect(findGlNavItemActiveLabel()).toBe('Issues'); }); - it('active item', () => { + it('correct active item count', () => { expect(findGlNavItemActiveCount().text()).toBe('2.4K'); }); + + it('correct active item count is highlighted', () => { + expect(findGlNavItemActiveCount().classes('gl-text-gray-900')).toBe(true); + }); + }); + + describe('scope navigation sets proper state with NO url scope set', () => { + beforeEach(() => { + createComponent({ + urlQuery: {}, + }); + }); + + it('correct item is active', () => { + expect(findGlNavItems().at(0).attributes('active')).toBe('true'); + expect(findGlNavItemActiveLabel()).toBe('Projects'); + }); }); }); diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 767eddb7f98..1812ac0288d 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -269,6 +269,38 @@ RSpec.describe Emails::Profile do is_expected.to have_body_text /#{token.name}/ end + it 'wont include the revocation reason' do + is_expected.not_to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$} + end + + it 'includes the email reason' do + is_expected.to have_body_text %r{You're receiving this email because of your account on localhost<\/a>} + end + end + + context 'when source is provided' do + subject { Notify.access_token_revoked_email(user, token.name, 'secret_detection') } + + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like 'a user cannot unsubscribe through footer link' + + it 'is sent to the user' do + is_expected.to deliver_to user.email + end + + it 'has the correct subject' do + is_expected.to have_subject /^A personal access token has been revoked$/i + end + + it 'provides the names of the token' do + is_expected.to have_body_text /#{token.name}/ + end + + it 'includes the revocation reason' do + is_expected.to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$} + end + it 'includes the email reason' do is_expected.to have_body_text %r{You're receiving this email because of your account on localhost<\/a>} end diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb index 4413bd8e98b..87077fe2db1 100644 --- a/spec/models/ci/secure_file_spec.rb +++ b/spec/models/ci/secure_file_spec.rb @@ -138,17 +138,48 @@ RSpec.describe Ci::SecureFile do end describe '#update_metadata!' do - it 'assigns the expected metadata when a parsable file is supplied' do + it 'assigns the expected metadata when a parsable .cer file is supplied' do file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer'))) file.update_metadata! + file.reload + expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40')) expect(file.metadata['id']).to eq('33669367788748363528491290218354043267') expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority') expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8') end + it 'assigns the expected metadata when a parsable .p12 file is supplied' do + file = create(:ci_secure_file, name: 'file1.p12', + file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.p12'))) + file.update_metadata! + + file.reload + + expect(file.expires_at).to eq(DateTime.parse('2022-09-21 14:56:00')) + expect(file.metadata['id']).to eq('75949910542696343243264405377658443914') + expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority') + expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8') + end + + it 'assigns the expected metadata when a parsable .mobileprovision file is supplied' do + file = create(:ci_secure_file, name: 'file1.mobileprovision', + file: CarrierWaveStringFile.new( + fixture_file('ci_secure_files/sample.mobileprovision') + )) + file.update_metadata! + + file.reload + + expect(file.expires_at).to eq(DateTime.parse('2023-08-01 23:15:13')) + expect(file.metadata['id']).to eq('6b9fcce1-b9a9-4b37-b2ce-ec4da2044abf') + expect(file.metadata['platforms'].first).to eq('iOS') + expect(file.metadata['app_name']).to eq('iOS Demo') + expect(file.metadata['app_id']).to eq('match Development com.gitlab.ios-demo') + end + it 'logs an error when something goes wrong with the file parsing' do corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111')) message = 'Validation failed: Metadata must be a valid json schema - not enough data.' diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 7857bd2263f..1ca14cd430b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -361,8 +361,14 @@ RSpec.describe NotificationService, :mailer do subject(:notification_service) { notification.access_token_revoked(user, pat.name) } - it 'sends email to the token owner' do - expect { notification_service }.to have_enqueued_email(user, pat.name, mail: "access_token_revoked_email") + it 'sends email to the token owner without source' do + expect { notification_service }.to have_enqueued_email(user, pat.name, nil, mail: "access_token_revoked_email") + end + + it 'sends email to the token owner with source' do + expect do + notification.access_token_revoked(user, pat.name, 'secret_detection') + end.to have_enqueued_email(user, pat.name, 'secret_detection', mail: "access_token_revoked_email") end context 'when user is not allowed to receive notifications' do diff --git a/spec/services/personal_access_tokens/revoke_service_spec.rb b/spec/services/personal_access_tokens/revoke_service_spec.rb index f16b6f00a0a..562d6017405 100644 --- a/spec/services/personal_access_tokens/revoke_service_spec.rb +++ b/spec/services/personal_access_tokens/revoke_service_spec.rb @@ -8,7 +8,12 @@ RSpec.describe PersonalAccessTokens::RevokeService do it { expect(service.token.revoked?).to be true } it 'logs the event' do - expect(Gitlab::AppLogger).to receive(:info).with(/PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '\d+'/) + expect(Gitlab::AppLogger).to receive(:info).with( + class: described_class.to_s, + message: 'PAT Revoked', + revoked_by: revoked_by, + revoked_for: token.user.username, + token_id: token.id) subject end @@ -29,7 +34,9 @@ RSpec.describe PersonalAccessTokens::RevokeService do let_it_be(:current_user) { create(:admin) } let_it_be(:token) { create(:personal_access_token) } - it_behaves_like 'a successfully revoked token' + it_behaves_like 'a successfully revoked token' do + let(:revoked_by) { current_user.username } + end end context 'when admin mode is disabled' do @@ -52,7 +59,38 @@ RSpec.describe PersonalAccessTokens::RevokeService do context 'token belongs to current_user' do let_it_be(:token) { create(:personal_access_token, user: current_user) } - it_behaves_like 'a successfully revoked token' + it_behaves_like 'a successfully revoked token' do + let(:revoked_by) { current_user.username } + end + end + end + + context 'when source' do + let(:service) { described_class.new(nil, token: token, source: source) } + + let_it_be(:current_user) { nil } + + context 'when source is valid' do + let_it_be(:source) { 'secret_detection' } + let_it_be(:token) { create(:personal_access_token) } + + it_behaves_like 'a successfully revoked token' do + let(:revoked_by) { 'secret_detection' } + end + end + + context 'when source is invalid' do + let_it_be(:source) { 'external_request' } + let_it_be(:token) { create(:personal_access_token) } + + it_behaves_like 'an unsuccessfully revoked token' + end + + context 'when source is missing' do + let_it_be(:source) { nil } + let_it_be(:token) { create(:personal_access_token) } + + it_behaves_like 'an unsuccessfully revoked token' end end end