Merge commit 'aedc5f692144aa36245fe47c277bb5ad4766a335' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2025-05-05 20:31:48 +02:00
15 changed files with 173 additions and 44 deletions

View File

@@ -620,7 +620,7 @@ GEM
psych (5.2.3)
date
stringio
public_suffix (6.0.1)
public_suffix (6.0.2)
puma (6.6.0)
nio4r (~> 2.0)
pundit (2.5.0)
@@ -743,7 +743,7 @@ GEM
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9)
rspec-support (3.13.3)
rubocop (1.75.4)
rubocop (1.75.5)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -800,7 +800,7 @@ GEM
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securerandom (0.4.1)
selenium-webdriver (4.31.0)
selenium-webdriver (4.32.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)

View File

@@ -72,6 +72,18 @@ module JsonLdHelper
!haystack.casecmp(needle).zero?
end
def safe_prefetched_embed(account, object, context)
return unless object.is_a?(Hash)
# NOTE: Replacing the object's context by that of the parent activity is
# not sound, but it's consistent with the rest of the codebase
object = object.merge({ '@context' => context })
return if value_or_id(first_of_value(object['attributedTo'])) != account.uri || non_matching_uri_hosts?(account.uri, object['id'])
object
end
def canonicalize(json)
graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
graph.dump(:normalize)

View File

@@ -19,14 +19,17 @@
"account.block_domain": "Blokuoti serverį {domain}",
"account.block_short": "Blokuoti",
"account.blocked": "Užblokuota",
"account.blocking": "Blokavimas",
"account.cancel_follow_request": "Atšaukti sekimą",
"account.copy": "Kopijuoti nuorodą į profilį",
"account.direct": "Privačiai paminėti @{name}",
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
"account.domain_blocking": "Blokuoti domeną",
"account.edit_profile": "Redaguoti profilį",
"account.enable_notifications": "Pranešti man, kai @{name} paskelbia",
"account.endorse": "Rodyti profilyje",
"account.featured": "Rodomi",
"account.featured.accounts": "Profiliai",
"account.featured.hashtags": "Saitažodžiai",
"account.featured.posts": "Įrašai",
"account.featured_tags.last_status_at": "Paskutinis įrašas {date}",
@@ -39,6 +42,7 @@
"account.following": "Sekama",
"account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}",
"account.follows.empty": "Šis naudotojas dar nieko neseka.",
"account.follows_you": "Seka tave",
"account.go_to_profile": "Eiti į profilį",
"account.hide_reblogs": "Slėpti pasidalinimus iš @{name}",
"account.in_memoriam": "Atminimui.",
@@ -53,6 +57,8 @@
"account.mute_notifications_short": "Nutildyti pranešimus",
"account.mute_short": "Nutildyti",
"account.muted": "Nutildytas",
"account.muting": "Užtildymas",
"account.mutual": "Jūs sekate vienas kitą",
"account.no_bio": "Nėra pateikto aprašymo.",
"account.open_original_page": "Atidaryti originalų puslapį",
"account.posts": "Įrašai",
@@ -61,6 +67,7 @@
"account.report": "Pranešti apie @{name}",
"account.requested": "Laukiama patvirtinimo. Spustelėk, kad atšauktum sekimo prašymą",
"account.requested_follow": "{name} paprašė tave sekti",
"account.requests_to_follow_you": "Prašymai sekti jus",
"account.share": "Bendrinti @{name} profilį",
"account.show_reblogs": "Rodyti pasidalinimus iš @{name}",
"account.statuses_counter": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}",
@@ -255,6 +262,7 @@
"disabled_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta.",
"dismissable_banner.community_timeline": "Tai naujausi vieši įrašai iš žmonių, kurių paskyros talpinamos {domain}.",
"dismissable_banner.dismiss": "Atmesti",
"dismissable_banner.explore_links": "Šiomis naujienų istorijomis šiandien \"Fediverse\" dalijamasi dažniausiai. Naujesnės istorijos, kurias paskelbė daugiau skirtingų žmonių, užima aukštesnę vietą.",
"domain_block_modal.block": "Blokuoti serverį",
"domain_block_modal.block_account_instead": "Blokuoti @{name} vietoj to",
"domain_block_modal.they_can_interact_with_old_posts": "Žmonės iš šio serverio gali bendrauti su tavo senomis įrašomis.",

View File

@@ -26,6 +26,8 @@
"account.edit_profile": "Upraviť profil",
"account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}",
"account.endorse": "Zobraziť na vlastnom profile",
"account.featured.accounts": "Profily",
"account.featured.hashtags": "Hashtagy",
"account.featured.posts": "Príspevky",
"account.featured_tags.last_status_at": "Posledný príspevok dňa {date}",
"account.featured_tags.last_status_never": "Žiadne príspevky",
@@ -37,6 +39,7 @@
"account.following": "Sledovaný účet",
"account.following_counter": "{count, plural, one {{counter} sledovaných} other {{counter} sledovaných}}",
"account.follows.empty": "Tento účet ešte nikoho nesleduje.",
"account.follows_you": "Nasleduje ťa",
"account.go_to_profile": "Prejsť na profil",
"account.hide_reblogs": "Skryť zdieľania od @{name}",
"account.in_memoriam": "In memoriam.",
@@ -208,6 +211,7 @@
"confirmations.redraft.confirm": "Vymazať a prepísať",
"confirmations.redraft.message": "Určite chcete tento príspevok vymazať a prepísať? Prídete o jeho zdieľania a ohviezdičkovania a odpovede na pôvodný príspevok budú odlúčené.",
"confirmations.redraft.title": "Vymazať a prepísať príspevok?",
"confirmations.remove_from_followers.confirm": "Odstrániť nasledovateľa",
"confirmations.reply.confirm": "Odpovedať",
"confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
"confirmations.reply.title": "Prepísať príspevok?",

View File

@@ -204,7 +204,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@quote.status = status
@quote.save
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, request_id: @options[:request_id])
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context'])
ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id])
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] })
end

View File

@@ -122,6 +122,11 @@ class ActivityPub::Parser::StatusParser
end.first
end
# The inlined quote; out of the attributes we support, only `https://w3id.org/fep/044f#quote` explicitly supports inlined objects
def quoted_object
as_array(@object['quote']).first
end
def quote_approval_uri
as_array(@object['quoteAuthorization']).first
end

View File

@@ -6,14 +6,13 @@
class HttpSignatureDraft
REQUEST_TARGET = '(request-target)'
def initialize(keypair, key_id, full_path: true)
def initialize(keypair, key_id)
@keypair = keypair
@key_id = key_id
@full_path = full_path
end
def request_target(verb, url)
if url.query.nil? || !@full_path
if url.query.nil?
"#{verb} #{url.path}"
else
"#{verb} #{url.path}?#{url.query}"

View File

@@ -75,7 +75,6 @@ class Request
@url = Addressable::URI.parse(url).normalize
@http_client = options.delete(:http_client)
@allow_local = options.delete(:allow_local)
@full_path = !options.delete(:omit_query_string)
@options = {
follow: {
max_hops: 3,
@@ -102,7 +101,7 @@ class Request
key_id = ActivityPub::TagManager.instance.key_uri_for(actor)
keypair = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : actor.keypair
@signing = HttpSignatureDraft.new(keypair, key_id, full_path: @full_path)
@signing = HttpSignatureDraft.new(keypair, key_id)
self
end

View File

@@ -57,20 +57,7 @@ class ActivityPub::FetchRepliesService < BaseService
return unless @allow_synchronous_requests
return if non_matching_uri_hosts?(@reference_uri, collection_or_uri)
# NOTE: For backward compatibility reasons, Mastodon signs outgoing
# queries incorrectly by default.
#
# While this is relevant for all URLs with query strings, this is
# the only code path where this happens in practice.
#
# Therefore, retry with correct signatures if this fails.
begin
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
rescue Mastodon::UnexpectedResponseError => e
raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present?
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary, request_options: { omit_query_string: false })
end
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
end
def filter_replies(items)

View File

@@ -273,7 +273,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
def update_quote!
return unless Mastodon::Feature.inbound_quotes_enabled?
quote = nil
quote_uri = @status_parser.quote_uri
if quote_uri.present?
@@ -294,21 +293,23 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
quote = Quote.create(status: @status, approval_uri: approval_uri)
@quote_changed = true
end
end
if quote.present?
begin
quote.save
ActivityPub::VerifyQuoteService.new.call(quote, fetchable_quoted_uri: quote_uri, request_id: @request_id)
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, quote.id, quote_uri, { 'request_id' => @request_id })
end
quote.save
fetch_and_verify_quote!(quote, quote_uri)
elsif @status.quote.present?
@status.quote.destroy!
@quote_changed = true
end
end
def fetch_and_verify_quote!(quote, quote_uri)
embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @activity_json['context'])
ActivityPub::VerifyQuoteService.new.call(quote, fetchable_quoted_uri: quote_uri, prefetched_quoted_object: embedded_quote, request_id: @request_id)
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, quote.id, quote_uri, { 'request_id' => @request_id })
end
def update_counts!
likes = @status_parser.favourites_count
shares = @status_parser.reblogs_count

View File

@@ -4,15 +4,15 @@ class ActivityPub::VerifyQuoteService < BaseService
include JsonLdHelper
# Optionally fetch quoted post, and verify the quote is authorized
def call(quote, fetchable_quoted_uri: nil, prefetched_body: nil, request_id: nil)
def call(quote, fetchable_quoted_uri: nil, prefetched_quoted_object: nil, prefetched_approval: nil, request_id: nil)
@request_id = request_id
@quote = quote
@fetching_error = nil
fetch_quoted_post_if_needed!(fetchable_quoted_uri)
fetch_quoted_post_if_needed!(fetchable_quoted_uri, prefetched_body: prefetched_quoted_object)
return if fast_track_approval! || quote.approval_uri.blank?
@json = fetch_approval_object(quote.approval_uri, prefetched_body:)
@json = fetch_approval_object(quote.approval_uri, prefetched_body: prefetched_approval)
return quote.reject! if @json.nil?
return if non_matching_uri_hosts?(quote.approval_uri, value_or_id(@json['attributedTo']))
@@ -68,11 +68,11 @@ class ActivityPub::VerifyQuoteService < BaseService
ActivityPub::TagManager.instance.uri_for(@quote.status) == value_or_id(@json['interactingObject'])
end
def fetch_quoted_post_if_needed!(uri)
def fetch_quoted_post_if_needed!(uri, prefetched_body: nil)
return if uri.nil? || @quote.quoted_status.present?
status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id)
status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id)
@quote.update(quoted_status: status) if status.present?
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
if ENV['REDIS_NAMESPACE']
es_configured = ENV['ES_ENABLED'] == 'true' || ENV.fetch('ES_HOST', 'localhost') != 'localhost' || ENV.fetch('ES_PORT', '9200') != '9200' || ENV.fetch('ES_PASS', 'password') != 'password'
warn <<~MESSAGE
WARNING: the REDIS_NAMESPACE environment variable is deprecated and will be removed in Mastodon 4.4.0.
Please see documentation at https://github.com/mastodon/redis_namespace_migration
MESSAGE
warn <<~MESSAGE if es_configured && !ENV['ES_PREFIX']
In addition, as REDIS_NAMESPACE is being used as a prefix for Elasticsearch, please do not forget to set ES_PREFIX to "#{ENV.fetch('REDIS_NAMESPACE')}".
MESSAGE
end

View File

@@ -36,6 +36,25 @@ nan:
approved_msg: 成功審核 %{username} ê註冊申請ah
are_you_sure: Lí kám確定
avatar: 標頭
by_domain: 域名
change_email:
changed_msg: Email改成功ah
current_email: 現在ê email
label: 改email
new_email: 新ê email
submit: 改email
title: 替 %{username} 改email
change_role:
changed_msg: 角色改成功ah
edit_roles: 管理用者ê角色
label: 改角色
no_role: 無角色
title: 替 %{username} 改角色
confirm: 確認
confirmed: 確認ah
confirming: Teh確認
custom: 自訂
delete: Thâi資料
deleted: Thâi掉ah
demote: 降級
destroyed_msg: Teh-beh thâi掉 %{username} ê資料
@@ -49,15 +68,61 @@ nan:
email: 電子phue箱
email_status: 電子phue ê狀態
enable: 取消冷凍
enable_sign_in_token_auth: 啟用電子phue ê token認證
enabled: 啟用ah
enabled_msg: 成功kā %{username} ê口座退冰
followers: 跟tuè lí ê
follows: Lí跟tuè ê
header: 封面ê圖
inbox_url: 收件kheh-á ê URL
invite_request_text: 加入ê理由
invited_by: 邀請ê lâng
ip: IP
joined: 加入ê時
location:
all: Kui ê
local: 本地
remote: 別ê站
title: 位置
login_status: 登入ê狀態
media_attachments: 媒體ê附件
memorialize: 變做故人ê口座
memorialized: 變做故人ê口座ah
memorialized_msg: 成功kā %{username} 變做故人ê口座ah
moderation:
active: 活ê
all: 全部
disabled: 停止使用ah
pending: Teh審核
silenced: 受限制
suspended: 權限中止ah
title: 管理
moderation_notes: 管理ê註釋
most_recent_activity: 最近ê活動時間
most_recent_ip: 最近ê IP
no_account_selected: 因為無揀任何口座所以lóng無改變
no_limits_imposed: 無受著限制
no_role_assigned: 無分著角色
not_subscribed: 無訂
pending: Teh等審核
perform_full_suspension: 中止權限
previous_strikes: Khah早ê處份
remove_avatar: Thâi掉標頭
removed_avatar_msg: 成功thâi掉 %{username} ê 標頭影像
username: 用者ê名
view_domain: 看域名ê摘要
warn: 警告
web: 網頁
whitelisted: 允准佇聯邦傳資料
action_logs:
action_types:
approve_appeal: 批准投訴
approve_user: 批准用者
assigned_to_self_report: 分配檢舉
change_email_user: 替用者改email
change_role_user: 改用者ê角色
confirm_user: 確認用者
create_account_warning: 建立警告
remove_avatar_user: Thâi掉標頭
actions:
remove_avatar_user_html: "%{name} thâi掉 %{target} ê標頭"

View File

@@ -89,6 +89,37 @@ RSpec.describe ActivityPub::VerifyQuoteService do
end
end
context 'with a valid activity for a post that cannot be fetched but is passed as fetched_quoted_object' do
let(:quoted_status) { nil }
let(:approval_interaction_target) { 'https://b.example.com/unknown-quoted' }
let(:prefetched_object) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Note',
id: 'https://b.example.com/unknown-quoted',
to: 'https://www.w3.org/ns/activitystreams#Public',
attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_account),
content: 'previously unknown post',
}.with_indifferent_access
end
before do
stub_request(:get, 'https://b.example.com/unknown-quoted')
.to_return(status: 404)
end
it 'updates the status' do
expect { subject.call(quote, fetchable_quoted_uri: 'https://b.example.com/unknown-quoted', prefetched_quoted_object: prefetched_object) }
.to change(quote, :state).to('accepted')
expect(a_request(:get, approval_uri))
.to have_been_made.once
expect(quote.reload.quoted_status.content).to eq 'previously unknown post'
end
end
context 'with a valid activity for a post that cannot be fetched but is inlined' do
let(:quoted_status) { nil }
@@ -148,7 +179,7 @@ RSpec.describe ActivityPub::VerifyQuoteService do
context 'with a valid activity for already-fetched posts, with a pre-fetched approval' do
it 'updates the status without fetching the activity' do
expect { subject.call(quote, prefetched_body: Oj.dump(json)) }
expect { subject.call(quote, prefetched_approval: Oj.dump(json)) }
.to change(quote, :state).to('accepted')
expect(a_request(:get, approval_uri))

View File

@@ -5496,8 +5496,8 @@ __metadata:
linkType: hard
"babel-plugin-formatjs@npm:^10.5.37":
version: 10.5.37
resolution: "babel-plugin-formatjs@npm:10.5.37"
version: 10.5.38
resolution: "babel-plugin-formatjs@npm:10.5.38"
dependencies:
"@babel/core": "npm:^7.26.10"
"@babel/helper-plugin-utils": "npm:^7.26.5"
@@ -5510,7 +5510,7 @@ __metadata:
"@types/babel__helper-plugin-utils": "npm:^7.10.3"
"@types/babel__traverse": "npm:^7.20.6"
tslib: "npm:^2.8.0"
checksum: 10c0/e206ff1a8ad3cbcb3db2d2735d8821701df9d54c8aeb5e8b2861c945af91d4662b9cd37b1ff9d7e17954cdd31aec81788a3d044a1cd9f3e7e8e4f93177097b83
checksum: 10c0/5aeb9839ee5be198b82c9ab6c85160e7edcc07f6184efe296560d229d1cc680d9c1e507bde47da74234d612a3e49c919b3e11c855b7fd4e6d256ec23f8b8b1e0
languageName: node
linkType: hard
@@ -18961,8 +18961,8 @@ __metadata:
linkType: hard
"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.18.0":
version: 8.18.1
resolution: "ws@npm:8.18.1"
version: 8.18.2
resolution: "ws@npm:8.18.2"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
@@ -18971,7 +18971,7 @@ __metadata:
optional: true
utf-8-validate:
optional: true
checksum: 10c0/e498965d6938c63058c4310ffb6967f07d4fa06789d3364829028af380d299fe05762961742971c764973dce3d1f6a2633fe8b2d9410c9b52e534b4b882a99fa
checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4
languageName: node
linkType: hard