Merge commit '8f1847df31f5195abb427854fa1e2bf1e8ba23a0' into glitch-soc/stable-4.6
This commit is contained in:
commit
222dfdf233
17 files changed
+76
-19
No files matched your search
+15
-1
@@ -2,9 +2,23 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.6.8] - 2026-09-15
|
||||
|
||||
### Security
|
||||
|
||||
- Temporarily disable HEIF support
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix relative privacy policy links in subscription emails (#40486 by @crafkaz)
|
||||
- Fix canonical email blocks interfering with freezing or approving users (#40463 by @ClearlyClaire)
|
||||
- Fix 500 error when submitting a status twice (#40439 by @ClearlyClaire)
|
||||
- Fix account deletion not deleting generated annual reports (#40394 by @ClearlyClaire)
|
||||
- Fix notifications not being cleaned up when notification requests are deleted in bulk (#40393 by @ClearlyClaire)
|
||||
|
||||
## [4.6.7] - 2026-09-01
|
||||
|
||||
## Security
|
||||
### Security
|
||||
|
||||
- Update dependencies
|
||||
- Fix password authentication bypass in 2FA auth for LDAP/PAM/SSO accounts ([GHSA-vx32-x96w-qq65](https://github.com/mastodon/mastodon/security/advisories/GHSA-vx32-x96w-qq65))
|
||||
|
||||
@@ -45,6 +45,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
|
||||
end
|
||||
|
||||
def dismiss_bulk
|
||||
FilteredNotificationCleanupWorker.perform_async(current_account.id, @requests.map(&:from_account_id))
|
||||
@requests.each(&:destroy!)
|
||||
render_empty
|
||||
end
|
||||
|
||||
@@ -40,6 +40,7 @@ module Account::Associations
|
||||
has_many :statuses
|
||||
has_many :keypairs
|
||||
has_many :email_subscriptions
|
||||
has_many :generated_annual_reports
|
||||
|
||||
has_one :deletion_request, class_name: 'AccountDeletionRequest'
|
||||
has_one :follow_recommendation_suppression
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class User < ApplicationRecord
|
||||
validates :email, presence: true, email_address: true, length: { maximum: 320 }
|
||||
validates :email, email_mx: { attempt_ip: :sign_up_ip }, if: :validate_email_dns?
|
||||
|
||||
validates_with UserEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
|
||||
validates_with UserEmailValidator, if: -> { (ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed?) && will_save_change_to_email? }
|
||||
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||
|
||||
# Honeypot/anti-spam fields
|
||||
|
||||
@@ -28,6 +28,7 @@ class DeleteAccountService < BaseService
|
||||
scheduled_statuses
|
||||
status_pins
|
||||
tag_follows
|
||||
generated_annual_reports
|
||||
).freeze
|
||||
|
||||
# The following associations have no important side-effects
|
||||
@@ -53,6 +54,7 @@ class DeleteAccountService < BaseService
|
||||
scheduled_statuses
|
||||
status_pins
|
||||
tag_follows
|
||||
generated_annual_reports
|
||||
).freeze
|
||||
|
||||
ASSOCIATIONS_ON_DESTROY = %w(
|
||||
|
||||
@@ -20,6 +20,15 @@ class PostStatusService < BaseService
|
||||
end
|
||||
end
|
||||
|
||||
class IdempotencyError < StandardError
|
||||
attr_reader :status
|
||||
|
||||
def initialize(status)
|
||||
super()
|
||||
@status = status
|
||||
end
|
||||
end
|
||||
|
||||
# Post a text status update, fetch and notify remote users mentioned
|
||||
# @param [Account] account Account from which to post
|
||||
# @param [Hash] options
|
||||
@@ -46,9 +55,10 @@ class PostStatusService < BaseService
|
||||
@in_reply_to = @options[:thread]
|
||||
@quoted_status = @options[:quoted_status]
|
||||
|
||||
preprocess_attributes!
|
||||
|
||||
with_idempotency do
|
||||
validate_media!
|
||||
preprocess_attributes!
|
||||
|
||||
if scheduled?
|
||||
schedule_status!
|
||||
@@ -63,7 +73,7 @@ class PostStatusService < BaseService
|
||||
end
|
||||
|
||||
@status
|
||||
rescue Antispam::SilentlyDrop => e
|
||||
rescue Antispam::SilentlyDrop, IdempotencyError => e
|
||||
e.status
|
||||
end
|
||||
|
||||
@@ -253,7 +263,7 @@ class PostStatusService < BaseService
|
||||
return yield unless idempotency_given?
|
||||
|
||||
with_redis_lock("idempotency:lock:status:#{@account.id}:#{@options[:idempotency]}") do
|
||||
return idempotency_duplicate if idempotency_duplicate?
|
||||
raise IdempotencyError, idempotency_duplicate if idempotency_duplicate?
|
||||
|
||||
yield
|
||||
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
|
||||
- content_for :footer do
|
||||
%p.email-footer-p= t('email_subscription_mailer.notification.footer.reason_for_email_html', name: display_name(@subscription.account), unsubscribe_path: @unsubscribe_url)
|
||||
%p.email-footer-p= t('email_subscription_mailer.notification.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_path)
|
||||
%p.email-footer-p= t('email_subscription_mailer.notification.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_url)
|
||||
- if Setting.email_footer_text.present?
|
||||
%p.email-footer-p= Setting.email_footer_text
|
||||
@@ -33,6 +33,6 @@
|
||||
|
||||
- content_for :footer do
|
||||
%p.email-footer-p= t('.footer.reason_for_email_html', name: display_name(@subscription.account), unsubscribe_path: @unsubscribe_url)
|
||||
%p.email-footer-p= t('.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_path)
|
||||
%p.email-footer-p= t('.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_url)
|
||||
- if Setting.email_footer_text.present?
|
||||
%p.email-footer-p= Setting.email_footer_text
|
||||
@@ -3,7 +3,7 @@
|
||||
class FilteredNotificationCleanupWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
def perform(account_id, from_account_id)
|
||||
Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).in_batches(order: :desc).delete_all
|
||||
def perform(account_id, from_account_ids)
|
||||
Notification.where(account_id: account_id, from_account_id: from_account_ids, filtered: true).in_batches(order: :desc).delete_all
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,6 @@ Vips.block('VipsForeign', true)
|
||||
VipsForeignLoadJpeg
|
||||
VipsForeignLoadPng
|
||||
VipsForeignLoadWebp
|
||||
VipsForeignLoadHeif
|
||||
VipsForeignSavePng
|
||||
VipsForeignSaveSpng
|
||||
VipsForeignSaveJpeg
|
||||
|
||||
+3
-3
@@ -60,7 +60,7 @@ services:
|
||||
web:
|
||||
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
||||
# build: .
|
||||
image: ghcr.io/glitch-soc/mastodon:v4.6.7
|
||||
image: ghcr.io/glitch-soc/mastodon:v4.6.8
|
||||
restart: always
|
||||
env_file: .env.production
|
||||
command: bundle exec puma -C config/puma.rb
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
# build:
|
||||
# dockerfile: ./streaming/Dockerfile
|
||||
# context: .
|
||||
image: ghcr.io/glitch-soc/mastodon-streaming:v4.6.7
|
||||
image: ghcr.io/glitch-soc/mastodon-streaming:v4.6.8
|
||||
restart: always
|
||||
env_file: .env.production
|
||||
command: node ./streaming/index.js
|
||||
@@ -103,7 +103,7 @@ services:
|
||||
sidekiq:
|
||||
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
||||
# build: .
|
||||
image: ghcr.io/glitch-soc/mastodon:v4.6.7
|
||||
image: ghcr.io/glitch-soc/mastodon:v4.6.8
|
||||
restart: always
|
||||
env_file: .env.production
|
||||
command: bundle exec sidekiq
|
||||
|
||||
@@ -13,7 +13,7 @@ module Mastodon
|
||||
end
|
||||
|
||||
def patch
|
||||
7
|
||||
8
|
||||
end
|
||||
|
||||
def default_prerelease
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe EmailSubscriptionMailer do
|
||||
shared_examples 'an absolute privacy policy link' do
|
||||
it 'links to the privacy policy with an absolute URL' do
|
||||
expect(mail.html_part.body.decoded)
|
||||
.to include(%(href="#{privacy_policy_url}"))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.confirmation' do
|
||||
let(:email_subscription) { Fabricate(:email_subscription) }
|
||||
let(:mail) { described_class.with(subscription: email_subscription).confirmation }
|
||||
@@ -15,6 +22,8 @@ RSpec.describe EmailSubscriptionMailer do
|
||||
subject: I18n.t('email_subscription_mailer.confirmation.subject')
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'an absolute privacy policy link'
|
||||
end
|
||||
|
||||
describe '.notification' do
|
||||
@@ -33,6 +42,8 @@ RSpec.describe EmailSubscriptionMailer do
|
||||
subject: I18n.t('email_subscription_mailer.notification.subject.singular', name: email_subscription.account.display_name, excerpt: statuses.first.text.truncate(17))
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'an absolute privacy policy link'
|
||||
end
|
||||
|
||||
context 'with multiple statuses' do
|
||||
|
||||
@@ -122,7 +122,9 @@ RSpec.describe 'Requests' do
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:notifications'
|
||||
|
||||
it 'returns http success and destroys the notification request', :aggregate_failures do
|
||||
expect { subject }.to change(NotificationRequest, :count).by(-1)
|
||||
expect { subject }
|
||||
.to change(NotificationRequest, :count).by(-1)
|
||||
.and enqueue_sidekiq_job(FilteredNotificationCleanupWorker).with(user.account_id, be_a(Array))
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.content_type)
|
||||
|
||||
@@ -26,6 +26,7 @@ RSpec.describe DeleteAccountService do
|
||||
let!(:follow_notification) { Fabricate(:notification, account: local_follower, activity: active_relationship, type: :follow) }
|
||||
|
||||
let!(:account_note) { Fabricate(:account_note, account: account) }
|
||||
let!(:generated_annual_report) { Fabricate(:generated_annual_report, account: account) }
|
||||
|
||||
it 'deletes associated owned and target records and target notifications' do
|
||||
subject
|
||||
@@ -48,6 +49,7 @@ RSpec.describe DeleteAccountService do
|
||||
expect { poll_vote.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { account_note.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { collection.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { generated_annual_report.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def expect_deletion_of_associated_target_records
|
||||
|
||||
@@ -57,8 +57,8 @@ RSpec.describe PostStatusService do
|
||||
|
||||
it 'returns existing status when used twice with idempotency key' do
|
||||
account = Fabricate(:account)
|
||||
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||
status1 = described_class.new.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||
status2 = described_class.new.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||
expect(status2.id).to eq status1.id
|
||||
end
|
||||
|
||||
@@ -341,8 +341,8 @@ RSpec.describe PostStatusService do
|
||||
|
||||
it 'returns existing status when used twice with idempotency key' do
|
||||
account = Fabricate(:account)
|
||||
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep')
|
||||
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep')
|
||||
status1 = described_class.new.call(account, text: 'test', idempotency: 'meepmeep')
|
||||
status2 = described_class.new.call(account, text: 'test', idempotency: 'meepmeep')
|
||||
expect(status2.id).to eq status1.id
|
||||
end
|
||||
|
||||
|
||||
@@ -20,5 +20,20 @@ RSpec.describe FilteredNotificationCleanupWorker do
|
||||
.to change { recipient.notifications.where(from_account: sender).count }.from(2).to(0)
|
||||
.and(not_change { recipient.notifications.where(from_account: bystander).count })
|
||||
end
|
||||
|
||||
context 'when given an array of IDs as parameter' do
|
||||
let(:other_sender) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate(:notification, account: recipient, activity: Fabricate(:favourite, account: other_sender), filtered: true)
|
||||
end
|
||||
|
||||
it 'deletes all filtered notifications to the account' do
|
||||
expect { described_class.new.perform(recipient.id, [sender.id, other_sender.id]) }
|
||||
.to change { recipient.notifications.where(from_account: sender).count }.from(2).to(0)
|
||||
.and change { recipient.notifications.where(from_account: other_sender).count }.from(1).to(0)
|
||||
.and(not_change { recipient.notifications.where(from_account: bystander).count })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user