diff options
Diffstat (limited to 'lib/banzai')
36 files changed, 214 insertions, 150 deletions
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 8bc2dd18bda..ef4578aabd6 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -54,42 +54,42 @@ module Banzai self.class.references_in(*args, &block) end + # Implement in child class + # Example: project.merge_requests.find def find_object(project, id) - # Implement in child class - # Example: project.merge_requests.find end - def find_object_cached(project, id) - if RequestStore.active? - cache = find_objects_cache[object_class][project.id] + # Override if the link reference pattern produces a different ID (global + # ID vs internal ID, for instance) to the regular reference pattern. + def find_object_from_link(project, id) + find_object(project, id) + end - get_or_set_cache(cache, id) { find_object(project, id) } - else + # Implement in child class + # Example: project_merge_request_url + def url_for_object(object, project) + end + + def find_object_cached(project, id) + cached_call(:banzai_find_object, id, path: [object_class, project.id]) do find_object(project, id) end end - def project_from_ref_cached(ref) - if RequestStore.active? - cache = project_refs_cache - - get_or_set_cache(cache, ref) { project_from_ref(ref) } - else - project_from_ref(ref) + def find_object_from_link_cached(project, id) + cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do + find_object_from_link(project, id) end end - def url_for_object(object, project) - # Implement in child class - # Example: project_merge_request_url + def project_from_ref_cached(ref) + cached_call(:banzai_project_refs, ref) do + project_from_ref(ref) + end end def url_for_object_cached(object, project) - if RequestStore.active? - cache = url_for_object_cache[object_class][project.id] - - get_or_set_cache(cache, object) { url_for_object(object, project) } - else + cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do url_for_object(object, project) end end @@ -120,7 +120,7 @@ module Banzai if link == inner_html && inner_html =~ /\A#{link_pattern}/ replace_link_node_with_text(node, link) do - object_link_filter(inner_html, link_pattern) + object_link_filter(inner_html, link_pattern, link_reference: true) end next @@ -128,7 +128,7 @@ module Banzai if link =~ /\A#{link_pattern}\z/ replace_link_node_with_href(node, link) do - object_link_filter(link, link_pattern, link_content: inner_html) + object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true) end next @@ -146,15 +146,26 @@ module Banzai # text - String text to replace references in. # pattern - Reference pattern to match against. # link_content - Original content of the link being replaced. + # link_reference - True if this was using the link reference pattern, + # false otherwise. # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text, pattern, link_content: nil) + def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| project_path = full_project_path(namespace_ref, project_ref) project = project_from_ref_cached(project_path) - if project && object = find_object_cached(project, id) + if project + object = + if link_reference + find_object_from_link_cached(project, id) + else + find_object_cached(project, id) + end + end + + if object title = object_link_title(object) klass = reference_class(object_sym) @@ -216,12 +227,7 @@ module Banzai @references_per_project ||= begin refs = Hash.new { |hash, key| hash[key] = Set.new } - regex = - if uses_reference_pattern? - Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) - else - object_class.link_reference_pattern - end + regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) nodes.each do |node| node.to_html.scan(regex) do @@ -264,7 +270,7 @@ module Banzai found = [] projects.each do |project| - ref = project.path_with_namespace + ref = project.full_path get_or_set_cache(cache, ref) { project } found << ref end @@ -282,7 +288,7 @@ module Banzai end def current_project_path - @current_project_path ||= project.path_with_namespace + @current_project_path ||= project.full_path end def current_project_namespace_path @@ -302,15 +308,17 @@ module Banzai RequestStore[:banzai_project_refs] ||= {} end - def find_objects_cache - RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } - end - end + def cached_call(request_store_key, cache_key, path: []) + if RequestStore.active? + cache = RequestStore[request_store_key] ||= Hash.new do |hash, key| + hash[key] = Hash.new { |h, k| h[k] = {} } + end - def url_for_object_cache - RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } + cache = cache.dig(*path) if path.any? + + get_or_set_cache(cache, cache_key) { yield } + else + yield end end @@ -323,14 +331,6 @@ module Banzai value end end - - # There might be special cases like filters - # that should ignore reference pattern - # eg: IssueReferenceFilter when using a external issues tracker - # In those cases this method should be overridden on the filter subclass - def uses_reference_pattern? - true - end end end end diff --git a/lib/banzai/filter/ascii_doc_post_processing_filter.rb b/lib/banzai/filter/ascii_doc_post_processing_filter.rb new file mode 100644 index 00000000000..c9fcf057c5f --- /dev/null +++ b/lib/banzai/filter/ascii_doc_post_processing_filter.rb @@ -0,0 +1,13 @@ +module Banzai + module Filter + class AsciiDocPostProcessingFilter < HTML::Pipeline::Filter + def call + doc.search('[data-math-style]').each do |node| + node.set_attribute('class', 'code math js-render-math') + end + + doc + end + end + end +end diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index eaacb9591b1..21bcb1c5ca8 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -30,7 +30,7 @@ module Banzai def url_for_object(range, project) h = Gitlab::Routing.url_helpers - h.namespace_project_compare_url(project.namespace, project, + h.project_compare_url(project, range.to_param.merge(only_path: context[:only_path])) end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index 69c06117eda..714e0319025 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -24,7 +24,7 @@ module Banzai def url_for_object(commit, project) h = Gitlab::Routing.url_helpers - h.namespace_project_commit_url(project.namespace, project, commit, + h.project_commit_url(project, commit, only_path: context[:only_path]) end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index dce4de3ceaf..ed01a72ff9f 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -3,6 +3,8 @@ module Banzai # HTML filter that replaces external issue tracker references with links. # References are ignored if the project doesn't use an external issue # tracker. + # + # This filter does not support cross-project references. class ExternalIssueReferenceFilter < ReferenceFilter self.reference_type = :external_issue @@ -87,16 +89,16 @@ module Banzai end def issue_reference_pattern - external_issues_cached(:issue_reference_pattern) + external_issues_cached(:external_issue_reference_pattern) end private def external_issues_cached(attribute) - return project.public_send(attribute) unless RequestStore.active? + return project.public_send(attribute) unless RequestStore.active? # rubocop:disable GitlabSecurity/PublicSend cached_attributes = RequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} } - cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? + cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend cached_attributes[project.id][attribute] end end diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index d67d466bce8..d6327ef31cb 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -2,16 +2,17 @@ module Banzai module Filter # HTML Filter to modify the attributes of external links class ExternalLinkFilter < HTML::Pipeline::Filter + SCHEMES = ['http', 'https', nil].freeze + def call links.each do |node| - href = href_to_lowercase_scheme(node["href"].to_s) + uri = uri(node['href'].to_s) + next unless uri - unless node["href"].to_s == href - node.set_attribute('href', href) - end + node.set_attribute('href', uri.to_s) - if href =~ %r{\A(https?:)?//[^/]} && external_url?(href) - node.set_attribute('rel', 'nofollow noreferrer') + if SCHEMES.include?(uri.scheme) && external_url?(uri) + node.set_attribute('rel', 'nofollow noreferrer noopener') node.set_attribute('target', '_blank') end end @@ -21,27 +22,26 @@ module Banzai private + def uri(href) + URI.parse(href) + rescue URI::Error + nil + end + def links query = 'descendant-or-self::a[@href and not(@href = "")]' doc.xpath(query) end - def href_to_lowercase_scheme(href) - scheme_match = href.match(/\A(\w+):\/\//) - - if scheme_match - scheme_match.to_s.downcase + scheme_match.post_match - else - href - end - end + def external_url?(uri) + # Relative URLs miss a hostname + return false unless uri.hostname - def external_url?(url) - !url.start_with?(internal_url) + uri.hostname != internal_url.hostname end def internal_url - @internal_url ||= Gitlab.config.gitlab.url + @internal_url ||= URI.parse(Gitlab.config.gitlab.url) end end end diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index 0ea4eeaed5b..2e259904673 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -118,7 +118,7 @@ module Banzai end if path - content_tag(:img, nil, src: path, class: 'gfm') + content_tag(:img, nil, data: { src: path }, class: 'gfm') end end diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb new file mode 100644 index 00000000000..bcb4f332267 --- /dev/null +++ b/lib/banzai/filter/image_lazy_load_filter.rb @@ -0,0 +1,16 @@ +module Banzai + module Filter + # HTML filter that moves the value of the src attribute to the data-src attribute so it can be lazy loaded + class ImageLazyLoadFilter < HTML::Pipeline::Filter + def call + doc.xpath('descendant-or-self::img').each do |img| + img['class'] ||= '' << 'lazy' + img['data-src'] = img['src'] + img['src'] = LazyImageTagHelper.placeholder_image + end + + doc + end + end + end +end diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index 123c92fd250..f318c425962 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -10,7 +10,7 @@ module Banzai link = doc.document.create_element( 'a', class: 'no-attachment-icon', - href: img['src'], + href: img['data-src'] || img['src'], target: '_blank', rel: 'noopener noreferrer' ) diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 044d18ff824..ce1ab977d3b 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -15,16 +15,12 @@ module Banzai Issue end - def uses_reference_pattern? - context[:project].default_issues_tracker? - end - def find_object(project, iid) issues_per_project[project][iid] end def url_for_object(issue, project) - IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path]) + IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true) end def project_from_ref(ref) @@ -38,13 +34,7 @@ module Banzai projects_per_reference.each do |path, project| issue_ids = references_per_project[path] - - issues = - if project.default_issues_tracker? - project.issues.where(iid: issue_ids.to_a) - else - issue_ids.map { |id| ExternalIssue.new(id, project) } - end + issues = project.issues.where(iid: issue_ids.to_a) issues.each do |issue| hash[project][issue.iid.to_i] = issue @@ -55,26 +45,6 @@ module Banzai end end - def object_link_title(object) - if object.is_a?(ExternalIssue) - "Issue in #{object.project.external_issue_tracker.title}" - else - super - end - end - - def data_attributes_for(text, project, object, link: false) - if object.is_a?(ExternalIssue) - data_attribute( - project: project.id, - external_issue: object.id, - reference_type: ExternalIssueReferenceFilter.reference_type - ) - else - super - end - end - def projects_relation_for_paths(paths) super(paths).includes(:gitlab_issue_tracker_service) end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index a605dea149e..5364984c9d3 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -61,8 +61,7 @@ module Banzai def url_for_object(label, project) h = Gitlab::Routing.url_helpers - h.namespace_project_issues_url(project.namespace, project, label_name: label.name, - only_path: context[:only_path]) + h.project_issues_url(project, label_name: label.name, only_path: context[:only_path]) end def object_link_text(object, matches) diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index 3888acf935e..0eab865ac04 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -17,7 +17,7 @@ module Banzai def url_for_object(mr, project) h = Gitlab::Routing.url_helpers - h.namespace_project_merge_request_url(project.namespace, project, mr, + h.project_merge_request_url(project, mr, only_path: context[:only_path]) end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index f12014e191f..4fc5f211e84 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -8,8 +8,15 @@ module Banzai Milestone end + # Links to project milestones contain the IID, but when we're handling + # 'regular' references, we need to use the global ID to disambiguate + # between group and project milestones. def find_object(project, id) - project.milestones.find_by(iid: id) + find_milestone_with_finder(project, id: id) + end + + def find_object_from_link(project, iid) + find_milestone_with_finder(project, iid: iid) end def references_in(text, pattern = Milestone.reference_pattern) @@ -22,7 +29,7 @@ module Banzai milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~[:namespace], $~ + yield match, milestone.id, $~[:project], $~[:namespace], $~ else match end @@ -36,7 +43,8 @@ module Banzai return unless project milestone_params = milestone_params(milestone_id, milestone_name) - project.milestones.find_by(milestone_params) + + find_milestone_with_finder(project, milestone_params) end def milestone_params(iid, name) @@ -47,15 +55,27 @@ module Banzai end end + def find_milestone_with_finder(project, params) + finder_params = { project_ids: [project.id], order: nil } + + # We don't support IID lookups for group milestones, because IIDs can + # clash between group and project milestones. + if project.group && !params[:iid] + finder_params[:group_ids] = [project.group.id] + end + + MilestonesFinder.new(finder_params).execute.find_by(params) + end + def url_for_object(milestone, project) - h = Gitlab::Routing.url_helpers - h.namespace_project_milestone_url(project.namespace, project, milestone, - only_path: context[:only_path]) + Gitlab::Routing + .url_helpers + .milestone_url(milestone, only_path: context[:only_path]) end def object_link_text(object, matches) milestone_link = escape_once(super) - reference = object.project.to_reference(project) + reference = object.project&.to_reference(project) if reference.present? "#{milestone_link} <i>in #{reference}</i>".html_safe diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 6640168bfa2..a6f8650ed3d 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -30,6 +30,8 @@ module Banzai attributes = attributes.reject { |_, v| v.nil? } attributes[:reference_type] ||= self.class.reference_type + attributes[:container] ||= 'body' + attributes[:placement] ||= 'bottom' attributes.delete(:original) if context[:no_original_data] attributes.map do |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 9e23c8f8c55..758f15c8a67 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -22,6 +22,7 @@ module Banzai doc.css('img, video').each do |el| process_link_attr el.attribute('src') + process_link_attr el.attribute('data-src') end doc @@ -50,7 +51,7 @@ module Banzai uri.path = [ relative_url_root, - context[:project].path_with_namespace, + context[:project].full_path, uri_type(file_path), Addressable::URI.escape(ref), Addressable::URI.escape(file_path) diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 522217deae4..2d6e8ffc90f 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -31,6 +31,10 @@ module Banzai # Allow span elements whitelist[:elements].push('span') + # Allow data-math-style attribute in order to support LaTeX formatting + whitelist[:attributes]['code'] = %w(data-math-style) + whitelist[:attributes]['pre'] = %w(data-math-style) + # Allow html5 details/summary elements whitelist[:elements].push('details') whitelist[:elements].push('summary') diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb index 212a0bbf2a0..134a192c22b 100644 --- a/lib/banzai/filter/snippet_reference_filter.rb +++ b/lib/banzai/filter/snippet_reference_filter.rb @@ -17,7 +17,7 @@ module Banzai def url_for_object(snippet, project) h = Gitlab::Routing.url_helpers - h.namespace_project_snippet_url(project.namespace, project, snippet, + h.project_snippet_url(project, snippet, only_path: context[:only_path]) end end diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb index 45bb66dc99f..09844931be5 100644 --- a/lib/banzai/filter/upload_link_filter.rb +++ b/lib/banzai/filter/upload_link_filter.rb @@ -28,7 +28,7 @@ module Banzai end def build_url(uri) - File.join(Gitlab.config.gitlab.url, project.path_with_namespace, uri) + File.join(Gitlab.config.gitlab.url, project.full_path, uri) end def project diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index a798927823f..f3356d6c51e 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -107,7 +107,7 @@ module Banzai if author && !project.team.member?(author) link_content else - url = urls.namespace_project_url(project.namespace, project, + url = urls.project_url(project, only_path: context[:only_path]) data = data_attribute(project: project.id, author: author.try(:id)) diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index 002a3341ccd..2196a92474c 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -37,7 +37,7 @@ module Banzai objects.each_with_index do |object, index| redacted_data = redacted[index] - object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) + object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) # rubocop:disable GitlabSecurity/PublicSend object.user_visible_reference_count = redacted_data[:visible_reference_count] end end diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb new file mode 100644 index 00000000000..1048b927cd3 --- /dev/null +++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb @@ -0,0 +1,14 @@ +module Banzai + module Pipeline + class AsciiDocPipeline < BasePipeline + def self.filters + FilterArray[ + Filter::SanitizationFilter, + Filter::ExternalLinkFilter, + Filter::PlantumlFilter, + Filter::AsciiDocPostProcessingFilter + ] + end + end + end +end diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb index 321fd5bbe14..3ae3bed570d 100644 --- a/lib/banzai/pipeline/base_pipeline.rb +++ b/lib/banzai/pipeline/base_pipeline.rb @@ -18,7 +18,7 @@ module Banzai define_method(meth) do |text, context| context = transform_context(context) - html_pipeline.send(meth, text, context) + html_pipeline.__send__(meth, text, context) # rubocop:disable GitlabSecurity/PublicSend end end end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index bd4d1aa9ff8..3208abfc538 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -16,6 +16,7 @@ module Banzai Filter::MathFilter, Filter::UploadLinkFilter, Filter::VideoLinkFilter, + Filter::ImageLazyLoadFilter, Filter::ImageLinkFilter, Filter::EmojiFilter, Filter::TableOfContentsFilter, diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb new file mode 100644 index 00000000000..c56d908009f --- /dev/null +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -0,0 +1,13 @@ +module Banzai + module Pipeline + class MarkupPipeline < BasePipeline + def self.filters + @filters ||= FilterArray[ + Filter::SanitizationFilter, + Filter::ExternalLinkFilter, + Filter::PlantumlFilter + ] + end + end + end +end diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb index 8e3b0c4db79..7e6357f8a00 100644 --- a/lib/banzai/reference_extractor.rb +++ b/lib/banzai/reference_extractor.rb @@ -10,8 +10,8 @@ module Banzai end def references(type, project, current_user = nil) - processor = Banzai::ReferenceParser[type]. - new(project, current_user) + processor = Banzai::ReferenceParser[type] + .new(project, current_user) processor.process(html_documents) end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index c2503fa2adc..279fca8d043 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -62,7 +62,7 @@ module Banzai nodes.select do |node| if node.has_attribute?(project_attr) - can_read_reference?(user, projects[node]) + can_read_reference?(user, projects[node], node) else true end @@ -163,14 +163,15 @@ module Banzai # been queried the object is returned from the cache. def collection_objects_for_ids(collection, ids) if RequestStore.active? + ids = ids.map(&:to_i) cache = collection_cache[collection_cache_key(collection)] - to_query = ids.map(&:to_i) - cache.keys + to_query = ids - cache.keys unless to_query.empty? collection.where(id: to_query).each { |row| cache[row.id] = row } end - cache.values + cache.values_at(*ids).compact else collection.where(id: ids) end @@ -230,7 +231,7 @@ module Banzai # see reference comments. # Override this method on subclasses # to check if user can read resource - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) raise NotImplementedError end diff --git a/lib/banzai/reference_parser/commit_parser.rb b/lib/banzai/reference_parser/commit_parser.rb index 8c54a041cb8..30dc87248b4 100644 --- a/lib/banzai/reference_parser/commit_parser.rb +++ b/lib/banzai/reference_parser/commit_parser.rb @@ -32,7 +32,7 @@ module Banzai private - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) can?(user, :download_code, ref_project) end end diff --git a/lib/banzai/reference_parser/commit_range_parser.rb b/lib/banzai/reference_parser/commit_range_parser.rb index 0878b6afba3..a50e6f8ef8f 100644 --- a/lib/banzai/reference_parser/commit_range_parser.rb +++ b/lib/banzai/reference_parser/commit_range_parser.rb @@ -36,7 +36,7 @@ module Banzai private - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) can?(user, :download_code, ref_project) end end diff --git a/lib/banzai/reference_parser/external_issue_parser.rb b/lib/banzai/reference_parser/external_issue_parser.rb index 6e7b7669578..1802cd04854 100644 --- a/lib/banzai/reference_parser/external_issue_parser.rb +++ b/lib/banzai/reference_parser/external_issue_parser.rb @@ -21,10 +21,14 @@ module Banzai gather_attributes_per_project(nodes, self.class.data_attribute) end - private + # we extract only external issue trackers references here, we don't extract cross-project references, + # so we don't need to do anything here. + def can_read_reference?(user, ref_project, node) + true + end - def can_read_reference?(user, ref_project) - can?(user, :read_issue, ref_project) + def nodes_visible_to_user(user, nodes) + nodes end end end diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index e02b360924a..a65bbe23958 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -4,13 +4,10 @@ module Banzai self.reference_type = :issue def nodes_visible_to_user(user, nodes) - # It is not possible to check access rights for external issue trackers - return nodes if project && project.external_issue_tracker - issues = issues_for_nodes(nodes) - readable_issues = Ability. - issues_readable_by_user(issues.values, user).to_set + readable_issues = Ability + .issues_readable_by_user(issues.values, user).to_set nodes.select do |node| readable_issues.include?(issues[node]) @@ -28,7 +25,7 @@ module Banzai nodes, Issue.all.includes( :author, - :assignee, + :assignees, { # These associations are primarily used for checking permissions. # Eager loading these ensures we don't end up running dozens of diff --git a/lib/banzai/reference_parser/label_parser.rb b/lib/banzai/reference_parser/label_parser.rb index aa76c64ac5f..30e2a012f09 100644 --- a/lib/banzai/reference_parser/label_parser.rb +++ b/lib/banzai/reference_parser/label_parser.rb @@ -9,7 +9,7 @@ module Banzai private - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) can?(user, :read_label, ref_project) end end diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb index 84a28b33d7c..75cbc7fdac4 100644 --- a/lib/banzai/reference_parser/merge_request_parser.rb +++ b/lib/banzai/reference_parser/merge_request_parser.rb @@ -33,12 +33,17 @@ module Banzai { namespace: :owner }, { group: [:owners, :group_members] }, :invited_groups, - :project_members + :project_members, + :project_feature ] }), self.class.data_attribute ) end + + def can_read_reference?(user, ref_project, node) + can?(user, :read_merge_request, ref_project) + end end end end diff --git a/lib/banzai/reference_parser/milestone_parser.rb b/lib/banzai/reference_parser/milestone_parser.rb index d3968d6b229..68675abe22a 100644 --- a/lib/banzai/reference_parser/milestone_parser.rb +++ b/lib/banzai/reference_parser/milestone_parser.rb @@ -9,7 +9,7 @@ module Banzai private - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) can?(user, :read_milestone, ref_project) end end diff --git a/lib/banzai/reference_parser/snippet_parser.rb b/lib/banzai/reference_parser/snippet_parser.rb index 63b592137bb..3ade168b566 100644 --- a/lib/banzai/reference_parser/snippet_parser.rb +++ b/lib/banzai/reference_parser/snippet_parser.rb @@ -9,8 +9,8 @@ module Banzai private - def can_read_reference?(user, ref_project) - can?(user, :read_project_snippet, ref_project) + def can_read_reference?(user, ref_project, node) + can?(user, :read_project_snippet, referenced_by([node]).first) end end end diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb index 09b66cbd8fb..4d336068861 100644 --- a/lib/banzai/reference_parser/user_parser.rb +++ b/lib/banzai/reference_parser/user_parser.rb @@ -99,11 +99,11 @@ module Banzai def find_users_for_projects(ids) return [] if ids.empty? - collection_objects_for_ids(Project, ids). - flat_map { |p| p.team.members.to_a } + collection_objects_for_ids(Project, ids) + .flat_map { |p| p.team.members.to_a } end - def can_read_reference?(user, ref_project) + def can_read_reference?(user, ref_project, node) can?(user, :read_project, ref_project) end end diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index c7801cb5baf..95d82d17658 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -43,7 +43,7 @@ module Banzai # Same as +render_field+, but without consulting or updating the cache field def self.cacheless_render_field(object, field, options = {}) - text = object.__send__(field) + text = object.__send__(field) # rubocop:disable GitlabSecurity/PublicSend context = object.banzai_render_context(field).merge(options) cacheless_render(text, context) @@ -132,6 +132,8 @@ module Banzai end def self.cacheless_render(text, context = {}) + return text.to_s unless text.present? + Gitlab::Metrics.measure(:banzai_cacheless_render) do result = render_result(text, context) @@ -154,7 +156,7 @@ module Banzai # method. def self.full_cache_multi_key(cache_key, pipeline_name) return unless cache_key - Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name)) + Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend end # GitLab EE needs to disable updates on GET requests in Geo |