diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/api/repositories.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/inline_observability_filter.rb | 16 | ||||
-rw-r--r-- | lib/extracts_ref.rb | 14 | ||||
-rw-r--r-- | lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb | 17 | ||||
-rw-r--r-- | lib/gitlab/regex.rb | 58 | ||||
-rw-r--r-- | lib/gitlab/unicode.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/untrusted_regexp.rb | 11 | ||||
-rw-r--r-- | lib/gitlab/url_sanitizer.rb | 90 | ||||
-rw-r--r-- | lib/rouge/formatters/html_gitlab.rb | 9 |
9 files changed, 160 insertions, 65 deletions
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/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'] - - 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 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/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/regex.rb b/lib/gitlab/regex.rb index 93d23add5eb..943218a9972 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -448,6 +448,17 @@ module Gitlab ) }mx.freeze + # Code blocks: + # ``` + # Anything, including `>>>` blocks which are ignored by this filter + # ``` + MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED = + '(?P<code>' \ + '^```\n' \ + '(?:\n|.)*?' \ + '\n```\ *$' \ + ')'.freeze + MARKDOWN_HTML_BLOCK_REGEX = %r{ (?<html> # HTML block: @@ -461,27 +472,19 @@ module Gitlab ) }mx.freeze - MARKDOWN_HTML_COMMENT_LINE_REGEX = %r{ - (?<html_comment_line> - # HTML comment line: - # <!-- some commented text --> - - ^<!--\ .*?\ -->\ *$ - ) - }mx.freeze - - MARKDOWN_HTML_COMMENT_BLOCK_REGEX = %r{ - (?<html_comment_block> - # HTML comment block: - # <!-- some commented text - # additional text - # --> + # HTML comment line: + # <!-- some commented text --> + MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED = + '(?P<html_comment_line>' \ + '^<!--\ .*?\ -->\ *$' \ + ')'.freeze - ^<!--.*\n - .+? - \n-->\ *$ - ) - }mx.freeze + MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED = + '(?P<html_comment_block>' \ + '^<!--.*?\n' \ + '(?:\n|.)*?' \ + '\n.*?-->\ *$' \ + ')'.freeze def markdown_code_or_html_blocks @markdown_code_or_html_blocks ||= %r{ @@ -491,14 +494,13 @@ module Gitlab }mx.freeze end - def markdown_code_or_html_comments - @markdown_code_or_html_comments ||= %r{ - #{MARKDOWN_CODE_BLOCK_REGEX} - | - #{MARKDOWN_HTML_COMMENT_LINE_REGEX} - | - #{MARKDOWN_HTML_COMMENT_BLOCK_REGEX} - }mx.freeze + def markdown_code_or_html_comments_untrusted + @markdown_code_or_html_comments_untrusted ||= + "#{MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED}" \ + "|" \ + "#{MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED}" \ + "|" \ + "#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED}" end # Based on Jira's project key format 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/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index 96e74f00c78..7c7bda3a8f9 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -47,6 +47,17 @@ module Gitlab RE2.Replace(text, regexp, rewrite) end + # #scan returns an array of the groups captured, rather than MatchData. + # Use this to give the capture group name and grab the proper value + def extract_named_group(name, match) + return unless match + + match_position = regexp.named_capturing_groups[name.to_s] + raise RegexpError, "Invalid named capture group: #{name}" unless match_position + + match[match_position - 1] + end + def ==(other) self.source == other.source end 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 %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">) 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 %(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>) 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| %(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>) |