Add whitelist mode (#11291)

This commit is contained in:
Eugen Rochko 2019-07-30 11:10:46 +02:00 committed by GitHub
parent 85b7b565de
commit 24552b5160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 302 additions and 53 deletions

View File

@ -3,6 +3,7 @@
class AboutController < ApplicationController class AboutController < ApplicationController
layout 'public' layout 'public'
before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show before_action :set_body_classes, only: :show
before_action :set_instance_presenter before_action :set_instance_presenter
before_action :set_expires_in before_action :set_expires_in
@ -19,6 +20,10 @@ class AboutController < ApplicationController
private private
def require_open_federation!
not_found if whitelist_mode?
end
def new_user def new_user
User.new.tap do |user| User.new.tap do |user|
user.build_account user.build_account

View File

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

View File

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

View 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

View File

@ -2,6 +2,10 @@
module Admin module Admin
class InstancesController < BaseController 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 def index
authorize :instance, :index? authorize :instance, :index?
@ -11,20 +15,38 @@ module Admin
def show def show
authorize :instance, :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 @following_count = Follow.where(account: Account.where(domain: params[:id])).count
@followers_count = Follow.where(target_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 @reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.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) @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) @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@domain_block = DomainBlock.rule_for(params[:id])
end end
private 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 def filtered_instances
InstanceFilter.new(filter_params).results InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
end end
def paginated_instances def paginated_instances

View File

@ -9,6 +9,7 @@ class Api::BaseController < ApplicationController
skip_before_action :store_current_location skip_before_action :store_current_location
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
before_action :set_cache_headers before_action :set_cache_headers
protect_from_forgery with: :null_session protect_from_forgery with: :null_session
@ -69,6 +70,10 @@ class Api::BaseController < ApplicationController
nil nil
end end
def require_authenticated_user!
render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
end
def require_user! def require_user!
if !current_user if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422 render json: { error: 'This method requires an authenticated user' }, status: 422
@ -94,4 +99,8 @@ class Api::BaseController < ApplicationController
def set_cache_headers def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end end
def disallow_unauthenticated_api_access?
authorized_fetch_mode?
end
end end

View File

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

View File

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

View File

@ -2,6 +2,7 @@
class Api::V1::Instances::ActivityController < Api::BaseController class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api! before_action :require_enabled_api!
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
respond_to :json respond_to :json
@ -33,6 +34,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
end end
def require_enabled_api! def require_enabled_api!
head 404 unless Setting.activity_api_enabled head 404 unless Setting.activity_api_enabled && !whitelist_mode?
end end
end end

View File

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

View File

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

View File

@ -11,12 +11,14 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern include UserTrackingConcern
include SessionTrackingConcern include SessionTrackingConcern
include CacheConcern include CacheConcern
include DomainControlHelper
helper_method :current_account helper_method :current_account
helper_method :current_session helper_method :current_session
helper_method :current_theme helper_method :current_theme
helper_method :single_user_mode? helper_method :single_user_mode?
helper_method :use_seamless_external_login? helper_method :use_seamless_external_login?
helper_method :whitelist_mode?
rescue_from ActionController::RoutingError, with: :not_found rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found
@ -38,7 +40,7 @@ class ApplicationController < ActionController::Base
end end
def authorized_fetch_mode? def authorized_fetch_mode?
ENV['AUTHORIZED_FETCH'] == 'true' ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
end end
def public_fetch_mode? def public_fetch_mode?

View File

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

View File

@ -3,7 +3,8 @@
class DirectoriesController < ApplicationController class DirectoriesController < ApplicationController
layout 'public' 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_instance_presenter
before_action :set_tag, only: :show before_action :set_tag, only: :show
before_action :set_tags before_action :set_tags
@ -19,7 +20,7 @@ class DirectoriesController < ApplicationController
private private
def check_enabled def require_enabled!
return not_found unless Setting.profile_directory return not_found unless Setting.profile_directory
end end

View File

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

View File

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

View File

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

View File

@ -3,7 +3,8 @@
class PublicTimelinesController < ApplicationController class PublicTimelinesController < ApplicationController
layout 'public' 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_body_classes
before_action :set_instance_presenter before_action :set_instance_presenter
@ -16,7 +17,7 @@ class PublicTimelinesController < ApplicationController
private private
def check_enabled def require_enabled!
not_found unless Setting.timeline_preview not_found unless Setting.timeline_preview
end end

View File

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

View File

@ -8,6 +8,7 @@ class TagsController < ApplicationController
layout 'public' layout 'public'
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } 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_tag
before_action :set_body_classes before_action :set_body_classes
before_action :set_instance_presenter before_action :set_instance_presenter

View File

@ -12,6 +12,14 @@ module DomainControlHelper
end end
end end
if whitelist_mode?
!DomainAllow.allowed?(domain)
else
DomainBlock.blocked?(domain) DomainBlock.blocked?(domain)
end end
end
def whitelist_mode?
Rails.configuration.x.whitelist_mode
end
end end

View 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

View File

@ -7,8 +7,9 @@ class Instance
def initialize(resource) def initialize(resource)
@domain = resource.domain @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_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain)
@domain_allow = resource.is_a?(DomainAllow) ? resource : DomainAllow.rule_for(domain)
end end
def countable? def countable?

View File

@ -12,6 +12,10 @@ class InstanceFilter
scope = DomainBlock scope = DomainBlock
scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
scope.order(id: :desc) 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 else
scope = Account.remote scope = Account.remote
scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class DomainAllowPolicy < ApplicationPolicy
def create?
admin?
end
def destroy?
admin?
end
end

View File

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

View 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

View 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

View File

@ -6,12 +6,18 @@
%strong= t('admin.instances.moderation.title') %strong= t('admin.instances.moderation.title')
%ul %ul
%li= filter_link_to t('admin.instances.moderation.all'), limited: nil %li= filter_link_to t('admin.instances.moderation.all'), limited: nil
- unless whitelist_mode?
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
%div{ style: 'flex: 1 1 auto; text-align: right' } %div{ style: 'flex: 1 1 auto; text-align: right' }
- 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' = 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 - unless whitelist_mode?
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
.fields-group .fields-group
- Admin::FilterHelper::INSTANCES_FILTERS.each do |key| - Admin::FilterHelper::INSTANCES_FILTERS.each do |key|
- if params[key].present? - if params[key].present?
@ -47,8 +53,11 @@
- unless first_item - unless first_item
&bull; &bull;
= t('admin.domain_blocks.rejecting_reports') = t('admin.domain_blocks.rejecting_reports')
- elsif whitelist_mode?
= t('admin.accounts.whitelisted')
- else - else
= t('admin.accounts.no_limits_imposed') = t('admin.accounts.no_limits_imposed')
- if instance.countable? - 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 .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 = paginate paginated_instances

View File

@ -38,7 +38,9 @@
= link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'
%div{ style: 'float: right' } %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' = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
- else - else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'

View File

@ -42,6 +42,7 @@
%hr.spacer/ %hr.spacer/
- unless whitelist_mode?
.fields-group .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') = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
@ -54,6 +55,7 @@
.fields-group .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') = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
- unless whitelist_mode?
.fields-group .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') = 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')
@ -76,7 +78,7 @@
.fields-group .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 :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 :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') = 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')

View File

@ -33,7 +33,7 @@
= f.input :invite_code, as: :hidden = f.input :invite_code, as: :hidden
.fields-group .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 .actions
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit

View File

@ -10,10 +10,13 @@
= link_to root_url, class: 'brand' do = link_to root_url, class: 'brand' do
= svg_logo_full = svg_logo_full
- unless whitelist_mode?
= link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory = 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.about_this'), about_more_path, class: 'nav-link optional'
= link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
.nav-center .nav-center
.nav-right .nav-right
- if user_signed_in? - if user_signed_in?
= link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn' = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'

View File

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

View File

@ -186,6 +186,7 @@ en:
username: Username username: Username
warn: Warn warn: Warn
web: Web web: Web
whitelisted: Whitelisted
action_logs: action_logs:
actions: actions:
assigned_to_self_report: "%{name} assigned report %{target} to themselves" assigned_to_self_report: "%{name} assigned report %{target} to themselves"
@ -269,6 +270,11 @@ en:
week_interactions: interactions this week week_interactions: interactions this week
week_users_active: active this week week_users_active: active this week
week_users_new: users 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: domain_blocks:
add_new: Add new domain block add_new: Add new domain block
created_msg: Domain block is now being processed created_msg: Domain block is now being processed
@ -524,6 +530,7 @@ en:
apply_for_account: Request an invite apply_for_account: Request an invite
change_password: Password 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_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: 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. 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? didnt_get_confirmation: Didn't receive confirmation instructions?

View File

@ -38,6 +38,8 @@ en:
setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
username: Your username will be unique on %{domain} 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 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: featured_tag:
name: 'You might want to use one of these:' name: 'You might want to use one of these:'
imports: imports:

View File

@ -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 :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 :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 :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? } 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 end

View File

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

View 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

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" 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" t.index ["account_id"], name: "index_custom_filters_on_account_id"
end 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| create_table "domain_blocks", force: :cascade do |t|
t.string "domain", default: "", null: false t.string "domain", default: "", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false

View File

@ -12,17 +12,33 @@ module Mastodon
end end
option :dry_run, type: :boolean 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 long_desc <<-LONG_DESC
Remove all accounts from a given DOMAIN without leaving behind any Remove all accounts from a given DOMAIN without leaving behind any
records. Unlike a suspension, if the DOMAIN still exists in the wild, records. Unlike a suspension, if the DOMAIN still exists in the wild,
it means the accounts could return if they are resolved again. 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 LONG_DESC
def purge(domain) def purge(domain = nil)
removed = 0 removed = 0
dry_run = options[:dry_run] ? ' (DRY RUN)' : '' 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] SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run]
removed += 1 removed += 1
say('.', :green, false) say('.', :green, false)

View File

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

View File

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

View File

@ -12,6 +12,7 @@ const uuid = require('uuid');
const fs = require('fs'); const fs = require('fs');
const env = process.env.NODE_ENV || 'development'; const env = process.env.NODE_ENV || 'development';
const alwaysRequireAuth = process.env.WHITELIST_MODE === 'true' || process.env.AUTHORIZED_FETCH === 'true';
dotenv.config({ dotenv.config({
path: env === 'production' ? '.env.production' : '.env', path: env === 'production' ? '.env.production' : '.env',
@ -271,7 +272,7 @@ const startWorker = (workerId) => {
const wsVerifyClient = (info, cb) => { const wsVerifyClient = (info, cb) => {
const location = url.parse(info.req.url, true); 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 = []; const allowedScopes = [];
if (authRequired) { if (authRequired) {
@ -306,7 +307,7 @@ const startWorker = (workerId) => {
return; return;
} }
const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path); const authRequired = alwaysRequireAuth || !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
const allowedScopes = []; const allowedScopes = [];
if (authRequired) { if (authRequired) {