about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/about_controller.rb34
-rw-r--r--app/javascript/packs/public.js9
-rw-r--r--app/javascript/styles/mastodon/tables.scss67
-rw-r--r--app/models/domain_block.rb1
-rw-r--r--app/models/form/admin_settings.rb8
-rw-r--r--app/views/about/blocks.html.haml48
-rw-r--r--app/views/admin/settings/edit.html.haml6
-rw-r--r--config/locales/en.yml26
-rw-r--r--config/routes.rb7
-rw-r--r--config/settings.yml2
10 files changed, 199 insertions, 9 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index f41e52aae..5003ae61c 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -4,10 +4,12 @@ class AboutController < ApplicationController
   before_action :set_pack
   layout 'public'
 
-  before_action :require_open_federation!, only: [:show, :more]
+  before_action :require_open_federation!, only: [:show, :more, :blocks]
+  before_action :check_blocklist_enabled, only: [:blocks]
+  before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required?
   before_action :set_body_classes, only: :show
   before_action :set_instance_presenter
-  before_action :set_expires_in
+  before_action :set_expires_in, only: [:show, :more, :terms]
 
   skip_before_action :require_functional!, only: [:more, :terms]
 
@@ -19,12 +21,40 @@ class AboutController < ApplicationController
 
   def terms; end
 
+  def blocks
+    @show_rationale = Setting.show_domain_blocks_rationale == 'all'
+    @show_rationale |= Setting.show_domain_blocks_rationale == 'users' && !current_user.nil? && current_user.functional?
+    @blocks = DomainBlock.with_user_facing_limitations.order('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain').to_a
+  end
+
   private
 
   def require_open_federation!
     not_found if whitelist_mode?
   end
 
+  def check_blocklist_enabled
+    not_found if Setting.show_domain_blocks == 'disabled'
+  end
+
+  def blocklist_account_required?
+    Setting.show_domain_blocks == 'users'
+  end
+
+  def block_severity_text(block)
+    if block.severity == 'suspend'
+      I18n.t('domain_blocks.suspension')
+    else
+      limitations = []
+      limitations << I18n.t('domain_blocks.media_block') if block.reject_media?
+      limitations << I18n.t('domain_blocks.silence') if block.severity == 'silence'
+      limitations.join(', ')
+    end
+  end
+
+  helper_method :block_severity_text
+  helper_method :public_fetch_mode?
+
   def new_user
     User.new.tap do |user|
       user.build_account
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 14317cbf4..76b7e408e 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -132,6 +132,15 @@ function main() {
     return false;
   });
 
+  delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
+    e.preventDefault();
+
+    const classList = this.firstElementChild.classList;
+    classList.toggle('fa-chevron-down');
+    classList.toggle('fa-chevron-up');
+    this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
+  });
+
   delegate(document, '.modal-button', 'click', e => {
     e.preventDefault();
 
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 11ac6dfeb..fe6beba5d 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -241,3 +241,70 @@ a.table-action-link {
     }
   }
 }
+
+.blocks-table {
+  width: 100%;
+  max-width: 100%;
+  border-spacing: 0;
+  border-collapse: collapse;
+  table-layout: fixed;
+  border: 1px solid darken($ui-base-color, 8%);
+
+  thead {
+    border: 1px solid darken($ui-base-color, 8%);
+    background: darken($ui-base-color, 4%);
+    font-weight: 500;
+
+    th.severity-column {
+      width: 120px;
+    }
+
+    th.button-column {
+      width: 23px;
+    }
+  }
+
+  tbody > tr {
+    border: 1px solid darken($ui-base-color, 8%);
+    border-bottom: 0;
+    background: darken($ui-base-color, 4%);
+
+    &:hover {
+      background: darken($ui-base-color, 2%);
+    }
+
+    &.even {
+      background: $ui-base-color;
+
+      &:hover {
+        background: lighten($ui-base-color, 2%);
+      }
+    }
+
+    &.rationale {
+      background: lighten($ui-base-color, 4%);
+      border-top: 0;
+
+      &:hover {
+        background: lighten($ui-base-color, 6%);
+      }
+
+      &.hidden {
+        display: none;
+      }
+    }
+
+    td:first-child {
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+
+  th,
+  td {
+    padding: 8px;
+    line-height: 18px;
+    vertical-align: top;
+    text-align: left;
+  }
+}
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 26cec6ae6..1d0b25772 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -29,6 +29,7 @@ class DomainBlock < ApplicationRecord
 
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
   scope :unprocessed, -> { where(processing: true) }
+  scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)).or(where(reject_unknown: true).or(where(manual_only: true))) }
 
   before_save :set_processing
 
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index c9cd3a87f..08107ee68 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -41,10 +41,8 @@ class Form::AdminSettings
     always_mark_instance_actors_known
     werewolf_status
     spam_check_enabled
-    mark_known_from_follows
-    mark_known_from_mentions
-    mark_known_from_boosts
-    mark_known_from_favourites
+    show_domain_blocks
+    show_domain_blocks_rationale
   ).freeze
 
   BOOLEAN_KEYS = %i(
@@ -92,6 +90,8 @@ class Form::AdminSettings
   validates :site_contact_email, :site_contact_username, presence: true
   validates :site_contact_username, existing_username: true
   validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
+  validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
+  validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
 
   def initialize(_attributes = {})
     super
diff --git a/app/views/about/blocks.html.haml b/app/views/about/blocks.html.haml
new file mode 100644
index 000000000..a81a4d1eb
--- /dev/null
+++ b/app/views/about/blocks.html.haml
@@ -0,0 +1,48 @@
+- content_for :page_title do
+  = t('domain_blocks.title', instance: site_hostname)
+
+.grid
+  .column-0
+    .box-widget.rich-formatting
+      %h2= t('domain_blocks.blocked_domains')
+      %p= t('domain_blocks.description', instance: site_hostname)
+      .table-wrapper
+        %table.blocks-table
+          %thead
+            %tr
+              %th= t('domain_blocks.domain')
+              %th.severity-column= t('domain_blocks.severity')
+              - if @show_rationale
+                %th.button-column
+          %tbody
+            - if @blocks.empty?
+              %tr
+                %td{ colspan: @show_rationale ? 3 : 2 }= t('domain_blocks.no_domain_blocks')
+            - else
+              - @blocks.each_with_index do |block, i|
+                %tr{ class: i % 2 == 0 ? 'even': nil }
+                  %td{ title: block.domain }= block.domain
+                  %td= block_severity_text(block)
+                  - if @show_rationale
+                    %td
+                      - if block.public_comment.present?
+                        %button.icon-button{ title: t('domain_blocks.show_rationale'), 'aria-label' => t('domain_blocks.show_rationale') }
+                          = fa_icon 'chevron-down fw', 'aria-hidden' => true
+                - if @show_rationale
+                  - if block.public_comment.present?
+                    %tr.rationale.hidden
+                      %td{ colspan: 3 }= block.public_comment.presence
+      %h2= t('domain_blocks.severity_legend.title')
+      - if @blocks.any? { |block| block.reject_media? }
+        %h3= t('domain_blocks.media_block')
+        %p= t('domain_blocks.severity_legend.media_block')
+      - if @blocks.any? { |block| block.severity == 'silence' }
+        %h3= t('domain_blocks.silence')
+        %p= t('domain_blocks.severity_legend.silence')
+      - if @blocks.any? { |block| block.severity == 'suspend' }
+        %h3= t('domain_blocks.suspension')
+        %p= t('domain_blocks.severity_legend.suspension')
+        - if public_fetch_mode?
+          %p= t('domain_blocks.severity_legend.suspension_disclaimer')
+  .column-1
+    = render 'application/sidebar'
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index d1549cbe4..92900de85 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -108,6 +108,12 @@
   .fields-group
     = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user halfmod moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
   .fields-group
     = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
     = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 71cb35381..dd92e2d19 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -461,6 +461,13 @@ en:
       enable_keybase:
         desc_html: Allow your users to prove their identity via keybase
         title: Enable keybase integration
+      domain_blocks:
+        all: To everyone
+        disabled: To no one
+        title: Show domain blocks
+        users: To logged-in local users
+      domain_blocks_rationale:
+        title: Show rationale
       hero:
         desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
         title: Hero image
@@ -680,6 +687,25 @@ en:
     people:
       one: "%{count} monster"
       other: "%{count} monsters"
+  domain_blocks:
+    blocked_domains: List of limited and blocked domains
+    description: This is the list of servers that %{instance} limits or reject federation with.
+    domain: Domain
+    media_block: Media block
+    no_domain_blocks: "(No domain blocks)"
+    severity: Severity
+    severity_legend:
+      media_block: Media files coming from the server are neither fetched, stored, or displayed to the user.
+      silence: Accounts from silenced servers can be found, followed and interacted with, but their toots will not appear in the public timelines, and notifications from them will not reach local users who are not following them.
+      suspension: No content from suspended servers is stored or displayed, nor is any content sent to them. Interactions from suspended servers are ignored.
+      suspension_disclaimer: Suspended servers may occasionally retrieve public content from this server.
+      title: Severities
+    show_rationale: Show rationale
+    silence: Silence
+    suspension: Suspension
+    title: "%{instance} List of blocked instances"
+  domain_validator:
+    invalid_domain: is not a valid domain name
   errors:
     '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
diff --git a/config/routes.rb b/config/routes.rb
index e05f1ed2b..9891965ec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -425,9 +425,10 @@ Rails.application.routes.draw do
 
   get '/web/(*any)', to: 'home#index', as: :web
 
-  get '/about',      to: 'about#show'
-  get '/about/more', to: 'about#more'
-  get '/terms',      to: 'about#terms'
+  get '/about',        to: 'about#show'
+  get '/about/more',   to: 'about#more'
+  get '/about/blocks', to: 'about#blocks'
+  get '/terms',        to: 'about#terms'
 
   get '/policies',   to: 'domain_policy#show'
 
diff --git a/config/settings.yml b/config/settings.yml
index 04d8bbaea..4f4ac0db8 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -77,6 +77,8 @@ defaults: &defaults
   mark_known_from_mentions: true
   mark_known_from_boosts: true
   mark_known_from_favourites: false
+  show_domain_blocks: 'disabled'
+  show_domain_blocks_rationale: 'disabled'
 
 development:
   <<: *defaults