Add whitelist mode (#11291)
This commit is contained in:
		| @@ -3,6 +3,7 @@ | ||||
| class AboutController < ApplicationController | ||||
|   layout 'public' | ||||
|  | ||||
|   before_action :require_open_federation!, only: [:show, :more] | ||||
|   before_action :set_body_classes, only: :show | ||||
|   before_action :set_instance_presenter | ||||
|   before_action :set_expires_in | ||||
| @@ -19,6 +20,10 @@ class AboutController < ApplicationController | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def require_open_federation! | ||||
|     not_found if whitelist_mode? | ||||
|   end | ||||
|  | ||||
|   def new_user | ||||
|     User.new.tap do |user| | ||||
|       user.build_account | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class ActivityPub::BaseController < Api::BaseController | ||||
|   skip_before_action :require_authenticated_user! | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_cache_headers | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class ActivityPub::InboxesController < Api::BaseController | ||||
| class ActivityPub::InboxesController < ActivityPub::BaseController | ||||
|   include SignatureVerification | ||||
|   include JsonLdHelper | ||||
|   include AccountOwnedConcern | ||||
|   | ||||
							
								
								
									
										40
									
								
								app/controllers/admin/domain_allows_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/controllers/admin/domain_allows_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Admin::DomainAllowsController < Admin::BaseController | ||||
|   before_action :set_domain_allow, only: [:destroy] | ||||
|  | ||||
|   def new | ||||
|     authorize :domain_allow, :create? | ||||
|  | ||||
|     @domain_allow = DomainAllow.new(domain: params[:_domain]) | ||||
|   end | ||||
|  | ||||
|   def create | ||||
|     authorize :domain_allow, :create? | ||||
|  | ||||
|     @domain_allow = DomainAllow.new(resource_params) | ||||
|  | ||||
|     if @domain_allow.save | ||||
|       log_action :create, @domain_allow | ||||
|       redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg') | ||||
|     else | ||||
|       render :new | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def destroy | ||||
|     authorize @domain_allow, :destroy? | ||||
|     UnallowDomainService.new.call(@domain_allow) | ||||
|     redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg') | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_domain_allow | ||||
|     @domain_allow = DomainAllow.find(params[:id]) | ||||
|   end | ||||
|  | ||||
|   def resource_params | ||||
|     params.require(:domain_allow).permit(:domain) | ||||
|   end | ||||
| end | ||||
| @@ -2,6 +2,10 @@ | ||||
|  | ||||
| module Admin | ||||
|   class InstancesController < BaseController | ||||
|     before_action :set_domain_block, only: :show | ||||
|     before_action :set_domain_allow, only: :show | ||||
|     before_action :set_instance, only: :show | ||||
|  | ||||
|     def index | ||||
|       authorize :instance, :index? | ||||
|  | ||||
| @@ -11,20 +15,38 @@ module Admin | ||||
|     def show | ||||
|       authorize :instance, :show? | ||||
|  | ||||
|       @instance        = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id])) | ||||
|       @following_count = Follow.where(account: Account.where(domain: params[:id])).count | ||||
|       @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count | ||||
|       @reports_count   = Report.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) | ||||
|       @media_storage   = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) | ||||
|       @domain_block    = DomainBlock.rule_for(params[:id]) | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
|     def set_domain_block | ||||
|       @domain_block = DomainBlock.rule_for(params[:id]) | ||||
|     end | ||||
|  | ||||
|     def set_domain_allow | ||||
|       @domain_allow = DomainAllow.rule_for(params[:id]) | ||||
|     end | ||||
|  | ||||
|     def set_instance | ||||
|       resource   = Account.by_domain_accounts.find_by(domain: params[:id]) | ||||
|       resource ||= @domain_block | ||||
|       resource ||= @domain_allow | ||||
|  | ||||
|       if resource | ||||
|         @instance = Instance.new(resource) | ||||
|       else | ||||
|         not_found | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     def filtered_instances | ||||
|       InstanceFilter.new(filter_params).results | ||||
|       InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results | ||||
|     end | ||||
|  | ||||
|     def paginated_instances | ||||
|   | ||||
| @@ -9,6 +9,7 @@ class Api::BaseController < ApplicationController | ||||
|   skip_before_action :store_current_location | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? | ||||
|   before_action :set_cache_headers | ||||
|  | ||||
|   protect_from_forgery with: :null_session | ||||
| @@ -69,6 +70,10 @@ class Api::BaseController < ApplicationController | ||||
|     nil | ||||
|   end | ||||
|  | ||||
|   def require_authenticated_user! | ||||
|     render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user | ||||
|   end | ||||
|  | ||||
|   def require_user! | ||||
|     if !current_user | ||||
|       render json: { error: 'This method requires an authenticated user' }, status: 422 | ||||
| @@ -94,4 +99,8 @@ class Api::BaseController < ApplicationController | ||||
|   def set_cache_headers | ||||
|     response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' | ||||
|   end | ||||
|  | ||||
|   def disallow_unauthenticated_api_access? | ||||
|     authorized_fetch_mode? | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   before_action :check_account_suspension, only: [:show] | ||||
|   before_action :check_enabled_registrations, only: [:create] | ||||
|  | ||||
|   skip_before_action :require_authenticated_user!, only: :create | ||||
|  | ||||
|   respond_to :json | ||||
|  | ||||
|   def show | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Api::V1::AppsController < Api::BaseController | ||||
|   skip_before_action :require_authenticated_user! | ||||
|  | ||||
|   def create | ||||
|     @app = Doorkeeper::Application.create!(application_options) | ||||
|     render json: @app, serializer: REST::ApplicationSerializer | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| class Api::V1::Instances::ActivityController < Api::BaseController | ||||
|   before_action :require_enabled_api! | ||||
|  | ||||
|   skip_before_action :set_cache_headers | ||||
|  | ||||
|   respond_to :json | ||||
| @@ -33,6 +34,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def require_enabled_api! | ||||
|     head 404 unless Setting.activity_api_enabled | ||||
|     head 404 unless Setting.activity_api_enabled && !whitelist_mode? | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| class Api::V1::Instances::PeersController < Api::BaseController | ||||
|   before_action :require_enabled_api! | ||||
|  | ||||
|   skip_before_action :set_cache_headers | ||||
|  | ||||
|   respond_to :json | ||||
| @@ -14,6 +15,6 @@ class Api::V1::Instances::PeersController < Api::BaseController | ||||
|   private | ||||
|  | ||||
|   def require_enabled_api! | ||||
|     head 404 unless Setting.peers_api_enabled | ||||
|     head 404 unless Setting.peers_api_enabled && !whitelist_mode? | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| class Api::V1::InstancesController < Api::BaseController | ||||
|   respond_to :json | ||||
|  | ||||
|   skip_before_action :set_cache_headers | ||||
|  | ||||
|   def show | ||||
|   | ||||
| @@ -11,12 +11,14 @@ class ApplicationController < ActionController::Base | ||||
|   include UserTrackingConcern | ||||
|   include SessionTrackingConcern | ||||
|   include CacheConcern | ||||
|   include DomainControlHelper | ||||
|  | ||||
|   helper_method :current_account | ||||
|   helper_method :current_session | ||||
|   helper_method :current_theme | ||||
|   helper_method :single_user_mode? | ||||
|   helper_method :use_seamless_external_login? | ||||
|   helper_method :whitelist_mode? | ||||
|  | ||||
|   rescue_from ActionController::RoutingError, with: :not_found | ||||
|   rescue_from ActiveRecord::RecordNotFound, with: :not_found | ||||
| @@ -38,7 +40,7 @@ class ApplicationController < ActionController::Base | ||||
|   end | ||||
|  | ||||
|   def authorized_fetch_mode? | ||||
|     ENV['AUTHORIZED_FETCH'] == 'true' | ||||
|     ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode | ||||
|   end | ||||
|  | ||||
|   def public_fetch_mode? | ||||
|   | ||||
| @@ -4,6 +4,7 @@ module AccountOwnedConcern | ||||
|   extend ActiveSupport::Concern | ||||
|  | ||||
|   included do | ||||
|     before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json } | ||||
|     before_action :set_account, if: :account_required? | ||||
|     before_action :check_account_approval, if: :account_required? | ||||
|     before_action :check_account_suspension, if: :account_required? | ||||
|   | ||||
| @@ -3,7 +3,8 @@ | ||||
| class DirectoriesController < ApplicationController | ||||
|   layout 'public' | ||||
|  | ||||
|   before_action :check_enabled | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|   before_action :require_enabled! | ||||
|   before_action :set_instance_presenter | ||||
|   before_action :set_tag, only: :show | ||||
|   before_action :set_tags | ||||
| @@ -19,7 +20,7 @@ class DirectoriesController < ApplicationController | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def check_enabled | ||||
|   def require_enabled! | ||||
|     return not_found unless Setting.profile_directory | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,7 @@ class HomeController < ApplicationController | ||||
|   end | ||||
|  | ||||
|   def default_redirect_path | ||||
|     if request.path.start_with?('/web') | ||||
|     if request.path.start_with?('/web') || whitelist_mode? | ||||
|       new_user_session_path | ||||
|     elsif single_user_mode? | ||||
|       short_account_path(Account.local.without_suspended.where('id > 0').first) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ class MediaController < ApplicationController | ||||
|  | ||||
|   skip_before_action :store_current_location | ||||
|  | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|   before_action :set_media_attachment | ||||
|   before_action :verify_permitted_status! | ||||
|   before_action :check_playable, only: :player | ||||
|   | ||||
| @@ -5,6 +5,8 @@ class MediaProxyController < ApplicationController | ||||
|  | ||||
|   skip_before_action :store_current_location | ||||
|  | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|  | ||||
|   def show | ||||
|     RedisLock.acquire(lock_options) do |lock| | ||||
|       if lock.acquired? | ||||
|   | ||||
| @@ -3,7 +3,8 @@ | ||||
| class PublicTimelinesController < ApplicationController | ||||
|   layout 'public' | ||||
|  | ||||
|   before_action :check_enabled | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|   before_action :require_enabled! | ||||
|   before_action :set_body_classes | ||||
|   before_action :set_instance_presenter | ||||
|  | ||||
| @@ -16,7 +17,7 @@ class PublicTimelinesController < ApplicationController | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def check_enabled | ||||
|   def require_enabled! | ||||
|     not_found unless Setting.timeline_preview | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ class RemoteInteractionController < ApplicationController | ||||
|  | ||||
|   layout 'modal' | ||||
|  | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|   before_action :set_interaction_type | ||||
|   before_action :set_status | ||||
|   before_action :set_body_classes | ||||
|   | ||||
| @@ -8,6 +8,7 @@ class TagsController < ApplicationController | ||||
|   layout 'public' | ||||
|  | ||||
|   before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } | ||||
|   before_action :authenticate_user!, if: :whitelist_mode? | ||||
|   before_action :set_tag | ||||
|   before_action :set_body_classes | ||||
|   before_action :set_instance_presenter | ||||
|   | ||||
| @@ -12,6 +12,14 @@ module DomainControlHelper | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     DomainBlock.blocked?(domain) | ||||
|     if whitelist_mode? | ||||
|       !DomainAllow.allowed?(domain) | ||||
|     else | ||||
|       DomainBlock.blocked?(domain) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def whitelist_mode? | ||||
|     Rails.configuration.x.whitelist_mode | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										33
									
								
								app/models/domain_allow.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/models/domain_allow.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| # == Schema Information | ||||
| # | ||||
| # Table name: domain_allows | ||||
| # | ||||
| #  id         :bigint(8)        not null, primary key | ||||
| #  domain     :string           default(""), not null | ||||
| #  created_at :datetime         not null | ||||
| #  updated_at :datetime         not null | ||||
| # | ||||
|  | ||||
| class DomainAllow < ApplicationRecord | ||||
|   include DomainNormalizable | ||||
|  | ||||
|   validates :domain, presence: true, uniqueness: true | ||||
|  | ||||
|   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } | ||||
|  | ||||
|   class << self | ||||
|     def allowed?(domain) | ||||
|       !rule_for(domain).nil? | ||||
|     end | ||||
|  | ||||
|     def rule_for(domain) | ||||
|       return if domain.blank? | ||||
|  | ||||
|       uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') } | ||||
|  | ||||
|       find_by(domain: uri.normalized_host) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -7,8 +7,9 @@ class Instance | ||||
|  | ||||
|   def initialize(resource) | ||||
|     @domain         = resource.domain | ||||
|     @accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count | ||||
|     @accounts_count = resource.respond_to?(:accounts_count) ? resource.accounts_count : nil | ||||
|     @domain_block   = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain) | ||||
|     @domain_allow   = resource.is_a?(DomainAllow) ? resource : DomainAllow.rule_for(domain) | ||||
|   end | ||||
|  | ||||
|   def countable? | ||||
|   | ||||
| @@ -12,6 +12,10 @@ class InstanceFilter | ||||
|       scope = DomainBlock | ||||
|       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? | ||||
|       scope.order(id: :desc) | ||||
|     elsif params[:allowed].present? | ||||
|       scope = DomainAllow | ||||
|       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? | ||||
|       scope.order(id: :desc) | ||||
|     else | ||||
|       scope = Account.remote | ||||
|       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/policies/domain_allow_policy.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/policies/domain_allow_policy.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class DomainAllowPolicy < ApplicationPolicy | ||||
|   def create? | ||||
|     admin? | ||||
|   end | ||||
|  | ||||
|   def destroy? | ||||
|     admin? | ||||
|   end | ||||
| end | ||||
| @@ -14,6 +14,6 @@ module Payloadable | ||||
|   end | ||||
|  | ||||
|   def signing_enabled? | ||||
|     ENV['AUTHORIZED_FETCH'] != 'true' | ||||
|     ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.whitelist_mode | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/services/unallow_domain_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/services/unallow_domain_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class UnallowDomainService < BaseService | ||||
|   def call(domain_allow) | ||||
|     Account.where(domain: domain_allow.domain).find_each do |account| | ||||
|       SuspendAccountService.new.call(account, destroy: true) | ||||
|     end | ||||
|  | ||||
|     domain_allow.destroy | ||||
|   end | ||||
| end | ||||
							
								
								
									
										14
									
								
								app/views/admin/domain_allows/new.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/views/admin/domain_allows/new.html.haml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| - content_for :header_tags do | ||||
|   = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous' | ||||
|  | ||||
| - content_for :page_title do | ||||
|   = t('admin.domain_allows.add_new') | ||||
|  | ||||
| = simple_form_for @domain_allow, url: admin_domain_allows_path do |f| | ||||
|   = render 'shared/error_messages', object: @domain_allow | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), required: true | ||||
|  | ||||
|   .actions | ||||
|     = f.button :button, t('admin.domain_allows.add_new'), type: :submit | ||||
| @@ -6,24 +6,30 @@ | ||||
|     %strong= t('admin.instances.moderation.title') | ||||
|     %ul | ||||
|       %li= filter_link_to t('admin.instances.moderation.all'), limited: nil | ||||
|       %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' | ||||
|  | ||||
|       - unless whitelist_mode? | ||||
|         %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' | ||||
|  | ||||
|   %div{ style: 'flex: 1 1 auto; text-align: right' } | ||||
|     = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button' | ||||
|     - if whitelist_mode? | ||||
|       = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button' | ||||
|     - else | ||||
|       = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button' | ||||
|  | ||||
| = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do | ||||
|   .fields-group | ||||
|     - Admin::FilterHelper::INSTANCES_FILTERS.each do |key| | ||||
|       - if params[key].present? | ||||
|         = hidden_field_tag key, params[key] | ||||
| - unless whitelist_mode? | ||||
|   = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do | ||||
|     .fields-group | ||||
|       - Admin::FilterHelper::INSTANCES_FILTERS.each do |key| | ||||
|         - if params[key].present? | ||||
|           = hidden_field_tag key, params[key] | ||||
|  | ||||
|     - %i(by_domain).each do |key| | ||||
|       .input.string.optional | ||||
|         = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}") | ||||
|       - %i(by_domain).each do |key| | ||||
|         .input.string.optional | ||||
|           = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}") | ||||
|  | ||||
|     .actions | ||||
|       %button= t('admin.accounts.search') | ||||
|       = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative' | ||||
|       .actions | ||||
|         %button= t('admin.accounts.search') | ||||
|         = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative' | ||||
|  | ||||
| %hr.spacer/ | ||||
|  | ||||
| @@ -47,8 +53,11 @@ | ||||
|               - unless first_item | ||||
|                 • | ||||
|               = t('admin.domain_blocks.rejecting_reports') | ||||
|           - elsif whitelist_mode? | ||||
|             = t('admin.accounts.whitelisted') | ||||
|           - else | ||||
|             = t('admin.accounts.no_limits_imposed') | ||||
|       - if instance.countable? | ||||
|         .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true | ||||
|  | ||||
| = paginate paginated_instances | ||||
|   | ||||
| @@ -38,7 +38,9 @@ | ||||
|     = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' | ||||
|  | ||||
|   %div{ style: 'float: right' } | ||||
|     - if @domain_block | ||||
|     - if @domain_allow | ||||
|       = 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 } | ||||
|     - elsif @domain_block | ||||
|       = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button' | ||||
|     - else | ||||
|       = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' | ||||
|   | ||||
| @@ -42,11 +42,12 @@ | ||||
|  | ||||
|   %hr.spacer/ | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html') | ||||
|   - unless whitelist_mode? | ||||
|     .fields-group | ||||
|       = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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') | ||||
|     .fields-group | ||||
|       = 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') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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 @@ | ||||
|   .fields-group | ||||
|     = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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') | ||||
|   - unless whitelist_mode? | ||||
|     .fields-group | ||||
|       = 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') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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') | ||||
|     .fields-group | ||||
|       = 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') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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') | ||||
|     .fields-group | ||||
|       = 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') | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html') | ||||
|     .fields-group | ||||
|       = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html') | ||||
|  | ||||
|   .fields-group | ||||
|     = 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 @@ | ||||
|  | ||||
|   .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 } | ||||
|     = 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? | ||||
|     = 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 } | ||||
|     = 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') | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ | ||||
|   = f.input :invite_code, as: :hidden | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path) | ||||
|     = 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) | ||||
|  | ||||
|   .actions | ||||
|     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit | ||||
|   | ||||
| @@ -10,10 +10,13 @@ | ||||
|             = link_to root_url, class: 'brand' do | ||||
|               = svg_logo_full | ||||
|  | ||||
|             = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory | ||||
|             = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' | ||||
|             = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' | ||||
|             - unless whitelist_mode? | ||||
|               = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory | ||||
|               = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' | ||||
|               = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' | ||||
|  | ||||
|           .nav-center | ||||
|  | ||||
|           .nav-right | ||||
|             - if user_signed_in? | ||||
|               = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn' | ||||
|   | ||||
							
								
								
									
										5
									
								
								config/initializers/2_whitelist_mode.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								config/initializers/2_whitelist_mode.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| Rails.application.configure do | ||||
|   config.x.whitelist_mode = ENV['WHITELIST_MODE'] == 'true' | ||||
| end | ||||
| @@ -186,6 +186,7 @@ en: | ||||
|       username: Username | ||||
|       warn: Warn | ||||
|       web: Web | ||||
|       whitelisted: Whitelisted | ||||
|     action_logs: | ||||
|       actions: | ||||
|         assigned_to_self_report: "%{name} assigned report %{target} to themselves" | ||||
| @@ -269,6 +270,11 @@ en: | ||||
|       week_interactions: interactions this week | ||||
|       week_users_active: active this week | ||||
|       week_users_new: users this week | ||||
|     domain_allows: | ||||
|       add_new: Whitelist domain | ||||
|       created_msg: Domain has been successfully whitelisted | ||||
|       destroyed_msg: Domain has been removed from the whitelist | ||||
|       undo: Remove from whitelist | ||||
|     domain_blocks: | ||||
|       add_new: Add new domain block | ||||
|       created_msg: Domain block is now being processed | ||||
| @@ -524,6 +530,7 @@ en: | ||||
|     apply_for_account: Request an invite | ||||
|     change_password: Password | ||||
|     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> | ||||
|     checkbox_agreement_without_rules_html: I agree to the <a href="%{terms_path}" target="_blank">terms of service</a> | ||||
|     delete_account: Delete account | ||||
|     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation. | ||||
|     didnt_get_confirmation: Didn't receive confirmation instructions? | ||||
|   | ||||
| @@ -38,6 +38,8 @@ en: | ||||
|         setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed | ||||
|         username: Your username will be unique on %{domain} | ||||
|         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 | ||||
|       featured_tag: | ||||
|         name: 'You might want to use one of these:' | ||||
|       imports: | ||||
|   | ||||
| @@ -39,7 +39,7 @@ SimpleNavigation::Configuration.run do |navigation| | ||||
|       s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts} | ||||
|       s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path | ||||
|       s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path | ||||
|       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? } | ||||
|       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? } | ||||
|       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? } | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -154,6 +154,7 @@ Rails.application.routes.draw do | ||||
|   namespace :admin do | ||||
|     get '/dashboard', to: 'dashboard#index' | ||||
|  | ||||
|     resources :domain_allows, only: [:new, :create, :show, :destroy] | ||||
|     resources :domain_blocks, only: [:new, :create, :show, :destroy] | ||||
|     resources :email_domain_blocks, only: [:index, :new, :create, :destroy] | ||||
|     resources :action_logs, only: [:index] | ||||
|   | ||||
							
								
								
									
										9
									
								
								db/migrate/20190705002136_create_domain_allows.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								db/migrate/20190705002136_create_domain_allows.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| class CreateDomainAllows < ActiveRecord::Migration[5.2] | ||||
|   def change | ||||
|     create_table :domain_allows do |t| | ||||
|       t.string :domain, default: '', null: false, index: { unique: true } | ||||
|  | ||||
|       t.timestamps | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -10,7 +10,7 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 2019_07_26_175042) do | ||||
| ActiveRecord::Schema.define(version: 2019_07_28_084117) do | ||||
|  | ||||
|   # These are extensions that must be enabled in order to support this database | ||||
|   enable_extension "plpgsql" | ||||
| @@ -245,6 +245,13 @@ ActiveRecord::Schema.define(version: 2019_07_26_175042) do | ||||
|     t.index ["account_id"], name: "index_custom_filters_on_account_id" | ||||
|   end | ||||
|  | ||||
|   create_table "domain_allows", force: :cascade do |t| | ||||
|     t.string "domain", default: "", null: false | ||||
|     t.datetime "created_at", null: false | ||||
|     t.datetime "updated_at", null: false | ||||
|     t.index ["domain"], name: "index_domain_allows_on_domain", unique: true | ||||
|   end | ||||
|  | ||||
|   create_table "domain_blocks", force: :cascade do |t| | ||||
|     t.string "domain", default: "", null: false | ||||
|     t.datetime "created_at", null: false | ||||
|   | ||||
| @@ -12,17 +12,33 @@ module Mastodon | ||||
|     end | ||||
|  | ||||
|     option :dry_run, type: :boolean | ||||
|     desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace' | ||||
|     option :whitelist_mode, type: :boolean | ||||
|     desc 'purge [DOMAIN]', 'Remove accounts from a DOMAIN without a trace' | ||||
|     long_desc <<-LONG_DESC | ||||
|       Remove all accounts from a given DOMAIN without leaving behind any | ||||
|       records. Unlike a suspension, if the DOMAIN still exists in the wild, | ||||
|       it means the accounts could return if they are resolved again. | ||||
|  | ||||
|       When the --whitelist-mode option is given, instead of purging accounts | ||||
|       from a single domain, all accounts from domains that are not whitelisted | ||||
|       are removed from the database. | ||||
|     LONG_DESC | ||||
|     def purge(domain) | ||||
|     def purge(domain = nil) | ||||
|       removed = 0 | ||||
|       dry_run = options[:dry_run] ? ' (DRY RUN)' : '' | ||||
|  | ||||
|       Account.where(domain: domain).find_each do |account| | ||||
|       scope = begin | ||||
|         if options[:whitelist_mode] | ||||
|           Account.remote.where.not(domain: DomainAllow.pluck(:domain)) | ||||
|         elsif domain.present? | ||||
|           Account.remote.where(domain: domain) | ||||
|         else | ||||
|           say('No domain given', :red) | ||||
|           exit(1) | ||||
|         end | ||||
|       end | ||||
|  | ||||
|       scope.find_each do |account| | ||||
|         SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run] | ||||
|         removed += 1 | ||||
|         say('.', :green, false) | ||||
|   | ||||
							
								
								
									
										3
									
								
								spec/fabricators/domain_allow_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								spec/fabricators/domain_allow_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| Fabricator(:domain_allow) do | ||||
|   domain "MyString" | ||||
| end | ||||
							
								
								
									
										5
									
								
								spec/models/domain_allow_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/models/domain_allow_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe DomainAllow, type: :model do | ||||
|   pending "add some examples to (or delete) #{__FILE__}" | ||||
| end | ||||
| @@ -12,6 +12,7 @@ const uuid = require('uuid'); | ||||
| const fs = require('fs'); | ||||
|  | ||||
| const env = process.env.NODE_ENV || 'development'; | ||||
| const alwaysRequireAuth = process.env.WHITELIST_MODE === 'true' || process.env.AUTHORIZED_FETCH === 'true'; | ||||
|  | ||||
| dotenv.config({ | ||||
|   path: env === 'production' ? '.env.production' : '.env', | ||||
| @@ -271,7 +272,7 @@ const startWorker = (workerId) => { | ||||
|  | ||||
|   const wsVerifyClient = (info, cb) => { | ||||
|     const location = url.parse(info.req.url, true); | ||||
|     const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream); | ||||
|     const authRequired = alwaysRequireAuth || !PUBLIC_STREAMS.some(stream => stream === location.query.stream); | ||||
|     const allowedScopes = []; | ||||
|  | ||||
|     if (authRequired) { | ||||
| @@ -306,7 +307,7 @@ const startWorker = (workerId) => { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path); | ||||
|     const authRequired = alwaysRequireAuth || !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path); | ||||
|     const allowedScopes = []; | ||||
|  | ||||
|     if (authRequired) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user