debian-mirror-gitlab/app/models/concerns/participable.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

170 lines
4.8 KiB
Ruby
Raw Normal View History

2018-11-20 20:47:30 +05:30
# frozen_string_literal: true
2015-09-11 14:41:01 +05:30
# == Participable concern
#
# Contains functionality related to objects that can have participants, such as
# an author, an assignee and people mentioned in its description or comments.
#
# Usage:
#
2019-07-07 11:18:12 +05:30
# class Issue < ApplicationRecord
2015-09-11 14:41:01 +05:30
# include Participable
#
# # ...
#
# participant :author
# participant :assignee
# participant :notes
#
# participant -> (current_user, ext) do
# ext.analyze('...')
# end
2015-09-11 14:41:01 +05:30
# end
#
# issue = Issue.last
# users = issue.participants
module Participable
extend ActiveSupport::Concern
2018-11-20 20:47:30 +05:30
class_methods do
# Adds a list of participant attributes. Attributes can either be symbols or
# Procs.
#
# When using a Proc instead of a Symbol the Proc will be given two
# arguments:
#
# 1. The current user (as an instance of User)
# 2. An instance of `Gitlab::ReferenceExtractor`
#
# It is expected that a Proc populates the given reference extractor
# instance with data. The return value of the Proc is ignored.
#
# attr - The name of the attribute or a Proc
def participant(attr)
participant_attrs << attr
2015-09-11 14:41:01 +05:30
end
2016-08-24 12:49:21 +05:30
end
2015-09-11 14:41:01 +05:30
2016-08-24 12:49:21 +05:30
included do
# Accessor for participant attributes.
cattr_accessor :participant_attrs, instance_accessor: false do
[]
2015-09-11 14:41:01 +05:30
end
end
# Returns the users participating in a discussion.
#
# This method processes attributes of objects in breadth-first order.
#
# Returns an Array of User instances.
2021-04-29 21:17:54 +05:30
def participants(user = nil)
filtered_participants_hash[user]
end
2022-01-26 12:08:38 +05:30
# Returns only participants visible for the user
#
# Returns an Array of User instances.
def visible_participants(user)
filter_by_ability(raw_participants(user, verify_access: true))
end
2021-04-29 21:17:54 +05:30
# Checks if the user is a participant in a discussion.
#
# This method processes attributes of objects in breadth-first order.
#
# Returns a Boolean.
def participant?(user)
can_read_participable?(user) &&
all_participants_hash[user].include?(user)
2016-06-22 15:30:34 +05:30
end
private
2021-04-29 21:17:54 +05:30
def all_participants_hash
@all_participants_hash ||= Hash.new do |hash, user|
2018-03-17 18:26:18 +05:30
hash[user] = raw_participants(user)
end
end
2021-04-29 21:17:54 +05:30
def filtered_participants_hash
@filtered_participants_hash ||= Hash.new do |hash, user|
hash[user] = filter_by_ability(all_participants_hash[user])
end
end
2022-01-26 12:08:38 +05:30
def raw_participants(current_user = nil, verify_access: false)
2022-08-13 15:12:31 +05:30
extractor = Gitlab::ReferenceExtractor.new(project, current_user)
# Used to extract references from confidential notes.
# Referenced users that cannot read confidential notes are
# later removed from participants array.
internal_notes_extractor = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new
process = [self]
2015-09-11 14:41:01 +05:30
until process.empty?
source = process.pop
2015-10-24 18:46:33 +05:30
case source
when User
participants << source
when Participable
2022-01-26 12:08:38 +05:30
next unless !verify_access || source_visible_to_user?(source, current_user)
source.class.participant_attrs.each do |attr|
if attr.respond_to?(:call)
2022-08-13 15:12:31 +05:30
ext = use_internal_notes_extractor_for?(source) ? internal_notes_extractor : extractor
source.instance_exec(current_user, ext, &attr)
else
2018-03-17 18:26:18 +05:30
process << source.__send__(attr) # rubocop:disable GitlabSecurity/PublicSend
end
end
when Enumerable, ActiveRecord::Relation
# This uses reverse_each so we can use "pop" to get the next value to
# process (in order). Using unshift instead of pop would require
# moving all Array values one index to the left (which can be
# expensive).
source.reverse_each { |obj| process << obj }
2015-09-11 14:41:01 +05:30
end
end
2022-08-13 15:12:31 +05:30
participants.merge(users_that_can_read_internal_notes(internal_notes_extractor))
participants.merge(extractor.users)
end
def use_internal_notes_extractor_for?(source)
source.is_a?(Note) && source.confidential?
end
def users_that_can_read_internal_notes(extractor)
return [] unless self.is_a?(Noteable) && self.try(:resource_parent)
Ability.users_that_can_read_internal_notes(extractor.users, self.resource_parent)
2018-10-15 14:42:47 +05:30
end
2022-01-26 12:08:38 +05:30
def source_visible_to_user?(source, user)
Ability.allowed?(user, "read_#{source.model_name.element}".to_sym, source)
end
2018-10-15 14:42:47 +05:30
def filter_by_ability(participants)
2017-08-17 22:00:37 +05:30
case self
when PersonalSnippet
Ability.users_that_can_read_personal_snippet(participants.to_a, self)
else
Ability.users_that_can_read_project(participants.to_a, project)
end
2015-09-11 14:41:01 +05:30
end
2021-04-29 21:17:54 +05:30
def can_read_participable?(participant)
case self
when PersonalSnippet
participant.can?(:read_snippet, self)
else
participant.can?(:read_project, project)
end
end
2015-09-11 14:41:01 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
Participable.prepend_mod_with('Participable')