Only allow group/project members to mention @all
This commit is contained in:
parent
d84ca3e815
commit
672cbbff95
9 changed files with 59 additions and 18 deletions
|
@ -5,6 +5,7 @@ v 8.4.0 (unreleased)
|
||||||
- Implement new UI for group page
|
- Implement new UI for group page
|
||||||
- Implement search inside emoji picker
|
- Implement search inside emoji picker
|
||||||
- Add project permissions to all project API endpoints (Stan Hu)
|
- Add project permissions to all project API endpoints (Stan Hu)
|
||||||
|
- Only allow group/project members to mention `@all`
|
||||||
|
|
||||||
v 8.3.1 (unreleased)
|
v 8.3.1 (unreleased)
|
||||||
- Fix Error 500 when global milestones have slashes (Stan Hu)
|
- Fix Error 500 when global milestones have slashes (Stan Hu)
|
||||||
|
|
|
@ -178,7 +178,7 @@ class ProjectsController < ApplicationController
|
||||||
def markdown_preview
|
def markdown_preview
|
||||||
text = params[:text]
|
text = params[:text]
|
||||||
|
|
||||||
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
|
ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
|
||||||
ext.analyze(text)
|
ext.analyze(text)
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
|
|
|
@ -44,7 +44,7 @@ module Mentionable
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_references(current_user = self.author, text = nil)
|
def all_references(current_user = self.author, text = nil)
|
||||||
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
|
ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author)
|
||||||
|
|
||||||
if text
|
if text
|
||||||
ext.analyze(text)
|
ext.analyze(text)
|
||||||
|
|
|
@ -11,7 +11,7 @@ module Banzai
|
||||||
class RedactorFilter < HTML::Pipeline::Filter
|
class RedactorFilter < HTML::Pipeline::Filter
|
||||||
def call
|
def call
|
||||||
doc.css('a.gfm').each do |node|
|
doc.css('a.gfm').each do |node|
|
||||||
unless user_can_reference?(node)
|
unless user_can_see_reference?(node)
|
||||||
# The reference should be replaced by the original text,
|
# The reference should be replaced by the original text,
|
||||||
# which is not always the same as the rendered text.
|
# which is not always the same as the rendered text.
|
||||||
text = node.attr('data-original') || node.text
|
text = node.attr('data-original') || node.text
|
||||||
|
@ -24,12 +24,12 @@ module Banzai
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def user_can_reference?(node)
|
def user_can_see_reference?(node)
|
||||||
if node.has_attribute?('data-reference-filter')
|
if node.has_attribute?('data-reference-filter')
|
||||||
reference_type = node.attr('data-reference-filter')
|
reference_type = node.attr('data-reference-filter')
|
||||||
reference_filter = Banzai::Filter.const_get(reference_type)
|
reference_filter = Banzai::Filter.const_get(reference_type)
|
||||||
|
|
||||||
reference_filter.user_can_reference?(current_user, node, context)
|
reference_filter.user_can_see_reference?(current_user, node, context)
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ module Banzai
|
||||||
# :project (required) - Current project, ignored if reference is cross-project.
|
# :project (required) - Current project, ignored if reference is cross-project.
|
||||||
# :only_path - Generate path-only links.
|
# :only_path - Generate path-only links.
|
||||||
class ReferenceFilter < HTML::Pipeline::Filter
|
class ReferenceFilter < HTML::Pipeline::Filter
|
||||||
def self.user_can_reference?(user, node, context)
|
def self.user_can_see_reference?(user, node, context)
|
||||||
if node.has_attribute?('data-project')
|
if node.has_attribute?('data-project')
|
||||||
project_id = node.attr('data-project').to_i
|
project_id = node.attr('data-project').to_i
|
||||||
return true if project_id == context[:project].try(:id)
|
return true if project_id == context[:project].try(:id)
|
||||||
|
@ -24,6 +24,10 @@ module Banzai
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.user_can_reference?(user, node, context)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def self.referenced_by(node)
|
def self.referenced_by(node)
|
||||||
raise NotImplementedError, "#{self} does not implement #{__method__}"
|
raise NotImplementedError, "#{self} does not implement #{__method__}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,7 +35,9 @@ module Banzai
|
||||||
|
|
||||||
return if context[:reference_filter] && reference_filter != context[:reference_filter]
|
return if context[:reference_filter] && reference_filter != context[:reference_filter]
|
||||||
|
|
||||||
return unless reference_filter.user_can_reference?(current_user, node, context)
|
return if author && !reference_filter.user_can_reference?(author, node, context)
|
||||||
|
|
||||||
|
return unless reference_filter.user_can_see_reference?(current_user, node, context)
|
||||||
|
|
||||||
references = reference_filter.referenced_by(node)
|
references = reference_filter.referenced_by(node)
|
||||||
return unless references
|
return unless references
|
||||||
|
@ -57,6 +59,10 @@ module Banzai
|
||||||
def current_user
|
def current_user
|
||||||
context[:current_user]
|
context[:current_user]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def author
|
||||||
|
context[:author]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ module Banzai
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.user_can_reference?(user, node, context)
|
def self.user_can_see_reference?(user, node, context)
|
||||||
if node.has_attribute?('data-group')
|
if node.has_attribute?('data-group')
|
||||||
group = Group.find(node.attr('data-group')) rescue nil
|
group = Group.find(node.attr('data-group')) rescue nil
|
||||||
Ability.abilities.allowed?(user, :read_group, group)
|
Ability.abilities.allowed?(user, :read_group, group)
|
||||||
|
@ -48,6 +48,18 @@ module Banzai
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.user_can_reference?(user, node, context)
|
||||||
|
# Only team members can reference `@all`
|
||||||
|
if node.has_attribute?('data-project')
|
||||||
|
project = Project.find(node.attr('data-project')) rescue nil
|
||||||
|
return false unless project
|
||||||
|
|
||||||
|
user && project.team.member?(user)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
replace_text_nodes_matching(User.reference_pattern) do |content|
|
replace_text_nodes_matching(User.reference_pattern) do |content|
|
||||||
user_link_filter(content)
|
user_link_filter(content)
|
||||||
|
|
|
@ -3,11 +3,12 @@ require 'banzai'
|
||||||
module Gitlab
|
module Gitlab
|
||||||
# Extract possible GFM references from an arbitrary String for further processing.
|
# Extract possible GFM references from an arbitrary String for further processing.
|
||||||
class ReferenceExtractor < Banzai::ReferenceExtractor
|
class ReferenceExtractor < Banzai::ReferenceExtractor
|
||||||
attr_accessor :project, :current_user
|
attr_accessor :project, :current_user, :author
|
||||||
|
|
||||||
def initialize(project, current_user = nil)
|
def initialize(project, current_user = nil, author = nil)
|
||||||
@project = project
|
@project = project
|
||||||
@current_user = current_user
|
@current_user = current_user
|
||||||
|
@author = author
|
||||||
|
|
||||||
@references = {}
|
@references = {}
|
||||||
|
|
||||||
|
@ -20,18 +21,22 @@ module Gitlab
|
||||||
|
|
||||||
%i(user label merge_request snippet commit commit_range).each do |type|
|
%i(user label merge_request snippet commit commit_range).each do |type|
|
||||||
define_method("#{type}s") do
|
define_method("#{type}s") do
|
||||||
@references[type] ||= references(type, project: project, current_user: current_user)
|
@references[type] ||= references(type, reference_context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def issues
|
def issues
|
||||||
options = { project: project, current_user: current_user }
|
|
||||||
|
|
||||||
if project && project.jira_tracker?
|
if project && project.jira_tracker?
|
||||||
@references[:external_issue] ||= references(:external_issue, options)
|
@references[:external_issue] ||= references(:external_issue, reference_context)
|
||||||
else
|
else
|
||||||
@references[:issue] ||= references(:issue, options)
|
@references[:issue] ||= references(:issue, reference_context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def reference_context
|
||||||
|
{ project: project, current_user: current_user, author: author }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,12 +37,25 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
|
||||||
.to eq urls.namespace_project_url(project.namespace, project)
|
.to eq urls.namespace_project_url(project.namespace, project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when the author is a member of the project" do
|
||||||
|
|
||||||
it 'adds to the results hash' do
|
it 'adds to the results hash' do
|
||||||
result = reference_pipeline_result("Hey #{reference}")
|
result = reference_pipeline_result("Hey #{reference}", author: project.creator)
|
||||||
expect(result[:references][:user]).to eq [project.creator]
|
expect(result[:references][:user]).to eq [project.creator]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when the author is not a member of the project" do
|
||||||
|
|
||||||
|
let(:other_user) { create(:user) }
|
||||||
|
|
||||||
|
it "doesn't add to the results hash" do
|
||||||
|
result = reference_pipeline_result("Hey #{reference}", author: other_user)
|
||||||
|
expect(result[:references][:user]).to eq []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'mentioning a user' do
|
context 'mentioning a user' do
|
||||||
it 'links to a User' do
|
it 'links to a User' do
|
||||||
doc = reference_filter("Hey #{reference}")
|
doc = reference_filter("Hey #{reference}")
|
||||||
|
|
Loading…
Reference in a new issue