From e6b5b61559c0fbb277213541f8986fc25fbfe7cd Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Jan 2025 11:10:08 +0100 Subject: [PATCH 01/15] Merge commit from fork --- app/lib/delivery_failure_tracker.rb | 2 ++ .../activitypub/process_account_service.rb | 27 ++++++++++++++----- spec/lib/delivery_failure_tracker_spec.rb | 4 +-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index e17b45d667..96292923f4 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -46,6 +46,8 @@ class DeliveryFailureTracker urls.reject do |url| host = Addressable::URI.parse(url).normalized_host unavailable_domains_map[host] + rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError + true end end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index b6941725d1..5c55defbda 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -11,6 +11,8 @@ class ActivityPub::ProcessAccountService < BaseService SCAN_SEARCHABILITY_RE = /\[searchability:(public|followers|reactors|private)\]/ SCAN_SEARCHABILITY_FEDIBIRD_RE = /searchable_by_(all_users|followers_only|reacted_users_only|nobody)/ + VALID_URI_SCHEMES = %w(http https).freeze + # Should be called with confirmed valid JSON # and WebFinger-resolved username and domain def call(username, domain, json, options = {}) # rubocop:disable Metrics/PerceivedComplexity @@ -113,16 +115,28 @@ class ActivityPub::ProcessAccountService < BaseService end def set_immediate_protocol_attributes! - @account.inbox_url = @json['inbox'] || '' - @account.outbox_url = @json['outbox'] || '' - @account.shared_inbox_url = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || '' - @account.followers_url = @json['followers'] || '' + @account.inbox_url = valid_collection_uri(@json['inbox']) + @account.outbox_url = valid_collection_uri(@json['outbox']) + @account.shared_inbox_url = valid_collection_uri(@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) + @account.followers_url = valid_collection_uri(@json['followers']) @account.url = url || @uri @account.uri = @uri @account.actor_type = actor_type @account.created_at = @json['published'] if @json['published'].present? end + def valid_collection_uri(uri) + uri = uri.first if uri.is_a?(Array) + uri = uri['id'] if uri.is_a?(Hash) + return '' unless uri.is_a?(String) + + parsed_uri = Addressable::URI.parse(uri) + + VALID_URI_SCHEMES.include?(parsed_uri.scheme) && parsed_uri.host.present? ? parsed_uri : '' + rescue Addressable::URI::InvalidURIError + '' + end + def set_immediate_attributes! @account.featured_collection_url = @json['featured'] || '' @account.display_name = @json['name'] || '' @@ -398,10 +412,11 @@ class ActivityPub::ProcessAccountService < BaseService end def collection_info(type) - return [nil, nil] if @json[type].blank? + collection_uri = valid_collection_uri(@json[type]) + return [nil, nil] if collection_uri.blank? return @collections[type] if @collections.key?(type) - collection = fetch_resource_without_id_validation(@json[type]) + collection = fetch_resource_without_id_validation(collection_uri) total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil has_first_page = collection.is_a?(Hash) && collection['first'].present? diff --git a/spec/lib/delivery_failure_tracker_spec.rb b/spec/lib/delivery_failure_tracker_spec.rb index 40c8adc4c8..34912c8133 100644 --- a/spec/lib/delivery_failure_tracker_spec.rb +++ b/spec/lib/delivery_failure_tracker_spec.rb @@ -42,8 +42,8 @@ RSpec.describe DeliveryFailureTracker do Fabricate(:unavailable_domain, domain: 'foo.bar') end - it 'removes URLs that are unavailable' do - results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox']) + it 'removes URLs that are bogus or unavailable' do + results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox', '{foo:']) expect(results).to include('http://example.com/good/inbox') expect(results).to_not include('http://foo.bar/unavailable/inbox') From 4ab134ae505463a0684371f8bab07767d21f66ee Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 4 Dec 2024 04:05:58 -0500 Subject: [PATCH 02/15] Fix empty authors preview card serialization (#33151) --- app/models/preview_card.rb | 9 +++- .../rest/preview_card_serializer_spec.rb | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 spec/serializers/rest/preview_card_serializer_spec.rb diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 82b25d4649..38cf5fe831 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -134,7 +134,7 @@ class PreviewCard < ApplicationRecord end def authors - @authors ||= [PreviewCard::Author.new(self)] + @authors ||= Array(serialized_authors) end class Author < ActiveModelSerializers::Model @@ -169,6 +169,13 @@ class PreviewCard < ApplicationRecord private + def serialized_authors + if author_name? || author_url? + PreviewCard::Author + .new(self) + end + end + def extract_dimensions file = image.queued_for_write[:original] diff --git a/spec/serializers/rest/preview_card_serializer_spec.rb b/spec/serializers/rest/preview_card_serializer_spec.rb new file mode 100644 index 0000000000..6dbc337865 --- /dev/null +++ b/spec/serializers/rest/preview_card_serializer_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe REST::PreviewCardSerializer do + subject do + serialized_record_json( + preview_card, + described_class + ) + end + + context 'when preview card does not have author data' do + let(:preview_card) { Fabricate.build :preview_card } + + it 'includes empty authors array' do + expect(subject.deep_symbolize_keys) + .to include( + authors: be_an(Array).and(be_empty) + ) + end + end + + context 'when preview card has author data' do + let(:preview_card) { Fabricate.build :preview_card, author_name: 'Name', author_url: 'https://host.example/123' } + + it 'includes populated authors array' do + expect(subject.deep_symbolize_keys) + .to include( + authors: be_an(Array).and( + contain_exactly( + include( + name: 'Name', + url: 'https://host.example/123' + ) + ) + ) + ) + end + end +end From 775f2b8624a7a54831512928092e960d8bde52d7 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 6 Jan 2025 11:04:25 +0100 Subject: [PATCH 03/15] Fix `fediverse:creator` metadata not showing up in REST API (#33466) --- app/models/preview_card.rb | 2 +- .../rest/preview_card_serializer_spec.rb | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 38cf5fe831..bd248a7b7a 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -170,7 +170,7 @@ class PreviewCard < ApplicationRecord private def serialized_authors - if author_name? || author_url? + if author_name? || author_url? || author_account_id? PreviewCard::Author .new(self) end diff --git a/spec/serializers/rest/preview_card_serializer_spec.rb b/spec/serializers/rest/preview_card_serializer_spec.rb index 6dbc337865..41ba305b7c 100644 --- a/spec/serializers/rest/preview_card_serializer_spec.rb +++ b/spec/serializers/rest/preview_card_serializer_spec.rb @@ -21,7 +21,24 @@ RSpec.describe REST::PreviewCardSerializer do end end - context 'when preview card has author data' do + context 'when preview card has fediverse author data' do + let(:preview_card) { Fabricate.build :preview_card, author_account: Fabricate(:account) } + + it 'includes populated authors array' do + expect(subject.deep_symbolize_keys) + .to include( + authors: be_an(Array).and( + contain_exactly( + include( + account: be_present + ) + ) + ) + ) + end + end + + context 'when preview card has non-fediverse author data' do let(:preview_card) { Fabricate.build :preview_card, author_name: 'Name', author_url: 'https://host.example/123' } it 'includes populated authors array' do From 4e1262f8bd186c5ea0a9062933a62340ad8c905a Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 5 Dec 2024 10:38:48 +0100 Subject: [PATCH 04/15] Fix deletion of unconfirmed users with Webauthn set (#33186) --- app/workers/scheduler/user_cleanup_scheduler.rb | 1 + spec/workers/scheduler/user_cleanup_scheduler_spec.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb index f755128332..03544e2e98 100644 --- a/app/workers/scheduler/user_cleanup_scheduler.rb +++ b/app/workers/scheduler/user_cleanup_scheduler.rb @@ -19,6 +19,7 @@ class Scheduler::UserCleanupScheduler User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).find_in_batches do |batch| # We have to do it separately because of missing database constraints AccountModerationNote.where(target_account_id: batch.map(&:account_id)).delete_all + WebauthnCredential.where(user_id: batch.map(&:id)).delete_all Account.where(id: batch.map(&:account_id)).delete_all User.where(id: batch.map(&:id)).delete_all end diff --git a/spec/workers/scheduler/user_cleanup_scheduler_spec.rb b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb index b1be7c4611..604f528586 100644 --- a/spec/workers/scheduler/user_cleanup_scheduler_spec.rb +++ b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Scheduler::UserCleanupScheduler do let!(:old_unconfirmed_user) { Fabricate(:user) } let!(:confirmed_user) { Fabricate(:user) } let!(:moderation_note) { Fabricate(:account_moderation_note, account: Fabricate(:account), target_account: old_unconfirmed_user.account) } + let!(:webauthn_credential) { Fabricate(:webauthn_credential, user_id: old_unconfirmed_user.id) } describe '#perform' do before do @@ -26,6 +27,8 @@ RSpec.describe Scheduler::UserCleanupScheduler do .from(true).to(false) expect { moderation_note.reload } .to raise_error(ActiveRecord::RecordNotFound) + expect { webauthn_credential.reload } + .to raise_error(ActiveRecord::RecordNotFound) expect_preservation_of(new_unconfirmed_user) expect_preservation_of(confirmed_user) end From 94d9a1930e8b7fa26da9b7cb9c7d96dc20fa27ab Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 11 Dec 2024 13:59:29 +0100 Subject: [PATCH 05/15] Fix processing of mentions for post edits with an existing corresponding silent mention (#33227) --- .../process_status_update_service.rb | 20 +++++-------------- app/services/process_mentions_service.rb | 8 +++++--- app/workers/mention_resolve_worker.rb | 2 +- .../process_status_update_service_spec.rb | 16 +++++++++++++-- spec/services/update_status_service_spec.rb | 8 ++++++++ 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 3b69cadfce..7b6649d1ec 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -259,40 +259,30 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def update_mentions! - previous_mentions = @status.active_mentions.includes(:account).to_a - current_mentions = [] unresolved_mentions = [] - @raw_mentions.each do |href| + currently_mentioned_account_ids = @raw_mentions.filter_map do |href| next if href.blank? account = ActivityPub::TagManager.instance.uri_to_resource(href, Account) account ||= ActivityPub::FetchRemoteAccountService.new.call(href, request_id: @request_id) - next if account.nil? - - mention = previous_mentions.find { |x| x.account_id == account.id } - mention ||= account.mentions.new(status: @status) - - current_mentions << mention + account&.id rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS # Since previous mentions are about already-known accounts, # they don't try to resolve again and won't fall into this case. # In other words, this failure case is only for new mentions and won't # affect `removed_mentions` so they can safely be retried asynchronously unresolved_mentions << href + nil end - current_mentions.each do |mention| - mention.save if mention.new_record? - end + @status.mentions.upsert_all(currently_mentioned_account_ids.map { |id| { account_id: id, silent: false } }, unique_by: %w(status_id account_id)) # If previous mentions are no longer contained in the text, convert them # to silent mentions, since withdrawing access from someone who already # received a notification might be more confusing - removed_mentions = previous_mentions - current_mentions - - Mention.where(id: removed_mentions.map(&:id)).update_all(silent: true) unless removed_mentions.empty? + @status.mentions.where.not(account_id: currently_mentioned_account_ids).update_all(silent: true) # Queue unresolved mentions for later unresolved_mentions.uniq.each do |uri| diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 31b0e56022..a8d95a53d7 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -15,7 +15,7 @@ class ProcessMentionsService < BaseService return unless @status.local? - @previous_mentions = @status.active_mentions.includes(:account).to_a + @previous_mentions = @status.mentions.includes(:account).to_a @current_mentions = [] Status.transaction do @@ -63,6 +63,8 @@ class ProcessMentionsService < BaseService mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } mention ||= @status.mentions.new(account: mentioned_account) + mention.silent = false + @current_mentions << mention "@#{mentioned_account.acct}" @@ -87,7 +89,7 @@ class ProcessMentionsService < BaseService end @current_mentions.each do |mention| - mention.save if mention.new_record? && @save_records + mention.save if (mention.new_record? || mention.silent_changed?) && @save_records end # If previous mentions are no longer contained in the text, convert them @@ -95,7 +97,7 @@ class ProcessMentionsService < BaseService # received a notification might be more confusing removed_mentions = @previous_mentions - @current_mentions - Mention.where(id: removed_mentions.map(&:id)).update_all(silent: true) unless removed_mentions.empty? + Mention.where(id: removed_mentions.map(&:id), silent: false).update_all(silent: true) unless removed_mentions.empty? end def mention_undeliverable?(mentioned_account) diff --git a/app/workers/mention_resolve_worker.rb b/app/workers/mention_resolve_worker.rb index 72dcd9633f..8c5938aeaf 100644 --- a/app/workers/mention_resolve_worker.rb +++ b/app/workers/mention_resolve_worker.rb @@ -16,7 +16,7 @@ class MentionResolveWorker return if account.nil? - status.mentions.create!(account: account, silent: false) + status.mentions.upsert({ account_id: account.id, silent: false }, unique_by: %w(status_id account_id)) rescue ActiveRecord::RecordNotFound # Do nothing rescue Mastodon::UnexpectedResponseError => e diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 62dc556b8c..a2c5d9c8da 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -36,7 +36,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do let(:media_attachments) { [] } before do - mentions.each { |a| Fabricate(:mention, status: status, account: a) } + mentions.each { |(account, silent)| Fabricate(:mention, status: status, account: account, silent: silent) } tags.each { |t| status.tags << t } media_attachments.each { |m| status.media_attachments << m } end @@ -320,7 +320,19 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end context 'when originally with mentions' do - let(:mentions) { [alice, bob] } + let(:mentions) { [[alice, false], [bob, false]] } + + before do + subject.call(status, json, json) + end + + it 'updates mentions' do + expect(status.active_mentions.reload.map(&:account_id)).to eq [alice.id] + end + end + + context 'when originally with silent mentions' do + let(:mentions) { [[alice, true], [bob, true]] } before do subject.call(status, json, json) diff --git a/spec/services/update_status_service_spec.rb b/spec/services/update_status_service_spec.rb index 99eddf2f88..2a2bbd68b6 100644 --- a/spec/services/update_status_service_spec.rb +++ b/spec/services/update_status_service_spec.rb @@ -199,6 +199,14 @@ RSpec.describe UpdateStatusService do .to eq [bob.id] expect(status.mentions.pluck(:account_id)) .to contain_exactly(alice.id, bob.id) + + # Going back when a mention was switched to silence should still be possible + subject.call(status, status.account_id, text: 'Hello @alice') + + expect(status.active_mentions.pluck(:account_id)) + .to eq [alice.id] + expect(status.mentions.pluck(:account_id)) + .to contain_exactly(alice.id, bob.id) end end From cc25d993302556f1bff25cea53f05c65686b0e9e Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 9 Jan 2025 14:47:12 +0100 Subject: [PATCH 06/15] Fix last paginated notification group only including data on a single notification (#33271) --- .../api/v2/notifications_controller.rb | 23 ++++++++- app/models/notification_group.rb | 26 +++++++--- spec/requests/api/v2/notifications_spec.rb | 49 +++++++++++++++++++ 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/v2/notifications_controller.rb b/app/controllers/api/v2/notifications_controller.rb index c070c0e5e7..cc38b95114 100644 --- a/app/controllers/api/v2/notifications_controller.rb +++ b/app/controllers/api/v2/notifications_controller.rb @@ -80,10 +80,31 @@ class Api::V2::NotificationsController < Api::BaseController return [] if @notifications.empty? MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do - NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types]) + pagination_range = (@notifications.last.id)..@notifications.first.id + + # If the page is incomplete, we know we are on the last page + if incomplete_page? + if paginating_up? + pagination_range = @notifications.last.id...(params[:max_id]&.to_i) + else + range_start = params[:since_id]&.to_i + range_start += 1 unless range_start.nil? + pagination_range = range_start..(@notifications.first.id) + end + end + + NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types]) end end + def incomplete_page? + @notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT) + end + + def paginating_up? + params[:min_id].present? + end + def browserable_account_notifications current_account.notifications.without_suspended.browserable( types: Array(browserable_params[:types]), diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 7d17774f20..e94008311b 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -89,22 +89,32 @@ class NotificationGroup < ActiveModelSerializers::Model binds = [ account_id, SAMPLE_ACCOUNTS_SIZE, - pagination_range.begin, - pagination_range.end, ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)), + pagination_range.begin || 0, ] + binds << pagination_range.end unless pagination_range.end.nil? + + upper_bound_cond = begin + if pagination_range.end.nil? + '' + elsif pagination_range.exclude_end? + 'AND id < $5' + else + 'AND id <= $5' + end + end ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] } SELECT groups.group_key, - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1), - array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT $2), - (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) AS notifications_count, - array(SELECT activity_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 AND activity_type = 'EmojiReaction'), + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1), + array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2), + (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count, + array(SELECT activity_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} AND activity_type = 'EmojiReaction'), (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id, - (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1) + (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1) FROM - unnest($5::text[]) AS groups(group_key); + unnest($3::text[]) AS groups(group_key); SQL else binds = [ diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index ffa0a71c77..aa4a861557 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -143,6 +143,55 @@ RSpec.describe 'Notifications' do end end + context 'when there are numerous notifications for the same final group' do + before do + user.account.notifications.destroy_all + 5.times.each { FavouriteService.new.call(Fabricate(:account), user.account.statuses.first) } + end + + context 'with no options' do + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + + context 'with min_id param' do + let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } } + + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + end + context 'with no options' do it 'returns expected notification types', :aggregate_failures do subject From 3867cc85049a7046235ea7ca30b9d2e5db5111ab Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 16 Dec 2024 13:33:55 +0100 Subject: [PATCH 07/15] Fix error decrementing status count when `FeaturedTags#last_status_at` is `nil` (#33320) --- app/models/featured_tag.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index 529056f9c6..dfc700649c 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -47,7 +47,7 @@ class FeaturedTag < ApplicationRecord def decrement(deleted_status) if statuses_count <= 1 update(statuses_count: 0, last_status_at: nil) - elsif last_status_at > deleted_status.created_at + elsif last_status_at.present? && last_status_at > deleted_status.created_at update(statuses_count: statuses_count - 1) else # Fetching the latest status creation time can be expensive, so only perform it From da9f9a74afd5f5be8b45cecff5d8be12935bfe84 Mon Sep 17 00:00:00 2001 From: Jesse Karmani Date: Wed, 18 Dec 2024 00:52:47 -0800 Subject: [PATCH 08/15] Fix down clause for notification policy v2 migrations (#33340) --- db/migrate/20240808124338_migrate_notifications_policy_v2.rb | 2 +- ...808124339_post_deployment_migrate_notifications_policy_v2.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20240808124338_migrate_notifications_policy_v2.rb b/db/migrate/20240808124338_migrate_notifications_policy_v2.rb index 2e0684826a..ba8f809c66 100644 --- a/db/migrate/20240808124338_migrate_notifications_policy_v2.rb +++ b/db/migrate/20240808124338_migrate_notifications_policy_v2.rb @@ -18,7 +18,7 @@ class MigrateNotificationsPolicyV2 < ActiveRecord::Migration[7.1] def down NotificationPolicy.in_batches.update_all(<<~SQL.squish) filter_not_following = CASE for_not_following WHEN 0 THEN false ELSE true END, - filter_not_following = CASE for_not_followers WHEN 0 THEN false ELSE true END, + filter_not_followers = CASE for_not_followers WHEN 0 THEN false ELSE true END, filter_new_accounts = CASE for_new_accounts WHEN 0 THEN false ELSE true END, filter_private_mentions = CASE for_private_mentions WHEN 0 THEN false ELSE true END SQL diff --git a/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb b/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb index eb0c909729..11b2b5bd2a 100644 --- a/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb +++ b/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb @@ -18,7 +18,7 @@ class PostDeploymentMigrateNotificationsPolicyV2 < ActiveRecord::Migration[7.1] def down NotificationPolicy.in_batches.update_all(<<~SQL.squish) filter_not_following = CASE for_not_following WHEN 0 THEN false ELSE true END, - filter_not_following = CASE for_not_followers WHEN 0 THEN false ELSE true END, + filter_not_followers = CASE for_not_followers WHEN 0 THEN false ELSE true END, filter_new_accounts = CASE for_new_accounts WHEN 0 THEN false ELSE true END, filter_private_mentions = CASE for_private_mentions WHEN 0 THEN false ELSE true END SQL From fd8ca6fc296a3a3ea9ff38d9b34e3e02ad08c1f2 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 18 Dec 2024 13:25:40 +0100 Subject: [PATCH 09/15] Fix incorrect notification settings migration for non-followers (#33348) --- db/migrate/20240808124338_migrate_notifications_policy_v2.rb | 2 +- ...808124339_post_deployment_migrate_notifications_policy_v2.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20240808124338_migrate_notifications_policy_v2.rb b/db/migrate/20240808124338_migrate_notifications_policy_v2.rb index ba8f809c66..ed1642d6b8 100644 --- a/db/migrate/20240808124338_migrate_notifications_policy_v2.rb +++ b/db/migrate/20240808124338_migrate_notifications_policy_v2.rb @@ -9,7 +9,7 @@ class MigrateNotificationsPolicyV2 < ActiveRecord::Migration[7.1] def up NotificationPolicy.in_batches.update_all(<<~SQL.squish) for_not_following = CASE filter_not_following WHEN true THEN 1 ELSE 0 END, - for_not_followers = CASE filter_not_following WHEN true THEN 1 ELSE 0 END, + for_not_followers = CASE filter_not_followers WHEN true THEN 1 ELSE 0 END, for_new_accounts = CASE filter_new_accounts WHEN true THEN 1 ELSE 0 END, for_private_mentions = CASE filter_private_mentions WHEN true THEN 1 ELSE 0 END SQL diff --git a/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb b/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb index 11b2b5bd2a..5daf646643 100644 --- a/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb +++ b/db/post_migrate/20240808124339_post_deployment_migrate_notifications_policy_v2.rb @@ -9,7 +9,7 @@ class PostDeploymentMigrateNotificationsPolicyV2 < ActiveRecord::Migration[7.1] def up NotificationPolicy.in_batches.update_all(<<~SQL.squish) for_not_following = CASE filter_not_following WHEN true THEN 1 ELSE 0 END, - for_not_followers = CASE filter_not_following WHEN true THEN 1 ELSE 0 END, + for_not_followers = CASE filter_not_followers WHEN true THEN 1 ELSE 0 END, for_new_accounts = CASE filter_new_accounts WHEN true THEN 1 ELSE 0 END, for_private_mentions = CASE filter_private_mentions WHEN true THEN 1 ELSE 0 END SQL From af2f6597cc394b418acb72fc5995a53d760b391d Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 3 Jan 2025 14:23:01 +0100 Subject: [PATCH 10/15] Fix incorrect `relationship_severance_event` attribute name in changelog (#33443) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a12cfe5b..b940efbaf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -135,7 +135,7 @@ The following changelog entries focus on changes visible to users, administrator - **Add notifications of severed relationships** (#27511, #29665, #29668, #29670, #29700, #29714, #29712, and #29731 by @ClearlyClaire and @Gargron)\ Notify local users when they lose relationships as a result of a local moderator blocking a remote account or server, allowing the affected user to retrieve the list of broken relationships.\ Note that this does not notify remote users.\ - This adds the `severed_relationships` notification type to the REST API and streaming, with a new [`relationship_severance_event` attribute](https://docs.joinmastodon.org/entities/Notification/#relationship_severance_event). + This adds the `severed_relationships` notification type to the REST API and streaming, with a new [`event` attribute](https://docs.joinmastodon.org/entities/Notification/#relationship_severance_event). - **Add hover cards in web UI** (#30754, #30864, #30850, #30879, #30928, #30949, #30948, #30931, and #31300 by @ClearlyClaire, @Gargron, and @renchap)\ Hovering over an avatar or username will now display a hover card with the first two lines of the user's description and their first two profile fields.\ This can be disabled in the “Animations and accessibility” section of the preferences. From d936349a02318f109015c5f64f0b79b6d4d66560 Mon Sep 17 00:00:00 2001 From: Michael Stanclift Date: Tue, 14 Jan 2025 10:35:58 -0600 Subject: [PATCH 11/15] Fix libyaml missing from Dockerfile build stage (#33591) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 4d6287912e..df6563a9a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -153,6 +153,7 @@ RUN \ libpq-dev \ libssl-dev \ libtool \ + libyaml-dev \ meson \ nasm \ pkg-config \ From 0805b13a944761c737a075c88896ae38c7152729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Thu, 16 Jan 2025 22:38:42 +0900 Subject: [PATCH 12/15] =?UTF-8?q?Add:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=82=BD=E3=83=95=E3=83=88=E3=82=A6=E3=82=A7=E3=82=A2?= =?UTF-8?q?=E5=90=8D=E3=81=AB`Iceshrimp.NET`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/instance_info.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/instance_info.rb b/app/models/instance_info.rb index 32b1969f94..b6f5e8986d 100644 --- a/app/models/instance_info.rb +++ b/app/models/instance_info.rb @@ -25,6 +25,7 @@ class InstanceInfo < ApplicationRecord firefish hollo iceshrimp + Iceshrimp.NET meisskey misskey pleroma From 92e529834d87fd4e02b5a28b11654b13316b8103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Tue, 31 Dec 2024 11:33:57 +0900 Subject: [PATCH 13/15] =?UTF-8?q?Add:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=81=AB`yojo-art?= =?UTF-8?q?`=20(#957)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/instance_info.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/instance_info.rb b/app/models/instance_info.rb index b6f5e8986d..3729a5755d 100644 --- a/app/models/instance_info.rb +++ b/app/models/instance_info.rb @@ -31,6 +31,7 @@ class InstanceInfo < ApplicationRecord pleroma sharkey tanukey + yojo-art ).freeze QUOTE_AVAILABLE_SOFTWARES = EMOJI_REACTION_AVAILABLE_SOFTWARES + %w(bridgy-fed).freeze From b803e5d4f18d63924a5d4f8dfd3a70c38bb7a1d7 Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 16 Jan 2025 23:02:38 +0900 Subject: [PATCH 14/15] Bump version to kb16.2 --- CHANGELOG.md | 18 ++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b940efbaf1..0b6d52c376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. +## [4.3.3] - 2025-01-16 + +### Security + +- Fix insufficient validation of account URIs ([GHSA-5wxh-3p65-r4g6](https://github.com/mastodon/mastodon/security/advisories/GHSA-5wxh-3p65-r4g6)) +- Update dependencies + +### Fixed + +- Fix `libyaml` missing from `Dockerfile` build stage (#33591 by @vmstan) +- Fix incorrect notification settings migration for non-followers (#33348 by @ClearlyClaire) +- Fix down clause for notification policy v2 migrations (#33340 by @jesseplusplus) +- Fix error decrementing status count when `FeaturedTags#last_status_at` is `nil` (#33320 by @ClearlyClaire) +- Fix last paginated notification group only including data on a single notification (#33271 by @ClearlyClaire) +- Fix processing of mentions for post edits with an existing corresponding silent mention (#33227 by @ClearlyClaire) +- Fix deletion of unconfirmed users with Webauthn set (#33186 by @ClearlyClaire) +- Fix empty authors preview card serialization (#33151, #33466 by @mjankowski and @ClearlyClaire) + ## [4.3.2] - 2024-12-03 ### Added diff --git a/docker-compose.yml b/docker-compose.yml index 4698c292a2..c88e186c2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,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: kmyblue:16.1 + image: kmyblue:16.2 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: build: dockerfile: ./streaming/Dockerfile context: . - image: kmyblue-streaming:16.1 + image: kmyblue-streaming:16.2 restart: always env_file: .env.production command: node ./streaming/index.js @@ -101,7 +101,7 @@ services: sidekiq: build: . - image: kmyblue:16.1 + image: kmyblue:16.2 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 88f4e20dbb..d90ef59b5b 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def kmyblue_minor - 1 + 2 end def kmyblue_flag From f03a5abc217a1769195a500a203ce803e8719e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Fri, 17 Jan 2025 09:33:11 +0900 Subject: [PATCH 15/15] Fix test --- app/models/notification_group.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index e94008311b..2bfe0894f3 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -111,7 +111,7 @@ class NotificationGroup < ActiveModelSerializers::Model array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2), (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count, array(SELECT activity_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} AND activity_type = 'EmojiReaction'), - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id, + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $4 ORDER BY id ASC LIMIT 1) AS min_id, (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1) FROM unnest($3::text[]) AS groups(group_key);