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
@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
end
def reject_media?
- DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media?
+ DomainBlock.reject_media?(@media_attachment.account.domain)
end
end
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?
private
def skip_reports?
- DomainBlock.find_by(domain: @account.domain)&.reject_reports?
+ DomainBlock.reject_reports?(@account.domain)
end
def object_uris
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|
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
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,
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
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
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
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?
end
def blocked_domain_accounts
- Account.where(domain: blocked_domain)
+ Account.by_domain_and_subdomains(blocked_domain)
end
def media_from_blocked_domain
end
def emojis_from_blocked_domains
- CustomEmoji.where(domain: blocked_domain)
+ CustomEmoji.by_domain_and_subdomains(blocked_domain)
end
end
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
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
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
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
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)
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)