Browse Source

Add whitelist mode (#11291)

tags/v3.0.0rc1
Eugen Rochko 2 months ago
parent
commit
24552b5160
No account linked to committer's email address
44 changed files with 302 additions and 53 deletions
  1. 5
    0
      app/controllers/about_controller.rb
  2. 2
    0
      app/controllers/activitypub/base_controller.rb
  3. 1
    1
      app/controllers/activitypub/inboxes_controller.rb
  4. 40
    0
      app/controllers/admin/domain_allows_controller.rb
  5. 25
    3
      app/controllers/admin/instances_controller.rb
  6. 9
    0
      app/controllers/api/base_controller.rb
  7. 2
    0
      app/controllers/api/v1/accounts_controller.rb
  8. 2
    0
      app/controllers/api/v1/apps_controller.rb
  9. 2
    1
      app/controllers/api/v1/instances/activity_controller.rb
  10. 2
    1
      app/controllers/api/v1/instances/peers_controller.rb
  11. 1
    0
      app/controllers/api/v1/instances_controller.rb
  12. 3
    1
      app/controllers/application_controller.rb
  13. 1
    0
      app/controllers/concerns/account_owned_concern.rb
  14. 3
    2
      app/controllers/directories_controller.rb
  15. 1
    1
      app/controllers/home_controller.rb
  16. 1
    0
      app/controllers/media_controller.rb
  17. 2
    0
      app/controllers/media_proxy_controller.rb
  18. 3
    2
      app/controllers/public_timelines_controller.rb
  19. 1
    0
      app/controllers/remote_interaction_controller.rb
  20. 1
    0
      app/controllers/tags_controller.rb
  21. 9
    1
      app/helpers/domain_control_helper.rb
  22. 33
    0
      app/models/domain_allow.rb
  23. 2
    1
      app/models/instance.rb
  24. 4
    0
      app/models/instance_filter.rb
  25. 11
    0
      app/policies/domain_allow_policy.rb
  26. 1
    1
      app/services/concerns/payloadable.rb
  27. 11
    0
      app/services/unallow_domain_service.rb
  28. 14
    0
      app/views/admin/domain_allows/new.html.haml
  29. 22
    13
      app/views/admin/instances/index.html.haml
  30. 3
    1
      app/views/admin/instances/show.html.haml
  31. 15
    13
      app/views/admin/settings/edit.html.haml
  32. 1
    1
      app/views/auth/registrations/new.html.haml
  33. 6
    3
      app/views/layouts/public.html.haml
  34. 5
    0
      config/initializers/2_whitelist_mode.rb
  35. 7
    0
      config/locales/en.yml
  36. 2
    0
      config/locales/simple_form.en.yml
  37. 1
    1
      config/navigation.rb
  38. 1
    0
      config/routes.rb
  39. 9
    0
      db/migrate/20190705002136_create_domain_allows.rb
  40. 8
    1
      db/schema.rb
  41. 19
    3
      lib/mastodon/domains_cli.rb
  42. 3
    0
      spec/fabricators/domain_allow_fabricator.rb
  43. 5
    0
      spec/models/domain_allow_spec.rb
  44. 3
    2
      streaming/index.js

+ 5
- 0
app/controllers/about_controller.rb View File

@@ -3,6 +3,7 @@
3 3
 class AboutController < ApplicationController
4 4
   layout 'public'
5 5
 
6
+  before_action :require_open_federation!, only: [:show, :more]
6 7
   before_action :set_body_classes, only: :show
7 8
   before_action :set_instance_presenter
8 9
   before_action :set_expires_in
@@ -19,6 +20,10 @@ class AboutController < ApplicationController
19 20
 
20 21
   private
21 22
 
23
+  def require_open_federation!
24
+    not_found if whitelist_mode?
25
+  end
26
+
22 27
   def new_user
23 28
     User.new.tap do |user|
24 29
       user.build_account

+ 2
- 0
app/controllers/activitypub/base_controller.rb View File

@@ -1,6 +1,8 @@
1 1
 # frozen_string_literal: true
2 2
 
3 3
 class ActivityPub::BaseController < Api::BaseController
4
+  skip_before_action :require_authenticated_user!
5
+
4 6
   private
5 7
 
6 8
   def set_cache_headers

+ 1
- 1
app/controllers/activitypub/inboxes_controller.rb View File

@@ -1,6 +1,6 @@
1 1
 # frozen_string_literal: true
2 2
 
3
-class ActivityPub::InboxesController < Api::BaseController
3
+class ActivityPub::InboxesController < ActivityPub::BaseController
4 4
   include SignatureVerification
5 5
   include JsonLdHelper
6 6
   include AccountOwnedConcern

+ 40
- 0
app/controllers/admin/domain_allows_controller.rb View File

@@ -0,0 +1,40 @@
1
+# frozen_string_literal: true
2
+
3
+class Admin::DomainAllowsController < Admin::BaseController
4
+  before_action :set_domain_allow, only: [:destroy]
5
+
6
+  def new
7
+    authorize :domain_allow, :create?
8
+
9
+    @domain_allow = DomainAllow.new(domain: params[:_domain])
10
+  end
11
+
12
+  def create
13
+    authorize :domain_allow, :create?
14
+
15
+    @domain_allow = DomainAllow.new(resource_params)
16
+
17
+    if @domain_allow.save
18
+      log_action :create, @domain_allow
19
+      redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
20
+    else
21
+      render :new
22
+    end
23
+  end
24
+
25
+  def destroy
26
+    authorize @domain_allow, :destroy?
27
+    UnallowDomainService.new.call(@domain_allow)
28
+    redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
29
+  end
30
+
31
+  private
32
+
33
+  def set_domain_allow
34
+    @domain_allow = DomainAllow.find(params[:id])
35
+  end
36
+
37
+  def resource_params
38
+    params.require(:domain_allow).permit(:domain)
39
+  end
40
+end

+ 25
- 3
app/controllers/admin/instances_controller.rb View File

@@ -2,6 +2,10 @@
2 2
 
3 3
 module Admin
4 4
   class InstancesController < BaseController
5
+    before_action :set_domain_block, only: :show
6
+    before_action :set_domain_allow, only: :show
7
+    before_action :set_instance, only: :show
8
+
5 9
     def index
6 10
       authorize :instance, :index?
7 11
 
@@ -11,20 +15,38 @@ module Admin
11 15
     def show
12 16
       authorize :instance, :show?
13 17
 
14
-      @instance        = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
15 18
       @following_count = Follow.where(account: Account.where(domain: params[:id])).count
16 19
       @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
17 20
       @reports_count   = Report.where(target_account: Account.where(domain: params[:id])).count
18 21
       @blocks_count    = Block.where(target_account: Account.where(domain: params[:id])).count
19 22
       @available       = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
20 23
       @media_storage   = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
21
-      @domain_block    = DomainBlock.rule_for(params[:id])
22 24
     end
23 25
 
24 26
     private
25 27
 
28
+    def set_domain_block
29
+      @domain_block = DomainBlock.rule_for(params[:id])
30
+    end
31
+
32
+    def set_domain_allow
33
+      @domain_allow = DomainAllow.rule_for(params[:id])
34
+    end
35
+
36
+    def set_instance
37
+      resource   = Account.by_domain_accounts.find_by(domain: params[:id])
38
+      resource ||= @domain_block
39
+      resource ||= @domain_allow
40
+
41
+      if resource
42
+        @instance = Instance.new(resource)
43
+      else
44
+        not_found
45
+      end
46
+    end
47
+
26 48
     def filtered_instances
27
-      InstanceFilter.new(filter_params).results
49
+      InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
28 50
     end
29 51
 
30 52
     def paginated_instances

+ 9
- 0
app/controllers/api/base_controller.rb View File

@@ -9,6 +9,7 @@ class Api::BaseController < ApplicationController
9 9
   skip_before_action :store_current_location
10 10
   skip_before_action :require_functional!
11 11
 
12
+  before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
12 13
   before_action :set_cache_headers
13 14
 
14 15
   protect_from_forgery with: :null_session
@@ -69,6 +70,10 @@ class Api::BaseController < ApplicationController
69 70
     nil
70 71
   end
71 72
 
73
+  def require_authenticated_user!
74
+    render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
75
+  end
76
+
72 77
   def require_user!
73 78
     if !current_user
74 79
       render json: { error: 'This method requires an authenticated user' }, status: 422
@@ -94,4 +99,8 @@ class Api::BaseController < ApplicationController
94 99
   def set_cache_headers
95 100
     response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
96 101
   end
102
+
103
+  def disallow_unauthenticated_api_access?
104
+    authorized_fetch_mode?
105
+  end
97 106
 end

+ 2
- 0
app/controllers/api/v1/accounts_controller.rb View File

@@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController
12 12
   before_action :check_account_suspension, only: [:show]
13 13
   before_action :check_enabled_registrations, only: [:create]
14 14
 
15
+  skip_before_action :require_authenticated_user!, only: :create
16
+
15 17
   respond_to :json
16 18
 
17 19
   def show

+ 2
- 0
app/controllers/api/v1/apps_controller.rb View File

@@ -1,6 +1,8 @@
1 1
 # frozen_string_literal: true
2 2
 
3 3
 class Api::V1::AppsController < Api::BaseController
4
+  skip_before_action :require_authenticated_user!
5
+
4 6
   def create
5 7
     @app = Doorkeeper::Application.create!(application_options)
6 8
     render json: @app, serializer: REST::ApplicationSerializer

+ 2
- 1
app/controllers/api/v1/instances/activity_controller.rb View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 class Api::V1::Instances::ActivityController < Api::BaseController
4 4
   before_action :require_enabled_api!
5
+
5 6
   skip_before_action :set_cache_headers
6 7
 
7 8
   respond_to :json
@@ -33,6 +34,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
33 34
   end
34 35
 
35 36
   def require_enabled_api!
36
-    head 404 unless Setting.activity_api_enabled
37
+    head 404 unless Setting.activity_api_enabled && !whitelist_mode?
37 38
   end
38 39
 end

+ 2
- 1
app/controllers/api/v1/instances/peers_controller.rb View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 class Api::V1::Instances::PeersController < Api::BaseController
4 4
   before_action :require_enabled_api!
5
+
5 6
   skip_before_action :set_cache_headers
6 7
 
7 8
   respond_to :json
@@ -14,6 +15,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
14 15
   private
15 16
 
16 17
   def require_enabled_api!
17
-    head 404 unless Setting.peers_api_enabled
18
+    head 404 unless Setting.peers_api_enabled && !whitelist_mode?
18 19
   end
19 20
 end

+ 1
- 0
app/controllers/api/v1/instances_controller.rb View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 class Api::V1::InstancesController < Api::BaseController
4 4
   respond_to :json
5
+
5 6
   skip_before_action :set_cache_headers
6 7
 
7 8
   def show

+ 3
- 1
app/controllers/application_controller.rb View File

@@ -11,12 +11,14 @@ class ApplicationController < ActionController::Base
11 11
   include UserTrackingConcern
12 12
   include SessionTrackingConcern
13 13
   include CacheConcern
14
+  include DomainControlHelper
14 15
 
15 16
   helper_method :current_account
16 17
   helper_method :current_session
17 18
   helper_method :current_theme
18 19
   helper_method :single_user_mode?
19 20
   helper_method :use_seamless_external_login?
21
+  helper_method :whitelist_mode?
20 22
 
21 23
   rescue_from ActionController::RoutingError, with: :not_found
22 24
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
@@ -38,7 +40,7 @@ class ApplicationController < ActionController::Base
38 40
   end
39 41
 
40 42
   def authorized_fetch_mode?
41
-    ENV['AUTHORIZED_FETCH'] == 'true'
43
+    ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
42 44
   end
43 45
 
44 46
   def public_fetch_mode?

+ 1
- 0
app/controllers/concerns/account_owned_concern.rb View File

@@ -4,6 +4,7 @@ module AccountOwnedConcern
4 4
   extend ActiveSupport::Concern
5 5
 
6 6
   included do
7
+    before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
7 8
     before_action :set_account, if: :account_required?
8 9
     before_action :check_account_approval, if: :account_required?
9 10
     before_action :check_account_suspension, if: :account_required?

+ 3
- 2
app/controllers/directories_controller.rb View File

@@ -3,7 +3,8 @@
3 3
 class DirectoriesController < ApplicationController
4 4
   layout 'public'
5 5
 
6
-  before_action :check_enabled
6
+  before_action :authenticate_user!, if: :whitelist_mode?
7
+  before_action :require_enabled!
7 8
   before_action :set_instance_presenter
8 9
   before_action :set_tag, only: :show
9 10
   before_action :set_tags
@@ -19,7 +20,7 @@ class DirectoriesController < ApplicationController
19 20
 
20 21
   private
21 22
 
22
-  def check_enabled
23
+  def require_enabled!
23 24
     return not_found unless Setting.profile_directory
24 25
   end
25 26
 

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

@@ -55,7 +55,7 @@ class HomeController < ApplicationController
55 55
   end
56 56
 
57 57
   def default_redirect_path
58
-    if request.path.start_with?('/web')
58
+    if request.path.start_with?('/web') || whitelist_mode?
59 59
       new_user_session_path
60 60
     elsif single_user_mode?
61 61
       short_account_path(Account.local.without_suspended.where('id > 0').first)

+ 1
- 0
app/controllers/media_controller.rb View File

@@ -5,6 +5,7 @@ class MediaController < ApplicationController
5 5
 
6 6
   skip_before_action :store_current_location
7 7
 
8
+  before_action :authenticate_user!, if: :whitelist_mode?
8 9
   before_action :set_media_attachment
9 10
   before_action :verify_permitted_status!
10 11
   before_action :check_playable, only: :player

+ 2
- 0
app/controllers/media_proxy_controller.rb View File

@@ -5,6 +5,8 @@ class MediaProxyController < ApplicationController
5 5
 
6 6
   skip_before_action :store_current_location
7 7
 
8
+  before_action :authenticate_user!, if: :whitelist_mode?
9
+
8 10
   def show
9 11
     RedisLock.acquire(lock_options) do |lock|
10 12
       if lock.acquired?

+ 3
- 2
app/controllers/public_timelines_controller.rb View File

@@ -3,7 +3,8 @@
3 3
 class PublicTimelinesController < ApplicationController
4 4
   layout 'public'
5 5
 
6
-  before_action :check_enabled
6
+  before_action :authenticate_user!, if: :whitelist_mode?
7
+  before_action :require_enabled!
7 8
   before_action :set_body_classes
8 9
   before_action :set_instance_presenter
9 10
 
@@ -16,7 +17,7 @@ class PublicTimelinesController < ApplicationController
16 17
 
17 18
   private
18 19
 
19
-  def check_enabled
20
+  def require_enabled!
20 21
     not_found unless Setting.timeline_preview
21 22
   end
22 23
 

+ 1
- 0
app/controllers/remote_interaction_controller.rb View File

@@ -5,6 +5,7 @@ class RemoteInteractionController < ApplicationController
5 5
 
6 6
   layout 'modal'
7 7
 
8
+  before_action :authenticate_user!, if: :whitelist_mode?
8 9
   before_action :set_interaction_type
9 10
   before_action :set_status
10 11
   before_action :set_body_classes

+ 1
- 0
app/controllers/tags_controller.rb View File

@@ -8,6 +8,7 @@ class TagsController < ApplicationController
8 8
   layout 'public'
9 9
 
10 10
   before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
11
+  before_action :authenticate_user!, if: :whitelist_mode?
11 12
   before_action :set_tag
12 13
   before_action :set_body_classes
13 14
   before_action :set_instance_presenter

+ 9
- 1
app/helpers/domain_control_helper.rb View File

@@ -12,6 +12,14 @@ module DomainControlHelper
12 12
       end
13 13
     end
14 14
 
15
-    DomainBlock.blocked?(domain)
15
+    if whitelist_mode?
16
+      !DomainAllow.allowed?(domain)
17
+    else
18
+      DomainBlock.blocked?(domain)
19
+    end
20
+  end
21
+
22
+  def whitelist_mode?
23
+    Rails.configuration.x.whitelist_mode
16 24
   end
17 25
 end

+ 33
- 0
app/models/domain_allow.rb View File

@@ -0,0 +1,33 @@
1
+# frozen_string_literal: true
2
+
3
+# == Schema Information
4
+#
5
+# Table name: domain_allows
6
+#
7
+#  id         :bigint(8)        not null, primary key
8
+#  domain     :string           default(""), not null
9
+#  created_at :datetime         not null
10
+#  updated_at :datetime         not null
11
+#
12
+
13
+class DomainAllow < ApplicationRecord
14
+  include DomainNormalizable
15
+
16
+  validates :domain, presence: true, uniqueness: true
17
+
18
+  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
19
+
20
+  class << self
21
+    def allowed?(domain)
22
+      !rule_for(domain).nil?
23
+    end
24
+
25
+    def rule_for(domain)
26
+      return if domain.blank?
27
+
28
+      uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') }
29
+
30
+      find_by(domain: uri.normalized_host)
31
+    end
32
+  end
33
+end

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

@@ -7,8 +7,9 @@ class Instance
7 7
 
8 8
   def initialize(resource)
9 9
     @domain         = resource.domain
10
-    @accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count
10
+    @accounts_count = resource.respond_to?(:accounts_count) ? resource.accounts_count : nil
11 11
     @domain_block   = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain)
12
+    @domain_allow   = resource.is_a?(DomainAllow) ? resource : DomainAllow.rule_for(domain)
12 13
   end
13 14
 
14 15
   def countable?

+ 4
- 0
app/models/instance_filter.rb View File

@@ -12,6 +12,10 @@ class InstanceFilter
12 12
       scope = DomainBlock
13 13
       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
14 14
       scope.order(id: :desc)
15
+    elsif params[:allowed].present?
16
+      scope = DomainAllow
17
+      scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
18
+      scope.order(id: :desc)
15 19
     else
16 20
       scope = Account.remote
17 21
       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?

+ 11
- 0
app/policies/domain_allow_policy.rb View File

@@ -0,0 +1,11 @@
1
+# frozen_string_literal: true
2
+
3
+class DomainAllowPolicy < ApplicationPolicy
4
+  def create?
5
+    admin?
6
+  end
7
+
8
+  def destroy?
9
+    admin?
10
+  end
11
+end

+ 1
- 1
app/services/concerns/payloadable.rb View File

@@ -14,6 +14,6 @@ module Payloadable
14 14
   end
15 15
 
16 16
   def signing_enabled?
17
-    ENV['AUTHORIZED_FETCH'] != 'true'
17
+    ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.whitelist_mode
18 18
   end
19 19
 end

+ 11
- 0
app/services/unallow_domain_service.rb View File

@@ -0,0 +1,11 @@
1
+# frozen_string_literal: true
2
+
3
+class UnallowDomainService < BaseService
4
+  def call(domain_allow)
5
+    Account.where(domain: domain_allow.domain).find_each do |account|
6
+      SuspendAccountService.new.call(account, destroy: true)
7
+    end
8
+
9
+    domain_allow.destroy
10
+  end
11
+end

+ 14
- 0
app/views/admin/domain_allows/new.html.haml View File

@@ -0,0 +1,14 @@
1
+- content_for :header_tags do
2
+  = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
3
+
4
+- content_for :page_title do
5
+  = t('admin.domain_allows.add_new')
6
+
7
+= simple_form_for @domain_allow, url: admin_domain_allows_path do |f|
8
+  = render 'shared/error_messages', object: @domain_allow
9
+
10
+  .fields-group
11
+    = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), required: true
12
+
13
+  .actions
14
+    = f.button :button, t('admin.domain_allows.add_new'), type: :submit

+ 22
- 13
app/views/admin/instances/index.html.haml View File

@@ -6,24 +6,30 @@
6 6
     %strong= t('admin.instances.moderation.title')
7 7
     %ul
8 8
       %li= filter_link_to t('admin.instances.moderation.all'), limited: nil
9
-      %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
9
+
10
+      - unless whitelist_mode?
11
+        %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
10 12
 
11 13
   %div{ style: 'flex: 1 1 auto; text-align: right' }
12
-    = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
14
+    - if whitelist_mode?
15
+      = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
16
+    - else
17
+      = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
13 18
 
14
-= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
15
-  .fields-group
16
-    - Admin::FilterHelper::INSTANCES_FILTERS.each do |key|
17
-      - if params[key].present?
18
-        = hidden_field_tag key, params[key]
19
+- unless whitelist_mode?
20
+  = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
21
+    .fields-group
22
+      - Admin::FilterHelper::INSTANCES_FILTERS.each do |key|
23
+        - if params[key].present?
24
+          = hidden_field_tag key, params[key]
19 25
 
20
-    - %i(by_domain).each do |key|
21
-      .input.string.optional
22
-        = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}")
26
+      - %i(by_domain).each do |key|
27
+        .input.string.optional
28
+          = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}")
23 29
 
24
-    .actions
25
-      %button= t('admin.accounts.search')
26
-      = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative'
30
+      .actions
31
+        %button= t('admin.accounts.search')
32
+        = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative'
27 33
 
28 34
 %hr.spacer/
29 35
 
@@ -47,8 +53,11 @@
47 53
               - unless first_item
48 54
                 &bull;
49 55
               = t('admin.domain_blocks.rejecting_reports')
56
+          - elsif whitelist_mode?
57
+            = t('admin.accounts.whitelisted')
50 58
           - else
51 59
             = t('admin.accounts.no_limits_imposed')
52 60
       - if instance.countable?
53 61
         .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true
62
+
54 63
 = paginate paginated_instances

+ 3
- 1
app/views/admin/instances/show.html.haml View File

@@ -38,7 +38,9 @@
38 38
     = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'
39 39
 
40 40
   %div{ style: 'float: right' }
41
-    - if @domain_block
41
+    - if @domain_allow
42
+      = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
43
+    - elsif @domain_block
42 44
       = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
43 45
     - else
44 46
       = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'

+ 15
- 13
app/views/admin/settings/edit.html.haml View File

@@ -42,11 +42,12 @@
42 42
 
43 43
   %hr.spacer/
44 44
 
45
-  .fields-group
46
-    = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
45
+  - unless whitelist_mode?
46
+    .fields-group
47
+      = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
47 48
 
48
-  .fields-group
49
-    = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
49
+    .fields-group
50
+      = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
50 51
 
51 52
   .fields-group
52 53
     = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
@@ -54,17 +55,18 @@
54 55
   .fields-group
55 56
     = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
56 57
 
57
-  .fields-group
58
-    = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
58
+  - unless whitelist_mode?
59
+    .fields-group
60
+      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
59 61
 
60
-  .fields-group
61
-    = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
62
+    .fields-group
63
+      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
62 64
 
63
-  .fields-group
64
-    = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
65
+    .fields-group
66
+      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
65 67
 
66
-  .fields-group
67
-    = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
68
+    .fields-group
69
+      = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
68 70
 
69 71
   .fields-group
70 72
     = f.input :spam_check_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.spam_check_enabled.title'), hint: t('admin.settings.spam_check_enabled.desc_html')
@@ -76,7 +78,7 @@
76 78
 
77 79
   .fields-group
78 80
     = 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 }
79
-    = 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 }
81
+    = 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?
80 82
     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
81 83
     = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
82 84
 

+ 1
- 1
app/views/auth/registrations/new.html.haml View File

@@ -33,7 +33,7 @@
33 33
   = f.input :invite_code, as: :hidden
34 34
 
35 35
   .fields-group
36
-    = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
36
+    = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
37 37
 
38 38
   .actions
39 39
     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit

+ 6
- 3
app/views/layouts/public.html.haml View File

@@ -10,10 +10,13 @@
10 10
             = link_to root_url, class: 'brand' do
11 11
               = svg_logo_full
12 12
 
13
-            = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
14
-            = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
15
-            = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
13
+            - unless whitelist_mode?
14
+              = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
15
+              = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
16
+              = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
17
+
16 18
           .nav-center
19
+
17 20
           .nav-right
18 21
             - if user_signed_in?
19 22
               = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'

+ 5
- 0
config/initializers/2_whitelist_mode.rb View File

@@ -0,0 +1,5 @@
1
+# frozen_string_literal: true
2
+
3
+Rails.application.configure do
4
+  config.x.whitelist_mode = ENV['WHITELIST_MODE'] == 'true'
5
+end

+ 7
- 0
config/locales/en.yml View File

@@ -186,6 +186,7 @@ en:
186 186
       username: Username
187 187
       warn: Warn
188 188
       web: Web
189
+      whitelisted: Whitelisted
189 190
     action_logs:
190 191
       actions:
191 192
         assigned_to_self_report: "%{name} assigned report %{target} to themselves"
@@ -269,6 +270,11 @@ en:
269 270
       week_interactions: interactions this week
270 271
       week_users_active: active this week
271 272
       week_users_new: users this week
273
+    domain_allows:
274
+      add_new: Whitelist domain
275
+      created_msg: Domain has been successfully whitelisted
276
+      destroyed_msg: Domain has been removed from the whitelist
277
+      undo: Remove from whitelist
272 278
     domain_blocks:
273 279
       add_new: Add new domain block
274 280
       created_msg: Domain block is now being processed
@@ -524,6 +530,7 @@ en:
524 530
     apply_for_account: Request an invite
525 531
     change_password: Password
526 532
     checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
533
+    checkbox_agreement_without_rules_html: I agree to the <a href="%{terms_path}" target="_blank">terms of service</a>
527 534
     delete_account: Delete account
528 535
     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
529 536
     didnt_get_confirmation: Didn't receive confirmation instructions?

+ 2
- 0
config/locales/simple_form.en.yml View File

@@ -38,6 +38,8 @@ en:
38 38
         setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
39 39
         username: Your username will be unique on %{domain}
40 40
         whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
41
+      domain_allow:
42
+        domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored
41 43
       featured_tag:
42 44
         name: 'You might want to use one of these:'
43 45
       imports:

+ 1
- 1
config/navigation.rb View File

@@ -39,7 +39,7 @@ SimpleNavigation::Configuration.run do |navigation|
39 39
       s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
40 40
       s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
41 41
       s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path
42
-      s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.admin? }
42
+      s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? }
43 43
       s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
44 44
     end
45 45
 

+ 1
- 0
config/routes.rb View File

@@ -154,6 +154,7 @@ Rails.application.routes.draw do
154 154
   namespace :admin do
155 155
     get '/dashboard', to: 'dashboard#index'
156 156
 
157
+    resources :domain_allows, only: [:new, :create, :show, :destroy]
157 158
     resources :domain_blocks, only: [:new, :create, :show, :destroy]
158 159
     resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
159 160
     resources :action_logs, only: [:index]

+ 9
- 0
db/migrate/20190705002136_create_domain_allows.rb View File

@@ -0,0 +1,9 @@
1
+class CreateDomainAllows < ActiveRecord::Migration[5.2]
2
+  def change
3
+    create_table :domain_allows do |t|
4
+      t.string :domain, default: '', null: false, index: { unique: true }
5
+
6
+      t.timestamps
7
+    end
8
+  end
9
+end

+ 8
- 1
db/schema.rb View File

@@ -10,7 +10,7 @@
10 10
 #
11 11
 # It's strongly recommended that you check this file into your version control system.
12 12
 
13
-ActiveRecord::Schema.define(version: 2019_07_26_175042) do
13
+ActiveRecord::Schema.define(version: 2019_07_28_084117) do
14 14
 
15 15
   # These are extensions that must be enabled in order to support this database
16 16
   enable_extension "plpgsql"
@@ -245,6 +245,13 @@ ActiveRecord::Schema.define(version: 2019_07_26_175042) do
245 245
     t.index ["account_id"], name: "index_custom_filters_on_account_id"
246 246
   end
247 247
 
248
+  create_table "domain_allows", force: :cascade do |t|
249
+    t.string "domain", default: "", null: false
250
+    t.datetime "created_at", null: false
251
+    t.datetime "updated_at", null: false
252
+    t.index ["domain"], name: "index_domain_allows_on_domain", unique: true
253
+  end
254
+
248 255
   create_table "domain_blocks", force: :cascade do |t|
249 256
     t.string "domain", default: "", null: false
250 257
     t.datetime "created_at", null: false

+ 19
- 3
lib/mastodon/domains_cli.rb View File

@@ -12,17 +12,33 @@ module Mastodon
12 12
     end
13 13
 
14 14
     option :dry_run, type: :boolean
15
-    desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace'
15
+    option :whitelist_mode, type: :boolean
16
+    desc 'purge [DOMAIN]', 'Remove accounts from a DOMAIN without a trace'
16 17
     long_desc <<-LONG_DESC
17 18
       Remove all accounts from a given DOMAIN without leaving behind any
18 19
       records. Unlike a suspension, if the DOMAIN still exists in the wild,
19 20
       it means the accounts could return if they are resolved again.
21
+
22
+      When the --whitelist-mode option is given, instead of purging accounts
23
+      from a single domain, all accounts from domains that are not whitelisted
24
+      are removed from the database.
20 25
     LONG_DESC
21
-    def purge(domain)
26
+    def purge(domain = nil)
22 27
       removed = 0
23 28
       dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
24 29
 
25
-      Account.where(domain: domain).find_each do |account|
30
+      scope = begin
31
+        if options[:whitelist_mode]
32
+          Account.remote.where.not(domain: DomainAllow.pluck(:domain))
33
+        elsif domain.present?
34
+          Account.remote.where(domain: domain)
35
+        else
36
+          say('No domain given', :red)
37
+          exit(1)
38
+        end
39
+      end
40
+
41
+      scope.find_each do |account|
26 42
         SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run]
27 43
         removed += 1
28 44
         say('.', :green, false)

+ 3
- 0
spec/fabricators/domain_allow_fabricator.rb View File

@@ -0,0 +1,3 @@
1
+Fabricator(:domain_allow) do
2
+  domain "MyString"
3
+end

+ 5
- 0
spec/models/domain_allow_spec.rb View File

@@ -0,0 +1,5 @@
1
+require 'rails_helper'
2
+
3
+RSpec.describe DomainAllow, type: :model do
4
+  pending "add some examples to (or delete) #{__FILE__}"
5
+end

+ 3
- 2
streaming/index.js View File

@@ -12,6 +12,7 @@ const uuid = require('uuid');
12 12
 const fs = require('fs');
13 13
 
14 14
 const env = process.env.NODE_ENV || 'development';
15
+const alwaysRequireAuth = process.env.WHITELIST_MODE === 'true' || process.env.AUTHORIZED_FETCH === 'true';
15 16
 
16 17
 dotenv.config({
17 18
   path: env === 'production' ? '.env.production' : '.env',
@@ -271,7 +272,7 @@ const startWorker = (workerId) => {
271 272
 
272 273
   const wsVerifyClient = (info, cb) => {
273 274
     const location = url.parse(info.req.url, true);
274
-    const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
275
+    const authRequired = alwaysRequireAuth || !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
275 276
     const allowedScopes = [];
276 277
 
277 278
     if (authRequired) {
@@ -306,7 +307,7 @@ const startWorker = (workerId) => {
306 307
       return;
307 308
     }
308 309
 
309
-    const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
310
+    const authRequired = alwaysRequireAuth || !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
310 311
     const allowedScopes = [];
311 312
 
312 313
     if (authRequired) {

Loading…
Cancel
Save