Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update v4.2.8 #236

Merged
merged 4 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

All notable changes to this project will be documented in this file.

## [4.2.8] - 2024-02-23

### Added

- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355))
In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week.
When this happens, users with the permission to change server settings will receive an email notification.
This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`.

### Changed

- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280))
If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations.
Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again.

### Fixed

- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335))
- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358))

## [4.2.7] - 2024-02-16

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/packs/admin.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'c
const onChangeRegistrationMode = (target) => {
const enabled = target.value === 'approved';

[].forEach.call(document.querySelectorAll('.form_admin_settings_registrations_mode .warning-hint'), (warning_hint) => {
warning_hint.style.display = target.value === 'open' ? 'inline' : 'none';
});

[].forEach.call(document.querySelectorAll('#form_admin_settings_require_invite_text'), (input) => {
input.disabled = !enabled;
if (enabled) {
Expand Down
6 changes: 6 additions & 0 deletions app/mailers/admin_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def new_critical_software_updates
end
end

def auto_close_registrations
locale_for_account(@me) do
mail subject: default_i18n_subject(instance: @instance)
end
end

private

def process_params
Expand Down
11 changes: 8 additions & 3 deletions app/services/activitypub/process_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,15 @@ def image_url(key)
value = first_of_value(@json[key])

return if value.nil?
return value['url'] if value.is_a?(Hash)

image = fetch_resource_without_id_validation(value)
image['url'] if image
if value.is_a?(String)
value = fetch_resource_without_id_validation(value)
return if value.nil?
end

value = first_of_value(value['url']) if value.is_a?(Hash) && value['type'] == 'Image'
value = value['href'] if value.is_a?(Hash)
value if value.is_a?(String)
end

def public_key
Expand Down
2 changes: 1 addition & 1 deletion app/services/verify_link_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def call(field)

def perform_request!
@body = Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res|
res.code == 200 ? res.body_with_limit : nil
res.code == 200 ? res.truncated_body : nil
end
end

Expand Down
4 changes: 3 additions & 1 deletion app/views/admin/settings/registrations/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

%p.lead= t('admin.settings.registrations.preamble')

.flash-message= t('admin.settings.registrations.moderation_recommandation')

.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
= f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }, warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint')

.fields-row__column.fields-row__column-6.fields-group
= f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations?
Expand Down
3 changes: 3 additions & 0 deletions app/views/admin_mailer/auto_close_registrations.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= raw t('admin_mailer.auto_close_registrations.body', instance: @instance) %>

<%= raw t('application_mailer.view')%> <%= admin_settings_registrations_url %>
33 changes: 33 additions & 0 deletions app/workers/scheduler/auto_close_registrations_scheduler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

class Scheduler::AutoCloseRegistrationsScheduler
include Sidekiq::Worker
include Redisable

sidekiq_options retry: 0

# Automatically switch away from open registrations if no
# moderator had any activity in that period of time
OPEN_REGISTRATIONS_MODERATOR_THRESHOLD = 1.week + UserTrackingConcern::SIGN_IN_UPDATE_FREQUENCY

def perform
return if Rails.configuration.x.email_domains_whitelist.present? || ENV['DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS'] == 'true'
return unless Setting.registrations_mode == 'open'

switch_to_approval_mode! unless active_moderators?
end

private

def active_moderators?
User.those_who_can(:manage_reports).exists?(current_sign_in_at: OPEN_REGISTRATIONS_MODERATOR_THRESHOLD.ago...)
end

def switch_to_approval_mode!
Setting.registrations_mode = 'approved'

User.those_who_can(:manage_settings).includes(:account).find_each do |user|
AdminMailer.with(recipient: user.account).auto_close_registrations.deliver_later
end
end
end
3 changes: 0 additions & 3 deletions config/deploy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
SYSTEMD_SERVICES = %i[sidekiq streaming web].freeze
SERVICE_ACTIONS = %i[reload restart status].freeze

SYSTEMD_SERVICES = %i[sidekiq streaming web].freeze
SERVICE_ACTIONS = %i[reload restart status].freeze

namespace :systemd do
SYSTEMD_SERVICES.each do |service|
SERVICE_ACTIONS.each do |action|
Expand Down
5 changes: 5 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -764,13 +764,15 @@ en:
disabled: To no one
users: To logged-in local users
registrations:
moderation_recommandation: Please make sure you have an adequate and reactive moderation team before you open registrations to everyone!
preamble: Control who can create an account on your server.
title: Registrations
registrations_mode:
modes:
approved: Approval required for sign up
none: Nobody can sign up
open: Anyone can sign up
warning_hint: We recommend using “Approval required for sign up” unless you are confident your moderation team can handle spam and malicious registrations in a timely fashion.
security:
authorized_fetch: Require authentication from federated servers
authorized_fetch_hint: Requiring authentication from federated servers enables stricter enforcement of both user-level and server-level blocks. However, this comes at the cost of a performance penalty, reduces the reach of your replies, and may introduce compatibility issues with some federated services. In addition, this will not prevent dedicated actors from fetching your public posts and accounts.
Expand Down Expand Up @@ -963,6 +965,9 @@ en:
title: Webhooks
webhook: Webhook
admin_mailer:
auto_close_registrations:
body: Due to a lack of recent moderator activity, registrations on %{instance} have been automatically switched to requiring manual review, to prevent %{instance} from being used as a platform for potential bad actors. You can switch it back to open registrations at any time.
subject: Registrations for %{instance} have been automatically switched to requiring approval
new_appeal:
actions:
delete_statuses: to delete their posts
Expand Down
2 changes: 1 addition & 1 deletion config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defaults: &defaults
site_terms: ''
site_contact_username: ''
site_contact_email: ''
registrations_mode: 'open'
registrations_mode: 'none'
profile_directory: true
closed_registrations_message: ''
timeline_preview: true
Expand Down
4 changes: 4 additions & 0 deletions config/sidekiq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@
queue: scheduler
interval: '5m'
class: Scheduler::ForYouMammothScheduler
auto_close_registrations_scheduler:
interval: 1 hour
class: Scheduler::AutoCloseRegistrationsScheduler
queue: scheduler
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ services:

web:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.8
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
Expand All @@ -77,7 +77,7 @@ services:

streaming:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.8
restart: always
env_file: .env.production
command: node ./streaming
Expand All @@ -95,7 +95,7 @@ services:

sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.7
image: ghcr.io/mastodon/mastodon:v4.2.8
restart: always
env_file: .env.production
command: bundle exec sidekiq
Expand Down
2 changes: 1 addition & 1 deletion lib/mastodon/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def minor
end

def patch
7
8
end

def default_prerelease
Expand Down
20 changes: 5 additions & 15 deletions spec/models/setting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,22 +142,12 @@
context 'when records includes nothing' do
let(:records) { [] }

context 'when default_value is not a Hash' do
it 'includes Setting with value of default_value' do
setting = described_class.all_as_records[key]

expect(setting).to be_a described_class
expect(setting).to have_attributes(var: key)
expect(setting).to have_attributes(value: 'default_value')
end
end

context 'when default_value is a Hash' do
let(:default_value) { { 'foo' => 'fuga' } }
it 'includes Setting with value of default_value' do
setting = described_class.all_as_records[key]

it 'returns {}' do
expect(described_class.all_as_records).to eq({})
end
expect(setting).to be_a described_class
expect(setting).to have_attributes(var: key)
expect(setting).to have_attributes(value: default_value)
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ def get(path, headers: nil, sign_with: nil, **args)
self.use_transactional_tests = false

DatabaseCleaner.cleaning do
# NOTE: we switched registrations mode to closed by default, but the specs
# very heavily rely on having it enabled by default, as it relies on users
# being approved by default except in select cases where explicitly testing
# other registration modes
# Also needs to be set per-example here because of the database cleaner.
Setting.registrations_mode = 'open'

example.run
end

Expand Down
51 changes: 41 additions & 10 deletions spec/services/activitypub/process_account_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
RSpec.describe ActivityPub::ProcessAccountService, type: :service do
subject { described_class.new }

context 'with property values' do
context 'with property values, an avatar, and a profile header' do
let(:payload) do
{
id: 'https://foo.test',
Expand All @@ -16,19 +16,50 @@
{ type: 'PropertyValue', name: 'Occupation', value: 'Unit test' },
{ type: 'PropertyValue', name: 'non-string', value: %w(foo bar) },
],
image: {
type: 'Image',
mediaType: 'image/png',
url: 'https://foo.test/image.png',
},
icon: {
type: 'Image',
url: [
{
mediaType: 'image/png',
href: 'https://foo.test/icon.png',
},
],
},
}.with_indifferent_access
end

it 'parses out of attachment' do
before do
stub_request(:get, 'https://foo.test/image.png').to_return(request_fixture('avatar.txt'))
stub_request(:get, 'https://foo.test/icon.png').to_return(request_fixture('avatar.txt'))
end

it 'parses property values, avatar and profile header as expected' do
account = subject.call('alice', 'example.com', payload)
expect(account.fields).to be_a Array
expect(account.fields.size).to eq 2
expect(account.fields[0]).to be_a Account::Field
expect(account.fields[0].name).to eq 'Pronouns'
expect(account.fields[0].value).to eq 'They/them'
expect(account.fields[1]).to be_a Account::Field
expect(account.fields[1].name).to eq 'Occupation'
expect(account.fields[1].value).to eq 'Unit test'

expect(account.fields)
.to be_an(Array)
.and have_attributes(size: 2)
expect(account.fields.first)
.to be_an(Account::Field)
.and have_attributes(
name: eq('Pronouns'),
value: eq('They/them')
)
expect(account.fields.last)
.to be_an(Account::Field)
.and have_attributes(
name: eq('Occupation'),
value: eq('Unit test')
)
expect(account).to have_attributes(
avatar_remote_url: 'https://foo.test/icon.png',
header_remote_url: 'https://foo.test/image.png'
)
end
end

Expand Down
13 changes: 7 additions & 6 deletions spec/services/verify_link_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,24 +80,25 @@
"
<!doctype html>
<body>
<a rel=\"me\" href=\"#{ActivityPub::TagManager.instance.url_for(account)}\"
<a rel=\"me\" href=\"#{ActivityPub::TagManager.instance.url_for(account)}\">
"
end

it 'marks the field as not verified' do
expect(field.verified?).to be false
it 'marks the field as verified' do
expect(field.verified?).to be true
end
end

context 'when a link back might be truncated' do
context 'when a link tag might be truncated' do
let(:html) do
"
<!doctype html>
<body>
<a rel=\"me\" href=\"#{ActivityPub::TagManager.instance.url_for(account)}"
<a rel=\"me\" href=\"#{ActivityPub::TagManager.instance.url_for(account)}\"
"
end

it 'does not mark the field as verified' do
it 'marks the field as not verified' do
expect(field.verified?).to be false
end
end
Expand Down
6 changes: 6 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
config.before :suite do
Rails.application.load_seed
Chewy.strategy(:bypass)

# NOTE: we switched registrations mode to closed by default, but the specs
# very heavily rely on having it enabled by default, as it relies on users
# being approved by default except in select cases where explicitly testing
# other registration modes
Setting.registrations_mode = 'open'
end

config.after :suite do
Expand Down
Loading
Loading