Merge pull request #487 from kmycode/kb-draft-10.1

Release: 10.1
This commit is contained in:
KMY(雪あすか) 2024-01-25 12:22:17 +09:00 committed by GitHub
commit eac1d8ad5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 495 additions and 160 deletions

6
.bundler-audit.yml Normal file
View file

@ -0,0 +1,6 @@
---
ignore:
# devise-two-factor advisory about brute-forcing TOTP
# We have rate-limits on authentication endpoints in place (including second
# factor verification) since Mastodon v3.2.0
- CVE-2024-0227

View file

@ -1 +1 @@
3.2.2
3.2.3

View file

@ -2,6 +2,65 @@
All notable changes to this project will be documented in this file.
## [4.2.4] - 2024-01-24
### Fixed
- Fix error when processing remote files with unusually long names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28823))
- Fix processing of compacted single-item JSON-LD collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28816))
- Retry 401 errors on replies fetching ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28788))
- Fix `RecordNotUnique` errors in LinkCrawlWorker ([tribela](https://github.com/mastodon/mastodon/pull/28748))
- Fix Mastodon not correctly processing HTTP Signatures with query strings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28443), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28476))
- Fix potential redirection loop of streaming endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28665))
- Fix streaming API redirection ignoring the port of `streaming_api_base_url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28558))
- Fix error when processing link preview with an array as `inLanguage` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28252))
- Fix unsupported time zone or locale preventing sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/28035))
- Fix "Hide these posts from home" list setting not refreshing when switching lists ([brianholley](https://github.com/mastodon/mastodon/pull/27763))
- Fix missing background behind dismissable banner in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/27479))
- Fix line wrapping of language selection button with long locale codes ([gunchleoc](https://github.com/mastodon/mastodon/pull/27100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27127))
- Fix `Undo Announce` activity not being sent to non-follower authors ([MitarashiDango](https://github.com/mastodon/mastodon/pull/18482))
- Fix N+1s because of association preloaders not actually getting called ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28339))
- Fix empty column explainer getting cropped under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28337))
- Fix `LinkCrawlWorker` error when encountering empty OEmbed response ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28268))
- Fix call to inefficient `delete_matched` cache method in domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28367))
### Security
- Add rate-limit of TOTP authentication attempts at controller level ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28801))
## [4.2.3] - 2023-12-05
### Fixed
- Fix dependency on `json-canonicalization` version that has been made unavailable since last release
## [4.2.2] - 2023-12-04
### Changed
- Change dismissed banners to be stored server-side ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27055))
- Change GIF max matrix size error to explicitly mention GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27927))
- Change `Follow` activities delivery to bypass availability check ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/27586))
- Change single-column navigation notice to be displayed outside of the logo container ([renchap](https://github.com/mastodon/mastodon/pull/27462), [renchap](https://github.com/mastodon/mastodon/pull/27476))
- Change Content-Security-Policy to be tighter on media paths ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26889))
- Change post language code to include country code when relevant ([gunchleoc](https://github.com/mastodon/mastodon/pull/27099), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27207))
### Fixed
- Fix upper border radius of onboarding columns ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27890))
- Fix incoming status creation date not being restricted to standard ISO8601 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27655), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28081))
- Fix some posts from threads received out-of-order sometimes not being inserted into timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27653))
- Fix posts from force-sensitized accounts being able to trend ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27620))
- Fix error when trying to delete already-deleted file with OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27569))
- Fix batch attachment deletion when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27554))
- Fix processing LDSigned activities from actors with unknown public keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27474))
- Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27459))
- Fix report processing notice not mentioning the report number when performing a custom action ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27442))
- Fix handling of `inLanguage` attribute in preview card processing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27423))
- Fix own posts being removed from home timeline when unfollowing a used hashtag ([kmycode](https://github.com/mastodon/mastodon/pull/27391))
- Fix some link anchors being recognized as hashtags ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27271), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27584))
- Fix format-dependent redirects being cached regardless of requested format ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27634))
## [4.2.1] - 2023-10-10
### Added

View file

@ -1,11 +1,7 @@
# syntax=docker/dockerfile:1.4
# Please see https://docs.docker.com/engine/reference/builder for information about
# the extended buildx capabilities used in this file.
# Make sure multiarch TARGETPLATFORM is available for interpolation
# See: https://docs.docker.com/build/building/multi-platform/
ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM}
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.3-slim as ruby
FROM node:${NODE_VERSION} as build
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.2"]
ARG RUBY_VERSION="3.2.2"

View file

@ -481,7 +481,8 @@ GEM
timeout
net-smtp (0.4.0)
net-protocol
nio4r (2.5.9)
net-ssh (7.1.0)
nio4r (2.7.0)
nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@ -544,7 +545,7 @@ GEM
psych (5.1.2)
stringio
public_suffix (5.0.4)
puma (6.4.1)
puma (6.4.2)
nio4r (~> 2.0)
pundit (2.3.1)
activesupport (>= 3.0.0)

View file

@ -2,7 +2,7 @@
class Api::V1::StreamingController < Api::BaseController
def index
if Rails.configuration.x.streaming_api_base_url == request.host
if same_host?
not_found
else
redirect_to streaming_api_url, status: 301, allow_other_host: true
@ -11,6 +11,11 @@ class Api::V1::StreamingController < Api::BaseController
private
def same_host?
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
request.host == base_url.host && request.port == (base_url.port || 80)
end
def streaming_api_url
Addressable::URI.parse(request.url).tap do |uri|
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)

View file

@ -1,6 +1,10 @@
# frozen_string_literal: true
class Auth::SessionsController < Devise::SessionsController
include Redisable
MAX_2FA_ATTEMPTS_PER_HOUR = 10
layout 'auth'
skip_before_action :check_self_destruct!
@ -130,9 +134,23 @@ class Auth::SessionsController < Devise::SessionsController
session.delete(:attempt_user_updated_at)
end
def clear_2fa_attempt_from_user(user)
redis.del(second_factor_attempts_key(user))
end
def check_second_factor_rate_limits(user)
attempts, = redis.multi do |multi|
multi.incr(second_factor_attempts_key(user))
multi.expire(second_factor_attempts_key(user), 1.hour)
end
attempts >= MAX_2FA_ATTEMPTS_PER_HOUR
end
def on_authentication_success(user, security_measure)
@on_authentication_success_called = true
clear_2fa_attempt_from_user(user)
clear_attempt_from_session
user.update_sign_in!(new_sign_in: true)
@ -164,4 +182,8 @@ class Auth::SessionsController < Devise::SessionsController
user_agent: request.user_agent
)
end
def second_factor_attempts_key(user)
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
end
end

View file

@ -66,6 +66,11 @@ module Auth::TwoFactorAuthenticationConcern
end
def authenticate_with_two_factor_via_otp(user)
if check_second_factor_rate_limits(user)
flash.now[:alert] = I18n.t('users.rate_limited')
return prompt_for_two_factor(user)
end
if valid_otp_attempt?(user)
on_authentication_success(user, :otp)
else

View file

@ -163,7 +163,7 @@ module JsonLdHelper
end
end
def fetch_resource(uri, id, on_behalf_of = nil)
def fetch_resource(uri, id, on_behalf_of = nil, request_options: {})
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)
@ -172,14 +172,14 @@ module JsonLdHelper
uri = json['id']
end
json = fetch_resource_without_id_validation(uri, on_behalf_of)
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
json.present? && json['id'] == uri ? json : nil
end
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of).perform do |response|
build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
body_to_json(response.body_with_limit) if response.code == 200
@ -212,8 +212,8 @@ module JsonLdHelper
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
end
def build_request(uri, on_behalf_of = nil)
Request.new(:get, uri).tap do |request|
def build_request(uri, on_behalf_of = nil, options: {})
Request.new(:get, uri, **options).tap do |request|
request.on_behalf_of(on_behalf_of) if on_behalf_of
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
end

View file

@ -169,6 +169,7 @@ class PrivacyDropdown extends PureComponent {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
noDirect: PropTypes.bool,
noLimited: PropTypes.bool,
replyToLimited: PropTypes.bool,
container: PropTypes.func,
disabled: PropTypes.bool,
@ -260,6 +261,10 @@ class PrivacyDropdown extends PureComponent {
);
}
if (this.props.noLimited) {
this.options = this.options.filter((opt) => !['mutual', 'circle'].includes(opt.value));
}
this.selectableOptions = [...this.options];
if (!enableLoginPrivacy) {

View file

@ -110,6 +110,7 @@ class BoostModal extends ImmutablePureComponent {
{status.get('visibility') !== 'private' && !status.get('reblogged') && (
<PrivacyDropdown
noDirect
noLimited
value={privacy}
container={this._findContainer}
onChange={this.props.onChangeBoostPrivacy}

View file

@ -33,7 +33,8 @@ class AccountStatusesFilter
available_visibilities -= [:unlisted] if (domain_block&.detect_invalid_subscription || misskey_software?) && @account.user&.setting_reject_unlisted_subscription
available_visibilities -= [:login] if current_account.nil?
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
scope.merge!(scope.where(sensitive: false)) if domain_block&.reject_send_sensitive
scope.merge!(scope.where(searchability: available_searchabilities))
scope.merge!(scope.where(visibility: available_visibilities))
@ -153,9 +154,9 @@ class AccountStatusesFilter
end
def domain_block
return nil if @account.nil? || @account.local?
return nil if @current_account.nil? || @current_account.local?
@domain_block = DomainBlock.find_by(domain: @account.domain)
@domain_block = DomainBlock.find_by(domain: @current_account.domain)
end
def misskey_software?

View file

@ -38,47 +38,39 @@ class StatusReachFinder
private
def reached_account_inboxes
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
end
def reached_account_inboxes_for_misskey
Account.where(id: reached_account_ids, domain: banned_domains_for_misskey - friend_domains).inboxes
end
def reached_account_inboxes_for_friend
Account.where(id: reached_account_ids, domain: friend_domains).inboxes
end
def reached_account_ids
# When the status is a reblog, there are no interactions with it
# directly, we assume all interactions are with the original one
if @status.reblog?
[]
[reblog_of_account_id]
elsif @status.limited_visibility?
Account.where(id: mentioned_account_ids).where.not(domain: banned_domains).inboxes
[mentioned_account_ids]
else
Account.where(id: reached_account_ids).where.not(domain: banned_domains + friend_domains).inboxes
end
end
def reached_account_inboxes_for_misskey
if @status.reblog? || @status.limited_visibility?
[]
else
Account.where(id: reached_account_ids, domain: banned_domains_for_misskey - friend_domains).inboxes
end
end
def reached_account_inboxes_for_friend
if @status.reblog? || @status.limited_visibility?
[]
else
Account.where(id: reached_account_ids, domain: friend_domains).inboxes
end
end
def reached_account_ids
[
replied_to_account_id,
reblog_of_account_id,
mentioned_account_ids,
reblogs_account_ids,
favourites_account_ids,
replies_account_ids,
quoted_account_id,
].tap do |arr|
arr.flatten!
arr.compact!
arr.uniq!
[
replied_to_account_id,
reblog_of_account_id,
mentioned_account_ids,
reblogs_account_ids,
favourites_account_ids,
replies_account_ids,
quoted_account_id,
].tap do |arr|
arr.flatten!
arr.compact!
arr.uniq!
end
end
end

View file

@ -15,13 +15,6 @@ module Account::OtherSettings
false
end
def link_preview?
return user.setting_link_preview if local? && user.present?
return settings['link_preview'] if settings.present? && settings.key?('link_preview')
true
end
def allow_quote?
return user.setting_allow_quote if local? && user.present?
return settings['allow_quote'] if settings.present? && settings.key?('allow_quote')
@ -95,7 +88,6 @@ module Account::OtherSettings
'hide_following_count' => hide_following_count?,
'hide_followers_count' => hide_followers_count?,
'translatable_private' => translatable_private?,
'link_preview' => link_preview?,
'allow_quote' => allow_quote?,
'emoji_reaction_policy' => Setting.enable_emoji_reaction ? emoji_reaction_policy.to_s : 'block',
}

View file

@ -127,6 +127,10 @@ module User::HasSettings
settings['allow_quote']
end
def setting_reject_send_limited_to_suspects
settings['reject_send_limited_to_suspects']
end
def setting_noindex
settings['noindex']
end
@ -135,10 +139,6 @@ module User::HasSettings
settings['translatable_private']
end
def setting_link_preview
settings['link_preview']
end
def setting_dtl_force_visibility
settings['dtl_force_visibility']&.to_sym || :unchange
end

View file

@ -17,17 +17,17 @@ class InstanceInfo < ApplicationRecord
after_commit :reset_cache
EMOJI_REACTION_AVAILABLE_SOFTWARES = %w(
misskey
calckey
cherrypick
meisskey
sharkey
firefish
catodon
renedon
fedibird
pleroma
akkoma
calckey
catodon
cherrypick
fedibird
firefish
iceshrimp
meisskey
misskey
pleroma
sharkey
).freeze
def self.emoji_reaction_available?(domain)

View file

@ -618,7 +618,7 @@ class Status < ApplicationRecord
end
def distributable_friend?
public_visibility? || public_unlisted_visibility? || (unlisted_visibility? && (public_searchability? || public_unlisted_searchability?))
public_visibility? || public_unlisted_visibility? || login_visibility? || (unlisted_visibility? && (public_searchability? || public_unlisted_searchability?))
end
private

View file

@ -12,7 +12,6 @@ class UserSettings
setting :theme, default: -> { ::Setting.theme }
setting :noindex, default: -> { ::Setting.noindex }
setting :translatable_private, default: false
setting :link_preview, default: true
setting :bio_markdown, default: false
setting :discoverable_local, default: false
setting :hide_statuses_count, default: false
@ -42,6 +41,7 @@ class UserSettings
setting :dtl_force_subscribable, default: false
setting :lock_follow_from_bot, default: false
setting :allow_quote, default: true
setting :reject_send_limited_to_suspects, default: false
setting_inverse_alias :indexable, :noindex

View file

@ -23,9 +23,9 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
case collection['type']
when 'Collection', 'CollectionPage'
collection['items']
as_array(collection['items'])
when 'OrderedCollection', 'OrderedCollectionPage'
collection['orderedItems']
as_array(collection['orderedItems'])
end
end

View file

@ -26,9 +26,9 @@ class ActivityPub::FetchRepliesService < BaseService
case collection['type']
when 'Collection', 'CollectionPage'
collection['items']
as_array(collection['items'])
when 'OrderedCollection', 'OrderedCollectionPage'
collection['orderedItems']
as_array(collection['orderedItems'])
end
end
@ -37,7 +37,20 @@ class ActivityPub::FetchRepliesService < BaseService
return unless @allow_synchronous_requests
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
fetch_resource_without_id_validation(collection_or_uri, nil, true)
# 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, true)
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, true, request_options: { with_query_string: true })
end
end
def filtered_replies

View file

@ -59,9 +59,9 @@ class ActivityPub::SynchronizeFollowersService < BaseService
case collection['type']
when 'Collection', 'CollectionPage'
collection['items']
as_array(collection['items'])
when 'OrderedCollection', 'OrderedCollectionPage'
collection['orderedItems']
as_array(collection['orderedItems'])
end
end

View file

@ -7,6 +7,8 @@ module AccountScope
scope_local
when :private
scope_account_local_followers(status.account)
when :limited
scope_status_all_mentioned(status)
else
scope_status_mentioned(status)
end
@ -24,6 +26,10 @@ module AccountScope
Account.local.where(id: status.active_mentions.select(:account_id)).reorder(nil)
end
def scope_status_all_mentioned(status)
Account.local.where(id: status.mentions.select(:account_id)).reorder(nil)
end
# TODO: not work
def scope_list_following_account(account)
account.lists_for_local_distribution.select(:id).reorder(nil)

View file

@ -20,7 +20,7 @@ class FetchLinkCardService < BaseService
@status = status
@original_url = parse_urls
return if @original_url.nil? || @status.with_preview_card? || !@status.account.link_preview?
return if @original_url.nil? || @status.with_preview_card?
@url = @original_url.to_s
@ -88,6 +88,10 @@ class FetchLinkCardService < BaseService
end
def referenced_urls
referenced_urls_raw.filter { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Status, url: true).present? }
end
def referenced_urls_raw
unless @status.local?
document = Nokogiri::HTML(@status.text)
document.search('a[href^="http://"]', 'a[href^="https://"]').each do |link|

View file

@ -69,7 +69,7 @@ class Keys::QueryService < BaseService
return if json['items'].blank?
@devices = json['items'].map do |device|
@devices = as_array(json['items']).map do |device|
Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim'])
end
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e

View file

@ -105,7 +105,10 @@ class ProcessMentionsService < BaseService
def process_mutual!
mentioned_account_ids = @current_mentions.map(&:account_id)
@status.account.mutuals.reorder(nil).find_each do |target_account|
mutuals = @status.account.mutuals
mutuals = mutuals.where.not(domain: InstanceInfo.where(software: 'misskey').select(:domain)).or(mutuals.where(domain: nil)) if @status.account.user&.setting_reject_send_limited_to_suspects
mutuals.reorder(nil).find_each do |target_account|
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
end
end

View file

@ -44,11 +44,7 @@ class ReblogService < BaseService
def create_notification(reblog)
reblogged_status = reblog.reblog
if reblogged_status.account.local?
LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, 'reblog')
elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account)
ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url)
end
LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, 'reblog') if reblogged_status.account.local?
end
def increment_statistics

View file

@ -19,10 +19,16 @@
= ff.input :translatable_private, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_translatable_private')
.fields-group
= ff.input :link_preview, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_link_preview'), hint: I18n.t('simple_form.hints.defaults.setting_link_preview')
.fields-group
= f.input :subscription_policy, kmyblue: true, collection: %w(allow followers_only block), label_method: ->(item) { safe_join([t("simple_form.labels.subscription_policy.#{item}")]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: t('simple_form.labels.defaults.subscription_policy'), hint: t('simple_form.hints.defaults.subscription_policy')
= f.input :subscription_policy,
as: :radio_buttons,
collection: %w(allow followers_only block),
collection_wrapper_tag: 'ul',
hint: t('simple_form.hints.defaults.subscription_policy'),
item_wrapper_tag: 'li',
kmyblue: true,
label: t('simple_form.labels.defaults.subscription_policy'),
label_method: ->(item) { safe_join([t("simple_form.labels.subscription_policy.#{item}")]) },
wrapper: :with_floating_label
.fields-group
= ff.input :allow_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_allow_quote'), hint: false
@ -39,5 +45,8 @@
.fields-group
= ff.input :reject_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_unlisted_subscription'), hint: I18n.t('simple_form.hints.defaults.setting_reject_unlisted_subscription')
.fields-group
= ff.input :reject_send_limited_to_suspects, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_send_limited_to_suspects'), hint: I18n.t('simple_form.hints.defaults.setting_reject_send_limited_to_suspects')
.actions
= f.button :button, t('generic.save_changes'), type: :submit

View file

@ -7,7 +7,7 @@ class LinkCrawlWorker
def perform(status_id)
FetchLinkCardService.new.call(Status.find(status_id))
rescue ActiveRecord::RecordNotFound
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique
true
end
end

View file

@ -2075,6 +2075,7 @@ en:
go_to_sso_account_settings: Go to your identity provider's account settings
invalid_otp_token: Invalid two-factor code
otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
rate_limited: Too many authentication attempts, try again later.
seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
signed_in_as: 'Signed in as:'
verification:

View file

@ -278,11 +278,11 @@ en:
setting_hide_network: Hide your social graph
setting_hide_recent_emojis: Hide recent emojis
setting_hide_statuses_count: Hide statuses count
setting_link_preview: Generate post link preview card
setting_lock_follow_from_bot: Request approval about bot follow
setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app
setting_reduce_motion: Reduce motion in animations
setting_reject_public_unlisted_subscription: Reject sending public unlisted visibility/non-public searchability posts to Misskey, Calckey
setting_reject_send_limited_to_suspects: Reject sending mutual posts to Misskey
setting_reject_unlisted_subscription: Reject sending unlisted visibility/non-public searchability posts to Misskey, Calckey
setting_send_without_domain_blocks: Send your post to all server with administrator set as rejecting-post-server for protect you [DEPRECATED]
setting_show_application: Disclose application used to send posts

View file

@ -79,8 +79,8 @@ ja:
setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、スタンプ機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
setting_enable_emoji_reaction: この機能を無効にしても、他の人はあなたの投稿にスタンプをつけられます
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
setting_link_preview: プレビュー生成を停止することは、センシティブなサイトへのリンクを頻繁に投稿する人にも有効かもしれません
setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
setting_reject_send_limited_to_suspects: これは「相互のみ」投稿に適用されます。サークル投稿は例外なく配送されます。一部のMisskeyサーバーが独自に限定投稿へ対応しましたが、相互のみ投稿を行うたびに相手に通知されるなど複数の問題があるため、気になる人向けの設定です
setting_reject_unlisted_subscription: Misskeyやそのフォークは、フォローしていないアカウントの「非収載」投稿を **購読・検索** することができます。これはkmyblueの挙動と異なります。そのようなサーバーに、指定した公開範囲の投稿を「フォロワーのみ」として配送します。ただし構造上、完璧な対応は困難でたまに非収載として配信されること、ご理解ください
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
setting_single_ref_to_quote: 当サーバーがまだ対象投稿を取り込んでいない場合、引用が相手に正常に認識されない場合があります
@ -287,7 +287,6 @@ ja:
setting_hide_network: 繋がりを隠す
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(絵文字デッキのみを表示する)
setting_hide_statuses_count: 投稿数を隠す
setting_link_preview: リンクのプレビューを生成する
setting_lock_follow_from_bot: botからのフォローを承認制にする
setting_show_quote_in_home: ホーム・リスト・アンテナなどで引用を表示する
setting_show_quote_in_public: 公開タイムライン(ローカル・連合)で引用を表示する
@ -295,6 +294,7 @@ ja:
setting_public_post_to_unlisted: サードパーティから公開範囲「公開」で投稿した場合、「ローカル公開」に変更する
setting_reduce_motion: アニメーションの動きを減らす
setting_reject_public_unlisted_subscription: Misskey系サーバーに「ローカル公開」かつ検索許可「誰でも以外」の投稿を「フォロワーのみ」に変換して配送する
setting_reject_send_limited_to_suspects: Misskey系サーバーに「相互のみ」投稿を配送しない
setting_reject_unlisted_subscription: Misskey系サーバーに「非収載」かつ検索許可「誰でも以外」の投稿を「フォロワーのみ」に変換して配送する
setting_send_without_domain_blocks: 管理人の設定した配送停止設定を拒否する (非推奨)
setting_show_application: 送信したアプリを開示する

View file

@ -56,7 +56,7 @@ services:
web:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.1
image: ghcr.io/mastodon/mastodon:v4.2.4
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -77,7 +77,7 @@ services:
streaming:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.1
image: ghcr.io/mastodon/mastodon:v4.2.4
restart: always
env_file: .env.production
command: node ./streaming
@ -95,7 +95,7 @@ services:
sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.1
image: ghcr.io/mastodon/mastodon:v4.2.4
restart: always
env_file: .env.production
command: bundle exec sidekiq

View file

@ -6,8 +6,8 @@ Install Ruby
EOF
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.2
rbenv global 3.2.2
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.3
rbenv global 3.2.3
cat << EOF

View file

@ -6,8 +6,8 @@ Install Ruby
EOF
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.2
rbenv global 3.2.2
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.2.3
rbenv global 3.2.3
cat << EOF

View file

@ -9,7 +9,7 @@ module Mastodon
end
def kmyblue_minor
0
1
end
def kmyblue_flag

View file

@ -16,7 +16,7 @@ module Paperclip
private
def cache_current_values
@original_filename = filename_from_content_disposition.presence || filename_from_path.presence || 'data'
@original_filename = truncated_filename
@tempfile = copy_to_tempfile(@target)
@content_type = ContentTypeDetector.new(@tempfile.path).detect
@size = File.size(@tempfile)
@ -43,6 +43,13 @@ module Paperclip
source.response.connection.close
end
def truncated_filename
filename = filename_from_content_disposition.presence || filename_from_path.presence || 'data'
extension = File.extname(filename)
basename = File.basename(filename, extension)
[basename[...20], extension[..4]].compact_blank.join
end
def filename_from_content_disposition
disposition = @target.response.headers['content-disposition']
disposition&.match(/filename="([^"]*)"/)&.captures&.first

View file

@ -17,7 +17,7 @@ namespace :db do
task :pre_migration_check do
version = ActiveRecord::Base.connection.select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
abort 'This version of Mastodon requires PostgreSQL 9.5 or newer. Please update PostgreSQL before updating Mastodon' if version < 90_500
abort 'This version of Mastodon requires PostgreSQL 10.0 or newer. Please update PostgreSQL before updating Mastodon' if version < 100_000
end
Rake::Task['db:migrate'].enhance(['db:pre_migration_check'])

View file

@ -5,7 +5,7 @@ require 'rails_helper'
describe Api::V1::StreamingController do
around do |example|
before = Rails.configuration.x.streaming_api_base_url
Rails.configuration.x.streaming_api_base_url = Rails.configuration.x.web_domain
Rails.configuration.x.streaming_api_base_url = "wss://#{Rails.configuration.x.web_domain}"
example.run
Rails.configuration.x.streaming_api_base_url = before
end

View file

@ -262,6 +262,26 @@ RSpec.describe Auth::SessionsController do
end
end
context 'when repeatedly using an invalid TOTP code before using a valid code' do
before do
stub_const('Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR', 2)
end
it 'does not log the user in' do
# Travel to the beginning of an hour to avoid crossing rate-limit buckets
travel_to '2023-12-20T10:00:00Z'
Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR.times do
post :create, params: { user: { otp_attempt: '1234' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
expect(controller.current_user).to be_nil
end
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
expect(controller.current_user).to be_nil
expect(flash[:alert]).to match I18n.t('users.rate_limited')
end
end
context 'when using a valid OTP' do
before do
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }

View file

@ -267,6 +267,80 @@ RSpec.describe AccountStatusesFilter do
it_behaves_like 'filter params'
end
context 'when accessed by a remote account' do
let(:current_account) { Fabricate(:account, uri: 'https://example.com/', domain: 'example.com') }
let!(:sensitive_status_with_cw) { Fabricate(:status, account: account, visibility: :public, spoiler_text: 'CW', sensitive: true) }
let!(:sensitive_status_with_media) do
Fabricate(:status, account: account, visibility: :public, sensitive: true).tap do |status|
Fabricate(:media_attachment, account: account, status: status)
end
end
shared_examples 'as_like_public_visibility' do
it 'returns private statuses, replies, and reblogs' do
expect(results_unique_visibilities).to match_array %w(login unlisted public_unlisted public)
expect(results_in_reply_to_ids).to_not be_empty
expect(results_reblog_of_ids).to_not be_empty
end
context 'when there is a direct status mentioning the non-follower' do
let!(:direct_status) { status_with_mention!(:direct, current_account) }
it 'returns the direct status' do
expect(results_ids).to include(direct_status.id)
end
end
context 'when there is a direct status mentioning other user' do
let!(:direct_status) { status_with_mention!(:direct) }
it 'not returns the direct status' do
expect(results_ids).to_not include(direct_status.id)
end
end
context 'when there is a limited status mentioning the non-follower' do
let!(:limited_status) { status_with_mention!(:limited, current_account) }
it 'returns the limited status' do
expect(results_ids).to include(limited_status.id)
end
end
context 'when there is a limited status mentioning other user' do
let!(:limited_status) { status_with_mention!(:limited) }
it 'not returns the limited status' do
expect(results_ids).to_not include(limited_status.id)
end
end
end
it_behaves_like 'as_like_public_visibility'
it_behaves_like 'filter params'
it 'returns the sensitive status' do
expect(results_ids).to include(sensitive_status_with_cw.id)
expect(results_ids).to include(sensitive_status_with_media.id)
end
context 'when domain-blocked reject_media' do
before do
Fabricate(:domain_block, domain: 'example.com', severity: :noop, reject_send_sensitive: true)
end
it_behaves_like 'as_like_public_visibility'
it_behaves_like 'filter params'
it 'does not return the sensitive status' do
expect(results_ids).to_not include(sensitive_status_with_cw.id)
expect(results_ids).to_not include(sensitive_status_with_media.id)
end
end
end
private
def results_unique_visibilities

View file

@ -139,6 +139,14 @@ RSpec.describe ActivityPub::TagManager do
expect(subject.cc(status)).to include(subject.uri_for(foo))
expect(subject.cc(status)).to_not include(subject.uri_for(alice))
end
it 'returns poster of reblogged post, if reblog' do
bob = Fabricate(:account, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/bob')
alice = Fabricate(:account, username: 'alice')
status = Fabricate(:status, visibility: :public, account: bob)
reblog = Fabricate(:status, visibility: :public, account: alice, reblog: status)
expect(subject.cc(reblog)).to include(subject.uri_for(bob))
end
end
describe '#cc_for_misskey' do

View file

@ -397,13 +397,9 @@ RSpec.describe Account do
describe '#public_settings_for_local' do
subject { account.public_settings_for_local }
let(:account) { Fabricate(:user, settings: { link_preview: false, allow_quote: true, hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account }
let(:account) { Fabricate(:user, settings: { allow_quote: true, hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account }
shared_examples 'some settings' do |permitted, emoji_reaction_policy|
it 'link_preview is disallowed' do
expect(subject['link_preview']).to be permitted.include?(:link_preview)
end
it 'allow_quote is allowed' do
expect(subject['allow_quote']).to be permitted.include?(:allow_quote)
end
@ -423,8 +419,14 @@ RSpec.describe Account do
it_behaves_like 'some settings', %i(allow_quote hide_statuses_count), 'followers_only'
context 'when default true setting is set false' do
let(:account) { Fabricate(:user, settings: { allow_quote: false, hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account }
it_behaves_like 'some settings', %i(hide_statuses_count), 'followers_only'
end
context 'when remote user' do
let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor', settings: { 'link_preview' => false, 'allow_quote' => true, 'hide_statuses_count' => true, 'emoji_reaction_policy' => 'followers_only' }) }
let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor', settings: { 'allow_quote' => true, 'hide_statuses_count' => true, 'emoji_reaction_policy' => 'followers_only' }) }
it_behaves_like 'some settings', %i(allow_quote hide_statuses_count), 'followers_only'
end
@ -432,7 +434,7 @@ RSpec.describe Account do
context 'when remote user by server other_settings is not supported' do
let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor') }
it_behaves_like 'some settings', %i(link_preview allow_quote), 'allow'
it_behaves_like 'some settings', %i(allow_quote), 'allow'
end
end

View file

@ -31,7 +31,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
}
end
let(:status_json_pinned_unknown_unreachable) do
let(:status_json_pinned_unknown_reachable) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Note',
@ -75,7 +75,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known))
stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined))
stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404)
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_unreachable))
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null))
subject.call(actor, note: true, hashtag: false)
@ -115,6 +115,21 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
end
it_behaves_like 'sets pinned posts'
context 'when there is a single item, with the array compacted away' do
let(:items) { 'https://example.com/account/pinned/unknown-reachable' }
before do
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
subject.call(actor, note: true, hashtag: false)
end
it 'sets expected posts as pinned posts' do
expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly(
'https://example.com/account/pinned/unknown-reachable'
)
end
end
end
context 'when the endpoint is a paginated Collection' do
@ -136,6 +151,21 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
end
it_behaves_like 'sets pinned posts'
context 'when there is a single item, with the array compacted away' do
let(:items) { 'https://example.com/account/pinned/unknown-reachable' }
before do
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
subject.call(actor, note: true, hashtag: false)
end
it 'sets expected posts as pinned posts' do
expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly(
'https://example.com/account/pinned/unknown-reachable'
)
end
end
end
end
end

View file

@ -34,6 +34,18 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do
describe '#call' do
context 'when the payload is a Collection with inlined replies' do
context 'when there is a single reply, with the array compacted away' do
let(:items) { 'http://example.com/self-reply-1' }
it 'queues the expected worker' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1'])
end
end
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)

View file

@ -7,6 +7,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:html) { '<!doctype html><title>Hello world</title>' }
let(:oembed_cache) { nil }
let(:custom_before) { false }
before do
stub_request(:get, 'http://example.com/html').to_return(headers: { 'Content-Type' => 'text/html' }, body: html)
@ -30,7 +31,7 @@ RSpec.describe FetchLinkCardService, type: :service do
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
subject.call(status)
subject.call(status) unless custom_before
end
context 'with a local status' do
@ -236,32 +237,53 @@ RSpec.describe FetchLinkCardService, type: :service do
end
end
context 'with URL of reference' do
let(:status) { Fabricate(:status, text: 'RT http://example.com/html') }
it 'creates preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with URL of reference and normal page' do
context 'with URI of reference and normal page' do
let(:status) { Fabricate(:status, text: 'RT http://example.com/text http://example.com/html') }
let(:custom_before) { true }
before { Fabricate(:status, uri: 'http://example.com/text') }
it 'creates preview card' do
subject.call(status)
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'Hello world'
end
end
context 'with URL but author is not allow preview card' do
let(:account) { Fabricate(:user, settings: { link_preview: false }).account }
let(:status) { Fabricate(:status, text: 'http://example.com/html', account: account) }
context 'with URI of reference' do
let(:status) { Fabricate(:status, text: 'RT http://example.com/text') }
let(:custom_before) { true }
it 'not create preview card' do
before { Fabricate(:status, uri: 'http://example.com/text') }
it 'does not create preview card' do
subject.call(status)
expect(status.preview_card).to be_nil
end
end
context 'with URL of reference' do
let(:status) { Fabricate(:status, text: 'RT http://example.com/text') }
let(:custom_before) { true }
before { Fabricate(:status, uri: 'http://example.com/text/activity', url: 'http://example.com/text') }
it 'does not create preview card' do
subject.call(status)
expect(status.preview_card).to be_nil
end
end
context 'with reference normal URL' do
let(:status) { Fabricate(:status, text: 'RT http://example.com/html') }
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'Hello world'
end
end
end
context 'with a remote status' do
@ -282,14 +304,6 @@ RSpec.describe FetchLinkCardService, type: :service do
it 'ignores URLs to hashtags' do
expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made
end
context 'with URL but author is not allow preview card' do
let(:account) { Fabricate(:account, domain: 'example.com', settings: { link_preview: false }) }
it 'not create link preview' do
expect(status.preview_card).to be_nil
end
end
end
context 'with a remote status of reference' do
@ -298,8 +312,12 @@ RSpec.describe FetchLinkCardService, type: :service do
RT <a href="http://example.com/html" target="_blank" rel="noopener noreferrer" title="http://example.com/html">Hello</a>&nbsp;
TEXT
end
let(:custom_before) { true }
before { Fabricate(:status, uri: 'http://example.com/html') }
it 'creates preview card' do
subject.call(status)
expect(status.preview_card).to be_nil
end
end
@ -311,8 +329,12 @@ RSpec.describe FetchLinkCardService, type: :service do
<a href="http://example.com/html_sub" target="_blank" rel="noopener noreferrer" title="http://example.com/html_sub">Hello</a>&nbsp;
TEXT
end
let(:custom_before) { true }
before { Fabricate(:status, uri: 'http://example.com/html') }
it 'creates preview card' do
subject.call(status)
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html_sub'
expect(status.preview_card.title).to eq 'Hello world'

View file

@ -192,21 +192,50 @@ RSpec.describe PostStatusService, type: :service do
expect(mention_service).to have_received(:call).with(status, limited_type: '', circle: nil, save_records: false)
end
it 'mutual visibility' do
account = Fabricate(:account)
mutual_account = Fabricate(:account)
other_account = Fabricate(:account)
text = 'This is an English text.'
context 'with mutual visibility' do
let(:sender) { Fabricate(:user).account }
let(:io_account) { Fabricate(:account, domain: 'misskey.io', uri: 'https://misskey.io/actor', inbox_url: 'https://misskey.io/inbox') }
let(:local_account) { Fabricate(:account) }
let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor', inbox_url: 'https://example.com/inbox') }
let(:follower) { Fabricate(:account) }
let(:followee) { Fabricate(:account) }
mutual_account.follow!(account)
account.follow!(mutual_account)
other_account.follow!(account)
status = subject.call(account, text: text, visibility: 'mutual')
before do
stub_request(:post, 'https://misskey.io/inbox').to_return(status: 200)
stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
Fabricate(:instance_info, domain: 'misskey.io', software: 'misskey')
io_account.follow!(sender)
local_account.follow!(sender)
remote_account.follow!(sender)
follower.follow!(sender)
sender.follow!(io_account)
sender.follow!(local_account)
sender.follow!(remote_account)
sender.follow!(followee)
end
expect(status.visibility).to eq 'limited'
expect(status.limited_scope).to eq 'mutual'
expect(status.mentioned_accounts.count).to eq 1
expect(status.mentioned_accounts.first.id).to eq mutual_account.id
it 'visibility is set' do
status = subject.call(sender, text: 'text', visibility: 'mutual')
expect(status.visibility).to eq 'limited'
expect(status.limited_scope).to eq 'mutual'
end
it 'sent to mutuals' do
status = subject.call(sender, text: 'text', visibility: 'mutual')
expect(status.mentioned_accounts.count).to eq 3
expect(status.mentioned_accounts.pluck(:id)).to contain_exactly(io_account.id, local_account.id, remote_account.id)
end
it 'sent to mutuals without misskey.io users' do
sender.user.update!(settings: { reject_send_limited_to_suspects: true })
status = subject.call(sender, text: 'text', visibility: 'mutual')
expect(status.mentioned_accounts.count).to eq 2
expect(status.mentioned_accounts.pluck(:id)).to contain_exactly(local_account.id, remote_account.id)
end
end
it 'limited visibility and direct searchability' do

View file

@ -86,9 +86,5 @@ RSpec.describe ReblogService, type: :service do
it 'distributes to followers' do
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
end
it 'sends an announce activity to the author' do
expect(a_request(:post, bob.inbox_url)).to have_been_made.once
end
end
end

View file

@ -160,4 +160,22 @@ RSpec.describe RemoveStatusService, type: :service do
)).to have_been_made.once
end
end
context 'when removed status is a reblog of a non-follower' do
let!(:original_status) { Fabricate(:status, account: bill, text: 'Hello ThisIsASecret', visibility: :public) }
let!(:status) { ReblogService.new.call(alice, original_status) }
it 'sends Undo activity to followers' do
subject.call(status)
expect(a_request(:post, bill.shared_inbox_url).with(
body: hash_including({
'type' => 'Undo',
'object' => hash_including({
'type' => 'Announce',
'object' => ActivityPub::TagManager.instance.uri_for(original_status),
}),
})
)).to have_been_made.once
end
end
end