forked from cybrespace/mastodon
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
This commit is contained in:
parent
49ebda4d49
commit
707ddf7808
|
@ -13,7 +13,7 @@ module Admin
|
||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
|
|
||||||
@domain_block = DomainBlock.new(resource_params)
|
@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)
|
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
||||||
@domain_block.save
|
@domain_block.save
|
||||||
|
|
|
@ -18,7 +18,7 @@ module Admin
|
||||||
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
|
@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)
|
@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)
|
@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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -39,6 +39,6 @@ class MediaProxyController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_media?
|
def reject_media?
|
||||||
DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media?
|
DomainBlock.reject_media?(@media_attachment.account.domain)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -380,7 +380,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
def skip_download?
|
def skip_download?
|
||||||
return @skip_download if defined?(@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
|
end
|
||||||
|
|
||||||
def reply_to_local?
|
def reply_to_local?
|
||||||
|
|
|
@ -23,7 +23,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
|
||||||
private
|
private
|
||||||
|
|
||||||
def skip_reports?
|
def skip_reports?
|
||||||
DomainBlock.find_by(domain: @account.domain)&.reject_reports?
|
DomainBlock.reject_reports?(@account.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_uris
|
def object_uris
|
||||||
|
|
|
@ -148,7 +148,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_media
|
def save_media
|
||||||
do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media?
|
do_not_download = DomainBlock.reject_media?(@account.domain)
|
||||||
media_attachments = []
|
media_attachments = []
|
||||||
|
|
||||||
@xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
|
@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
|
end
|
||||||
|
|
||||||
def save_emojis(parent)
|
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
|
return if do_not_download
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ class Account < ApplicationRecord
|
||||||
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
|
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 :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 :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,
|
delegate :email,
|
||||||
:unconfirmed_email,
|
:unconfirmed_email,
|
||||||
|
|
|
@ -39,6 +39,7 @@ class CustomEmoji < ApplicationRecord
|
||||||
scope :local, -> { where(domain: nil) }
|
scope :local, -> { where(domain: nil) }
|
||||||
scope :remote, -> { where.not(domain: nil) }
|
scope :remote, -> { where.not(domain: nil) }
|
||||||
scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) }
|
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
|
remotable_attachment :image, LIMIT
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,41 @@ class DomainBlock < ApplicationRecord
|
||||||
|
|
||||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||||
|
|
||||||
def self.blocked?(domain)
|
class << self
|
||||||
where(domain: domain, severity: :suspend).exists?
|
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
|
end
|
||||||
|
|
||||||
def stricter_than?(other_block)
|
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.suspend? && (silence? || noop?)
|
||||||
return false if other_block.silence? && noop?
|
return false if other_block.silence? && noop?
|
||||||
|
|
||||||
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
|
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Instance
|
||||||
def initialize(resource)
|
def initialize(resource)
|
||||||
@domain = resource.domain
|
@domain = resource.domain
|
||||||
@accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count
|
@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
|
end
|
||||||
|
|
||||||
def cached_sample_accounts
|
def cached_sample_accounts
|
||||||
|
|
|
@ -205,7 +205,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
|
|
||||||
def domain_block
|
def domain_block
|
||||||
return @domain_block if defined?(@domain_block)
|
return @domain_block if defined?(@domain_block)
|
||||||
@domain_block = DomainBlock.find_by(domain: @domain)
|
@domain_block = DomainBlock.rule_for(@domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def key_changed?
|
def key_changed?
|
||||||
|
|
|
@ -76,7 +76,7 @@ class BlockDomainService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked_domain_accounts
|
def blocked_domain_accounts
|
||||||
Account.where(domain: blocked_domain)
|
Account.by_domain_and_subdomains(blocked_domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_from_blocked_domain
|
def media_from_blocked_domain
|
||||||
|
@ -84,6 +84,6 @@ class BlockDomainService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def emojis_from_blocked_domains
|
def emojis_from_blocked_domains
|
||||||
CustomEmoji.where(domain: blocked_domain)
|
CustomEmoji.by_domain_and_subdomains(blocked_domain)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -146,7 +146,7 @@ class ResolveAccountService < BaseService
|
||||||
|
|
||||||
def domain_block
|
def domain_block
|
||||||
return @domain_block if defined?(@domain_block)
|
return @domain_block if defined?(@domain_block)
|
||||||
@domain_block = DomainBlock.find_by(domain: @domain)
|
@domain_block = DomainBlock.rule_for(@domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def atom_url
|
def atom_url
|
||||||
|
|
|
@ -14,7 +14,8 @@ class UnblockDomainService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked_accounts
|
def blocked_accounts
|
||||||
scope = Account.where(domain: domain_block.domain)
|
scope = Account.by_domain_and_subdomains(domain_block.domain)
|
||||||
|
|
||||||
if domain_block.silence?
|
if domain_block.silence?
|
||||||
scope.where(silenced_at: @domain_block.created_at)
|
scope.where(silenced_at: @domain_block.created_at)
|
||||||
else
|
else
|
||||||
|
|
|
@ -26,7 +26,7 @@ class UpdateRemoteProfileService < BaseService
|
||||||
account.note = remote_profile.note || ''
|
account.note = remote_profile.note || ''
|
||||||
account.locked = remote_profile.locked?
|
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?
|
if remote_profile.avatar.present?
|
||||||
account.avatar_remote_url = remote_profile.avatar
|
account.avatar_remote_url = remote_profile.avatar
|
||||||
else
|
else
|
||||||
|
@ -46,7 +46,7 @@ class UpdateRemoteProfileService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_emojis
|
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
|
return if do_not_download
|
||||||
|
|
||||||
|
|
|
@ -687,6 +687,23 @@ RSpec.describe Account, type: :model do
|
||||||
end
|
end
|
||||||
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
|
describe 'expiring' do
|
||||||
it 'returns remote accounts with followers whose subscription expiration date is past or not given' do
|
it 'returns remote accounts with followers whose subscription expiration date is past or not given' do
|
||||||
local = Fabricate(:account, domain: nil)
|
local = Fabricate(:account, domain: nil)
|
||||||
|
|
|
@ -21,23 +21,40 @@ RSpec.describe DomainBlock, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'blocked?' do
|
describe '.blocked?' do
|
||||||
it 'returns true if the domain is suspended' do
|
it 'returns true if the domain is suspended' do
|
||||||
Fabricate(:domain_block, domain: 'domain', severity: :suspend)
|
Fabricate(:domain_block, domain: 'example.com', severity: :suspend)
|
||||||
expect(DomainBlock.blocked?('domain')).to eq true
|
expect(DomainBlock.blocked?('example.com')).to eq true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false even if the domain is silenced' do
|
it 'returns false even if the domain is silenced' do
|
||||||
Fabricate(:domain_block, domain: 'domain', severity: :silence)
|
Fabricate(:domain_block, domain: 'example.com', severity: :silence)
|
||||||
expect(DomainBlock.blocked?('domain')).to eq false
|
expect(DomainBlock.blocked?('example.com')).to eq false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false if the domain is not suspended nor silenced' do
|
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
|
||||||
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
|
it 'returns true if the new block has suspend severity while the old has lower severity' do
|
||||||
suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
|
suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
|
||||||
silence = DomainBlock.new(domain: 'domain', severity: :silence)
|
silence = DomainBlock.new(domain: 'domain', severity: :silence)
|
||||||
|
|
Loading…
Reference in New Issue