commit
eac1d8ad5b
48 changed files with 495 additions and 160 deletions
6
.bundler-audit.yml
Normal file
6
.bundler-audit.yml
Normal 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
|
|
@ -1 +1 @@
|
|||
3.2.2
|
||||
3.2.3
|
||||
|
|
59
CHANGELOG.md
59
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 送信したアプリを開示する
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def kmyblue_minor
|
||||
0
|
||||
1
|
||||
end
|
||||
|
||||
def kmyblue_flag
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
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>
|
||||
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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue