2018-07-25 09:30:33 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-07-26 15:43:27 +00:00
|
|
|
class NotificationRecipient
|
2018-05-28 18:43:46 +00:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
2017-12-28 17:25:02 +00:00
|
|
|
attr_reader :user, :type, :reason
|
2019-08-15 17:37:36 +00:00
|
|
|
|
2020-09-29 15:10:08 +00:00
|
|
|
def initialize(user, type, opts = {})
|
2017-08-04 18:53:17 +00:00
|
|
|
unless NotificationSetting.levels.key?(type) || type == :subscription
|
|
|
|
raise ArgumentError, "invalid type: #{type.inspect}"
|
|
|
|
end
|
|
|
|
|
2017-12-28 17:25:02 +00:00
|
|
|
@custom_action = opts[:custom_action]
|
|
|
|
@acting_user = opts[:acting_user]
|
|
|
|
@target = opts[:target]
|
|
|
|
@project = opts[:project] || default_project
|
|
|
|
@group = opts[:group] || @project&.group
|
2017-07-26 15:43:27 +00:00
|
|
|
@user = user
|
|
|
|
@type = type
|
2017-12-28 17:25:02 +00:00
|
|
|
@reason = opts[:reason]
|
|
|
|
@skip_read_ability = opts[:skip_read_ability]
|
2017-07-26 15:43:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def notification_setting
|
|
|
|
@notification_setting ||= find_notification_setting
|
|
|
|
end
|
|
|
|
|
|
|
|
def notification_level
|
2017-08-18 20:40:09 +00:00
|
|
|
@notification_level ||= notification_setting&.level&.to_sym
|
2017-07-26 15:43:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def notifiable?
|
|
|
|
return false unless has_access?
|
2019-08-15 17:37:36 +00:00
|
|
|
return false if emails_disabled?
|
2017-07-26 15:43:27 +00:00
|
|
|
return false if own_activity?
|
|
|
|
|
2017-08-18 20:40:09 +00:00
|
|
|
# even users with :disabled notifications receive manual subscriptions
|
|
|
|
return !unsubscribed? if @type == :subscription
|
2017-07-26 15:43:27 +00:00
|
|
|
|
2017-08-18 20:40:09 +00:00
|
|
|
return false unless suitable_notification_level?
|
2022-09-09 18:12:57 +00:00
|
|
|
return false if email_blocked?
|
2017-07-26 15:43:27 +00:00
|
|
|
|
2017-08-18 20:40:09 +00:00
|
|
|
# check this last because it's expensive
|
|
|
|
# nobody should receive notifications if they've specifically unsubscribed
|
2018-03-16 01:05:44 +00:00
|
|
|
# except if they were mentioned.
|
|
|
|
return false if @type != :mention && unsubscribed?
|
2017-07-26 15:43:27 +00:00
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2017-08-18 20:40:09 +00:00
|
|
|
def suitable_notification_level?
|
|
|
|
case notification_level
|
|
|
|
when :mention
|
|
|
|
@type == :mention
|
2019-04-17 09:10:42 +00:00
|
|
|
when :participating
|
2020-07-28 03:09:35 +00:00
|
|
|
participating_custom_action? || participating_or_mention?
|
2019-04-17 09:10:42 +00:00
|
|
|
when :custom
|
2020-07-28 03:09:35 +00:00
|
|
|
custom_enabled? || participating_or_mention?
|
2019-04-17 09:10:42 +00:00
|
|
|
when :watch
|
|
|
|
!excluded_watcher_action?
|
2017-08-18 20:40:09 +00:00
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def custom_enabled?
|
2020-02-27 18:09:21 +00:00
|
|
|
return false unless @custom_action
|
|
|
|
return false unless notification_setting
|
|
|
|
|
|
|
|
notification_setting.event_enabled?(@custom_action) ||
|
|
|
|
# fixed_pipeline is a subset of success_pipeline event
|
|
|
|
(@custom_action == :fixed_pipeline &&
|
|
|
|
notification_setting.event_enabled?(:success_pipeline))
|
2017-08-18 20:40:09 +00:00
|
|
|
end
|
|
|
|
|
2017-07-26 15:43:27 +00:00
|
|
|
def unsubscribed?
|
2020-03-27 09:08:28 +00:00
|
|
|
subscribable_target = @target.is_a?(Note) ? @target.noteable : @target
|
2017-07-26 15:43:27 +00:00
|
|
|
|
2020-03-27 09:08:28 +00:00
|
|
|
return false unless subscribable_target
|
|
|
|
return false unless subscribable_target.respond_to?(:subscriptions)
|
|
|
|
|
|
|
|
subscription = subscribable_target.subscriptions.find { |subscription| subscription.user_id == @user.id }
|
2017-07-26 15:43:27 +00:00
|
|
|
subscription && !subscription.subscribed
|
|
|
|
end
|
|
|
|
|
|
|
|
def own_activity?
|
|
|
|
return false unless @acting_user
|
|
|
|
|
2017-12-28 17:25:02 +00:00
|
|
|
if user == @acting_user
|
|
|
|
# if activity was generated by the same user, change reason to :own_activity
|
|
|
|
@reason = NotificationReason::OWN_ACTIVITY
|
|
|
|
# If the user wants to be notified, we must return `false`
|
|
|
|
!@acting_user.notified_of_own_activity?
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
2017-07-26 15:43:27 +00:00
|
|
|
end
|
|
|
|
|
2022-09-09 18:12:57 +00:00
|
|
|
def email_blocked?
|
|
|
|
recipient_email = user.notification_email_for(@group)
|
|
|
|
|
|
|
|
Gitlab::ApplicationRateLimiter.peek(:permanent_email_failure, scope: recipient_email) ||
|
|
|
|
Gitlab::ApplicationRateLimiter.peek(:temporary_email_failure, scope: recipient_email)
|
|
|
|
end
|
|
|
|
|
2017-07-26 15:43:27 +00:00
|
|
|
def has_access?
|
|
|
|
DeclarativePolicy.subject_scope do
|
2018-04-18 09:19:40 +00:00
|
|
|
break false unless user.can?(:receive_notifications)
|
|
|
|
break true if @skip_read_ability
|
2017-08-10 17:41:25 +00:00
|
|
|
|
2018-04-18 09:19:40 +00:00
|
|
|
break false if @target && !user.can?(:read_cross_project)
|
|
|
|
break false if @project && !user.can?(:read_project, @project)
|
2017-08-01 17:42:54 +00:00
|
|
|
|
2018-04-18 09:19:40 +00:00
|
|
|
break true unless read_ability
|
|
|
|
break true unless DeclarativePolicy.has_policy?(@target)
|
2017-08-01 17:42:54 +00:00
|
|
|
|
2017-08-02 21:14:53 +00:00
|
|
|
user.can?(read_ability, @target)
|
2017-07-26 15:43:27 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def excluded_watcher_action?
|
2019-06-12 15:55:09 +00:00
|
|
|
return false unless @type == :watch
|
2019-04-17 09:10:42 +00:00
|
|
|
return false unless @custom_action
|
2017-07-26 15:43:27 +00:00
|
|
|
|
|
|
|
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
|
2017-07-25 11:56:09 +00:00
|
|
|
end
|
|
|
|
|
2017-07-26 15:43:27 +00:00
|
|
|
private
|
|
|
|
|
2019-08-15 17:37:36 +00:00
|
|
|
# They are disabled if the project or group has disallowed it.
|
|
|
|
# No need to check the group if there is already a project
|
|
|
|
def emails_disabled?
|
|
|
|
@project ? @project.emails_disabled? : @group&.emails_disabled?
|
|
|
|
end
|
|
|
|
|
2022-07-19 09:08:45 +00:00
|
|
|
def emails_enabled?
|
|
|
|
!emails_disabled?
|
|
|
|
end
|
|
|
|
|
2017-08-02 21:14:53 +00:00
|
|
|
def read_ability
|
2019-02-08 12:19:53 +00:00
|
|
|
return if @skip_read_ability
|
2017-08-02 21:14:53 +00:00
|
|
|
return @read_ability if instance_variable_defined?(:@read_ability)
|
|
|
|
|
|
|
|
@read_ability =
|
2019-04-17 01:26:49 +00:00
|
|
|
if @target.is_a?(Ci::Pipeline)
|
2017-08-02 21:14:53 +00:00
|
|
|
:read_build # We have build trace in pipeline emails
|
2019-04-17 01:26:49 +00:00
|
|
|
elsif default_ability_for_target
|
|
|
|
:"read_#{default_ability_for_target}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_ability_for_target
|
|
|
|
@default_ability_for_target ||=
|
|
|
|
if @target.respond_to?(:to_ability_name)
|
|
|
|
@target.to_ability_name
|
|
|
|
elsif @target.class.respond_to?(:model_name)
|
|
|
|
@target.class.model_name.name.underscore
|
2017-08-02 21:14:53 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-04 18:53:17 +00:00
|
|
|
def default_project
|
2019-02-08 12:19:53 +00:00
|
|
|
return if @target.nil?
|
2017-08-04 18:53:17 +00:00
|
|
|
return @target if @target.is_a?(Project)
|
|
|
|
return @target.project if @target.respond_to?(:project)
|
|
|
|
end
|
|
|
|
|
2017-07-26 15:43:27 +00:00
|
|
|
def find_notification_setting
|
|
|
|
project_setting = @project && user.notification_settings_for(@project)
|
|
|
|
|
|
|
|
return project_setting unless project_setting.nil? || project_setting.global?
|
|
|
|
|
2019-06-12 15:55:09 +00:00
|
|
|
group_setting = closest_non_global_group_notification_setting
|
2017-07-26 15:43:27 +00:00
|
|
|
|
2018-05-28 18:43:46 +00:00
|
|
|
return group_setting unless group_setting.nil?
|
2017-07-26 15:43:27 +00:00
|
|
|
|
|
|
|
user.global_notification_setting
|
|
|
|
end
|
2018-05-28 18:43:46 +00:00
|
|
|
|
2019-02-25 10:42:31 +00:00
|
|
|
# Returns the notification_setting of the lowest group in hierarchy with non global level
|
2019-06-12 15:55:09 +00:00
|
|
|
def closest_non_global_group_notification_setting
|
2018-05-28 18:43:46 +00:00
|
|
|
return unless @group
|
|
|
|
|
2019-04-26 15:33:29 +00:00
|
|
|
@group
|
|
|
|
.notification_settings(hierarchy_order: :asc)
|
|
|
|
.where(user: user)
|
|
|
|
.where.not(level: NotificationSetting.levels[:global])
|
|
|
|
.first
|
2018-05-28 18:43:46 +00:00
|
|
|
end
|
2020-07-28 03:09:35 +00:00
|
|
|
|
|
|
|
def participating_custom_action?
|
|
|
|
%i[failed_pipeline fixed_pipeline moved_project].include?(@custom_action)
|
|
|
|
end
|
|
|
|
|
|
|
|
def participating_or_mention?
|
|
|
|
%i[participating mention].include?(@type)
|
|
|
|
end
|
2017-07-26 15:43:27 +00:00
|
|
|
end
|