Add option to include resolved DNS records when blacklisting e-mail domains in admin UI (#13254)
* Add shortcuts to blacklist a user's e-mail domain in admin UI * Add option to blacklist resolved MX and IP records for e-mail domains
This commit is contained in:
		| @@ -6,12 +6,12 @@ module Admin | ||||
|  | ||||
|     def index | ||||
|       authorize :email_domain_block, :index? | ||||
|       @email_domain_blocks = EmailDomainBlock.page(params[:page]) | ||||
|       @email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page]) | ||||
|     end | ||||
|  | ||||
|     def new | ||||
|       authorize :email_domain_block, :create? | ||||
|       @email_domain_block = EmailDomainBlock.new | ||||
|       @email_domain_block = EmailDomainBlock.new(domain: params[:_domain]) | ||||
|     end | ||||
|  | ||||
|     def create | ||||
| @@ -21,6 +21,28 @@ module Admin | ||||
|  | ||||
|       if @email_domain_block.save | ||||
|         log_action :create, @email_domain_block | ||||
|  | ||||
|         if @email_domain_block.with_dns_records? | ||||
|           hostnames = [] | ||||
|           ips       = [] | ||||
|  | ||||
|           Resolv::DNS.open do |dns| | ||||
|             dns.timeouts = 1 | ||||
|  | ||||
|             hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } | ||||
|  | ||||
|             ([@email_domain_block.domain] + hostnames).uniq.each do |hostname| | ||||
|               ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s }) | ||||
|               ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s }) | ||||
|             end | ||||
|           end | ||||
|  | ||||
|           (hostnames + ips).each do |hostname| | ||||
|             another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block) | ||||
|             log_action :create, another_email_domain_block if another_email_domain_block.save | ||||
|           end | ||||
|         end | ||||
|  | ||||
|         redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') | ||||
|       else | ||||
|         render :new | ||||
| @@ -41,7 +63,7 @@ module Admin | ||||
|     end | ||||
|  | ||||
|     def resource_params | ||||
|       params.require(:email_domain_block).permit(:domain) | ||||
|       params.require(:email_domain_block).permit(:domain, :with_dns_records) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -7,13 +7,27 @@ | ||||
| #  domain     :string           default(""), not null | ||||
| #  created_at :datetime         not null | ||||
| #  updated_at :datetime         not null | ||||
| #  parent_id  :bigint(8) | ||||
| # | ||||
|  | ||||
| class EmailDomainBlock < ApplicationRecord | ||||
|   include DomainNormalizable | ||||
|  | ||||
|   belongs_to :parent, class_name: 'EmailDomainBlock', optional: true | ||||
|   has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy | ||||
|  | ||||
|   validates :domain, presence: true, uniqueness: true, domain: true | ||||
|  | ||||
|   def with_dns_records=(val) | ||||
|     @with_dns_records = ActiveModel::Type::Boolean.new.cast(val) | ||||
|   end | ||||
|  | ||||
|   def with_dns_records? | ||||
|     @with_dns_records | ||||
|   end | ||||
|  | ||||
|   alias with_dns_records with_dns_records? | ||||
|  | ||||
|   def self.block?(email) | ||||
|     _, domain = email.split('@', 2) | ||||
|  | ||||
|   | ||||
| @@ -96,10 +96,17 @@ | ||||
|               = table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user) | ||||
|  | ||||
|           %tr | ||||
|             %th= t('admin.accounts.email') | ||||
|             %td= @account.user_email | ||||
|             %th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email') | ||||
|             %td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email | ||||
|             %td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user) | ||||
|  | ||||
|           %tr | ||||
|             %td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}") | ||||
|  | ||||
|           - if can?(:create, :email_domain_block) | ||||
|             %tr | ||||
|               %td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last) | ||||
|  | ||||
|           - if @account.user_unconfirmed_email.present? | ||||
|             %tr | ||||
|               %th= t('admin.accounts.unconfirmed_email') | ||||
| @@ -204,7 +211,7 @@ | ||||
|         = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account) | ||||
|  | ||||
|       - unless @account.local? | ||||
|         - if DomainBlock.where(domain: @account.domain).exists? | ||||
|         - if DomainBlock.rule_for(@account.domain) | ||||
|           = link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button' | ||||
|         - else | ||||
|           = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' | ||||
|   | ||||
| @@ -3,3 +3,13 @@ | ||||
|     %samp= email_domain_block.domain | ||||
|   %td | ||||
|     = table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(email_domain_block), method: :delete | ||||
|  | ||||
| - email_domain_block.children.each do |child_email_domain_block| | ||||
|   %tr | ||||
|     %td | ||||
|       %samp= child_email_domain_block.domain | ||||
|       %span.muted-hint | ||||
|         = surround '(', ')' do | ||||
|           = t('admin.email_domain_blocks.from_html', domain: content_tag(:samp, email_domain_block.domain)) | ||||
|     %td | ||||
|       = table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(child_email_domain_block), method: :delete | ||||
|   | ||||
| @@ -5,7 +5,10 @@ | ||||
|   = render 'shared/error_messages', object: @email_domain_block | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :domain, wrapper: :with_label, label: t('admin.email_domain_blocks.domain') | ||||
|     = f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain') | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :with_dns_records, as: :boolean, wrapper: :with_label | ||||
|  | ||||
|   .actions | ||||
|     = f.button :button, t('.create'), type: :submit | ||||
|   | ||||
| @@ -92,6 +92,7 @@ en: | ||||
|       delete: Delete | ||||
|       destroyed_msg: Moderation note successfully destroyed! | ||||
|     accounts: | ||||
|       add_email_domain_block: Blacklist e-mail domain | ||||
|       approve: Approve | ||||
|       approve_all: Approve all | ||||
|       are_you_sure: Are you sure? | ||||
| @@ -172,6 +173,7 @@ en: | ||||
|         staff: Staff | ||||
|         user: User | ||||
|       search: Search | ||||
|       search_same_email_domain: Other users with the same e-mail domain | ||||
|       search_same_ip: Other users with the same IP | ||||
|       shared_inbox_url: Shared inbox URL | ||||
|       show: | ||||
| @@ -358,6 +360,7 @@ en: | ||||
|       destroyed_msg: Successfully deleted e-mail domain from blacklist | ||||
|       domain: Domain | ||||
|       empty: No e-mail domains currently blacklisted. | ||||
|       from_html: from %{domain} | ||||
|       new: | ||||
|         create: Add domain | ||||
|         title: New e-mail blacklist entry | ||||
|   | ||||
| @@ -54,6 +54,9 @@ en: | ||||
|         whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word | ||||
|       domain_allow: | ||||
|         domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored | ||||
|       email_domain_block: | ||||
|         domain: This can be the domain name that shows up in the e-mail address, the MX record that domain resolves to, or IP of the server that MX record resolves to. Those will be checked upon user sign-up and the sign-up will be rejected. | ||||
|         with_dns_records: An attempt to resolve the given domain's DNS records will be made and the results will also be blacklisted | ||||
|       featured_tag: | ||||
|         name: 'You might want to use one of these:' | ||||
|       form_challenge: | ||||
| @@ -152,6 +155,8 @@ en: | ||||
|         username: Username | ||||
|         username_or_email: Username or Email | ||||
|         whole_word: Whole word | ||||
|       email_domain_block: | ||||
|         with_dns_records: Include MX records and IPs of the domain | ||||
|       featured_tag: | ||||
|         name: Hashtag | ||||
|       interactions: | ||||
|   | ||||
| @@ -0,0 +1,5 @@ | ||||
| class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2] | ||||
|   def change | ||||
|     add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false | ||||
|   end | ||||
| end | ||||
| @@ -10,7 +10,7 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 2020_03_12_144258) do | ||||
| ActiveRecord::Schema.define(version: 2020_03_12_185443) do | ||||
|  | ||||
|   # These are extensions that must be enabled in order to support this database | ||||
|   enable_extension "plpgsql" | ||||
| @@ -336,6 +336,7 @@ ActiveRecord::Schema.define(version: 2020_03_12_144258) do | ||||
|     t.string "domain", default: "", null: false | ||||
|     t.datetime "created_at", null: false | ||||
|     t.datetime "updated_at", null: false | ||||
|     t.bigint "parent_id" | ||||
|     t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true | ||||
|   end | ||||
|  | ||||
| @@ -869,6 +870,7 @@ ActiveRecord::Schema.define(version: 2020_03_12_144258) do | ||||
|   add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade | ||||
|   add_foreign_key "conversation_mutes", "conversations", on_delete: :cascade | ||||
|   add_foreign_key "custom_filters", "accounts", on_delete: :cascade | ||||
|   add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade | ||||
|   add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade | ||||
|   add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade | ||||
|   add_foreign_key "featured_tags", "accounts", on_delete: :cascade | ||||
|   | ||||
		Reference in New Issue
	
	Block a user