From 9072983091b38d3b9a94e8545a4d599c192a1037 Mon Sep 17 00:00:00 2001 From: Mohammed Bilal Date: Thu, 1 Sep 2022 14:37:04 +0000 Subject: [PATCH 1/2] New upstream version 15.3.2+ds1 --- .rubocop_todo/gitlab/namespaced_class.yml | 1 + CHANGELOG.md | 22 + GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 +- VERSION | 2 +- .../ide/components/preview/clientside.vue | 22 +- .../ide/components/preview/navigator.vue | 6 +- .../notebook/cells/output/html.vue | 9 +- app/controllers/jwt_controller.rb | 45 +- .../git_http_client_controller.rb | 23 +- .../resolvers/paginated_tree_resolver.rb | 6 +- .../timeline_event_type.rb | 7 +- app/helpers/commits_helper.rb | 2 +- app/helpers/labels_helper.rb | 2 +- app/models/integrations/zentao.rb | 4 + app/models/issue.rb | 8 +- app/models/repository.rb | 10 +- app/models/snippet.rb | 15 +- app/models/tree.rb | 4 +- app/presenters/commit_presenter.rb | 10 +- app/validators/bytesize_validator.rb | 30 + app/views/projects/commits/_commit.html.haml | 2 +- .../initializers/rack_VULNDB-255039_patch.rb | 35 ++ config/initializers/sawyer_patch.rb | 44 ++ doc/topics/git/troubleshooting_git.md | 5 + doc/user/packages/dependency_proxy/index.md | 4 + doc/user/packages/pypi_repository/index.md | 5 + .../account/two_factor_authentication.md | 33 + lib/api/commits.rb | 4 +- lib/api/entities/commit.rb | 4 +- lib/api/entities/commit_detail.rb | 6 +- .../helpers/packages/basic_auth_helpers.rb | 18 +- lib/api/pypi_packages.rb | 20 +- lib/api/repositories.rb | 2 +- lib/api/search.rb | 6 +- lib/api/submodules.rb | 2 +- lib/banzai/filter/commit_trailers_filter.rb | 34 +- lib/banzai/filter/image_link_filter.rb | 13 +- .../filter/pathological_markdown_filter.rb | 27 + .../pipeline/plain_markdown_pipeline.rb | 1 + lib/gitlab/git/rugged_impl/tree.rb | 9 +- lib/gitlab/git/tree.rb | 9 +- lib/gitlab/gitaly_client/commit_service.rb | 8 +- lib/gitlab/markdown_cache.rb | 2 +- lib/gitlab/set_cache.rb | 4 + lib/gitlab/zentao/client.rb | 50 +- locale/gitlab.pot | 5 +- package.json | 2 +- .../pypi/pypi_upload_install_package.yaml.erb | 2 +- .../package_registry/pypi_repository_spec.rb | 11 +- .../emails/valid_reply_signed_smime.eml | 588 +++++++++--------- .../ide/components/preview/clientside_spec.js | 36 +- .../ide/components/preview/navigator_spec.js | 20 +- .../cells/output/html_sanitize_fixtures.js | 4 +- .../notebook/cells/output/index_spec.js | 14 +- spec/helpers/commits_helper_spec.rb | 2 +- spec/helpers/labels_helper_spec.rb | 8 + .../rack_VULNDB-255039_patch_spec.rb | 17 + spec/initializers/sawyer_patch_spec.rb | 69 ++ .../filter/commit_trailers_filter_spec.rb | 25 +- .../banzai/filter/image_link_filter_spec.rb | 45 ++ .../pathological_markdown_filter_spec.rb | 27 + .../lib/banzai/pipeline/full_pipeline_spec.rb | 12 + spec/lib/gitlab/git/tree_spec.rb | 19 +- .../gitaly_client/commit_service_spec.rb | 11 +- .../gitlab/reactive_cache_set_cache_spec.rb | 14 + spec/lib/gitlab/zentao/client_spec.rb | 127 +++- spec/models/integrations/zentao_spec.rb | 20 + spec/models/issue_spec.rb | 14 +- spec/models/repository_spec.rb | 2 +- spec/models/snippet_spec.rb | 39 ++ spec/presenters/commit_presenter_spec.rb | 56 +- .../timeline_events_spec.rb | 10 +- spec/requests/api/search_spec.rb | 90 +++ spec/requests/git_http_spec.rb | 41 +- spec/requests/jwt_controller_spec.rb | 41 +- spec/support/helpers/login_helpers.rb | 2 + .../api/pypi_packages_shared_examples.rb | 51 +- spec/validators/bytesize_validator_spec.rb | 36 ++ .../commits/_commit.html.haml_spec.rb | 37 +- yarn.lock | 51 +- 82 files changed, 1496 insertions(+), 635 deletions(-) create mode 100644 app/validators/bytesize_validator.rb create mode 100644 config/initializers/rack_VULNDB-255039_patch.rb create mode 100644 config/initializers/sawyer_patch.rb create mode 100644 lib/banzai/filter/pathological_markdown_filter.rb create mode 100644 spec/initializers/rack_VULNDB-255039_patch_spec.rb create mode 100644 spec/initializers/sawyer_patch_spec.rb create mode 100644 spec/lib/banzai/filter/pathological_markdown_filter_spec.rb create mode 100644 spec/validators/bytesize_validator_spec.rb diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml index ef87efb666..b79402ce5b 100644 --- a/.rubocop_todo/gitlab/namespaced_class.yml +++ b/.rubocop_todo/gitlab/namespaced_class.yml @@ -726,6 +726,7 @@ Gitlab/NamespacedClass: - 'app/validators/top_level_group_validator.rb' - 'app/validators/untrusted_regexp_validator.rb' - 'app/validators/x509_certificate_credentials_validator.rb' + - 'app/validators/bytesize_validator.rb' - 'app/workers/admin_email_worker.rb' - 'app/workers/approve_blocked_pending_approval_users_worker.rb' - 'app/workers/archive_trace_worker.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index b129961cd7..a9b2d11964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 15.3.2 (2022-08-30) + +### Security (17 changes) + +- [No overriding methods for Sawyer class](gitlab-org/security/gitlab@397aa9e269676f4ab3dfba4c3ba8fef131b5b4bd) ([merge request](gitlab-org/security/gitlab!2754)) +- [Update Oj to v3.13.21](gitlab-org/security/gitlab@15f86c00b579ad1b4aeedd395f9239e8229c6f8b) ([merge request](gitlab-org/security/gitlab!2730)) +- [Prevent long loops when generating suggested branch name](gitlab-org/security/gitlab@1479c9e2a0444794ea274b07e0f59e8a50ced6ee) ([merge request](gitlab-org/security/gitlab!2743)) +- [IDOR in Zentao integration issue show page](gitlab-org/security/gitlab@92fdf89045bf294d4ee0338ba3f26c91094a073e) ([merge request](gitlab-org/security/gitlab!2740)) +- [Patch VULNDB-255039 (potential Rack cache poisoning)](gitlab-org/security/gitlab@383c926cc8aa4e2c4273556a181e1ddc1b71049f) ([merge request](gitlab-org/security/gitlab!2697)) +- [HTML escape the label background color](gitlab-org/security/gitlab@1e43656560fbc13907af72d5d4f696df95d7f49c) ([merge request](gitlab-org/security/gitlab!2719)) +- [Sandbox jupyter notebook HTML output](gitlab-org/security/gitlab@3ade5f2fadbb0c15d9e5a14306d0a79136a8f23e) ([merge request](gitlab-org/security/gitlab!2710)) +- [Fix unauthorized GFM references in Incident Timeline](gitlab-org/security/gitlab@2e18b59472b5a43921d39433e60038b0f254d123) ([merge request](gitlab-org/security/gitlab!2707)) +- [Optimize handling repositories with huge trees](gitlab-org/security/gitlab@4bfaca71c8d8f663242138049cf5639e69326bbb) ([merge request](gitlab-org/security/gitlab!2706)) +- [Parse commit trailers without using regexp](gitlab-org/security/gitlab@c15b2cd9b5e572a9bbc7c0c5cb7c9511f1a04ead) ([merge request](gitlab-org/security/gitlab!2699)) +- [Check for pathological markdown input](gitlab-org/security/gitlab@2fd5e1133e1acd82cdb524f059b554976cd68f51) ([merge request](gitlab-org/security/gitlab!2733)) +- [Replaced smooshpack to fix the vulnerability in LivePreview](gitlab-org/security/gitlab@114637f8f0d9add00914ac3e4562419b0f1b4f63) ([merge request](gitlab-org/security/gitlab!2739)) +- [Update package auth for group IP allowlist](gitlab-org/security/gitlab@7e830349a8425dbab65ce92d3e8ebd0afa734381) ([merge request](gitlab-org/security/gitlab!2686)) +- [Don't show pipeline status](gitlab-org/security/gitlab@1b5fbb9bcb4dde12a2af075e45407cbc6109494d) ([merge request](gitlab-org/security/gitlab!2712)) +- [Sanitize img attributes in Banzai::Filter::ImageLinkFilter](gitlab-org/security/gitlab@22ece3568d6b3aed305ed97aab9fdbb22ca068e8) ([merge request](gitlab-org/security/gitlab!2722)) +- [Validate description length for snippets](gitlab-org/security/gitlab@24592d39d7b8956a0e712026e5b988a82d37e771) ([merge request](gitlab-org/security/gitlab!2702)) +- [Prevent brute force vuln for Git over HTTP(S) requests](gitlab-org/security/gitlab@fcff307eff525d15e835e65e0e3e3a2395f0b840) ([merge request](gitlab-org/security/gitlab!2716)) + ## 15.3.1 (2022-08-22) ### Security (1 change) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 2471c64e3c..7bb26bde92 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -15.3.1 \ No newline at end of file +15.3.2 \ No newline at end of file diff --git a/Gemfile b/Gemfile index e85488a34f..c8eca55525 100644 --- a/Gemfile +++ b/Gemfile @@ -533,7 +533,7 @@ gem 'valid_email', '~> 0.1' # JSON gem 'json', '~> 2.5.1' gem 'json_schemer', '~> 0.2.18' -gem 'oj', '~> 3.13.20' +gem 'oj', '~> 3.13.21' gem 'multi_json', '~> 1.14.1' gem 'yajl-ruby', '~> 1.4.3', require: 'yajl' diff --git a/Gemfile.lock b/Gemfile.lock index f04445e1d5..218a66d8f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -887,7 +887,7 @@ GEM plist (~> 3.1) train-core wmi-lite (~> 1.0) - oj (3.13.20) + oj (3.13.21) omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) @@ -1651,7 +1651,7 @@ DEPENDENCIES oauth2 (~> 2.0) octokit (~> 4.15) ohai (~> 16.10) - oj (~> 3.13.20) + oj (~> 3.13.21) omniauth (~> 1.8) omniauth-alicloud (~> 1.0.1) omniauth-atlassian-oauth2 (~> 0.2.0) diff --git a/VERSION b/VERSION index 2471c64e3c..7bb26bde92 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -15.3.1 \ No newline at end of file +15.3.2 \ No newline at end of file diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue index b1f6f2c87b..70b881b6ff 100644 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -2,7 +2,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { listen } from 'codesandbox-api'; import { isEmpty, debounce } from 'lodash'; -import { Manager } from 'smooshpack'; +import { SandpackClient } from '@codesandbox/sandpack-client'; import { mapActions, mapGetters, mapState } from 'vuex'; import { packageJsonPath, @@ -21,7 +21,7 @@ export default { }, data() { return { - manager: {}, + client: {}, loading: false, sandpackReady: false, }; @@ -94,11 +94,11 @@ export default { this.sandpackReady = false; eventHub.$off('ide.files.change', this.onFilesChangeCallback); - if (!isEmpty(this.manager)) { - this.manager.listener(); + if (!isEmpty(this.client)) { + this.client.cleanup(); } - this.manager = {}; + this.client = {}; if (this.listener) { this.listener(); @@ -120,7 +120,7 @@ export default { return this.loadFileContent(this.mainEntry) .then(() => this.$nextTick()) .then(() => { - this.initManager(); + this.initClient(); this.listener = listen((e) => { switch (e.type) { @@ -136,15 +136,15 @@ export default { update() { if (!this.sandpackReady) return; - if (isEmpty(this.manager)) { + if (isEmpty(this.client)) { this.initPreview(); return; } - this.manager.updatePreview(this.sandboxOpts); + this.client.updatePreview(this.sandboxOpts); }, - initManager() { + initClient() { const { codesandboxBundlerUrl: bundlerURL } = this; const settings = { @@ -155,7 +155,7 @@ export default { ...(bundlerURL ? { bundlerURL } : {}), }; - this.manager = new Manager('#ide-preview', this.sandboxOpts, settings); + this.client = new SandpackClient('#ide-preview', this.sandboxOpts, settings); }, }, }; @@ -164,7 +164,7 @@ export default { diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 8eebf9fbf6..84f5632854 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -36,31 +36,40 @@ class JwtController < ApplicationController @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) if @authentication_result.failed? - render_unauthorized + log_authentication_failed(login, @authentication_result) + render_access_denied end end rescue Gitlab::Auth::MissingPersonalAccessTokenError - render_missing_personal_access_token + render_access_denied end - def render_missing_personal_access_token - render json: { - errors: [ - { code: 'UNAUTHORIZED', - message: _('HTTP Basic: Access denied\n' \ - 'You must use a personal access token with \'api\' scope for Git over HTTP.\n' \ - 'You can generate one at %{profile_personal_access_tokens_url}') % { profile_personal_access_tokens_url: profile_personal_access_tokens_url } } - ] - }, status: :unauthorized + def log_authentication_failed(login, result) + log_info = { + message: 'JWT authentication failed', + http_user: login, + remote_ip: request.ip, + auth_service: params[:service], + 'auth_result.type': result.type, + 'auth_result.actor_type': result.actor&.class + }.merge(::Gitlab::ApplicationContext.current) + + Gitlab::AuthLogger.warn(log_info) end - def render_unauthorized - render json: { - errors: [ - { code: 'UNAUTHORIZED', - message: 'HTTP Basic: Access denied' } - ] - }, status: :unauthorized + def render_access_denied + help_page = help_page_url( + 'user/profile/account/two_factor_authentication', + anchor: 'troubleshooting' + ) + + render( + json: { errors: [{ + code: 'UNAUTHORIZED', + message: format(_("HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password. See %{help_page_url}"), help_page_url: help_page) + }] }, + status: :unauthorized + ) end def auth_params diff --git a/app/controllers/repositories/git_http_client_controller.rb b/app/controllers/repositories/git_http_client_controller.rb index 8d7ba3e38c..fbf5d82a45 100644 --- a/app/controllers/repositories/git_http_client_controller.rb +++ b/app/controllers/repositories/git_http_client_controller.rb @@ -67,9 +67,21 @@ module Repositories end send_challenges - render plain: "HTTP Basic: Access denied\n", status: :unauthorized + render_access_denied rescue Gitlab::Auth::MissingPersonalAccessTokenError - render_missing_personal_access_token + render_access_denied + end + + def render_access_denied + help_page = help_page_url( + 'topics/git/troubleshooting_git', + anchor: 'error-on-git-fetch-http-basic-access-denied' + ) + + render( + plain: format(_("HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password. See %{help_page_url}"), help_page_url: help_page), + status: :unauthorized + ) end def basic_auth_provided? @@ -103,13 +115,6 @@ module Repositories @container, @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(repository_path) end - def render_missing_personal_access_token - render plain: "HTTP Basic: Access denied\n" \ - "You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.\n" \ - "You can generate one at #{profile_personal_access_tokens_url}", - status: :unauthorized - end - def repository strong_memoize(:repository) do repo_type.repository_for(container) diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb index 1b4211366e..c7e9e522c2 100644 --- a/app/graphql/resolvers/paginated_tree_resolver.rb +++ b/app/graphql/resolvers/paginated_tree_resolver.rb @@ -32,7 +32,11 @@ module Resolvers page_token: cursor } - tree = repository.tree(args[:ref], args[:path], recursive: args[:recursive], pagination_params: pagination_params) + tree = repository.tree( + args[:ref], args[:path], recursive: args[:recursive], + skip_flat_paths: false, + pagination_params: pagination_params + ) next_cursor = tree.cursor&.next_cursor Gitlab::Graphql::ExternallyPaginatedArray.new(cursor, next_cursor, *tree) diff --git a/app/graphql/types/incident_management/timeline_event_type.rb b/app/graphql/types/incident_management/timeline_event_type.rb index a6d3f57404..690facc873 100644 --- a/app/graphql/types/incident_management/timeline_event_type.rb +++ b/app/graphql/types/incident_management/timeline_event_type.rb @@ -33,11 +33,6 @@ module Types null: true, description: 'Text note of the timeline event.' - field :note_html, - GraphQL::Types::String, - null: true, - description: 'HTML note of the timeline event.' - field :promoted_from_note, Types::Notes::NoteType, null: true, @@ -67,6 +62,8 @@ module Types Types::TimeType, null: false, description: 'Timestamp when the event updated.' + + markdown_field :note_html, null: true, description: 'HTML note of the timeline event.' end end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 1920650bc9..4493bc2bc6 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -171,7 +171,7 @@ module CommitsHelper ref, { merge_request: merge_request&.cache_key, - pipeline_status: commit.status_for(ref)&.cache_key, + pipeline_status: commit.detailed_status_for(ref)&.cache_key, xhr: request.xhr?, controller: controller.controller_path, path: @path # referred to in #link_to_browse_code diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 2d0bc1bc63..e865db128c 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -247,7 +247,7 @@ module LabelsHelper class="#{css_class}" data-container="body" data-html="true" - #{"style=\"background-color: #{bg_color}\"" if bg_color} + #{"style=\"background-color: #{h bg_color}\"" if bg_color} >#{ERB::Util.html_escape_once(name)}#{suffix} HTML end diff --git a/app/models/integrations/zentao.rb b/app/models/integrations/zentao.rb index 5319408929..459756c865 100644 --- a/app/models/integrations/zentao.rb +++ b/app/models/integrations/zentao.rb @@ -69,6 +69,10 @@ module Integrations } end + def client_url + api_url.presence || url + end + def self.to_param name.demodulize.downcase end diff --git a/app/models/issue.rb b/app/models/issue.rb index 4114467eb2..df8ee34b3c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -458,7 +458,13 @@ class Issue < ApplicationRecord return to_branch_name unless project.repository.branch_exists?(to_branch_name) start_counting_from = 2 - Uniquify.new(start_counting_from).string(-> (counter) { "#{to_branch_name}-#{counter}" }) do |suggested_branch_name| + + branch_name_generator = -> (counter) do + suffix = counter > 5 ? SecureRandom.hex(8) : counter + "#{to_branch_name}-#{suffix}" + end + + Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name| project.repository.branch_exists?(suggested_branch_name) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index eb8e45877f..26c3b01a46 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -677,24 +677,24 @@ class Repository @head_commit ||= commit(self.root_ref) end - def head_tree + def head_tree(skip_flat_paths: true) if head_commit - @head_tree ||= Tree.new(self, head_commit.sha, nil) + @head_tree ||= Tree.new(self, head_commit.sha, nil, skip_flat_paths: skip_flat_paths) end end - def tree(sha = :head, path = nil, recursive: false, pagination_params: nil) + def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil) if sha == :head return unless head_commit if path.nil? - return head_tree + return head_tree(skip_flat_paths: skip_flat_paths) else sha = head_commit.sha end end - Tree.new(self, sha, path, recursive: recursive, pagination_params: pagination_params) + Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params) end def blob_at_branch(branch_name, path) diff --git a/app/models/snippet.rb b/app/models/snippet.rb index fd882633a4..943d09d983 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -22,6 +22,8 @@ class Snippet < ApplicationRecord MAX_FILE_COUNT = 10 + DESCRIPTION_LENGTH_MAX = 1.megabyte + cache_markdown_field :title, pipeline: :single_line cache_markdown_field :description cache_markdown_field :content @@ -57,19 +59,10 @@ class Snippet < ApplicationRecord validates :title, presence: true, length: { maximum: 255 } validates :file_name, length: { maximum: 255 } + validates :description, bytesize: { maximum: -> { DESCRIPTION_LENGTH_MAX } }, if: :description_changed? validates :content, presence: true - validates :content, - length: { - maximum: ->(_) { Gitlab::CurrentSettings.snippet_size_limit }, - message: -> (_, data) do - current_value = ActiveSupport::NumberHelper.number_to_human_size(data[:value].size) - max_size = ActiveSupport::NumberHelper.number_to_human_size(Gitlab::CurrentSettings.snippet_size_limit) - - _("is too long (%{current_value}). The maximum size is %{max_size}.") % { current_value: current_value, max_size: max_size } - end - }, - if: :content_changed? + validates :content, bytesize: { maximum: -> { Gitlab::CurrentSettings.snippet_size_limit } }, if: :content_changed? after_create :create_statistics diff --git a/app/models/tree.rb b/app/models/tree.rb index fd416ebded..941d0394b9 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -6,7 +6,7 @@ class Tree attr_accessor :repository, :sha, :path, :entries, :cursor - def initialize(repository, sha, path = '/', recursive: false, pagination_params: nil) + def initialize(repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil) path = '/' if path.blank? @repository = repository @@ -14,7 +14,7 @@ class Tree @path = path git_repo = @repository.raw_repository - @entries, @cursor = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive, pagination_params) + @entries, @cursor = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive, skip_flat_paths, pagination_params) end def readme_path diff --git a/app/presenters/commit_presenter.rb b/app/presenters/commit_presenter.rb index 7df45ca03b..2cb8817984 100644 --- a/app/presenters/commit_presenter.rb +++ b/app/presenters/commit_presenter.rb @@ -5,12 +5,20 @@ class CommitPresenter < Gitlab::View::Presenter::Delegated presents ::Commit, as: :commit - def status_for(ref) + def detailed_status_for(ref) + return unless can?(current_user, :read_pipeline, commit.latest_pipeline(ref)) return unless can?(current_user, :read_commit_status, commit.project) commit.latest_pipeline(ref)&.detailed_status(current_user) end + def status_for(ref = nil) + return unless can?(current_user, :read_pipeline, commit.latest_pipeline(ref)) + return unless can?(current_user, :read_commit_status, commit.project) + + commit.status(ref) + end + def any_pipelines? return false unless can?(current_user, :read_pipeline, commit.project) diff --git a/app/validators/bytesize_validator.rb b/app/validators/bytesize_validator.rb new file mode 100644 index 0000000000..adbdd81d5c --- /dev/null +++ b/app/validators/bytesize_validator.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# BytesizeValidator +# +# Custom validator for verifying that bytesize of a field doesn't exceed the specified limit. +# It is different from Rails length validator because it takes .bytesize into account instead of .size/.length +# +# Example: +# +# class Snippet < ActiveRecord::Base +# validates :content, bytesize: { maximum: -> { Gitlab::CurrentSettings.snippet_size_limit } } +# end +# +# Configuration options: +# * maximum - Proc that evaluates the bytesize limit that cannot be exceeded +class BytesizeValidator < ActiveModel::EachValidator + def validate_each(record, attr, value) + size = value.to_s.bytesize + max_size = options[:maximum].call + + return if size <= max_size + + error_message = format(_('is too long (%{size}). The maximum size is %{max_size}.'), { + size: ActiveSupport::NumberHelper.number_to_human_size(size), + max_size: ActiveSupport::NumberHelper.number_to_human_size(max_size) + }) + + record.errors.add(attr, error_message) + end +end diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 71485e203d..6f44c13060 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -14,7 +14,7 @@ - project = local_assigns.fetch(:project) { merge_request&.project } - ref = local_assigns.fetch(:ref) { merge_request&.source_branch } - commit = commit.present(current_user: current_user) -- commit_status = commit.status_for(ref) +- commit_status = commit.detailed_status_for(ref) - collapsible = local_assigns.fetch(:collapsible, true) - link_data_attrs = local_assigns.fetch(:link_data_attrs, {}) - link = commit_path(project, commit, merge_request: merge_request) diff --git a/config/initializers/rack_VULNDB-255039_patch.rb b/config/initializers/rack_VULNDB-255039_patch.rb new file mode 100644 index 0000000000..b613ed9bdb --- /dev/null +++ b/config/initializers/rack_VULNDB-255039_patch.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +if Gem.loaded_specs['rack'].version >= Gem::Version.new("3.0.0") + raise <<~ERR + This patch is unnecessary in Rack versions 3.0.0 or newer. + Please remove this file and the associated spec. + + See https://github.com/rack/rack/blob/main/CHANGELOG.md#security (issue #1733) + ERR +end + +# Patches a cache poisoning attack vector in Rack by not allowing semicolons +# to delimit query parameters. +# See https://github.com/rack/rack/issues/1732. +# +# Solution is taken from the same issue. +# +# The actual patch is due for release in Rack 3.0.0. +module Rack + class Request + Helpers.module_eval do + # rubocop: disable Naming/MethodName + def GET + if get_header(RACK_REQUEST_QUERY_STRING) == query_string + get_header(RACK_REQUEST_QUERY_HASH) + else + query_hash = parse_query(query_string, '&') # only allow ampersand here + set_header(RACK_REQUEST_QUERY_STRING, query_string) + set_header(RACK_REQUEST_QUERY_HASH, query_hash) + end + end + # rubocop: enable Naming/MethodName + end + end +end diff --git a/config/initializers/sawyer_patch.rb b/config/initializers/sawyer_patch.rb new file mode 100644 index 0000000000..08d249645c --- /dev/null +++ b/config/initializers/sawyer_patch.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +# +# This patch updates SawyerResource class to not allow Ruby methods to be overridden and accessed. +# Any attempt to access a Ruby method will result in an exception. +module SawyerClassPatch + def attr_accessor(*attrs) + attrs.each do |attribute| + class_eval do + # rubocop:disable Gitlab/ModuleWithInstanceVariables + if method_defined?(attribute) || method_defined?("#{attribute}=") || method_defined?("#{attribute}?") + define_method attribute do + raise Sawyer::Error, + "Sawyer method \"#{attribute}\" overlaps Ruby method. Convert to a hash to access the attribute." + end + + define_method "#{attribute}=" do |value| + raise Sawyer::Error, + "Sawyer method \"#{attribute}\" overlaps Ruby method. Convert to a hash to access the attribute." + end + + define_method "#{attribute}?" do + raise Sawyer::Error, + "Sawyer method \"#{attribute}\" overlaps Ruby method. Convert to a hash to access the attribute." + end + else + define_method attribute do + @attrs[attribute.to_sym] + end + + define_method "#{attribute}=" do |value| + @attrs[attribute.to_sym] = value + end + + define_method "#{attribute}?" do + !!@attrs[attribute.to_sym] + end + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + end + end +end + +Sawyer::Resource.singleton_class.prepend(SawyerClassPatch) diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md index 36c26a0206..484f3a100b 100644 --- a/doc/topics/git/troubleshooting_git.md +++ b/doc/topics/git/troubleshooting_git.md @@ -267,3 +267,8 @@ To resolve this issue, you can update the password expiration by either: ``` The bug was reported [in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/332455). + +## Error on Git fetch: "HTTP Basic: Access Denied" + +If you receive an `HTTP Basic: Access denied` error when using Git over HTTP(S), +refer to the [two-factor authentication troubleshooting guide](../../user/profile/account/two_factor_authentication.md#troubleshooting). diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index ea9435de12..b570bba73e 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -299,6 +299,10 @@ hub_docker_quota_check: ## Troubleshooting +## Authentication error: "HTTP Basic: Access Denied" + +If you receive an `HTTP Basic: Access denied` error when authenticating against the Dependency Proxy, refer to the [two-factor authentication troubleshooting guide](../../profile/account/two_factor_authentication.md#troubleshooting). + ### Dependency Proxy Connection Failure If a service alias is not set the `docker:20.10.16` image is unable to find the diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md index b8996dc296..ba9ecbe50a 100644 --- a/doc/user/packages/pypi_repository/index.md +++ b/doc/user/packages/pypi_repository/index.md @@ -345,6 +345,11 @@ when a PyPI package is not found in the Package Registry, the request is forward Administrators can disable this behavior in the [Continuous Integration settings](../../admin_area/settings/continuous_integration.md). +WARNING: +When you use the `--index-url` option, do not specify the port if it is a default +port, such as `80` for a URL starting with `http` or `443` for a URL starting +with `https`. + ### Install from the project level To install the latest version of a package, use the following command: diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index 3af033c713..0256795835 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -427,6 +427,39 @@ a GitLab global administrator disable 2FA for your account: ## Troubleshooting +### Error: "HTTP Basic: Access denied. The provided password or token ..." + +When making a request, you can receive the following error: + +```plaintext +HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal +access token instead of a password. +``` + +This error occurs in the following scenarios: + +- You have 2FA enabled and have attempted to authenticate with a username and + password. For 2FA-enabled users, a [personal access token](../personal_access_tokens.md) (PAT) + must be used instead of a password. To authenticate: + - Git requests over HTTP(S), a PAT with `read_repository` or `write_repository` scope is required. + - [GitLab Container Registry](../../packages/container_registry/index.md#authenticate-with-the-container-registry) requests, a PAT + with `read_registry` or `write_registry` scope is required. + - [Dependency Proxy](../../packages/dependency_proxy/index.md#authenticate-with-the-dependency-proxy) requests, a PAT with + `read_registry` and `write_registry` scopes is required. +- You do not have 2FA enabled and have sent an incorrect username or password + with your request. +- You do not have 2FA enabled but an administrator has enabled the + [enforce 2FA for all users](../../../security/two_factor_authentication.md#enforce-2fa-for-all-users) setting. +- You do not have 2FA enabled, but an administrator has disabled the + [password authentication enabled for Git over HTTP(S)](../../admin_area/settings/sign_in_restrictions.md#password-authentication-enabled) + setting. If LDAP is: + - Configured, an [LDAP password](../../../administration/auth/ldap/index.md) + or a [personal access token](../personal_access_tokens.md) + must be used to authenticate Git requests over HTTP(S). + - Not configured, you must use a [personal access token](../personal_access_tokens.md). + +### Error: "invalid pin code" + If you receive an `invalid pin code` error, this can indicate that there is a time sync issue between the authentication application and the GitLab instance itself. To avoid the time sync issue, enable time synchronization in the device that generates the codes. For example: diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 7a6c3e4d53..50d0687ba7 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -144,7 +144,7 @@ module API Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user, project: user_project) end - present commit_detail, with: Entities::CommitDetail, stats: params[:stats] + present commit_detail, with: Entities::CommitDetail, include_stats: params[:stats], current_user: current_user else render_api_error!(result[:message], 400) end @@ -163,7 +163,7 @@ module API not_found! 'Commit' unless commit - present commit, with: Entities::CommitDetail, stats: params[:stats], current_user: current_user + present commit, with: Entities::CommitDetail, include_stats: params[:stats], current_user: current_user end desc 'Get the diff for a specific commit of a project' do diff --git a/lib/api/entities/commit.rb b/lib/api/entities/commit.rb index fd23c23b98..6cd180cd58 100644 --- a/lib/api/entities/commit.rb +++ b/lib/api/entities/commit.rb @@ -12,7 +12,9 @@ module API expose :trailers expose :web_url do |commit, _options| - Gitlab::UrlBuilder.build(commit) + c = commit + c = c.__subject__ if c.is_a?(Gitlab::View::Presenter::Base) + Gitlab::UrlBuilder.build(c) end end end diff --git a/lib/api/entities/commit_detail.rb b/lib/api/entities/commit_detail.rb index 61238102e9..cc52963935 100644 --- a/lib/api/entities/commit_detail.rb +++ b/lib/api/entities/commit_detail.rb @@ -3,8 +3,10 @@ module API module Entities class CommitDetail < Commit - expose :stats, using: Entities::CommitStats, if: :stats - expose :status + include ::API::Helpers::Presentable + + expose :stats, using: Entities::CommitStats, if: :include_stats + expose :status_for, as: :status expose :project_id expose :last_pipeline do |commit, options| diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index 6c381d85cd..ebedb3b756 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -14,28 +14,12 @@ module API include Constants include Gitlab::Utils::StrongMemoize - def unauthorized_user_project - @unauthorized_user_project ||= find_project(params[:id]) - end - - def unauthorized_user_project! - unauthorized_user_project || not_found! - end - - def unauthorized_user_group - @unauthorized_user_group ||= find_group(params[:id]) - end - - def unauthorized_user_group! - unauthorized_user_group || not_found! - end - def authorized_user_project @authorized_user_project ||= authorized_project_find! end def authorized_project_find! - project = unauthorized_user_project + project = find_project(params[:id]) unless project && can?(current_user, :read_project, project) return unauthorized_or! { not_found! } diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index ae53f08fb1..f8a7a3c0ec 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -84,6 +84,16 @@ module API body content end + + def ensure_group! + find_group(params[:id]) || not_found! + find_authorized_group! + end + + def ensure_project! + find_project(params[:id]) || not_found! + authorized_user_project + end end params do @@ -91,7 +101,7 @@ module API end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do after_validation do - unauthorized_user_group! + ensure_group! end namespace ':id/-/packages/pypi' do @@ -101,7 +111,8 @@ module API route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'files/:sha256/*file_identifier' do - group = unauthorized_user_group! + group = find_authorized_group! + authorize_read_package!(group) filename = "#{params[:file_identifier]}.#{params[:format]}" package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute @@ -146,7 +157,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do - unauthorized_user_project! + ensure_project! end namespace ':id/packages/pypi' do @@ -160,7 +171,8 @@ module API route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'files/:sha256/*file_identifier' do - project = unauthorized_user_project! + project = authorized_user_project + authorize_read_package!(project) filename = "#{params[:file_identifier]}.#{params[:format]}" package = Packages::Pypi::PackageFinder.new(current_user, project, { filename: filename, sha256: params[:sha256] }).execute diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index cef72d898e..c6a2d582d8 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -189,7 +189,7 @@ module API compare = CompareService.new(user_project, params[:to]).execute(target_project, params[:from], straight: params[:straight]) if compare - present compare, with: Entities::Compare + present compare, with: Entities::Compare, current_user: current_user else not_found!("Ref") end diff --git a/lib/api/search.rb b/lib/api/search.rb index c78aff705a..7aa3cf8a5c 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -123,7 +123,7 @@ module API get do verify_search_scope!(resource: nil) - present search, with: entity + present search, with: entity, current_user: current_user end end @@ -145,7 +145,7 @@ module API get ':id/(-/)search' do verify_search_scope!(resource: user_group) - present search(group_id: user_group.id), with: entity + present search(group_id: user_group.id), with: entity, current_user: current_user end end @@ -166,7 +166,7 @@ module API use :pagination end get ':id/(-/)search' do - present search({ project_id: user_project.id, repository_ref: params[:ref] }), with: entity + present search({ project_id: user_project.id, repository_ref: params[:ref] }), with: entity, current_user: current_user end end end diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb index 5c71a18c6d..2b51ab91c4 100644 --- a/lib/api/submodules.rb +++ b/lib/api/submodules.rb @@ -39,7 +39,7 @@ module API if result[:status] == :success commit_detail = user_project.repository.commit(result[:result]) - present commit_detail, with: Entities::CommitDetail + present commit_detail, with: Entities::CommitDetail, current_user: current_user else render_api_error!(result[:message], result[:http_status] || 400) end diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb index a615abc198..817bea4275 100644 --- a/lib/banzai/filter/commit_trailers_filter.rb +++ b/lib/banzai/filter/commit_trailers_filter.rb @@ -17,21 +17,10 @@ module Banzai include ActionView::Helpers::TagHelper include AvatarsHelper - TRAILER_REGEXP = /(?