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 5773737c41b..a4b3b83a855 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -110,9 +110,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 494e270a66c..95e0c94527b 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'; @@ -121,7 +121,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, @@ -129,7 +129,8 @@ export default function setupVueRepositoryList() { return createElement(RefSelector, { props: { projectId, - value: ref, + value: refType ? joinPaths('refs', refType, ref) : ref, + useSymbolicRefNames: true, }, on: { input(selectedRef) { diff --git a/app/assets/javascripts/repository/utils/ref_switcher_utils.js b/app/assets/javascripts/repository/utils/ref_switcher_utils.js index c62f7f709c4..bcad4a2c822 100644 --- a/app/assets/javascripts/repository/utils/ref_switcher_utils.js +++ b/app/assets/javascripts/repository/utils/ref_switcher_utils.js @@ -16,22 +16,29 @@ const getNamespaceTargetRegex = (ref) => new RegExp(`(/-/(blob|tree))/${ref}/(.* * @param {string} selectedRef - The selected ref from the ref dropdown. */ export function generateRefDestinationPath(projectRootPath, ref, selectedRef) { - const currentPath = window.location.pathname; - const encodedHash = '%23'; + 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 NAMESPACE_TARGET_REGEX = getNamespaceTargetRegex(ref); 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 59cea00e26b..b71af82535a 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 4c2bd2a9d42..f55fc2242a4 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" project_network_path(@project, @id, ref_type: ref_type) when "graphs" 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 71ad747b6b1..d71b782c62b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -171,11 +171,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/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 6cd3c584f2a..d494d9cc36d 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.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0 .tree-ref-holder.gl-max-w-26 - #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..50d4aac70cc 100644 --- a/lib/banzai/filter/inline_observability_filter.rb +++ b/lib/banzai/filter/inline_observability_filter.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'uri' + module Banzai module Filter class InlineObservabilityFilter < ::Banzai::Filter::InlineEmbedsFilter @@ -15,7 +17,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 +31,15 @@ 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) - create_element(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 dba1aad639c..49c9772f760 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 88a17b7d697..695b567039e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42698,6 +42698,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