From 297ad29837559abc899e81626ab9deeab11c1c2c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 29 Mar 2023 23:47:44 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- .../environments/environment_names_finder.rb | 11 +-------- app/models/project_feature.rb | 3 ++- .../explore/projects/page_out_of_bounds.html.haml | 2 +- .../environments/environment_names_finder_spec.rb | 26 +++++++++++++++++----- spec/policies/project_policy_spec.rb | 4 ++-- .../projects/page_out_of_bounds.html.haml_spec.rb | 26 ++++++++++++++++++++++ 6 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb diff --git a/app/finders/environments/environment_names_finder.rb b/app/finders/environments/environment_names_finder.rb index d4928f0fc84..ffb689f45e2 100644 --- a/app/finders/environments/environment_names_finder.rb +++ b/app/finders/environments/environment_names_finder.rb @@ -32,18 +32,9 @@ module Environments end def namespace_environments - # We assume reporter access is needed for the :read_environment permission - # here. This expection is also present in - # IssuableFinder::Params#min_access_level, which is used for filtering out - # merge requests that don't have the right permissions. - # - # We use this approach so we don't need to load every project into memory - # just to verify if we can see their environments. Doing so would not be - # efficient, and possibly mess up pagination if certain projects are not - # meant to be visible. projects = project_or_group .all_projects - .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER) + .filter_by_feature_visibility(:environments, current_user) Environment.for_project(projects) end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 11f4a3f3b6f..d7bb9fa18ae 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -36,7 +36,8 @@ class ProjectFeature < ApplicationRecord merge_requests: Gitlab::Access::REPORTER, metrics_dashboard: Gitlab::Access::REPORTER, container_registry: Gitlab::Access::REPORTER, - package_registry: Gitlab::Access::REPORTER + package_registry: Gitlab::Access::REPORTER, + environments: Gitlab::Access::REPORTER }.freeze PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze diff --git a/app/views/explore/projects/page_out_of_bounds.html.haml b/app/views/explore/projects/page_out_of_bounds.html.haml index ef5ee2c679e..e13768a3ccb 100644 --- a/app/views/explore/projects/page_out_of_bounds.html.haml +++ b/app/views/explore/projects/page_out_of_bounds.html.haml @@ -18,5 +18,5 @@ %h5= _("Maximum page reached") %p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.") - = render Pajamas::ButtonComponent.new(href: request.params.merge(page: @max_page_number)) do + = render Pajamas::ButtonComponent.new(href: safe_params.merge(page: @max_page_number)) do = _("Back to page %{number}") % { number: @max_page_number } diff --git a/spec/finders/environments/environment_names_finder_spec.rb b/spec/finders/environments/environment_names_finder_spec.rb index 438f9e9ea7c..c2336c59119 100644 --- a/spec/finders/environments/environment_names_finder_spec.rb +++ b/spec/finders/environments/environment_names_finder_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do describe '#execute' do let!(:group) { create(:group) } let!(:public_project) { create(:project, :public, namespace: group) } + let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) } let!(:private_project) { create(:project, :private, namespace: group) } let!(:user) { create(:user) } @@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do create(:environment, name: 'gprd', project: public_project) create(:environment, name: 'gprd', project: private_project) create(:environment, name: 'gcny', project: private_project) + create(:environment, name: 'gprivprd', project: public_project_with_private_environments) + create(:environment, name: 'gprivstg', project: public_project_with_private_environments) + + public_project_with_private_environments.update!(namespace: group) + public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE) end context 'using a group' do @@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do names = described_class.new(group, user).execute - expect(names).to eq(%w[gcny gprd gstg]) + expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg]) end end @@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do names = described_class.new(group, user).execute - expect(names).to eq(%w[gcny gprd gstg]) + expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg]) end end @@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do end end + context 'with a public project reporter which has private environments' do + it 'returns environment names for public projects' do + public_project_with_private_environments.add_reporter(user) + + names = described_class.new(group, user).execute + + expect(names).to eq(%w[gprd gprivprd gprivstg gstg]) + end + end + context 'with a group guest' do - it 'returns environment names for all public projects' do + it 'returns environment names for public projects' do group.add_guest(user) names = described_class.new(group, user).execute @@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do end context 'with a non-member' do - it 'returns environment names for all public projects' do + it 'returns environment names for only public projects with public environments' do names = described_class.new(group, user).execute expect(names).to eq(%w[gprd gstg]) @@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do end context 'without a user' do - it 'returns environment names for all public projects' do + it 'returns environment names for only public projects with public environments' do names = described_class.new(group).execute expect(names).to eq(%w[gprd gstg]) diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index a98f091b9fc..2a4a169e5c5 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -2016,7 +2016,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true - :public | ProjectFeature::PRIVATE | :guest | true + :public | ProjectFeature::PRIVATE | :guest | false :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false @@ -2028,7 +2028,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true - :internal | ProjectFeature::PRIVATE | :guest | true + :internal | ProjectFeature::PRIVATE | :guest | false :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false diff --git a/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb new file mode 100644 index 00000000000..1ace28be5b4 --- /dev/null +++ b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'explore/projects/page_out_of_bounds.html.haml', feature_category: :projects do + let(:page_limit) { 10 } + let(:unsafe_param) { 'hacked_using_unsafe_param!' } + + before do + assign(:max_page_number, page_limit) + + controller.params[:action] = 'index' + controller.params[:host] = unsafe_param + controller.params[:protocol] = unsafe_param + controller.params[:sort] = 'name_asc' + end + + it 'removes unsafe params from the link' do + render + + href = "/explore/projects?page=#{page_limit}&sort=name_asc" + button_text = format(_("Back to page %{number}"), number: page_limit) + expect(rendered).to have_link(button_text, href: href) + expect(rendered).not_to include(unsafe_param) + end +end -- cgit v1.2.1 From ea1912e51181abbbad5f33486ac5f455c19f1d2e Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 29 Mar 2023 23:52:20 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- app/assets/javascripts/pages/projects/project.js | 3 +- app/assets/javascripts/repository/index.js | 9 ++- .../repository/utils/ref_switcher_utils.js | 30 +++++--- app/controllers/concerns/confirm_email_warning.rb | 14 +++- app/controllers/projects/blob_controller.rb | 10 +++ app/controllers/projects/refs_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 9 +++ app/controllers/projects_controller.rb | 10 ++- app/finders/notes_finder.rb | 8 ++ app/models/hooks/web_hook.rb | 18 ++++- app/policies/project_policy.rb | 1 - .../merge_requests/push_options_handler_service.rb | 10 ++- app/views/projects/tree/_tree_header.html.haml | 2 +- ..._nullify_last_error_from_project_mirror_data.rb | 26 +++++++ db/schema_migrations/20230208131808 | 1 + .../output_example_snapshots/snapshot_spec.html | 16 ++-- lib/api/repositories.rb | 4 + lib/extracts_ref.rb | 14 +++- .../nullify_last_error_from_project_mirror_data.rb | 17 ++++ lib/gitlab/unicode.rb | 6 ++ lib/gitlab/url_sanitizer.rb | 90 ++++++++++++++-------- lib/rouge/formatters/html_gitlab.rb | 9 ++- locale/gitlab.pot | 3 + spec/controllers/admin/hooks_controller_spec.rb | 3 +- .../concerns/confirm_email_warning_spec.rb | 34 +++++++- spec/controllers/projects/blob_controller_spec.rb | 33 +++++++- .../projects/clusters_controller_spec.rb | 23 +++++- .../environments/prometheus_api_controller_spec.rb | 23 +++++- spec/controllers/projects/refs_controller_spec.rb | 4 +- spec/controllers/projects/tree_controller_spec.rb | 37 +++++++-- spec/controllers/projects_controller_spec.rb | 63 +++++++++++++++ spec/factories/project_hooks.rb | 2 +- spec/features/admin/users/user_spec.rb | 30 ++++++++ spec/finders/notes_finder_spec.rb | 20 +++++ .../repository/utils/ref_switcher_utils_spec.js | 31 +++++++- spec/helpers/hooks_helper_spec.rb | 2 +- ...ify_last_error_from_project_mirror_data_spec.rb | 84 ++++++++++++++++++++ spec/lib/gitlab/url_sanitizer_spec.rb | 31 +++++--- spec/lib/rouge/formatters/html_gitlab_spec.rb | 21 ++++- ...ify_last_error_from_project_mirror_data_spec.rb | 37 +++++++++ spec/models/hooks/web_hook_spec.rb | 28 +++++++ spec/policies/project_policy_spec.rb | 46 ++++++++++- spec/requests/api/repositories_spec.rb | 16 ++++ .../push_options_handler_service_spec.rb | 15 ++++ spec/services/web_hook_service_spec.rb | 4 +- 45 files changed, 790 insertions(+), 109 deletions(-) create mode 100644 db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb create mode 100644 db/schema_migrations/20230208131808 create mode 100644 lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb create mode 100644 spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb create mode 100644 spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 4c9eb830ff6..27659348dcc 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -118,9 +118,10 @@ export default class Project { const urlParams = { [fieldName]: ref }; if (params.group === BRANCH_GROUP_NAME) { urlParams.ref_type = BRANCH_REF_TYPE; - } else { + } else if (params.group === TAG_GROUP_NAME) { urlParams.ref_type = TAG_REF_TYPE; } + link.href = mergeUrlParams(urlParams, linkTarget); } diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index e5d22f50d72..0f6b159ffc5 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; -import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; +import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import initWebIdeLink from '~/pages/projects/shared/web_ide_link'; import PerformancePlugin from '~/performance/vue_performance_plugin'; @@ -119,7 +119,7 @@ export default function setupVueRepositoryList() { if (!refSwitcherEl) return false; - const { projectId, projectRootPath } = refSwitcherEl.dataset; + const { projectId, projectRootPath, refType } = refSwitcherEl.dataset; return new Vue({ el: refSwitcherEl, @@ -127,11 +127,12 @@ export default function setupVueRepositoryList() { return createElement(RefSelector, { props: { projectId, - value: ref, + value: refType ? joinPaths('refs', refType, ref) : ref, + useSymbolicRefNames: true, }, on: { input(selectedRef) { - visitUrl(generateRefDestinationPath(projectRootPath, selectedRef)); + visitUrl(generateRefDestinationPath(projectRootPath, ref, selectedRef)); }, }, }); diff --git a/app/assets/javascripts/repository/utils/ref_switcher_utils.js b/app/assets/javascripts/repository/utils/ref_switcher_utils.js index f296b5e9b4a..fc0d521ad54 100644 --- a/app/assets/javascripts/repository/utils/ref_switcher_utils.js +++ b/app/assets/javascripts/repository/utils/ref_switcher_utils.js @@ -15,22 +15,30 @@ const NAMESPACE_TARGET_REGEX = /(\/-\/(blob|tree))\/.*?\/(.*)/; * @param {string} projectRootPath - The root path for a project. * @param {string} selectedRef - The selected ref from the ref dropdown. */ -export function generateRefDestinationPath(projectRootPath, selectedRef) { - const currentPath = window.location.pathname; - const encodedHash = '%23'; + +export function generateRefDestinationPath(projectRootPath, ref, selectedRef) { + const url = new URL(window.location.href); + const currentPath = url.pathname; + let refType = null; let namespace = '/-/tree'; let target; + let actualRef = selectedRef; + + const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/); + if (matches) { + [, refType, actualRef] = matches; + } + if (refType) { + url.searchParams.set('ref_type', refType); + } else { + url.searchParams.delete('ref_type'); + } + const match = NAMESPACE_TARGET_REGEX.exec(currentPath); if (match) { [, namespace, , target] = match; } + url.pathname = joinPaths(projectRootPath, namespace, actualRef, target); - const destinationPath = joinPaths( - projectRootPath, - namespace, - encodeURI(selectedRef).replace(/#/g, encodedHash), - target, - ); - - return `${destinationPath}${window.location.hash}`; + return url.toString(); } diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index ec5140bf223..2711c823275 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module ConfirmEmailWarning + include Gitlab::Utils::StrongMemoize extend ActiveSupport::Concern included do @@ -17,11 +18,9 @@ module ConfirmEmailWarning return unless current_user return if current_user.confirmed? - email = current_user.unconfirmed_email || current_user.email - flash.now[:warning] = format( confirm_warning_message, - email: email, + email: email_to_display, resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post), update_link: view_context.link_to(_('Update it'), profile_path) ).html_safe @@ -29,7 +28,16 @@ module ConfirmEmailWarning private + def email + current_user.unconfirmed_email || current_user.email + end + strong_memoize_attr :email + def confirm_warning_message _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.") end + + def email_to_display + html_escape(email) + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 4eda76f4f21..dd2f980e880 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy] before_action :commit, except: [:new, :create] + before_action :check_for_ambiguous_ref, only: [:show] before_action :blob, except: [:new, :create] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] @@ -145,6 +146,15 @@ class Projects::BlobController < Projects::ApplicationController end end + def check_for_ambiguous_ref + @ref_type = ref_type + + if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + redirect_to project_blob_path(@project, File.join(branch.target, @path)) + end + end + def commit @commit ||= @repository.commit(@ref) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 8ac6d872aae..0b52d03ab95 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -22,7 +22,7 @@ class Projects::RefsController < Projects::ApplicationController when "tree" project_tree_path(@project, @id) when "blob" - project_blob_path(@project, @id) + project_blob_path(@project, @id, ref_type: ref_type) when "graph" if Feature.enabled?(:use_ref_type_parameter, @project) project_network_path(@project, @id, ref_type: ref_type) diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 737a6290431..1f7912e15df 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -28,6 +28,15 @@ class Projects::TreeController < Projects::ApplicationController def show return render_404 unless @commit + @ref_type = ref_type + if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + if branch + redirect_to project_tree_path(@project, branch.target) + return + end + end + if tree.entries.empty? if @repository.blob_at(@commit.id, @path) redirect_to project_blob_path(@project, File.join(@ref, @path)) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ee2c268ff33..ed7c163c108 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -172,11 +172,19 @@ class ProjectsController < Projects::ApplicationController flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } end + if ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + + # The files view would render a ref other than the default branch + # This redirect can be removed once the view is fixed + redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref")) + return + end + respond_to do |format| format.html do @notification_setting = current_user.notification_settings_for(@project) if current_user @project = @project.present(current_user: current_user) - render_landing_page end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index c542ffbce7e..ea16bc01029 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -30,6 +30,7 @@ class NotesFinder notes = init_collection notes = since_fetch_at(notes) notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter? + notes = redact_internal(notes) sort(notes) end @@ -181,6 +182,13 @@ class NotesFinder notes.order_by(sort) end + + def redact_internal(notes) + subject = @project || target + return notes if Ability.allowed?(@current_user, :reporter_access, subject) + + notes.not_internal + end end NotesFinder.prepend_mod_with('NotesFinder') diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 49418cda3ac..a787eb1d5f0 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -41,7 +41,7 @@ class WebHook < ApplicationRecord after_initialize :initialize_url_variables before_validation :reset_token - before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) } + before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }, on: :update before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches? validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex? validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard? @@ -189,7 +189,7 @@ class WebHook < ApplicationRecord # See app/validators/json_schemas/web_hooks_url_variables.json VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze - def interpolated_url + def interpolated_url(url = self.url, url_variables = self.url_variables) return url unless url.include?('{') vars = url_variables @@ -215,7 +215,19 @@ class WebHook < ApplicationRecord end def reset_url_variables - self.url_variables = {} if url_changed? && !encrypted_url_variables_changed? + interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were) + + return if url_variables_were.empty? || interpolated_url_was == interpolated_url + + self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any? + end + + def decrypt_url_was + self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was)) + end + + def url_variables_were + self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was) end def next_failure_count diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index b85a57f81cd..cd05cc1763d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -404,7 +404,6 @@ class ProjectPolicy < BasePolicy end rule { can?(:metrics_dashboard) }.policy do - enable :read_prometheus enable :read_deployment end diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb index 711978dc3f7..66af1c087ed 100644 --- a/app/services/merge_requests/push_options_handler_service.rb +++ b/app/services/merge_requests/push_options_handler_service.rb @@ -54,7 +54,15 @@ module MergeRequests end def validate_service - errors << 'User is required' if current_user.nil? + if current_user.nil? + errors << 'User is required' + return + end + + unless current_user&.can?(:read_code, target_project) + errors << 'User access was denied' + return + end unless target_project.merge_requests_enabled? errors << "Merge requests are not enabled for project #{target_project.full_path}" diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index fd807350245..543cbc4e4e4 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -2,7 +2,7 @@ .tree-ref-container.gl-display-flex.mb-2.mb-md-0 .tree-ref-holder - #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } } + #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } } #js-repo-breadcrumb{ data: breadcrumb_data_attributes } diff --git a/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb b/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb new file mode 100644 index 00000000000..73e6f257498 --- /dev/null +++ b/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class NullifyLastErrorFromProjectMirrorData < Gitlab::Database::Migration[2.1] + MIGRATION = 'NullifyLastErrorFromProjectMirrorData' + INTERVAL = 2.minutes + BATCH_SIZE = 10_000 + SUB_BATCH_SIZE = 1_000 + + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + queue_batched_background_migration( + MIGRATION, + :project_mirror_data, + :id, + job_interval: INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :project_mirror_data, :id, []) + end +end diff --git a/db/schema_migrations/20230208131808 b/db/schema_migrations/20230208131808 new file mode 100644 index 00000000000..24c5b21f6ad --- /dev/null +++ b/db/schema_migrations/20230208131808 @@ -0,0 +1 @@ +784f8f189eee7b5cf3136f0a859874a1d170d2b148f4c260f968b144816f1322 \ No newline at end of file diff --git a/glfm_specification/output_example_snapshots/snapshot_spec.html b/glfm_specification/output_example_snapshots/snapshot_spec.html index 415ad1d0b11..73e1cd0a235 100644 --- a/glfm_specification/output_example_snapshots/snapshot_spec.html +++ b/glfm_specification/output_example_snapshots/snapshot_spec.html @@ -7028,7 +7028,7 @@ references and their corresponding code points.

-
<p>  &amp; © Æ Ď
+
<p>  &amp; © Æ Ď
 ¾ ℋ ⅆ
 ∲ ≧̸</p>
@@ -7344,11 +7344,11 @@ stripped in this way:

-
` b `
+
` b `
-
<p><code> b </code></p>
+
<p><code> b </code></p>
@@ -7356,12 +7356,12 @@ stripped in this way:

-
` `
+
` `
 `  `
-
<p><code> </code>
+
<p><code> </code>
 <code>  </code></p>
@@ -7832,11 +7832,11 @@ not part of a [left-flanking delimiter run]:

-
* a *
+
* a *
-
<p>* a *</p>
+
<p>* a *</p>
@@ -9790,7 +9790,7 @@ Other [Unicode whitespace] like non-breaking space doesn't work.

-
[link](/url "title")
+
[link](/url "title")
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 70535496b12..6f8d34ea387 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -203,6 +203,10 @@ module API render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400) end + unless can?(current_user, :read_code, target_project) + forbidden!("You don't have access to this fork's parent project") + end + cache_key = compare_cache_key(current_user, user_project, target_project, declared_params) cache_action(cache_key, expires_in: 1.minute) do diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb index f22996df0a5..23f93ad1317 100644 --- a/lib/extracts_ref.rb +++ b/lib/extracts_ref.rb @@ -5,7 +5,8 @@ # Can be extended for different types of repository object, e.g. Project or Snippet module ExtractsRef InvalidPathError = Class.new(StandardError) - + BRANCH_REF_TYPE = 'heads' + TAG_REF_TYPE = 'tags' # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # @@ -91,7 +92,7 @@ module ExtractsRef def ref_type return unless params[:ref_type].present? - params[:ref_type] == 'tags' ? 'tags' : 'heads' + params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE end private @@ -154,4 +155,13 @@ module ExtractsRef def repository_container raise NotImplementedError end + + def ambiguous_ref?(project, ref) + return true if project.repository.ambiguous_ref?(ref) + + return false unless ref&.starts_with?('refs/') + + unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '') + project.repository.commit(unprefixed_ref).present? + end end diff --git a/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb b/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb new file mode 100644 index 00000000000..6ea5c17353b --- /dev/null +++ b/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Nullifies last_error value from project_mirror_data table as they + # potentially included sensitive data. + # https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3041 + class NullifyLastErrorFromProjectMirrorData < BatchedMigrationJob + feature_category :source_code_management + operation_name :update_all + + def perform + each_sub_batch { |rel| rel.update_all(last_error: nil) } + end + end + end +end diff --git a/lib/gitlab/unicode.rb b/lib/gitlab/unicode.rb index b49c5647dab..f291ea1b4ee 100644 --- a/lib/gitlab/unicode.rb +++ b/lib/gitlab/unicode.rb @@ -9,6 +9,12 @@ module Gitlab # https://idiosyncratic-ruby.com/41-proper-unicoding.html BIDI_REGEXP = /\p{Bidi Control}/.freeze + # Regular expression for identifying space characters + # + # In web browsers space characters can be confused with simple + # spaces which may be misleading + SPACE_REGEXP = /\p{Space_Separator}/.freeze + class << self # Warning message used to highlight bidi characters in the GUI def bidi_warning diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index e3bf11b00b4..79e124a58f5 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -2,15 +2,37 @@ module Gitlab class UrlSanitizer + include Gitlab::Utils::StrongMemoize + ALLOWED_SCHEMES = %w[http https ssh git].freeze ALLOWED_WEB_SCHEMES = %w[http https].freeze + SCHEMIFIED_SCHEME = 'glschemelessuri' + SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://".freeze + # URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or + # relative URLs. This section will match schemeless URIs with userinfo + # e.g. user:pass@gitlab.com but will not match scp-style URIs e.g. + # user@server:path/to/file) + # + # The userinfo part is very loose compared to URI's implementation so we + # also match non-escaped userinfo e.g foo:b?r@gitlab.com which should be + # encoded as foo:b%3Fr@gitlab.com + URI_REGEXP = %r{ + (?: + #{URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)} + | + (?:(?:(?!@)[%#{URI::REGEXP::PATTERN::UNRESERVED}#{URI::REGEXP::PATTERN::RESERVED}])+(?:@)) + (?# negative lookahead ensures this isn't an SCP-style URL: [host]:[rel_path|abs_path] server:path/to/file) + (?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH})) + #{URI::REGEXP::PATTERN::HOSTPORT} + ) + }x def self.sanitize(content) - regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES) - - content.gsub(regexp) { |url| new(url).masked_url } - rescue Addressable::URI::InvalidURIError - content.gsub(regexp, '') + content.gsub(URI_REGEXP) do |url| + new(url).masked_url + rescue Addressable::URI::InvalidURIError + '' + end end def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES) @@ -37,34 +59,45 @@ module Gitlab @url = parse_url(url) end + def credentials + @credentials ||= { user: @url.user.presence, password: @url.password.presence } + end + + def user + credentials[:user] + end + def sanitized_url - @sanitized_url ||= safe_url.to_s + safe_url = @url.dup + safe_url.password = nil + safe_url.user = nil + reverse_schemify(safe_url.to_s) end + strong_memoize_attr :sanitized_url def masked_url url = @url.dup url.password = "*****" if url.password.present? url.user = "*****" if url.user.present? - url.to_s - end - - def credentials - @credentials ||= { user: @url.user.presence, password: @url.password.presence } - end - - def user - credentials[:user] + reverse_schemify(url.to_s) end + strong_memoize_attr :masked_url def full_url - @full_url ||= generate_full_url.to_s + return reverse_schemify(@url.to_s) unless valid_credentials? + + url = @url.dup + url.password = encode_percent(credentials[:password]) if credentials[:password].present? + url.user = encode_percent(credentials[:user]) if credentials[:user].present? + reverse_schemify(url.to_s) end + strong_memoize_attr :full_url private def parse_url(url) - url = url.to_s.strip - match = url.match(%r{\A(?:git|ssh|http(?:s?))\://(?:(.+)(?:@))?(.+)}) + url = schemify(url.to_s.strip) + match = url.match(%r{\A(?:(?:#{SCHEMIFIED_SCHEME}|git|ssh|http(?:s?)):)?//(?:(.+)(?:@))?(.+)}o) raw_credentials = match[1] if match if raw_credentials.present? @@ -83,24 +116,19 @@ module Gitlab url end - def generate_full_url - return @url unless valid_credentials? - - @url.dup.tap do |generated| - generated.password = encode_percent(credentials[:password]) if credentials[:password].present? - generated.user = encode_percent(credentials[:user]) if credentials[:user].present? - end + def schemify(url) + # Prepend the placeholder scheme unless the URL has a scheme or is relative + url.prepend(SCHEMIFY_PLACEHOLDER) unless url.starts_with?(%r{(?:#{URI::REGEXP::PATTERN::SCHEME}:)?//}o) + url end - def safe_url - safe_url = @url.dup - safe_url.password = nil - safe_url.user = nil - safe_url + def reverse_schemify(url) + url.slice!(SCHEMIFY_PLACEHOLDER) if url.starts_with?(SCHEMIFY_PLACEHOLDER) + url end def valid_credentials? - credentials && credentials.is_a?(Hash) && credentials.any? + credentials.is_a?(Hash) && credentials.values.any? end def encode_percent(string) diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 436739bed12..a7e95a96b8b 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -25,7 +25,10 @@ module Rouge yield %() line.each do |token, value| - yield highlight_unicode_control_characters(span(token, value.chomp! || value)) + value = value.chomp! || value + value = replace_space_characters(value) + + yield highlight_unicode_control_characters(span(token, value)) end yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present? @@ -42,6 +45,10 @@ module Rouge %(#{@ellipsis_svg}) end + def replace_space_characters(text) + text.gsub(Gitlab::Unicode::SPACE_REGEXP, ' ') + end + def highlight_unicode_control_characters(text) text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char| %(#{char}) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ae06cb3ae7a..f8626094af5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42005,6 +42005,9 @@ msgstr "" msgid "The default branch for this project has been changed. Please update your bookmarks." msgstr "" +msgid "The default branch of this project clashes with another ref" +msgstr "" + msgid "The dependency list details information about the components used within your project." msgstr "" diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb index 4101bd7f658..4e68ffdda2a 100644 --- a/spec/controllers/admin/hooks_controller_spec.rb +++ b/spec/controllers/admin/hooks_controller_spec.rb @@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do enable_ssl_verification: false, url_variables: [ { key: 'token', value: 'some secret value' }, + { key: 'baz', value: 'qux' }, { key: 'foo', value: nil } ] } @@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do expect(flash[:notice]).to include('was updated') expect(hook).to have_attributes(hook_params.except(:url_variables)) expect(hook).to have_attributes( - url_variables: { 'token' => 'some secret value', 'baz' => 'woo' } + url_variables: { 'token' => 'some secret value', 'baz' => 'qux' } ) end end diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index 334c156e1ae..b8a4b94aa66 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ConfirmEmailWarning do +RSpec.describe ConfirmEmailWarning, feature_category: :system_access do before do stub_feature_flags(soft_email_confirmation: true) end @@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do it { is_expected.to set_confirm_warning_for(user.email) } end end + + context 'when user is being impersonated' do + let(:impersonator) { create(:admin) } + + before do + allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id }) + + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when impersonated user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com
") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end + + context 'when user is not being impersonated' do + before do + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end end end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 887a5ba598f..ec92d92e2a9 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -2,15 +2,16 @@ require 'spec_helper' -RSpec.describe Projects::BlobController do +RSpec.describe Projects::BlobController, feature_category: :source_code_management do include ProjectForksHelper let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } describe "GET show" do - def request - get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) + let(:params) { { namespace_id: project.namespace, project_id: project, id: id } } + let(:request) do + get(:show, params: params) end render_views @@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do context 'with file path' do before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) request end + context 'when the ref is ambiguous' do + let(:ref) { 'ambiguous_ref' } + let(:path) { 'README.md' } + let(:id) { "#{ref}/#{path}" } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}")) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end + end + context "valid branch, valid file" do let(:id) { 'master/README.md' } diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 894f0f8354d..64c77726527 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController do include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let(:user) { create(:user) } @@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController do expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index 68d50cf19f0..6b0c164e432 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::Environments::PrometheusApiController do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let_it_be(:proxyable) { create(:environment, project: project) } before do @@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index a7a8361ae20..e36eb1207c1 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id) } + 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id) } 'graphs' | nil | lazy { project_graph_path(project, id) } @@ -60,7 +60,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id) } + 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') } 'graphs' | nil | lazy { project_graph_path(project, id) } diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 9bc3065b6da..37149e1d3ca 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TreeController do +RSpec.describe Projects::TreeController, feature_category: :source_code_management do let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } let(:user) { create(:user) } @@ -15,18 +15,41 @@ RSpec.describe Projects::TreeController do end describe "GET show" do + let(:params) do + { + namespace_id: project.namespace.to_param, project_id: project, id: id + } + end + # Make sure any errors accessing the tree in our views bubble up to this spec render_views before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) + get :show, params: params + end - get(:show, - params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: id - }) + context 'when the ref is ambiguous' do + let(:id) { 'ambiguous_ref' } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id)) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end end context "valid branch, no path" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index bc58eaa1d6f..8c81a1414ce 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -163,6 +163,69 @@ RSpec.describe ProjectsController do expect(assigns(:notification_setting).level).to eq("watch") end end + + context 'when there is a tag with the same name as the default branch' do + let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) } + let(:tree_with_default_branch) do + branch = tagged_project.repository.find_branch(tagged_project.default_branch) + project_tree_path(tagged_project, branch.target) + end + + before do + tagged_project.repository.create_file( + tagged_project.creator, + 'file_for_tag', + 'content for file', + message: "Automatically created file", + branch_name: 'branch-to-tag' + ) + + tagged_project.repository.add_tag( + tagged_project.creator, + tagged_project.default_branch, # tag name + 'branch-to-tag' # target + ) + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project } + expect(response).to redirect_to(tree_with_default_branch) + end + end + + context 'when the default branch name can resolve to another ref' do + let!(:project_with_default_branch) do + create(:project, :public, :custom_repo, files: ['somefile']).tap do |p| + p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master') + p.change_head("refs/heads/#{other_ref}") + end.reload + end + + let(:other_ref) { 'branch-name' } + + context 'but there is no other ref' do + it 'responds with ok' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to be_ok + end + end + + context 'and that other ref exists' do + let(:tree_with_default_branch) do + branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch) + project_tree_path(project_with_default_branch, branch.target) + end + + before do + project_with_default_branch.repository.create_branch(other_ref, 'master') + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to redirect_to(tree_with_default_branch) + end + end + end end describe "when project repository is disabled" do diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index 946b3925ee9..d84e287d765 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -7,7 +7,7 @@ FactoryBot.define do project trait :url_variables do - url_variables { { 'abc' => 'supers3cret' } } + url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } } end trait :token do diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 1552d4e6187..66129617220 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do icon = first('[data-testid="incognito-icon"]') expect(icon).not_to be nil end + + context 'when viewing the confirm email warning', :js do + let_it_be(:another_user) { create(:user, :unconfirmed) } + + let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') } + let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } } + + context 'with an email that does not contain HTML' do + before do + subject + end + + it 'displays the warning alert including the email' do + expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify") + end + end + + context 'with an email that contains HTML' do + let(:malicious_email) { "malicious@test.com" } + let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) } + + before do + subject + end + + it 'displays the impersonation alert, excludes email, and disables links' do + expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify") + end + end + end end context 'ending impersonation' do diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 792a14e3064..1255a882114 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -106,6 +106,26 @@ RSpec.describe NotesFinder do end end + context 'for notes on public issue in public project' do + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:guest_member) { create(:user) } + let_it_be(:reporter_member) { create(:user) } + let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) } + let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) } + let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) } + let_it_be(:public_note) { create(:note_on_issue, project: public_project) } + + it 'shows all notes when the current_user has reporter access' do + notes = described_class.new(reporter_member, project: public_project).execute + expect(notes).to contain_exactly internal_note, public_note + end + + it 'shows only public notes when the current_user has guest access' do + notes = described_class.new(guest_member, project: public_project).execute + expect(notes).to contain_exactly public_note + end + end + context 'for target type' do let(:project) { create(:project, :repository) } let!(:note1) { create :note_on_issue, project: project } diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js index 4d0250fffbf..220dbf17398 100644 --- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js +++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js @@ -1,5 +1,6 @@ import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'spec/test_constants'; import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data'; const projectRootPath = 'root/Project1'; @@ -16,14 +17,38 @@ describe('generateRefDestinationPath', () => { ${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`} - `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { + `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { setWindowLocation(currentPath); - expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result); + expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }); + + describe('when using symbolic ref names', () => { + it.each` + currentPath | nextRef | result + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`} + `( + 'generates the correct destination path for $currentPath with ref type when it can be extracted', + ({ currentPath, result, nextRef }) => { + setWindowLocation(currentPath); + expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }, + ); }); it('encodes the selected ref', () => { const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`; - expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result); + expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe( + `${TEST_HOST}/${result}`, + ); }); }); diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb index 98a1f77b414..83d0a86eaa3 100644 --- a/spec/helpers/hooks_helper_spec.rb +++ b/spec/helpers/hooks_helper_spec.rb @@ -26,7 +26,7 @@ RSpec.describe HooksHelper do it 'returns proper data' do expect(subject).to match( url: project_hook.url, - url_variables: Gitlab::Json.dump([{ key: 'abc' }]) + url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }]) ) end end diff --git a/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb new file mode 100644 index 00000000000..62f908ed79b --- /dev/null +++ b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength + it 'nullifies last_error column on all rows' do + namespaces = table(:namespaces) + projects = table(:projects) + project_import_states = table(:project_mirror_data) + + group = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + + project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org') + + project_1 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_1.id, + name: 'test1' + ) + project_2 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_2.id, + name: 'test2' + ) + project_3 = projects.create!( + namespace_id: group.id, + project_namespace_id: project_namespace_3.id, + name: 'test3' + ) + + project_import_state_1 = project_import_states.create!( + project_id: project_1.id, + status: 0, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: 2.days.ago, + last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + project_import_states.create!( + project_id: project_2.id, + status: 1, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: nil, + next_execution_timestamp: 1.day.from_now, + last_error: '', + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + project_import_state_3 = project_import_states.create!( + project_id: project_3.id, + status: 2, + last_update_started_at: 1.hour.ago, + last_update_scheduled_at: 1.hour.ago, + last_update_at: 1.hour.ago, + last_successful_update_at: 1.hour.ago, + next_execution_timestamp: 1.day.from_now, + last_error: nil, + correlation_id_value: SecureRandom.uuid, + jid: SecureRandom.uuid + ) + + migration = described_class.new( + start_id: project_import_state_1.id, + end_id: project_import_state_3.id, + batch_table: :project_mirror_data, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection + ) + + w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord + expect { migration.perform }.to change(&w_last_error_count).from(2).to(0) + end +end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 0ffbf5f81e7..c02cbef8328 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -10,29 +10,36 @@ RSpec.describe Gitlab::UrlSanitizer do # We want to try with multi-line content because is how error messages are formatted described_class.sanitize(%Q{ remote: Not Found - fatal: repository '#{url}' not found + fatal: repository `#{url}` not found }) end where(:input, :output) do - 'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/' - 'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/' - 'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git' - - # git protocol does not support authentication but clean any details anyway - 'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git' - 'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git' + # http(s), ssh, git, relative, and schemeless URLs should all be masked correctly + urls = ['http://', 'https://', 'ssh://', 'git://', '//', ''].flat_map do |protocol| + [ + ["#{protocol}test.com", "#{protocol}test.com"], + ["#{protocol}test.com/", "#{protocol}test.com/"], + ["#{protocol}test.com/path/to/repo.git", "#{protocol}test.com/path/to/repo.git"], + ["#{protocol}user@test.com", "#{protocol}*****@test.com"], + ["#{protocol}user:pass@test.com", "#{protocol}*****:*****@test.com"], + ["#{protocol}user:@test.com", "#{protocol}*****@test.com"], + ["#{protocol}:pass@test.com", "#{protocol}:*****@test.com"] + ] + end # SCP-style URLs are left unmodified - 'user@server:project.git' | 'user@server:project.git' - 'user:pass@server:project.git' | 'user:pass@server:project.git' + urls << ['user@server:project.git', 'user@server:project.git'] + urls << ['user:@server:project.git', 'user:@server:project.git'] + urls << [':pass@server:project.git', ':pass@server:project.git'] + urls << ['user:pass@server:project.git', 'user:pass@server:project.git'] # return an empty string for invalid URLs - 'ssh://' | '' + urls << ['ssh://', ''] end with_them do - it { expect(sanitize_url(input)).to include("repository '#{output}' not found") } + it { expect(sanitize_url(input)).to include("repository `#{output}` not found") } end end diff --git a/spec/lib/rouge/formatters/html_gitlab_spec.rb b/spec/lib/rouge/formatters/html_gitlab_spec.rb index 79bfdb262c0..6fc1b395fc8 100644 --- a/spec/lib/rouge/formatters/html_gitlab_spec.rb +++ b/spec/lib/rouge/formatters/html_gitlab_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Rouge::Formatters::HTMLGitlab do +RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_management do describe '#format' do subject { described_class.format(tokens, **options) } @@ -67,5 +67,24 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do is_expected.to include(%{}).exactly(4).times end end + + context 'when space characters and zero-width spaces are used' do + let(:lang) { 'ruby' } + let(:tokens) { lexer.lex(code, continue: false) } + + let(:code) do + <<~JS + def\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000hello + JS + end + + it 'replaces the space characters with spaces' do + is_expected.to eq( + "" \ + "def hello" \ + "" + ) + end + end end end diff --git a/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb new file mode 100644 index 00000000000..6c5679b674e --- /dev/null +++ b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do + let(:migration) { described_class::MIGRATION } + + before do + migrate! + end + + describe '#up' do + it 'schedules background jobs for each batch of projects' do + expect(migration).to( + have_scheduled_batched_migration( + table_name: :project_mirror_data, + column_name: :id, + interval: described_class::INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + ) + end + end + + describe '#down' do + before do + schema_migrate_down! + end + + it 'deletes all batched migration records' do + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 75ff917c036..48d5b3b83c5 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -242,6 +242,22 @@ RSpec.describe WebHook, feature_category: :integrations do expect(hook.url_variables).to eq({}) end + it 'resets url variables if url is changed and url variables are appended' do + hook.url = 'http://suspicious.example.com/{abc}/{foo}' + hook.url_variables = hook.url_variables.merge('foo' => 'bar') + + expect(hook).not_to be_valid + expect(hook.url_variables).to eq({}) + end + + it 'resets url variables if url is changed and url variables are removed' do + hook.url = 'http://suspicious.example.com/{abc}' + hook.url_variables = hook.url_variables.except("def") + + expect(hook).not_to be_valid + expect(hook.url_variables).to eq({}) + end + it 'does not reset url variables if both url and url variables are changed' do hook.url = 'http://example.com/{one}/{two}' hook.url_variables = { 'one' => 'foo', 'two' => 'bar' } @@ -249,6 +265,18 @@ RSpec.describe WebHook, feature_category: :integrations do expect(hook).to be_valid expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' }) end + + context 'without url variables' do + subject(:hook) { build_stubbed(:project_hook, project: project, url: 'http://example.com') } + + it 'does not reset url variables' do + hook.url = 'http://example.com/{one}/{two}' + hook.url_variables = { 'one' => 'foo', 'two' => 'bar' } + + expect(hook).to be_valid + expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' }) + end + end end it "only consider these branch filter strategies are valid" do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 2a4a169e5c5..52d1e70d7a0 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -697,6 +697,39 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio end end + describe 'read_prometheus', feature_category: :metrics do + using RSpec::Parameterized::TableSyntax + + before do + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + let(:policy) { :read_prometheus } + + where(:project_visibility, :role, :allowed) do + :public | :anonymous | false + :public | :guest | false + :public | :reporter | true + :internal | :anonymous | false + :internal | :guest | false + :internal | :reporter | true + :private | :anonymous | false + :private | :guest | false + :private | :reporter | true + end + + with_them do + let(:current_user) { public_send(role) } + let(:project) { public_send("#{project_visibility}_project") } + + if params[:allowed] + it { is_expected.to be_allowed(policy) } + else + it { is_expected.not_to be_allowed(policy) } + end + end + end + describe 'update_max_artifacts_size' do context 'when no user' do let(:current_user) { anonymous } @@ -972,7 +1005,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -982,7 +1015,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) } @@ -1008,12 +1041,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1036,7 +1071,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1046,6 +1081,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end @@ -1068,12 +1104,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1092,12 +1130,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 555ba2bc978..be26fe24061 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -573,6 +573,22 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do context 'when authenticated', 'as a developer' do it_behaves_like 'repository compare' do let(:current_user) { user } + + context 'when user does not have read access to the parent project' do + let_it_be(:group) { create(:group) } + let(:forked_project) { fork_project(project, current_user, repository: true, namespace: group) } + + before do + forked_project.add_guest(current_user) + end + + it 'returns 403 error' do + get api(route, current_user), params: { from: 'improve/awesome', to: 'feature', from_project_id: forked_project.id } + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq("403 Forbidden - You don't have access to this fork's parent project") + end + end end end diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index 251bf6f0d9d..03f3d56cdd2 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -861,6 +861,21 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do end end + describe 'when user does not have access to target project' do + let(:push_options) { { create: true, target: 'my-branch' } } + let(:changes) { default_branch_changes } + + before do + allow(user1).to receive(:can?).with(:read_code, project).and_return(false) + end + + it 'records an error', :sidekiq_inline do + service.execute + + expect(service.errors).to eq(["User access was denied"]) + end + end + describe 'when MRs are not enabled' do let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } } let(:push_options) { { create: true } } diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 4b925a058e7..bc11895a12a 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -130,8 +130,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state context 'there is userinfo' do before do project_hook.update!( - url: 'http://{one}:{two}@example.com', - url_variables: { 'one' => 'a', 'two' => 'b' } + url: 'http://{foo}:{bar}@example.com', + url_variables: { 'foo' => 'a', 'bar' => 'b' } ) stub_full_request('http://example.com', method: :post) end -- cgit v1.2.1 From 3263a137a1aee366ca86f1fe5fe066c8a48160d2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 29 Mar 2023 23:53:26 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- .../behaviors/markdown/render_observability.js | 45 ++++++++++++------- lib/banzai/filter/inline_observability_filter.rb | 15 +++++-- .../markdown/render_observability_spec.js | 12 ++++- .../filter/inline_observability_filter_spec.rb | 52 ++++++++++++++++++++++ 4 files changed, 103 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_observability.js b/app/assets/javascripts/behaviors/markdown/render_observability.js index 704d85cf22e..d5d46c10efd 100644 --- a/app/assets/javascripts/behaviors/markdown/render_observability.js +++ b/app/assets/javascripts/behaviors/markdown/render_observability.js @@ -7,23 +7,36 @@ export function getFrameSrc(url) { } const mountVueComponent = (element) => { - const url = [element.dataset.frameUrl]; + const { frameUrl, observabilityUrl } = element.dataset; - return new Vue({ - el: element, - render(h) { - return h('iframe', { - style: { - height: '366px', - width: '768px', - }, - attrs: { - src: getFrameSrc(url), - frameBorder: '0', - }, - }); - }, - }); + try { + if ( + !observabilityUrl || + !frameUrl || + new URL(frameUrl)?.host !== new URL(observabilityUrl).host + ) + return; + + // eslint-disable-next-line no-new + new Vue({ + el: element, + render(h) { + return h('iframe', { + style: { + height: '366px', + width: '768px', + }, + attrs: { + src: getFrameSrc(frameUrl), + frameBorder: '0', + }, + }); + }, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } }; export default function renderObservability(elements) { diff --git a/lib/banzai/filter/inline_observability_filter.rb b/lib/banzai/filter/inline_observability_filter.rb index 334c04f2b59..883965ccae0 100644 --- a/lib/banzai/filter/inline_observability_filter.rb +++ b/lib/banzai/filter/inline_observability_filter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require 'uri' module Banzai module Filter @@ -15,7 +16,8 @@ module Banzai doc.document.create_element( 'div', class: 'js-render-observability', - 'data-frame-url': url + 'data-frame-url': url, + 'data-observability-url': Gitlab::Observability.observability_url ) end @@ -28,8 +30,15 @@ module Banzai # obtained from the target link def element_to_embed(node) url = node['href'] - - create_element(url) + uri = URI.parse(url) + observability_uri = URI.parse(Gitlab::Observability.observability_url) + + if uri.scheme == observability_uri.scheme && + uri.port == observability_uri.port && + uri.host.casecmp?(observability_uri.host) && + uri.path.downcase.exclude?("auth/start") + create_element(url) + end end private diff --git a/spec/frontend/behaviors/markdown/render_observability_spec.js b/spec/frontend/behaviors/markdown/render_observability_spec.js index c87d11742dc..03a0cb2fcc2 100644 --- a/spec/frontend/behaviors/markdown/render_observability_spec.js +++ b/spec/frontend/behaviors/markdown/render_observability_spec.js @@ -16,7 +16,7 @@ describe('Observability iframe renderer', () => { }); it('renders an observability iframe', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; expect(findObservabilityIframes()).toHaveLength(0); @@ -26,7 +26,7 @@ describe('Observability iframe renderer', () => { }); it('renders iframe with dark param when GL has dark theme', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true); expect(findObservabilityIframes('dark')).toHaveLength(0); @@ -35,4 +35,12 @@ describe('Observability iframe renderer', () => { expect(findObservabilityIframes('dark')).toHaveLength(1); }); + + it('does not render if url is different from observability url', () => { + document.body.innerHTML = `
`; + + renderEmbeddedObservability(); + + expect(findObservabilityIframes()).toHaveLength(0); + }); }); diff --git a/spec/lib/banzai/filter/inline_observability_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_filter_spec.rb index fb1ba46e76c..69a9dc96c2c 100644 --- a/spec/lib/banzai/filter/inline_observability_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_observability_filter_spec.rb @@ -34,6 +34,58 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do end end + context 'when the document contains an embeddable observability link with redirect' do + let(:url) { 'https://observe.gitlab.com@example.com/12345' } + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + + context 'when the document contains an embeddable observability link with different port' do + let(:url) { 'https://observe.gitlab.com:3000/12345' } + let(:observe_url) { 'https://observe.gitlab.com:3001' } + + before do + stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) + end + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + + context 'when the document contains an embeddable observability link with auth/start' do + let(:url) { 'https://observe.gitlab.com/auth/start' } + let(:observe_url) { 'https://observe.gitlab.com' } + + before do + stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) + end + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + context 'when feature flag is disabled' do let(:url) { 'https://observe.gitlab.com/12345' } -- cgit v1.2.1 From 49f77b37b621ef7d909ed92c76423c9835a29ac7 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 29 Mar 2023 23:57:06 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- app/assets/javascripts/pages/projects/project.js | 3 +- app/assets/javascripts/repository/index.js | 9 ++-- .../repository/utils/ref_switcher_utils.js | 30 ++++------- app/controllers/concerns/confirm_email_warning.rb | 14 ++--- app/controllers/projects/blob_controller.rb | 10 ---- app/controllers/projects/refs_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 9 ---- app/controllers/projects_controller.rb | 10 +--- app/policies/project_policy.rb | 1 + app/views/projects/tree/_tree_header.html.haml | 2 +- lib/extracts_ref.rb | 14 +---- locale/gitlab.pot | 3 -- .../concerns/confirm_email_warning_spec.rb | 34 +----------- spec/controllers/projects/blob_controller_spec.rb | 33 ++---------- .../projects/clusters_controller_spec.rb | 23 +------- .../environments/prometheus_api_controller_spec.rb | 23 +------- spec/controllers/projects/refs_controller_spec.rb | 4 +- spec/controllers/projects/tree_controller_spec.rb | 37 +++---------- spec/controllers/projects_controller_spec.rb | 63 ---------------------- spec/features/admin/users/user_spec.rb | 30 ----------- .../repository/utils/ref_switcher_utils_spec.js | 31 ++--------- spec/policies/project_policy_spec.rb | 46 ++-------------- 22 files changed, 47 insertions(+), 384 deletions(-) diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 27659348dcc..4c9eb830ff6 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -118,10 +118,9 @@ export default class Project { const urlParams = { [fieldName]: ref }; if (params.group === BRANCH_GROUP_NAME) { urlParams.ref_type = BRANCH_REF_TYPE; - } else if (params.group === TAG_GROUP_NAME) { + } else { urlParams.ref_type = TAG_REF_TYPE; } - link.href = mergeUrlParams(urlParams, linkTarget); } diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 0f6b159ffc5..e5d22f50d72 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; -import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; +import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import initWebIdeLink from '~/pages/projects/shared/web_ide_link'; import PerformancePlugin from '~/performance/vue_performance_plugin'; @@ -119,7 +119,7 @@ export default function setupVueRepositoryList() { if (!refSwitcherEl) return false; - const { projectId, projectRootPath, refType } = refSwitcherEl.dataset; + const { projectId, projectRootPath } = refSwitcherEl.dataset; return new Vue({ el: refSwitcherEl, @@ -127,12 +127,11 @@ export default function setupVueRepositoryList() { return createElement(RefSelector, { props: { projectId, - value: refType ? joinPaths('refs', refType, ref) : ref, - useSymbolicRefNames: true, + value: ref, }, on: { input(selectedRef) { - visitUrl(generateRefDestinationPath(projectRootPath, ref, selectedRef)); + visitUrl(generateRefDestinationPath(projectRootPath, selectedRef)); }, }, }); diff --git a/app/assets/javascripts/repository/utils/ref_switcher_utils.js b/app/assets/javascripts/repository/utils/ref_switcher_utils.js index fc0d521ad54..f296b5e9b4a 100644 --- a/app/assets/javascripts/repository/utils/ref_switcher_utils.js +++ b/app/assets/javascripts/repository/utils/ref_switcher_utils.js @@ -15,30 +15,22 @@ const NAMESPACE_TARGET_REGEX = /(\/-\/(blob|tree))\/.*?\/(.*)/; * @param {string} projectRootPath - The root path for a project. * @param {string} selectedRef - The selected ref from the ref dropdown. */ - -export function generateRefDestinationPath(projectRootPath, ref, selectedRef) { - const url = new URL(window.location.href); - const currentPath = url.pathname; - let refType = null; +export function generateRefDestinationPath(projectRootPath, selectedRef) { + const currentPath = window.location.pathname; + const encodedHash = '%23'; let namespace = '/-/tree'; let target; - let actualRef = selectedRef; - - const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/); - if (matches) { - [, refType, actualRef] = matches; - } - if (refType) { - url.searchParams.set('ref_type', refType); - } else { - url.searchParams.delete('ref_type'); - } - const match = NAMESPACE_TARGET_REGEX.exec(currentPath); if (match) { [, namespace, , target] = match; } - url.pathname = joinPaths(projectRootPath, namespace, actualRef, target); - return url.toString(); + const destinationPath = joinPaths( + projectRootPath, + namespace, + encodeURI(selectedRef).replace(/#/g, encodedHash), + target, + ); + + return `${destinationPath}${window.location.hash}`; } diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index 2711c823275..ec5140bf223 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module ConfirmEmailWarning - include Gitlab::Utils::StrongMemoize extend ActiveSupport::Concern included do @@ -18,9 +17,11 @@ module ConfirmEmailWarning return unless current_user return if current_user.confirmed? + email = current_user.unconfirmed_email || current_user.email + flash.now[:warning] = format( confirm_warning_message, - email: email_to_display, + email: email, resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post), update_link: view_context.link_to(_('Update it'), profile_path) ).html_safe @@ -28,16 +29,7 @@ module ConfirmEmailWarning private - def email - current_user.unconfirmed_email || current_user.email - end - strong_memoize_attr :email - def confirm_warning_message _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.") end - - def email_to_display - html_escape(email) - end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index dd2f980e880..4eda76f4f21 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -31,7 +31,6 @@ class Projects::BlobController < Projects::ApplicationController before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy] before_action :commit, except: [:new, :create] - before_action :check_for_ambiguous_ref, only: [:show] before_action :blob, except: [:new, :create] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] @@ -146,15 +145,6 @@ class Projects::BlobController < Projects::ApplicationController end end - def check_for_ambiguous_ref - @ref_type = ref_type - - if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) - branch = @project.repository.find_branch(@ref) - redirect_to project_blob_path(@project, File.join(branch.target, @path)) - end - end - def commit @commit ||= @repository.commit(@ref) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 0b52d03ab95..8ac6d872aae 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -22,7 +22,7 @@ class Projects::RefsController < Projects::ApplicationController when "tree" project_tree_path(@project, @id) when "blob" - project_blob_path(@project, @id, ref_type: ref_type) + project_blob_path(@project, @id) when "graph" if Feature.enabled?(:use_ref_type_parameter, @project) project_network_path(@project, @id, ref_type: ref_type) diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 1f7912e15df..737a6290431 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -28,15 +28,6 @@ class Projects::TreeController < Projects::ApplicationController def show return render_404 unless @commit - @ref_type = ref_type - if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) - branch = @project.repository.find_branch(@ref) - if branch - redirect_to project_tree_path(@project, branch.target) - return - end - end - if tree.entries.empty? if @repository.blob_at(@commit.id, @path) redirect_to project_blob_path(@project, File.join(@ref, @path)) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ed7c163c108..ee2c268ff33 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -172,19 +172,11 @@ class ProjectsController < Projects::ApplicationController flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } end - if ambiguous_ref?(@project, @ref) - branch = @project.repository.find_branch(@ref) - - # The files view would render a ref other than the default branch - # This redirect can be removed once the view is fixed - redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref")) - return - end - respond_to do |format| format.html do @notification_setting = current_user.notification_settings_for(@project) if current_user @project = @project.present(current_user: current_user) + render_landing_page end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index cd05cc1763d..b85a57f81cd 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -404,6 +404,7 @@ class ProjectPolicy < BasePolicy end rule { can?(:metrics_dashboard) }.policy do + enable :read_prometheus enable :read_deployment end diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 543cbc4e4e4..fd807350245 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -2,7 +2,7 @@ .tree-ref-container.gl-display-flex.mb-2.mb-md-0 .tree-ref-holder - #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } } + #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } } #js-repo-breadcrumb{ data: breadcrumb_data_attributes } diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb index 23f93ad1317..f22996df0a5 100644 --- a/lib/extracts_ref.rb +++ b/lib/extracts_ref.rb @@ -5,8 +5,7 @@ # Can be extended for different types of repository object, e.g. Project or Snippet module ExtractsRef InvalidPathError = Class.new(StandardError) - BRANCH_REF_TYPE = 'heads' - TAG_REF_TYPE = 'tags' + # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # @@ -92,7 +91,7 @@ module ExtractsRef def ref_type return unless params[:ref_type].present? - params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE + params[:ref_type] == 'tags' ? 'tags' : 'heads' end private @@ -155,13 +154,4 @@ module ExtractsRef def repository_container raise NotImplementedError end - - def ambiguous_ref?(project, ref) - return true if project.repository.ambiguous_ref?(ref) - - return false unless ref&.starts_with?('refs/') - - unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '') - project.repository.commit(unprefixed_ref).present? - end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f8626094af5..ae06cb3ae7a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42005,9 +42005,6 @@ msgstr "" msgid "The default branch for this project has been changed. Please update your bookmarks." msgstr "" -msgid "The default branch of this project clashes with another ref" -msgstr "" - msgid "The dependency list details information about the components used within your project." msgstr "" diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index b8a4b94aa66..334c156e1ae 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ConfirmEmailWarning, feature_category: :system_access do +RSpec.describe ConfirmEmailWarning do before do stub_feature_flags(soft_email_confirmation: true) end @@ -82,38 +82,6 @@ RSpec.describe ConfirmEmailWarning, feature_category: :system_access do it { is_expected.to set_confirm_warning_for(user.email) } end end - - context 'when user is being impersonated' do - let(:impersonator) { create(:admin) } - - before do - allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id }) - - get :index - end - - it { is_expected.to set_confirm_warning_for(user.email) } - - context 'when impersonated user email has html in their email' do - let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com") } - - it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } - end - end - - context 'when user is not being impersonated' do - before do - get :index - end - - it { is_expected.to set_confirm_warning_for(user.email) } - - context 'when user email has html in their email' do - let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com") } - - it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } - end - end end end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index ec92d92e2a9..887a5ba598f 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -2,16 +2,15 @@ require 'spec_helper' -RSpec.describe Projects::BlobController, feature_category: :source_code_management do +RSpec.describe Projects::BlobController do include ProjectForksHelper let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } describe "GET show" do - let(:params) { { namespace_id: project.namespace, project_id: project, id: id } } - let(:request) do - get(:show, params: params) + def request + get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) end render_views @@ -19,32 +18,8 @@ RSpec.describe Projects::BlobController, feature_category: :source_code_manageme context 'with file path' do before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) - project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) - request - end - - context 'when the ref is ambiguous' do - let(:ref) { 'ambiguous_ref' } - let(:path) { 'README.md' } - let(:id) { "#{ref}/#{path}" } - let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } - context 'and explicitly requesting a branch' do - let(:ref_type) { 'heads' } - - it 'redirects to blob#show with sha for the branch' do - expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}")) - end - end - - context 'and explicitly requesting a tag' do - let(:ref_type) { 'tags' } - - it 'responds with success' do - expect(response).to be_ok - end - end + request end context "valid branch, valid file" do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 64c77726527..894f0f8354d 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController do include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - let_it_be_with_reload(:project) { create(:project) } + let_it_be(:project) { create(:project) } let(:user) { create(:user) } @@ -140,27 +140,6 @@ RSpec.describe Projects::ClustersController do expect(response).to redirect_to(new_user_session_path) end end - - context 'with a public project' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - end - - context 'with guest user' do - let(:prometheus_body) { nil } - - before do - project.add_guest(user) - end - - it 'returns 404' do - get :prometheus_proxy, params: prometheus_proxy_params - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end end end diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index 6b0c164e432..68d50cf19f0 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::Environments::PrometheusApiController do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project) } + let_it_be(:project) { create(:project) } let_it_be(:proxyable) { create(:environment, project: project) } before do @@ -70,27 +70,6 @@ RSpec.describe Projects::Environments::PrometheusApiController do expect(response).to redirect_to(new_user_session_path) end end - - context 'with a public project' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - end - - context 'with guest user' do - let(:prometheus_body) { nil } - - before do - project.add_guest(user) - end - - it 'returns 404' do - get :prometheus_proxy, params: prometheus_proxy_params - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end end end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index e36eb1207c1..a7a8361ae20 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } + 'blob' | 'heads' | lazy { project_blob_path(project, id) } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id) } 'graphs' | nil | lazy { project_graph_path(project, id) } @@ -60,7 +60,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } + 'blob' | 'heads' | lazy { project_blob_path(project, id) } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') } 'graphs' | nil | lazy { project_graph_path(project, id) } diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 37149e1d3ca..9bc3065b6da 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TreeController, feature_category: :source_code_management do +RSpec.describe Projects::TreeController do let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } let(:user) { create(:user) } @@ -15,41 +15,18 @@ RSpec.describe Projects::TreeController, feature_category: :source_code_manageme end describe "GET show" do - let(:params) do - { - namespace_id: project.namespace.to_param, project_id: project, id: id - } - end - # Make sure any errors accessing the tree in our views bubble up to this spec render_views before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) - project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) - get :show, params: params - end - - context 'when the ref is ambiguous' do - let(:id) { 'ambiguous_ref' } - let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } - - context 'and explicitly requesting a branch' do - let(:ref_type) { 'heads' } - - it 'redirects to blob#show with sha for the branch' do - expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id)) - end - end - - context 'and explicitly requesting a tag' do - let(:ref_type) { 'tags' } - it 'responds with success' do - expect(response).to be_ok - end - end + get(:show, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: id + }) end context "valid branch, no path" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 8c81a1414ce..bc58eaa1d6f 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -163,69 +163,6 @@ RSpec.describe ProjectsController do expect(assigns(:notification_setting).level).to eq("watch") end end - - context 'when there is a tag with the same name as the default branch' do - let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) } - let(:tree_with_default_branch) do - branch = tagged_project.repository.find_branch(tagged_project.default_branch) - project_tree_path(tagged_project, branch.target) - end - - before do - tagged_project.repository.create_file( - tagged_project.creator, - 'file_for_tag', - 'content for file', - message: "Automatically created file", - branch_name: 'branch-to-tag' - ) - - tagged_project.repository.add_tag( - tagged_project.creator, - tagged_project.default_branch, # tag name - 'branch-to-tag' # target - ) - end - - it 'redirects to tree view for the default branch' do - get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project } - expect(response).to redirect_to(tree_with_default_branch) - end - end - - context 'when the default branch name can resolve to another ref' do - let!(:project_with_default_branch) do - create(:project, :public, :custom_repo, files: ['somefile']).tap do |p| - p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master') - p.change_head("refs/heads/#{other_ref}") - end.reload - end - - let(:other_ref) { 'branch-name' } - - context 'but there is no other ref' do - it 'responds with ok' do - get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } - expect(response).to be_ok - end - end - - context 'and that other ref exists' do - let(:tree_with_default_branch) do - branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch) - project_tree_path(project_with_default_branch, branch.target) - end - - before do - project_with_default_branch.repository.create_branch(other_ref, 'master') - end - - it 'redirects to tree view for the default branch' do - get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } - expect(response).to redirect_to(tree_with_default_branch) - end - end - end end describe "when project repository is disabled" do diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 66129617220..1552d4e6187 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -271,36 +271,6 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do icon = first('[data-testid="incognito-icon"]') expect(icon).not_to be nil end - - context 'when viewing the confirm email warning', :js do - let_it_be(:another_user) { create(:user, :unconfirmed) } - - let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') } - let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } } - - context 'with an email that does not contain HTML' do - before do - subject - end - - it 'displays the warning alert including the email' do - expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify") - end - end - - context 'with an email that contains HTML' do - let(:malicious_email) { "malicious@test.com" } - let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) } - - before do - subject - end - - it 'displays the impersonation alert, excludes email, and disables links' do - expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify") - end - end - end end context 'ending impersonation' do diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js index 220dbf17398..4d0250fffbf 100644 --- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js +++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js @@ -1,6 +1,5 @@ import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { TEST_HOST } from 'spec/test_constants'; import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data'; const projectRootPath = 'root/Project1'; @@ -17,38 +16,14 @@ describe('generateRefDestinationPath', () => { ${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`} - `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { + `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { setWindowLocation(currentPath); - expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe( - `${TEST_HOST}/${result}`, - ); - }); - - describe('when using symbolic ref names', () => { - it.each` - currentPath | nextRef | result - ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`} - ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} - ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} - ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} - ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} - ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`} - `( - 'generates the correct destination path for $currentPath with ref type when it can be extracted', - ({ currentPath, result, nextRef }) => { - setWindowLocation(currentPath); - expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe( - `${TEST_HOST}/${result}`, - ); - }, - ); + expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result); }); it('encodes the selected ref', () => { const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`; - expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe( - `${TEST_HOST}/${result}`, - ); + expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result); }); }); diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 52d1e70d7a0..2a4a169e5c5 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -697,39 +697,6 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio end end - describe 'read_prometheus', feature_category: :metrics do - using RSpec::Parameterized::TableSyntax - - before do - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - end - - let(:policy) { :read_prometheus } - - where(:project_visibility, :role, :allowed) do - :public | :anonymous | false - :public | :guest | false - :public | :reporter | true - :internal | :anonymous | false - :internal | :guest | false - :internal | :reporter | true - :private | :anonymous | false - :private | :guest | false - :private | :reporter | true - end - - with_them do - let(:current_user) { public_send(role) } - let(:project) { public_send("#{project_visibility}_project") } - - if params[:allowed] - it { is_expected.to be_allowed(policy) } - else - it { is_expected.not_to be_allowed(policy) } - end - end - end - describe 'update_max_artifacts_size' do context 'when no user' do let(:current_user) { anonymous } @@ -1005,7 +972,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_allowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1015,7 +982,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_allowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) } @@ -1041,14 +1008,12 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1071,7 +1036,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_allowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1081,7 +1046,6 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end end end @@ -1104,14 +1068,12 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1130,14 +1092,12 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } end end end -- cgit v1.2.1 From 1507f8fd84ad211c4c2d93167ef21ce080eae006 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 29 Mar 2023 23:58:37 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- .../behaviors/markdown/render_observability.js | 45 +++++++------------ lib/banzai/filter/inline_observability_filter.rb | 15 ++----- .../markdown/render_observability_spec.js | 12 +---- .../filter/inline_observability_filter_spec.rb | 52 ---------------------- 4 files changed, 21 insertions(+), 103 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_observability.js b/app/assets/javascripts/behaviors/markdown/render_observability.js index d5d46c10efd..704d85cf22e 100644 --- a/app/assets/javascripts/behaviors/markdown/render_observability.js +++ b/app/assets/javascripts/behaviors/markdown/render_observability.js @@ -7,36 +7,23 @@ export function getFrameSrc(url) { } const mountVueComponent = (element) => { - const { frameUrl, observabilityUrl } = element.dataset; + const url = [element.dataset.frameUrl]; - try { - if ( - !observabilityUrl || - !frameUrl || - new URL(frameUrl)?.host !== new URL(observabilityUrl).host - ) - return; - - // eslint-disable-next-line no-new - new Vue({ - el: element, - render(h) { - return h('iframe', { - style: { - height: '366px', - width: '768px', - }, - attrs: { - src: getFrameSrc(frameUrl), - frameBorder: '0', - }, - }); - }, - }); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - } + return new Vue({ + el: element, + render(h) { + return h('iframe', { + style: { + height: '366px', + width: '768px', + }, + attrs: { + src: getFrameSrc(url), + frameBorder: '0', + }, + }); + }, + }); }; export default function renderObservability(elements) { diff --git a/lib/banzai/filter/inline_observability_filter.rb b/lib/banzai/filter/inline_observability_filter.rb index 883965ccae0..334c04f2b59 100644 --- a/lib/banzai/filter/inline_observability_filter.rb +++ b/lib/banzai/filter/inline_observability_filter.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'uri' module Banzai module Filter @@ -16,8 +15,7 @@ module Banzai doc.document.create_element( 'div', class: 'js-render-observability', - 'data-frame-url': url, - 'data-observability-url': Gitlab::Observability.observability_url + 'data-frame-url': url ) end @@ -30,15 +28,8 @@ module Banzai # obtained from the target link def element_to_embed(node) url = node['href'] - uri = URI.parse(url) - observability_uri = URI.parse(Gitlab::Observability.observability_url) - - if uri.scheme == observability_uri.scheme && - uri.port == observability_uri.port && - uri.host.casecmp?(observability_uri.host) && - uri.path.downcase.exclude?("auth/start") - create_element(url) - end + + create_element(url) end private diff --git a/spec/frontend/behaviors/markdown/render_observability_spec.js b/spec/frontend/behaviors/markdown/render_observability_spec.js index 03a0cb2fcc2..c87d11742dc 100644 --- a/spec/frontend/behaviors/markdown/render_observability_spec.js +++ b/spec/frontend/behaviors/markdown/render_observability_spec.js @@ -16,7 +16,7 @@ describe('Observability iframe renderer', () => { }); it('renders an observability iframe', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; expect(findObservabilityIframes()).toHaveLength(0); @@ -26,7 +26,7 @@ describe('Observability iframe renderer', () => { }); it('renders iframe with dark param when GL has dark theme', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true); expect(findObservabilityIframes('dark')).toHaveLength(0); @@ -35,12 +35,4 @@ describe('Observability iframe renderer', () => { expect(findObservabilityIframes('dark')).toHaveLength(1); }); - - it('does not render if url is different from observability url', () => { - document.body.innerHTML = `
`; - - renderEmbeddedObservability(); - - expect(findObservabilityIframes()).toHaveLength(0); - }); }); diff --git a/spec/lib/banzai/filter/inline_observability_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_filter_spec.rb index 69a9dc96c2c..fb1ba46e76c 100644 --- a/spec/lib/banzai/filter/inline_observability_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_observability_filter_spec.rb @@ -34,58 +34,6 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do end end - context 'when the document contains an embeddable observability link with redirect' do - let(:url) { 'https://observe.gitlab.com@example.com/12345' } - - it 'leaves the original link unchanged' do - expect(doc.at_css('a').to_s).to eq(input) - end - - it 'does not append an observability charts placeholder' do - node = doc.at_css('.js-render-observability') - - expect(node).not_to be_present - end - end - - context 'when the document contains an embeddable observability link with different port' do - let(:url) { 'https://observe.gitlab.com:3000/12345' } - let(:observe_url) { 'https://observe.gitlab.com:3001' } - - before do - stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) - end - - it 'leaves the original link unchanged' do - expect(doc.at_css('a').to_s).to eq(input) - end - - it 'does not append an observability charts placeholder' do - node = doc.at_css('.js-render-observability') - - expect(node).not_to be_present - end - end - - context 'when the document contains an embeddable observability link with auth/start' do - let(:url) { 'https://observe.gitlab.com/auth/start' } - let(:observe_url) { 'https://observe.gitlab.com' } - - before do - stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) - end - - it 'leaves the original link unchanged' do - expect(doc.at_css('a').to_s).to eq(input) - end - - it 'does not append an observability charts placeholder' do - node = doc.at_css('.js-render-observability') - - expect(node).not_to be_present - end - end - context 'when feature flag is disabled' do let(:url) { 'https://observe.gitlab.com/12345' } -- cgit v1.2.1 From 78f7a11fbc5d29dcb1ae882ae0331407b9872d6a Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 30 Mar 2023 00:00:25 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- .../behaviors/markdown/render_observability.js | 45 ++++++++++------ app/assets/javascripts/pages/projects/project.js | 3 +- app/assets/javascripts/repository/index.js | 9 ++-- .../repository/utils/ref_switcher_utils.js | 30 +++++++---- app/controllers/concerns/confirm_email_warning.rb | 14 +++-- app/controllers/projects/blob_controller.rb | 10 ++++ app/controllers/projects/refs_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 9 ++++ app/controllers/projects_controller.rb | 10 +++- app/policies/project_policy.rb | 1 - app/views/projects/tree/_tree_header.html.haml | 2 +- lib/banzai/filter/inline_observability_filter.rb | 15 ++++-- lib/extracts_ref.rb | 14 ++++- locale/gitlab.pot | 3 ++ .../concerns/confirm_email_warning_spec.rb | 34 +++++++++++- spec/controllers/projects/blob_controller_spec.rb | 33 ++++++++++-- .../projects/clusters_controller_spec.rb | 23 +++++++- .../environments/prometheus_api_controller_spec.rb | 23 +++++++- spec/controllers/projects/refs_controller_spec.rb | 4 +- spec/controllers/projects/tree_controller_spec.rb | 37 ++++++++++--- spec/controllers/projects_controller_spec.rb | 63 ++++++++++++++++++++++ spec/features/admin/users/user_spec.rb | 30 +++++++++++ .../markdown/render_observability_spec.js | 12 ++++- .../repository/utils/ref_switcher_utils_spec.js | 31 +++++++++-- .../filter/inline_observability_filter_spec.rb | 52 ++++++++++++++++++ spec/policies/project_policy_spec.rb | 46 ++++++++++++++-- 26 files changed, 487 insertions(+), 68 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_observability.js b/app/assets/javascripts/behaviors/markdown/render_observability.js index 704d85cf22e..d5d46c10efd 100644 --- a/app/assets/javascripts/behaviors/markdown/render_observability.js +++ b/app/assets/javascripts/behaviors/markdown/render_observability.js @@ -7,23 +7,36 @@ export function getFrameSrc(url) { } const mountVueComponent = (element) => { - const url = [element.dataset.frameUrl]; + const { frameUrl, observabilityUrl } = element.dataset; - return new Vue({ - el: element, - render(h) { - return h('iframe', { - style: { - height: '366px', - width: '768px', - }, - attrs: { - src: getFrameSrc(url), - frameBorder: '0', - }, - }); - }, - }); + try { + if ( + !observabilityUrl || + !frameUrl || + new URL(frameUrl)?.host !== new URL(observabilityUrl).host + ) + return; + + // eslint-disable-next-line no-new + new Vue({ + el: element, + render(h) { + return h('iframe', { + style: { + height: '366px', + width: '768px', + }, + attrs: { + src: getFrameSrc(frameUrl), + frameBorder: '0', + }, + }); + }, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } }; export default function renderObservability(elements) { diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 4c9eb830ff6..27659348dcc 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -118,9 +118,10 @@ export default class Project { const urlParams = { [fieldName]: ref }; if (params.group === BRANCH_GROUP_NAME) { urlParams.ref_type = BRANCH_REF_TYPE; - } else { + } else if (params.group === TAG_GROUP_NAME) { urlParams.ref_type = TAG_REF_TYPE; } + link.href = mergeUrlParams(urlParams, linkTarget); } diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index e5d22f50d72..0f6b159ffc5 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import Vuex from 'vuex'; import { parseBoolean } from '~/lib/utils/common_utils'; -import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; +import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import initWebIdeLink from '~/pages/projects/shared/web_ide_link'; import PerformancePlugin from '~/performance/vue_performance_plugin'; @@ -119,7 +119,7 @@ export default function setupVueRepositoryList() { if (!refSwitcherEl) return false; - const { projectId, projectRootPath } = refSwitcherEl.dataset; + const { projectId, projectRootPath, refType } = refSwitcherEl.dataset; return new Vue({ el: refSwitcherEl, @@ -127,11 +127,12 @@ export default function setupVueRepositoryList() { return createElement(RefSelector, { props: { projectId, - value: ref, + value: refType ? joinPaths('refs', refType, ref) : ref, + useSymbolicRefNames: true, }, on: { input(selectedRef) { - visitUrl(generateRefDestinationPath(projectRootPath, selectedRef)); + visitUrl(generateRefDestinationPath(projectRootPath, ref, selectedRef)); }, }, }); diff --git a/app/assets/javascripts/repository/utils/ref_switcher_utils.js b/app/assets/javascripts/repository/utils/ref_switcher_utils.js index f296b5e9b4a..fc0d521ad54 100644 --- a/app/assets/javascripts/repository/utils/ref_switcher_utils.js +++ b/app/assets/javascripts/repository/utils/ref_switcher_utils.js @@ -15,22 +15,30 @@ const NAMESPACE_TARGET_REGEX = /(\/-\/(blob|tree))\/.*?\/(.*)/; * @param {string} projectRootPath - The root path for a project. * @param {string} selectedRef - The selected ref from the ref dropdown. */ -export function generateRefDestinationPath(projectRootPath, selectedRef) { - const currentPath = window.location.pathname; - const encodedHash = '%23'; + +export function generateRefDestinationPath(projectRootPath, ref, selectedRef) { + const url = new URL(window.location.href); + const currentPath = url.pathname; + let refType = null; let namespace = '/-/tree'; let target; + let actualRef = selectedRef; + + const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/); + if (matches) { + [, refType, actualRef] = matches; + } + if (refType) { + url.searchParams.set('ref_type', refType); + } else { + url.searchParams.delete('ref_type'); + } + const match = NAMESPACE_TARGET_REGEX.exec(currentPath); if (match) { [, namespace, , target] = match; } + url.pathname = joinPaths(projectRootPath, namespace, actualRef, target); - const destinationPath = joinPaths( - projectRootPath, - namespace, - encodeURI(selectedRef).replace(/#/g, encodedHash), - target, - ); - - return `${destinationPath}${window.location.hash}`; + return url.toString(); } diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index ec5140bf223..2711c823275 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module ConfirmEmailWarning + include Gitlab::Utils::StrongMemoize extend ActiveSupport::Concern included do @@ -17,11 +18,9 @@ module ConfirmEmailWarning return unless current_user return if current_user.confirmed? - email = current_user.unconfirmed_email || current_user.email - flash.now[:warning] = format( confirm_warning_message, - email: email, + email: email_to_display, resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post), update_link: view_context.link_to(_('Update it'), profile_path) ).html_safe @@ -29,7 +28,16 @@ module ConfirmEmailWarning private + def email + current_user.unconfirmed_email || current_user.email + end + strong_memoize_attr :email + def confirm_warning_message _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.") end + + def email_to_display + html_escape(email) + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 4eda76f4f21..dd2f980e880 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy] before_action :commit, except: [:new, :create] + before_action :check_for_ambiguous_ref, only: [:show] before_action :blob, except: [:new, :create] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] @@ -145,6 +146,15 @@ class Projects::BlobController < Projects::ApplicationController end end + def check_for_ambiguous_ref + @ref_type = ref_type + + if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + redirect_to project_blob_path(@project, File.join(branch.target, @path)) + end + end + def commit @commit ||= @repository.commit(@ref) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 8ac6d872aae..0b52d03ab95 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -22,7 +22,7 @@ class Projects::RefsController < Projects::ApplicationController when "tree" project_tree_path(@project, @id) when "blob" - project_blob_path(@project, @id) + project_blob_path(@project, @id, ref_type: ref_type) when "graph" if Feature.enabled?(:use_ref_type_parameter, @project) project_network_path(@project, @id, ref_type: ref_type) diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 737a6290431..1f7912e15df 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -28,6 +28,15 @@ class Projects::TreeController < Projects::ApplicationController def show return render_404 unless @commit + @ref_type = ref_type + if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + if branch + redirect_to project_tree_path(@project, branch.target) + return + end + end + if tree.entries.empty? if @repository.blob_at(@commit.id, @path) redirect_to project_blob_path(@project, File.join(@ref, @path)) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ee2c268ff33..ed7c163c108 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -172,11 +172,19 @@ class ProjectsController < Projects::ApplicationController flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } end + if ambiguous_ref?(@project, @ref) + branch = @project.repository.find_branch(@ref) + + # The files view would render a ref other than the default branch + # This redirect can be removed once the view is fixed + redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref")) + return + end + respond_to do |format| format.html do @notification_setting = current_user.notification_settings_for(@project) if current_user @project = @project.present(current_user: current_user) - render_landing_page end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index b85a57f81cd..cd05cc1763d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -404,7 +404,6 @@ class ProjectPolicy < BasePolicy end rule { can?(:metrics_dashboard) }.policy do - enable :read_prometheus enable :read_deployment end diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index fd807350245..543cbc4e4e4 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -2,7 +2,7 @@ .tree-ref-container.gl-display-flex.mb-2.mb-md-0 .tree-ref-holder - #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } } + #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } } #js-repo-breadcrumb{ data: breadcrumb_data_attributes } diff --git a/lib/banzai/filter/inline_observability_filter.rb b/lib/banzai/filter/inline_observability_filter.rb index 334c04f2b59..883965ccae0 100644 --- a/lib/banzai/filter/inline_observability_filter.rb +++ b/lib/banzai/filter/inline_observability_filter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require 'uri' module Banzai module Filter @@ -15,7 +16,8 @@ module Banzai doc.document.create_element( 'div', class: 'js-render-observability', - 'data-frame-url': url + 'data-frame-url': url, + 'data-observability-url': Gitlab::Observability.observability_url ) end @@ -28,8 +30,15 @@ module Banzai # obtained from the target link def element_to_embed(node) url = node['href'] - - create_element(url) + uri = URI.parse(url) + observability_uri = URI.parse(Gitlab::Observability.observability_url) + + if uri.scheme == observability_uri.scheme && + uri.port == observability_uri.port && + uri.host.casecmp?(observability_uri.host) && + uri.path.downcase.exclude?("auth/start") + create_element(url) + end end private diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb index f22996df0a5..23f93ad1317 100644 --- a/lib/extracts_ref.rb +++ b/lib/extracts_ref.rb @@ -5,7 +5,8 @@ # Can be extended for different types of repository object, e.g. Project or Snippet module ExtractsRef InvalidPathError = Class.new(StandardError) - + BRANCH_REF_TYPE = 'heads' + TAG_REF_TYPE = 'tags' # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # @@ -91,7 +92,7 @@ module ExtractsRef def ref_type return unless params[:ref_type].present? - params[:ref_type] == 'tags' ? 'tags' : 'heads' + params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE end private @@ -154,4 +155,13 @@ module ExtractsRef def repository_container raise NotImplementedError end + + def ambiguous_ref?(project, ref) + return true if project.repository.ambiguous_ref?(ref) + + return false unless ref&.starts_with?('refs/') + + unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '') + project.repository.commit(unprefixed_ref).present? + end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ae06cb3ae7a..f8626094af5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42005,6 +42005,9 @@ msgstr "" msgid "The default branch for this project has been changed. Please update your bookmarks." msgstr "" +msgid "The default branch of this project clashes with another ref" +msgstr "" + msgid "The dependency list details information about the components used within your project." msgstr "" diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index 334c156e1ae..b8a4b94aa66 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ConfirmEmailWarning do +RSpec.describe ConfirmEmailWarning, feature_category: :system_access do before do stub_feature_flags(soft_email_confirmation: true) end @@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do it { is_expected.to set_confirm_warning_for(user.email) } end end + + context 'when user is being impersonated' do + let(:impersonator) { create(:admin) } + + before do + allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id }) + + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when impersonated user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end + + context 'when user is not being impersonated' do + before do + get :index + end + + it { is_expected.to set_confirm_warning_for(user.email) } + + context 'when user email has html in their email' do + let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com") } + + it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") } + end + end end end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 887a5ba598f..ec92d92e2a9 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -2,15 +2,16 @@ require 'spec_helper' -RSpec.describe Projects::BlobController do +RSpec.describe Projects::BlobController, feature_category: :source_code_management do include ProjectForksHelper let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } describe "GET show" do - def request - get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) + let(:params) { { namespace_id: project.namespace, project_id: project, id: id } } + let(:request) do + get(:show, params: params) end render_views @@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do context 'with file path' do before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) request end + context 'when the ref is ambiguous' do + let(:ref) { 'ambiguous_ref' } + let(:path) { 'README.md' } + let(:id) { "#{ref}/#{path}" } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}")) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end + end + context "valid branch, valid file" do let(:id) { 'master/README.md' } diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 894f0f8354d..64c77726527 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController do include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let(:user) { create(:user) } @@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController do expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index 68d50cf19f0..6b0c164e432 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Projects::Environments::PrometheusApiController do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } let_it_be(:proxyable) { create(:environment, project: project) } before do @@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do expect(response).to redirect_to(new_user_session_path) end end + + context 'with a public project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + context 'with guest user' do + let(:prometheus_body) { nil } + + before do + project.add_guest(user) + end + + it 'returns 404' do + get :prometheus_proxy, params: prometheus_proxy_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index a7a8361ae20..e36eb1207c1 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id) } + 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id) } 'graphs' | nil | lazy { project_graph_path(project, id) } @@ -60,7 +60,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme 'tree' | nil | lazy { project_tree_path(project, id) } 'tree' | 'heads' | lazy { project_tree_path(project, id) } 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id) } + 'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') } 'graph' | nil | lazy { project_network_path(project, id) } 'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') } 'graphs' | nil | lazy { project_graph_path(project, id) } diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 9bc3065b6da..37149e1d3ca 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TreeController do +RSpec.describe Projects::TreeController, feature_category: :source_code_management do let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) } let(:previous_default_branch) { nil } let(:user) { create(:user) } @@ -15,18 +15,41 @@ RSpec.describe Projects::TreeController do end describe "GET show" do + let(:params) do + { + namespace_id: project.namespace.to_param, project_id: project, id: id + } + end + # Make sure any errors accessing the tree in our views bubble up to this spec render_views before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id) + project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id) + get :show, params: params + end - get(:show, - params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: id - }) + context 'when the ref is ambiguous' do + let(:id) { 'ambiguous_ref' } + let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } } + + context 'and explicitly requesting a branch' do + let(:ref_type) { 'heads' } + + it 'redirects to blob#show with sha for the branch' do + expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id)) + end + end + + context 'and explicitly requesting a tag' do + let(:ref_type) { 'tags' } + + it 'responds with success' do + expect(response).to be_ok + end + end end context "valid branch, no path" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index bc58eaa1d6f..8c81a1414ce 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -163,6 +163,69 @@ RSpec.describe ProjectsController do expect(assigns(:notification_setting).level).to eq("watch") end end + + context 'when there is a tag with the same name as the default branch' do + let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) } + let(:tree_with_default_branch) do + branch = tagged_project.repository.find_branch(tagged_project.default_branch) + project_tree_path(tagged_project, branch.target) + end + + before do + tagged_project.repository.create_file( + tagged_project.creator, + 'file_for_tag', + 'content for file', + message: "Automatically created file", + branch_name: 'branch-to-tag' + ) + + tagged_project.repository.add_tag( + tagged_project.creator, + tagged_project.default_branch, # tag name + 'branch-to-tag' # target + ) + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project } + expect(response).to redirect_to(tree_with_default_branch) + end + end + + context 'when the default branch name can resolve to another ref' do + let!(:project_with_default_branch) do + create(:project, :public, :custom_repo, files: ['somefile']).tap do |p| + p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master') + p.change_head("refs/heads/#{other_ref}") + end.reload + end + + let(:other_ref) { 'branch-name' } + + context 'but there is no other ref' do + it 'responds with ok' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to be_ok + end + end + + context 'and that other ref exists' do + let(:tree_with_default_branch) do + branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch) + project_tree_path(project_with_default_branch, branch.target) + end + + before do + project_with_default_branch.repository.create_branch(other_ref, 'master') + end + + it 'redirects to tree view for the default branch' do + get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch } + expect(response).to redirect_to(tree_with_default_branch) + end + end + end end describe "when project repository is disabled" do diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 1552d4e6187..66129617220 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do icon = first('[data-testid="incognito-icon"]') expect(icon).not_to be nil end + + context 'when viewing the confirm email warning', :js do + let_it_be(:another_user) { create(:user, :unconfirmed) } + + let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') } + let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } } + + context 'with an email that does not contain HTML' do + before do + subject + end + + it 'displays the warning alert including the email' do + expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify") + end + end + + context 'with an email that contains HTML' do + let(:malicious_email) { "malicious@test.com" } + let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) } + + before do + subject + end + + it 'displays the impersonation alert, excludes email, and disables links' do + expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify") + end + end + end end context 'ending impersonation' do diff --git a/spec/frontend/behaviors/markdown/render_observability_spec.js b/spec/frontend/behaviors/markdown/render_observability_spec.js index c87d11742dc..03a0cb2fcc2 100644 --- a/spec/frontend/behaviors/markdown/render_observability_spec.js +++ b/spec/frontend/behaviors/markdown/render_observability_spec.js @@ -16,7 +16,7 @@ describe('Observability iframe renderer', () => { }); it('renders an observability iframe', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; expect(findObservabilityIframes()).toHaveLength(0); @@ -26,7 +26,7 @@ describe('Observability iframe renderer', () => { }); it('renders iframe with dark param when GL has dark theme', () => { - document.body.innerHTML = `
`; + document.body.innerHTML = `
`; jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true); expect(findObservabilityIframes('dark')).toHaveLength(0); @@ -35,4 +35,12 @@ describe('Observability iframe renderer', () => { expect(findObservabilityIframes('dark')).toHaveLength(1); }); + + it('does not render if url is different from observability url', () => { + document.body.innerHTML = `
`; + + renderEmbeddedObservability(); + + expect(findObservabilityIframes()).toHaveLength(0); + }); }); diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js index 4d0250fffbf..220dbf17398 100644 --- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js +++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js @@ -1,5 +1,6 @@ import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'spec/test_constants'; import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data'; const projectRootPath = 'root/Project1'; @@ -16,14 +17,38 @@ describe('generateRefDestinationPath', () => { ${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`} ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`} - `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { + `('generates the correct destination path for $currentPath', ({ currentPath, result }) => { setWindowLocation(currentPath); - expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result); + expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }); + + describe('when using symbolic ref names', () => { + it.each` + currentPath | nextRef | result + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`} + ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`} + `( + 'generates the correct destination path for $currentPath with ref type when it can be extracted', + ({ currentPath, result, nextRef }) => { + setWindowLocation(currentPath); + expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe( + `${TEST_HOST}/${result}`, + ); + }, + ); }); it('encodes the selected ref', () => { const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`; - expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result); + expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe( + `${TEST_HOST}/${result}`, + ); }); }); diff --git a/spec/lib/banzai/filter/inline_observability_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_filter_spec.rb index fb1ba46e76c..69a9dc96c2c 100644 --- a/spec/lib/banzai/filter/inline_observability_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_observability_filter_spec.rb @@ -34,6 +34,58 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do end end + context 'when the document contains an embeddable observability link with redirect' do + let(:url) { 'https://observe.gitlab.com@example.com/12345' } + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + + context 'when the document contains an embeddable observability link with different port' do + let(:url) { 'https://observe.gitlab.com:3000/12345' } + let(:observe_url) { 'https://observe.gitlab.com:3001' } + + before do + stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) + end + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + + context 'when the document contains an embeddable observability link with auth/start' do + let(:url) { 'https://observe.gitlab.com/auth/start' } + let(:observe_url) { 'https://observe.gitlab.com' } + + before do + stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url) + end + + it 'leaves the original link unchanged' do + expect(doc.at_css('a').to_s).to eq(input) + end + + it 'does not append an observability charts placeholder' do + node = doc.at_css('.js-render-observability') + + expect(node).not_to be_present + end + end + context 'when feature flag is disabled' do let(:url) { 'https://observe.gitlab.com/12345' } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 2a4a169e5c5..52d1e70d7a0 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -697,6 +697,39 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio end end + describe 'read_prometheus', feature_category: :metrics do + using RSpec::Parameterized::TableSyntax + + before do + project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) + end + + let(:policy) { :read_prometheus } + + where(:project_visibility, :role, :allowed) do + :public | :anonymous | false + :public | :guest | false + :public | :reporter | true + :internal | :anonymous | false + :internal | :guest | false + :internal | :reporter | true + :private | :anonymous | false + :private | :guest | false + :private | :reporter | true + end + + with_them do + let(:current_user) { public_send(role) } + let(:project) { public_send("#{project_visibility}_project") } + + if params[:allowed] + it { is_expected.to be_allowed(policy) } + else + it { is_expected.not_to be_allowed(policy) } + end + end + end + describe 'update_max_artifacts_size' do context 'when no user' do let(:current_user) { anonymous } @@ -972,7 +1005,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -982,7 +1015,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) } @@ -1008,12 +1041,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1036,7 +1071,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_allowed(:read_prometheus) } + it { is_expected.to be_disallowed(:read_prometheus) } it { is_expected.to be_allowed(:read_deployment) } it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) } it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) } @@ -1046,6 +1081,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end @@ -1068,12 +1104,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end @@ -1092,12 +1130,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio let(:current_user) { guest } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } end end end -- cgit v1.2.1 From 9fee2c5a068e9439136ff362b89d25640c9e44a7 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 30 Mar 2023 11:21:44 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- lib/gitlab/url_sanitizer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 79e124a58f5..da078cc7b92 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -7,7 +7,7 @@ module Gitlab ALLOWED_SCHEMES = %w[http https ssh git].freeze ALLOWED_WEB_SCHEMES = %w[http https].freeze SCHEMIFIED_SCHEME = 'glschemelessuri' - SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://".freeze + SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://" # URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or # relative URLs. This section will match schemeless URIs with userinfo # e.g. user:pass@gitlab.com but will not match scp-style URIs e.g. @@ -25,7 +25,7 @@ module Gitlab (?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH})) #{URI::REGEXP::PATTERN::HOSTPORT} ) - }x + }x.freeze def self.sanitize(content) content.gsub(URI_REGEXP) do |url| -- cgit v1.2.1 From 6e8a7cffeee1c16045203e9c7679cd55f2f280b4 Mon Sep 17 00:00:00 2001 From: GitLab Release Tools Bot Date: Thu, 30 Mar 2023 13:53:22 +0000 Subject: Update VERSION files [ci skip] --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index da467831eb0..317374c81d4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -15.8.4 \ No newline at end of file +15.8.5 \ No newline at end of file -- cgit v1.2.1 From c57c04a970e6e1afcd0dbc287afe1006a103afd4 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 30 Mar 2023 13:55:22 +0000 Subject: Add latest changes from gitlab-org/security/gitlab@15-8-stable-ee --- CHANGELOG.md | 21 +++++++++++++++++++++ GITALY_SERVER_VERSION | 2 +- GITLAB_PAGES_VERSION | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6071648bf66..bf4ae0fdefa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 15.8.5 (2023-03-30) + +### Security (16 changes) + +- [Fix rubocop offenses in lib/gitlab/url_sanitizer.rb](gitlab-org/security/gitlab@ddc04cf7059e411e20033b95e1297381d64d4b22) ([merge request](gitlab-org/security/gitlab!3175)) +- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@a22ce3851128eb900dbabe9e38c07889967a2915) ([merge request](gitlab-org/security/gitlab!3032)) +- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@fad24ae9d8fa0e7bd9eff0c9e6914c8267451b4d) ([merge request](gitlab-org/security/gitlab!3134)) +- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@85be0fbfc98cdb774d68070479e35be22f6ba40a) ([merge request](gitlab-org/security/gitlab!3125)) +- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@2df2fa2dc4b9015d044d0ddc5d26e17e9e5f85c0) ([merge request](gitlab-org/security/gitlab!3164)) +- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@53f7f06843eea4d666d361f5a1d349bd1e3f4312) ([merge request](gitlab-org/security/gitlab!3085)) +- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@9fa9dbff463f6015ffaf8d082db3d41ae623763e) ([merge request](gitlab-org/security/gitlab!3141)) +- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@20d77d4d680d13f916fb69de0d79802753421c8f) ([merge request](gitlab-org/security/gitlab!3137)) +- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@0fed113756b27a3a078f87f29711b225e1ed4cce) ([merge request](gitlab-org/security/gitlab!3150)) +- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@2285088f37aca877b1dcd59c728cdf33171b30cb) ([merge request](gitlab-org/security/gitlab!3109)) +- [Check access to target project before looking for branch](gitlab-org/security/gitlab@37b8d855d87c88170322e6a6d4c285fee6c6cb64) ([merge request](gitlab-org/security/gitlab!3038)) +- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@66f8cc2eb13509397b980d53a4b67ca03d8903f7) ([merge request](gitlab-org/security/gitlab!3121)) +- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e1859de393b4794e1356d6318e56ede4b557c059) ([merge request](gitlab-org/security/gitlab!3112)) +- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@13f9c6231cea956f73355c5b5b820163f523e7d8) ([merge request](gitlab-org/security/gitlab!3100)) +- [Fix security report authorization](gitlab-org/security/gitlab@19baab85c7a5a64a09e3e4808e8550fc72e18323) ([merge request](gitlab-org/security/gitlab!3105)) +- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@be5491c5db05161e4b14d53900dd19b66848de48) ([merge request](gitlab-org/security/gitlab!3131)) + ## 15.8.4 (2023-03-02) ### Security (12 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index da467831eb0..317374c81d4 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -15.8.4 \ No newline at end of file +15.8.5 \ No newline at end of file diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index da467831eb0..317374c81d4 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -15.8.4 \ No newline at end of file +15.8.5 \ No newline at end of file -- cgit v1.2.1