From 22326c3c028ff9e542ba4cbc8422076c420287fc Mon Sep 17 00:00:00 2001 From: Fire Demon Date: Mon, 27 Jul 2020 07:45:50 -0500 Subject: [Privacy] Improve handling of mixed permissions --- app/lib/command_tag/commands/status_tools.rb | 6 --- app/lib/command_tag/processor.rb | 9 ---- app/models/account.rb | 8 ++++ app/models/status.rb | 70 +++++++++++++++++++++++++--- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/app/lib/command_tag/commands/status_tools.rb b/app/lib/command_tag/commands/status_tools.rb index 0e6524001..23bdcbfc3 100644 --- a/app/lib/command_tag/commands/status_tools.rb +++ b/app/lib/command_tag/commands/status_tools.rb @@ -27,10 +27,4 @@ module CommandTag::Commands::StatusTools alias handle_v_before_save handle_visibility_before_save alias handle_privacy_before_save handle_visibility_before_save - - def handle_semiprivate_before_save(args) - return unless author_of_status? - - @semiprivate = args.blank? || read_boolean_from(args[0]) - end end diff --git a/app/lib/command_tag/processor.rb b/app/lib/command_tag/processor.rb index e9b8f5c05..da6d49160 100644 --- a/app/lib/command_tag/processor.rb +++ b/app/lib/command_tag/processor.rb @@ -59,7 +59,6 @@ class CommandTag::Processor elsif @status.destroyed? %w(after_destroy once_after_destroy).each { |suffix| execute_statements(suffix) } else - update_semiprivate_attribute @status.text = @text process_inline_images! if @status.save @@ -175,14 +174,6 @@ class CommandTag::Processor @status.destroy unless @status.destroyed? end - def status_still_semiprivate? - @status.domain_permissions.exists? || @account.followers.where(domain: @account.domain_permissions.select(:domain)).exists? - end - - def update_semiprivate_attribute - @status.semiprivate = @semiprivate.presence || status_still_semiprivate? - end - def normalize(text) text.to_s.strip.downcase end diff --git a/app/models/account.rb b/app/models/account.rb index 301dc6c45..f7de58776 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -383,6 +383,14 @@ class Account < ApplicationRecord end end + def public_domain_permissions? + domain_permissions.where(visibility: [:public, :unlisted]).exists? + end + + def private_domain_permissions? + domain_permissions.where(visibility: [:private, :direct, :limited]).exists? + end + class Field < ActiveModelSerializers::Model attributes :name, :value, :verified_at, :account, :errors diff --git a/app/models/status.rb b/app/models/status.rb index 8bb830c9d..c92d00935 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -160,6 +160,13 @@ class Status < ApplicationRecord delegate :max_visibility_for_domain, to: :account REAL_TIME_WINDOW = 6.hours + SORTED_VISIBILITY = { + direct: 0, + limited: 1, + private: 2, + unlisted: 3, + public: 4, + }.with_indifferent_access.freeze def searchable_by(preloaded = nil) ids = [] @@ -284,6 +291,18 @@ class Status < ApplicationRecord update_status_stat!(key => [public_send(key) - 1, 0].max) end + def less_private_than?(other_visibility) + return false if other_visibility.blank? + + SORTED_VISIBILITY[visibility] > SORTED_VISIBILITY[other_visibility] + end + + def more_private_than?(other_visibility) + return false if other_visibility.blank? + + SORTED_VISIBILITY[visibility] < SORTED_VISIBILITY[other_visibility] + end + def visibility_for_domain(domain) v = domain_permissions.find_by(domain: domain)&.visibility || visibility.to_s @@ -301,6 +320,31 @@ class Status < ApplicationRecord end end + def public_domain_permissions? + return @public_permissions if defined?(@public_permissions) + return @public_permissions = false unless account.local? + + @public_permissions = domain_permissions.where(visibility: [:public, :unlisted]).exists? + end + + def private_domain_permissions? + return @private_permissions if defined?(@private_permissions) + return @private_permissions = false unless account.local? + + @private_permissions = domain_permissions.where(visibility: [:private, :direct, :limited]).exists? + end + + def should_be_semiprivate? + return @should_be_semiprivate if defined?(@should_be_semiprivate) + return @should_be_semiprivate = true if distributable? && (!conversation.public? || private_domain_permissions? || account.private_domain_permissions?) + + @should_be_semiprivate = !distributable? && (conversation.public? || public_domain_permissions? || account.public_domain_permissions?) + end + + def should_limit_visibility? + less_private_than?(thread&.visibility) + end + after_create_commit :increment_counter_caches after_destroy_commit :decrement_counter_caches @@ -316,10 +360,12 @@ class Status < ApplicationRecord before_validation :set_reblog before_validation :set_conversation_perms before_validation :set_local - before_validation :set_semiprivate, if: :local? after_create :set_poll_id + after_save :set_domain_permissions, if: :local? + after_save :set_semiprivate, if: :local? + class << self def selectable_visibilities visibilities.keys - %w(direct limited) @@ -575,16 +621,16 @@ class Status < ApplicationRecord self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply self.visibility = reblog.visibility if reblog? && visibility.nil? self.visibility = (account.locked? ? :private : :public) if visibility.nil? - self.visibility = thread.visibility unless thread.nil? || %w(public unlisted).include?(thread.visibility) || ['direct', 'limited', thread.visibility].include?(visibility.to_s) + self.visibility = thread.visibility if should_limit_visibility? self.sensitive = false if sensitive.nil? if reply? && !thread.nil? self.in_reply_to_account_id = carried_over_reply_to_account_id self.conversation_id = thread.conversation_id if conversation_id.nil? elsif conversation_id.nil? - self.conversation = reply? ? Conversation.new(account_id: nil, public: false) : Conversation.new(account_id: account_id, public: %w(public unlisted).include?(visibility.to_s)) - elsif !reply? && account_id != conversation.account_id - conversation.update!(account_id: account_id, public: %w(public unlisted).include?(visibility.to_s)) + self.conversation = reply? ? Conversation.new(account_id: nil, public: false) : Conversation.new(account_id: account_id, public: distributable?) + elsif !reply? && account_id != conversation.account_id || conversation.public? != distributable? + conversation.update!(account_id: account_id, public: distributable?) end end @@ -610,8 +656,20 @@ class Status < ApplicationRecord end end + def set_domain_permissions + return unless saved_change_to_visibility? + + domain_permissions.transaction do + existing_domains = domain_permissions.select(:domain) + permissions = account.domain_permissions.where.not(domain: existing_domains) + permissions.find_each do |permission| + domain_permissions.create!(domain: permission.domain, visibility: permission.visibility) if less_private_than?(permission.visibility) + end + end + end + def set_semiprivate - self.semiprivate = domain_permissions.exists? || account.followers.where(domain: account.domain_permissions.select(:domain)).exists? + update_column(:semiprivate, should_be_semiprivate?) if semiprivate != should_be_semiprivate? end def update_statistics -- cgit