diff --git a/.nvmrc b/.nvmrc index 403f75d03823be..f666621e50063d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.20 +24.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index bebb2a3b15834b..f634505428b5c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,12 @@ All notable changes to this project will be documented in this file. ### Added -- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, and #36461 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\ +- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516 and #36528 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\ This includes a revamp of the composer interface.\ See https://blog.joinmastodon.org/2025/09/introducing-quote-posts/ for a user-centric overview of the feature, and https://docs.joinmastodon.org/client/quotes/ for API documentation. -- **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, and #36239 by @ClearlyClaire, @Gargron, and @diondiondion) +- **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, #36239, #36484 and #36481 by @ClearlyClaire, @Gargron, and @diondiondion) - **Add ability to block words in usernames** (#35407, #35655, and #35806 by @ClearlyClaire and @Gargron) +- Add support for displaying of quote posts in Moderator UI (#35964 by @ThisIsMissEm) - Add support for displaying link previews for Admin UI (#35958 by @ThisIsMissEm) - Add support for dynamic viewport height (#36272 by @e1berd) - Add support for numeric-based URIs for new local accounts (#32724, #36304, #36316, and #36365 by @ClearlyClaire) @@ -26,18 +27,20 @@ All notable changes to this project will be documented in this file. - Add delivery failure tracking and handling to FASP jobs (#35625, #35628, and #35723 by @oneiros) - Add example of quote post with a preview card to development sample data (#35616 by @ClearlyClaire) - Add second set of blocked text that applies to accounts regardless of account age for spam-blocking (#35563 by @ClearlyClaire) -- Add experimental feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, and #36402 by @ChaosExAnima and @braddunbar)\ +- Added emoji from Twemoji v16 (#36501 and #36530 by @ChaosExAnima) +- Add experimental feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, #36402, #36503, #36502 and #36532 by @ChaosExAnima and @braddunbar)\ This also completely reworks the processing and rendering of emojis and server-rendered HTML in statuses and other places. ### Changed - Change confirmation dialogs for follow button actions “unfollow”, “unblock”, and “withdraw request” (#36289 by @diondiondion) - Change “Follow” button labels (#36264 by @diondiondion) +- Change appearance settings to introduce new Advanced settings section (#36496 and #36506 by @diondiondion) - Change display of content warnings in Admin UI (#35935 by @ThisIsMissEm) - Change index on `follows` table to improve performance of some queries (#36374 by @ClearlyClaire) - Change links to accounts in settings and moderation views to link to local view unless account is suspended (#36340 by @diondiondion) - Change redirection for denied registration from web app to sign-in page with error message (#36384 by @ClearlyClaire) -- Change `timeline_preview` setting into four more granular settings (#36338 and #36467 by @ClearlyClaire) +- Change `timeline_preview` setting into four more granular settings (#36338, #36467 and #36497 by @ClearlyClaire) - Change wording and design of interaction dialog to simplify it (#36124 by @diondiondion) - Change dropdown menus to allow disabled items to be focused (#36078 by @diondiondion) - Change modal background colours in light mode (#36069 by @diondiondion) @@ -49,9 +52,11 @@ All notable changes to this project will be documented in this file. - Change auditable accounts to be sorted by username in admin action logs interface (#35272 by @breadtk) - Change order of translation restoration and service credit on post card (#33619 by @colindean) - Change position of ‘add more’ to be inside table toolbar on reports (#35963 by @ThisIsMissEm) +- Change docker-compose.yml sidekiq health check to work for both 4.4 and 4.5 (#36498 by @ClearlyClaire) ### Fixed +- Fix relationship not being fetched to evaluate whether to show a quote post (#36517 by @ClearlyClaire) - Fix rendering of poll options in status history modal (#35633 by @ThisIsMissEm) - Fix “mute” button being displayed to unauthenticated visitors in hashtag dropdown (#36353 by @mkljczk) - Fix overflow handling of `.more-from-author` (#36310 by @edent) @@ -76,6 +81,12 @@ All notable changes to this project will be documented in this file. - Fix glitchy status keyboard navigation (#35455 and #35504 by @diondiondion) - Fix post being submitted when pressing “Enter” in the CW field (#35445 by @diondiondion) +## [4.4.8] - 2025-10-21 + +### Security + +- Fix quote control bypass ([GHSA-8h43-rcqj-wpc6](https://github.com/mastodon/mastodon/security/advisories/GHSA-8h43-rcqj-wpc6)) + ## [4.4.7] - 2025-10-15 ### Fixed diff --git a/Dockerfile b/Dockerfile index e457ae3623b316..1c9c956b72c92f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,9 +14,9 @@ ARG BASE_REGISTRY="docker.io" # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"] # renovate: datasource=docker depName=docker.io/ruby ARG RUBY_VERSION="3.4.7" -# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] +# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="22"] # renovate: datasource=node-version depName=node -ARG NODE_MAJOR_VERSION="22" +ARG NODE_MAJOR_VERSION="24" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] ARG DEBIAN_VERSION="trixie" # Node.js image to use for base image based on combined variables (ex: 20-trixie-slim) diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 27dea05bf64313..12f48ebb2b3da0 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -7,7 +7,7 @@ def perform return if non_matching_uri_hosts?(@account.uri, @json['id']) quoted_status = status_from_uri(object_uri) - return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? + return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? || quoted_status.reblog? if StatusPolicy.new(@account, quoted_status).quote? accept_quote_request!(quoted_status) diff --git a/app/models/concerns/status/interaction_policy_concern.rb b/app/models/concerns/status/interaction_policy_concern.rb index 5f4fd086b312a0..7de73986c3f7cb 100644 --- a/app/models/concerns/status/interaction_policy_concern.rb +++ b/app/models/concerns/status/interaction_policy_concern.rb @@ -29,7 +29,7 @@ def quote_policy_as_keys(kind) # Returns `:automatic`, `:manual`, `:unknown` or `:denied` def quote_policy_for_account(other_account, preloaded_relations: {}) - return :denied if other_account.nil? || direct_visibility? || limited_visibility? + return :denied if other_account.nil? || direct_visibility? || limited_visibility? || reblog? following_author = nil followed_by_author = nil diff --git a/app/models/quote.rb b/app/models/quote.rb index 0d24cb239a8fd4..e81d427089da8e 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -39,6 +39,7 @@ class Quote < ApplicationRecord validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? } validates :approval_uri, absence: true, if: -> { quoted_account&.local? } validate :validate_visibility + validate :validate_original_quoted_status after_create_commit :increment_counter_caches! after_destroy_commit :decrement_counter_caches! @@ -85,6 +86,10 @@ def validate_visibility errors.add(:quoted_status_id, :visibility_mismatch) end + def validate_original_quoted_status + errors.add(:quoted_status_id, :reblog_unallowed) if quoted_status&.reblog? + end + def set_activity_uri self.activity_uri = [ActivityPub::TagManager.instance.uri_for(account), '/quote_requests/', SecureRandom.uuid].join end diff --git a/app/serializers/rest/base_quote_serializer.rb b/app/serializers/rest/base_quote_serializer.rb index be9d5cbe6f238e..2637014b697316 100644 --- a/app/serializers/rest/base_quote_serializer.rb +++ b/app/serializers/rest/base_quote_serializer.rb @@ -14,7 +14,7 @@ def state end def quoted_status - object.quoted_status if object.accepted? && object.quoted_status.present? && !status_filter.filtered_for_quote? + object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && !status_filter.filtered_for_quote? end private diff --git a/app/serializers/rest/scheduled_status_serializer.rb b/app/serializers/rest/scheduled_status_serializer.rb index 7c54f39c0d137d..71ddb7b3e1707b 100644 --- a/app/serializers/rest/scheduled_status_serializer.rb +++ b/app/serializers/rest/scheduled_status_serializer.rb @@ -8,4 +8,11 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer def id object.id.to_s end + + def params + object.params.merge( + quoted_status_id: object.params['quoted_status_id']&.to_s, + quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS.keys.find { |key| object.params['quote_approval_policy']&.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[key] << 16) }&.to_s || 'nobody' + ) + end end diff --git a/app/services/activitypub/verify_quote_service.rb b/app/services/activitypub/verify_quote_service.rb index 5ed516cde87d75..6e0a225fde0b90 100644 --- a/app/services/activitypub/verify_quote_service.rb +++ b/app/services/activitypub/verify_quote_service.rb @@ -81,7 +81,7 @@ def fetch_quoted_post_if_needed!(uri, prefetched_body: nil) status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id, depth: @depth + 1) - @quote.update(quoted_status: status) if status.present? + @quote.update(quoted_status: status) if status.present? && !status.reblog? rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e @fetching_error = e end @@ -99,7 +99,7 @@ def import_quoted_post_if_needed!(uri) status = ActivityPub::FetchRemoteStatusService.new.call(object['id'], prefetched_body: object, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id, depth: @depth) - if status.present? + if status.present? && !status.reblog? @quote.update(quoted_status: status) true else diff --git a/app/workers/publish_scheduled_status_worker.rb b/app/workers/publish_scheduled_status_worker.rb index bcf20b49431639..0e34aa77916c90 100644 --- a/app/workers/publish_scheduled_status_worker.rb +++ b/app/workers/publish_scheduled_status_worker.rb @@ -23,6 +23,7 @@ def options_with_objects(options) options.tap do |options_hash| options_hash[:application] = Doorkeeper::Application.find(options_hash.delete(:application_id)) if options[:application_id] options_hash[:thread] = Status.find(options_hash.delete(:in_reply_to_id)) if options_hash[:in_reply_to_id] + options_hash[:quoted_status] = Status.find(options_hash.delete(:quoted_status_id)) if options_hash[:quoted_status_id] end end end diff --git a/config/initializers/strong_migrations.rb b/config/initializers/strong_migrations.rb index 59053ca18738ee..d722b16a0f4ffc 100644 --- a/config/initializers/strong_migrations.rb +++ b/config/initializers/strong_migrations.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true StrongMigrations.start_after = 2017_09_24_022025 -StrongMigrations.target_version = 13 +StrongMigrations.target_version = 14 diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index 9ee1b3873c9a06..cee48c7dedcfcb 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -65,6 +65,7 @@ be: setting_display_media_hide_all: Заўсёды хаваць медыя setting_display_media_show_all: Заўсёды паказваць медыя setting_emoji_style: Як паказваць эмодзі. "Аўтаматычны" будзе намагацца выкарыстоўваць мясцовыя эмодзі, але для састарэлых браўзераў — Twemoji. + setting_quick_boosting_html: Калі ўключана, клік па %{boost_icon} іконцы пашырэння адразу пашырыць допіс, без адкрыцця меню "пашырыць/цытаваць". Перасоўвае дзеянне цытавання ў меню %{options_icon} (выбару). setting_system_scrollbars_ui: Працуе толькі ў камп'ютарных браўзерах на аснове Safari і Chrome setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі @@ -254,6 +255,7 @@ be: setting_expand_spoilers: Заўжды разгортваць допісы з папярэджаннем аб змесціве setting_hide_network: Схаваць вашы сувязі setting_missing_alt_text_modal: Папярэджваць перад публікацыяй допісу без альтэрнатыўнага тэксту + setting_quick_boosting: Уключыць хуткае пашырэнне setting_reduce_motion: Памяншэнне руху ў анімацыях setting_system_font_ui: Выкарыстоўваць прадвызначаны сістэмны шрыфт setting_system_scrollbars_ui: Паказваць паласу пракручвання па змаўчанні diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index 889f59dbed4ce9..a536fdf99b4fb5 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -65,6 +65,7 @@ nn: setting_display_media_hide_all: Alltid skjul alt media setting_display_media_show_all: Vis alltid media setting_emoji_style: Korleis du skal visa smilefjes. «Auto» prøver å visa innebygde smilefjes, men bruker Twemoji som reserveløysing for eldre nettlesarar. + setting_quick_boosting_html: Når dette er skrudd på og du klikkar på %{boost_icon} framhev-ikonet, vil du framheva innlegget med ein gong i staden for å opna framhev/siter-menyen. Du finn siteringa i %{options_icon} (Val)-menyen. setting_system_scrollbars_ui: Gjeld berre skrivebordsnettlesarar som er bygde på Safari og Chrome setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk @@ -252,6 +253,7 @@ nn: setting_expand_spoilers: Vid alltid ut tut som er merka med innhaldsåtvaringar setting_hide_network: Gøym nettverket ditt setting_missing_alt_text_modal: Åtvar meg før eg legg ut media utan alternativ tekst + setting_quick_boosting: Skru på rask framheving setting_reduce_motion: Minsk rørsle i animasjonar setting_system_font_ui: Bruk standardskrifttypen på systemet setting_system_scrollbars_ui: Bruk standardrullefeltet til systemet diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 34618a3193e729..6809bb3f509cd4 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -65,6 +65,7 @@ tr: setting_display_media_hide_all: Medyayı her zaman gizle setting_display_media_show_all: Medyayı her zaman göster setting_emoji_style: Emojiler nasıl görüntülensin. "Otomatik" seçeneği yerel emojileri kullanmaya çalışır, ancak eski tarayıcılar için Twemoji'yi kullanır. + setting_quick_boosting_html: Etkinleştirildiğinde, %{boost_icon} Öne Çıkar simgesine tıklandığında, öne çıkar/alıntı açılır menüsünü görüntüleme yerine hemen öne çıkarma işlemi gerçekleştirilir. Alıntı işlevi %{options_icon} (Seçenekler) menüsüne taşınır. setting_system_scrollbars_ui: Yalnızca Safari ve Chrome tabanlı masaüstü tarayıcılar için geçerlidir setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin @@ -252,6 +253,7 @@ tr: setting_expand_spoilers: İçerik uyarılarıyla işaretli gönderileri her zaman genişlet setting_hide_network: Sosyal grafiğini gizle setting_missing_alt_text_modal: Alternatif metni olmayan bir medya göndermeden önce beni uyar + setting_quick_boosting: Hızlı öne çıkarmayı etkinleştir setting_reduce_motion: Animasyonlarda hareketi azalt setting_system_font_ui: Sistemin varsayılan yazı tipini kullan setting_system_scrollbars_ui: Sistemin varsayılan kaydırma çubuğunu kullan diff --git a/config/locales/tr.yml b/config/locales/tr.yml index f6438918887a32..8f2894e0c15310 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1191,6 +1191,7 @@ tr: advanced_settings: Gelişmiş ayarlar animations_and_accessibility: Animasyonlar ve erişilebilirlik boosting_preferences: Öne çıkarma seçenekleri + boosting_preferences_info_html: "İpucu: Ayarlardan bağımsız olarak, %{icon} Öne Çıkar simgesine Shift + Tıklama uygulandığından öne çıkarma hemen gerçekleştirilir." discovery: Keşfet localization: body: Mastodon, gönüllüler tarafından çevrilmektedir. diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index dc2ee319229916..8ae82f4d07953d 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -35,7 +35,7 @@ def patch end def default_prerelease - 'beta.1' + 'beta.2' end def prerelease diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 054f8b0177b641..e5ca6ac2ac5b1b 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -63,7 +63,7 @@ namespace :db do task pre_migration_check: :environment do pg_version = ActiveRecord::Base.connection.database_version - abort 'This version of Mastodon requires PostgreSQL 13.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 130_000 + abort 'This version of Mastodon requires PostgreSQL 14.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 140_000 schema_version = ActiveRecord::Migrator.current_version abort <<~MESSAGE if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && schema_version < 2023_09_07_150100 diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 999dff3ad1d095..4fb308c8399d4a 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -1879,6 +1879,60 @@ def activity_for_object(json) end end + context 'with a quote of a known reblog that is otherwise valid' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account, reblog: Fabricate(:status)) } + let(:approval_uri) { 'https://quoted.example.com/quote-approval' } + + let(:object_json) do + build_object( + type: 'Note', + content: 'woah what she said is amazing', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri + ) + end + + before do + stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization', + gts: 'https://gotosocial.org/ns#', + interactionPolicy: { + '@id': 'gts:interactionPolicy', + '@type': '@id', + }, + interactingObject: { + '@id': 'gts:interactingObject', + '@type': '@id', + }, + interactionTarget: { + '@id': 'gts:interactionTarget', + '@type': '@id', + }, + }, + ], + type: 'QuoteAuthorization', + id: approval_uri, + attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account), + interactingObject: object_json[:id], + interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status), + })) + end + + it 'creates a status without the verified quote' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + expect(status).to_not be_nil + expect(status.quote).to_not be_nil + expect(status.quote.state).to_not eq 'accepted' + expect(status.quote.quoted_status).to be_nil + end + end + context 'when a vote to a local poll' do let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } let!(:local_status) { Fabricate(:status, poll: poll) } diff --git a/spec/models/concerns/status/interaction_policy_concern_spec.rb b/spec/models/concerns/status/interaction_policy_concern_spec.rb index b59a1186d9bd62..ebc261fc76d5f8 100644 --- a/spec/models/concerns/status/interaction_policy_concern_spec.rb +++ b/spec/models/concerns/status/interaction_policy_concern_spec.rb @@ -15,6 +15,22 @@ describe '#quote_policy_for_account' do let(:account) { Fabricate(:account) } + context 'when the account is the author' do + let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0) } + + it 'returns :automatic' do + expect(status.quote_policy_for_account(account)).to eq :automatic + end + + context 'when it is a reblog' do + let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0, reblog: Fabricate(:status)) } + + it 'returns :automatic' do + expect(status.quote_policy_for_account(account)).to eq :denied + end + end + end + context 'when the account is not following the user' do it 'returns :manual because of the public entry in the manual policy' do expect(status.quote_policy_for_account(account)).to eq :manual diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb index 84bea97e800c7d..4316c1409d0574 100644 --- a/spec/requests/api/v1/accounts/credentials_spec.rb +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -91,6 +91,11 @@ expect(response).to have_http_status(422) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body) + .to include( + error: /Validation failed/, + details: include(note: contain_exactly(include(error: 'ERR_TOO_LONG', description: /character limit/))) + ) end end diff --git a/spec/requests/api/v1/accounts_spec.rb b/spec/requests/api/v1/accounts_spec.rb index 4604f8ccb8533b..2412181cfbf164 100644 --- a/spec/requests/api/v1/accounts_spec.rb +++ b/spec/requests/api/v1/accounts_spec.rb @@ -118,6 +118,11 @@ .to have_http_status(422) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body) + .to include( + error: /Validation failed/, + details: include(date_of_birth: contain_exactly(include(error: 'ERR_BELOW_LIMIT', description: /below the age limit/))) + ) end end diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb index 2cf00986542c4e..6fc2f2eca9cd67 100644 --- a/spec/serializers/rest/scheduled_status_serializer_spec.rb +++ b/spec/serializers/rest/scheduled_status_serializer_spec.rb @@ -10,14 +10,18 @@ ) end - let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, params: { application_id: 123 }) } + let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, params: { application_id: 123, quoted_status_id: 456, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 }) } describe 'serialization' do it 'returns expected values and removes application_id from params' do expect(subject.deep_symbolize_keys) .to include( scheduled_at: be_a(String).and(match_api_datetime_format), - params: include(:application_id) + params: a_hash_including( + application_id: 123, + quoted_status_id: '456', + quote_approval_policy: 'public' + ) ) end end diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 39990aa2c6df48..aa0c714aa68c83 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -1130,6 +1130,72 @@ end end + context 'when the status adds a verifiable quote of a reblog through an explicit update' do + let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account, reblog: Fabricate(:status)) } + let(:approval_uri) { 'https://quoted.example.com/approvals/1' } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri, + } + end + + before do + stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization', + gts: 'https://gotosocial.org/ns#', + interactionPolicy: { + '@id': 'gts:interactionPolicy', + '@type': '@id', + }, + interactingObject: { + '@id': 'gts:interactingObject', + '@type': '@id', + }, + interactionTarget: { + '@id': 'gts:interactionTarget', + '@type': '@id', + }, + }, + ], + type: 'QuoteAuthorization', + id: approval_uri, + attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account), + interactingObject: ActivityPub::TagManager.instance.uri_for(status), + interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status), + })) + end + + it 'updates the approval URI but does not verify the quote' do + expect { subject.call(status, json, json) } + .to change(status, :quote).from(nil) + expect(status.quote.approval_uri).to eq approval_uri + expect(status.quote.state).to_not eq 'accepted' + expect(status.quote.quoted_status).to be_nil + end + end + context 'when the status adds a unverifiable quote through an implicit update' do let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } let(:quoted_status) { Fabricate(:status, account: quoted_account) } diff --git a/spec/workers/publish_scheduled_status_worker_spec.rb b/spec/workers/publish_scheduled_status_worker_spec.rb index a91e66596582c5..81480f7e54cb5a 100644 --- a/spec/workers/publish_scheduled_status_worker_spec.rb +++ b/spec/workers/publish_scheduled_status_worker_spec.rb @@ -13,8 +13,12 @@ end context 'when the account is not disabled' do + let(:user) { Fabricate(:user) } + let(:scheduled_status) { Fabricate(:scheduled_status, account: user.account, params: { text: 'Hello world, future!', quoted_status_id: Fabricate(:status, account: user.account).id }) } + it 'creates a status and removes scheduled record' do expect(scheduled_status.account.statuses.first.text).to eq 'Hello world, future!' + expect(scheduled_status.account.statuses.first.quote).to_not be_nil expect(ScheduledStatus.find_by(id: scheduled_status.id)).to be_nil end diff --git a/streaming/Dockerfile b/streaming/Dockerfile index 679425dfcc2944..3a12007f68b19b 100644 --- a/streaming/Dockerfile +++ b/streaming/Dockerfile @@ -8,9 +8,9 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BASE_REGISTRY="docker.io" -# Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] +# Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="22"] # renovate: datasource=node-version depName=node -ARG NODE_MAJOR_VERSION="22" +ARG NODE_MAJOR_VERSION="24" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] ARG DEBIAN_VERSION="trixie" # Node image to use for base image based on combined variables (ex: 20-trixie-slim) @@ -32,20 +32,20 @@ ARG GID="991" # Apply Mastodon build options based on options above ENV \ -# Apply Mastodon version information + # Apply Mastodon version information MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \ MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \ -# Apply timezone + # Apply timezone TZ=${TZ} ENV \ -# Configure the IP to bind Mastodon to when serving traffic + # Configure the IP to bind Mastodon to when serving traffic BIND="0.0.0.0" \ -# Explicitly set PORT to match the exposed port + # Explicitly set PORT to match the exposed port PORT=4000 \ -# Use production settings for Yarn, Node and related nodejs based tools + # Use production settings for Yarn, Node and related nodejs based tools NODE_ENV="production" \ -# Add Ruby and Mastodon installation to the PATH + # Add Ruby and Mastodon installation to the PATH DEBIAN_FRONTEND="noninteractive" # Set default shell used for running commands @@ -56,29 +56,29 @@ ARG TARGETPLATFORM RUN echo "Target platform is ${TARGETPLATFORM}" RUN \ -# Remove automatic apt cache Docker cleanup scripts + # Remove automatic apt cache Docker cleanup scripts rm -f /etc/apt/apt.conf.d/docker-clean; \ -# Sets timezone + # Sets timezone echo "${TZ}" > /etc/localtime; \ -# Creates mastodon user/group and sets home directory + # Creates mastodon user/group and sets home directory groupadd -g "${GID}" mastodon; \ useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \ -# Creates symlink for /mastodon folder + # Creates symlink for /mastodon folder ln -s /opt/mastodon /mastodon; # hadolint ignore=DL3008,DL3005 RUN \ -# Mount Apt cache and lib directories from Docker buildx caches ---mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \ ---mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ -# Upgrade to check for security updates to Debian image + # Mount Apt cache and lib directories from Docker buildx caches + --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \ + --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ + # Upgrade to check for security updates to Debian image apt-get update; \ apt-get dist-upgrade -yq; \ apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - tzdata \ - wget \ + ca-certificates \ + curl \ + tzdata \ + wget \ ; # Set /opt/mastodon as working directory @@ -91,19 +91,19 @@ COPY .yarn /opt/mastodon/.yarn COPY ./streaming /opt/mastodon/streaming RUN \ -# Mount local Corepack and Yarn caches from Docker buildx caches ---mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ ---mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ + # Mount local Corepack and Yarn caches from Docker buildx caches + --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ + --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ # Configure Corepack rm /usr/local/bin/yarn*; \ corepack enable; \ corepack prepare --activate; RUN \ -# Mount Corepack and Yarn caches from Docker buildx caches ---mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ ---mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ -# Install Node packages + # Mount Corepack and Yarn caches from Docker buildx caches + --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \ + --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ + # Install Node packages yarn workspaces focus --production @mastodon/streaming; # Set the running user for resulting container diff --git a/yarn.lock b/yarn.lock index 98e18a89567946..a243be793029ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10424,27 +10424,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.55.0": - version: 1.55.0 - resolution: "playwright-core@npm:1.55.0" +"playwright-core@npm:1.56.1": + version: 1.56.1 + resolution: "playwright-core@npm:1.56.1" bin: playwright-core: cli.js - checksum: 10c0/c39d6aa30e7a4e73965942ca5e13405ae05c9cb49f755a35f04248c864c0b24cf662d9767f1797b3ec48d1cf4e54774dce4a19c16534bd5cfd2aa3da81c9dc3a + checksum: 10c0/ffd40142b99c68678b387445d5b42f1fee4ab0b65d983058c37f342e5629f9cdbdac0506ea80a0dfd41a8f9f13345bad54e9a8c35826ef66dc765f4eb3db8da7 languageName: node linkType: hard "playwright@npm:^1.54.1": - version: 1.55.0 - resolution: "playwright@npm:1.55.0" + version: 1.56.1 + resolution: "playwright@npm:1.56.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.55.0" + playwright-core: "npm:1.56.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/51605b7e57a5650e57972c5fdfc09d7a9934cca1cbee5beacca716fa801e25cb5bb7c1663de90c22b300fde884e5545a2b13a0505a93270b660687791c478304 + checksum: 10c0/8e9965aede86df0f4722063385748498977b219630a40a10d1b82b8bd8d4d4e9b6b65ecbfa024331a30800163161aca292fb6dd7446c531a1ad25f4155625ab4 languageName: node linkType: hard