Browse Source

Change domain blocks to automatically support subdomains (#11138)

* Change domain blocks to automatically support subdomains

If a more authoritative domain is blocked (example.com), then the
same block will be applied to a subdomain (foo.example.com)

* Match subdomains of existing accounts when blocking/unblocking domains

* Improve code style
tags/v2.9.1
Eugen Rochko 5 months ago
parent
commit
707ddf7808
No account linked to committer's email address

+ 1
- 1
app/controllers/admin/domain_blocks_controller.rb View File

@@ -13,7 +13,7 @@ module Admin
authorize :domain_block, :create?

@domain_block = DomainBlock.new(resource_params)
existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil

if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save

+ 1
- 1
app/controllers/admin/instances_controller.rb View File

@@ -18,7 +18,7 @@ module Admin
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@domain_block = DomainBlock.find_by(domain: params[:id])
@domain_block = DomainBlock.rule_for(params[:id])
end

private

+ 1
- 1
app/controllers/media_proxy_controller.rb View File

@@ -39,6 +39,6 @@ class MediaProxyController < ApplicationController
end

def reject_media?
DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media?
DomainBlock.reject_media?(@media_attachment.account.domain)
end
end

+ 1
- 1
app/lib/activitypub/activity/create.rb View File

@@ -380,7 +380,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity

def skip_download?
return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
@skip_download ||= DomainBlock.reject_media?(@account.domain)
end

def reply_to_local?

+ 1
- 1
app/lib/activitypub/activity/flag.rb View File

@@ -23,7 +23,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
private

def skip_reports?
DomainBlock.find_by(domain: @account.domain)&.reject_reports?
DomainBlock.reject_reports?(@account.domain)
end

def object_uris

+ 2
- 2
app/lib/ostatus/activity/creation.rb View File

@@ -148,7 +148,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
end

def save_media
do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media?
do_not_download = DomainBlock.reject_media?(@account.domain)
media_attachments = []

@xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
@@ -176,7 +176,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
end

def save_emojis(parent)
do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
do_not_download = DomainBlock.reject_media?(parent.account.domain)

return if do_not_download


+ 1
- 0
app/models/account.rb View File

@@ -98,6 +98,7 @@ class Account < ApplicationRecord
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
scope :popular, -> { order('account_stats.followers_count desc') }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }

delegate :email,
:unconfirmed_email,

+ 1
- 0
app/models/custom_emoji.rb View File

@@ -39,6 +39,7 @@ class CustomEmoji < ApplicationRecord
scope :local, -> { where(domain: nil) }
scope :remote, -> { where.not(domain: nil) }
scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }

remotable_attachment :image, LIMIT


+ 30
- 3
app/models/domain_block.rb View File

@@ -24,14 +24,41 @@ class DomainBlock < ApplicationRecord

scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }

def self.blocked?(domain)
where(domain: domain, severity: :suspend).exists?
class << self
def suspend?(domain)
!!rule_for(domain)&.suspend?
end

def silence?(domain)
!!rule_for(domain)&.silence?
end

def reject_media?(domain)
!!rule_for(domain)&.reject_media?
end

def reject_reports?(domain)
!!rule_for(domain)&.reject_reports?
end

alias blocked? suspend?

def rule_for(domain)
return if domain.blank?

uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') }
segments = uri.normalized_host.split('.')
variants = segments.map.with_index { |_, i| segments[i..-1].join('.') }

where(domain: variants[0..-2]).order(Arel.sql('char_length(domain) desc')).first
end
end

def stricter_than?(other_block)
return true if suspend?
return true if suspend?
return false if other_block.suspend? && (silence? || noop?)
return false if other_block.silence? && noop?

(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
end


+ 1
- 1
app/models/instance.rb View File

@@ -8,7 +8,7 @@ class Instance
def initialize(resource)
@domain = resource.domain
@accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count
@domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.find_by(domain: domain)
@domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain)
end

def cached_sample_accounts

+ 1
- 1
app/services/activitypub/process_account_service.rb View File

@@ -205,7 +205,7 @@ class ActivityPub::ProcessAccountService < BaseService

def domain_block
return @domain_block if defined?(@domain_block)
@domain_block = DomainBlock.find_by(domain: @domain)
@domain_block = DomainBlock.rule_for(@domain)
end

def key_changed?

+ 2
- 2
app/services/block_domain_service.rb View File

@@ -76,7 +76,7 @@ class BlockDomainService < BaseService
end

def blocked_domain_accounts
Account.where(domain: blocked_domain)
Account.by_domain_and_subdomains(blocked_domain)
end

def media_from_blocked_domain
@@ -84,6 +84,6 @@ class BlockDomainService < BaseService
end

def emojis_from_blocked_domains
CustomEmoji.where(domain: blocked_domain)
CustomEmoji.by_domain_and_subdomains(blocked_domain)
end
end

+ 1
- 1
app/services/resolve_account_service.rb View File

@@ -146,7 +146,7 @@ class ResolveAccountService < BaseService

def domain_block
return @domain_block if defined?(@domain_block)
@domain_block = DomainBlock.find_by(domain: @domain)
@domain_block = DomainBlock.rule_for(@domain)
end

def atom_url

+ 2
- 1
app/services/unblock_domain_service.rb View File

@@ -14,7 +14,8 @@ class UnblockDomainService < BaseService
end

def blocked_accounts
scope = Account.where(domain: domain_block.domain)
scope = Account.by_domain_and_subdomains(domain_block.domain)

if domain_block.silence?
scope.where(silenced_at: @domain_block.created_at)
else

+ 2
- 2
app/services/update_remote_profile_service.rb View File

@@ -26,7 +26,7 @@ class UpdateRemoteProfileService < BaseService
account.note = remote_profile.note || ''
account.locked = remote_profile.locked?

if !account.suspended? && !DomainBlock.find_by(domain: account.domain)&.reject_media?
if !account.suspended? && !DomainBlock.reject_media?(account.domain)
if remote_profile.avatar.present?
account.avatar_remote_url = remote_profile.avatar
else
@@ -46,7 +46,7 @@ class UpdateRemoteProfileService < BaseService
end

def save_emojis
do_not_download = DomainBlock.find_by(domain: account.domain)&.reject_media?
do_not_download = DomainBlock.reject_media?(account.domain)

return if do_not_download


+ 17
- 0
spec/models/account_spec.rb View File

@@ -687,6 +687,23 @@ RSpec.describe Account, type: :model do
end
end

describe 'by_domain_and_subdomains' do
it 'returns exact domain matches' do
account = Fabricate(:account, domain: 'example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
end

it 'returns subdomains' do
account = Fabricate(:account, domain: 'foo.example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
end

it 'does not return partially matching domains' do
account = Fabricate(:account, domain: 'grexample.com')
expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account]
end
end

describe 'expiring' do
it 'returns remote accounts with followers whose subscription expiration date is past or not given' do
local = Fabricate(:account, domain: nil)

+ 24
- 7
spec/models/domain_block_spec.rb View File

@@ -21,23 +21,40 @@ RSpec.describe DomainBlock, type: :model do
end
end

describe 'blocked?' do
describe '.blocked?' do
it 'returns true if the domain is suspended' do
Fabricate(:domain_block, domain: 'domain', severity: :suspend)
expect(DomainBlock.blocked?('domain')).to eq true
Fabricate(:domain_block, domain: 'example.com', severity: :suspend)
expect(DomainBlock.blocked?('example.com')).to eq true
end

it 'returns false even if the domain is silenced' do
Fabricate(:domain_block, domain: 'domain', severity: :silence)
expect(DomainBlock.blocked?('domain')).to eq false
Fabricate(:domain_block, domain: 'example.com', severity: :silence)
expect(DomainBlock.blocked?('example.com')).to eq false
end

it 'returns false if the domain is not suspended nor silenced' do
expect(DomainBlock.blocked?('domain')).to eq false
expect(DomainBlock.blocked?('example.com')).to eq false
end
end

describe 'stricter_than?' do
describe '.rule_for' do
it 'returns rule matching a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('example.com')).to eq block
end

it 'returns a rule matching a subdomain of a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block
end

it 'returns a rule matching a blocked subdomain' do
block = Fabricate(:domain_block, domain: 'sub.example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block
end
end

describe '#stricter_than?' do
it 'returns true if the new block has suspend severity while the old has lower severity' do
suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
silence = DomainBlock.new(domain: 'domain', severity: :silence)

Loading…
Cancel
Save