diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue index d4de4a0c4ce..c65266fce5a 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue @@ -19,6 +19,9 @@ export default { handleButtonClick(e) { if (this.isDropdownVariantStandalone || this.isDropdownVariantEmbedded) { this.toggleDropdownContents(); + } + + if (this.isDropdownVariantStandalone) { e.stopPropagation(); } }, @@ -31,9 +34,9 @@ export default { class="labels-select-dropdown-button js-dropdown-button w-100 text-left" @click="handleButtonClick" > - + {{ dropdownButtonText }} - + diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue index ef8218b5135..6839354fb3a 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue @@ -9,6 +9,13 @@ export default { DropdownContentsLabelsView, DropdownContentsCreateView, }, + props: { + renderOnTop: { + type: Boolean, + required: false, + default: false, + }, + }, computed: { ...mapState(['showDropdownContentsCreateView']), dropdownContentsView() { @@ -17,6 +24,13 @@ export default { } return 'dropdown-contents-labels-view'; }, + directionStyle() { + if (this.renderOnTop) { + return { bottom: '100%' }; + } + + return {}; + }, }, }; @@ -24,6 +38,7 @@ export default { diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss index e20b7788a8c..4363008b0d4 100644 --- a/app/assets/stylesheets/fontawesome_custom.scss +++ b/app/assets/stylesheets/fontawesome_custom.scss @@ -9,7 +9,7 @@ // scss-lint:disable MergeableSelector @font-face { font-family: 'FontAwesome'; - src: url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('fontawesome-webfont.woff?v=4.7.0') format('woff'); + src: asset-url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'), asset-url('fontawesome-webfont.woff?v=4.7.0') format('woff'); font-weight: normal; font-style: normal; } diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index b885e55f902..4b4bcc8d37e 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -33,9 +33,7 @@ module AuthenticatesWithTwoFactor end def locked_user_redirect(user) - flash.now[:alert] = locked_user_redirect_alert(user) - - render 'devise/sessions/new' + redirect_to new_user_session_path, alert: locked_user_redirect_alert(user) end def authenticate_with_two_factor @@ -54,7 +52,13 @@ module AuthenticatesWithTwoFactor private def locked_user_redirect_alert(user) - user.access_locked? ? _('Your account is locked.') : _('Invalid Login or password') + if user.access_locked? + _('Your account is locked.') + elsif !user.confirmed? + I18n.t('devise.failure.unconfirmed') + else + _('Invalid Login or password') + end end def clear_two_factor_attempt! diff --git a/app/models/project.rb b/app/models/project.rb index cbac11639df..a16669572df 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -167,7 +167,6 @@ class Project < ApplicationRecord has_one :youtrack_service has_one :custom_issue_tracker_service has_one :bugzilla_service - has_one :gitlab_issue_tracker_service, inverse_of: :project has_one :confluence_service has_one :external_wiki_service has_one :prometheus_service, inverse_of: :project diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb deleted file mode 100644 index b3f44e040bc..00000000000 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -class GitlabIssueTrackerService < IssueTrackerService - include Gitlab::Routing - - validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? - - default_value_for :default, true - - def title - 'GitLab' - end - - def description - s_('IssueTracker|GitLab issue tracker') - end - - def self.to_param - 'gitlab' - end - - def project_url - project_issues_url(project) - end - - def new_issue_url - new_project_issue_url(project) - end - - def issue_url(iid) - project_issue_url(project, id: iid) - end - - def issue_tracker_path - project_issues_path(project) - end - - def new_issue_path - new_project_issue_path(project) - end - - def issue_path(iid) - project_issue_path(project, id: iid) - end -end diff --git a/app/models/service.rb b/app/models/service.rb index 89bde61bfe1..1edf1cb5830 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -55,11 +55,9 @@ class Service < ApplicationRecord validates :instance, uniqueness: { scope: :type }, if: -> { instance? } validate :validate_is_instance_or_template - scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } scope :issue_trackers, -> { where(category: 'issue_tracker') } scope :external_wikis, -> { where(type: 'ExternalWikiService').active } scope :active, -> { where(active: true) } - scope :without_defaults, -> { where(default: false) } scope :by_type, -> (type) { where(type: type) } scope :by_active_flag, -> (flag) { where(active: flag) } scope :templates, -> { where(template: true, type: available_services_types) } @@ -77,7 +75,7 @@ class Service < ApplicationRecord scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :deployment_hooks, -> { where(deployment_events: true, active: true) } scope :alert_hooks, -> { where(alert_events: true, active: true) } - scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } + scope :external_issue_trackers, -> { issue_trackers.active } scope :deployment, -> { where(category: 'deployment') } default_value_for :category, 'common' diff --git a/app/presenters/commit_presenter.rb b/app/presenters/commit_presenter.rb index 998e5160f8b..c14dcab6000 100644 --- a/app/presenters/commit_presenter.rb +++ b/app/presenters/commit_presenter.rb @@ -17,14 +17,6 @@ class CommitPresenter < Gitlab::View::Presenter::Delegated commit.pipelines.any? end - def web_url - url_builder.build(commit) - end - - def web_path - url_builder.build(commit, only_path: true) - end - def signature_html return unless commit.has_signature? diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb index 004813d0374..185fcd3e934 100644 --- a/app/presenters/issue_presenter.rb +++ b/app/presenters/issue_presenter.rb @@ -3,10 +3,6 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated presents :issue - def web_url - url_builder.build(issue) - end - def issue_path url_builder.build(issue, only_path: true) end diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 33b6a98f73c..1ff02412994 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -202,10 +202,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end end - def web_url - Gitlab::UrlBuilder.build(merge_request) - end - def subscribed? merge_request.subscribed?(current_user, merge_request.target_project) end diff --git a/app/presenters/snippet_presenter.rb b/app/presenters/snippet_presenter.rb index 62a90025ce1..d814c4404b6 100644 --- a/app/presenters/snippet_presenter.rb +++ b/app/presenters/snippet_presenter.rb @@ -3,12 +3,8 @@ class SnippetPresenter < Gitlab::View::Presenter::Delegated presents :snippet - def web_url - Gitlab::UrlBuilder.build(snippet) - end - def raw_url - Gitlab::UrlBuilder.build(snippet, raw: true) + url_builder.build(snippet, raw: true) end def ssh_url_to_repo diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb index 2a4d6a070f8..f201b36346f 100644 --- a/app/presenters/user_presenter.rb +++ b/app/presenters/user_presenter.rb @@ -2,12 +2,4 @@ class UserPresenter < Gitlab::View::Presenter::Delegated presents :user - - def web_url - Gitlab::Routing.url_helpers.user_url(user) - end - - def web_path - Gitlab::Routing.url_helpers.user_path(user) - end end diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 674071ad92a..805602cd8cc 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -11,14 +11,14 @@ module Projects end def execute - if file_equals?(pages_config_file, pages_config_json) - return success(reload: false) + unless file_equals?(pages_config_file, pages_config_json) + update_file(pages_config_file, pages_config_json) + reload_daemon end - update_file(pages_config_file, pages_config_json) - reload_daemon - success(reload: true) + success rescue => e + Gitlab::ErrorTracking.track_exception(e) error(e.message) end diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index d00a3d266d8..6fc156cf4ed 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -5,8 +5,7 @@ .dropdown.js-project-filter-dropdown-wrap{ class: ('d-flex flex-grow-1 flex-shrink-1' if feature_project_list_filter_bar) } %button.dropdown-menu-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } - unless has_label - = icon('globe', class: 'mt-1') - %span.light.ml-3= _("Visibility:") + %span= _("Visibility:") - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml index 50d24ff8d92..0c15d20bfe0 100644 --- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml +++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml @@ -9,18 +9,19 @@ .filter-item.inline.update-issues-btn.float-left = button_tag _('Update all'), class: "btn update-selected-issues btn-info", disabled: true = button_tag _('Cancel'), class: "btn btn-default js-bulk-update-menu-hide float-right" - .block - .title - = _('Status') - .filter-item - = dropdown_tag(_("Select status"), options: { toggle_class: "js-issue-status", title: _("Change status"), dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: _("Status") } } ) do - %ul - %li - %a{ href: "#", data: { id: "reopen" } } - = _('Open') - %li - %a{ href: "#", data: { id: "close" } } - = _('Closed') + - if params[:state] != 'merged' + .block + .title + = _('Status') + .filter-item + = dropdown_tag(_("Select status"), options: { toggle_class: "js-issue-status", title: _("Change status"), dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: _("Status") } } ) do + %ul + %li + %a{ href: "#", data: { id: "reopen" } } + = _('Open') + %li + %a{ href: "#", data: { id: "close" } } + = _('Closed') .block .title = _('Assignee') diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index d699e32c1a0..aefa4bc4223 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -14,12 +14,10 @@ class PagesWorker # rubocop:disable Scalability/IdempotentWorker # rubocop: disable CodeReuse/ActiveRecord def deploy(build_id) build = Ci::Build.find_by(id: build_id) - result = Projects::UpdatePagesService.new(build.project, build).execute - if result[:status] == :success - result = Projects::UpdatePagesConfigurationService.new(build.project).execute + update_contents = Projects::UpdatePagesService.new(build.project, build).execute + if update_contents[:status] == :success + Projects::UpdatePagesConfigurationService.new(build.project).execute end - - result end # rubocop: enable CodeReuse/ActiveRecord diff --git a/changelogs/unreleased/20033-remove-status-in-merged-tab.yml b/changelogs/unreleased/20033-remove-status-in-merged-tab.yml new file mode 100644 index 00000000000..89d8918d774 --- /dev/null +++ b/changelogs/unreleased/20033-remove-status-in-merged-tab.yml @@ -0,0 +1,5 @@ +--- +title: Remove status dropdown in merged tab +merge_request: 37544 +author: +type: fixed diff --git a/changelogs/unreleased/42751-replace-fa-globe-with-gitlab-svg-earth-icon.yml b/changelogs/unreleased/42751-replace-fa-globe-with-gitlab-svg-earth-icon.yml new file mode 100644 index 00000000000..d0ea62dd896 --- /dev/null +++ b/changelogs/unreleased/42751-replace-fa-globe-with-gitlab-svg-earth-icon.yml @@ -0,0 +1,5 @@ +--- +title: Remove globe icon from explore projects dropdown +merge_request: 21659 +author: +type: other diff --git a/changelogs/unreleased/sh-fix-500-error-unconfirmed-user.yml b/changelogs/unreleased/sh-fix-500-error-unconfirmed-user.yml new file mode 100644 index 00000000000..79abae39a0c --- /dev/null +++ b/changelogs/unreleased/sh-fix-500-error-unconfirmed-user.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error when unconfirmed OAuth2 user with 2FA logs in +merge_request: 38104 +author: +type: fixed diff --git a/changelogs/unreleased/support-parentheses-in-conditions.yml b/changelogs/unreleased/support-parentheses-in-conditions.yml new file mode 100644 index 00000000000..f82143b70d8 --- /dev/null +++ b/changelogs/unreleased/support-parentheses-in-conditions.yml @@ -0,0 +1,5 @@ +--- +title: 'Add parenthesis support for if: conditions' +merge_request: 37574 +author: +type: added diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 50d697aa2e3..5417e195a74 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -114,7 +114,7 @@ since that is needed in all configurations. --- -URL scheme: `http://page.example.io` +URL scheme: `http://.example.io/` This is the minimum setup that you can use Pages with. It is the base for all other setups as described below. NGINX will proxy all requests to the daemon. @@ -139,7 +139,7 @@ Watch the [video tutorial](https://youtu.be/dD8c7WNcc6s) for this configuration. --- -URL scheme: `https://page.example.io` +URL scheme: `https://.example.io/` NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. @@ -254,7 +254,7 @@ you have IPv6 as well as IPv4 addresses, you can use them both. --- -URL scheme: `http://page.example.io` and `http://domain.com` +URL scheme: `http://.example.io/` and `http://custom-domain.com` In that case, the Pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -285,7 +285,7 @@ world. Custom domains are supported, but no TLS. --- -URL scheme: `https://page.example.io` and `https://domain.com` +URL scheme: `https://.example.io/` and `https://custom-domain.com` In that case, the Pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index c20d54ede0b..486bc7a8777 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -94,7 +94,7 @@ since that is needed in all configurations. - [Wildcard DNS setup](#dns-configuration) -URL scheme: `http://page.example.io` +URL scheme: `http://.example.io/` This is the minimum setup that you can use Pages with. It is the base for all other setups as described below. NGINX will proxy all requests to the daemon. @@ -157,7 +157,7 @@ The Pages daemon doesn't listen to the outside world. - [Wildcard DNS setup](#dns-configuration) - Wildcard TLS certificate -URL scheme: `https://page.example.io` +URL scheme: `https://.example.io/` NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. @@ -221,7 +221,7 @@ that without TLS certificates. - [Wildcard DNS setup](#dns-configuration) - Secondary IP -URL scheme: `http://page.example.io` and `http://domain.com` +URL scheme: `http://.example.io/` and `http://custom-domain.com` In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -286,7 +286,7 @@ world. Custom domains are supported, but no TLS. - Wildcard TLS certificate - Secondary IP -URL scheme: `https://page.example.io` and `https://domain.com` +URL scheme: `https://.example.io/` and `https://custom-domain.com` In that case, the pages daemon is running, NGINX still proxies requests to the daemon but the daemon is also able to receive requests from the outside diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 0249030da09..a0adbad0725 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -742,6 +742,40 @@ Precedence of operators follows the [Ruby 2.5 standard](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html), so `&&` is evaluated before `||`. +#### Parentheses + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230938) in GitLab 13.3 + +It is possible to use parentheses to group conditions. Parentheses have the highest +precedence of all operators. Expressions enclosed in parentheses are evaluated first, +and the result is used for the rest of the expression. + +Many nested parentheses can be used to create complex conditions, and the inner-most +expressions in parentheses are evaluated first. For an expression to be valid an equal +number of `(` and `)` need to be used. + +Examples: + +- `($VARIABLE1 =~ /^content.*/ || $VARIABLE2) && ($VARIABLE3 =~ /thing$/ || $VARIABLE4)` +- `($VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/) && $VARIABLE3` +- `$CI_COMMIT_BRANCH == "my-branch" || (($VARIABLE1 == "thing" || $VARIABLE2 == "thing") && $VARIABLE3)` + +The feature is currently deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can opt to enable it for your instance. + +To enable it: + +```ruby +Feature.enable(:ci_if_parenthesis_enabled) +``` + +To disable it: + +```ruby +Feature.disable(:ci_if_parenthesis_enabled) +``` + ### Storing regular expressions in variables It is possible to store a regular expression in a variable, to be used for pattern matching: diff --git a/doc/operations/feature_flags.md b/doc/operations/feature_flags.md index 4358aea1bbf..8348f8e6295 100644 --- a/doc/operations/feature_flags.md +++ b/doc/operations/feature_flags.md @@ -136,10 +136,47 @@ target users. See the [Ruby example](#ruby-application-example) below. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35930) in GitLab 13.1. -Enables the feature for lists of users created with the [Feature Flag User List API](../api/feature_flag_user_lists.md). +Enables the feature for lists of users created [in the Feature Flags UI](#create-a-user-list), or with the [Feature Flag User List API](../api/feature_flag_user_lists.md). Similar to [User IDs](#user-ids), it uses the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid) activation strategy. +#### Create a user list + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3. + +To create a user list: + +1. In your project, navigate to **Operations > Feature Flags**. +1. Click on **New list**. +1. Enter a name for the list. +1. Click **Create**. + +You can view a list's User IDs by clicking the **{pencil}** (edit) button next to it. +When viewing a list, you can rename it by clicking the **Edit** button. + +#### Add users to a user list + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3. + +To add users to a user list: + +1. In your project, navigate to **Operations > Feature Flags**. +1. Click on the **{pencil}** (edit) button next to the list you want to add users to. +1. Click on **Add Users**. +1. Enter the user IDs as a comma-separated list of values. For example, + `user@example.com, user2@example.com`, or `username1,username2,username3`, and so on. +1. Click on **Add**. + +#### Remove users from a user list + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3. + +To remove users from a user list: + +1. In your project, navigate to **Operations > Feature Flags**. +1. Click on the **{pencil}** (edit) button next to the list you want to change. +1. Click on the **{remove}** (remove) button next to the ID you want to remove. + ### Enable or disable feature flag strategies This feature is under development, but is ready for production use. It's diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md index bf1594130f4..c47eaf08a5b 100644 --- a/doc/topics/autodevops/stages.md +++ b/doc/topics/autodevops/stages.md @@ -90,6 +90,12 @@ Check the [currently supported languages](#currently-supported-languages). Auto Test uses tests you already have in your application. If there are no tests, it's up to you to add them. +NOTE: **Note:** +Not all buildpacks supported by [Auto Build](#auto-build) are supported by Auto Test. +Auto Test uses [Herokuish](https://gitlab.com/gitlab-org/gitlab/-/issues/212689), *not* +Cloud Native Buildpacks, and only buildpacks that implement the +[Testpack API](https://devcenter.heroku.com/articles/testpack-api) are supported. + ### Currently supported languages Note that not all buildpacks support Auto Test yet, as it's a relatively new diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md index db3564dc6a1..c202a8d303a 100644 --- a/doc/user/application_security/coverage_fuzzing/index.md +++ b/doc/user/application_security/coverage_fuzzing/index.md @@ -49,6 +49,14 @@ targets. Each fuzz target **must** have a separate job. For example, the [go-fuzzing-example project](https://gitlab.com/gitlab-org/security-products/demos/go-fuzzing-example) contains one job that extends `.fuzz_base` for its single fuzz target. +Note that the hidden job `.fuzz_base` uses several YAML keys that you must not override in your own +job. If you include these keys in your own job, you must copy their original content. These keys +are: + +- `before_script` +- `artifacts` +- `rules` + The `my_fuzz_target` job (the separate job for your fuzz target) does the following: - Extends `.fuzz_base`. diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index bf75e4671b3..7bdf9dedb27 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -61,9 +61,9 @@ GitLab uses the following tools to scan and report known vulnerabilities found i | [Dependency List](dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. | | [Dependency Scanning](dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. | | [Dynamic Application Security Testing (DAST)](dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. | -| [Secret Detection](secret_detection/index.md) **(ULTIMATE)** | Analyze Git history for leaked secrets. | +| [Secret Detection](secret_detection/index.md) **(ULTIMATE)** | Analyze Git history for leaked secrets. | | [Security Dashboard](security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all your projects and groups. | -| [Static Application Security Testing (SAST)](sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. | +| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. | | [Coverage fuzzing](coverage_fuzzing/index.md) **(ULTIMATE)** | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. | ## Security Scanning with Auto DevOps diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 04ba7c14f44..3524f492dff 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: reference, howto --- -# Static Application Security Testing (SAST) **(ULTIMATE)** +# Static Application Security Testing (SAST) > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3775) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.3. @@ -26,7 +26,9 @@ You can take advantage of SAST by doing one of the following: [Auto DevOps](../../../topics/autodevops/index.md). GitLab checks the SAST report, compares the found vulnerabilities between the -source and target branches, and shows the information right on the merge request. +source and target branches. + +Details of the vulnerabilities found are included in the merge request. **(ULTIMATE)** ![SAST Widget](img/sast_v13_2.png) @@ -56,7 +58,7 @@ To run SAST jobs, by default, you need a GitLab Runner with the [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor. If you're using the shared Runners on GitLab.com, this is enabled by default. -Beginning with GitLab 13.0, Docker privileged mode is necessary only if you've [enabled Docker-in-Docker for SAST](#enabling-docker-in-docker). +Beginning with GitLab 13.0, Docker privileged mode is necessary only if you've [enabled Docker-in-Docker for SAST](#enabling-docker-in-docker-ultimate). CAUTION: **Caution:** Our SAST jobs currently expect a Linux container type. Windows containers are not yet supported. @@ -69,27 +71,27 @@ is **not** `19.03.0`. See [troubleshooting information](#error-response-from-dae The following table shows which languages, package managers and frameworks are supported and which tools are used. -| Language (package managers) / framework | Scan tool | Introduced in GitLab Version | -|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------| -| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11., [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Go | [Gosec](https://github.com/securego/gosec) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 | -| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 | -| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 | -| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | -| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 | +| Language (package managers) / framework | Scan tool | Introduced in GitLab Version | +|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11., [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Go | [Gosec](https://github.com/securego/gosec) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 | +| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 | +| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 | +| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 | +| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 | NOTE: **Note:** The Java analyzers can also be used for variants like the @@ -102,7 +104,7 @@ All open source (OSS) analyzers have been moved to the GitLab Core tier. Progres tracked in the corresponding [epic](https://gitlab.com/groups/gitlab-org/-/epics/2098). -Please note that support for [Docker-in-Docker](#enabling-docker-in-docker) +Please note that support for [Docker-in-Docker](#enabling-docker-in-docker-ultimate) will not be extended to the GitLab Core tier. #### Summary of features per tier @@ -110,14 +112,14 @@ will not be extended to the GitLab Core tier. Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table: -| Capability | In Core | In Ultimate | -|:--------------------------------------------------------------------------|:--------------------|:-------------------| -| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** | -| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** | -| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** | -| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** | -| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities) | **{dotted-circle}** | **{check-circle}** | -| [Access to Security Dashboard](#security-dashboard) | **{dotted-circle}** | **{check-circle}** | +| Capability | In Core | In Ultimate | +|:-----------------------------------------------------------------------------------|:--------------------|:-------------------| +| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** | +| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** | +| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** | +| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** | +| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities-ultimate) | **{dotted-circle}** | **{check-circle}** | +| [Access to Security Dashboard](#security-dashboard-ultimate) | **{dotted-circle}** | **{check-circle}** | ## Contribute your scanner @@ -203,7 +205,7 @@ you can use the `MAVEN_CLI_OPTS` environment variable. Read more on [how to use private Maven repositories](../index.md#using-private-maven-repos). -### Enabling Docker-in-Docker +### Enabling Docker-in-Docker **(ULTIMATE)** If needed, you can enable Docker-in-Docker to restore the SAST behavior that existed prior to GitLab 13.0. Follow these steps to do so: @@ -310,12 +312,12 @@ of CA certs that you want to trust within the SAST environment. The following are Docker image-related variables. -| Environment variable | Description | -|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). | -| `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). | -| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). | -| `SAST_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker). This variable is `true` by default. | +| Environment variable | Description | +|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). | +| `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). | +| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). | +| `SAST_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker-ultimate). This variable is `true` by default. | #### Vulnerability filters @@ -324,7 +326,7 @@ Some analyzers make it possible to filter out vulnerabilities under a given thre | Environment variable | Default value | Description | |-------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `SAST_EXCLUDED_PATHS` | `spec, test, tests, tmp` | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec` ). Parent directories will also match patterns. | -| `SAST_BANDIT_EXCLUDED_PATHS` | | Comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*, */venv/*'` | +| `SAST_BANDIT_EXCLUDED_PATHS` | | Comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*, */venv/*'` | | `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. | | `SAST_DISABLE_BABEL` | `false` | Disable Babel processing for the NodeJsScan scanner. Set to `true` to disable Babel processing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33065) in GitLab 13.2. | | `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. | @@ -336,40 +338,40 @@ Some analyzers make it possible to filter out vulnerabilities under a given thre #### Docker-in-Docker orchestrator -The following variables configure the Docker-in-Docker orchestrator, and therefore are only used when the Docker-in-Docker mode is [enabled](#enabling-docker-in-docker). +The following variables configure the Docker-in-Docker orchestrator, and therefore are only used when the Docker-in-Docker mode is [enabled](#enabling-docker-in-docker-ultimate). -| Environment variable | Default value | Description | -|------------------------------------------|---------------|-------------| -| `SAST_ANALYZER_IMAGES` | | Comma-separated list of custom images. Default images are still enabled. Read more about [customizing analyzers](analyzers.md). | -| `SAST_PULL_ANALYZER_IMAGES` | 1 | Pull the images from the Docker registry (set to 0 to disable). Read more about [customizing analyzers](analyzers.md). | -| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. | -| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. | -| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`.| +| Environment variable | Default value | Description | +|------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `SAST_ANALYZER_IMAGES` | | Comma-separated list of custom images. Default images are still enabled. Read more about [customizing analyzers](analyzers.md). | +| `SAST_PULL_ANALYZER_IMAGES` | 1 | Pull the images from the Docker registry (set to 0 to disable). Read more about [customizing analyzers](analyzers.md). | +| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. | +| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. | +| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. | #### Analyzer settings Some analyzers can be customized with environment variables. -| Environment variable | Analyzer | Description | -|---------------------------------------|----------------------|-------------| -| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. | -| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. | -| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. | -| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195252) in GitLab 13.1. | -| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. | -| `ANT_PATH` | SpotBugs | Path to the `ant` executable. | -| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. | -| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. | -| `JAVA_PATH` | SpotBugs | Path to the `java` executable. | -| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. | -| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. | -| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. | -| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). | -| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. | -| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. | -| `SAST_GOSEC_CONFIG` | Gosec | Path to configuration for Gosec (optional). | -| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. | -| `SEARCH_MAX_DEPTH` | any | Maximum number of directories traversed when searching for source code files. Default: `4`. | +| Environment variable | Analyzer | Description | +|---------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. | +| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. | +| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. | +| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195252) in GitLab 13.1. | +| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. | +| `ANT_PATH` | SpotBugs | Path to the `ant` executable. | +| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. | +| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. | +| `JAVA_PATH` | SpotBugs | Path to the `java` executable. | +| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. | +| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. | +| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. | +| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). | +| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. | +| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. | +| `SAST_GOSEC_CONFIG` | Gosec | Path to configuration for Gosec (optional). | +| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. | +| `SEARCH_MAX_DEPTH` | any | Maximum number of directories traversed when searching for source code files. Default: `4`. | #### Custom environment variables @@ -471,13 +473,13 @@ Here's an example SAST report: Learn more about [Secret Detection](../secret_detection). -## Security Dashboard +## Security Dashboard **(ULTIMATE)** The Security Dashboard is a good place to get an overview of all the security vulnerabilities in your groups, projects and pipelines. Read more about the [Security Dashboard](../security_dashboard/index.md). -## Interacting with the vulnerabilities +## Interacting with the vulnerabilities **(ULTIMATE)** Once a vulnerability is found, you can interact with it. Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md index 449b1d58804..afb6d016f45 100644 --- a/doc/user/project/clusters/kubernetes_pod_logs.md +++ b/doc/user/project/clusters/kubernetes_pod_logs.md @@ -20,7 +20,7 @@ above the log file data, depending on your configuration: - **Namespace** - Select the environment to display. Users with Maintainer or greater [permissions](../../permissions.md) can also select Managed Apps. - **Search** - Only available if the Elastic Stack managed application is installed. -- **Time picker** - Select the range of time to display. Only available if the +- **Select time range** - Select the range of time to display. Only available if the Elastic Stack managed application is installed. - **Scroll to bottom** **{scroll_down}** - Scroll to the end of the displayed logs. - **Refresh** **{retry}** - Reload the displayed logs. diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index dc4b865bfb6..216418ee5fa 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -23,10 +23,6 @@ module Banzai issue_url(issue, project) end - def projects_relation_for_paths(paths) - super(paths).includes(:gitlab_issue_tracker_service) - end - def parent_records(parent, ids) parent.issues.where(iid: ids.to_a) end diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index cb1dbda8824..f619847078e 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -70,6 +70,10 @@ module Gitlab ::Feature.enabled?(:ci_bulk_insert_on_create, project, default_enabled: true) end + def self.ci_if_parenthesis_enabled? + ::Feature.enabled?(:ci_if_parenthesis_enabled) + end + def self.allow_to_create_merge_request_pipelines_in_target_project?(target_project) ::Feature.enabled?(:ci_allow_to_create_merge_request_pipelines_in_target_project, target_project, default_enabled: true) end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/and.rb b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb index 54a0e2ad9dd..422735bd104 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/and.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class And < Lexeme::Operator + class And < Lexeme::LogicalOperator PATTERN = /&&/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb index 7ebd2e25398..676857183cf 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb @@ -10,6 +10,10 @@ module Gitlab raise NotImplementedError end + def name + self.class.name.demodulize.underscore + end + def self.build(token) raise NotImplementedError end @@ -23,6 +27,10 @@ module Gitlab def self.pattern self::PATTERN end + + def self.consume?(lexeme) + lexeme && precedence >= lexeme.precedence + end end end end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb index 62f4c14f597..d35be12c996 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class Equals < Lexeme::Operator + class Equals < Lexeme::LogicalOperator PATTERN = /==/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/logical_operator.rb b/lib/gitlab/ci/pipeline/expression/lexeme/logical_operator.rb new file mode 100644 index 00000000000..05d5043c06e --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/logical_operator.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class LogicalOperator < Lexeme::Operator + # This operator class is design to handle single operators that take two + # arguments. Expression::Parser was originally designed to read infix operators, + # and so the two operands are called "left" and "right" here. If we wish to + # implement an Operator that takes a greater or lesser number of arguments, a + # structural change or additional Operator superclass will likely be needed. + + def initialize(left, right) + raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate + raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate + + @left = left + @right = right + end + + def inspect + "#{name}(#{@left.inspect}, #{@right.inspect})" + end + + def self.type + :logical_operator + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb index f7b0720d4a9..4d65b914d8d 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class Matches < Lexeme::Operator + class Matches < Lexeme::LogicalOperator PATTERN = /=~/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb index 8166bcd5730..64485a7e6b3 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class NotEquals < Lexeme::Operator + class NotEquals < Lexeme::LogicalOperator PATTERN = /!=/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb index 02479ed28a4..29c5aa5d753 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class NotMatches < Lexeme::Operator + class NotMatches < Lexeme::LogicalOperator PATTERN = /\!~/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/null.rb b/lib/gitlab/ci/pipeline/expression/lexeme/null.rb index be7258c201a..e7f7945532b 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/null.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/null.rb @@ -9,13 +9,17 @@ module Gitlab PATTERN = /null/.freeze def initialize(value = nil) - @value = nil + super end def evaluate(variables = {}) nil end + def inspect + 'null' + end + def self.build(_value) self.new end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb index 3ddab7800c8..a740c50c900 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb @@ -6,24 +6,10 @@ module Gitlab module Expression module Lexeme class Operator < Lexeme::Base - # This operator class is design to handle single operators that take two - # arguments. Expression::Parser was originally designed to read infix operators, - # and so the two operands are called "left" and "right" here. If we wish to - # implement an Operator that takes a greater or lesser number of arguments, a - # structural change or additional Operator superclass will likely be needed. - OperatorError = Class.new(Expression::ExpressionError) - def initialize(left, right) - raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate - raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate - - @left = left - @right = right - end - def self.type - :operator + raise NotImplementedError end def self.precedence diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/or.rb b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb index 807876f905a..c7d653ac859 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/or.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb @@ -5,7 +5,7 @@ module Gitlab module Pipeline module Expression module Lexeme - class Or < Lexeme::Operator + class Or < Lexeme::LogicalOperator PATTERN = /\|\|/.freeze def evaluate(variables = {}) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_close.rb b/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_close.rb new file mode 100644 index 00000000000..b0ca26c9f5d --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_close.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class ParenthesisClose < Lexeme::Operator + PATTERN = /\)/.freeze + + def self.type + :parenthesis_close + end + + def self.precedence + 900 + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_open.rb b/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_open.rb new file mode 100644 index 00000000000..924fe0663ab --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_open.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class ParenthesisOpen < Lexeme::Operator + PATTERN = /\(/.freeze + + def self.type + :parenthesis_open + end + + def self.precedence + # Needs to be higher than `ParenthesisClose` and all other Lexemes + 901 + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 0212fa9d661..514241e8ae2 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -11,7 +11,7 @@ module Gitlab PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze def initialize(regexp) - @value = regexp.gsub(/\\\//, '/') + super(regexp.gsub(/\\\//, '/')) unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value) raise Lexer::SyntaxError, 'Invalid regular expression!' @@ -24,6 +24,10 @@ module Gitlab raise Expression::RuntimeError, 'Invalid regular expression!' end + def inspect + "/#{value}/" + end + def self.pattern PATTERN end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/string.rb b/lib/gitlab/ci/pipeline/expression/lexeme/string.rb index 2db2bf011f1..e90e764bcd9 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/string.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/string.rb @@ -9,13 +9,17 @@ module Gitlab PATTERN = /("(?.*?)")|('(?.*?)')/.freeze def initialize(value) - @value = value + super(value) end def evaluate(variables = {}) @value.to_s end + def inspect + @value.inspect + end + def self.build(string) new(string.match(PATTERN)[:string]) end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/value.rb b/lib/gitlab/ci/pipeline/expression/lexeme/value.rb index ef9ddb6cae9..6d872fee39d 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/value.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/value.rb @@ -9,6 +9,10 @@ module Gitlab def self.type :value end + + def initialize(value) + @value = value + end end end end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb index 85c0899e4f6..11d2010909f 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb @@ -8,12 +8,12 @@ module Gitlab class Variable < Lexeme::Value PATTERN = /\$(?\w+)/.freeze - def initialize(name) - @name = name + def evaluate(variables = {}) + variables.with_indifferent_access.fetch(@value, nil) end - def evaluate(variables = {}) - variables.with_indifferent_access.fetch(@name, nil) + def inspect + "$#{@value}" end def self.build(string) diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb index 7d7582612f9..5b7365cb33b 100644 --- a/lib/gitlab/ci/pipeline/expression/lexer.rb +++ b/lib/gitlab/ci/pipeline/expression/lexer.rb @@ -10,6 +10,8 @@ module Gitlab SyntaxError = Class.new(Expression::ExpressionError) LEXEMES = [ + Expression::Lexeme::ParenthesisOpen, + Expression::Lexeme::ParenthesisClose, Expression::Lexeme::Variable, Expression::Lexeme::String, Expression::Lexeme::Pattern, @@ -22,6 +24,28 @@ module Gitlab Expression::Lexeme::Or ].freeze + # To be removed with `ci_if_parenthesis_enabled` + LEGACY_LEXEMES = [ + Expression::Lexeme::Variable, + Expression::Lexeme::String, + Expression::Lexeme::Pattern, + Expression::Lexeme::Null, + Expression::Lexeme::Equals, + Expression::Lexeme::Matches, + Expression::Lexeme::NotEquals, + Expression::Lexeme::NotMatches, + Expression::Lexeme::And, + Expression::Lexeme::Or + ].freeze + + def self.lexemes + if ::Gitlab::Ci::Features.ci_if_parenthesis_enabled? + LEXEMES + else + LEGACY_LEXEMES + end + end + MAX_TOKENS = 100 def initialize(statement, max_tokens: MAX_TOKENS) @@ -47,7 +71,7 @@ module Gitlab return tokens if @scanner.eos? - lexeme = LEXEMES.find do |type| + lexeme = self.class.lexemes.find do |type| type.scan(@scanner).tap do |token| tokens.push(token) if token.present? end diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb index edb55edf356..27d7aa2f37e 100644 --- a/lib/gitlab/ci/pipeline/expression/parser.rb +++ b/lib/gitlab/ci/pipeline/expression/parser.rb @@ -15,11 +15,18 @@ module Gitlab def tree results = [] - tokens_rpn.each do |token| + tokens = + if ::Gitlab::Ci::Features.ci_if_parenthesis_enabled? + tokens_rpn + else + legacy_tokens_rpn + end + + tokens.each do |token| case token.type when :value results.push(token.build) - when :operator + when :logical_operator right_operand = results.pop left_operand = results.pop @@ -27,7 +34,7 @@ module Gitlab results.push(res) end else - raise ParseError, 'Unprocessable token found in parse tree' + raise ParseError, "Unprocessable token found in parse tree: #{token.type}" end end @@ -45,6 +52,7 @@ module Gitlab # Parse the expression into Reverse Polish Notation # (See: Shunting-yard algorithm) + # Taken from: https://en.wikipedia.org/wiki/Shunting-yard_algorithm#The_algorithm_in_detail def tokens_rpn output = [] operators = [] @@ -53,7 +61,34 @@ module Gitlab case token.type when :value output.push(token) - when :operator + when :logical_operator + output.push(operators.pop) while token.lexeme.consume?(operators.last&.lexeme) + + operators.push(token) + when :parenthesis_open + operators.push(token) + when :parenthesis_close + output.push(operators.pop) while token.lexeme.consume?(operators.last&.lexeme) + + raise ParseError, 'Unmatched parenthesis' unless operators.last + + operators.pop if operators.last.lexeme.type == :parenthesis_open + end + end + + output.concat(operators.reverse) + end + + # To be removed with `ci_if_parenthesis_enabled` + def legacy_tokens_rpn + output = [] + operators = [] + + @tokens.each do |token| + case token.type + when :value + output.push(token) + when :logical_operator if operators.any? && token.lexeme.precedence >= operators.last.lexeme.precedence output.push(operators.pop) end diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb index 1aa57c1b19c..9dc687f7740 100644 --- a/lib/gitlab/view/presenter/base.rb +++ b/lib/gitlab/view/presenter/base.rb @@ -34,6 +34,14 @@ module Gitlab super || subject.is_a?(type) end + def web_url + url_builder.build(subject) + end + + def web_path + url_builder.build(subject, only_path: true) + end + class_methods do def presenter? true diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5ae025dff02..be2667832e9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10219,10 +10219,7 @@ msgstr "" msgid "FeatureFlags|Edit Feature Flag" msgstr "" -msgid "FeatureFlags|Edit Feature Flag User List" -msgstr "" - -msgid "FeatureFlags|Edit list" +msgid "FeatureFlags|Edit User List" msgstr "" msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies." @@ -10303,15 +10300,12 @@ msgstr "" msgid "FeatureFlags|New Feature Flag" msgstr "" -msgid "FeatureFlags|New Feature Flag User List" +msgid "FeatureFlags|New User List" msgstr "" msgid "FeatureFlags|New feature flag" msgstr "" -msgid "FeatureFlags|New list" -msgstr "" - msgid "FeatureFlags|Percent rollout (logged in users)" msgstr "" @@ -13195,9 +13189,6 @@ msgstr "" msgid "IssueTracker|Custom issue tracker" msgstr "" -msgid "IssueTracker|GitLab issue tracker" -msgstr "" - msgid "IssueTracker|Redmine issue tracker" msgstr "" diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 0b99f28f79b..dce996b977d 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -181,6 +181,23 @@ RSpec.describe OmniauthCallbacksController, type: :controller do end end + context 'when user with 2FA is unconfirmed' do + render_views + + let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider) } + + before do + user.update_column(:confirmed_at, nil) + end + + it 'redirects to login page' do + post provider + + expect(response).to redirect_to(new_user_session_path) + expect(flash[:alert]).to match(/You have to confirm your email address before continuing./) + end + end + context 'sign up' do include_context 'sign_up' diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 5340128e126..06b79b26bf0 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -399,7 +399,7 @@ RSpec.describe SessionsController do end it 'warns about invalid login' do - expect(response).to set_flash.now[:alert].to /Your account is locked./ + expect(flash[:alert]).to eq('Your account is locked.') end it 'locks the user' do @@ -409,7 +409,7 @@ RSpec.describe SessionsController do it 'keeps the user locked on future login attempts' do post(:create, params: { user: { login: user.username, password: user.password } }) - expect(response).to set_flash.now[:alert].to /Your account is locked./ + expect(flash[:alert]).to eq('Your account is locked.') end end end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 3dafc3404b2..7fbf6f16dc7 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -116,12 +116,6 @@ FactoryBot.define do issue_tracker end - factory :gitlab_issue_tracker_service do - project - active { true } - issue_tracker - end - trait :issue_tracker do transient do create_data { true } diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb index 34cea7f3b0b..165f4b10cff 100644 --- a/spec/features/issues/user_views_issues_spec.rb +++ b/spec/features/issues/user_views_issues_spec.rb @@ -10,10 +10,6 @@ RSpec.describe "User views issues" do let_it_be(:user) { create(:user) } - before do - stub_feature_flags(vue_issuables_list: false) - end - shared_examples "opens issue from list" do it "opens issue" do click_link(issue.title) @@ -112,7 +108,7 @@ RSpec.describe "User views issues" do end end - context "when signed in as developer" do + context "when signed in as developer", :js do before do project.add_developer(user) sign_in(user) @@ -122,27 +118,7 @@ RSpec.describe "User views issues" do include_examples "internal project" end - context "when not signed in" do + context "when not signed in", :js do include_examples "public project" end - - context 'when vue_issuables_list feature is enabled', :js do - before do - stub_feature_flags(vue_issuables_list: true) - end - - context 'when signed in' do - before do - project.add_developer(user) - sign_in(user) - end - - include_examples "public project" - include_examples "internal project" - end - - context 'when not signed in' do - include_examples "public project" - end - end end diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index df94fe2cbd0..589825f12af 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -37,6 +37,15 @@ RSpec.describe 'Merge requests > User mass updates', :js do expect(page).to have_selector('.merge-request', count: 0) end end + + it 'does not exist in merged state' do + merge_request.close + visit project_merge_requests_path(project, state: 'merged') + + click_button 'Edit merge requests' + + expect(page).not_to have_css('.js-issue-status') + end end context 'assignee' do diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js index 68c9d26bb1a..cb758797c63 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js @@ -41,23 +41,20 @@ describe('DropdownButton', () => { describe('methods', () => { describe('handleButtonClick', () => { it.each` - variant - ${'standalone'} - ${'embedded'} + variant | expectPropagationStopped + ${'standalone'} | ${true} + ${'embedded'} | ${false} `( - 'toggles dropdown content and stops event propagation when `state.variant` is "$variant"', - ({ variant }) => { + 'toggles dropdown content and handles event propagation when `state.variant` is "$variant"', + ({ variant, expectPropagationStopped }) => { const event = { stopPropagation: jest.fn() }; - wrapper = createComponent({ - ...mockConfig, - variant, - }); + wrapper = createComponent({ ...mockConfig, variant }); findDropdownButton().vm.$emit('click', event); expect(store.state.showDropdownContents).toBe(true); - expect(event.stopPropagation).toHaveBeenCalled(); + expect(event.stopPropagation).toHaveBeenCalledTimes(expectPropagationStopped ? 1 : 0); }, ); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index 9b01e0b9637..589be0ad7a4 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -17,53 +17,47 @@ import { mockConfig, mockLabels, mockRegularLabel } from './mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); -const createComponent = (initialState = mockConfig) => { - const store = new Vuex.Store({ - getters, - mutations, - state: { - ...defaultState(), - footerCreateLabelTitle: 'Create label', - footerManageLabelTitle: 'Manage labels', - }, - actions: { - ...actions, - fetchLabels: jest.fn(), - }, - }); - - store.dispatch('setInitialState', initialState); - store.dispatch('receiveLabelsSuccess', mockLabels); - - return shallowMount(DropdownContentsLabelsView, { - localVue, - store, - }); -}; - describe('DropdownContentsLabelsView', () => { let wrapper; - let wrapperStandalone; - let wrapperEmbedded; + + const createComponent = (initialState = mockConfig) => { + const store = new Vuex.Store({ + getters, + mutations, + state: { + ...defaultState(), + footerCreateLabelTitle: 'Create label', + footerManageLabelTitle: 'Manage labels', + }, + actions: { + ...actions, + fetchLabels: jest.fn(), + }, + }); + + store.dispatch('setInitialState', initialState); + store.dispatch('receiveLabelsSuccess', mockLabels); + + wrapper = shallowMount(DropdownContentsLabelsView, { + localVue, + store, + }); + }; beforeEach(() => { - wrapper = createComponent(); - wrapperStandalone = createComponent({ - ...mockConfig, - variant: 'standalone', - }); - wrapperEmbedded = createComponent({ - ...mockConfig, - variant: 'embedded', - }); + createComponent(); }); afterEach(() => { wrapper.destroy(); - wrapperStandalone.destroy(); - wrapperEmbedded.destroy(); + wrapper = null; }); + const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]'); + const findDropdownTitle = () => wrapper.find('[data-testid="dropdown-title"]'); + const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]'); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + describe('computed', () => { describe('visibleLabels', () => { it('returns matching labels filtered with `searchKey`', () => { @@ -83,6 +77,24 @@ describe('DropdownContentsLabelsView', () => { expect(wrapper.vm.visibleLabels.length).toBe(mockLabels.length); }); }); + + describe('showListContainer', () => { + it.each` + variant | loading | showList + ${'sidebar'} | ${false} | ${true} + ${'sidebar'} | ${true} | ${false} + ${'not-sidebar'} | ${true} | ${true} + ${'not-sidebar'} | ${false} | ${true} + `( + 'returns $showList if `state.variant` is "$variant" and `labelsFetchInProgress` is $loading', + ({ variant, loading, showList }) => { + createComponent({ ...mockConfig, variant }); + wrapper.vm.$store.state.labelsFetchInProgress = loading; + + expect(wrapper.vm.showListContainer).toBe(showList); + }, + ); + }); }); describe('methods', () => { @@ -199,7 +211,7 @@ describe('DropdownContentsLabelsView', () => { wrapper.vm.$store.dispatch('requestLabels'); return wrapper.vm.$nextTick(() => { - const loadingIconEl = wrapper.find(GlLoadingIcon); + const loadingIconEl = findLoadingIcon(); expect(loadingIconEl.exists()).toBe(true); expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading'); @@ -207,22 +219,24 @@ describe('DropdownContentsLabelsView', () => { }); it('renders dropdown title element', () => { - const titleEl = wrapper.find('.dropdown-title > span'); + const titleEl = findDropdownTitle(); expect(titleEl.exists()).toBe(true); expect(titleEl.text()).toBe('Assign labels'); }); it('does not render dropdown title element when `state.variant` is "standalone"', () => { - expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false); + createComponent({ ...mockConfig, variant: 'standalone' }); + expect(findDropdownTitle().exists()).toBe(false); }); it('renders dropdown title element when `state.variant` is "embedded"', () => { - expect(wrapperEmbedded.find('.dropdown-title').exists()).toBe(true); + createComponent({ ...mockConfig, variant: 'embedded' }); + expect(findDropdownTitle().exists()).toBe(true); }); it('renders dropdown close button element', () => { - const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton); + const closeButtonEl = findDropdownTitle().find(GlButton); expect(closeButtonEl.exists()).toBe(true); expect(closeButtonEl.props('icon')).toBe('close'); @@ -249,8 +263,7 @@ describe('DropdownContentsLabelsView', () => { }); return wrapper.vm.$nextTick(() => { - const labelsEl = wrapper.findAll('.dropdown-content li'); - const labelItemEl = labelsEl.at(0).find(LabelItem); + const labelItemEl = findDropdownContent().find(LabelItem); expect(labelItemEl.props('highlight')).toBe(true); }); @@ -262,22 +275,28 @@ describe('DropdownContentsLabelsView', () => { }); return wrapper.vm.$nextTick(() => { - const noMatchEl = wrapper.find('.dropdown-content li'); + const noMatchEl = findDropdownContent().find('li'); expect(noMatchEl.isVisible()).toBe(true); expect(noMatchEl.text()).toContain('No matching results'); }); }); + it('renders empty content while loading', () => { + wrapper.vm.$store.state.labelsFetchInProgress = true; + + return wrapper.vm.$nextTick(() => { + const dropdownContent = findDropdownContent(); + + expect(dropdownContent.exists()).toBe(true); + expect(dropdownContent.isVisible()).toBe(false); + }); + }); + it('renders footer list items', () => { - const createLabelLink = wrapper - .find('.dropdown-footer') - .findAll(GlLink) - .at(0); - const manageLabelsLink = wrapper - .find('.dropdown-footer') - .findAll(GlLink) - .at(1); + const footerLinks = findDropdownFooter().findAll(GlLink); + const createLabelLink = footerLinks.at(0); + const manageLabelsLink = footerLinks.at(1); expect(createLabelLink.exists()).toBe(true); expect(createLabelLink.text()).toBe('Create label'); @@ -289,8 +308,7 @@ describe('DropdownContentsLabelsView', () => { wrapper.vm.$store.state.allowLabelCreate = false; return wrapper.vm.$nextTick(() => { - const createLabelLink = wrapper - .find('.dropdown-footer') + const createLabelLink = findDropdownFooter() .findAll(GlLink) .at(0); @@ -299,11 +317,12 @@ describe('DropdownContentsLabelsView', () => { }); it('does not render footer list items when `state.variant` is "standalone"', () => { - expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false); + createComponent({ ...mockConfig, variant: 'standalone' }); + expect(findDropdownFooter().exists()).toBe(false); }); it('renders footer list items when `state.variant` is "embedded"', () => { - expect(wrapperEmbedded.find('.dropdown-footer').exists()).toBe(true); + expect(findDropdownFooter().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js index bb462acf11c..97946993857 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_spec.js @@ -10,12 +10,13 @@ import { mockConfig } from './mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); -const createComponent = (initialState = mockConfig) => { +const createComponent = (initialState = mockConfig, propsData = {}) => { const store = new Vuex.Store(labelsSelectModule()); store.dispatch('setInitialState', initialState); return shallowMount(DropdownContents, { + propsData, localVue, store, }); @@ -47,8 +48,15 @@ describe('DropdownContent', () => { }); describe('template', () => { - it('renders component container element with class `labels-select-dropdown-contents`', () => { + it('renders component container element with class `labels-select-dropdown-contents` and no styles', () => { expect(wrapper.attributes('class')).toContain('labels-select-dropdown-contents'); + expect(wrapper.attributes('style')).toBe(undefined); + }); + + it('renders component container element with styles when `renderOnTop` is true', () => { + wrapper = createComponent(mockConfig, { renderOnTop: true }); + + expect(wrapper.attributes('style')).toContain('bottom: 100%'); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js index 6e97b046be2..a1e0db4d29e 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js @@ -9,9 +9,14 @@ import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dr import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; +import { isInViewport } from '~/lib/utils/common_utils'; import { mockConfig } from './mock_data'; +jest.mock('~/lib/utils/common_utils', () => ({ + isInViewport: jest.fn().mockReturnValue(true), +})); + const localVue = createLocalVue(); localVue.use(Vuex); @@ -21,6 +26,9 @@ const createComponent = (config = mockConfig, slots = {}) => slots, store: new Vuex.Store(labelsSelectModule()), propsData: config, + stubs: { + 'dropdown-contents': DropdownContents, + }, }); describe('LabelsSelectRoot', () => { @@ -144,5 +152,42 @@ describe('LabelsSelectRoot', () => { expect(wrapper.find(DropdownContents).exists()).toBe(true); }); }); + + describe('sets content direction based on viewport', () => { + it('does not set direction when `state.variant` is not "embedded"', () => { + wrapper.vm.$store.dispatch('toggleDropdownContents'); + + wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false); + }); + }); + + describe('when `state.variant` is "embedded"', () => { + beforeEach(() => { + wrapper = createComponent({ ...mockConfig, variant: 'embedded' }); + wrapper.vm.$store.dispatch('toggleDropdownContents'); + }); + + it('set direction when out of viewport', () => { + isInViewport.mockImplementation(() => false); + wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true); + }); + }); + + it('does not set direction when inside of viewport', () => { + isInViewport.mockImplementation(() => true); + wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false); + }); + }); + }); + }); }); }); diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb index 6601537a2d3..1448b045b18 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb index 2bed47f0a87..ab223ae41fa 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index efcea0b0e09..0da04d8dcf7 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb index a81e1713ef0..3cde4c5d9dc 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb index f44fe19f86d..9bff2355d58 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb index 7fe445975eb..c7d89c4e1e9 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do describe '.type' do it 'is an operator' do - expect(described_class.type).to eq :operator + expect(described_class.type).to eq :logical_operator end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index 61c6ced4dac..6e242faa885 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -81,6 +81,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexer do with_them do it { is_expected.to eq(tokens) } end + + context 'with parentheses are used' do + where(:expression, :tokens) do + '($PRESENT_VARIABLE =~ /my var/) && $EMPTY_VARIABLE =~ /nope/' | ['(', '$PRESENT_VARIABLE', '=~', '/my var/', ')', '&&', '$EMPTY_VARIABLE', '=~', '/nope/'] + '$PRESENT_VARIABLE =~ /my var/ || ($EMPTY_VARIABLE =~ /nope/)' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '(', '$EMPTY_VARIABLE', '=~', '/nope/', ')'] + '($PRESENT_VARIABLE && (null || $EMPTY_VARIABLE == ""))' | ['(', '$PRESENT_VARIABLE', '&&', '(', 'null', '||', '$EMPTY_VARIABLE', '==', '""', ')', ')'] + end + + with_them do + context 'when ci_if_parenthesis_enabled is enabled' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: true) + end + + it { is_expected.to eq(tokens) } + end + + context 'when ci_if_parenthesis_enabled is disabled' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: false) + end + + it do + expect { subject } + .to raise_error described_class::SyntaxError + end + end + end + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb index 1704cabfd2e..3394a75ac0a 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb @@ -1,51 +1,79 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do + before do + stub_feature_flags(ci_if_parenthesis_enabled: true) + end + describe '#tree' do - context 'when using two operators' do - it 'returns a reverse descent parse tree' do - expect(described_class.seed('$VAR1 == "123"').tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals + context 'validates simple operators' do + using RSpec::Parameterized::TableSyntax + + where(:expression, :result_tree) do + '$VAR1 == "123"' | 'equals($VAR1, "123")' + '$VAR1 == "123" == $VAR2' | 'equals(equals($VAR1, "123"), $VAR2)' + '$VAR' | '$VAR' + '"some value"' | '"some value"' + 'null' | 'null' + '$VAR1 || $VAR2 && $VAR3' | 'or($VAR1, and($VAR2, $VAR3))' + '$VAR1 && $VAR2 || $VAR3' | 'or(and($VAR1, $VAR2), $VAR3)' + '$VAR1 && $VAR2 || $VAR3 && $VAR4' | 'or(and($VAR1, $VAR2), and($VAR3, $VAR4))' + '$VAR1 && ($VAR2 || $VAR3) && $VAR4' | 'and(and($VAR1, or($VAR2, $VAR3)), $VAR4)' + end + + with_them do + it { expect(described_class.seed(expression).tree.inspect).to eq(result_tree) } end end - context 'when using three operators' do - it 'returns a reverse descent parse tree' do - expect(described_class.seed('$VAR1 == "123" == $VAR2').tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals + context 'when combining && and OR operators' do + subject { described_class.seed('$VAR1 == "a" || $VAR2 == "b" && $VAR3 == "c" || $VAR4 == "d" && $VAR5 == "e"').tree } + + context 'when parenthesis engine is enabled' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: true) + end + + it 'returns operations in a correct order' do + expect(subject.inspect) + .to eq('or(or(equals($VAR1, "a"), and(equals($VAR2, "b"), equals($VAR3, "c"))), and(equals($VAR4, "d"), equals($VAR5, "e")))') + end + end + + context 'when parenthesis engine is disabled (legacy)' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: false) + end + + it 'returns operations in a invalid order' do + expect(subject.inspect) + .to eq('or(equals($VAR1, "a"), and(equals($VAR2, "b"), or(equals($VAR3, "c"), and(equals($VAR4, "d"), equals($VAR5, "e")))))') + end end end - context 'when using a single variable token' do - it 'returns a single token instance' do - expect(described_class.seed('$VAR').tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable - end - end + context 'when using parenthesis' do + subject { described_class.seed('(($VAR1 == "a" || $VAR2 == "b") && $VAR3 == "c" || $VAR4 == "d") && $VAR5 == "e"').tree } - context 'when using a single string token' do - it 'returns a single token instance' do - expect(described_class.seed('"some value"').tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String + before do + stub_feature_flags(ci_if_parenthesis_enabled: true) + end + + it 'returns operations in a correct order' do + expect(subject.inspect) + .to eq('and(or(and(or(equals($VAR1, "a"), equals($VAR2, "b")), equals($VAR3, "c")), equals($VAR4, "d")), equals($VAR5, "e"))') end end context 'when expression is empty' do - it 'returns a null token' do + it 'raises a parsing error' do expect { described_class.seed('').tree } .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError end end - context 'when expression is null' do - it 'returns a null token' do - expect(described_class.seed('null').tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null - end - end - context 'when two value tokens have no operator' do it 'raises a parsing error' do expect { described_class.seed('$VAR "text"').tree } @@ -66,5 +94,42 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError end end + + context 'when parenthesis are unmatched' do + context 'when parenthesis engine is enabled' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: true) + end + + where(:expression) do + [ + '$VAR == (', + '$VAR2 == ("aa"', + '$VAR2 == ("aa"))', + '$VAR2 == "aa")', + '(($VAR2 == "aa")', + '($VAR2 == "aa"))' + ] + end + + with_them do + it 'raises a ParseError' do + expect { described_class.seed(expression).tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError + end + end + end + + context 'when parenthesis engine is disabled' do + before do + stub_feature_flags(ci_if_parenthesis_enabled: false) + end + + it 'raises an SyntaxError' do + expect { described_class.seed('$VAR == (').tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError + end + end + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 642d6816030..cf3644c9ad5 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'rspec-parameterized' +require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do subject do @@ -109,6 +108,17 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do '$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable' '$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable' + + '($PRESENT_VARIABLE)' | 'my variable' + '(($PRESENT_VARIABLE))' | 'my variable' + '(($PRESENT_VARIABLE && null) || $EMPTY_VARIABLE == "")' | true + '($PRESENT_VARIABLE) && (null || $EMPTY_VARIABLE == "")' | true + '("string" || "test") == "string"' | true + '(null || ("test" == "string"))' | false + '("string" == ("test" && "string"))' | true + '("string" == ("test" || "string"))' | false + '("string" == "test" || "string")' | "string" + '("string" == ("string" || (("1" == "1") && ("2" == "3"))))' | true end with_them do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 4694422c1b7..f71c6e61c08 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -353,7 +353,6 @@ project: - youtrack_service - custom_issue_tracker_service - bugzilla_service -- gitlab_issue_tracker_service - external_wiki_service - mock_ci_service - mock_deployment_service diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb index 1ab6973e279..97d5e2b280d 100644 --- a/spec/lib/gitlab/view/presenter/base_spec.rb +++ b/spec/lib/gitlab/view/presenter/base_spec.rb @@ -57,4 +57,32 @@ RSpec.describe Gitlab::View::Presenter::Base do expect(presenter.present).to eq(presenter) end end + + describe '#url_builder' do + it 'returns the UrlBuilder instance' do + presenter = presenter_class.new(project) + + expect(presenter.url_builder).to eq(Gitlab::UrlBuilder.instance) + end + end + + describe '#web_url' do + it 'delegates to the UrlBuilder' do + presenter = presenter_class.new(project) + + expect(presenter.url_builder).to receive(:build).with(project) + + presenter.web_url + end + end + + describe '#web_path' do + it 'delegates to the UrlBuilder' do + presenter = presenter_class.new(project) + + expect(presenter.url_builder).to receive(:build).with(project, only_path: true) + + presenter.web_path + end + end end diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb deleted file mode 100644 index a6b7cb05836..00000000000 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GitlabIssueTrackerService do - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - - describe 'Validations' do - context 'when service is active' do - subject { described_class.new(project: create(:project), active: true) } - - it { is_expected.to validate_presence_of(:issues_url) } - it_behaves_like 'issue tracker service URL attribute', :issues_url - end - - context 'when service is inactive' do - subject { described_class.new(project: create(:project), active: false) } - - it { is_expected.not_to validate_presence_of(:issues_url) } - end - end - - describe 'project and issue urls' do - let(:project) { create(:project) } - let(:service) { project.create_gitlab_issue_tracker_service(active: true) } - - context 'with absolute urls' do - before do - allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root") - end - - it 'gives the correct path' do - expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues") - expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/new") - expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/432") - end - end - - context 'with relative urls' do - before do - allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root") - end - - it 'gives the correct path' do - expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.full_path}/-/issues") - expect(service.new_issue_path).to eq("/gitlab/root/#{project.full_path}/-/issues/new") - expect(service.issue_path(432)).to eq("/gitlab/root/#{project.full_path}/-/issues/432") - end - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a8220dd4f93..d2aa9340a8d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -61,7 +61,6 @@ RSpec.describe Project do it { is_expected.to have_one(:youtrack_service) } it { is_expected.to have_one(:custom_issue_tracker_service) } it { is_expected.to have_one(:bugzilla_service) } - it { is_expected.to have_one(:gitlab_issue_tracker_service) } it { is_expected.to have_one(:external_wiki_service) } it { is_expected.to have_one(:confluence_service) } it { is_expected.to have_one(:project_feature) } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 75bbb074526..19be46ba375 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -535,7 +535,7 @@ RSpec.describe Service do describe 'initialize service with no properties' do let(:service) do - GitlabIssueTrackerService.create( + BugzillaService.create( project: create(:project), project_url: 'http://gitlab.example.com' ) diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index c4c9fc779fa..41f51e80a02 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Projects::UpdatePagesConfigurationService do it 'updates the .update file' do expect(service).to receive(:reload_daemon).and_call_original - expect(subject).to include(status: :success, reload: true) + expect(subject).to include(status: :success) end end @@ -37,7 +37,7 @@ RSpec.describe Projects::UpdatePagesConfigurationService do it 'does not update the .update file' do expect(service).not_to receive(:reload_daemon) - expect(subject).to include(status: :success, reload: false) + expect(subject).to include(status: :success) end end end